Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
102 commits
Select commit Hold shift + click to select a range
10380dd
feat(mcpl): type MCPL request failures by what reached the server
Oct 7, 2026
2c9aff1
feat(mcp): legacy engine's protocol boundary, family selection, pagin…
Oct 8, 2026
3a3104c
feat(mcp): ModernMcpConnection — MCP 2026-07-28 over stdio and Stream…
Oct 8, 2026
7f7e91b
fix(mcp): review corrections to batches 1–2, outcomes from wire evide…
Oct 8, 2026
9a27108
fix(mcp): second review round, continuation scope, reaping, malformed…
Oct 8, 2026
f9954a5
feat(mcp): modern servers in the framework, standard result reading, …
Oct 8, 2026
b32cd73
fix(mcp): integration review, connect ownership, result views, select…
Oct 8, 2026
477d993
fix(mcp): an unreapable failed launch halts reconnecting instead of o…
Oct 8, 2026
8b74d24
fix(mcp): concurrent ModernMcpConnection.close() calls share the tear…
Oct 8, 2026
3aeea4c
fix(mcp): a stopping or stopped framework admits no new server connec…
Oct 8, 2026
ee4d424
fix(mcp): keep a cleanup failure in the legacy stopped-while-connecti…
Oct 8, 2026
91d2411
fix(mcp): one owner for every launch and every server id
Oct 8, 2026
7e0a97c
fix(mcp): saved tool results never overwrite each other; pin the scri…
Oct 8, 2026
c35d286
fix(mcp): one reconnect attempt at a time; a disconnect ends a connec…
Oct 8, 2026
df209f2
fix(mcp): a call that ends before the server's final answer is no-res…
Oct 8, 2026
0adf92e
feat(mcp): warn when a server's credential would cross the network un…
Oct 8, 2026
660ed6f
fix(mcp): a protocol-version verdict at startup raises the ops alert
Oct 8, 2026
f56d854
fix(mcp): the tool-result normalizer drops nothing silently, keeps in…
Oct 11, 2026
6d0ab41
fix(mcp): one transport rule for every selector, credentials kept out…
Oct 11, 2026
f1a6e8f
fix(mcp): the modern engine classifies refused sends and HTTP refusal…
Oct 11, 2026
559ecaa
fix(mcp): hold a server's id until its child is confirmed exited, eve…
Oct 11, 2026
5abaf9f
fix(code-execution): a script's structured result gives an MCPL peer'…
Oct 12, 2026
58ea8e4
fix(code-execution): a script is told what its tool functions return,…
Oct 13, 2026
501817f
Merge main (956c6d2) into feat/modern-mcp-client: one shutdown lifecy…
Oct 13, 2026
0baafd0
fix(mcp): the modern engine's teardown follows the MCPL engine's cont…
Oct 13, 2026
2f0382a
fix(mcp): an http(s) url can't carry userinfo, and a script reads a s…
Oct 13, 2026
75391e7
test(mcp): an auth wall on a later round of a call is a no-response, …
Oct 13, 2026
82ebcfd
fix(mcp): a restart refuses a colliding tool prefix before tearing th…
Oct 13, 2026
1b815b9
fix(mcp): one rule for a query name that may itself be a credential; …
Oct 13, 2026
d69add3
fix(mcp): a restart refuses a taken id or a stopping framework before…
Oct 13, 2026
b0a2daf
fix(mcp): redact configured credentials from recorded server text
Oct 13, 2026
11bfe47
test(mcp): initialize server configuration in the observe wire fixture
Oct 13, 2026
d4f8493
perf(mcp): a connection's records build their redaction once
Oct 13, 2026
fa29b6e
fix(mcp): recorded-text redaction covers the token as a url carries i…
Oct 13, 2026
a49e74f
fix(mcp): every record of server text is redacted, a restart refuses …
Oct 13, 2026
8c75a67
fix(mcp): a record of server text keeps the configuration its operati…
Oct 13, 2026
f62c02e
fix(mcp): only a short pair needs its boundary; a 401 or 403 is a ref…
Oct 13, 2026
5dc695f
fix(mcp): the policy request's records use one redactor taken before …
Oct 13, 2026
665ea4e
docs(mcp): a submitted configuration is the caller's to keep unchange…
Oct 13, 2026
a2bd86f
fix(mcp): every name=value pair keeps its boundary; env names in the …
Oct 13, 2026
5d27d07
fix(mcp): only string env values are derived; more credential words; …
Oct 13, 2026
a36f8ea
fix(mcp): a refresh's tool policy and prefix read the live configurat…
Oct 13, 2026
69ce7ec
fix(mcp): the modern tools/list refresh reads each server's configura…
Oct 13, 2026
c4eb1e8
Merge main (197a8491, #263) into feat/modern-mcp-client
Oct 13, 2026
7c76a2e
fix(mcp): a server request handler's failure is recorded with the con…
Oct 13, 2026
4f02562
fix(mcp): a restart holds the id and tool prefix it moves its server to
Oct 14, 2026
9fad7b2
Merge main ab5f1183 into the modern MCP client
Oct 14, 2026
379490b
test(mcp): a restart of an id another restart holds is refused
Oct 14, 2026
aafc2a8
fix(mcp): what main's merge left: a script's reference names its expi…
Oct 14, 2026
a02525a
docs(changelog): a script's expired reference carries expired
Oct 14, 2026
c5763d3
test(baseline): command + URL with no transport is refused, as this b…
Oct 14, 2026
792ef25
fix(mcp): what the full run and the round's readers found
Oct 14, 2026
e773a45
test(baseline): the platform tests' registry stub answers getServer
Oct 14, 2026
350c659
test(mcp): the every-server redactor's one pass spans the configurati…
Oct 14, 2026
7bffdca
fix(mcp): redact a username alone, JSON's escapes, and overlapping cr…
Oct 14, 2026
96c8526
fix(mcp): a disconnect or stop() during a restart ends it, and says t…
Oct 14, 2026
8a2d4f7
test(mcp): a stop() that begins in a restart's gap ends it, and says …
Oct 14, 2026
3168cd4
test(mcp): match the restart's refusal as assert.rejects reads it, wi…
Oct 14, 2026
406164e
fix(mcp): say which disconnect ends a restart; the test server answer…
Oct 14, 2026
0535033
Merge main cd036e0d into the modern MCP client: one stop rule for MCP…
Oct 14, 2026
ae1a568
fix(mcp): stop() and a disconnect end a connect in flight at once, in…
Oct 14, 2026
5a34a2f
fix(mcp): an aborted legacy connect says why as a modern one does; th…
Oct 14, 2026
3f8304e
fix(mcp): stop() ends a connect past its handshake too: its setup and…
Oct 14, 2026
636ddd7
fix(mcp): a disconnect of the id a restart moves to ends the restart,…
Oct 14, 2026
1d47151
fix(mcp): a refresh overtaken by a later one publishes nothing, so a …
Oct 14, 2026
4fa550d
fix(mcp): ModernMcpConnection.connect hands back the owner of a launc…
Oct 14, 2026
0576868
fix(mcp): MCPL policy on a modern server is refused only where its va…
Oct 15, 2026
42eb65c
fix(mcp): a modern child that exits during its connect's list-change …
Oct 15, 2026
74574fe
docs(mcp): listMcplServers' family and transport are null only when n…
Oct 15, 2026
0fbb71e
fix(mcp): a lost modern session is reaped before the reconnect, and c…
Oct 15, 2026
c59efc4
Merge main 91e70eda into the modern MCP client: #344's spawn wait and…
Oct 16, 2026
59f1986
test(mcp): a modern stdio command that can't be spawned fails its con…
Oct 16, 2026
023728d
test(mcp): narrowing MCPL policy on a modern server raises no warning…
Oct 16, 2026
2789a9f
fix(mcp): a modern server accepts MCPL allow-lists, refusing only ena…
Oct 16, 2026
0ab25e5
fix(mcp): close() during a modern stdio connect's spawn closes the ch…
Oct 16, 2026
c7eda6c
docs(mcp): the modern-server refusal list says exactly which values a…
Oct 16, 2026
87d1ae1
fix(mcp): a refresh dropped as older starts one more when it held a l…
Oct 17, 2026
d1e6e16
fix(mcp): a listing begun before its server's session closed publishe…
Oct 17, 2026
6d5962f
fix(mcp): a connect whose server is disconnected while its tools are …
Oct 17, 2026
efa0780
fix(mcp): a connection's credential patterns come from what each dial…
Oct 17, 2026
6c47617
docs(mcp): a call paused at input_required has no cancel, so the serv…
Oct 17, 2026
f98a657
fix(mcp): a connect and a listing count only while their connection s…
Oct 17, 2026
f8a5bf6
docs(mcp): the stdio-only configuration copy says why a dialing confi…
Oct 17, 2026
6bad04c
fix(mcp): a lost modern session's id is held until its reap is confir…
Oct 17, 2026
b965af0
fix(mcp): a server the host has given up on leaves the tool list, and…
Oct 17, 2026
179f66f
fix(mcp): a failed recycle of a server without reconnect takes its to…
Oct 17, 2026
6662cb1
Merge main 86a36834 into feat/modern-mcp-client
Oct 17, 2026
3c043d5
docs(mcp): name the one configuration that worked before and is now r…
Oct 18, 2026
cddfc19
test(mcp): a failed create() closes a modern server it had connected
Oct 18, 2026
5237b6d
test(mcp): a disconnect during a restart's tool listing ends the restart
Oct 18, 2026
5bcf3fd
Merge main e6703634 into feat/modern-mcp-client
Oct 18, 2026
323a32a
fix(mcp): a stdio child whose pipe failed is killed if it ignores SIG…
Oct 18, 2026
b75b6fa
docs(mcp): closeSession never rejects, which the loss path relies on
Oct 18, 2026
bd24be7
fix(mcp): a failed close for good takes the tools off, a held launch'…
Oct 18, 2026
991eb1a
docs(mcp): under Bun a child that closes its stdin isn't reported, so…
Oct 18, 2026
6dfee0e
fix(mcp): a queued manifest refresh logs a failed close, and the text…
Oct 18, 2026
0faeab1
docs(changelog): a connection closed by a pipe failure reconnects onl…
Oct 18, 2026
988fca9
docs(mcp): the stdio pipe failure's texts claim what the tests show, …
Oct 18, 2026
a79a56d
Merge main 124edb46 into feat/modern-mcp-client
Oct 18, 2026
3f343d6
test(mcpl): the merge's loose ends: a startup test waits for initiali…
Oct 18, 2026
fffa857
test(mcp): a modern server's tool prefix meets another server's under…
Oct 18, 2026
b1ce54c
docs(mcp): #506's in-flight words hold during a legacy handshake only
Oct 18, 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
66 changes: 66 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,72 @@ A pattern that matches no tool is reported once, as a console line and an
A pattern that could name a server's tools is not judged until that server
has listed them, so late connects and reconnects don't cause early reports.

#### Modern MCP servers (2026-07-28)

The same `mcplServers` list also takes servers that speak modern MCP
(revision `2026-07-28`), reached through the official SDK client. The
configuration chooses the family, and nothing is probed:

| Configuration | Family | Transport |
|---|---|---|
| `url: 'ws://…'` / `'wss://…'` | MCPL (MCP `2024-11-05`) | WebSocket |
| `url: 'http://…'` / `'https://…'` | modern MCP | Streamable HTTP (`token` / `accessProvider` as bearer) |
| `command` | MCPL | stdio |
| `command` + `protocol: 'modern'` | modern MCP | stdio |

```typescript
{
mcplServers: [
{ id: 'docs', url: 'https://mcp.example.com/mcp', accessProvider: getToken },
{ id: 'search', command: 'search-mcp', protocol: 'modern', reconnect: true },
],
}
```

A configuration with both `command` and `url` needs `transport` to say which
one is used; without it, it is refused rather than silently ignoring one.

A modern server's tools get the same prefix, `enabledTools`/`disabledTools`,
class overrides and dispatch as an MCPL server's, and `listMcplServers()`
shows its `family`, `protocolVersion` and `transport`. Its config differs in
three ways:
- It has no MCPL surface (grant, push, channels, inference requests, feature
sets). A setting that asks for that surface can't apply, so it is a
configuration error: `allowHostCommands: true`, an `enabledCapabilities`
pattern that matches a capability denied by default, `channelSubscription:
'auto'` or a non-empty list, a scope's non-empty `whitelist`, a
`toolLifecycle` `observe` or `inputs` block, and a
`toolLifecycle.maxInputBytes` or an `autofetch` bound above its default.
A setting
that only takes something away, or repeats a default, is already
satisfied, so it is accepted: `enabledFeatureSets` and the rest of
`enabledCapabilities`, which are allow-lists, `disabledFeatureSets`,
`disabledCapabilities`, a scope's `blacklist`, `channelSubscription:
'manual'`, a `toolLifecycle.maxInputBytes` or an `autofetch` bound at or
below its default, and `shouldTriggerInference`.
So a host that puts a deny-list on every server it deploys can deploy a
modern one.
- `requestTimeoutMs` is one deadline per tool call and must be 1 to 2^31−1;
0 is refused, where MCPL reads it as no watchdog.
- Over stdio it takes no credential: `token` or `accessProvider` on one is
refused.

At the deadline, cancellation is requested for a leg still in flight;
between the rounds of an `input_required` continuation nothing is in flight
to cancel. Either way the outcome is reported as unknown (`no-response`),
and the call is never retried. The protocol has no cancel for a paused call,
so whatever the server holds for it, beyond the `requestState` it gave this
client, stays until the server lets it go. A call that ends for any other
reason after the server asked for another round, before its final answer, is
reported the same way, since the server may already have acted. An MCPL
server that refuses `2024-11-05` with `-32022` fails with
`McplProtocolVersionError`, and is not retried. The error names the
revisions the server says it supports ("none listed" for an empty list), or its error text when it sends no list,
and when the list includes `2026-07-28` it says to configure the server with
`protocol: 'modern'` (stdio) or reach it over http(s).

Server and transport text the framework records has the server's configured credentials replaced, in both engines. That covers connection traces, console lines, stderr traces, ops alerts, and a tool call's failure as its caller, the model and the `tool:failed` trace get it. The credentials are those the configuration holds and those its dials sent: from the url as each dial sends it (a WebSocket dial sets the token in the query, which writes the whole query again, form-encoded, and an access provider's token can be in it), credential-like path segments and query names, credential-shaped or credential-named query values, and a WebSocket url's userinfo, in their encoded and decoded forms, replaced wherever they occur; every non-empty query parameter as its whole `name=value`, where no letter or digit adjoins it; the static `token`, and each token an access provider handed a dial, the newest sixteen, raw and url-encoded; and a stdio server's `env` values whose names have a part ending in a credential word, such as `GITHUB_TOKEN`, `PGPASSWORD` or `SESSION_COOKIE`, raw and url-encoded. A stdio server's command-line arguments aren't derived. Standalone strings shorter than eight characters stay unchanged because they may be ordinary words; short query values are still removed with their parameter names. Treat a submitted configuration as immutable while the server uses it: apply changes with `restartMcplServer(id, newConfig)`. Secrets neither the configuration holds nor a dial sent remain outside this filtering. JSON-RPC tool answers, and the error a host's own connect raises, pass through as sent.

#### Feature sets

`enabledFeatureSets` and `disabledFeatureSets` select which of a server's
Expand Down
16 changes: 16 additions & 0 deletions changelog.d/cleartext-mcp-credential-warning.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
- Connecting to an MCP server warns once when a credential would cross the
network unencrypted, to an `http://` or `ws://` url whose host isn't this
machine: its `token` or `accessProvider`, or a secret the url carries
itself (a `ws://` url's userinfo, a query parameter whose name says
credential: `token`, `key`, `secret`, `password`, `auth` or `signature`,
or whose value is shaped like one under a plain name, a query parameter
name that may itself be one, or a path segment that looks like one, as
the redacted url judges it). A name that may itself be
a credential is counted in the warning, never quoted. An `http(s)://` url
with userinfo is refused instead, since the HTTP client can't send it and
its error would quote it. Loopback hosts are
exempt, and so are the unspecified addresses `0.0.0.0` and `[::]`, which
reach this host too. It warns rather than refuses, since a private network
or a TLS-terminating proxy can make that deliberate.
`serverConfigWarnings` is exported beside `serverConfigProblems`, so a host
can apply the same check in its own configuration validation.
37 changes: 37 additions & 0 deletions changelog.d/legacy-mcp-protocol-boundary.breaking.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
- **MCPL/MCP server authors:** the legacy engine now enforces the revision it
speaks. `McplServerConnection` offers MCP `2024-11-05` and now also requires
it back: an `initialize` result with any other `protocolVersion`, or with
none, fails as `McplProtocolVersionError` (`kind: 'mismatch'`), stating what
each side said. Every server family probed answers `2024-11-05`, but a
hand-written server that omits the field must add it. An `initialize`
answered with JSON-RPC `-32022` fails as `McplProtocolVersionError`
(`kind: 'rejected'`), keeping the server's `supported` list, code and data.
When that list names modern MCP, the message says to configure the server
with `protocol: 'modern'`. A protocol-version verdict is never retried: no
reconnect stub, and a reconnecting connection stops its backoff loop
(`reconnect-failed` carries `permanent: true` and raises the ops alert at
once). At startup the verdict raises the same `mcpl-down` ops alert, so a
server refused for its revision doesn't drop out with only a log line. The
established revision is on `connection.protocolVersion`.
- `tools/list` now follows `nextCursor` across pages, so a paginating server's
whole inventory reaches the agent; before, every page after the first was
silently dropped. A repeated cursor, or more than 100 pages, is an error
rather than a partial inventory.
- **Hosts and operators:** a server configuration with both `command` and
`url` and no `transport` is now refused before anything is spawned or
dialed. Before, it was launched as stdio and the url was silently
ignored. Migration: drop the field you don't use, or set `transport` to
say which one is used. Unchanged: a configuration with only one of them,
or with both and a `transport`, resolves as before (a host can still
layer a url and `transport: 'websocket'` over a file's command). A legacy
`requestTimeoutMs` above 2^31−1 is refused too: Node ran it as a timer of
about 1 ms, so every request timed out at once. Every value that worked,
`0` included, passes.
- **Hosts:** `stop()` now rejects, after its whole teardown, when an MCP
server's child couldn't be confirmed exited, naming each such child and
its server. Before, closing a stdio server returned as soon as a signal
was sent, so `stop()` never failed for this. Code that chains work on
`stop()`, such as exiting a TUI or switching sessions, should catch that
rejection: the framework is stopped either way. As with any failed
teardown, an owned store stays open, and a later `stop()` retries the
reap, completing once the child has exited.
Loading
Loading