Files
openwork/packages/server/src/mcp.ts
ben 4bcbf8aae0 feat: openwork sync v0 (#280)
* docs: add openwork server PRD

* feat: wire OpenWork server remote management

* feat: add OpenWork server settings panel
2026-01-26 23:00:47 -08:00

54 lines
2.3 KiB
TypeScript

import { minimatch } from "minimatch";
import type { McpItem } from "./types.js";
import { readJsoncFile, updateJsoncTopLevel } from "./jsonc.js";
import { opencodeConfigPath } from "./workspace-files.js";
import { validateMcpConfig, validateMcpName } from "./validators.js";
function getMcpConfig(config: Record<string, unknown>): Record<string, Record<string, unknown>> {
const mcp = config.mcp;
if (!mcp || typeof mcp !== "object") return {};
return mcp as Record<string, Record<string, unknown>>;
}
function getDeniedToolPatterns(config: Record<string, unknown>): string[] {
const tools = config.tools;
if (!tools || typeof tools !== "object") return [];
const deny = (tools as { deny?: unknown }).deny;
if (!Array.isArray(deny)) return [];
return deny.filter((item) => typeof item === "string") as string[];
}
function isMcpDisabledByTools(config: Record<string, unknown>, name: string): boolean {
const patterns = getDeniedToolPatterns(config);
if (patterns.length === 0) return false;
const candidates = [`mcp.${name}`, `mcp.${name}.*`, `mcp:${name}`, `mcp:${name}:*`, "mcp.*", "mcp:*"];
return patterns.some((pattern) => candidates.some((candidate) => minimatch(candidate, pattern)));
}
export async function listMcp(workspaceRoot: string): Promise<McpItem[]> {
const { data: config } = await readJsoncFile(opencodeConfigPath(workspaceRoot), {} as Record<string, unknown>);
const mcpMap = getMcpConfig(config);
return Object.entries(mcpMap).map(([name, entry]) => ({
name,
config: entry,
source: "config.project",
disabledByTools: isMcpDisabledByTools(config, name) || undefined,
}));
}
export async function addMcp(workspaceRoot: string, name: string, config: Record<string, unknown>): Promise<void> {
validateMcpName(name);
validateMcpConfig(config);
const { data } = await readJsoncFile(opencodeConfigPath(workspaceRoot), {} as Record<string, unknown>);
const mcpMap = getMcpConfig(data);
mcpMap[name] = config;
await updateJsoncTopLevel(opencodeConfigPath(workspaceRoot), { mcp: mcpMap });
}
export async function removeMcp(workspaceRoot: string, name: string): Promise<void> {
const { data } = await readJsoncFile(opencodeConfigPath(workspaceRoot), {} as Record<string, unknown>);
const mcpMap = getMcpConfig(data);
delete mcpMap[name];
await updateJsoncTopLevel(opencodeConfigPath(workspaceRoot), { mcp: mcpMap });
}