MCP Inspector

by modelcontextprotocol

10.3k stars
2.9k downloads
Not rated
GitHub Website

About

A developer tool for testing and debugging MCP servers.

Details

Author
modelcontextprotocol
GitHub stars
10,299
Downloads
2,868
Categories
Developer Tools

- Interactive web UI for testing MCP servers
- Supports stdio, SSE, and streamable-http transports
- Bearer token authentication with session tokens
- Export server launch configurations as mcp.json
- Real-time adjustable timeout settings
- Persists configuration across sessions

Setting up with Highlight

This MCP is not yet compatible with Highlight’s one-click setup. However, you can still use it with Highlight by following these steps:

  1. Download and install Highlight from highlightai.com/download
  2. Navigate to the plugins tab and select "Add Custom Plugin"
  3. Configure the plugin with the settings below
    Plugin Name MCP Inspector
    Command (node, npx, python, etc.)

    Please refer to the README for specific instructions on how to obtain API keys or other required environment variables.

  4. Enable "Start Automatically" if you want the plugin to start when Highlight launches

From the repository

Run npx @modelcontextprotocol/inspector to start the UI at http://localhost:6274. To inspect a server from its repository, use npx @modelcontextprotocol/inspector node build/index.js with optional arguments and -e flags for environment variables. A Docker image is also available. Ports can be customized via CLIENT_PORT and SERVER_PORT environment variables.

Claude Desktop / Cursor

Paste into your MCP client config file to install this server.

{
    "mcpServers": {
        "mcp inspector": {
            "inspector": {
                "command": "npx",
                "args": [
                    "@modelcontextprotocol/inspector"
                ]
            }
        }
    }
}

McpServers

{
    "inspector": {
        "command": "npx",
        "args": [
            "@modelcontextprotocol/inspector"
        ]
    }
}
A developer tool for inspecting[Model Context Protocol(MCP) servers. It ships as a single package,`@modelcontextprotocol/inspector`, that provides three ways to inspect a server: - **Web**— a Vite + React +](https://modelcontextprotocol.io)[Mantinesingle-page app with a Node backend. - **CLI**— a scriptable command-line client for automation, CI, and fast agent feedback loops. - **TUI**— an interactive terminal UI built with](https://mantine.dev)[Ink. All three run through one global`mcp-inspector`binary: ``` `npx @modelcontextprotocol/inspector # web UI (default) npx @modelcontextprotocol/inspector --cli # CLI npx @modelcontextprotocol/inspector --tui # TUI` ``` **Upgrading from v1?**Read the](https://github.com/vadimdemedes/ink)[v1 → v2 migration guide— CLI flags, the new`--config`vs.`--catalog`split, the Node engine bump, and what no longer ships. **Repo status.**This is the**v2**line of the Inspector. Active development happens on**`v2/main`**(the develop branch — all v2 PRs target it), which is merged into**`main`**at milestone releases;`main`is the default branch and holds the latest released v2, published to the npm`latest`tag. The legacy**v1**line lives on**`v1/main`**— security fixes only, published straight from that branch to the npm`v1-latest`tag (`npx @modelcontextprotocol/inspector@v1-latest`). See](https://github.com/modelcontextprotocol/inspector/blob/HEAD/docs/v1-to-v2-migration.md)[`AGENTS.md`for branch/board conventions. v2 is**not**an npm workspace. Each client under`clients/*`keeps its own`package.json`and`node_modules`; shared code lives in`core/`and is consumed via a`@inspector/core`build-time alias (no`package.json`of its own). A single`npm install`at the root cascades installs into every client (see](https://github.com/modelcontextprotocol/inspector/blob/HEAD/AGENTS.md)[Setup). ``` `inspector/ ├── clients/ │ ├── web/ # Web client (Vite + React + Mantine). src/ = browser app; server/ = Node dev/prod backend │ ├── cli/ # CLI client (tsup bundle, @inspector/core alias) │ ├── tui/ # TUI client (Ink + React, tsup bundle) │ └── launcher/ # Shared launcher — provides the `mcp-inspector` bin, dispatches to web/cli/tui ├── core/ # Shared code consumed via the `@inspector/core` alias (no package.json) │ ├── auth/ # OAuth: providers, discovery, storage, endpoint overrides, mid-session recovery (browser/node/remote backends) │ ├── client/ # Install-level client config (`client.json`): browser-safe parse/validate + Node load/save, remote backend, secrets │ ├── json/ # JSON + parameter/argument conversion utilities, and the nullable-union │ │ # schema collapse shared by the web and TUI form builders │ ├── logging/ # Silent pino logger singleton │ ├── mcp/ # InspectorClient runtime, state stores, transports, config import, │ │ # and the RFC 6570 URI-template helpers the web form and TUI expand through │ ├── node/ # Node-only shared helpers: version reader, hostUrl (host normalize/canonicalize + all-interfaces/loopback detection) │ ├── react/ # React hooks over the state stores │ └── storage/ # File I/O helpers for the OAuth persist backends ├── test-servers/ # Composable MCP test servers + fixtures used by integration tests ├── scripts/ # Root build/verify tooling (install cascade, smokes, verify-build-gate, verify-format-coverage, verify-dep-lockstep, pack:verify) ├── docs/ # Task-oriented guides (v1→v2 migration, server configuration, MCP App review, launcher/config plan) ├── specification/ # Design/build specifications ├── AGENTS.md # Contribution rules for agents AND humans (see below) └── README.md # You are here` ``` Each client has its own README with client-specific detail:](#setup)[web·](https://github.com/modelcontextprotocol/inspector/blob/HEAD/clients/web/README.md)[cli·](https://github.com/modelcontextprotocol/inspector/blob/HEAD/clients/cli/README.md)[tui·](https://github.com/modelcontextprotocol/inspector/blob/HEAD/clients/tui/README.md)[launcher. - ](https://github.com/modelcontextprotocol/inspector/blob/HEAD/clients/launcher/README.md)[Migrating from v1 to v2— the v1 → v2 map: CLI flag mapping,`--config`vs.`--catalog`semantics with before/after examples, the Node engine bump (`>=22.7.5`→`>=22.19.0`), env-var renames, and the sub-packages that no longer ship. - ](https://github.com/modelcontextprotocol/inspector/blob/HEAD/docs/v1-to-v2-migration.md)[MCP server configuration— which server(s) the Inspector connects to:`--catalog`vs.`--config`, ad-hoc targets, the`--`separator, the file format and its Inspector-specific per-server fields. Shared by all three clients; the cli and tui READMEs delegate their server-options sections to it. - ](https://github.com/modelcontextprotocol/inspector/blob/HEAD/docs/mcp-server-configuration.md)[Reviewing an MCP App— the CLI-first → one-shot-web recipe for automated App-tool review:`--app-info`probe → deep-link navigate → rendered widget, plus OAuth handoff and proxy support. - ](https://github.com/modelcontextprotocol/inspector/blob/HEAD/docs/mcp-app-review.md)[Launcher and config consolidation— why the launcher runs a client in-process rather than spawning it, and how the shared config processor fits in. ``` `npm install # root install; postinstall cascades into every client` ``` - **Fresh clone:**run`npm install`at the repo root. - **After a pull that changes a client's dependencies:**re-run`npm install`at the root to re-sync every client. The cascade (`scripts/install-clients.mjs`) is dev-only — it exits early when the package is installed as a dependency, and the published tarball ships only each client's`build/`, so end users are unaffected. Set`INSPECTOR_SKIP_CLIENT_INSTALL=1`to skip it. **Where a dependency is declared.**The MCP SDK packages (`@modelcontextprotocol/client`,`core`,`server`,`server-legacy`,`ext-apps`) live in the**root**`package.json`only — never in a client's. Node resolution walks up, so the root install is on every client's chain, and the root manifest is already what the published tarball resolves against. Declaring them per client installs a second copy that can drift from the root's, which is how two versions of`ext-apps`(and of the transitive v1`@modelcontextprotocol/sdk`) ended up in the tree before](https://github.com/modelcontextprotocol/inspector/blob/HEAD/docs/launcher-config-consolidation-plan.md)[#1970— and a second copy of`client`/`core`is the failure`vitest.shared.mts`carries a`dedupe`workaround for. The same root-only placement holds for anything reached solely through root-owned code with no manifest of its own (`test-servers/src`,`core/`), and`vitest.shared.mts`aliases those to the repo root —`express`and`yaml`, both reached through`test-servers/src`, are the two today.**Whether such a package is a`dependency`or a`devDependency`follows from who consumes it at runtime, not from where it is declared:**anything`core/`imports at runtime must be a root**`dependency`**, because the client builds externalize npm packages and a published install resolves them from the root manifest, where devDependencies are absent.`express`is test-only and is a devDependency;`yaml`currently sits in`dependencies`.**`vite`and`@vitejs/plugin-react`are root`dependencies`for the same reason, not by mistake**— they look like build tooling, but`clients/web/server/start-vite-dev-server.ts`imports them at runtime for`mcp-inspector --web --dev`, and`clients/web/tsup.runner.config.ts`lists both as`external`, so a published install resolves them from the root manifest. Moving them to`devDependencies`would break`--web --dev`for consumers (and the on-demand`vite build`in`ensure-web-build.ts`) while passing every local check. It does mean they show up under`npm audit --omit=dev`, which is a feature: they really are in the production tree. For day-to-day web iteration, run Vite directly from the web client (fast HMR, no launcher build needed): The launcher-driven scripts below run the**built**launcher, so build first (`npm run build`): ``` `npm run web # prod web launcher against clients/web/dist npm run web:dev # web launcher in --dev mode (Vite)` ``` `core/`holds the logic shared by all three clients so that web, CLI, and TUI behave identically. Its entry point is the**`InspectorClient`**class (`core/mcp/`), which owns the connection to an MCP server, the request/response lifecycle, and a set of state stores;`core/react/`exposes React hooks over those stores that both the web and TUI (Ink) React trees consume. OAuth (`core/auth/`) is factored into isomorphic logic plus browser/node/remote backends so the same flows work in the browser, in Node, and against a remote backend. `core/`intentionally has**no`package.json`**— it is not published on its own. Each client bundles it in via a`@inspector/core`alias: - **CLI / TUI:**`esbuildOptions.alias`in their`tsup.config.ts`maps`@inspector/core`→ the repo`core/`directory, and`noExternal: ](https://github.com/modelcontextprotocol/inspector/issues/1970)[/^@inspector\/core/]`inlines it into the bundle. - **Web:**the same alias in`clients/web/vite.config.ts`for the browser app and the Node backend runner. Publishing`core/`as its own package (e.g. for third parties to build on) is deliberately deferred — see issue[#1636. ## Web client: "dumb components" + Storybook The v2 web client is built from**presentational ("dumb") components**— they accept data and callbacks as props and contain only display logic, with no direct data fetching or client state. State comes from the`@inspector/core`hooks, wired in near the top of the tree. This keeps components isolated, testable, and documentable. That approach is what makes**Storybook**first-class here: every screen and element component has a`*.stories.tsx`file (96+ stories) that renders it against fixture props. Storybook**play functions**double as interaction tests, run headless in CI (`npm run ci:storybook`, Chromium via Playwright). Styling follows a strict Mantine-first convention (theme variants and component props over CSS classes,`--inspector-*`CSS custom properties over raw color literals). The full rules live in](https://github.com/modelcontextprotocol/inspector/issues/1636)[`AGENTS.md`under**React instructions**— read them before touching web UI. Element components live in`clients/web/src/components/elements/`; theme variants in`clients/web/src/theme/`. `test-servers/`provides**composable MCP servers**used by the integration and smoke suites, so tests exercise a real server over a real transport instead of mocks. A server is assembled from**presets**(fixture factories in`test-servers/src/preset-registry.ts`— tools, resources, prompts, tasks, elicitation, sampling, OAuth, …) and can be driven two ways: - **In-process**— import the factories (`createTestServerHttp`,`createEchoTool`, …) and run the server inside the test's event loop (used by the HTTP integration paths). - **As a subprocess**—`test-servers/build/test-server-stdio.js`is spawned as a real stdio child (used by the CLI smoke and stdio integration tests). Configure a server declaratively with a JSON config (see`test-servers/configs/*.json`) selecting presets, then load it via`--config`. Because the servers are spawned as real subprocesses, the build output must exist first: ``` `npm run test-servers:build # (from clients/web) → tsc -p test-servers, emits test-servers/build/` ``` The Vite alias`@modelcontextprotocol/inspector-test-server`(in`clients/web/vite.config.ts`) points at`test-servers/build/index.js`so`getTestMcpServerPath()`resolves to a real`.js`path. A streamable-HTTP server can also serve the**modern (2026-07-28) protocol era**via the SDK's`createMcpHandler`: - Set`transport.modern`in the JSON config —`true`for dual-era stateless serving, or`{ "legacy": "reject" }`for modern-only strict. - Or pass`modern`on the`ServerConfig`for an in-process`createTestServerHttp`. This is what lets an Inspector connection negotiating`protocolEra: "auto" | "modern"`reach the modern leg (populated`server/discover`, sessionless). See`test-servers/configs/modern-http.json`. Each config below is a ready-made server for exercising one feature by hand. Load one with`--config`, and unless noted, connect with**Protocol Era = Modern**. `mcp-app-http.json`serves the`mcp_app_demo`tool (`_meta.ui.resourceUri`) alongside its`mcp_app_demo_widget`UI resource, so the**Apps**tab has a real App to render. It is a plain streamable-HTTP server — connect with the**default (legacy)**protocol era, not Modern. Open the Apps tab, select`mcp_app_demo`, give it a title and click**Open App**: the widget renders inside the sandbox iframe and exercises the host-side UI protocol surface — host-context render,`size-changed`,`ui/message`, and a log line into the**App logs**panel. Because the widget is served through the sandbox proxy page, this config is also what reproduces](https://github.com/modelcontextprotocol/inspector/blob/HEAD/AGENTS.md)[#1859(a missing`clients/web/static/sandbox_proxy.html`surfaces here as a "Sandbox not loaded" message in place of the widget) — a failure that only ever appeared in an installed package, never in the repo. For the scripted version of the same flow (`--app-info`probe → deep link → rendered widget), see](https://github.com/modelcontextprotocol/inspector/issues/1859)[Reviewing an MCP App. `modern-mrtr-http.json`serves the`mrtr_confirm`tool (preset`mrtr_confirm`,`createMrtrTool`) over the modern leg. Its handler returns`inputRequired(...)`embedding a form elicitation, so invoking it produces a real round-trip:`input_required`→ the client fulfils the embedded elicitation and retries with a new id →`complete`. The Inspector drives MRTR manually (`inputRequired: { autoFulfill: false }`), so the embedded elicitation pauses at the pending-request modal (tagged "input_required") for you to answer, then the retry completes. Useful for eyeballing both that pending-request UX and the Protocol view's MRTR conversation grouping. `mrtr-showcase-http.json`bundles every MRTR preset in one server: Run`mrtr_empty`and answer its single elicitation: the Protocol tab groups the exchange as an MRTR conversation ending**COMPLETE**, and the Results panel says**"Empty result — The tool call completed successfully and returned no content."**On the broken build that same result rendered as**"No results yet"**, the panel's pre-run placeholder (](https://github.com/modelcontextprotocol/inspector/blob/HEAD/docs/mcp-app-review.md)[#1860) — so a call the user had just watched succeed read as a call that never ran. An empty`content`array with no`structuredContent`is a legal`CallToolResult`, and the panel only ever mounts once a result exists, so the placeholder wording could not be true there. (The neighbouring half of the same gap — a result whose payload lives only in`structuredContent`— was closed by](https://github.com/modelcontextprotocol/inspector/issues/1860)[#1908.) The legacy`collect_elicitation`preset calls`server.elicitInput`, which errors on the 2026-07-28 leg — server→client requests aren't allowed there. MRTR is the modern replacement. #### Network tab — standardized headers and error taxonomy `modern-network-http.json`covers SEP-2243 / SEP-2575. It serves a`get_weather`tool whose`city`argument carries an`x-mcp-header: "City"`annotation, so a modern client mirrors it to`Mcp-Param-City`. It also serves four`trigger_*`tools that the modern leg's spec-error injector (`transport.modern.injectSpecErrors: true`) answers with a real HTTP status plus JSON-RPC error body: Open the Network tab to see the mirrored`Mcp-*`headers highlighted, sentinel values decoded, and each error rendered distinctly. **`Mcp-Param-*`mirroring is built by the Inspector, not the SDK.**The SDK only mirrors inside`client.callTool()`, and skips it in the browser (`detectProbeEnvironment() !== "browser"`). The Inspector routes`tools/call`through`client.request()`to drive MRTR manually, so it builds the mirrored headers itself (](https://github.com/modelcontextprotocol/inspector/issues/1908)[#1846) — on**every**client, web included, since the web client's upstream request is issued by the Node backend rather than the browser. So`get_weather`is callable from web, CLI, and TUI alike, in both the plain and "Run as task" forms. - `echo`— plain tool. - `get_weather`— a**valid**`x-mcp-header: "City"`annotation on its`city`argument. - `invalid_header_tool`— an annotation using the header name`"Bad Header"`. The space makes it an invalid RFC 9110 token, so the whole tool definition is invalid. - `trigger_invalid_params`— answered with a real`-32602 Invalid params`error whose message is*not*about a missing tool. Open the Tools tab:`get_weather`'s detail panel shows a**"Mirrored request headers (SEP-2243)"**section (`city → Mcp-Param-City`), and`invalid_header_tool`appears struck-through under an**"Excluded (SEP-2243)"**divider with the reason on hover. A conforming Streamable HTTP client MUST drop it from`tools/list`; the Inspector surfaces*why*. Under SDK v2 a`tools/call`rejecting with`-32602`renders as a distinct error panel rather than an`isError`result — headed**"Unknown Tool"**when the message names a missing tool, or**"Invalid Parameters"**otherwise (run`trigger_invalid_params`). `pagination-http.json`serves 12 tools, 12 resources, and 12 prompts (presets`numbered_tools`/`numbered_resources`/`numbered_prompts`,`count: 12`) with a`maxPageSize`of 4 each, so every list paginates into three pages. Turn on**"Fetch Lists One Page at a Time"**(Server Settings — the`paginatedLists`setting, or the**Paginated**switch in a list sidebar) and the lists load page 1 only (4 items) with a**Load next page**control and an*N pages loaded*status. Each click fetches the next 4 and appends them; Refresh resets to page 1. With the switch off (the default), the same lists auto-aggregate all three pages on connect. `structured-output-http.json`serves`list_items`(nested`structuredContent`— objects inside arrays inside an object, the shape from](https://github.com/modelcontextprotocol/inspector/issues/1846)[#1908),`get_temp`(a flat three-key payload), and`echo`(no`outputSchema`at all). It is a plain streamable-HTTP server — connect with the**default (legacy)**protocol era. Run`list_items`from the Tools tab: the result panel shows the`content](https://github.com/modelcontextprotocol/inspector/issues/1908)[]`text summary ("Found 2 items.")**and**a collapsible**Structured Output**section rendering the schema-validated payload as pretty-printed, copyable JSON. That section is what v2 was dropping — a tool declaring an`outputSchema`returns its real data there, and the text block usually only summarizes it. Run`echo`to confirm the section is absent when a result carries no`structuredContent`. `duplicate-tool-names-http.json`serves`get_weather`,`get_temp`,`echo`, and`add`, then repeats`get_weather`and`echo`at the end of`tools/list`with the same`name`and a`(duplicate)`title (`duplicateToolNames`). No preset can produce this shape — the SDK's`registerTool`rejects a repeated name — but a real server can and does, and the Inspector has to render it faithfully. Connect (default legacy era), open the Tools tab, and type`get`into**Search tools**: the list must narrow to exactly the three`get_*`rows. On the broken build it kept a stale`echo`row, because the sidebar keyed rows by`tool.name`alone and the colliding keys orphaned a child during reconciliation ([#1957). The duplicated copies are appended rather than placed beside their twin on purpose. React matches a leading run of same-key children first, so a head-adjacent duplicate happens to line up and the defect hides; separating the pair is what makes it observable — and it is also the realistic shape, two tool sources concatenated. `nullable-fields-http.json`serves`record_shipment`, whose four arguments are each declared with Zod's`.nullish()`— "optional**and**explicitly nullable". That compiles to`anyOf: ](https://github.com/modelcontextprotocol/inspector/issues/1957)[<branch>, { "type": "null" }]`, so the real type (and, for the enum, its`enum`list) sits on a branch rather than at the top level.`get_temp`sits alongside it with a plain, non-nullable`units`enum for comparison. Plain streamable-HTTP — connect with the**default (legacy)**protocol era. Open the Tools tab and select`record_shipment`:`direction`must render as a**Select**(`envio`/`recebimento`) with a clear button that sets it back to`null`,`reference`as a text input,`quantity`as a number input, and`express`as a checkbox. On the broken build every one of them fell through to the raw-JSON textarea, which re-escaped its own contents on each keystroke until the value was unusable ([#1928). The tool echoes the arguments it received, so the result panel shows exactly what was sent. The**TUI**had the same gap and is worth checking against the same server (`--tui`, then test`record_shipment`):`direction`is a select,`quantity`an integer field,`express`a boolean. Both clients now share one collapse step —`normalizeNullableUnion`in](https://github.com/modelcontextprotocol/inspector/issues/1928)[`core/json/nullableUnion.ts`— precisely so they cannot drift on which schemas they can render. `rfc6570-templates-http.json`serves two resource templates straight out of](https://github.com/modelcontextprotocol/inspector/blob/HEAD/core/json/nullableUnion.ts)[#1919—`events_by_topic`(`foobar://events/{topic}`) and`events_by_query`(`foobar://events{?topic}`) — each echoing the URI it was matched against, plus a plain`foobar://events`resource (see below). Plain streamable-HTTP; connect with the**default (legacy)**protocol era. Open the Resources tab and pick**events_by_topic**, then enter`foo/bar`. The request must go out as`foobar://events/foo%2Fbar`, and the result echoes back the URI the server matched. On the broken build the value was spliced in raw, so the slash created a second path segment and the SDK's matcher answered`-32602 Resource not found: foobar://events/foo/bar`— the exact failure in the issue. The same holds for`?`,`#`,`%`, spaces, and non-ASCII text. **events_by_query**is the half that was invisible: the old`/\{(\w+)\}/g`scan could not see an expression carrying an operator, so no`topic`input was rendered at all. It now appears, marked**Optional**— RFC 6570 drops the whole expression when the variable is undefined, so reading with the field blank requests`foobar://events`, and filling it in requests`foobar://events?topic=foo%2Fbar`. The URI preview beside the title shows the partially-expanded form as you type, leaving unfilled expressions standing as written. The plain`foobar://events`resource is registered deliberately, not as filler. The SDK's`UriTemplate.match()`compiles`{?topic}`to a**required**`\?topic=(](https://github.com/modelcontextprotocol/inspector/issues/1919)[^&]+)`, so a template alone cannot serve the blank read —`match("foobar://events")`returns`null`. A real server exposes the unfiltered collection as its own resource; the showcase does the same so that step actually resolves. The web client and the TUI expand through one shared helper,[`core/mcp/uriTemplate.ts`— the web Resources form directly, the TUI via`InspectorClient.readResourceFromTemplate`— and both derive their**form fields**from its parser too, which is the half that makes the sharing real: a form submits values under the names it rendered, so a parser that mangles a name silently drops the value at expansion time. (The CLI is not a consumer: it has no template form, and its`resources/read`passes the already-expanded`--uri`straight through.) The SDK's`UriTemplate`is still used, but only to*validate*a template (constructing it is what rejects an unclosed expression). Its expander is not, because it is incomplete in five ways — each measured against the pinned SDK, not inferred: The`;`and`:3`rows are the ones a user sees directly: on the SDK's parse the form renders fields literally labelled`;id`and`id:3`. The`+`/`#`row is silent corruption rather than over-escaping — an IPv6 literal or an already-encoded path arrives at the server altered. A template that cannot be expanded at all — an out-of-grammar modifier (`{id:abc}`), or an expression declaring no variable (`{}`,`{a,}`,`{?}`) —**withholds the read**rather than sending something. Pick**events_malformed**(`foobar://events/{topic:abc}`) to see it: Read Resource is disabled, the reason is printed under the form, and the preview shows the template as the server declared it. The alternative is worse than it looks:`x://{}`would otherwise expand to`x://`with no inputs rendered, so the form's "everything required is filled" check passes vacuously and it reads a URI that is not the template the server published. Literals are pct-encoded on expansion too (RFC 6570 §3.1):`café/{var}`sends`caf%C3%A9/value`, not raw UTF-8 in the path — something the SDK's expander does not do either. And the*names*a template may use are RFC 6570's`varchar`plus a labelled tolerance for`-`and`~`: the conformance suite rejects`{default-graph-uri}`, but real servers publish such names and the SDK's matcher round-trips them, so the Inspector expands them and marks the variable`conforming: false`rather than refusing a resource that demonstrably works. An**undefined**variable is what omits its expression — a variable defined as the empty string expands (`x{?q}`gives`x?q=`,`x{;q}`gives`x;q`, per RFC 6570 §3.2.7). The expander honors that distinction, so a caller such as`readResourceFromTemplate`can request either URI. Collapsing the two is a*form*concern, not a template one: both clients seed every declared variable with`""`and a text input cannot express "defined but empty", so each form drops its blanks (`definedValues`) on the way in. Requiredness is a property of the**expression**, not the variable: RFC 6570 drops undefined names from a multi-name expression, so`{a,b}`with only`a`filled is expandable and a form must not block it.`requiredGroups`returns one entry per non-omittable expression and`hasRequiredValues`asks that each be satisfied by any one of its names — which no per-variable flag can express once a name recurs across expressions (`{a,b}{a,c}`is satisfied by filling`b`and`c`). `advertised-extensions-http.json`serves`echo`(always) and a`get_weather`tool**gated on the`io.modelcontextprotocol/tasks`extension**(`extensionGatedTools`): the tool is registered but starts disabled, and the server enables it on`notifications/initialized`only when the client declared that extension in its`capabilities.extensions`. - Connect — the Inspector advertises the Tasks extension by default, so the Tools list shows both`echo`and`get_weather`. - Open**Server Settings → Advertised Extensions**, uncheck**Tasks (io.modelcontextprotocol/tasks)**, and reconnect. - The client now advertises no extensions, the server never enables`get_weather`, and the Tools list shows only`echo`. This is the debugging knob for a server legitimately changing tool registration based on what the client advertises. Legacy stateful leg only — the modern per-request leg has no persistent`oninitialized`. `logging-legacy-http.json`and`logging-modern-http.json`both serve`logging: true`plus a`send_notification`tool that emits a`notifications/message`at a chosen level. The legacy one is a plain streamable-HTTP server; the modern one sets`transport.modern: true`. - **Legacy**— the**Logs**tab gives a session-scoped**Set Active Level**selector +**Set**button. Calling`send_notification`streams the log into the panel. - **Modern**— the same tab instead shows**Log Level per Request**. Pick a level to opt in and the client stamps`_meta](https://github.com/modelcontextprotocol/inspector/blob/HEAD/core/mcp/uriTemplate.ts)["io.modelcontextprotocol/logLevel"]`on every subsequent request (verify in the Network tab's request body). Calling`send_notification`streams the log over the request's SSE response. Set it back to**Off**and the same call is silently gated — the request omits the`logLevel`key, so the log never arrives. That gating is faithful to the spec ("a server MUST NOT emit`notifications/message`for a request that didn't opt in") because`send_notification`emits through the SDK's request-scoped, threshold-aware`extra.log`(`ctx.mcpReq.log`). On the modern 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`. Because it emits through the request's`notify`, the modern response upgrades to SSE and the log rides the originating request's stream. `subscriptions-legacy-http.json`and`subscriptions-modern-http.json`both serve three`numbered_resources`with`subscriptions: true`. The legacy one also serves an`update_resource`tool; the modern one sets`transport.modern: true`. The modern config deliberately**omits**`update_resource`. The SDK's modern leg is stateless/per-request (`createMcpHandler(() => createMcpServer(config))`), so the tool would run against a throwaway server instance — the content change wouldn't persist for the next`resources/read`, and its`resources/updated`wouldn't reach the separate listen stream. More confusing than useful.
No reviews yet — be the first

Sign in to leave a review

Use Google, GitHub, or an email account so ratings stay tied to real people.

Email sign in

No reviews posted yet.