Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
73ffb99
feat: use PowerShell 7 for Windows commands
Yuzu02 Aug 2, 2026
c435228
Declare kill_wait_ms in kill_command schema and drop legacy full outp…
cf-pages Aug 11, 2026
cd2e301
Retain head output per stream and count retention evictions
cf-pages Aug 11, 2026
7dc06f5
Merge pull request #34 from xyTom/fix/sessionless-process-runs
cf-pages Aug 11, 2026
b670dd8
Merge remote-tracking branch 'origin/main' into contrib-windows-pwsh
cf-pages Aug 11, 2026
784404c
Gate PowerShell dynamic syntax instead of scanning for cmdlet names
cf-pages Aug 11, 2026
ffe69b3
Run compliance CI on release and hotfix branches
cf-pages Aug 12, 2026
b79a9d4
Replay duplicate initialize on one persistent STDIO session
cf-pages Aug 12, 2026
db2f655
Return -32601 for unknown methods before the handshake
cf-pages Aug 12, 2026
5589099
Remove the session-scoped default cwd and resolve paths from the work…
cf-pages Aug 12, 2026
90a2340
Share one runtime safely across concurrent clients
cf-pages Aug 12, 2026
20b8a6d
Serve 2026-07-28 stateless requests alongside the legacy handshake
cf-pages Aug 12, 2026
1bc91e2
Shape modern results with resultType, serverInfo, and cache hints
cf-pages Aug 12, 2026
a77dd1d
Remove HTTP sessions and serve every request from one workspace runtime
cf-pages Aug 12, 2026
9abb9de
Validate modern mirror headers and map protocol errors to HTTP statuses
cf-pages Aug 12, 2026
190408b
Rework telemetry for the shared dual-era runtime
cf-pages Aug 12, 2026
43e3b0f
Add the dual-era compliance suite and official SDK smoke
cf-pages Aug 12, 2026
6879cd5
Enable server/discover for stateless clients
cf-pages Aug 12, 2026
bcc2d76
Document the dual-era contract and prepare the 0.3.0 release
cf-pages Aug 12, 2026
a03882d
Harden dual-era validation order, notification silence, and telemetry…
cf-pages Aug 13, 2026
57f12cd
Refresh the compliance evidence for the v0.3 contract
cf-pages Aug 13, 2026
d09e902
Preserve exact line boundaries when applying patches
cf-pages Aug 13, 2026
c98fb8a
Surface error category, retryability, and command recovery hints
cf-pages Aug 13, 2026
72371ab
Prepare the 0.3.0rc1 release candidate
cf-pages Aug 13, 2026
5d6e131
Prepare the 0.3.0 release
cf-pages Aug 13, 2026
38f83b9
Merge pull request #68 from xyTom/release/0.3.0
cf-pages Aug 13, 2026
1f74631
Merge remote-tracking branch 'origin/main' into fix/pr37-fallback
cf-pages Aug 13, 2026
b8d8bb3
Fall back to trusted cmd.exe when PowerShell 7 is unavailable
cf-pages Aug 13, 2026
d697470
Keep the PR branch's workflow triggers verbatim
cf-pages Aug 13, 2026
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
4 changes: 3 additions & 1 deletion .github/workflows/compliance.yml
Original file line number Diff line number Diff line change
Expand Up @@ -123,4 +123,6 @@ jobs:
run: |
call "%VCVARSALL%" x64
where cl.exe
python -m unittest tests.compliance.test_windows_msvc_smoke
python -m unittest ^
tests.compliance.test_windows_msvc_smoke ^
tests.test_windows_pwsh
224 changes: 220 additions & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,15 @@

### Changed

- Windows string commands now prefer a trusted PowerShell 7 launcher and
automatically retain `cmd.exe` compatibility when `pwsh` is unavailable.
The selected shell is visible to agents, and safe mode conservatively gates
dynamic syntax for both interpreters.

## 0.3.0 - 2026-08-13

### Changed

- **Breaking:** command handles are now named `command_id`; `kill_session` is
now `kill_command`; retained output references use
`command:<command_id>:stdout|stderr`. The former command `session_id`,
Expand All @@ -17,10 +26,217 @@
commands. Commands are still bounded by the existing active-count, retained
output, byte, timeout, and TTL limits and are terminated when the workspace
server shuts down.
- `default_cwd` remains scoped to one MCP transport session and may reset after
reconnect. Tool descriptions now direct remote clients to pass explicit
`path`/`workdir` arguments and include concrete examples for patching and
command continuation.
- **Breaking:** `notifications/cancelled` no longer terminates the command the
cancelled request started. The notification is still accepted and, as
before, answered with no response. A command outlives the request that
started it and is shared by every client of the workspace, and the mapping
was keyed by the client's own JSON-RPC id, so two clients that both used
`id: 1` could cancel each other's commands. Terminate a command with
`kill_command`; the reduced cancellation responsiveness is tracked in issue
#48.
- **Breaking:** `get_default_cwd` and `set_default_cwd` are removed and the
default catalog is now 18 tools. A relative `path` always resolves against
the workspace root, so there is no session-scoped working directory to set,
read, or lose on reconnect. Pass a workspace-relative `path`, or
`exec_command`'s `workdir`, to target a subdirectory. The `read_file`
`next_action` continuation now repeats the workspace-relative path it was
given rather than one relative to a session cwd, and `server_info` no longer
reports `default_cwd`.
- Tool descriptions now direct remote clients to pass explicit `path`/`workdir`
arguments and include concrete examples for patching and command
continuation.
- Error text now names the error's category and whether it is retryable, and
tells a model not to repeat a call that cannot succeed. `retryable` and
`category` were only ever in `structuredContent`, which most clients do not
forward to the model, so a permanent failure was indistinguishable from a
transient one.
- `COMMAND_NOT_FOUND` from `write_stdin`, `kill_command`, and `read_output` now
explains that the handle expired or never existed, states the retention
window a finished command's output has, and names `exec_command` as the way
to recover. Retrying a dead handle is the single largest source of failed
`write_stdin` calls.
- `kill_command` now declares `kill_wait_ms` (hard-kill escalation wait,
default 2000 ms) in its input schema; previously the runtime honored it but
schema validation rejected any call that passed it.
- `read_output` no longer accepts the undocumented `command:<id>:full`
reference form, which silently read stdout only. Use the per-stream
`command:<id>:stdout` / `command:<id>:stderr` references.
- Retained command output now keeps the earliest bytes per stream (a frozen
head segment, one eighth of the per-stream budget) in addition to the
rolling tail, so the command echo and first errors survive large outputs.
`read_output` reports `head_retained_bytes` and `evicted_gap_bytes`.
- `server_info` exposes an `output_retention` block naming the per-stream
retention budget (`buffer_bytes_per_stream`, `head_bytes_per_stream`). How
often that budget was actually hit is a runtime-wide measurement rather than
an answer to one client, so the eviction counters (`evict_events`,
`evicted_bytes_total`) and omitted-read counters
(`read_output_omitted_hits`, `poll_omitted_hits`) are reported in the
telemetry `session_end` event instead.
- `exec_command` and `read_output` tool descriptions now direct clients to
redirect very large output to a file and page it with `read_file` /
`search_text`.
- A method this server does not implement now returns `-32601` before the
handshake as well as after it. Such a call previously returned `-32002 Server
not initialized`, which tells a client to handshake and retry a method that
will never exist.
- **Breaking:** HTTP is now stateless. The server no longer issues an
`Mcp-Session-Id`, and a request that returns one from an older server is
served normally instead of being refused with `-32001 Unknown MCP session`.
Every request is answered by the one runtime that owns the workspace, so the
128-session ceiling and its `503`, the session idle expiry, and the
`MCP-Protocol-Version`-must-match-the-session check are all gone.
- **Breaking:** `DELETE /mcp` now returns `405` with `Allow: POST`; there is no
session to terminate. `DELETE` is no longer advertised in the `Allow`,
`Access-Control-Allow-Methods`, or server card `transport.methods` lists.
- **Breaking:** the handshake is no longer an admission gate. `tools/list`,
`tools/call`, and the other implemented methods are served whether or not
the caller sent `initialize` first, and `-32002 Server not initialized` is
never returned. `initialize` is now idempotent: each one negotiates a
version on its own and answers with it, so a repeat that names a different
supported version is answered with that version instead of `-32600 Server is
already initialized with a different protocol version`.
- **Breaking:** `server_info` reports `supported_protocol_versions` (every
version this server speaks, newest first) in place of `protocol_version`
(the one version a session had negotiated), and the server card at
`/.well-known/mcp.json` reports `supportedProtocolVersions` in place of
`protocolVersion`. Neither is a session-scoped value any more.
- Runtime state a shared server exposes to concurrent requests is hardened:
the runtime directory (and the `HOME`, `TMPDIR`, and cache directories under
it) is resolved to the primary or fallback location exactly once, so a later
failure reports `RUNTIME_DIR_UNWRITABLE` instead of moving a running
command's directories, and the non-git diff fallback snapshots its patch
baselines under the patch lock.
- The `dev` extra now installs the official MCP python SDK (`mcp`). The
compliance suite drives this server with it over both transports, which is
the only check in the suite that does not use a client we wrote ourselves.
- Telemetry now measures the process rather than one client's handshake. A
session is activated by the first request or notification that passes
envelope validation — in either era, and before the method runs, so a first
call that fails still reports it — while `ping` never activates one, leaving
an HTTP health probe against an idle server silent. Client identity moves
with the request that carried it: `initialize` emits its own `handshake`
event with the negotiated version and the `clientInfo` it was given, a
`2026-07-28` `tool_error` carries the sanitized `clientInfo` of that
request, and no other event claims to know who is calling.
`consecutive_failures` and the 20-error budget are runtime-wide across every
client, `session_end` adds per-era request counts and a `server/discover`
probe count, and the retained-output counters that `server_info` used to
report travel with it. Each protocol choice a process first serves is also
logged as one line on stderr, telemetry on or off. See
[docs/telemetry.md](docs/telemetry.md).
- **Behavior change:** `initialize` no longer fails with `-32602` when a client
asks for a `protocolVersion` this server does not speak. As the handshake
spec requires, the server now answers with an `InitializeResult` naming the
newest version it does speak (`2025-11-25`); a client that asks for a
supported version still gets that version back. Asking to handshake with
`2026-07-28` downgrades the same way, because that protocol states its
version per request instead of negotiating one.
- **Behavior change:** an HTTP request without an `MCP-Protocol-Version` header
is treated as `2025-11-25`, the newest handshake version this server speaks.
The older spec suggests assuming `2025-03-26`, which this server has never
spoken. The header value travels with the request as context and is available
to the runtime; nothing echoes it, records it, or acts on it, and no method
behaves differently for it.
- The runtime contract is now
[docs/runtime-contract-v0.3.md](docs/runtime-contract-v0.3.md), and
[docs/migration-0.3.md](docs/migration-0.3.md) collects every breaking change
above with what to do about it. The v0.2 contract is kept, frozen, as the
0.2.x record.

### Added

- Support for MCP `2026-07-28`, which serves a request without a handshake.
Such a request states its own protocol version in `params._meta`
(`io.modelcontextprotocol/protocolVersion` and
`io.modelcontextprotocol/clientCapabilities` are required,
`io.modelcontextprotocol/clientInfo` is optional) and may call
`server/discover`, `ping`, `tools/list`, and `tools/call` immediately. A
`_meta` version this server does not speak is answered with `-32022` and the
versions it does
(`data.supported`); a missing or mistyped required `_meta` field is answered
with `-32602`. Requests without that `_meta` key — including legacy requests
that carry `_meta.progressToken`, and every `initialize` — keep the
handshake behavior they had.
- `server/discover` answers the probe a `2026-07-28` client sends instead of a
handshake, so such a client never has to send one: it reports the versions
this server speaks per request (`["2026-07-28"]` alone, since naming a
handshake-era version here would invite the client to put one in its
`_meta`, where it is unsupported), the `tools` capability, and the same
workspace instructions `initialize` returns. Those instructions quote the
workspace's own instruction files, so the result carries `ttlMs: 0` and
`cacheScope: "private"` as `tools/list` does. A probe that states no
protocol version in `_meta` is a handshake-era request and is still answered
with `-32601`, which is what sends such a client to `initialize`.
- Streamable HTTP serves `2026-07-28` as well, with the mirror headers
SEP-2243 requires. Such a request must repeat its `_meta` protocol version
in `MCP-Protocol-Version` and its method in `Mcp-Method`; `tools/call`,
`resources/read`, and `prompts/get` must also repeat their subject
(`params.name`, or `params.uri` for `resources/read`) in `Mcp-Name`, either
literally or wrapped as `=?base64?<payload>?=`. Any header that contradicts
the body — including a `2026-07-28` version header on a request whose body
is a handshake-era one — is answered with `400` and the new `-32020`.
Handshake-era requests are not asked for these headers and are unaffected.
- A `2026-07-28` request that fails now reports it in the HTTP status as well:
`-32601` is `404`, and `-32602`, `-32020`, and `-32022` are `400`. Any other
code, `-32603` included, stays `200` with the JSON-RPC error, which is also
what every handshake-era error keeps returning.
- `MCP-Protocol-Version` accepts any version this server speaks. A header
naming an unknown version is still refused with `400` and `-32600`, and
`data.supported` now lists both eras.
- CORS preflight allows `Mcp-Method` and `Mcp-Name`, and no longer allows
`Mcp-Session-Id`.
- Results for `2026-07-28` requests carry `resultType: "complete"` and an
`_meta.io.modelcontextprotocol/serverInfo`; `tools/list` and
`server/discover` also carry the conservative cache hints `ttlMs: 0` and
`cacheScope: "private"` on the result root. Responses to handshake clients
are byte-for-byte what they were and never carry these fields.

### Fixed

- A repeated `initialize` on one persistent STDIO process is answered instead
of failing with `-32600 Server is already initialized`. Connectors that probe
for a newer protocol, fall back to the handshake, and then send `initialize`
again on the same process could not finish a tool scan at all (issue #39).
This shipped first in 0.2.3, which replayed the negotiated result; here there
is no handshake state to replay, so each `initialize` simply negotiates and
answers on its own, and a repeat naming a different supported version is
answered with that version rather than rejected.
- Two clients patching the same file no longer lose an update. Each HTTP
session used to own a runtime with its own patch lock while the files they
wrote were shared, so a second patch could validate against a baseline it had
read before the first one committed and overwrite it silently. One runtime
now owns the workspace, so its lock covers every client: the later patch is
answered with a retryable conflict.
- `apply_patch` no longer silently rewrites lines it was not asked to touch. It
split both the file and the patch with `str.splitlines()`, which breaks on
`\x0b`, `\x0c`, `\x1c`, `\x1d`, `\x1e`, `\x85`, `\u2028`, and `\u2029` as well
as on `\n`, and then rejoined with `\n`. Any file containing one of those
characters had it replaced by a newline by any patch, including a patch that
changed an unrelated line, and a context line containing one could never
match.
- The number of newlines at the end of a file is now whatever the hunk says it
is. The trailing newline was captured from the file before applying and put
back unconditionally, so a hunk that added a final blank line had it removed
again and a hunk that removed the final newline had it restored. A file's
last line is now addressable like any other.
- A context line that is empty is accepted as the empty context line it stands
for, instead of failing the patch with `Invalid empty patch line`. V4A writes
such a line as a single space, and model output and intermediate layers
routinely strip that trailing space. Patch text ending in more than one
newline is likewise accepted.

## 0.2.3 - 2026-08-12

### Fixed

- A repeated `initialize` on one persistent STDIO session now replays the
negotiated handshake result instead of failing with `-32600 Server is
already initialized`. The initializer does not run again, so no session
state is reset and no second telemetry session is recorded. Connectors that
send `initialize` twice on the same process — the OpenAI Secure MCP Tunnel
probes with `server/discover` and then initializes twice — had their tool
scan aborted, surfacing as HTTP 424, even though the session was healthy
(issue #39).

## 0.2.2 - 2026-07-28

Expand Down
11 changes: 7 additions & 4 deletions COMPLIANCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ The one-command acceptance gate is:
make compliance
```

It runs protocol, golden tool, security, E2E, runtime-semantics, dogfood, compliance-report, required docs/evidence/workflow, and schema-drift checks. Report files:
It runs protocol, dual-era, golden tool, security, E2E, runtime-semantics, dogfood, compliance-report, required docs/evidence/workflow, and schema-drift checks. Report files:

- [reports/compliance/latest.json](reports/compliance/latest.json)
- [reports/compliance/latest.md](reports/compliance/latest.md)
Expand All @@ -23,17 +23,20 @@ It adds lint, typecheck, unittest discovery, required docs checks, schema-drift

## Coverage

- MCP initialize, `tools/list`, `tools/call`, schemas, annotations, structured success/failure output, unknown tool behavior, protocol errors, trace redaction, and stdout cleanliness.
- Protocol versions: full support for MCP `2026-07-28` — every implemented method, its `params._meta` validation, the SEP-2243 mirror headers, `server/discover`, result shaping, and the HTTP status each error maps to — alongside the handshake era `2025-11-25` and `2025-06-18`. `tools` with `listChanged: false` is the only advertised capability in either era.
- MCP `initialize`, `tools/list`, `tools/call`, schemas, annotations, structured success/failure output, unknown tool behavior, protocol errors, trace redaction, and stdout cleanliness.
- Interoperability with a client we did not write: the official MCP python SDK drives the server over stdio and HTTP, and CI fails rather than skips if it is not installed.
- Tool golden cases for read/list/search/patch/exec/stdin/kill/git status/git diff/image.
- Security cases for traversal, absolute paths, symlink escape, command workdir escape, direct and interpreter-mediated outside reads, direct syscall outside reads and writes where Landlock is available, destructive command policy, shell-expansion gating, obfuscated network access, risky env rejection, Linux Landlock fallback warnings, session timeout enforcement, watchdog cleanup, bounded output buffers, request-permission non-grants, and concurrent read-only calls.
- Deterministic E2E loops for JavaScript bugfix, Python function add, long-running stdin, session close behavior, workspace escape denial, and image viewing.
- Security cases for traversal, absolute paths, symlink escape, command workdir escape, direct and interpreter-mediated outside reads, direct syscall outside reads and writes where Landlock is available, destructive command policy, shell-expansion gating, obfuscated network access, risky env rejection, Linux Landlock fallback warnings, command timeout enforcement, watchdog cleanup, bounded output buffers, request-permission non-grants, and concurrent read-only calls.
- Deterministic E2E loops for JavaScript bugfix, Python function add, long-running stdin, command termination, workspace escape denial, and image viewing.
- MCP-only dogfood without direct filesystem or shell bypass during task execution.
- Compliance report generation semantics, including non-overclaiming partial-suite tool coverage.

## Individual Gates

```bash
make test-mcp-contract
make test-dual-era
make test-tool-golden
make test-security
make test-e2e
Expand Down
Loading
Loading