diff --git a/packages/opencode/src/cli/cmd/mcp.ts b/packages/opencode/src/cli/cmd/mcp.ts index f5e6fdbccc..86b7fd8ac5 100644 --- a/packages/opencode/src/cli/cmd/mcp.ts +++ b/packages/opencode/src/cli/cmd/mcp.ts @@ -63,16 +63,7 @@ type McpAddArgs = { type?: "local" | "remote" env?: string[] header?: string[] - scope?: "project" | "global" global?: boolean - enabled?: boolean - timeout?: number - oauth?: boolean - oauthClientId?: string - oauthClientSecret?: string - oauthScope?: string - oauthCallbackPort?: number - oauthRedirectUri?: string } function configuredServers(config: Config.Info) { @@ -486,53 +477,16 @@ export const McpAddCommand = effectCmd({ type: "string", array: true, }) - .option("scope", { - describe: "where to save the server", - type: "string", - choices: ["project", "global"] as const, - }) .option("global", { alias: ["g"], describe: "save to global config", type: "boolean", - }) - .option("enabled", { - describe: "enable or disable the server on startup", - type: "boolean", - }) - .option("timeout", { - describe: "timeout in milliseconds for MCP server requests", - type: "number", - }) - .option("oauth", { - describe: "enable OAuth for remote servers, or use --no-oauth to disable auto-detection", - type: "boolean", - }) - .option("oauth-client-id", { - describe: "OAuth client ID for remote servers", - type: "string", - }) - .option("oauth-client-secret", { - describe: "OAuth client secret for remote servers", - type: "string", - }) - .option("oauth-scope", { - describe: "OAuth scopes to request for remote servers", - type: "string", - }) - .option("oauth-callback-port", { - describe: "OAuth local callback port for remote servers", - type: "number", - }) - .option("oauth-redirect-uri", { - describe: "OAuth redirect URI for remote servers", - type: "string", }).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) + opencode mcp add --global (save to global config) Examples: opencode mcp add context7 -- npx -y @upstash/context7-mcp @@ -546,8 +500,6 @@ Examples: const inlineArgs = mcpAddArgs(input) const inlineConfig = parseInlineMcpAdd(input, inlineArgs) if (inlineConfig && "error" in inlineConfig) return yield* fail(inlineConfig.error) - 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") @@ -560,8 +512,7 @@ Examples: ]) const configPath = await (async () => { - if (input.global || input.scope === "global") return globalConfigPath - if (input.scope === "project") return projectConfigPath + if (input.global) return globalConfigPath if (inlineConfig) return project.vcs === "git" ? projectConfigPath : globalConfigPath if (project.vcs !== "git") return globalConfigPath const scopeResult = await prompts.select({ @@ -730,9 +681,6 @@ function parseInlineMcpAdd( const name = input.name?.trim() if (!name) return { error: "MCP server name is required" } 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 = input.type ?? (inlineArgs.length === 1 && URL.canParse(inlineArgs[0]) ? "remote" : "local") if (type === "local") return parseInlineLocalMcp(input, inlineArgs) @@ -740,26 +688,11 @@ function parseInlineMcpAdd( } function hasInlineMcpAdd(input: McpAddArgs, inlineArgs: string[]) { - return !!( - 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 - ) + return !!(input.name || inlineArgs.length > 0 || input.type || input.env?.length || input.header?.length) } function parseInlineLocalMcp(args: McpAddArgs, command: string[]): { config: ConfigMCP.Info } | { error: string } { if (args.header?.length) return { error: "--header can only be used with --type remote" } - if (hasOAuthOptions(args)) return { error: "OAuth options can only be used with --type remote" } const environment = parseEnv(args.env) if ("error" in environment) return environment return { @@ -767,8 +700,6 @@ function parseInlineLocalMcp(args: McpAddArgs, command: string[]): { config: Con type: "local", command, ...(environment.value && { environment: environment.value }), - ...(args.enabled !== undefined && { enabled: args.enabled }), - ...(args.timeout !== undefined && { timeout: args.timeout }), }, } } @@ -777,26 +708,13 @@ function parseInlineRemoteMcp(args: McpAddArgs, url: string[]): { config: Config if (url.length !== 1) return { error: "Remote MCP servers require exactly one URL" } if (!URL.canParse(url[0])) return { error: "Remote MCP server URL is invalid" } if (args.env?.length) return { error: "--env can only be used with --type local" } - if ( - args.oauthCallbackPort !== undefined && - (!Number.isInteger(args.oauthCallbackPort) || args.oauthCallbackPort < 1 || args.oauthCallbackPort > 65535) - ) { - return { error: "--oauth-callback-port must be an integer between 1 and 65535" } - } - if (args.oauth === false && hasOAuthConfigOptions(args)) { - return { error: "--no-oauth cannot be combined with OAuth options" } - } const headers = parseHeader(args.header) if ("error" in headers) return headers - const oauth = parseOAuth(args) return { config: { type: "remote", url: url[0], ...(headers.value && { headers: headers.value }), - ...(args.enabled !== undefined && { enabled: args.enabled }), - ...(args.timeout !== undefined && { timeout: args.timeout }), - ...(oauth !== undefined && { oauth }), }, } } @@ -829,32 +747,6 @@ function parseHeader(entries?: string[]): { value?: Record } | { return { value: Object.fromEntries(parsed.map((entry) => [entry.key, entry.value])) } } -function hasOAuthOptions(args: McpAddArgs) { - return !!(args.oauth !== undefined || hasOAuthConfigOptions(args)) -} - -function hasOAuthConfigOptions(args: McpAddArgs) { - return !!( - args.oauthClientId || - args.oauthClientSecret || - args.oauthScope || - args.oauthCallbackPort !== undefined || - args.oauthRedirectUri - ) -} - -function parseOAuth(args: McpAddArgs): ConfigMCP.Remote["oauth"] | undefined { - if (args.oauth === false) return false - if (!hasOAuthOptions(args)) return undefined - return { - ...(args.oauthClientId && { clientId: args.oauthClientId }), - ...(args.oauthClientSecret && { clientSecret: args.oauthClientSecret }), - ...(args.oauthScope && { scope: args.oauthScope }), - ...(args.oauthCallbackPort !== undefined && { callbackPort: args.oauthCallbackPort }), - ...(args.oauthRedirectUri && { redirectUri: args.oauthRedirectUri }), - } -} - export const McpDebugCommand = effectCmd({ command: "debug ", describe: "debug OAuth connection for an MCP server", 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 4d3e9690bc..425b5d176d 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 @@ -434,32 +434,22 @@ Positionals: args URL for remote servers or command and arguments for local servers [array] [default: []] Options: - -h, --help show help [boolean] - -v, --version show version number [boolean] - --print-logs print logs to stderr [boolean] - --log-level log level [string] [choices: "DEBUG", "INFO", "WARN", "ERROR"] - --pure run without external plugins [boolean] - --type server type: local or remote [string] [choices: "local", "remote"] - --env environment variable for local servers (KEY=VALUE) [array] - --header HTTP header for remote servers (KEY=VALUE or 'KEY: VALUE') [array] - --scope where to save the server [string] [choices: "project", "global"] - -g, --global save to global config [boolean] - --enabled enable or disable the server on startup [boolean] - --timeout timeout in milliseconds for MCP server requests [number] - --oauth enable OAuth for remote servers, or use --no-oauth to disable - auto-detection [boolean] - --oauth-client-id OAuth client ID for remote servers [string] - --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] + -h, --help show help [boolean] + -v, --version show version number [boolean] + --print-logs print logs to stderr [boolean] + --log-level log level [string] [choices: "DEBUG", "INFO", "WARN", "ERROR"] + --pure run without external plugins [boolean] + --type server type: local or remote [string] [choices: "local", "remote"] + --env environment variable for local servers (KEY=VALUE) [array] + --header HTTP header for remote servers (KEY=VALUE or 'KEY: VALUE') [array] + -g, --global save to global config [boolean] 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) + opencode mcp add --global (save to global config) Examples: opencode mcp add context7 -- npx -y @upstash/context7-mcp diff --git a/packages/opencode/test/cli/mcp.test.ts b/packages/opencode/test/cli/mcp.test.ts index b8fee0850f..ac54ad55d3 100644 --- a/packages/opencode/test/cli/mcp.test.ts +++ b/packages/opencode/test/cli/mcp.test.ts @@ -62,28 +62,6 @@ describe("opencode mcp", () => { }, }, }) - - const noOAuth = yield* opencode.spawn([ - "mcp", - "add", - "public", - "https://example.com/mcp", - "--no-oauth", - "--global", - ]) - opencode.expectExit(noOAuth, 0, "opencode mcp add no oauth") - - expect( - yield* Effect.promise(() => Bun.file(path.join(home, ".config/opencode/opencode.json")).json()), - ).toMatchObject({ - mcp: { - public: { - type: "remote", - url: "https://example.com/mcp", - oauth: false, - }, - }, - }) }), 120_000, ) diff --git a/packages/web/src/content/docs/mcp-servers.mdx b/packages/web/src/content/docs/mcp-servers.mdx index 883336b8f9..d2c7b94160 100644 --- a/packages/web/src/content/docs/mcp-servers.mdx +++ b/packages/web/src/content/docs/mcp-servers.mdx @@ -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 `--scope project` or `--global` to choose where the server is saved. +Use `--env KEY=VALUE` for local server environment variables and repeat it for multiple values. Use `--global` to save to global config. ---