#!/usr/bin/env node import process from "node:process"; import { startHttpToolServer } from "./mcp-http.js"; import { startToolServer } from "./mcp-stdio.js"; import { parseServerArgs } from "./root-args.js"; import { TOOL_DEFINITIONS } from "./tool-definitions.js"; import { COMMAND_TOOL_DEFINITIONS } from "./command-tool-definitions.js"; import { WorkspaceService } from "./workspace-service.js"; import { CommandService } from "./command-service.js"; import { AuditLog } from "./audit-log.js"; const SERVER_NAME = "coding-agent-mcp"; const SERVER_VERSION = "1.0.0"; function parseApiKeys() { const rawValue = process.env.CODING_AGENT_MCP_API_KEYS ?? process.env.MCP_API_KEYS ?? ""; return rawValue .split(",") .map((value) => value.trim()) .filter(Boolean); } function parseCorsOrigins() { return (process.env.CODING_AGENT_MCP_CORS_ORIGINS ?? "") .split(",") .map((value) => value.trim()) .filter(Boolean); } function resolveExecutionBackend(transport) { const configured = process.env.CODING_AGENT_MCP_EXECUTION_BACKEND; if (configured) return configured; return transport === "stdio" ? "local" : "disabled"; } async function main() { const options = parseServerArgs(process.argv.slice(2), { binaryName: SERVER_NAME, envVarName: "CODING_AGENT_MCP_ROOTS" }); const workspaceService = new WorkspaceService({ roots: options.roots }); const auditLog = new AuditLog(process.env.CODING_AGENT_MCP_AUDIT_LOG); const executionBackend = resolveExecutionBackend(options.transport); const commandService = new CommandService({ roots: options.roots, executionBackend }); // Do not advertise a command tool that this deployment intentionally cannot execute. An agent // cannot recover from that failure, and seeing it in tools/list makes it waste a turn trying. const tools = executionBackend === "local" ? [...TOOL_DEFINITIONS, ...COMMAND_TOOL_DEFINITIONS] : TOOL_DEFINITIONS; const callTool = async (toolName, toolArguments, context) => { switch (toolName) { case "list_files": return workspaceService.listFiles(toolArguments, context); case "read_file": return workspaceService.readFile(toolArguments, context); case "read_files": return workspaceService.readFiles(toolArguments, context); case "search_text": return workspaceService.searchText(toolArguments, context); case "write_file": return workspaceService.writeFile(toolArguments, context); case "read_binary_file": return workspaceService.readBinaryFile(toolArguments, context); case "write_binary_file": return workspaceService.writeBinaryFile(toolArguments, context); case "apply_patch": return workspaceService.applyPatch(toolArguments, context); case "make_directory": return workspaceService.makeDirectory(toolArguments, context); case "delete_path": return workspaceService.deletePath(toolArguments, context); case "move_path": return workspaceService.movePath(toolArguments, context); case "rename_path": return workspaceService.renamePath(toolArguments, context); case "file_info": return workspaceService.fileInfo(toolArguments, context); case "run_task": return commandService.runTask({ ...toolArguments, onEvent: context?.emitEvent, sessionId: context?.sessionId ?? null, workspaceSubpath: context?.workspaceSubpath ?? null }); default: throw new Error(`Unknown tool: ${toolName}`); } }; if (options.transport === "stdio" || options.transport === "both") { startToolServer({ serverName: SERVER_NAME, serverVersion: SERVER_VERSION, tools, callTool, auditLog }); } if (options.transport === "http" || options.transport === "both") { const listener = await startHttpToolServer({ serverName: SERVER_NAME, serverVersion: SERVER_VERSION, tools, callTool, onSessionInitialize: (context) => workspaceService.ensureWorkspace(context), auditLog, host: options.host, port: options.port, apiKeys: parseApiKeys(), corsOrigins: parseCorsOrigins(), requireAuth: true }); process.stderr.write( `${SERVER_NAME} HTTP listening on http://${listener.host}:${listener.port}\n` ); } } main().catch((error) => { process.stderr.write(`${error instanceof Error ? error.stack : String(error)}\n`); process.exit(1); });