execkit MCP server - stateful, structured, safe shell sessions for AI agents over local shells, SSH, and Docker.
Project description
execkit-mcp
An MCP server (stdio) that exposes
execkit shell sessions to any MCP-capable agent - Claude Code,
Cursor, Gemini CLI, and others.
Tools
| Tool | Args | Returns |
|---|---|---|
session_create |
transport ("local"/"ssh"/"docker"); for ssh: host, user, password or key_path, optional port, fingerprint (pin host key); for docker: container (running name/id); optional allow/deny command lists |
{ "session_id": "..." } |
session_exec |
session_id, command, optional budget (shape output: grep/tail/head/head_tail + max_chars) |
structured ExecResult JSON: stdout, stderr (split!), exit_code, duration_ms, cwd, truncated |
session_destroy |
session_id |
{ "destroyed": true } |
session_checkpoint |
session_id, optional label |
{ "checkpoint_id": "..." } |
session_checkpoints |
session_id |
[{ id, label, created }] |
session_restore |
session_id, optional checkpoint_id |
{ restored_to, files_changed } |
Sessions are stateful - cd/env persist across session_exec calls. Output
is ANSI-stripped, secret-redacted, and bounded; commands pass the optional policy
fence before running.
Checkpoints (remote only)
On SSH/Docker sessions execkit can snapshot the workspace before changing commands and restore it on demand - a filesystem "undo." It undoes FILES only, never side effects (DB writes, network, installs).
Two requirements: git on the remote host, and an explicit workspace.
Without a workspace, checkpoints and auto-snapshot are disabled - execkit will
not default to the cwd or home directory (snapshotting $HOME is slow and
would capture secrets). Set workspace to the project dir you want undo for (use
$HOME explicitly if you really mean it).
Control it via session_create:
workspace(root - REQUIRED to enable checkpoints)auto_snapshot(default true; effective only with a workspace)paths(sub-dirs under the root)checkpoint_ignores(extra gitignore-style patterns; added to the built-in defaults:.git,node_modules, build dirs, caches,.ssh,.aws, ...)
If git is absent, auto-snapshot disables itself and checkpoint calls return a clear "install git on the remote" error.
WARNING: session_restore is destructive. It reverts tracked files AND deletes ALL
untracked files and directories anywhere under the workspace (via git clean), not
only files created since the checkpoint. Do not restore if untracked files in the
workspace must be preserved.
Output budgets
Pass budget to session_exec (or output_budget to session_create for a
session default) to shape output and protect the agent's context window:
// keep the last 200 lines of a noisy build
{ "session_id": "...", "command": "npm run build",
"budget": { "keep": { "mode": "tail", "n": 200 } } }
// grep a 50k-line log for errors, with 2 lines of context
{ "session_id": "...", "command": "cat big.log",
"budget": { "grep": { "pattern": "error|fail", "context": 2 } } }
Shaping is line-based, client-side, and runs AFTER secret redaction; it never
changes the exit code or side effects. When applied, the result includes a
budget report: per-stream mode, lines_total, lines_kept.
Install
pip install execkit-mcp # the server binary, shipped as a wheel (no Rust toolchain)
cargo install execkit-mcp # ...or via cargo (or build from source: cargo build -p execkit-mcp)
Then check the install and your environment:
execkit-mcp --version
execkit-mcp doctor # reports audit, SSH, and Docker readiness
Wire it into an agent
execkit-mcp is a stdio MCP server - register the installed binary with your
client. The fastest way is to let it print the config with the right binary path
already filled in:
execkit-mcp setup claude # or: cursor | gemini
(cargo install puts the binary at ~/.cargo/bin/execkit-mcp; use the full path
if it isn't on the client's PATH.)
Claude Code - one command:
claude mcp add execkit -- execkit-mcp # add `-s user` to enable it everywhere
Cursor (~/.cursor/mcp.json) and Gemini CLI (~/.gemini/settings.json) -
add the same block:
{
"mcpServers": {
"execkit": { "command": "execkit-mcp" }
}
}
To turn on auditing or other operator settings, add an env block:
{
"mcpServers": {
"execkit": {
"command": "execkit-mcp",
"env": { "EXECKIT_MCP_AUDIT": "/var/log/execkit.jsonl" }
}
}
}
Then the agent can call session_create -> session_exec -> session_destroy.
Example session (what the agent sees)
// session_create {"transport":"local"} -> {"session_id":"1_local"}
// session_exec {"session_id":"1_local","command":"npm run build"}
// -> {"stdout":"...","stderr":"Error: Cannot find module 'webpack'",
// "exit_code":1,"duration_ms":3420,"cwd":"/home/u/app","truncated":false}
Security model
The agent driving these tools can be prompt-injected, so tool arguments are untrusted. Anything dangerous to the host/filesystem is therefore controlled by the operator at startup (env vars), not by per-call agent arguments:
| Env var | Purpose | Default |
|---|---|---|
EXECKIT_MCP_AUDIT |
append a JSONL audit log of every command here | off |
EXECKIT_MCP_AUDIT_DIR |
write one JSONL file per session into this directory (<session_id>-<open_ms>.jsonl); takes precedence over EXECKIT_MCP_AUDIT when both are set |
off |
EXECKIT_MCP_AUDIT_RETENTION_DAYS |
delete per-session log files older than this many days at startup (dir mode only); 0 disables |
14 |
EXECKIT_MCP_KEY_DIR |
SSH key_path must canonicalize to inside this dir |
~/.ssh |
EXECKIT_MCP_KNOWN_HOSTS |
SSH host-key verification file (TOFU; rejects changed keys) | ~/.ssh/known_hosts |
EXECKIT_MCP_INSECURE_ACCEPT_ANY_HOSTKEY |
DANGEROUS - disable host-key checks | unset |
EXECKIT_MCP_MAX_SESSIONS |
soft cap on concurrent live sessions | 64 |
EXECKIT_MCP_SESSION_TTL |
reap sessions idle longer than this many seconds (frees the process + cap slot); 0 disables |
1800 (30 min) |
- Host keys are verified by default (TOFU against known_hosts; a changed key
is rejected as a likely MITM). Pass a
fingerprintto require an exact key, or set the insecure env var only for throwaway/test hosts. key_pathis sandboxed toEXECKIT_MCP_KEY_DIR; out-of-bounds/traversal paths are rejected with a generic error (no path-existence leak).- Audit destination is operator-chosen, never a tool argument (prevents an injected agent from writing to arbitrary files).
- Docker sessions run
docker execagainst any container the daemon can see, so the agent reaches whatever yourdockercontext exposes. Grant the server Docker access only when you want that, and scope the daemon/context accordingly. - The server speaks MCP on stdout; all diagnostics go to stderr.
- Use
allow/denyfor a command fence, and run the agent + SSH user with least privilege. The fence is advisory - defense in depth, not a sandbox.
Watch live activity (read-only)
Point EXECKIT_MCP_AUDIT at a file, then watch it from another terminal:
execkit-mcp watch /var/log/execkit.jsonl # or just: execkit-mcp watch (uses $EXECKIT_MCP_AUDIT)
watch also accepts a directory - it tails every *.jsonl file in it and picks
up new session files as they appear:
execkit-mcp watch /var/log/execkit/ # or: execkit-mcp watch (uses $EXECKIT_MCP_AUDIT_DIR)
watch is a live, read-only TUI: the agent's sessions on the left, the selected
session's shell transcript on the right (prompt, command, stdout, stderr in red,
exit status) - rendered like a normal shell, not JSON. Switch sessions with 1-9
or the arrow keys, scroll with PgUp/PgDn, quit with q. It only ever reads the
log and never touches a session. Because the data comes from the server (not the
client), it works the same under any MCP client (Claude Code, Cursor, Gemini, ...).
For a headless or background view - no terminal required, pipeable - use
--follow instead of the TUI. It prints each command and its output as a line,
prefixed with the session id, as it happens:
execkit-mcp watch --follow /var/log/execkit/
# [1_local] /home/u $ npm run build
# [1_local] x exit 1 (3420ms)
# [2_ssh_deploy@web-01] /srv $ systemctl restart app
Session ids are self-identifying - <n>_local, <n>_ssh_<user>@<host>[:port],
or <n>_docker_<container> - so the audit filenames and the stream read clearly
at a glance.
In the agent's client (no terminal, no audit log)
The server also pushes each command to your MCP client as it runs, as standard
MCP notifications - so a host agent can show its own shell activity live without
anyone opening a watch terminal. Every session_exec emits:
- a log notification (
notifications/message) carrying the full shell transcript -infoon success,warningon a non-zero exit; and - a progress notification (
notifications/progress) with a one-line summary, when the client supplied aprogressTokenfor the call.
This needs no EXECKIT_MCP_AUDIT* setup - the server advertises the logging
capability and streams unconditionally. It reveals nothing new: the client
already receives the same stdout/stderr in the tool result, redacted and bounded.
How (or whether) the activity is surfaced is up to the client.
Apache-2.0.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distributions
Built Distributions
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file execkit_mcp-0.7.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.
File metadata
- Download URL: execkit_mcp-0.7.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
- Upload date:
- Size: 3.9 MB
- Tags: Python 3, manylinux: glibc 2.17+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6801fd431b7c4c5223894b9cc2215395f93e47e754836ebe6e7f6f62ea6d5a47
|
|
| MD5 |
eaa670100ba5eb20d954f02be7cda965
|
|
| BLAKE2b-256 |
613db99194730f45c10bdac170e7e363f4ec9cf4e4bac834fb78e1391063afa5
|
Provenance
The following attestation bundles were made for execkit_mcp-0.7.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl:
Publisher:
wheels.yml on blinkingbit-oss/execkit
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
execkit_mcp-0.7.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl -
Subject digest:
6801fd431b7c4c5223894b9cc2215395f93e47e754836ebe6e7f6f62ea6d5a47 - Sigstore transparency entry: 1871791505
- Sigstore integration time:
-
Permalink:
blinkingbit-oss/execkit@207baf8360cb9f9e1e705881132997744da79713 -
Branch / Tag:
refs/tags/v0.7.2 - Owner: https://github.com/blinkingbit-oss
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
wheels.yml@207baf8360cb9f9e1e705881132997744da79713 -
Trigger Event:
push
-
Statement type:
File details
Details for the file execkit_mcp-0.7.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.
File metadata
- Download URL: execkit_mcp-0.7.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
- Upload date:
- Size: 3.3 MB
- Tags: Python 3, manylinux: glibc 2.17+ ARM64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5506824943ee3d3d78575d1e9f352505a1a8a435ffdfc456499d430afecaaaa7
|
|
| MD5 |
44abe360a3aeaa786a9ee4ba0ae1dfdd
|
|
| BLAKE2b-256 |
7ba8f8aa01b1b86c48b44d3ee65c134674bf4837e10b3f251294a799c8261640
|
Provenance
The following attestation bundles were made for execkit_mcp-0.7.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl:
Publisher:
wheels.yml on blinkingbit-oss/execkit
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
execkit_mcp-0.7.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl -
Subject digest:
5506824943ee3d3d78575d1e9f352505a1a8a435ffdfc456499d430afecaaaa7 - Sigstore transparency entry: 1871791239
- Sigstore integration time:
-
Permalink:
blinkingbit-oss/execkit@207baf8360cb9f9e1e705881132997744da79713 -
Branch / Tag:
refs/tags/v0.7.2 - Owner: https://github.com/blinkingbit-oss
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
wheels.yml@207baf8360cb9f9e1e705881132997744da79713 -
Trigger Event:
push
-
Statement type:
File details
Details for the file execkit_mcp-0.7.2-py3-none-macosx_11_0_arm64.whl.
File metadata
- Download URL: execkit_mcp-0.7.2-py3-none-macosx_11_0_arm64.whl
- Upload date:
- Size: 3.6 MB
- Tags: Python 3, macOS 11.0+ ARM64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9004d8f0015967ec1d9c69b1417ac73f3ee6535c463cf9ee5ad8a8672ed70bcb
|
|
| MD5 |
a993a4b48494cb92255d17cf77f0786f
|
|
| BLAKE2b-256 |
366d749530a8bc6445cd486b8649d3f0798a8742d397f390c9ed0df8b3f71b90
|
Provenance
The following attestation bundles were made for execkit_mcp-0.7.2-py3-none-macosx_11_0_arm64.whl:
Publisher:
wheels.yml on blinkingbit-oss/execkit
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
execkit_mcp-0.7.2-py3-none-macosx_11_0_arm64.whl -
Subject digest:
9004d8f0015967ec1d9c69b1417ac73f3ee6535c463cf9ee5ad8a8672ed70bcb - Sigstore transparency entry: 1871791424
- Sigstore integration time:
-
Permalink:
blinkingbit-oss/execkit@207baf8360cb9f9e1e705881132997744da79713 -
Branch / Tag:
refs/tags/v0.7.2 - Owner: https://github.com/blinkingbit-oss
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
wheels.yml@207baf8360cb9f9e1e705881132997744da79713 -
Trigger Event:
push
-
Statement type:
File details
Details for the file execkit_mcp-0.7.2-py3-none-macosx_10_12_x86_64.whl.
File metadata
- Download URL: execkit_mcp-0.7.2-py3-none-macosx_10_12_x86_64.whl
- Upload date:
- Size: 3.8 MB
- Tags: Python 3, macOS 10.12+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4baa899139f46839d9a6a97381ea3614983091fd5b9875dc6f41ad8caeb2ea0a
|
|
| MD5 |
b3ccd585ed2b64be31a76781a4549a3e
|
|
| BLAKE2b-256 |
1d798484e2739d538cbe45542925ebb3db66c0846c4523a19556747f1b31d9c9
|
Provenance
The following attestation bundles were made for execkit_mcp-0.7.2-py3-none-macosx_10_12_x86_64.whl:
Publisher:
wheels.yml on blinkingbit-oss/execkit
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
execkit_mcp-0.7.2-py3-none-macosx_10_12_x86_64.whl -
Subject digest:
4baa899139f46839d9a6a97381ea3614983091fd5b9875dc6f41ad8caeb2ea0a - Sigstore transparency entry: 1871791328
- Sigstore integration time:
-
Permalink:
blinkingbit-oss/execkit@207baf8360cb9f9e1e705881132997744da79713 -
Branch / Tag:
refs/tags/v0.7.2 - Owner: https://github.com/blinkingbit-oss
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
wheels.yml@207baf8360cb9f9e1e705881132997744da79713 -
Trigger Event:
push
-
Statement type: