feat(mcp): expose Forge as an MCP server (forge mcp serve)#25
feat(mcp): expose Forge as an MCP server (forge mcp serve)#25hoangsonww merged 1 commit intomasterfrom
Conversation
New surface that lets Claude Desktop, Cursor, Continue, and any other MCP client drive Forge's planner / executor / task store from their own chat. Two trust tiers: - Read-only (default): forge_status, forge_plan, forge_get_task, forge_list_tasks. Never writes a file. - Execute (--allow-execute / FORGE_MCP_ALLOW_EXECUTE=true): adds forge_run and forge_cancel_task. Auto-approves routine permissions because MCP cannot show interactive prompts; critical-risk shell commands are still hard-blocked by the sandbox classifier. Implementation in src/mcp-server/server.ts wraps the same orchestrateRun() that powers the CLI/REPL/dashboard, so MCP-driven plans are byte-identical paths to forge run. Wired via 'forge mcp serve' subcommand in src/cli/commands/mcp.ts (supports --allow-execute and --cwd). - 9 unit tests covering tool exposure (4 vs 6), forge_status payload, list-task filters, get-task not-found path, plan invocation, cancel idempotency on terminal tasks. - docs/MCP-SERVER.md: full reference + per-client setup (Claude Desktop, Cursor, plain MCP clients). - ARCHITECTURE.md §9.2: surface diagram + permission model. - README.md: new pointer block in the Skills/MCP section.
There was a problem hiding this comment.
Code Review
This pull request enables Forge to operate as a Model Context Protocol (MCP) server, allowing external agents like Claude Desktop or Cursor to utilize Forge's planning and execution capabilities. The implementation includes a new forge mcp serve CLI command, comprehensive documentation, and a server architecture with distinct read-only and execution trust tiers. Review feedback identifies a high-severity logic bug where task filtering is applied after the database limit, an incorrect import path for the Zod library, and redundant default value handling in the task listing tool.
|
|
||
| import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; | ||
| import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; | ||
| import { z } from 'zod/v3'; |
| let rows: TaskIndexRow[] = listTasks(projectId, limit ?? 20); | ||
| if (status) rows = rows.filter((r) => r.status === status); |
There was a problem hiding this comment.
There is a logic bug in how tasks are filtered by status. The listTasks function applies the limit at the database level before the status filter is applied in memory. This means if the most recent limit tasks do not match the requested status, the result will be empty even if matching tasks exist further back in the history. To fix this, the status filter should be passed down to the database query in listTasks.
| }, | ||
| }, | ||
| async ({ limit, status, projectId }) => { | ||
| let rows: TaskIndexRow[] = listTasks(projectId, limit ?? 20); |
There was a problem hiding this comment.
The nullish coalescing operator ?? 20 is redundant here because the inputSchema for this tool already defines a default value of 20 for the limit parameter. Zod ensures that limit will always be a number when the handler is executed.
| let rows: TaskIndexRow[] = listTasks(projectId, limit ?? 20); | |
| let rows: TaskIndexRow[] = listTasks(projectId, limit); |
New surface that lets Claude Desktop, Cursor, Continue, and any other MCP client drive Forge's planner / executor / task store from their own chat.
Two trust tiers:
Implementation in src/mcp-server/server.ts wraps the same orchestrateRun() that powers the CLI/REPL/dashboard, so MCP-driven plans are byte-identical paths to forge run.
Wired via 'forge mcp serve' subcommand in src/cli/commands/mcp.ts (supports --allow-execute and --cwd).