feat: seed the MCP org example server as a remote first-run card (#2201)

Adds a third entry to `DEFAULT_SEED_CONFIG`, `example-server-default`, a
Streamable HTTP card pointing at the MCP org's own feature-reference server
(https://example-server.modelcontextprotocol.io/mcp). Until now every seeded
server was a local stdio process and every remote example in the docs was a
placeholder, so trying a remote connection meant finding or standing up a
server first.

The endpoint is protected by OAuth, but its authorization server advertises
Dynamic Client Registration and fronts a *mock* upstream IdP, so the whole
round trip completes with no account, no API key and no pre-registered client
— which is what makes it viable as a shipped default. Verified end to end
against a prod build: the card connects, negotiates MCP 2025-11-25 and lists
the reference server's tools.

The seed carries no `protocolEra`. That is not a default-by-omission: the
server answers the plain `initialize` handshake and rejects `server/discover`,
so it has no modern (SEP-2663) era to negotiate — `"auto"` would only buy a
failed probe and `"modern"` would fail the connection outright.

Docs updated alongside: the seeded-catalog block and per-client table in
`docs/mcp-server-configuration.md`, the Streamable HTTP example there (which
now leads with the live endpoint and keeps a placeholder to illustrate
`headers`), and the seed description in `clients/launcher/README.md`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012x3rC3JXU17aW3uSwjPoi5
Signed-off-by: cliffhall <cliff@futurescale.com>
This commit is contained in:
cliffhall 2026-09-06 22:57:53 -04:00
commit 3e2546b040
5 changed files with 79 additions and 15 deletions

View file

@ -19,7 +19,7 @@ editable (see [specification/v2_catalog_launch_config.md](../../specification/v2
| Invocation | Server list | Editable in UI? |
| ---------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | --------------- |
| `mcp-inspector --web` | Default catalog `~/.mcp-inspector/mcp.json` (seeded with the two sample servers if missing) | Yes |
| `mcp-inspector --web` | Default catalog `~/.mcp-inspector/mcp.json` (seeded with the three sample servers if missing) | Yes |
| `mcp-inspector --web --catalog <path>` (or `MCP_CATALOG_PATH=<path>`) | That file as the active catalog (same seed-if-missing behavior) | Yes |
| `mcp-inspector --web --config <path>` | That file as a **read-only session** — shown but never written, seeded, or migrated (safe for a foreign config) | No |
| `mcp-inspector --web --server-url <url> --transport http --header "Name: Value"` (or a positional command) | One ad-hoc server held in memory, connectable with the given `--header`s | No |
@ -30,8 +30,10 @@ and is applied to that connection (it is no longer a warn-only no-op).
**Seed contents are web-specific.** When the web backend creates a missing
writable catalog it seeds `DEFAULT_SEED_CONFIG` (`core/mcp/serverList.ts`) — a
`filesystem-server-default` scoped to `/tmp` plus the canonical
`everything-server-default` — so a first launch has something to connect to.
`filesystem-server-default` scoped to `/tmp`, the canonical
`everything-server-default`, and `example-server-default`, the MCP org's
remote feature-reference server (Streamable HTTP, no local process and no API
key needed) — so a first launch has something to connect to.
The CLI and TUI seed an **empty** catalog instead; see the next section. A
read-only `--config` is never seeded on any surface.
@ -49,7 +51,7 @@ resolved by the shared `core/mcp/node/config.ts` helpers:
Note the seed contrast with `--web` above: the CLI and TUI write an **empty**
`{ "mcpServers": {} }` (`seedEmptyCatalog` in `core/mcp/node/config.ts`), not
the web client's two sample servers — they are non-interactive or list-driven,
the web client's three sample servers — they are non-interactive or list-driven,
so sample entries would be noise rather than a starting point.
Rules (shared `serverSourceConflict`): `--catalog` and `--config` are mutually

View file

@ -3,6 +3,7 @@ import {
cleanAuthorizationParams,
cleanRoots,
DEFAULT_SEED_CONFIG,
EXAMPLE_SERVER_URL,
envPairsToRecord,
envRecordToPairs,
expectedSecretFields,
@ -865,17 +866,22 @@ describe("serializeMcpConfig", () => {
});
describe("DEFAULT_SEED_CONFIG", () => {
it("contains the two canonical seed servers", () => {
it("contains the three canonical seed servers", () => {
expect(Object.keys(DEFAULT_SEED_CONFIG.mcpServers)).toEqual([
"filesystem-server-default",
"everything-server-default",
"example-server-default",
]);
});
it("uses stdio + npx for both seeds", () => {
for (const cfg of Object.values(DEFAULT_SEED_CONFIG.mcpServers)) {
expect(cfg.type).toBe("stdio");
if (cfg.type === "stdio") {
it("uses stdio + npx for both local seeds", () => {
for (const key of [
"filesystem-server-default",
"everything-server-default",
]) {
const cfg = DEFAULT_SEED_CONFIG.mcpServers[key];
expect(cfg?.type).toBe("stdio");
if (cfg?.type === "stdio") {
expect(cfg.command).toBe("npx");
}
}
@ -887,6 +893,25 @@ describe("DEFAULT_SEED_CONFIG", () => {
expect(fs.args).toContain("/tmp");
}
});
it("seeds the MCP org example server over streamable-http", () => {
const example = DEFAULT_SEED_CONFIG.mcpServers["example-server-default"];
expect(example?.type).toBe("streamable-http");
if (example?.type === "streamable-http") {
expect(example.url).toBe(EXAMPLE_SERVER_URL);
expect(example.url).toBe(
"https://example-server.modelcontextprotocol.io/mcp",
);
}
});
it("leaves the remote seed on the default protocol era", () => {
// Omitted rather than written as "legacy": `serverEntriesToMcpConfig`
// strips the field when it equals DEFAULT_PROTOCOL_ERA, so a seed that
// spelled it out would round-trip into a different file than it seeded.
const example = DEFAULT_SEED_CONFIG.mcpServers["example-server-default"];
expect(example).not.toHaveProperty("protocolEra");
});
});
describe("extractSecretsFromStored", () => {

View file

@ -95,6 +95,7 @@ describe("useServers", () => {
expect(ids).toEqual([
"filesystem-server-default",
"everything-server-default",
"example-server-default",
]);
// Map key is used as both id and name; connection initializes disconnected
for (const s of result.current.servers) {

View file

@ -1055,11 +1055,31 @@ export function expectedSecretFields(stored: StoredMCPServer): string[] {
return fields;
}
/**
* Public MCP org demo server seeded as the remote example (#2201). It is the
* feature-reference server the MCP org hosts, so pointing a shipped default at
* it keeps the seed in-org rather than at a third-party endpoint.
*
* Its authorization server advertises Dynamic Client Registration and fronts a
* *mock* upstream IdP, so the whole OAuth round trip completes with no account,
* no API key and no pre-registered client — which is what makes it usable as a
* seed at all. Everything it exposes is synthetic reference data.
*/
export const EXAMPLE_SERVER_URL =
"https://example-server.modelcontextprotocol.io/mcp";
/**
* Default seeds written to `~/.mcp-inspector/mcp.json` on first launch when
* the file is absent. Picked to cover the two shapes a developer reaches for
* first: a real filesystem scoped to /tmp, and the canonical "everything"
* reference server.
* the file is absent. Picked to cover the three shapes a developer reaches for
* first: a real filesystem scoped to /tmp, the canonical "everything"
* reference server, and a remote Streamable HTTP server behind OAuth.
*
* The remote seed carries no `protocolEra`, so it connects under
* `DEFAULT_PROTOCOL_ERA` (`"legacy"`) like every other entry that omits the
* field. That matches the server, which answers the plain `initialize`
* handshake at 2025-11-25 and rejects `server/discover` — it has no modern
* (SEP-2663) era to negotiate, so `"auto"` would only cost a failed probe and
* `"modern"` would fail the connection outright.
*/
export const DEFAULT_SEED_CONFIG: MCPConfig = {
mcpServers: {
@ -1073,5 +1093,9 @@ export const DEFAULT_SEED_CONFIG: MCPConfig = {
command: "npx",
args: ["-y", "@modelcontextprotocol/server-everything"],
},
"example-server-default": {
type: "streamable-http",
url: EXAMPLE_SERVER_URL,
},
},
};

View file

@ -32,7 +32,7 @@ Use `--config` when pointing the Inspector at a config file belonging to somethi
A missing **writable** catalog is created on first use, but **what gets written differs by client**:
- **Web** seeds two sample servers (`DEFAULT_SEED_CONFIG` in `core/mcp/serverList.ts`) so a first launch has something to connect to immediately:
- **Web** seeds three sample servers (`DEFAULT_SEED_CONFIG` in `core/mcp/serverList.ts`) so a first launch has something to connect to immediately — two local stdio servers and one remote:
```json
{
@ -46,11 +46,17 @@ A missing **writable** catalog is created on first use, but **what gets written
"type": "stdio",
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-everything"]
},
"example-server-default": {
"type": "streamable-http",
"url": "https://example-server.modelcontextprotocol.io/mcp"
}
}
}
```
`example-server-default` is the feature-reference server the MCP org hosts, and it is the one seed that needs no local process. It is protected by OAuth, but its authorization server supports Dynamic Client Registration and sits in front of a *mock* upstream identity provider — so connecting takes no account, no API key and no client registration of your own: press **Connect**, approve the consent screen, and the flow completes. Everything it serves is synthetic reference data. It is a legacy-era server — it answers the plain `initialize` handshake and does not implement `server/discover` — so the seed carries no `protocolEra` and connects under the [`"legacy"` default](#inspector-specific-per-server-fields).
- **CLI and TUI** seed an empty `{ "mcpServers": {} }` (`seedEmptyCatalog` in `core/mcp/node/config.ts`). They are non-interactive or list-driven, so sample entries would be noise rather than a starting point.
Seeding happens **once per file**, only when that file is absent — not once per client. All three surfaces default to the _same_ path (`~/.mcp-inspector/mcp.json`, `getDefaultMcpConfigPath()` in `core/storage/store-io.ts`), so whichever client runs first decides the contents: run `--cli` first and a later `--web` opens the empty catalog it wrote, with no sample servers. An existing catalog is never re-seeded, and a read-only `--config` is never seeded on any surface.
@ -148,9 +154,13 @@ The file is the familiar MCP client-config shape — an `mcpServers` object keye
```json
{
"mcpServers": {
"example-server": {
"type": "http",
"url": "https://example-server.modelcontextprotocol.io/mcp"
},
"my-http-server": {
"type": "http",
"url": "https://api.example.com/mcp",
"url": "https://mcp.internal.example/mcp",
"headers": { "X-Tenant": "acme" }
}
}
@ -159,6 +169,8 @@ The file is the familiar MCP client-config shape — an `mcpServers` object keye
`type` may be `stdio`, `http` (Streamable HTTP), or `sse`.
`example-server` is a live endpoint you can paste as-is — the MCP org's feature-reference server, [seeded into a fresh web catalog](#what-a-seeded-catalog-contains) for the same reason. `my-http-server` is a placeholder, shown with `headers` to illustrate the field.
### Inspector-specific per-server fields
These have no analog in the broader `mcp.json` ecosystem. Each is **omitted on write when it equals its default**, so a round-trip through the Inspector keeps the file diff minimal.
@ -259,7 +271,7 @@ A catalog carrying these fields:
| | Web | CLI | TUI |
| ---------------------------- | ------------------------------------------------------------- | --------------------------------------- | ------------------------------------------------ |
| Seeds a missing catalog with | two sample servers | `{}` | `{}` |
| Seeds a missing catalog with | three sample servers | `{}` | `{}` |
| `--server` | a no-op — warns with a file source, silent with an ad-hoc one | yes — the only surface where it selects | not defined — `error: unknown option '--server'` |
| `--` separator | yes — after `--` → target | **reversed** — before `--` → target | yes — after `--` → target (Commander default) |
| OAuth client flags | no (uses the Client Settings dialog) | yes | yes |