Skip to content

Added MCP 2026-07-28 support - #316

Merged
jancurn merged 23 commits into
mainfrom
draft
Jul 31, 2026
Merged

jancurn merged 23 commits into
mainfrom
draft

Conversation

@jancurn

@jancurn jancurn commented Jul 23, 2026 •

Copy link
Copy Markdown
Member

jancurn and others added 9 commits July 22, 2026 14:51
)

Adds support for MCP protocol version 2026-07-28 ([release
candidate](https://blog.modelcontextprotocol.io/posts/2026-07-28-release-candidate/))
by migrating the client stack to the TypeScript SDK v2 beta
(`@modelcontextprotocol/client`). mcpc now probes each server with
`server/discover` and talks the new stateless protocol when supported,
falling back to 2025-11-25 automatically on the same connection.

- Resource subscriptions and list-change notifications use
`subscriptions/listen` streams on 2026-07-28 connections (opened only
for capabilities the server declares, re-established with backoff on
drops); legacy connections keep `resources/subscribe` and unsolicited
notifications
- `ping` maps to `server/discover` on 2026-07-28 servers;
`logging-set-level` errors there (both were removed from the protocol)
- Task commands are reimplemented on raw 2025-11-25 requests (the v2 SDK
dropped the experimental tasks API); they report the tasks extension as
not yet supported on 2026-07-28 servers
- OAuth ported to the v2 auth API, including RFC 9207 `iss` validation
of authorization callbacks
- The `--proxy` MCP server and e2e test server stay on SDK v1 (still
serving 2025-11-25) until the v2 server packages are adopted — planned
follow-up

E2E suite passes at parity with `main`.

## Follow-ups (separate PRs)

- **E2E protocol-version matrix**: add a second test server built on
`@modelcontextprotocol/server@beta` (mirroring the current server's
surface, ideally with a v2-only/`legacy: reject` mode to prove pure
2026-07-28 operation), parameterize the e2e framework with a
`--server-protocol` flag, and run the protocol-agnostic suites against
both servers in CI plus era-specific suites for fallback (v1) and
listen-stream behavior incl. re-listen and resource file-sync (v2). Each
future MCP revision then adds a matrix column instead of a rewrite,
guarding backward compatibility as versions accumulate. The current
suite covers the legacy-fallback path only; the modern path was verified
manually against a real v2 server
([review](#314 (comment))).
- **Tasks extension**: stop advertising the v1-shaped `tasks: {list,
cancel}` client capability on 2026-07-28 connections — capabilities are
declared before version negotiation, so the clean fix is moving tasks to
the negotiated `extensions` map once the SDK ships the
`io.modelcontextprotocol/tasks` extension.
- **v2 server packages**: migrate the `--proxy` MCP server (and the e2e
servers above) off SDK v1.

https://claude.ai/code/session_013kwM8qhutYS3ZmQATcaQ9C

---------

Co-authored-by: Claude <noreply@anthropic.com>
Adds the protocol-version test matrix from PR #314's follow-ups: the e2e
suites now also run against a second test server speaking pure MCP
2026-07-28 (legacy requests rejected), so both protocol eras are covered
in CI. The matrix immediately caught a real interop bug — SDK beta.5
servers require the new `Mcp-Method` header, which the beta.4 client
never sent, so mcpc silently fell back to the 2025 handshake and failed
against modern-only servers — fixed by updating the MCP SDK v2 to
`2.0.0-beta.5`.

- New `test/e2e/server/index-v2.ts` on `@modelcontextprotocol/server`
v2; both servers serve the same surface from shared `fixtures.ts`
- `./test/e2e/run.sh --server-protocol legacy|modern` (default: legacy)
+ `require_server_protocol` for era-specific suites; new CI jobs run the
modern column
- Both columns fully green (47/47), incl. previously untested
`subscriptions/listen` resource file-sync and re-sync paths
- MCP SDK packages exempted from pnpm's `minimumReleaseAge` gate so mcpc
can track the fast-moving v2 betas
- Test-robustness fixes: proxy/CA env passthrough to npx-spawned stdio
servers, `NO_PROXY` cleared in the env-proxy suite, wait-loops instead
of fixed sleeps after bridge kills

Refs #314

https://claude.ai/code/session_01JsCXKviUugepDQZu8myGMn

---------

Co-authored-by: Claude <noreply@anthropic.com>
Adds MCP's [Enterprise-Managed
Authorization](https://modelcontextprotocol.io/extensions/auth/enterprise-managed-authorization)
extension (SEP-990): sign in once with corporate SSO at the enterprise
IdP, and mcpc then obtains MCP server tokens via identity assertion
grants (ID-JAG, RFC 8693 token exchange + RFC 7523 jwt-bearer) with no
per-server consent screens. The SDK v2's `CrossAppAccessProvider` drives
the exchanges; mcpc supplies the IdP SSO and grant plumbing, mirroring
the existing client-credentials grant.

- New `mcpc login <server> --grant id-jag --idp <issuer> --idp-client-id
<id> [--idp-client-secret <s>] --client-id <id> --client-secret <s>`,
with full-chain validation at login (fail fast)
- ID token kept fresh via the IdP refresh token; rotated tokens
persisted from the bridge on the sanctioned OAuth-refresh keychain path;
dead SSO sessions land in `unauthorized` with a re-login hint
- Material stored as one OS keychain blob per profile, delivered to the
bridge via IPC (never argv); profile listing labels these profiles
"enterprise"
- Advertises the extension in client capabilities on id-jag connections
only
- 19 new unit tests (RFC 8693 wire format, refresh/rotation, error
paths); smoke-tested end-to-end against a mock IdP + mock MCP
authorization server

Follow-ups deferred: public/CIMD clients at the MCP AS (SDK beta.5
requires `clientSecret`), SAML assertions, e2e mock-IdP suite.

https://claude.ai/code/session_01KFAG6CNK4kftV6RvHe2Jqi

Co-authored-by: Claude <noreply@anthropic.com>
Routine dependency refresh: bumps every devDependency with a newer
release that clears the repo's 5-day `minimumReleaseAge` quarantine. The
MCP TypeScript SDK v2 packages are already on `2.0.0-beta.5` — the
newest published beta — so they're unchanged. No runtime dependencies
touched, hence no changelog entry.

- `@types/node` 25.9.4→26.1.1, `c8` 11→12, `vitest` +
`@vitest/coverage-v8` 4.1.9→4.1.10
- `eslint` 10.6.0→10.7.0, `typescript-eslint` 8.62.1→8.65.0, `prettier`
3.9.4→3.9.6, `tsx` 4.23.0→4.23.1, `viem` 2.55.4
- TypeScript 7.0.2 held back: `tsc` builds clean on it, but
typescript-eslint peers at `<6.1.0` and hard-errors at load
(typescript-eslint#10940)
- undici 8 held back: raises `engines.node` to >=22.19.0 (mcpc declares
>=22.12.0) and enables HTTP/2 by default, which changes the
proxied-fetch path in `src/lib/proxy.ts`
- Verified locally: lint, build, 892 unit tests, e2e legacy (47/47) and
modern protocol matrix

Refs #16

https://claude.ai/code/session_017R8sV6TmRF68ZvkK24dTGm

Co-authored-by: Claude <noreply@anthropic.com>
Adds `--mcp-version <version>` to `mcpc connect` (and an `mcpVersion`
field in mcp.json entries) to pin the MCP protocol version instead of
auto-negotiating. The pin is strict: the connection fails with an
actionable error unless the server agrees to exactly the requested
version.

- Modern pin (2026-07-28) maps to the SDK's `{ pin }` mode; legacy pins
run the plain `initialize` handshake offering only the pinned version
- Pin persists in sessions.json (survives `restart`) and shows as
`(pinned)` in `mcpc @session`
- Supported versions live in a dependency-free `src/core/protocol.ts`
(CLI startup never loads the SDK); a unit test guards drift against the
SDK's list
- New e2e suite runs in both eras of the protocol matrix;
docs/help/skill updated

Refs #314

https://claude.ai/code/session_01DCJziR1mQfJk9XynQCwsFx

Co-authored-by: Claude <noreply@anthropic.com>
Sessions resumed after a bridge crash lost their negotiated protocol
version: session details showed `Protocol: unknown (stateful)` without
the server name, and all requests on the resumed connection were sent
without the required `MCP-Protocol-Version` header. The SDK skips the
initialize handshake when reconnecting with a preserved
`MCP-Session-Id`, so mcpc now seeds the reconstructed transport with the
version persisted in sessions.json.

- Thread the stored `protocolVersion` through the resume path (bridge
manager → bridge → transport), using the SDK transport option intended
for exactly this case
- Derive the protocol era from the restored version so era-dependent
routing keeps working on resumed connections
- Fall back to persisted `serverInfo` in session details after
resumption
- New e2e test crashes the bridge and asserts the resumed session keeps
its protocol version, server info, and session ID (verified to fail
without the fix)
- Also adds an e2e guard asserting stdio sessions report a real protocol
version (the mcpc ≤ 0.5.0 symptom from
modelcontextprotocol/typescript-sdk#1468)

Fixes #324. Refs #325 (follow-up: capabilities/instructions after
resumption).

https://claude.ai/code/session_011pqNs7aoZWzXjgBUxAsias

---------

Co-authored-by: Claude <noreply@anthropic.com>
The MCP spec and TypeScript SDK use `protocolVersion` as the canonical
term, so this renames the `mcpc connect --mcp-version` flag to
`--protocol-version` and the mcp.json config field `mcpVersion` to
`protocolVersion`. No deprecation alias — the flag was added in
unreleased #320, so nothing shipped under the old name.

- Internal option fields, `protocol.ts` constants/helpers, docs (README,
SKILL.md, CLAUDE.md), and the changelog entry renamed to match
- E2E suite renamed to `protocol-version.test.sh`; the JSON pin now
appears as `_mcpc.server.protocolVersion`

Refs #320

https://claude.ai/code/session_01TpJ2kbEL9zSS4baM3CGU5E

Co-authored-by: Claude <noreply@anthropic.com>
…328)

Resuming a stateful HTTP session reuses the server-side session and so
skips the `initialize` handshake, leaving the SDK client with no
capabilities and no instructions. After a crash-recovery restart the
session showed no capabilities, `grep` no longer found the server
instructions, and `resources-subscribe` wrongly refused with "server
does not support resource subscriptions".

- Persist capabilities and instructions in `sessions.json` at the
connect that performs the handshake, and restore them for the lifetime
of a resumed bridge
- Route every bridge-side read through one helper, so the subscribe
pre-check, the tools-cache warmup and the `--proxy` server all see the
restored values
- Skip instructions larger than 32 KB, and keep them out of the `mcpc
--json` session list (they can be kilobytes per session)
- Extend the resume e2e test with capabilities, instructions and a
`resources-subscribe` round trip (verified to fail without the fix)

Fixes #325. Refs #324.

https://claude.ai/code/session_01YRqAADGF3hWDQ83t4FvtHJ

---------

Co-authored-by: Claude <noreply@anthropic.com>
A `protocolVersion` field in an mcp.json entry never reached the client,
so `mcpc connect cfg.json:entry` auto-negotiated instead of pinning,
with no error or warning — only the `--protocol-version` flag worked.
`substituteEnvVars()` rebuilt each entry from a per-field allowlist and
simply forgot the field.

- The config copier now passes every field through and rewrites only
those needing env-var substitution or URL normalization, so future
`ServerConfig` fields can't be dropped the same way.
- An unsupported pin from a config entry is now rejected up front with
the same error as the flag, instead of failing later inside the bridge.
- Unit tests for the preserved pin and unknown-field pass-through, plus
e2e coverage in `basic/protocol-version.test.sh` (both protocol eras).

No changelog entry: config-entry `protocolVersion` is still in
`[Unreleased]` (#320), so the existing "Added" line already describes
the working behaviour.

Fixes #323
Refs #320

https://claude.ai/code/session_01D4Y47mxUVcz73X13R5f4sJ

Co-authored-by: Claude <noreply@anthropic.com>
jancurn pushed a commit that referenced this pull request Jul 27, 2026
…-07-28

Extends the previous era-only gate: `--task`/`--detach` now also fail when the
server does not advertise `tasks.requests.tools.call`, instead of warning and
running the tool synchronously. The flags change the shape of the output, so
the fallback left callers parsing a `taskId` that was never there, with exit
code 0 — the output shape has to be predictable.

- Add `tasksUnsupportedByServerMessage()` beside the era message, so the CLI
  gate and the bridge backstop report the same reason
- Drop the "falling back to synchronous execution" warning path
- Add a `NO_TASKS` flag to the legacy test server (capability withheld, task
  handlers unregistered) and cover both refusal reasons in the e2e suite,
  one per protocol era
- Update the tools-call help text, README, skill and changelog

Refs #316

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DgotfZLGkL4CeYE6GGuq2F
jancurn and others added 6 commits July 27, 2026 22:21
…it tests (#329)

Follow-up review fixes on top of #316. `tools-call --detach` silently
fell through to a plain synchronous `tools/call` on 2026-07-28
connections, handing scripts a tool result with exit 0 where they parse
a `taskId` — while the README, CHANGELOG and skill all promised an
error. Also adds the unit-test file `mcp-client.ts` was missing.

- Gate `--task`/`--detach` in `shouldUseTask` (the CLI decides before
the bridge sees it), sharing one message with the `tasks/*` gate
- Hoist `assertTasksAvailable()` out of the task try blocks, so the
reason is no longer nested in `Failed to list tasks: …` with a doubled
period
- Add `test/unit/core/mcp-client.test.ts` (29 cases) for the
era-dependent logic: `subscriptions/listen` open/rollback/re-listen,
connection mode, resumed-session era fallback, stateless tools-cache
expiry, per-era gates
- Add a modern-only e2e suite guarding the task gates end to end
- Merge the duplicated re-listen backoff loops; README: sampling is not
deprecated by MCP (it gained `context`/`tools` in 2026-07-28)

Refs #316

https://claude.ai/code/session_01DgotfZLGkL4CeYE6GGuq2F

---------

Co-authored-by: Claude <noreply@anthropic.com>
#329 made `tools-call --task` fail with a server error when the server
doesn't advertise `tasks.requests.tools.call`, instead of warning and
falling back to a synchronous call. The conformance adapter still ran
that command expecting exit 0, so the `conformance / tools_call` job
failed and gated the release
([run](https://github.com/apify/mcpc/actions/runs/30302773413/job/90099506437))
— the scenario's own server-side checks passed 2/2, only the adapter's
exit code failed it.

Test-harness only, no behaviour change.

- `runMcpc()` takes an `expectExitCode` option (default `0`)
- The `tools_call` scenario now asserts the `--task` call exits with
code 2, so the gate stays guarded instead of being swallowed
- Refreshed the stale comment that still described the old fallback

Refs #329

https://claude.ai/code/session_01AxYXW15gWXNWFPpSGqQTLV

Co-authored-by: Claude <noreply@anthropic.com>
…333)

Wiring up the two SEP-1046 client-credentials conformance scenarios
found a real bug: `login --grant client-credentials` looked for the
authorization server only on the MCP server's own origin, never fetching
the RFC 9728 protected resource metadata document. An authorization
server on a different origin — what the spec prescribes — was
unreachable without passing `--token-endpoint` by hand. Discovery now
tries protected resource metadata first and follows
`authorization_servers`, falling back to the previous probes.

The default conformance matrix goes from 2 scenarios / 3 checks to 4
scenarios / 31 checks.

- Add `auth/client-credentials-{basic,jwt}` to the adapter and the CI
matrix
- Discover via protected resource metadata, incl. RFC 8414 issuer-path
handling; only the client-credentials path opts in, the id-jag callers
pass issuer URLs directly and are unchanged
- Cover the new discovery path with unit tests
- Document current coverage and the remaining gaps in
`test/conformance/README.md`
- Add a CLAUDE.md rule to keep conformance tests updated like the e2e
tests

The scenarios still missing all drive the browser-based
authorization-code grant and need adapter scaffolding first; tracked in
#332.

Refs #332, #329

https://claude.ai/code/session_01AxYXW15gWXNWFPpSGqQTLV

Co-authored-by: Claude <noreply@anthropic.com>
Upgrades the MCP TypeScript SDK v2 packages
(`@modelcontextprotocol/client`, `core`, `server`, `node`) from
`2.0.0-beta.5` to the stable `2.0.0` release. No code changes were
needed — the breaking changes since the betas (`serverInfo` moved to
response `_meta`, JSON-serialized response cache) are handled inside the
SDK.

Verified: unit tests (944/944), both e2e protocol matrices (legacy
2025-11-25 + modern 2026-07-28, 50/50 each), and all covered conformance
scenarios pass.

https://claude.ai/code/session_01HGa9mKfxSh5G1DmELuhaBA

Co-authored-by: Claude <noreply@anthropic.com>
Help text was wrapping onto two or three lines in the command lists
(`logging-set-level` took three), and `mcpc @session --help` never
documented its `--json` output. Session details also said `Protocol:
2025-11-25 (stateful)` — without saying which protocol, and with the
mode attached to the version rather than to the transport it actually
describes.

- Every command/option description now fits on one line; details moved
into titled `addHelpText` sections (`logging-set-level`, `ping`,
`connect --protocol-version/--x402`, 8 `login` options, `x402`)
- `mcpc @session`, `restart` and `close` now document their `--json`
shape like every other command
- Session details now read `MCP version: 2025-11-25 / Streamable HTTP
(stateless)` (or `/ stdio (stateful)`), naming the protocol and the
transport; new `_mcpc.transport` field in `--json`
- `mcpc @session` no longer offers `logging-set-level` / `tasks-*` on
2026-07-28 connections, where they only error out, and flags those
capabilities as era-limited
- e2e runner reported era-skipped suites as passed, so a missing matrix
column looked green: suites skip via `skip_suite` and `run.sh` prints
`⊘` plus a `Skipped: N` line

New guards in `test/e2e/suites/basic/help.test.sh` walk the whole help
surface for wrapped descriptions and missing JSON sections; CLAUDE.md
records the rules.

No originating issue — requested in the session below.

https://claude.ai/code/session_011nvJzr76VzjJFyG9eGZfB5

---------

Co-authored-by: Claude <noreply@anthropic.com>
Merges `main` into `draft` so the draft branch (SDK v2 migration) can
later merge back into `main` without conflicts. The conflicts all came
from the dependency update on main (#338) colliding with the SDK v2
migration on draft.

- package.json: keep the SDK v2 packages, take main's bumps of
`@modelcontextprotocol/sdk` (^1.30.0) and `viem` (^2.55.8)
- pnpm-workspace.yaml: union of both `minimumReleaseAgeExclude` lists
- CHANGELOG.md: keep draft's entries, fold in main's dependency-update
line
- pnpm-lock.yaml: regenerated from the merged package.json

Refs #338

https://claude.ai/code/session_01MtRQZh2W13VFvpmrJfFwWQ

Co-authored-by: Claude <noreply@anthropic.com>
@jancurn

jancurn commented Jul 28, 2026

Copy link
Copy Markdown
Member Author

@copilot resolve the merge conflicts in this pull request

Copilot AI commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

@copilot resolve the merge conflicts in this pull request

Resolved the merge conflicts in 972fa39. The conflicts were in CHANGELOG.md, package.json, pnpm-workspace.yaml, and pnpm-lock.yaml:

  • CHANGELOG.md: kept the [Unreleased] section from draft and added the [0.5.1] release section from main
  • package.json: kept draft's full dependency set (SDK v2 packages + @modelcontextprotocol/sdk)
  • pnpm-workspace.yaml: merged both minimumReleaseAgeExclude lists
  • pnpm-lock.yaml: took draft's version (already consistent with the resolved package.json)

jancurn and others added 6 commits July 28, 2026 16:17
…340)

Two wording fixes on the session details screens. `MCP version:` looked
like it also titled the transport after the slash, and the `JSON output
(--json):` block under `mcpc @session --help` read as if it applied to
all the subcommands listed above it.

- `MCP version: 2025-11-25 / Streamable HTTP (stateful)` → `MCP: version
2025-11-25 / Streamable HTTP (stateful)`
- session details `--json` help is now one inline sentence with the
shape, dropping the extra `_mcpc` field breakdown
- `connect`, `restart` and `mcpc @session` now share one wording and one
shape example (`connect` wraps it in an array, which is what it returns)

Refs #336

https://claude.ai/code/session_01BdeG5Pz7tSKWbTd7uG6Dx4

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
…r debug check (#347)

Fixes two OAuth-related log annoyances, one of which was masking a real
security check. `mcpc login` printed an SDK warning that the provider
does not implement `saveDiscoveryState()`/`discoveryState()`, leaving
the SEP-2352 callback-leg authorization-server binding unchecked; the
provider now records the discovered authorization server in memory (same
durability as the PKCE code verifier — both login legs share one
provider instance in one process), so the SDK verifies the authorization
code is redeemed at the server that minted it.

- Implement `saveDiscoveryState()`/`discoveryState()` on `OAuthProvider`
(+ unit tests)
- Remove a stale debug block in `createStreamableHttpTransport()` that
logged a spurious `authProvider.tokens() is NOT a function - this is a
bug!` error on every OAuth connection (SDK v2 wraps the provider in an
adapter exposing `token()`, so the check asserted an obsolete shape)

https://claude.ai/code/session_01Cy2yB65N7awpCvjMyLjJC1

Co-authored-by: Claude <noreply@anthropic.com>
…348)

Connecting to a server without 2026-07-28 support dumped a scary
`Transport error: SdkHttpError ... Bad Request: No valid session ID
provided` stack trace into the bridge log on every connect. That HTTP
400 is the expected way such servers decline the `server/discover`
version probe — the SDK falls back to the legacy `initialize` handshake
and the connection succeeds — so it is now logged as a single debug line
instead.

- Recognize the expected probe rejection (pre-connect, auto negotiation,
non-auth 4xx) in the transport error handler
- Keep full error logging for 401/403, 5xx, pinned `--protocol-version`
connections, and post-connect errors
- Unit tests for the classifier and all three logging paths; verified
end-to-end against the legacy e2e server

Refs #347 (removed a different spurious log message; this one remained).

https://claude.ai/code/session_01DceGk44vcV9jfyAHYKpp4h

Co-authored-by: Claude <noreply@anthropic.com>
Updates all version-pinned MCP spec links (schema, authorization,
security best practices, lifecycle) from `2025-11-25` to `2026-07-28`,
so docs and `--help` output point at the latest live spec.

Three anchors don't exist on the 2026-07-28 schema page and stay on
`2025-11-25` on purpose, via a new `LEGACY_SCHEMA_BASE` constant:
- `InitializeResult` — the stateless era replaced the `initialize`
handshake with `server/discover`'s `DiscoverResult`
- `CreateTaskResult`/`Task` — tasks moved out to the
`io.modelcontextprotocol/tasks` extension, not yet implemented by the
SDK; added a TODO to revisit these links once that support lands

The README sessions section keeps its `2025-11-25` lifecycle link too,
but that one's deliberate as a historical reference to the stateful
protocol era, not a miss.

Also fixed two related doc gaps found while cross-checking the schemas:
- `resources-list`/`resources-templates-list` `--help` showed `name?`
(optional) even though `name` is required per `BaseMetadata` in both
spec versions
- `resources-read`/`skills-get` `--help` didn't mention
`ttlMs`/`cacheScope`, new caching-hint fields `ReadResourceResult`
gained in 2026-07-28

All links (updated and kept-as-is) verified live against both spec
pages.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_01XYydaLm3b4Lger6sdkv7vE

---------

Co-authored-by: Claude <noreply@anthropic.com>
`mcpc connect` / `mcpc @session` / `restart` returned an
`InitializeResult`-shaped object, so on 2026-07-28 connections
everything `server/discover` carries beyond the legacy handshake was
dropped, and none of it reached `sessions.json`. `ServerDetails` now
reconciles both results — the fields they share, plus the discover-only
`supportedVersions` and `_meta` (absent on legacy connections; never
synthesized there).

- `--json` and `sessions.json` gained `supportedVersions` and `_meta`;
both are restored for resumed sessions, which skip the handshake
- `SessionData.serverInfo` is typed as the full `Implementation` it
already stored
- Fixes the server identity on modern connections: 2026-07-28 moved
`serverInfo` into a per-response `_meta` key, and the SDK only lifts it
at connect while `ping` refreshes the discover result — so after a
mid-session redeploy, details reported a stale `serverInfo` beside a
fresh `_meta`. Both now come from the same snapshot
- Tools cache honors a server-sent `ttlMs` hint (SEP-2549), and explicit
refreshes (`tools-list`, `list_changed`) pass `cacheMode: 'refresh'` so
the SDK's response cache cannot answer them with a stale list.
`cacheScope` needs no handling: one bridge serves one session, i.e. one
authorization context
- Reformatted the help for these commands: titled `Output:` / `JSON
output (--json):` sections instead of trailing prose, one schema link
per line, and the protocol eras named once

Refs #345

https://claude.ai/code/session_01RZJucu49mLZKJvUYiaY3UV

---------

Co-authored-by: Claude <noreply@anthropic.com>
…351)

The Windows/Bun leg of the last release run failed on `sessions/logs`
test 10 with `Session not found: @fold-...`. Suites that inject
synthetic session entries did an unlocked `jq ... > tmp && mv` on
`sessions.json` while a live bridge rewrote the same file on every
keepalive ping — when the bridge loaded the file before the `mv` and
saved after it, the injected entry vanished. Test-only flake, no product
bug.

- Add an `edit_sessions_json` framework helper that takes the same
`proper-lockfile` lock mcpc uses (the `<file>.lock` directory, created
with `mkdir`)
- Break the lock once after 10s, matching proper-lockfile's staleness
rule, so a SIGKILLed bridge's leaked lock can't hang a suite
- Route the five unlocked fixture edits through it: three in
`sessions/logs`, one each in `unauthorized-auto-detect` and
`unauthorized-persist`

Verified against a keepalive-style concurrent writer: the old pattern
lost 2 of 60 injected entries, the helper lost 0 of 60. Full `sessions/`
suite (18 tests) passes.

Refs https://github.com/apify/mcpc/actions/runs/30614984280

https://claude.ai/code/session_01LV8fLqLtF7s3VMqiL73qfv

Co-authored-by: Claude <noreply@anthropic.com>
@jancurn jancurn changed the title [DRAFT] MCP 2026-07-28 support Added MCP 2026-07-28 support Jul 31, 2026
@jancurn
jancurn merged commit 52c996b into main Jul 31, 2026
20 checks passed
@jancurn
jancurn deleted the draft branch July 31, 2026 21:05
jancurn pushed a commit that referenced this pull request Jul 31, 2026
main's tip (#316) is a squash-merge of the MCP 2026-07-28 work this branch
already carried as individual commits, so the merge brings no functional change:
the only difference between the two sides is this branch's dependency-age gate.

Two resolutions were needed. CHANGELOG.md conflicted where this branch adds its
`### Security` entry, kept as-is. pnpm-workspace.yaml auto-merged wrongly: the
squash re-added the unresolved `allowBuilds:` / `esbuild: set this to true or
false` placeholder that this branch deletes, and since the deletion is not a
competing change to that region from the merge base, git silently took main's
addition back. Removed again.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T7DpKbdofBXpGHfhi4TMMf
jancurn added a commit that referenced this pull request Jul 31, 2026
MCP 2026-07-28 removed `ping`, so `mcpc @session ping` silently probes
with `server/discover` instead — invisible until it shows up in a
server's access log, with no way to see what that request returns. This
adds a command that says what it does.

- `mcpc @session server-discover` sends `server/discover` and reports
the answer: supported protocol versions (negotiated one marked),
capabilities, instructions, `_meta`. `--json` prints the
`DiscoverResult` verbatim.
- On 2025-era connections it refuses (exit 2) and points at `mcpc
@session`, whose `initialize` result carries the same data.
- `ping` output now names the request it measured; `ping --json` is
unchanged.
- Capability rendering extracted so both screens share it;
`SERVER_INFO_META_KEY` mirrored in the dependency-free `protocol.ts`
with an SDK drift test.
- New `basic/server-discover.test.sh` e2e suite (one branch per protocol
era); README, CHANGELOG and the agent skill updated.

Refs #316

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_01KHDUfie1HZtvSiqFNTYs17

---
_Generated by [Claude
Code](https://claude.ai/code/session_01KHDUfie1HZtvSiqFNTYs17)_

Co-authored-by: Claude <noreply@anthropic.com>
jancurn pushed a commit that referenced this pull request Aug 2, 2026
A config entry's `env` routinely holds API tokens — directly or via `${VAR}`
substitution — but only `headers` were protected. Resolved `env` values were
written to `sessions.json` in plaintext, printed verbatim by `mcpc --json
@session` / `connect --json`, and passed to the bridge in its command line,
where `ps` exposed them.

They now follow the exact same path as headers: stored in the OS keychain
(`session:<name>:env`), redacted to `<redacted>` in `sessions.json` and all
`--json` output, and delivered to the bridge over IPC after spawn. The bridge
merges them back into the stdio transport config, so servers still get their
environment unchanged. Sessions written before this change keep working —
their on-disk plaintext is used as is; recreate a session to move its values
into the keychain.

Rebuilt on main after the draft branch was squashed in as #316.

Fixes #341

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rmdd458q2Bx68bPxuUtQGa
jancurn pushed a commit that referenced this pull request Aug 6, 2026
A config entry's `env` routinely holds API tokens — directly or via `${VAR}`
substitution — but only `headers` were protected. Resolved `env` values were
written to `sessions.json` in plaintext, printed verbatim by `mcpc --json
@session` / `connect --json`, and passed to the bridge in its command line,
where `ps` exposed them.

They now follow the exact same path as headers: stored in the OS keychain
(`session:<name>:env`), redacted to `<redacted>` in `sessions.json` and all
`--json` output, and delivered to the bridge over IPC after spawn. The bridge
merges them back into the stdio transport config, so servers still get their
environment unchanged. Sessions written before this change keep working —
their on-disk plaintext is used as is; recreate a session to move its values
into the keychain.

Rebuilt on main after the draft branch was squashed in as #316.

Fixes #341

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rmdd458q2Bx68bPxuUtQGa
jancurn added a commit that referenced this pull request Sep 15, 2026
mcpc's skills support tracked the pre-July-2026 SEP-2640 draft (a
`skill://index.json` discovery file with
`skill-md`/`archive`/`mcp-resource-template` entry types), none of which
survived into the extension as it landed. This implements the [stable
spec](https://github.com/modelcontextprotocol/ext-skills):
`skills/list`, `skills/get`, and the optional
`resources/directory/read`, on MCP 2026-07-28+ servers that declare the
extension. No backwards compatibility with the draft.

- `skills-list` prints spec-shaped entries (frontmatter + complete file
manifest) in `--json`
- `skills-get <skill> [file]` reads a SKILL.md or a supporting file and
verifies it against the manifest (size, digest, frontmatter) — content
failing a check is refused, not printed
- New `resources-directory-read <uri>`, gated on `"directoryRead": true`
- Adds a dependency-free SKILL.md frontmatter parser and result
validators (the SDK ships no schemas for these methods)
- E2E: the 2026-07-28 test server now serves all three methods and can
be made to contradict its own manifest; a new legacy suite pins the
refusal on 2025-11-25

Refs https://github.com/modelcontextprotocol/ext-skills, supersedes the
draft implementation from #316

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_01Cf7cxfnnBxmDAsDbV4tfEf

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants