* Extensions: advertised-extensions toggle in Server Settings (Phase 2 of #1633) Adds the debugging knob from #1633: users choose which extensions the Inspector advertises to a server, and a server may register different tools in response. Builds on Phase 1's registry + advertisedExtensions option (#1738). Settings + persistence (core): - InspectorServerSettings.advertisedExtensions + StoredMCPServer field, wired through serverList's read/write converters and INSPECTOR_FIELD_KEY_MAP (omit-when-empty, byte-stable round-trip). - validateSettings (remote PUT route): isBooleanRecord guard + carry a non-empty map; unknown/malformed rejected. - App.tsx threads savedSettings.advertisedExtensions into the client options. UI: - ServerSettingsForm renders an "Advertised Extensions" group of checkboxes driven by ADVERTISABLE_EXTENSIONS; a per-server override wins over the registry default. Modal folds a toggle into settings.advertisedExtensions. Test server + proof: - composable-test-server extensionGatedTools: a tool registered disabled and enabled on `initialized` iff the client declared the mapped extension (getClientCapabilities). Legacy stateful leg. Threaded through load-config/resolve-config; advertised-extensions-http.json showcase + README. - Integration test connects legacy with/without the tasks extension advertised and asserts the gated tool appears/disappears in tools/list — the acceptance criterion, proven end to end, not asserted. Tests: serverList round-trip (non-empty/absent/empty), ServerSettingsForm toggle rows, ServerSettingsModal fold, validateSettings accept/reject, and the integration gating test. npm run ci green (per-file ≥90 gate held). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * Extensions: add #1739 proof screenshots (advertised-extensions toggle) End-to-end smoke test in the web client against the legacy advertised-extensions-http.json test server (port 3220): connecting with the Tasks extension advertised (default) lists echo + get_weather, unchecking the Advertised Extensions → Tasks toggle and reconnecting lists only echo — the server's tool registration changing on the client-declared extension. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * Extensions: reconverge advertisedExtensions to no-override on default (review) Address Phase 2 review observation #1: toggling an extension back to its registry default now deletes the override key (and drops the whole map to undefined when it empties), so the on-disk `advertisedExtensions` reconverges to the byte-stable "no override" state instead of persisting a redundant `{ key: <default> }`. Matches the omit-when-default policy the other settings use. Only a value that differs from the default is persisted. Adds a modal test asserting the reconvergence (re-checking the sole disabling override drops advertisedExtensions to undefined). 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>
145 lines
7.1 KiB
Markdown
145 lines
7.1 KiB
Markdown
# Advertised-extensions toggle (#1739) — proof screenshots
|
|
|
|
End-to-end verification of the advertised-extensions debugging knob against a
|
|
real legacy HTTP test server (`test-servers/configs/advertised-extensions-http.json`
|
|
on port 3220), driven in a real browser through the web client's remote-proxy
|
|
transport. The server registers `echo` (always) and `get_weather` **gated on the
|
|
`io.modelcontextprotocol/tasks` extension** (`extensionGatedTools`): the tool is
|
|
enabled on `notifications/initialized` only when the connected client declared
|
|
that extension.
|
|
|
|

|
|
|
|
**Server Settings → Advertised Extensions** (the new section). With no override,
|
|
**Tasks (io.modelcontextprotocol/tasks)** is checked — the registry default —
|
|
so the Inspector advertises it in its client capabilities.
|
|
|
|

|
|
|
|
Connected with Tasks advertised. The server sees the declared extension on
|
|
`initialized` and enables the gated tool, so `tools/list` returns **both `echo`
|
|
and `get_weather`**.
|
|
|
|

|
|
|
|
Unchecking **Tasks** writes the per-server `advertisedExtensions` override. The
|
|
change takes effect on the next connect (as the section note says).
|
|
|
|

|
|
|
|
After reconnecting, the client advertises no extensions, so the server never
|
|
enables the gated tool — `tools/list` now returns **only `echo`**. The server's
|
|
tool registration demonstrably changed based on what the client advertised,
|
|
exactly the acceptance criterion for #1739.
|
|
|
|
---
|
|
|
|
# x-mcp-header Tools tooling (#1632) — proof screenshots
|
|
|
|
End-to-end verification of the SEP-2243 `x-mcp-header` Tools tooling against a
|
|
real modern (2026-07-28) HTTP test server
|
|
(`test-servers/configs/xmcpheader-modern-http.json` on port 3120), driven in a
|
|
real browser through the web client's remote-proxy transport.
|
|
|
|

|
|
|
|
Connected with **Protocol Era = Modern**. `invalid_header_tool` is dropped from
|
|
`tools/list` by the SDK (its `x-mcp-header` annotation is invalid), so the
|
|
Inspector re-lists the raw list and surfaces it struck-through under an
|
|
**"Excluded (SEP-2243)"** divider — showing _why_ a tool vanished rather than
|
|
silently omitting it.
|
|
|
|

|
|
|
|
Hovering the excluded tool shows the exact scan reason: the header name
|
|
`"Bad Header"` contains a space, so it is not a valid RFC 9110 token.
|
|
|
|

|
|
|
|
`get_weather`'s detail panel shows the **"Mirrored request headers (SEP-2243)"**
|
|
section: its `city` argument mirrors to `Mcp-Param-City`, with the note that the
|
|
SDK omits `Mcp-Param-*` on the browser transport.
|
|
|
|

|
|
|
|
Calling a tool the server no longer recognizes rejects with **`-32602`** (SDK v2)
|
|
instead of an `isError` result, and renders as an **"Unknown Tool"** error panel
|
|
with a targeted hint (reproduced by swapping the sessionless server to one
|
|
without `echo` while the cached list still showed it).
|
|
|
|

|
|
|
|
`-32602` is the generic _Invalid params_ code, so a **known** tool rejected for
|
|
bad arguments throws the same code as an unknown tool. The panel disambiguates
|
|
from the message: a `-32602` that does not name an unknown tool renders under
|
|
**"Invalid Parameters"** (with a schema hint) rather than "Unknown Tool".
|
|
Triggered live via the `trigger_invalid_params` tool, which returns a real
|
|
`-32602` JSON-RPC error whose message is not about a missing tool.
|
|
|
|
---
|
|
|
|
# Tasks extension era fork (#1631) — proof screenshots
|
|
|
|
End-to-end verification of the Tasks era fork against two real test servers
|
|
(`test-servers/configs/tasks-modern-http.json` on port 3222 and
|
|
`tasks-legacy-http.json` on 3223), driven in a real browser through the web
|
|
client's remote-proxy transport.
|
|
|
|
## Modern era (2026-07-28, `io.modelcontextprotocol/tasks` extension)
|
|
|
|

|
|
|
|
Connected with **Protocol Era = Modern** (`MCP 2026-07-28`). The **Tasks** tab
|
|
appears in the monitoring sidebar because the `io.modelcontextprotocol/tasks`
|
|
extension was negotiated (it is empty until a task runs).
|
|
|
|

|
|
|
|
Connected with **Protocol Era = Modern** (`MCP 2026-07-28`). The **Tasks** tab is
|
|
gated on the negotiated `io.modelcontextprotocol/tasks` extension (not
|
|
`capabilities.tasks`). Running `modern_task` with **Run as task** on issues a
|
|
`tools/call` that returns a `CreateTaskResult` (`resultType: "task"`), then polls
|
|
**`tasks/get`** (no `tasks/list`); the completed task **inlines its result** (no
|
|
blocking `tasks/result`) — shown both in the Results panel and the Tasks card.
|
|
|
|

|
|
|
|
`modern_input_task` moves to **`input_required`**: the `tasks/get` response's
|
|
`inputRequests` map (visible in the task's Full Task Object) carries an embedded
|
|
`elicitation/create`, surfaced through the same pending-request modal the MRTR
|
|
path uses — note the accurate wording _"your answer is submitted via a
|
|
tasks/update request (SEP-2663), not a retry"_. Answering it sends
|
|
**`tasks/update`** with the `inputResponses`, and the next poll completes the
|
|
task:
|
|
|
|

|
|
|
|
## Legacy era (2025-11-25, contrast — unchanged)
|
|
|
|

|
|
|
|
Connected with **Protocol Era = Legacy**. The Tasks tab is gated on
|
|
`capabilities.tasks`; `simple_task` is `taskSupport: "required"` so **Run as
|
|
task** is forced on. The legacy flow uses `tasks/list` to populate the list,
|
|
`tasks/get` to poll, and the blocking **`tasks/result`** to fetch the payload
|
|
(`{ "message": "Task completed: no message", "taskId": … }`). Note the legacy-only
|
|
**Logs** tab (this server advertises `logging`), absent from the modern monitor
|
|
set.
|
|
|
|
## Notes
|
|
|
|
- SDK v2 removed all tasks support **and** era-gates the `tasks/*` spec methods
|
|
out of the 2026-07-28 era on **both** the client (outbound) and server
|
|
(inbound), and its codec rejects a `resultType: "task"` result outright. So the
|
|
Inspector drives the extension itself: the task-creation frame is rewritten at
|
|
the transport into a `CallToolResult` carrying the handle (the true
|
|
`resultType: "task"` frame is still logged to the Protocol/Network tabs), and
|
|
`tasks/get` / `tasks/update` / `tasks/cancel` ride a raw-wire request channel
|
|
that carries the full modern envelope and is consumed by the transport before
|
|
the SDK Client sees it. The test server serves `tasks/*` from an Express
|
|
interceptor ahead of the SDK handler (the SDK's modern leg would answer them
|
|
`-32601`).
|
|
- On modern, task creation is **server-directed** (SEP-2663): the client declares
|
|
the extension once and any tool may return a task, so the Tools screen offers
|
|
**Run as task** for every tool on a modern connection (rather than gating on the
|
|
legacy per-tool `taskSupport`).
|