Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]

### Added
- **MCP client diagnostics and safe disconnects.**
`MCPSocketServer.clientConnections` reports a private connection id, the
kernel-reported peer pid when available, connection time, and last byte
activity — never a command line or path. `onConnectionsChange` publishes the
same snapshots, and `disconnectClient(id:)` closes exactly that socket
without signalling or killing its process.
- **`SourceCursorStore.save(changed:all:)`** — the call the periodic cursor
save makes. `changed` is what actually moved; `all` is the complete set, so
a store that can only replace still has what it needs. The default writes
Expand All @@ -19,6 +25,16 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
makes the claim below assertable rather than timed.

### Changed
- **A desktop-sized MCP client budget, optional idle reclamation, and explicit
refusal.** The default cap is 64 rather than 16 because desktop clients may
keep one stdio bridge per open task; hosts can tune it. Idle expiry is now an
opt-in timeout for hosts that know their clients reconnect after EOF. Closing
a socket makes `MCPStdioBridge` return immediately even when its stdin owner
forgot to close the pipe, while real EOF and transport errors still clean up
immediately. At the configured client cap the
listener now sends an explicit JSON-RPC capacity error before closing, and a
stdio bridge turns that private frame into an actionable stderr message and
exit code 2 instead of exiting 0 with empty stdout.
- **The periodic save writes the cursors that moved, not all of them.** The
save had one bit of state — "something moved" — and answered it by writing
every cursor the coordinator held. One harness appending a transcript line a
Expand Down
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,15 @@ a socket someone is *answering* on is not — the server reports
`.socketOwnedByAnotherInstance` rather than cutting off the agents attached to
the other copy.

The default client cap is 64; pass `maximumConnections:` to tune it. Hosts
whose stdio clients reconnect after EOF may also opt into idle reclamation
with `idleTimeout:`; it is disabled by default because the library cannot
assume every client respawns a deliberately closed child. `clientConnections` and
`onConnectionsChange` expose only a connection id, peer pid, connected time,
and last activity. A host can call `disconnectClient(id:)` to close one socket
safely. A client beyond the configured cap receives a framed JSON-RPC
capacity error instead of a silent close.

`MCPStdioBridge` runs the same binary as a plain stdio MCP server that pumps
bytes to that socket, which is how MCP clients that spawn a command reach a
running GUI app:
Expand All @@ -163,6 +172,10 @@ if bridge.isRequested() { exit(bridge.run()) }
The static functions this wraps (`MCPStdioBridge.isRequested`,
`.socketPath`, `.run`, each taking `config` explicitly) are still there
unchanged, for callers that would rather not hold an instance.
If the listener is full, the bridge writes the configured
`connectionLimitMessage` to stderr and exits with
`MCPStdioBridge.ExitCode.connectionLimit` (2); it does not report a successful
empty session.

`MCPJSON`, `MCPRequest`, `MCPResponse`, `MCPTool`, `MCPResource`, and
`MCPArguments` are here too, so a host writes its tool catalog and its
Expand Down
Loading
Loading