/** * Pure converters between the on-disk `mcp.json` shape (`MCPConfig`) and the * in-memory list of `ServerEntry` records the UI consumes. No I/O, no Node * deps — safe to import from the browser side of core/ as well as the * remote-server route handlers. */ import type { MCPConfig, MCPServerConfig, ServerEntry, ServerType, StoredMCPServer, } from "./types.js"; // The full set of valid `type` discriminator values, used to reject anything // else read off disk so unknown strings can't propagate to narrowing sites. const VALID_SERVER_TYPES: ReadonlySet = new Set([ "stdio", "sse", "streamable-http", ]); /** * Normalizes server type: * - missing / unknown / non-string → "stdio" (matches Claude Desktop's default) * - "http" → "streamable-http" (legacy alias) * - valid ServerType → passed through unchanged * * Lives here (rather than in node/config.ts) so the file stays Node-free * and the same normalization is applied by every consumer of `mcp.json`. * The "unknown → stdio" branch keeps a hand-edited file with `"type":"websocket"` * or `"type": 42` from leaking through `as ServerType` casts into narrowing * sites that would then fall through in surprising ways. */ export function normalizeServerType( config: Record & { type?: unknown }, ): MCPServerConfig { const type = config.type; let normalizedType: ServerType; if (typeof type !== "string") { normalizedType = "stdio"; } else if (type === "http") { normalizedType = "streamable-http"; } else if (VALID_SERVER_TYPES.has(type as ServerType)) { normalizedType = type as ServerType; } else { normalizedType = "stdio"; } return { ...config, type: normalizedType } as MCPServerConfig; } /** * Convert the on-disk `MCPConfig` into the `ServerEntry[]` the Servers screen * consumes. Map key becomes both `id` and `name`. Connection state initializes * to `disconnected` — the React layer drives it from there. The optional * `settings` node is lifted from the stored entry into `ServerEntry.settings` * so the rest of the app sees `config` as the pure SDK shape. */ export function mcpConfigToServerEntries(config: MCPConfig): ServerEntry[] { return Object.entries(config.mcpServers).map(([id, raw]) => { const { settings, ...rest } = raw as StoredMCPServer; const normalizedConfig = normalizeServerType( rest as unknown as Record & { type?: string }, ); const entry: ServerEntry = { id, name: id, config: normalizedConfig, connection: { status: "disconnected" }, }; if (settings !== undefined) entry.settings = settings; return entry; }); } /** * Convert `ServerEntry[]` back into `MCPConfig` for serialization. Strips * runtime-only fields (connection, info, name); persists `config` and the * optional `settings` node so the file remains a clean canonical `mcp.json` * plus the Inspector-specific extension. */ export function serverEntriesToMcpConfig(entries: ServerEntry[]): MCPConfig { const mcpServers: Record = {}; for (const entry of entries) { const stored: StoredMCPServer = { ...entry.config } as StoredMCPServer; if (entry.settings !== undefined) stored.settings = entry.settings; mcpServers[entry.id] = stored; } return { mcpServers }; } /** * Canonical JSON serialization for `mcp.json` files. Two-space indent — the * same format `serializeStore` in core/storage/store-io.ts writes on the * backend, so a round-trip through export → hand-edit → import preserves * the on-disk shape. Browser-safe (no Node imports); the backend uses the * Node-only serializeStore but the formatting must match. */ export function serializeMcpConfig(entries: ServerEntry[]): string { return JSON.stringify(serverEntriesToMcpConfig(entries), null, 2); } /** * 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. */ export const DEFAULT_SEED_CONFIG: MCPConfig = { mcpServers: { "filesystem-server-default": { type: "stdio", command: "npx", args: ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"], }, "everything-server-default": { type: "stdio", command: "npx", args: ["-y", "@modelcontextprotocol/server-everything"], }, }, };