Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -767,6 +767,23 @@ forge mcp status
Both `stdio` and HTTP-stream transports supported. OAuth 2.0 + PKCE or
API key auth. Tokens stored in the OS keychain.

### Forge as an MCP server

Forge can also run *as* an MCP server, not just consume them. Drop this into your Claude Desktop / Cursor / Continue config and the calling agent gets `forge_status`, `forge_plan`, `forge_get_task`, `forge_list_tasks` (read-only by default) plus `forge_run` and `forge_cancel_task` when started with `--allow-execute`:

```json
{
"mcpServers": {
"forge": {
"command": "forge",
"args": ["mcp", "serve"]
}
}
}
```

Full reference, security model, and per-client setup: [`docs/MCP-SERVER.md`](docs/MCP-SERVER.md).

---

## Run in a container (Docker or Podman)
Expand Down
42 changes: 42 additions & 0 deletions docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
- [8. Conversation & persistence](#8-conversation--persistence)
- [9. UI topology](#9-ui-topology)
- [9.1 VS Code extension](#91-vs-code-extension)
- [9.2 MCP server (`forge mcp serve`)](#92-mcp-server-forge-mcp-serve)
- [10. CI/CD pipeline](#10-cicd-pipeline)
- [11. Deployment topologies](#11-deployment-topologies)
- [12. Runtime metrics at a glance](#12-runtime-metrics-at-a-glance)
Expand All @@ -44,6 +45,7 @@ flowchart TB
REPL["REPL (raw-mode editor)"]:::surface
UI["Dashboard (HTTP + WS)"]:::surface
VSCX["VS Code extension<br/>(vscode-extension/)"]:::surface
MCPS["MCP server<br/>(forge mcp serve)"]:::surface
end

ORCH["Orchestrator<br/>src/core/orchestrator.ts"]:::core
Expand Down Expand Up @@ -108,6 +110,7 @@ Code it maps to:
| REPL | `src/cli/repl.ts` + `src/cli/repl-input.ts` |
| UI | `src/ui/server.ts` + `src/ui/public/` |
| VS Code extension | `vscode-extension/` (separate npm package, ships to the VS Code Marketplace) |
| MCP server | `src/mcp-server/server.ts` + `forge mcp serve` (exposes Forge as an MCP server consumed by Claude Desktop, Cursor, …) |
| Orchestrator | `src/core/orchestrator.ts` |
| Agentic loop | `src/core/loop.ts` |
| Agents | `src/agents/{planner,architect,executor,reviewer,debugger,memory}.ts` |
Expand Down Expand Up @@ -548,6 +551,45 @@ npx @vscode/vsce package --no-dependencies # produces .vsix
npx @vscode/vsce publish --no-dependencies # marketplace
```

### 9.2 MCP server (`forge mcp serve`)

Forge can also run *as* an MCP server, not just a consumer. Other agents (Claude Desktop, Cursor, Continue, your own MCP client) register Forge once and can plan or run tasks through their own chat surfaces.

```mermaid
flowchart LR
classDef ag fill:#0f172a,stroke:#38bdf8,color:#f1f5f9,rx:4,ry:4
classDef srv fill:#082f49,stroke:#38bdf8,color:#e0f2fe,rx:4,ry:4
classDef core fill:#0c4a6e,stroke:#22d3ee,color:#cffafe,rx:4,ry:4
classDef d fill:#18181b,stroke:#f59e0b,color:#fef3c7,rx:4,ry:4

CD["Claude Desktop / Cursor / Continue"]:::ag
MCP["forge mcp serve<br/>(stdio JSON-RPC)"]:::srv
ORCH["src/core/orchestrator.ts"]:::core
IDX[("~/.forge/global/index.db")]:::d
TASKS[("project/.forge/tasks/*.json")]:::d

CD <-- "tools/list · tools/call" --> MCP
MCP -- "forge_plan · forge_run" --> ORCH
MCP -- "forge_get_task · forge_list_tasks" --> IDX
MCP -- "forge_get_task" --> TASKS
ORCH -- "writes" --> TASKS
ORCH -- "writes" --> IDX
```

Two trust tiers:

- **Read-only** (default): `forge_status`, `forge_plan`, `forge_get_task`, `forge_list_tasks`. Never writes a file. Safe to expose to any agent.
- **Execute** (opt-in via `--allow-execute` or `FORGE_MCP_ALLOW_EXECUTE=true`): adds `forge_run` and `forge_cancel_task`. The calling agent can edit files and run shell commands inside the working directory.

Implementation lives in `src/mcp-server/server.ts`. The server uses the same `orchestrateRun()` entry point as the CLI / REPL / dashboard, and the same `~/.forge/global/index.db` index that powers task lookups everywhere else — so tools called through the MCP surface are byte-identical paths to tools called through `forge run`.

Permission model when called from an MCP client:

- Read-only tools never trigger the permission manager.
- `forge_run` sets `skipRoutine: true`, `allowFiles: true`, `allowShell: true`, `nonInteractive: true`. Critical-risk shell commands (classified by `src/sandbox/shell.ts`) are still hard-blocked.

Full reference: [`docs/MCP-SERVER.md`](MCP-SERVER.md). Includes example configurations for Claude Desktop, Cursor, and any plain MCP client.

---

## 10. CI/CD pipeline
Expand Down
250 changes: 250 additions & 0 deletions docs/MCP-SERVER.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,250 @@
# Forge as an MCP server

Forge can run as a [Model Context Protocol](https://modelcontextprotocol.io) server, exposing its planner, executor, and task store as MCP tools. Other agents — Claude Desktop, Cursor, Continue, Zed, your own MCP client — register Forge once and can plan or run tasks through their own chat surfaces, with no shell round-trip and no special integration code.

## What gets exposed

Two tiers, by trust level.

### Read-only tier (always on)

| Tool | What it does | Side effects |
|---|---|---|
| `forge_status` | Runtime info: version, default provider, available providers, cwd. | None. |
| `forge_plan` | Generate a plan for a task without executing. Returns the plan JSON. | None — `planOnly: true`. |
| `forge_get_task` | Look up a task by ID. Resolves the project automatically from the global index. | None — read from `~/.forge/global/index.db` and the project's task JSON. |
| `forge_list_tasks` | Recent tasks newest-first. Filter by `status` or `projectId`. | None. |

### Execute tier (opt-in)

Enabled by `--allow-execute` or `FORGE_MCP_ALLOW_EXECUTE=true`. **These tools modify your project.**

| Tool | What it does | Side effects |
|---|---|---|
| `forge_run` | Full classify → plan → execute → verify. Permission prompts are auto-approved (`skipRoutine: true`, `allowFiles: true`, `allowShell: true`) because MCP cannot show interactive UI. | Writes files, runs shell commands, makes model calls. |
| `forge_cancel_task` | Cancel a live task by ID. Idempotent — already-terminal tasks return their current state without retransitioning. | Updates the task's status. |

## Quick start

```bash
# 1. Install Forge if you haven't
npm install -g @hoangsonw/forge

# 2. Sanity check
forge mcp serve --help
```

## Wire it up

### Claude Desktop

Edit your config file:

- macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
- Windows: `%APPDATA%\Claude\claude_desktop_config.json`
- Linux: `~/.config/Claude/claude_desktop_config.json`

```json
{
"mcpServers": {
"forge": {
"command": "forge",
"args": ["mcp", "serve"],
"env": {}
}
}
}
```

To enable execution tools as well:

```json
{
"mcpServers": {
"forge": {
"command": "forge",
"args": ["mcp", "serve", "--allow-execute"]
}
}
}
```

Restart Claude Desktop. The Forge tools appear under the 🔌 icon.

### Cursor

`~/.cursor/mcp.json` (or `Cmd+Shift+P → MCP: Edit Configuration`):

```json
{
"mcpServers": {
"forge": {
"command": "forge",
"args": ["mcp", "serve"]
}
}
}
```

### Any MCP client

The transport is plain stdio JSON-RPC. Spawn `forge mcp serve` (or `forge mcp serve --allow-execute`) and follow the [MCP spec](https://modelcontextprotocol.io/specification).

## Flags

| Flag | Default | Notes |
|---|---|---|
| `--allow-execute` | off | Adds `forge_run` and `forge_cancel_task`. |
| `--cwd <path>` | `process.cwd()` | Default working directory used when a tool call doesn't specify one. |

Environment variables:

| Var | Effect |
|---|---|
| `FORGE_MCP_ALLOW_EXECUTE=true` | Same as `--allow-execute`. Useful when the MCP client doesn't let you customize args. |

## Tool reference

### `forge_status`

```jsonc
// input
{}

// output
{
"version": "1.0.0",
"provider": "ollama",
"defaultMode": "balanced",
"cwd": "/Users/me/work/project",
"providers": [
{ "name": "ollama", "available": true },
{ "name": "anthropic", "available": false }
],
"allowExecute": false
}
```

### `forge_plan`

```jsonc
// input
{
"task": "Add a /healthz endpoint and a test for it.",
"cwd": "/Users/me/work/project" // optional
}

// output
{
"taskId": "task_22ce1f014275",
"plan": { "steps": [ /* … */ ] },
"summary": "…",
"status": "planned"
}
```

### `forge_run` (requires `--allow-execute`)

```jsonc
// input
{
"task": "Fix the failing test in src/server.test.ts.",
"mode": "balanced" // optional; "balanced" or "risky"
}

// output
{
"taskId": "task_…",
"status": "completed",
"success": true,
"summary": "Fixed.",
"filesChanged": ["src/server.ts"],
"durationMs": 18412,
"costUsd": 0
}
```

### `forge_get_task`

```jsonc
// input
{ "taskId": "task_22ce1f014275" }

// output: full Task JSON (id, prompt, plan, status, result, …)
```

Resolves the project automatically — works for tasks created in any project on the host, not just `cwd`.

### `forge_list_tasks`

```jsonc
// input
{ "limit": 20, "status": "completed", "projectId": "proj_xxx" } // all optional

// output
[
{ "id": "task_…", "title": "…", "status": "completed", "mode": "balanced",
"updated_at": "2026-04-27T12:00:00Z", "attempts": 1, "project_id": "…" },
]
```

### `forge_cancel_task` (requires `--allow-execute`)

```jsonc
// input
{ "taskId": "task_…" }

// output
{ "taskId": "task_…", "status": "cancelled" }
// or, on an already-terminal task:
{ "taskId": "task_…", "status": "completed", "alreadyTerminal": true }
```

## Permission model

When called from an MCP client, Forge cannot show an interactive permission prompt. Instead:

- **Read-only tools** never need permission — they don't trigger the permission manager at all.
- **`forge_run`** sets `skipRoutine: true`, `allowFiles: true`, `allowShell: true`, `nonInteractive: true`. Critical-risk shell commands (classified by `src/sandbox/shell.ts`) are still hard-blocked.
- **`forge_cancel_task`** is a state-machine transition, not a tool invocation, so the permission manager isn't involved.

This means: enabling `--allow-execute` is a meaningful trust decision. The calling agent is allowed to write files and run shell commands inside whatever directory you set as `--cwd` (or wherever Forge was launched from). Pair it with sandboxing if the calling agent isn't fully trusted.

## Architecture

```mermaid
flowchart LR
classDef ag fill:#0f172a,stroke:#38bdf8,color:#f1f5f9,rx:4,ry:4
classDef srv fill:#082f49,stroke:#38bdf8,color:#e0f2fe,rx:4,ry:4
classDef core fill:#0c4a6e,stroke:#22d3ee,color:#cffafe,rx:4,ry:4
classDef d fill:#18181b,stroke:#f59e0b,color:#fef3c7,rx:4,ry:4

CD["Claude Desktop / Cursor / Continue"]:::ag
MCP["forge mcp serve<br/>(stdio JSON-RPC)"]:::srv
ORCH["src/core/orchestrator.ts"]:::core
IDX[("~/.forge/global/index.db")]:::d
TASKS[("project/.forge/tasks/*.json")]:::d

CD <-- "tools/list · tools/call" --> MCP
MCP -- "forge_plan · forge_run" --> ORCH
MCP -- "forge_get_task · forge_list_tasks" --> IDX
MCP -- "forge_get_task" --> TASKS
ORCH -- "writes" --> TASKS
ORCH -- "writes" --> IDX
```

The MCP server is a thin shim — it doesn't reimplement orchestration, it calls the same `orchestrateRun()` that the CLI / REPL / dashboard use, and it reads from the same SQLite index that powers the dashboard's task list. So a plan generated through Claude Desktop and a plan generated through `forge run` are byte-identical paths.

## Troubleshooting

- **`forge: command not found`** — confirm `forge` is on your PATH (`which forge`). If you installed it via nvm, the MCP client may need an absolute path: `"command": "/Users/you/.nvm/versions/node/v20.x/bin/forge"`.
- **Tools don't appear in Claude Desktop** — check the Claude logs (`~/Library/Logs/Claude/`) for MCP connection errors. The most common cause is a typo in the JSON config.
- **`forge_run` not exposed** — you forgot `--allow-execute` (or `FORGE_MCP_ALLOW_EXECUTE=true`). Read-only is the default for safety.
- **Cross-project task lookups fail** — make sure the projects you want to access have been opened by `forge` at least once so they're indexed in `~/.forge/global/index.db`.

## Related

- [`docs/ARCHITECTURE.md` §9.2](ARCHITECTURE.md) — where the MCP server fits in the surface map.
- [`vscode-extension/README.md`](../vscode-extension/README.md) — the in-editor surface, complementary to the MCP server.
- [`actions/forge-run/README.md`](../../actions/forge-run/README.md) — the GitHub Action surface.
20 changes: 19 additions & 1 deletion src/cli/commands/mcp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,25 @@ import { McpHttpClient } from '../../mcp/http-transport';
import { authorize, ensureAccessToken, OAuthConfig, loadTokens } from '../../mcp/oauth';
import { setSecret, getSecret } from '../../keychain';

export const mcpCommand = new Command('mcp').description('MCP connection management.');
export const mcpCommand = new Command('mcp').description(
'MCP connection management. Also `forge mcp serve` to expose Forge itself as an MCP server.',
);

mcpCommand
.command('serve')
.description(
'Run Forge as an MCP server on stdio. Other agents (Claude Desktop, Cursor, Continue, …) can register this and call forge_plan / forge_run / forge_get_task / forge_list_tasks / forge_status. Read-only by default; pass --allow-execute or set FORGE_MCP_ALLOW_EXECUTE=true to enable forge_run and forge_cancel_task.',
)
.option('--allow-execute', 'enable execution tools (forge_run, forge_cancel_task)', false)
.option('--cwd <path>', 'default working directory for tool calls')
.action(async (opts: { allowExecute?: boolean; cwd?: string }) => {
bootstrap();
const { runForgeMcpServerOnStdio } = await import('../../mcp-server/server');
await runForgeMcpServerOnStdio({
allowExecute: opts.allowExecute,
defaultCwd: opts.cwd,
});
});

mcpCommand
.command('list')
Expand Down
Loading
Loading