fix: actually modify opencode config with mcp add (#7339)

This commit is contained in:
Paolo Ricciuti
2026-01-13 01:02:59 +01:00
committed by GitHub
parent 835e48cd28
commit 498a4ab408

View File

@@ -1,7 +1,6 @@
import { cmd } from "./cmd" import { cmd } from "./cmd"
import { Client } from "@modelcontextprotocol/sdk/client/index.js" import { Client } from "@modelcontextprotocol/sdk/client/index.js"
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js" import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js"
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js"
import { UnauthorizedError } from "@modelcontextprotocol/sdk/client/auth.js" import { UnauthorizedError } from "@modelcontextprotocol/sdk/client/auth.js"
import * as prompts from "@clack/prompts" import * as prompts from "@clack/prompts"
import { UI } from "../ui" import { UI } from "../ui"
@@ -13,6 +12,7 @@ import { Instance } from "../../project/instance"
import { Installation } from "../../installation" import { Installation } from "../../installation"
import path from "path" import path from "path"
import { Global } from "../../global" import { Global } from "../../global"
import { modify, applyEdits } from "jsonc-parser"
function getAuthStatusIcon(status: MCP.AuthStatus): string { function getAuthStatusIcon(status: MCP.AuthStatus): string {
switch (status) { switch (status) {
@@ -366,13 +366,83 @@ export const McpLogoutCommand = cmd({
}, },
}) })
async function resolveConfigPath(baseDir: string, global = false) {
// Check for existing config files (prefer .jsonc over .json, check .opencode/ subdirectory too)
const candidates = [path.join(baseDir, "opencode.json"), path.join(baseDir, "opencode.jsonc")]
if (!global) {
candidates.push(path.join(baseDir, ".opencode", "opencode.json"), path.join(baseDir, ".opencode", "opencode.jsonc"))
}
for (const candidate of candidates) {
if (await Bun.file(candidate).exists()) {
return candidate
}
}
// Default to opencode.json if none exist
return candidates[0]
}
async function addMcpToConfig(name: string, mcpConfig: Config.Mcp, configPath: string) {
const file = Bun.file(configPath)
let text = "{}"
if (await file.exists()) {
text = await file.text()
}
// Use jsonc-parser to modify while preserving comments
const edits = modify(text, ["mcp", name], mcpConfig, {
formattingOptions: { tabSize: 2, insertSpaces: true },
})
const result = applyEdits(text, edits)
await Bun.write(configPath, result)
return configPath
}
export const McpAddCommand = cmd({ export const McpAddCommand = cmd({
command: "add", command: "add",
describe: "add an MCP server", describe: "add an MCP server",
async handler() { async handler() {
await Instance.provide({
directory: process.cwd(),
async fn() {
UI.empty() UI.empty()
prompts.intro("Add MCP server") prompts.intro("Add MCP server")
const project = Instance.project
// Resolve config paths eagerly for hints
const [projectConfigPath, globalConfigPath] = await Promise.all([
resolveConfigPath(Instance.worktree),
resolveConfigPath(Global.Path.config, true),
])
// Determine scope
let configPath = globalConfigPath
if (project.vcs === "git") {
const scopeResult = await prompts.select({
message: "Location",
options: [
{
label: "Current project",
value: projectConfigPath,
hint: projectConfigPath,
},
{
label: "Global",
value: globalConfigPath,
hint: globalConfigPath,
},
],
})
if (prompts.isCancel(scopeResult)) throw new UI.CancelledError()
configPath = scopeResult
}
const name = await prompts.text({ const name = await prompts.text({
message: "Enter MCP server name", message: "Enter MCP server name",
validate: (x) => (x && x.length > 0 ? undefined : "Required"), validate: (x) => (x && x.length > 0 ? undefined : "Required"),
@@ -404,7 +474,13 @@ export const McpAddCommand = cmd({
}) })
if (prompts.isCancel(command)) throw new UI.CancelledError() if (prompts.isCancel(command)) throw new UI.CancelledError()
prompts.log.info(`Local MCP server "${name}" configured with command: ${command}`) const mcpConfig: Config.Mcp = {
type: "local",
command: command.split(" "),
}
await addMcpToConfig(name, mcpConfig, configPath)
prompts.log.success(`MCP server "${name}" added to ${configPath}`)
prompts.outro("MCP server added successfully") prompts.outro("MCP server added successfully")
return return
} }
@@ -428,6 +504,8 @@ export const McpAddCommand = cmd({
}) })
if (prompts.isCancel(useOAuth)) throw new UI.CancelledError() if (prompts.isCancel(useOAuth)) throw new UI.CancelledError()
let mcpConfig: Config.Mcp
if (useOAuth) { if (useOAuth) {
const hasClientId = await prompts.confirm({ const hasClientId = await prompts.confirm({
message: "Do you have a pre-registered client ID?", message: "Do you have a pre-registered client ID?",
@@ -457,44 +535,37 @@ export const McpAddCommand = cmd({
clientSecret = secret clientSecret = secret
} }
prompts.log.info(`Remote MCP server "${name}" configured with OAuth (client ID: ${clientId})`) mcpConfig = {
prompts.log.info("Add this to your opencode.json:") type: "remote",
prompts.log.info(` url,
"mcp": { oauth: {
"${name}": { clientId,
"type": "remote", ...(clientSecret && { clientSecret }),
"url": "${url}", },
"oauth": {
"clientId": "${clientId}"${clientSecret ? `,\n "clientSecret": "${clientSecret}"` : ""}
}
}
}`)
} else {
prompts.log.info(`Remote MCP server "${name}" configured with OAuth (dynamic registration)`)
prompts.log.info("Add this to your opencode.json:")
prompts.log.info(`
"mcp": {
"${name}": {
"type": "remote",
"url": "${url}",
"oauth": {}
}
}`)
} }
} else { } else {
const client = new Client({ mcpConfig = {
name: "opencode", type: "remote",
version: "1.0.0", url,
}) oauth: {},
const transport = new StreamableHTTPClientTransport(new URL(url)) }
await client.connect(transport) }
prompts.log.info(`Remote MCP server "${name}" configured with URL: ${url}`) } else {
mcpConfig = {
type: "remote",
url,
} }
} }
await addMcpToConfig(name, mcpConfig, configPath)
prompts.log.success(`MCP server "${name}" added to ${configPath}`)
}
prompts.outro("MCP server added successfully") prompts.outro("MCP server added successfully")
}, },
}) })
},
})
export const McpDebugCommand = cmd({ export const McpDebugCommand = cmd({
command: "debug <name>", command: "debug <name>",