farion1231/cc-switch · warning · Error

stdio 类型的 MCP 服务器必须包含 command 字段

Error message

stdio 类型的 MCP 服务器必须包含 command 字段

What it means

A server entry with no type defaults to 'stdio', and stdio servers must carry a string command ('stdio MCP server must include command field'). The classic trigger is a remote server config pasted without type = "http"/"sse": its url makes the direct-config branch fire, the type defaults to stdio, and command is absent.

Source

Thrown at src/utils/tomlUtils.ts:113

};

/**
 * 规范化服务器配置对象为 McpServer 格式
 * 保留所有字段(包括扩展字段如 timeout_ms)
 */
function normalizeServerConfig(config: any): McpServerSpec {
  if (!config || typeof config !== "object") {
    throw new Error("服务器配置必须是对象");
  }

  const type = (config.type as string) || "stdio";

  // 已知字段列表(用于后续排除)
  const knownFields = new Set<string>();

  if (type === "stdio") {
    if (!config.command || typeof config.command !== "string") {
      throw new Error("stdio 类型的 MCP 服务器必须包含 command 字段");
    }

    const server: McpServerSpec = {
      type: "stdio",
      command: config.command,
    };
    knownFields.add("type");
    knownFields.add("command");

    // 可选字段
    if (config.args && Array.isArray(config.args)) {
      server.args = config.args.map((arg: any) => String(arg));
      knownFields.add("args");
    }
    if (config.env && typeof config.env === "object") {
      const env: Record<string, string> = {};
      for (const [k, v] of Object.entries(config.env)) {
        env[k] = String(v);

View on GitHub (pinned to a2e22f3302)

Solutions

  1. For remote servers add type = "http" (or "sse") so the url branch is taken
  2. For local servers add a string command, e.g. command = "npx"
  3. Ensure command is a quoted string, not a bare TOML value

Example fix

# before (throws: defaults to stdio, no command)
url = "https://mcp.example.com/sse"

# after
type = "sse"
url = "https://mcp.example.com/sse"
Defensive patterns

Strategy: validation

Validate before calling

function inferServerType(entry: Record<string, unknown>): "stdio" | "http" | "sse" | null {
  const type = typeof entry.type === "string" ? entry.type : "stdio";
  if (type === "stdio") {
    return typeof entry.command === "string" && entry.command ? "stdio" : null;
  }
  if (type === "http" || type === "sse") {
    return typeof entry.url === "string" && entry.url ? type : null;
  }
  return null;
}

const type = inferServerType(entry);
if (!type) {
  setImportError("Remote servers need type + url; local servers need command");
}

Type guard

function isStdioEntry(e: Record<string, unknown>): e is { type: "stdio"; command: string } {
  const type = typeof e.type === "string" ? e.type : "stdio";
  return type === "stdio" && typeof e.command === "string" && e.command.length > 0;
}

Try / catch

try {
  const server = tomlToMcpServer(tomlText);
} catch (e) {
  if (e instanceof Error && e.message.includes("必须包含 command 字段")) {
    setImportError("Local stdio servers need a command; remote servers need type = \"http\"/\"sse\" plus url");
  } else {
    throw e;
  }
}

Prevention

When it happens

Trigger: Entry with url but no type line; entry with only args/env fields; command set to a non-string TOML value (e.g. a bare number).

Common situations: Copying a remote server snippet that omits the type line; accidentally deleting or renaming the command key.

Related errors


AI-assisted analysis of farion1231/cc-switch@a2e22f3302 (2026-08-16). Data as JSON: /api/errors/ae6b52a0e15a1a55. Report an issue: GitHub.