| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
cd5dde839c |
test: drive the cancellation showcase through its checked-in config
The two cancellation tests each built their own `slow_task`, so the documented showcase — the config file, its preset registration, and the fixture's own abort loop — was covered by nothing. A misspelt preset name or a regression in that handler would have surfaced only when someone ran the repro by hand. Resolve `cancellation-modern-http.json` through `loadConfig`/`resolveConfig` and drive the real preset, following the pattern the nullable-fields and MRTR showcase tests already use. It asserts on the handler's own return value rather than on progress notifications. Counting ticks is a false negative here: the SDK drops a cancelled request's progress handler locally, so the client stops seeing ticks the moment it cancels whether or not the server ever stopped — verified, that version passed with the fix reverted. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013hzwzS8UvBsr4yZm7o7sev Signed-off-by: cliffhall <cliff@futurescale.com> |
||
|
|
85c5206895 |
fix: cancel a Streamable HTTP call by closing its stream (#2140)
The Cancel button POSTed `notifications/cancelled` on every transport. On a 2026-07-28 Streamable HTTP connection that is the wrong signal — the spec makes closing the request's SSE response stream the cancellation signal there and says the notification is neither required nor expected — so a spec-compliant server acknowledged it 202 and dropped it while the tool ran to completion. The SDK already implements the fork, off `transport.hasPerRequestStream`. Every Inspector connection is wrapped in `MessageTrackingTransport`, which did not forward that flag, so the SDK read `undefined` and took the stdio branch on every client — the CLI and TUI included, whose real `StreamableHTTPClientTransport` advertises it correctly. The web client needed three more links: its browser transport answers for the upstream transport that lives on the backend, so it advertises the flag for `streamable-http` and applies the SDK's `requestSignal` to its `POST /api/mcp/send`; and `/api/mcp/send`, held open for the whole call, forwards the browser's disconnect to the upstream `transport.send`. The test fixture's `toWebRequest` built a Web Request with no `signal`, so no fixture server could observe a disconnect at all — without that, a test asserting the cancel reaches the server passes vacuously. stdio and SSE keep `notifications/cancelled`: they multiplex every request over one shared channel and have no per-request stream to close. So does the legacy era, which predates the mechanism. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013hzwzS8UvBsr4yZm7o7sev Signed-off-by: cliffhall <cliff@futurescale.com> |
||
|
|
b33532c024 |
feat: allow complex JSON values in request _meta
Closes #1910 `_meta` was typed `Record<string, string>` end to end in the Inspector — on disk as a `{ key, value }[]` pair array, in the Server Settings form as key/value rows, and in every request verb's per-call metadata parameter. Nothing in the MCP spec restricts a `_meta` value to a string, and the SDK models the field as a `z.looseObject`, so the restriction was ours alone. Widen the whole path to `RequestMetadata` (`Record<string, JsonValue>`): - Per-server default metadata is a JSON **object** on disk and in memory. The pre-#1910 pair array is still read (`normalizeStoredMetadata`) so an existing `mcp.json` keeps working; it is never written back, so a save migrates the entry. - The Server Settings → Request Metadata section is a JSON editor (`JsonObjectInput`) instead of key/value rows — rows cannot express an object value. It keeps the typed text as the display source and emits only parsed JSON, the same draft split `SchemaJsonField` uses, so the box stays typeable; invalid text is flagged inline and not applied. - The CLI stops flattening `--metadata` / `--tool-metadata` through `metaValueToString`. Those values were already JSON-parsed, so `trace={"id":"abc"}` used to reach the wire as the *string* `{"id":"abc"}` and now reaches it as an object. - `servers/show` redaction replaces a sensitive key's whole value rather than a string, so a structured value cannot leak through its members. - `progressTokenOf` narrows the reserved `progressToken` key back to `string | number` before it reaches the SDK; any other JSON still travels inside `_meta` but wires up no progress callback. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0177mPHAdECD18nLLCTwR5rh Signed-off-by: cliffhall <cliff@futurescale.com> |
||
|
|
f5757c0f95 |
test(integration): drive the mrtr_empty reproduction through the showcase config
Copilot review: the unit test covered the panel's rendering, but nothing executed `mrtr_empty`, so the handler, its preset-registry branch, and the showcase config entry could regress with CI green. Adds a live modern-era test that loads `mrtr-showcase-http.json` through `loadConfig`/`resolveConfig` — the same artifacts the README's manual reproduction uses, so the config entry and the registry branch are covered rather than just the fixture factory — answers the embedded elicitation, and asserts the terminal result really is an empty `CallToolResult`: that is the input the Tools panel must render as "completed, returned nothing". Signed-off-by: cliffhall <cliff@futurescale.com> |
||
|
|
ec185f1053 |
Logging: per-request log level on modern servers (era fork) (#1629) (#1724)
Some checks failed
Run install, format, lint, build, and test on every push / build (push) Has been cancelled
Run install, format, lint, build, and test on every push / publish (push) Has been cancelled
Run install, format, lint, build, and test on every push / publish-github-container-registry (push) Has been cancelled
* Logging: per-request log level on modern servers (era fork) (#1629) On modern (2026-07-28) servers `logging/setLevel` and the session-scoped level are gone: the client opts into logs per request via the `io.modelcontextprotocol/logLevel` `_meta` key, and the SDK does not attach it automatically, so server logs are silently absent until stamped. Core (`InspectorClient`): - `setModernLogLevel(level | undefined)` / `getModernLogLevel()` store the opt-in level; `mergeMeta` stamps it on every outgoing request, gated on the negotiated modern era so legacy connections are untouched. - Cleared on disconnect so it can't leak into the next connection. Web UI: - `LogControls` forks on `protocolEra`: legacy keeps the `logging/setLevel` selector + Set button; modern shows a per-request opt-in Select (Off + levels, applied immediately) with copy explaining logs arrive on the originating request's stream. - Threaded `protocolEra` / `modernLogLevel` / `onSetModernLogLevel` through LoggingScreen and InspectorView from App. Tests: unit coverage for the modern LogControls/LoggingScreen fork; integration coverage proving the `logLevel` `_meta` is stamped on a real modern server once opted in (and not on legacy). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * test-servers: logging era-fork showcase configs + proof screenshots (#1629) Add two manual-testing configs and README docs for the logging era fork: - logging-legacy-http.json: plain streamable-HTTP server with `logging: true` and a `send_notification` tool. The Logs tab shows the legacy session-scoped "Set Active Level" selector + Set button; calling the tool streams logs. - logging-modern-http.json: same, with `transport.modern: true`. Connect with Protocol Era = Modern and the Logs tab shows the per-request opt-in control; the client stamps `_meta["io.modelcontextprotocol/logLevel"]` on every request (visible in the Network tab request body). pr-screenshots/ documents the verified behavior end-to-end against both real servers: the legacy control with logs streaming, the modern per-request control, and the modern tools/call request body carrying the stamped logLevel _meta. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * test-servers: send_notification emits via per-request notifier so modern logs stream (#1629) The `send_notification` tool sent its `notifications/message` through the global `server.server.notification()`. On the modern (2026-07-28) stateless leg that isn't tied to the in-flight request, so the SDK answered the tool call with plain JSON and dropped the log — nothing reached the Inspector's Logs panel. Prefer the per-request `extra.sendNotification` (rebuilt from `ctx.mcpReq.notify`): scoped to the current request, it makes the SDK upgrade the response to SSE and stream the log back on the originating request's stream. Falls back to the global notifier (legacy path / no per-request context). Verified end-to-end: opting in via the Logs-tab "Log Level per Request" control and calling send_notification now streams the entry on the modern server; legacy behavior unchanged (server-notification integration tests green). README documents the modern logging showcase and the caveat that this test server streams whenever a tool emits — the SDK beta strips the per-request envelope from the handler context, so the tool can't gate on the client's logLevel opt-in (the client-side `_meta` stamp #1629 owns is still fully visible in the Network tab). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * test-servers: send_notification uses SDK request-scoped log() → spec-faithful modern gating (#1629) The previous fix routed `send_notification` through `extra.sendNotification` (`ctx.mcpReq.notify`), which streams on the modern leg but emits unconditionally — the log showed up even when the client hadn't opted in via the per-request `logLevel`. The SDK already provides the correct primitive: `ctx.mcpReq.log(level, data, logger)` (added in `McpServer.buildContext`). It applies the era-correct threshold gating itself — on the modern (2026-07-28) leg it reads the per-request `logLevel` opt-in from the request envelope and drops the message when the client didn't opt in or the level is below the requested severity; on legacy it honors the session level from `logging/setLevel` — and emits through `notify`, so the modern response still upgrades to SSE and the log rides the originating request's stream. Surface it as `extra.log` (`toHandlerExtra` maps `ctx.mcpReq.log`) and have `send_notification` prefer it, falling back to the global notifier for callers without per-request context. Verified end to end: opting in via the Logs-tab "Log Level per Request" control stamps `logLevel` in the request `_meta` and streams the log; setting it back to Off omits the key and the server gates the log (plain JSON response, no notification). Legacy server-notification integration tests still green. (No SDK change needed — the earlier apparent gap was our test server using the wrong low-level API.) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * web+core: add "Log Level per Request" as a per-server setting, defaulting to on (#1629) Adds the modern per-request log level as a persisted Server Settings option (Options → "Log Level per Request", below Protocol Era), so a server can opt into logs by default instead of the Logs-tab control starting at Off. - types.ts: `ModernLogLevel` (`"off"` | the 8 levels), `DEFAULT_MODERN_LOG_LEVEL` = "debug" (default-on so a modern connection surfaces server logs out of the box), `MODERN_LOG_LEVELS`, and an `isModernLogLevel` guard. Field added to `InspectorServerSettings` and `StoredMCPServer`. - serverList.ts: round-trip the field (lift on read, omit-on-default on write, drop unknown literals) mirroring `protocolEra`; add to the persisted field allowlist. - remote/node/server.ts: validateSettings accepts/validates it and the normalize path drops a malformed literal. - inspectorClient.ts: the constructor seeds `modernLogLevel` from the setting (`"off"` → not opted in), so a modern connection stamps the level from the first request without UI interaction. mergeMeta still gates on the modern era. - ServerSettingsForm/Modal: a Select (Off + 8 levels) with a modern-era description. App seeds the live Logs-tab control from the setting on connect so the two stay in sync. Tests: form Select (default/onChange/off/reflect), modal handler, serverList round-trips, route validation (accept/reject/drop), and constructor seeding (seeded level stamped from the first request; "off" opts out). The prior modern-era "stamps once set" test is updated for the new default-on behavior. Coverage gate green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * Fix CI + review: format ServerSettingsForm, drop stray network export, broaden ignore - Prettier-format ServerSettingsForm.tsx (the format:check failure that broke CI — my pre-push `npm run format` from the repo root was a silent no-op since there is no root format script; formatting is per-client). - Remove `inspector-network-logging-modern-2026-07-19T15-11-27.013Z.json`, a Network-tab export debugging artifact accidentally committed in ca2aa526 (flagged by review). - Broaden the .gitignore debugging-artifacts section with `/inspector-network-*.json` so future network exports are ignored (the existing `/inspector-history-*.json` didn't match the network export naming). Verified with the full `npm run ci` (validate → coverage → smoke → storybook), green across all clients. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * test: add modern-era log delivery + gating e2e tests (#1629) The modern-era suite covered the client-side `_meta` logLevel stamp but not that a log actually flows back — the legacy suite already has that (`should receive server-initiated notifications via SSE/streamable-http`), so the modern side was the gap. Add two live e2e tests against a modern (`createMcpHandler`) server serving the `send_notification` tool with `logging: true`: - opted-in (server setting = a level): calling the tool delivers a `notifications/message` over the request's SSE stream, with the right level/logger/data. - not opted in (setting "off"): the tool still returns a result, but the server gates the log — no `notifications/message` is received. This exercises the full path end to end: the client seeding + `_meta` opt-in, and the server's `send_notification` → `ctx.mcpReq.log` era-correct gating. Verified with the full `npm run ci` (green across all clients). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * web: hide the modern "Log Level per Request" setting on legacy connections (#1629) The per-request log level is meaningless on a legacy server (which uses logging/setLevel), so hide it in Server Settings when the era is legacy: - protocolEra pinned to "legacy" → hidden (no connection needed), or - protocolEra "auto" that negotiated legacy at connect time → hidden. It stays visible for a pinned "modern" server, and for an "auto" server that is either not yet connected (era unknown) or resolved to modern. Threads the negotiated era down: App passes `negotiatedEra` to ServerSettingsModal only when the settings target is the live-connected server (`connectionStatus === "connected" && settingsModalTargetId === activeServerId`); the modal forwards it to ServerSettingsForm, which gates the control on `configuredEra === "modern" || (configuredEra === "auto" && negotiatedEra !== "legacy")`. Tests: visibility matrix on the form (legacy hidden; auto+legacy hidden; auto+unconnected and auto+modern and modern shown) and a modal hide case; the existing modern-log tests now base on a modern-era settings object. Verified in the browser (legacy hides, switching to modern reveals it reactively) and with the full `npm run ci` (green across all clients). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
9b2c188d11 |
MRTR: drive input_required manually to keep pending-request UX (#1704) (#1719)
Some checks failed
Run install, format, lint, build, and test on every push / build (push) Has been cancelled
Run install, format, lint, build, and test on every push / publish (push) Has been cancelled
Run install, format, lint, build, and test on every push / publish-github-container-registry (push) Has been cancelled
* MRTR: drive input_required manually to keep pending-request UX (#1704) Follow-up to #1627/#1705. Instead of letting the SDK auto-fulfil the modern (2026-07-28) MRTR retry loop, InspectorClient now drives it itself so the Inspector keeps its pending-request UX, owns the loop, and shows era-accurate semantics. Core (core/mcp/inspectorClient.ts): - Construct the SDK Client with `inputRequired: { autoFulfill: false }` (unconditional; a no-op on legacy, which never returns input_required). - New `requestWithInputRequired()` driver: issues the request with `allowInputRequired` + `withInputRequired(schema)`, and while the result is `input_required`, fulfils each embedded request through the SAME pending sampling/elicitation plumbing, then retries the original call with `inputResponses` + the echoed `requestState` on a fresh id, bounded by MRTR_MAX_ROUNDS. - Route tools/call, prompts/get, and resources/read through the driver. - Embedded roots/list is auto-answered from configured roots (no pending UX); decline/cancel is echoed to the server (not treated as an abort); a tool-call abort mid-round settles the pending request and surfaces ToolCallCancelledError. - Extract shared `enqueuePendingSample`/`enqueuePendingElicitation` helpers used by both the legacy inbound handlers and the driver; add a per-request `origin` ("server-request" | "input-required") to the pending wrappers. Web UI: - New `MrtrOriginNote` element shows an "input_required — your answer returns as a retry" note on a modern MRTR round; legacy panels are unchanged. - Thread `origin` App -> PendingClientRequestModal -> the sampling/elicitation panels (and the inline panels). Test servers: new MRTR presets (multi-round, roots, sampling, loop, edge-case param shaping). Integration tests cover the manual round-trip (asserting the pending UI pauses and the retry carries inputResponses + requestState on a new id), multi-round, roots auto-answer, sampling, decline-echoed, maxRounds bound, abort mid-round, and requestState-only/inputRequests-only param shaping. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * Review: clarify sampling-reject and between-rounds abort comments (#1704) Address review nits on PR #1719 (comment-only, no behavior change): - enqueuePendingSample docstring: sampling has no decline/cancel action; the panel either sends a result (resolve) or Rejects (reject → fails the call). The shared "declined/cancelled resolves" wording was accurate only for the elicitation case. - callToolWithRetries abort comment: note the between-rounds case — when the abort lands while an MRTR round awaits an embedded pending request there is no in-flight SDK request, so nothing is sent on the wire; the outcome (ToolCallCancelledError, no failed-call record) is identical. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * test-servers: add mrtr-showcase config exercising every MRTR preset (#1704) A single modern-era streamable-HTTP server (port 3111) bundling all six MRTR presets — mrtr_confirm, mrtr_two_step, mrtr_sample, mrtr_roots, mrtr_edge, mrtr_loop — for manual testing of the manual-driving pending-request UX across single-round, multi-round, sampling, roots, param-shaping, and maxRounds paths. Document it in the README alongside modern-mrtr-http.json. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
73d9b181fe |
History: MRTR conversation grouping + modern message vocabulary (#1705)
Some checks failed
Run install, format, lint, build, and test on every push / build (push) Has been cancelled
Run install, format, lint, build, and test on every push / publish (push) Has been cancelled
Run install, format, lint, build, and test on every push / publish-github-container-registry (push) Has been cancelled
* History: MRTR conversation grouping + modern message vocabulary Teach the Protocol/History view the modern (2026-07-28) message vocabulary, correlating it off the frames the transport already captures — no change to how MRTR is driven (manual driving is deferred to #1704). - Correlation helpers in protocolUtils: extractResultType, extractRequestState, extractSubscriptionId, isModernFrame, and groupProtocolEntries — clustering the contiguous rounds of one logical operation (original call -> input_required -> retried call with a new id -> final result) that share a requestState token. - New MrtrConversation group renders that cluster as one expandable unit with a status derived from the final round, reusing ProtocolEntry for each round. - ProtocolEntry: resultType badge (input required / complete), a "modern" frame badge for server/discover, subscriptions/listen, and the acknowledged notification, and a copyable subscriptionId. - ProtocolListPanel folds each section through the grouping pass and shows an era badge in the header. Era is threaded from the negotiated protocolEra (App -> InspectorView -> ProtocolScreen -> panel) and never inferred from frames, respecting the transcript-classification caveat. - Shared EraBadge element (+ eraUtils); ConnectionInfoContent reuses it. - test-servers/configs/modern-mrtr-http.json serves an elicitation tool over the modern leg for eyeballing a real MRTR round-trip. - Pre-bundle @modelcontextprotocol/client in optimizeDeps.include so the Storybook vitest project doesn't re-optimize mid-run once protocolUtils pulls the client barrel into the browser graph. Closes #1627 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * test-servers: real MRTR tool + live-verified History grouping While driving the modern flow in the browser, the elicitation-based config proved unable to produce MRTR: the modern (2026-07-28) SDK server rejects `server.elicitInput` — server→client requests aren't allowed on that leg; a tool must instead return `inputRequired(...)`. So the shipped `modern-mrtr-http.json` (and its README claim) were wrong. - Add a genuine MRTR tool preset (`mrtr_confirm` / `createMrtrTool`): its handler returns `inputRequired({ inputRequests: { confirm: elicit(...) } })` on the first call and the final result once the client echoes the answer back on the retry. - Teach the composable test-server to support it: the tool wrapper forwards an `InputRequiredResult` verbatim (instead of coercing it to a CallToolResult), and `toHandlerExtra` surfaces `inputResponses`/`requestState` so a handler can branch between the original call and the retry. - Point `modern-mrtr-http.json` at the new preset; correct the README to describe the real mechanism. - Add an integration test that drives the full round-trip through InspectorClient (auto-fulfilling the embedded elicitation) and asserts the final result is only reachable after the input_required round + retry — regression cover that would have caught the original config bug. Verified live in the browser: the History view renders the round-trip as one grouped "MRTR conversation" (Round 1 input_required → Round 2 complete), keyed by the real requestState, under the Modern era badge. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * Address review notes: dedupe status badge, unique MRTR token, import alias Non-blocking review feedback on #1705: - ProtocolEntry: suppress the redundant green "OK" status badge when a `resultType` badge already conveys the outcome (a modern success shows only `complete`/`input required`). Errors/pending have no `resultType`, so their status badge still shows. - createMrtrTool: mint a unique `requestState` per original call (closure counter) so two same-action invocations don't fold into one conversation during manual eyeballing. Real SDK tokens are already unique per operation. - ProtocolEntry: align the `MessageEntry` import to the `@inspector/core` alias used by its siblings (was a deep relative path). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * Review round 2: green `complete` badge, lighter embedded MRTR container Non-blocking observations from the second review: - ProtocolEntry: `resultType` "complete" now renders green (was gray) so a modern success keeps the at-a-glance green success affordance a legacy "OK" had, now that the redundant OK badge is suppressed. `input_required` stays yellow. - MrtrConversation: in the narrow monitoring (embedded) column, drop the outer container border and tighten padding — the per-round entries are already bordered cards, so this avoids a busy border-in-border nesting. The full-width view keeps the bordered surface that sets a conversation apart. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * Review round 3: module-scope MRTR counter; revert embedded border change - test-servers: hoist the MRTR `requestState` counter to module scope. The modern (2026-07-28) leg is stateless — `createMcpHandler` rebuilds the server (and every tool closure) per request — so the previous per-closure counter reset to 0 on every request and every token ended in `:1`, defeating its purpose. A module-level counter survives the per-request factory recreation and increments monotonically across the process, so same-action invocations get distinct tokens and don't fold into one conversation. (Review finding.) - MrtrConversation: revert the embedded borderless/tighter-padding change — per maintainer feedback from manual testing, dropping the border didn't improve the narrow-column visual, so the group keeps its border in all layouts. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
e06c0a45f7 |
Add modern-era (2026-07-28) test-server preset (#1700) (#1703)
* Add modern-era (2026-07-28) test-server preset (#1700) The era-aware connection model (#1626, PR #1697) added per-server `protocolEra` and the era-aware Connection Info UI, but no bundled test server negotiated the modern era — the modern happy path had only unit/Storybook fixtures, no live coverage. Serve the modern (2026-07-28) protocol era from the HTTP test server via the SDK's `createMcpHandler`. A `modern` flag on `ServerConfig` (and a `transport.modern` field in the JSON config) mounts the handler with a dual-era `legacy: "stateless"` fallback by default, or `legacy: "reject"` for modern-only strict serving. The web-standard `fetch` handler reuses the existing `toWebRequest`/`writeWebResponse` conversion, so no new `@modelcontextprotocol/node` dependency is needed. An SDK v2 client negotiating `protocolEra: "auto" | "modern"` now reaches the modern leg end-to-end: `getProtocolEra() === "modern"`, `getNegotiatedProtocolVersion() === "2026-07-28"`, and a populated `getDiscoverResult()`. Verified live in the Inspector — Connection Info shows Era Modern / Session Sessionless / the Discovery section. - Modern serving path + `listen`/`startModernHttp` helpers in test-server-http.ts; modern-leg teardown in stop(). - `modern` config field threaded through load-config/resolve-config with a streamable-http-only validation guard; modern-http.json demo config. - Integration test covering auto/modern negotiation, the dual-era legacy fallback, a modern tools/call, and the strict `legacy: "reject"` rejection. Closes #1700 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * Review nit: gate transport.modern guard on truthiness A falsy `transport.modern` (e.g. `false`) is a no-op that `resolveConfig` normalizes to `undefined`, so it shouldn't trip the streamable-http-only guard. Gate on truthiness instead of `!== undefined` so only *enabling* modern on a non-HTTP transport is rejected. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cd5dde839c | ||
|
|
85c5206895 | ||
|
|
b33532c024 | ||
|
|
f5757c0f95 | ||
|
|
ec185f1053 |
||
|
|
9b2c188d11 |
||
|
|
73d9b181fe |
||
|
|
e06c0a45f7 |