feat(agent): add external MCP client support
Reuse OpenCode 2 HTTP/stdio clients with per-session read-only grants, native approval and sanitized owner-scoped MCP status. Narrow the environment inherited by trusted local MCP processes. Validated in an isolated checkout with bun run check and 20 focused tests.
This commit is contained in:
@@ -7,6 +7,7 @@ import { resolve } from "node:path";
|
||||
import { realpath, rm } from "node:fs/promises";
|
||||
import { createConversationWorkspace, resolveConversationWorkspace } from "./conversationWorkspace.js";
|
||||
import { serviceOpencodeConfig } from "./opencode2-config.js";
|
||||
import { mcpReadonlyTools, mcpRuntimeReportSchema } from "./mcp-config.js";
|
||||
|
||||
import { config } from "../config.js";
|
||||
import { logger } from "../logger.js";
|
||||
@@ -95,6 +96,25 @@ export class OpencodeRuntimeAdapter {
|
||||
return { healthy: true, version: info.version };
|
||||
}
|
||||
|
||||
mcpReadonlyTools(): string[] {
|
||||
return mcpReadonlyTools(buildOpencodeConfig());
|
||||
}
|
||||
|
||||
async mcpStatus(sessionId: string) {
|
||||
const client = await this.ensureClient();
|
||||
const session = await client.session.get({ sessionID: sessionId });
|
||||
const directory = resolve(session.location.directory) === process.cwd() ? process.cwd()
|
||||
: await resolveConversationWorkspace(session.location.directory, config.CONVERSATION_WORKSPACE_ROOT);
|
||||
return this.readMcpReport(client, directory);
|
||||
}
|
||||
|
||||
private async readMcpReport(client: OpencodeClient, directory: string) {
|
||||
const response = await client.rpc.call({
|
||||
rpcID: "tjwater.tools", method: "mcp", input: {}, location: { directory },
|
||||
}, { signal: AbortSignal.timeout(15_000) });
|
||||
return mcpRuntimeReportSchema.parse(response.output);
|
||||
}
|
||||
|
||||
async resolveAvailableModel(preferredModelIds: string[]): Promise<string> {
|
||||
const client = await this.ensureClient();
|
||||
// V2 2.0.24 model.list alone does not await a cold Location's plugin setup.
|
||||
@@ -126,6 +146,8 @@ export class OpencodeRuntimeAdapter {
|
||||
if (!tools?.some((tool) => tool.id === "shell" && tool.sandboxed)) throw new Error("OpenCode shell sandbox replacement is missing");
|
||||
const actualRoot = tools.find((tool) => tool.id === "shell")?.workspaceRoot;
|
||||
if (!actualRoot || await realpath(actualRoot) !== await realpath(config.CONVERSATION_WORKSPACE_ROOT)) throw new Error("OpenCode and Agent conversation workspace roots do not match");
|
||||
const mcp = await this.readMcpReport(client, session.location.directory);
|
||||
if (mcp.configuration_errors.length) throw new Error("MCP configuration invalid");
|
||||
const models = await client.model.list({ location: { directory: session.location.directory } });
|
||||
const model = models.data.find((model) => `${model.providerID}/${model.id}` === config.OPENCODE_MODEL);
|
||||
if (!model?.enabled || !model.capabilities.tools) {
|
||||
|
||||
Reference in New Issue
Block a user