Skip to content

Commit e790e91

Browse files
author
saravmajestic
committed
fix(mcps): bypass LLM for /mcps list, enable, disable commands
Add direct handler in SessionPrompt.command for the mcps slash command. Instead of routing through the AI template, the handler calls MCP.status(), MCP.connect(), and MCP.disconnect() directly and returns a structured MessageV2.WithParts response, matching the TUI HTTP API behavior. - /mcps (no args): queries MCP.status() and renders a markdown table - /mcps enable <name>: calls MCP.connect(name) and reports result - /mcps disable <name>: calls MCP.disconnect(name) and confirms
1 parent 599807a commit e790e91

1 file changed

Lines changed: 102 additions & 0 deletions

File tree

packages/opencode/src/session/prompt.ts

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2602,6 +2602,108 @@ NOTE: At any point in time through this workflow you should feel free to ask the
26022602
}
26032603
const agentName = command.agent ?? input.agent ?? (await Agent.defaultAgent())
26042604

2605+
// altimate_change start — /mcps enable/disable: direct handler bypasses LLM
2606+
if (input.command === "mcps") {
2607+
const trimmed = input.arguments.trim()
2608+
2609+
if (!trimmed) {
2610+
// /mcps (no args): return actual runtime status directly
2611+
const userMsg = await createUserMessage({
2612+
sessionID: input.sessionID,
2613+
messageID: input.messageID,
2614+
parts: [{ type: "text", text: "/mcps" }],
2615+
})
2616+
const model = await lastModel(input.sessionID)
2617+
const statusMap = await MCP.status()
2618+
const rows = Object.entries(statusMap)
2619+
.map(([srv, s]) => {
2620+
const icon = s.status === "connected" ? "\u2713" : "\u25cb"
2621+
const label =
2622+
s.status === "failed"
2623+
? icon + " " + s.status + " (" + (s as any).error + ")"
2624+
: icon + " " + s.status
2625+
return "| `" + srv + "` | " + label + " |"
2626+
})
2627+
.join("\n")
2628+
const responseText = rows
2629+
? "MCP servers:\n\n| Server | Status |\n|---|---|\n" + rows
2630+
: "No MCP servers configured."
2631+
2632+
const now = Date.now()
2633+
const assistantMsg: MessageV2.Assistant = {
2634+
id: MessageID.ascending(), role: "assistant", sessionID: input.sessionID,
2635+
parentID: userMsg.info.id, modelID: model.modelID, providerID: model.providerID,
2636+
mode: "builder", agent: "builder",
2637+
path: { cwd: Instance.directory, root: Instance.worktree },
2638+
cost: 0, tokens: { total: 0, input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } },
2639+
finish: "stop", time: { created: now, completed: now },
2640+
}
2641+
await Session.updateMessage(assistantMsg)
2642+
const textPart: MessageV2.TextPart = {
2643+
id: PartID.ascending(), sessionID: input.sessionID, messageID: assistantMsg.id,
2644+
type: "text", text: responseText, time: { start: now, end: now },
2645+
}
2646+
await Session.updatePart(textPart)
2647+
Bus.publish(Command.Event.Executed, {
2648+
name: input.command, sessionID: input.sessionID,
2649+
arguments: input.arguments, messageID: assistantMsg.id,
2650+
})
2651+
return { info: assistantMsg, parts: [textPart] } as MessageV2.WithParts
2652+
}
2653+
2654+
const subMatch = trimmed.match(/^(enable|disable)\s+(\S+)/)
2655+
if (subMatch) {
2656+
const [, subCmd, name] = subMatch
2657+
const isEnable = subCmd === "enable"
2658+
2659+
const userMsg = await createUserMessage({
2660+
sessionID: input.sessionID,
2661+
messageID: input.messageID,
2662+
parts: [{ type: "text", text: `/mcps ${subCmd} ${name}` }],
2663+
})
2664+
2665+
const model = await lastModel(input.sessionID)
2666+
let responseText: string
2667+
2668+
if (isEnable) {
2669+
await MCP.connect(name)
2670+
const statusMap = await MCP.status()
2671+
const entry = statusMap[name]
2672+
if (entry?.status === "connected") {
2673+
responseText = `MCP server **${name}** enabled. Status: connected.`
2674+
} else {
2675+
responseText = `Attempted to enable MCP server **${name}**. Status: ${entry?.status ?? "unknown"}${(entry as any)?.error ? " — " + (entry as any).error : "."}.`
2676+
}
2677+
} else {
2678+
await MCP.disconnect(name)
2679+
responseText = `MCP server **${name}** disabled.`
2680+
}
2681+
2682+
const now = Date.now()
2683+
const assistantMsg: MessageV2.Assistant = {
2684+
id: MessageID.ascending(), role: "assistant", sessionID: input.sessionID,
2685+
parentID: userMsg.info.id, modelID: model.modelID, providerID: model.providerID,
2686+
mode: "builder", agent: "builder",
2687+
path: { cwd: Instance.directory, root: Instance.worktree },
2688+
cost: 0, tokens: { total: 0, input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } },
2689+
finish: "stop", time: { created: now, completed: now },
2690+
}
2691+
await Session.updateMessage(assistantMsg)
2692+
const textPart: MessageV2.TextPart = {
2693+
id: PartID.ascending(), sessionID: input.sessionID, messageID: assistantMsg.id,
2694+
type: "text", text: responseText, time: { start: now, end: now },
2695+
}
2696+
await Session.updatePart(textPart)
2697+
Bus.publish(Command.Event.Executed, {
2698+
name: input.command, sessionID: input.sessionID,
2699+
arguments: input.arguments, messageID: assistantMsg.id,
2700+
})
2701+
return { info: assistantMsg, parts: [textPart] } as MessageV2.WithParts
2702+
}
2703+
}
2704+
// altimate_change end
2705+
2706+
26052707
const raw = input.arguments.match(argsRegex) ?? []
26062708
const args = raw.map((arg) => arg.replace(quoteTrimRegex, ""))
26072709

0 commit comments

Comments
 (0)