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
3 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) {