Skip to content

feat(mcp): expose Forge as an MCP server (forge mcp serve)#25

Merged
hoangsonww merged 1 commit intomasterfrom
feat/forge-as-mcp-server
Apr 29, 2026
Merged

feat(mcp): expose Forge as an MCP server (forge mcp serve)#25
hoangsonww merged 1 commit intomasterfrom
feat/forge-as-mcp-server

Conversation

@hoangsonww
Copy link
Copy Markdown
Owner

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.

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.
@hoangsonww hoangsonww self-assigned this Apr 29, 2026
@hoangsonww hoangsonww added bug Something isn't working documentation Improvements or additions to documentation enhancement New feature or request help wanted Extra attention is needed good first issue Good for newcomers feature Feature request labels Apr 29, 2026
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread src/mcp-server/server.ts

import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { z } from 'zod/v3';
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The import path zod/v3 is incorrect. The standard way to import Zod is from the base zod package. This will likely cause a module resolution error at runtime or during build.

Suggested change
import { z } from 'zod/v3';
import { z } from 'zod';

Comment thread src/mcp-server/server.ts
Comment on lines +162 to +163
let rows: TaskIndexRow[] = listTasks(projectId, limit ?? 20);
if (status) rows = rows.filter((r) => r.status === status);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

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.

Comment thread src/mcp-server/server.ts
},
},
async ({ limit, status, projectId }) => {
let rows: TaskIndexRow[] = listTasks(projectId, limit ?? 20);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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.

Suggested change
let rows: TaskIndexRow[] = listTasks(projectId, limit ?? 20);
let rows: TaskIndexRow[] = listTasks(projectId, limit);

@hoangsonww hoangsonww merged commit c2f19fd into master Apr 29, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working documentation Improvements or additions to documentation enhancement New feature or request feature Feature request good first issue Good for newcomers help wanted Extra attention is needed

Projects

Development

Successfully merging this pull request may close these issues.

1 participant