diff --git a/packages/opencode/src/cli/cmd/mcp.ts b/packages/opencode/src/cli/cmd/mcp.ts index cba723f5063..f5e6fdbccce 100644 --- a/packages/opencode/src/cli/cmd/mcp.ts +++ b/packages/opencode/src/cli/cmd/mcp.ts @@ -59,7 +59,7 @@ type McpAddArgs = { _?: Array "--"?: string[] name?: string - urlOrCommand?: string[] + args?: string[] type?: "local" | "remote" env?: string[] header?: string[] @@ -456,7 +456,7 @@ async function addMcpToConfig(name: string, mcpConfig: ConfigMCP.Info, configPat } export const McpAddCommand = effectCmd({ - command: "add [name] [urlOrCommand..]", + command: "add [name] [args..]", describe: "add an MCP server", builder: (yargs) => yargs @@ -465,8 +465,8 @@ export const McpAddCommand = effectCmd({ describe: "name of the MCP server", type: "string", }) - .positional("urlOrCommand", { - describe: "URL for remote servers or command for local servers", + .positional("args", { + describe: "URL for remote servers or command and arguments for local servers", type: "string", array: true, default: [], @@ -527,15 +527,27 @@ export const McpAddCommand = effectCmd({ .option("oauth-redirect-uri", { describe: "OAuth redirect URI for remote servers", type: "string", - }), - handler: Effect.fn("Cli.mcp.add")(function* (args: McpAddArgs) { + }).epilogue(`Usage: + opencode mcp add -- [args...] (local MCP server) + opencode mcp add --env KEY=VALUE -- [args...] (local MCP server with env vars) + opencode mcp add (remote MCP server) + opencode mcp add --header KEY=VALUE (remote MCP server with headers) + opencode mcp add --scope project -- [args...] (save to project config) + +Examples: + opencode mcp add context7 -- npx -y @upstash/context7-mcp + opencode mcp add local-env --env FOO=bar -- node server.js + opencode mcp add sg --header Authorization=token https://sg.example/mcp + opencode mcp add hugging-face https://huggingface.co/mcp`), + handler: Effect.fn("Cli.mcp.add")(function* (input: McpAddArgs) { const maybeCtx = yield* InstanceRef if (!maybeCtx) return yield* Effect.die("InstanceRef not provided") const ctx = maybeCtx - const urlOrCommand = mcpAddUrlOrCommand(args) - const inlineConfig = parseInlineMcpAdd(args, urlOrCommand) + const inlineArgs = mcpAddArgs(input) + const inlineConfig = parseInlineMcpAdd(input, inlineArgs) if (inlineConfig && "error" in inlineConfig) return yield* fail(inlineConfig.error) - if (args.global && args.scope === "project") return yield* fail("--global cannot be combined with --scope project") + if (input.global && input.scope === "project") + return yield* fail("--global cannot be combined with --scope project") yield* Effect.promise(async () => { UI.empty() prompts.intro("Add MCP server") @@ -548,8 +560,8 @@ export const McpAddCommand = effectCmd({ ]) const configPath = await (async () => { - if (args.global || args.scope === "global") return globalConfigPath - if (args.scope === "project") return projectConfigPath + if (input.global || input.scope === "global") return globalConfigPath + if (input.scope === "project") return projectConfigPath if (inlineConfig) return project.vcs === "git" ? projectConfigPath : globalConfigPath if (project.vcs !== "git") return globalConfigPath const scopeResult = await prompts.select({ @@ -572,8 +584,8 @@ export const McpAddCommand = effectCmd({ })() if (inlineConfig) { - await addMcpToConfig(args.name!.trim(), inlineConfig.config, configPath) - prompts.log.success(`MCP server "${args.name!.trim()}" added to ${configPath}`) + await addMcpToConfig(input.name!.trim(), inlineConfig.config, configPath) + prompts.log.success(`MCP server "${input.name!.trim()}" added to ${configPath}`) prompts.outro("MCP server added successfully") return } @@ -701,47 +713,47 @@ export const McpAddCommand = effectCmd({ }), }) -function mcpAddUrlOrCommand(args: McpAddArgs) { - const addIndex = args._?.lastIndexOf("add") ?? -1 +function mcpAddArgs(input: McpAddArgs) { + const addIndex = input._?.lastIndexOf("add") ?? -1 return [ - ...(args.urlOrCommand ?? []), - ...(addIndex === -1 || !args._ ? [] : args._.slice(addIndex + 1).map(String)), - ...(args["--"] ?? []), + ...(input.args ?? []), + ...(addIndex === -1 || !input._ ? [] : input._.slice(addIndex + 1).map(String)), + ...(input["--"] ?? []), ] } function parseInlineMcpAdd( - args: McpAddArgs, - urlOrCommand: string[], + input: McpAddArgs, + inlineArgs: string[], ): { config: ConfigMCP.Info } | { error: string } | undefined { - if (!hasInlineMcpAdd(args, urlOrCommand)) return undefined - const name = args.name?.trim() + if (!hasInlineMcpAdd(input, inlineArgs)) return undefined + const name = input.name?.trim() if (!name) return { error: "MCP server name is required" } - if (urlOrCommand.length === 0) return { error: "URL or command is required" } - if (args.timeout !== undefined && (!Number.isInteger(args.timeout) || args.timeout <= 0)) { + if (inlineArgs.length === 0) return { error: "URL or command is required" } + if (input.timeout !== undefined && (!Number.isInteger(input.timeout) || input.timeout <= 0)) { return { error: "--timeout must be a positive integer" } } - const type = args.type ?? (urlOrCommand.length === 1 && URL.canParse(urlOrCommand[0]) ? "remote" : "local") - if (type === "local") return parseInlineLocalMcp(args, urlOrCommand) - return parseInlineRemoteMcp(args, urlOrCommand) + const type = input.type ?? (inlineArgs.length === 1 && URL.canParse(inlineArgs[0]) ? "remote" : "local") + if (type === "local") return parseInlineLocalMcp(input, inlineArgs) + return parseInlineRemoteMcp(input, inlineArgs) } -function hasInlineMcpAdd(args: McpAddArgs, urlOrCommand: string[]) { +function hasInlineMcpAdd(input: McpAddArgs, inlineArgs: string[]) { return !!( - args.name || - urlOrCommand.length > 0 || - args.type || - args.env?.length || - args.header?.length || - args.enabled !== undefined || - args.timeout !== undefined || - args.oauth !== undefined || - args.oauthClientId || - args.oauthClientSecret || - args.oauthScope || - args.oauthCallbackPort !== undefined || - args.oauthRedirectUri + input.name || + inlineArgs.length > 0 || + input.type || + input.env?.length || + input.header?.length || + input.enabled !== undefined || + input.timeout !== undefined || + input.oauth !== undefined || + input.oauthClientId || + input.oauthClientSecret || + input.oauthScope || + input.oauthCallbackPort !== undefined || + input.oauthRedirectUri ) } diff --git a/packages/opencode/test/cli/help/__snapshots__/help-snapshots.test.ts.snap b/packages/opencode/test/cli/help/__snapshots__/help-snapshots.test.ts.snap index 8aee63866d3..4d3e9690bc8 100644 --- a/packages/opencode/test/cli/help/__snapshots__/help-snapshots.test.ts.snap +++ b/packages/opencode/test/cli/help/__snapshots__/help-snapshots.test.ts.snap @@ -27,11 +27,11 @@ exports[`opencode CLI help-text snapshots every documented command emits stable manage MCP (Model Context Protocol) servers Commands: - opencode mcp add [name] [urlOrCommand..] add an MCP server - opencode mcp list list MCP servers and their status [aliases: ls] - opencode mcp auth [name] authenticate with an OAuth-enabled MCP server - opencode mcp logout [name] remove OAuth credentials for an MCP server - opencode mcp debug debug OAuth connection for an MCP server + opencode mcp add [name] [args..] add an MCP server + opencode mcp list list MCP servers and their status [aliases: ls] + opencode mcp auth [name] authenticate with an OAuth-enabled MCP server + opencode mcp logout [name] remove OAuth credentials for an MCP server + opencode mcp debug debug OAuth connection for an MCP server Options: -h, --help show help [boolean] @@ -425,13 +425,13 @@ Options: `; exports[`opencode CLI help-text snapshots every documented command emits stable help text: opencode mcp add --help 1`] = ` -"opencode mcp add [name] [urlOrCommand..] +"opencode mcp add [name] [args..] add an MCP server Positionals: - name name of the MCP server [string] - urlOrCommand URL for remote servers or command for local servers [array] [default: []] + name name of the MCP server [string] + args URL for remote servers or command and arguments for local servers [array] [default: []] Options: -h, --help show help [boolean] @@ -452,7 +452,20 @@ Options: --oauth-client-secret OAuth client secret for remote servers [string] --oauth-scope OAuth scopes to request for remote servers [string] --oauth-callback-port OAuth local callback port for remote servers [number] - --oauth-redirect-uri OAuth redirect URI for remote servers [string]" + --oauth-redirect-uri OAuth redirect URI for remote servers [string] + +Usage: + opencode mcp add -- [args...] (local MCP server) + opencode mcp add --env KEY=VALUE -- [args...] (local MCP server with env vars) + opencode mcp add (remote MCP server) + opencode mcp add --header KEY=VALUE (remote MCP server with headers) + opencode mcp add --scope project -- [args...] (save to project config) + +Examples: + opencode mcp add context7 -- npx -y @upstash/context7-mcp + opencode mcp add local-env --env FOO=bar -- node server.js + opencode mcp add sg --header Authorization=token https://sg.example/mcp + opencode mcp add hugging-face https://huggingface.co/mcp" `; exports[`opencode CLI help-text snapshots every documented command emits stable help text: opencode mcp auth --help 1`] = ` diff --git a/packages/opencode/test/cli/mcp.test.ts b/packages/opencode/test/cli/mcp.test.ts index e1b496f7643..b8fee0850f6 100644 --- a/packages/opencode/test/cli/mcp.test.ts +++ b/packages/opencode/test/cli/mcp.test.ts @@ -39,12 +39,13 @@ describe("opencode mcp", () => { "mcp", "add", "everything", - "npx", - "-y", - "@modelcontextprotocol/server-everything", "--env", "FOO=bar", "--global", + "--", + "npx", + "-y", + "@modelcontextprotocol/server-everything", ]) opencode.expectExit(local, 0, "opencode mcp add local") diff --git a/packages/web/src/content/docs/mcp-servers.mdx b/packages/web/src/content/docs/mcp-servers.mdx index cc0e7d31aa4..883336b8f99 100644 --- a/packages/web/src/content/docs/mcp-servers.mdx +++ b/packages/web/src/content/docs/mcp-servers.mdx @@ -50,7 +50,7 @@ You can add MCP servers from the command line with `opencode mcp add`. ```bash # Local server -opencode mcp add mcp_everything npx -y @modelcontextprotocol/server-everything +opencode mcp add mcp_everything -- npx -y @modelcontextprotocol/server-everything # Remote server opencode mcp add github https://api.githubcopilot.com/mcp \ @@ -58,7 +58,7 @@ opencode mcp add github https://api.githubcopilot.com/mcp \ --header "Authorization: Bearer YOUR_GITHUB_PAT" ``` -Use `--env KEY=VALUE` for local server environment variables and repeat it for multiple values. Use `--global` to save to global config; otherwise the command saves to the current project when run inside a git repository. +Use `--env KEY=VALUE` for local server environment variables and repeat it for multiple values. Use `--scope project` or `--global` to choose where the server is saved. ---