Skip to content

v2.1.0

Choose a tag to compare

@github-actions github-actions released this 02 Sep 02:39
· 26 commits to main since this release

Re-based on the MCP 2026-07-28 revision: tools/list is now one complete, deterministic, cacheable listing that never changes, and the runtime browser_tools activation toggle 2.0.0 shipped three days ago is removed. The honest headline on cost: the default listing grows from ≈2,457 to ≈8,471 tokens (46 entries, 33,886 bytes, measured over raw stdio against the real entry point), because a standards-conformant list must be complete and static — still −58% against 1.x's ≈20,200. CDP_TOOL_PROFILE=core keeps the lean ≈2,266-token surface (13 entries, 9,066 bytes, −89% vs 1.x) for hosts that eagerly load every schema. Tool names, parameters, and schema shapes are unchanged, now gated by a durable wire-vs-manifest deep-equal check rather than the one-off strip-descriptions comparison 1.10.0 and 2.0.0 used. No new runtime dependency.

Added

  • The 2026-07-28 protocol era, actually served. src/mcp.ts connects through serveStdio (dual-era, default legacy: 'serve') instead of Server.connect(new StdioServerTransport()). A modern client that opens with server/discover gets supportedVersions: ["2026-07-28"], resultType: "complete", and the _meta['io.modelcontextprotocol/serverInfo'] envelope; a 2025-era client that opens with initialize still negotiates 2025-11-25 and sees no difference at all. The era is pinned per connection, and the server logs which one it pinned to stderr.
  • Cache hints on the listing. cacheHints now declares ttlMs: 3600000 + cacheScope: "public" for both tools/list and server/discover (emitted on modern-era connections), instead of the SDK's conservative { ttlMs: 0, cacheScope: 'private' } default. The listing is identical for every caller — a stdio server has no per-caller auth — and a restart under a different CDP_TOOL_PROFILE or --browser is a different server configuration, not a mid-life change to this one.
  • describe_tool now doubles as the catalog. Its name parameter became optional; called with no name it returns the grouped catalog of every tool the selected browser supports — a header line (cdp-toolkit 2.1.0 · browser=chrome · 45 tools available, 46 in tools/list (CDP_TOOL_PROFILE=full)), then one [listed]/[hidden] <group> (n): names line per group, then Unlisted tools are callable by name; describe_tool {name} documents any of them. The whole 45-tool catalog is 1,141 characters.
  • CDP_TOOL_PROFILE accepts a comma-separated group list on top of full and core — e.g. core,network,console (17 entries, 11,893 bytes, ≈2,973 tokens). core is always included, so a list can never strand the basics, and the label is canonicalised to group order so equivalent spellings read the same. An unknown group name is now a configuration error rather than a silent fallback: the server prints CDP_TOOL_PROFILE: unknown tool group 'x'. Known: full, core, input, … and exits 1.
  • src/version.ts — the VERSION constant moved out of src/mcp.ts into its own module, and a test asserts it equals package.json's version. The two drifted apart between 1.11.0 and 2.0.0; that can no longer happen quietly.
  • test/mcp-protocol.test.ts — raw-stdio protocol coverage: server/discover and tools/list field shapes, the cache hints, byte-identical deterministic ordering across repeated calls and connections, the wire-vs-manifest deep-equal gate (the durable replacement for the one-off strip-descriptions gate), the legacy initialize handshake, a -32022 negative control, each profile, and proof that an unlisted tool is still callable. test/tool-profile.test.ts covers resolveProfile directly.

Changed

  • The tool listing is static. It is computed once at startup — describe_tool first, then manifest order, filtered by backend availability and then by the profile — frozen, and returned as the same array to every tools/list on every connection. capabilities.tools.listChanged is now false; nothing will ever notify, because nothing ever changes. The 2026-07-28 revision makes it a MUST that a server's tool set not change as a side effect of other requests on the connection, and the client best-practices page notes that adding or removing tool definitions mid-conversation invalidates the host's prompt cache.
  • CDP_TOOL_PROFILE default flipped core → full. Three reasons, in order of weight: (1) the standard puts lazy discovery on the host — the client runs its own catalog → inspect → execute funnel — and a host that defers schemas (Claude Code lists MCP tool names and loads schemas on demand) pays little for a complete list; (2) consumers that hold per-tool permission allowlists, or per-tool interception keyed on the tool NAME, for non-core tools (the console and network readers, performance_analyze_insight) were silently broken by a core default, because those tools were simply absent from tools/list; (3) a list that never changes never invalidates the prompt cache. core remains a first-class choice for hosts that eagerly load every schema — it is now a deployment decision, not a default imposed on everyone.
  • Host protocol eras, MEASURED 2026-09-01. Two real hosts were pointed at the 2.1 server through a wrapper that tees its stderr: Claude Code 2.1.258 and Codex CLI 0.147.0 both connected and both logged connection pinned to the legacy protocol era — today they still open with initialize even though their binaries carry the 2026-07-28 client strings (capability present, not exercised by default). The SDK 2.0.0 Client does the same unless versionNegotiation opts in. So the dual-era default is load-bearing, not courtesy: a modern-only server would be unreachable from every host measured. The modern era is currently exercised by the raw-stdio test suite and by mcp:smoke's pinned client. The static listing pays off on either era; the ttlMs/cacheScope hints are emitted only on modern connections.
  • The server instructions block trades the activation walkthrough for how to read a fixed, cacheable listing (tools/list is complete and fixed for this process's life — cache it; describe_tool {name} / describe_tool {}; profile semantics; unlisted tools stay callable). 2,019 → 1,976 characters, still under the 2 KB cap.
  • SDK usage, deliberately unchanged in shape. The server stays on the low-level Server with string-method setRequestHandler('tools/list', …), which SDK 2.0 still supports for advanced use cases (and which McpServer itself is built on; McpServerFactory explicitly accepts a Server). McpServer.registerTool was evaluated and rejected on two grounds: it wants a Standard-Schema (zod) object for inputSchema, and CONTRACT.md rule 1 forbids any runtime dependency beyond the SDK — our manifest is plain JSON Schema — and it cannot express "unlisted tools remain callable by name", which is the whole point of CDP_TOOL_PROFILE.
  • test/mcp-smoke.ts additionally connects with the SDK client pinned to 2026-07-28 and asserts the modern era end to end, alongside the existing legacy handshake and live tools/call round trip.
  • Under CDP_BROWSER=firefox the default listing is 35 entries (26,533 bytes, ≈6,633 tokens): the 34 tools Firefox's BiDi surface supports, plus describe_tool.

Removed

  • browser_tools — the list_groups/activate/deactivate/set_profile meta-tool, removed three days after 2.0.0 shipped it. Mutating the advertised tool set at runtime and firing notifications/tools/list_changed is precisely what the 2026-07-28 revision forbids (the tool set MUST NOT change as a side effect of other requests) and what the client best-practices page warns invalidates the host's prompt cache. Its open question — "does any given client actually re-fetch on listChanged?" — becomes moot once the list is static. Calling it now returns unknown tool: browser_tools; nothing else needs it, because every tool it could have activated is either already listed under the new full default or reachable by name under a narrower profile, and describe_tool {} covers what list_groups reported.

Fixed

  • v2.0.0 was not on the current protocol revision, despite running on the 2.0 SDK. Probed raw over stdio, it answered server/discover with -32601 Method not found and served a tools/list carrying no resultType, ttlMs, or cacheScope: a 2025-11-25 (legacy-era) server, because Server.connect(new StdioServerTransport()) is the legacy path. The "2026-07-28" literal that appears inside the SDK packages is SUPPORTED_MODERN_PROTOCOL_VERSIONS in @modelcontextprotocol/server/stdio — the modern era is reachable only through serveStdio (or the HTTP createMcpHandler), never through connect(). Both eras are now served.