Merge pull request #2283 from modelcontextprotocol/v2/feat/2201-seed-example-server
feat: seed the MCP org example server as a remote first-run card
This commit is contained in:
commit
aab6475527
6 changed files with 81 additions and 17 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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", () => {
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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 |
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@ Replaces the hardcoded `SEED_SERVERS` in `clients/web/src/App.tsx:47` with a fil
|
|||
|
||||
## First-run behavior
|
||||
|
||||
If the file does not exist when the backend boots, write a file containing the two current `SEED_SERVERS`. User immediately sees a non-empty Servers screen and discovers the file by editing one of the seeds. Subsequent boots read whatever the user has saved.
|
||||
If the file does not exist when the backend first **serves the catalog** — the `GET /api/servers` handler, not boot — write a file containing `DEFAULT_SEED_CONFIG`. Booting the backend alone leaves the path untouched; the write happens on the first read of the list, inside the write lock so a concurrent `POST`/`PUT`/`DELETE` cannot be clobbered by it. A read-only `--config` source is never seeded. User immediately sees a non-empty Servers screen and discovers the file by editing one of the seeds. Subsequent boots read whatever the user has saved. The seed set started as the two `SEED_SERVERS` this design replaced and is three as of #2201 — the two local stdio servers plus `example-server-default`, the MCP org's remote feature-reference server over Streamable HTTP, so a first launch can reach a remote server without standing one up. `core/mcp/serverList.ts` is the source of truth for the contents; only the _behavior_ is specified here.
|
||||
|
||||
## Architecture
|
||||
|
||||
|
|
@ -113,7 +113,7 @@ Pure converters between on-disk `MCPConfig` and in-memory `ServerEntry[]`. No I/
|
|||
```ts
|
||||
export function mcpConfigToServerEntries(config: MCPConfig): ServerEntry[];
|
||||
export function serverEntriesToMcpConfig(entries: ServerEntry[]): MCPConfig;
|
||||
export function DEFAULT_SEED_CONFIG: MCPConfig; // the two existing seeds
|
||||
export const DEFAULT_SEED_CONFIG: MCPConfig; // the default seeds (see First-run behavior)
|
||||
```
|
||||
|
||||
`mcpConfigToServerEntries` sets `connection: { status: "disconnected" }` and uses the map key as both `id` and `name`. `serverEntriesToMcpConfig` strips `connection` / `info` (runtime-only) before serializing.
|
||||
|
|
|
|||
Loading…
Reference in a new issue