mcp-sitter
v0.2.0
Published
Hot-reload bridge for stdio MCP servers. Rebuild your MCP server without restarting the MCP client. Injects restart notices and tools-list diffs into tool/call responses so the AI self-corrects after schema changes.
Downloads
40
Maintainers
Readme
mcp-sitter
A hot-reload bridge for stdio Model Context Protocol (MCP) servers, so you can rebuild your MCP server during development without restarting the MCP client (e.g. Claude Code, Claude Desktop, Cursor, Cline, etc).
Install mcp-sitter (npm install -g mcp-sitter), then point Claude Code
at it:
claude mcp add --scope user my-baby-dev `
mcp-sitter `
C:\path\to\my-baby-mcp.exe [args...]That's the entire setup. Your tools are now available as mcp__my-baby-dev__*.
Rebuild your server any time — call sitter_kill to unlock the binary, run
your build, and the next tool call lazily respawns the child. No client
restart, no lost conversation context, and the AI sees a [mcp-sitter]
notice with the tools-diff so it self-corrects after schema changes.
Other MCP clients (Claude Desktop, Cursor, Cline, ...) work the same way: point the client at
mcp-sitterand pass your server exe + args. Building from source instead? See Build from source.
Point your MCP client at mcp-sitter instead of your in-development server.
mcp-sitter spawns your server as a child process and proxies all JSON-RPC
messages. When you need to rebuild, call sitter_kill to unlock the binary
(killing all processes running that exe, not just the child). After the
build, your next tools/call lazily respawns the child and, if the tool
set changed, pushes notifications/tools/list_changed to the client.
More importantly, the first tool/call response after a respawn gets a
[mcp-sitter] notice prepended to its content array, telling the AI:
- how many times the server has been restarted,
- how long the startup took,
- what version/build-date the new binary has,
- whether the previous instance crashed,
- and exactly which tools were added / removed / changed.
This makes the AI self-correcting: if you renamed a tool or changed a schema, the AI sees the diff in the same response as any error, and retries with the correct name/shape automatically.
No more "kill the MCP client, rebuild, restart the MCP client, re-open your conversation, re-establish context" loop.
Why
Building a stdio MCP server is frustrating:
- Your MCP client locks the server binary while the server is running.
- To rebuild, you have to kill the running server process, which usually means killing the client (since the client owns it).
- After the build succeeds, you have to restart the client to get the new server loaded.
- You lose your conversation context every time.
mcp-sitter sits between the client and your server and owns the child
process lifecycle independently of the client.
How mcp-sitter is different from other MCP hot-reload tools
Unlike simple stdin: inherit passthrough wrappers:
- Correct handshake replay.
initializeandnotifications/initializedare cached on the very first client handshake and re-sent to the respawned child with internal request ids, so the child is always properly initialized — even with strict MCP server implementations. - Tools-diff awareness.
mcp-sitterissues an internaltools/liston respawn, diffs against the previous list by tool name and schema, and surfaces the result both asnotifications/tools/list_changed(for the client infra's tool cache) and as a[mcp-sitter]content notice on the next tool/call response (for the AI itself). - Content-level notices. MCP clients aren't guaranteed to surface
protocol notifications to the LLM.
mcp-sitterinjects a short text block atcontent[0]of the next tool/call response so the AI always sees that a restart happened and what changed.
Architecture
MCP Client <--stdio--> mcp-sitter <--stdio--> your MCP server
(spawned and owned by the bridge; lazily respawned after kill)See the GitHub README for rendered architecture and sequence diagrams.
Typical dev cycle
- Client sends
tools/call sitter_kill(or you rebuild directly). mcp-sittercloses stdin on the child, waits 300 ms, thenSIGKILLs if needed; it also kills every other process on the machine running the sameMainModule.FileName, so the binary is fully unlocked.- You (or your AI) runs
dotnet build/cargo build/npm run build/ whatever produces the new server binary. - Client sends
tools/call my_tool. The child is down, somcp-sitter:- spawns a new child,
- replays the cached
initialize+notifications/initializedusing an internal request id (the response never leaks to the client), - issues an internal
tools/listand computes a diff vs. the last known tool set, - sends
notifications/tools/list_changedto the client if the diff is non-empty, - forwards the original tool/call to the child,
- receives the response, and
- prepends a
[mcp-sitter]notice to the response'scontentarray with spawn #, startup time, binary version/age, previous exit info, and the tools diff.
The client surfaces the merged response to the AI on the very next turn — inside the same conversation, with no restart.
Example restart notice
A typical notice (one text block, pre-pended to the first tool/call response after a respawn):
[mcp-sitter] server restarted (spawn #3, pid 12345, startup 1.8s).
path: C:\dev\my-server\dist\my-server.exe.
binary v1.2.3+abc123 built 2 min ago. previous: exit 0 after 45s.
tools: +1 added (search_index), 1 schema changed (read_file).Crash-after-respawn:
[mcp-sitter] server restarted (spawn #4, pid 12346, startup 0.9s).
path: C:\dev\my-server\dist\my-server.exe.
binary built 5 min ago. previous: ⚠ crashed (exit -1073741819) after 2.1s.Crash self-diagnosis flow
When a child crashes on startup or mid-session, mcp-sitter turns the usual
"invisible exit code" into something the AI can act on. The child's stderr
(usually where .NET/Node/Python dump unhandled exceptions) is buffered in a
1000-line ring persisted across respawns, and exposed via
sitter_child_stderr. Combined with sitter_status, this lets the AI
diagnose and fix its own server without the developer even noticing:
MCP Client <--stdio--> mcp-sitter <--stdio--> your MCP server
(spawned and owned by the bridge; lazily respawned after kill)See the GitHub README for rendered architecture and sequence diagrams.
Because the stderr ring survives respawns and the delimiter clearly marks
the boundary, the AI sees both the crashed generation's stack trace and
the fresh generation's output in the same response. No developer
intervention, no strace-equivalent setup, no log file plumbing.
Testing
Integration tests are Pester v5 scenarios in
test/McpSitter.Tests.ps1, running against a
minimal stdio MCP server (test/FakeMcp) over the real
JSON-RPC handshake:
mcp-sitter core— tools/list merging, tool forwarding,sitter_kill+ lazy respawn with[mcp-sitter]restart notice injection, post-respawnsitter_statusdeltas.sitter_child_stderr— startup stderr capture, mid-lifecycle lines via afake_logtool,since_spawn=truefilter,since_spawn=falsewith a----- child respawn (gen N, pid P) -----delimiter between generations.
dotnet build -c Debug
dotnet build test/FakeMcp/FakeMcp.csproj -c Debug
Invoke-Pester test/McpSitter.Tests.ps1 -Output DetailedCI runs the same Pester suite on Windows / Ubuntu / macOS matrix
via .github/workflows/ci.yml. Tagged
releases run .github/workflows/release.yml — a three-job flow that
NativeAOT-publishes for each RID (win-x64 / linux-x64 / osx-arm64),
Authenticode-signs the Windows binary via Azure Key Vault on a
Windows runner, and publishes three platform-specific npm
sub-packages (@ytsuda/mcp-sitter-win32-x64, @ytsuda/mcp-sitter-linux-x64,
@ytsuda/mcp-sitter-darwin-arm64) plus the umbrella mcp-sitter
package, all with SLSA provenance. The umbrella resolves the
matching sub-package at runtime via its optionalDependencies.
Built-in tools
mcp-sitter exposes four tools of its own, merged into the child's
tools/list response:
| Tool | What it does |
| --------------------- | ------------ |
| sitter_status | Returns child state, binary path/version, child PID, per-spawn and sitter uptimes, spawn/kill counts (including external / crash deaths), last kill reason/time, last startup duration, previous exit info, and last tools-diff summary. |
| sitter_kill | Kills the child AND every other process running the same executable, so the binary is unlocked for rebuild. The child is lazily respawned on the next tool call. |
| sitter_binary_info | Returns version, build date (mtime), size, and metadata of the child binary. Use this to confirm which build is currently loaded and to detect stale binaries. |
| sitter_child_stderr | Tails the child MCP server's stderr from a bounded ring buffer (~1000 lines, persisted across respawns). Crucial for diagnosing child startup failures and crashes — pair with sitter_status after a non-zero previousExitCode. |
Everything else (tools/list, tools/call for non-built-in tools, ping,
notifications/*, resources/*, prompts/*, etc.) is forwarded
transparently between the client and the child.
Build from source
Requires .NET 9 SDK. Most users should prefer the npm prebuilt binary shown at the top of this README.
git clone https://github.com/yotsuda/mcp-sitter.git
cd mcp-sitter
.\Build.ps1The AOT binary lands at dist/mcp-sitter.exe. Each release runs
dotnet publish for all three platform RIDs (win-x64 / linux-x64 /
osx-arm64) on matrix runners, Authenticode-signs the Windows binary
via Azure Key Vault, and publishes per-platform npm sub-packages with
SLSA provenance; see .github/workflows/release.yml.
CLI
mcp-sitter [options] [--] <child-exe> [child-args...]| Option | Description |
| -------------- | ----------- |
| --cwd <path> | Working directory for the child process. Defaults to mcp-sitter's cwd. |
| --help, -h | Show help. |
Use -- to separate mcp-sitter options from the child command if any
child argument happens to start with --.
How it works
Lazy spawn
The child process is not eagerly restarted after being killed. Instead,
the next incoming tools/call or tools/list from the client triggers
a spawn:
- Spawn the child with the same command line.
- Replay the cached
initializerequest with a fresh internal id (the response is absorbed via the bridge's pending-request table and never leaks to the client) and forward the cachednotifications/initialized. - Issue an internal
tools/listrequest and diff the response by tool name and schema against the previous cache. If diff is non-empty, sendnotifications/tools/list_changedto the client and stash the diff summary for the next restart notice. - Forward the original client request to the child.
- When the child responds to the original request, prepend a
[mcp-sitter]notice toresult.content[0]and forward.
This eliminates all timing issues with proactive reload — no risk of respawning while the build is still writing files.
sitter_kill
Kills all processes running the child executable — not just the one
mcp-sitter spawned. This ensures the binary is fully unlocked for the
build tool. Process matching is by MainModule.FileName comparison
(case-insensitive, full-path).
During downtime
While the child is down, incoming tools/call requests trigger a lazy
respawn (which may take a moment). Incoming tools/list requests are served
from the bridge's cache (merged with the built-in tools) if the spawn fails,
so the AI's tool roster stays stable.
Limitations
- Stateful children lose state across restart. Session ids, subscriptions, in-memory caches, resource watches, etc. are gone after respawn. For pure "tools over data" servers this is a non-issue.
- Only
tools/list_changedis actively surfaced. Changes toresources/*orprompts/*pass through transparently but the bridge does not issue anotifications/resources/list_changedafter respawn on its own (yet). - Platform coverage. v0.2.0 ships
win32-x64,linux-x64, anddarwin-arm64(Apple Silicon) native binaries. Intel Mac is not shipped. The Windows binary is Authenticode-signed by the project's code-signing cert via Azure Key Vault; Linux and macOS binaries ship unsigned (all three are covered by npm SLSA provenance). - One child per bridge instance. The child command is fixed at bridge
startup; to supervise multiple MCP servers, register multiple
mcp-sitterinstances with different names. - Restart notice is only injected when the response has a
result.contentarray (= tool-level response). JSON-RPC error responses (theerrorfield at the envelope level) are forwarded unchanged; the notification vianotifications/tools/list_changedstill reaches the client infra.
Project layout
mcp-sitter/
Program.cs entry + CLI help
Sitter.cs core: stdio pumps, message routing,
child lifecycle, lazy spawn +
handshake replay, restart notice,
tools-diff, stderr ring buffer
SitterTools.cs sitter_status / sitter_kill /
sitter_binary_info / sitter_child_stderr
tool definitions
SitterConfig.cs CLI argument parsing
Log.cs stderr logger
McpSitter.csproj net9.0 console app (AOT-published)
Build.ps1 local dev build
.github/workflows/ci.yml Pester matrix (win/linux/macOS)
.github/workflows/release.yml AOT build → Azure KV sign (Windows)
→ npm publish + GitHub release
test/
McpSitter.Tests.ps1 Pester v5 integration tests
FakeMcp/ minimal stdio MCP server for tests
npm/
package.json umbrella mcp-sitter; optionalDependencies
pin all 3 platform sub-packages
bin/cli.mjs resolves mcp-sitter-<platform>-<arch>
at runtime via require.resolve
platforms/
win32-x64/ package.json @ytsuda/mcp-sitter-win32-x64 (os/cpu pinned);
bin/mcp-sitter.exe populated by CI
linux-x64/ package.json @ytsuda/mcp-sitter-linux-x64; bin/ populated by CI
darwin-arm64/ package.json @ytsuda/mcp-sitter-darwin-arm64; bin/ populated by CIThe .NET namespace and types are McpSitter / Sitter (PascalCase, .NET
convention). The binary and user-facing CLI name are mcp-sitter
(kebab-case, ecosystem convention).
License
MIT — see LICENSE.
