forge-mcp
# forge-mcp
**The director of the forge pipeline.** An MCP server that tells Claude Code *which phase to
run next, which subagent must run it, and what it must produce*, then validates the evidence
before letting the flow advance.
**No phase is executed by the main agent.** Each one goes to a dedicated subagent with the
phase's full criterion; the main agent directs β it asks for the phase, launches the subagent,
and closes the phase with its evidence. forge never runs the work itself, and never blocks
either: see [the contract](./PROTOCOL.md) for who enforces what.
πͺπΈ [LΓ©elo en espaΓ±ol](./README.es.md)
---
## The idea
A long piece of work β design a feature, build it, test it, ship it β is easy to do out of
order, skip a step of, or declare "done" without proof. forge turns that flow into a
**pipeline of phases** that can only advance in order, and only when each phase hands back
real evidence.
- forge **says which phase is next**, **which subagent runs it**, and exactly what it expects
(the full criteria, not a summary) β as a brief ready to hand to an `Agent`.
- **A dedicated subagent executes** the phase β reading code, writing it, running tests. The
main agent directs; it does not build.
- The subagent **records what it decided and why** as it goes, so the reasoning outlives the
session instead of dying with the context.
- The phase is **reported done with evidence**; forge **validates** it and **advances**.
- You **cannot skip a phase**, cannot close one with empty or fake evidence, and cannot
finish before every phase is closed.
Why a subagent and not the main agent: 13 phases of full criteria would flood the main
context, and each phase deserves to start clean. The main agent keeps the thread with the
user; the subagents keep the work.
The state lives in **SQLite** (`node:sqlite`, a Node built-in β no native build step), so a
run **resumes in any session**: Claude's context does not survive a close, a compaction or
picking up the next day β the flow's state does.
---
## The 13 phases
```
classify β clarify β setup β precondition β design β plan β build
β gates β qa β reconcile β contraste β reflect β deliver
```
| Phase | Subagent | What it does | Asks the user | Optional |
|---|---|---|:-:|:-:|
| `classify` | analyst | Classify the request's nature (QUESTION / MICRO / STANDARD / HIGH-RISK) and scope. | | |
| `clarify` | analyst | Detect ambiguities that change the product, with options and consequences. | **yes** | |
| `setup` | architect | Decide the stack and its REAL versions (via `npm view` / official CLIs, never from memory); scaffold, install deps, strict linter. | | |
| `precondition` | analyst | Verify the conditions to safely start the build are actually met (tools present, env ready). | | |
| `design` | architect | Before coding: brainstorm the solution, a UX/UI design brief, a QA plan with edge cases, a code-quality guide β persisted as artifacts. | | |
| `plan` | architect | Decompose the work into atomic tasks with disjoint file ownership, grouped into independent blocks. | | |
| `build` | builder | Implement the plan β reusing what exists, never rewriting a whole file, respecting the strict linter. | | |
| `gates` | verifier | Run the repo's real gates (strict lint + build + tests). Truth is the exit code, not the model's self-report. | | |
| `qa` | qa | Verify for real: run the app end to end, then ATTACK it (odd inputs, limits, impossible states) and report what broke. | | |
| `reconcile` | builder | Only if parallel work may have duplicated logic or created conflicts β resolve them. | | **yes** |
| `contraste` | reviewer | An independent review that does NOT know the QA verdict, exploring the finished build with fresh eyes. | | |
| `reflect` | analyst | Look back on how this run went (what passed, what fell back, what failed) and extract lessons. | | |
| `deliver` | releaser | Publish per the request (remote push, deploy). Never reports "online" without a real URL; skips explicitly with a reason if it does not apply. | | **yes** |
Each phase carries a `goal` (short instruction), a full `systemPrompt` (the complete criteria,
handed to the subagent verbatim), an `agent` role, a `delegationBrief` (one subagent or several
in parallel β `build` opens by block, `contraste` demands a blind one), the `skills` to load,
and flags for whether it needs the user or is optional. All in `src/phases.ts`.
The `agent` field is a **role**, not a specific agent name, because every project has its own:
the main agent resolves the role against whatever agents exist and falls back to a
general-purpose one.
One consequence worth stating: `clarify` runs in a subagent, but **the subagent does not ask
the user**. It detects the ambiguities and returns them; the main agent brings them to the
user, because it is the one holding the conversation.
---
## Evidence is validated, not trusted
forge does not accept "done" as a string. `forge_complete_phase` validates the **evidence**
each phase must hand back, and rejects the close if it does not hold up:
- `gates` requires the real exit codes (lint / build / test) and they must all be `0`.
- `qa` requires a structured result: it passed, and it was actually attacked.
- `clarify` (a user decision) requires an explicit `userConfirmed`.
- An **optional** phase can only be skipped with a stated reason.
So a phase cannot be closed with an invented summary. The pipeline advances on proof.
---
## The skills library (128)
`src/skills.ts` + the `skills/` folder ship 128 skills, each with its own `SKILL.md`,
versioned in the repo (forge is self-contained β it does not depend on anything external for
these). Each phase declares which skills it loads; the domain map (`SKILL_MAP`) says which
skills belong to which domain. Skills load **on demand** β Claude asks for the list with
`forge_skills` and the content of a specific one with `forge_skill(name)`, never all at once.
---
## The 10 tools
| Tool | What it does |
|---|---|
| `forge_start(request, cwd)` | Start a new run; returns the first phase (`classify`) as a delegation brief. |
| `forge_status(runId?)` | Which phase a run is in and its progress (`[x]` closed, `[>]` current, `[ ]` pending). |
| `forge_next(runId?)` | The CURRENT phase as a **brief ready to hand to a subagent**: which role to use, how to split it, the skills to load, and the full `systemPrompt` to pass verbatim. |
| `forge_start_phase(runId?, agentId)` | Called right BEFORE launching the subagent. Starts the phase clock and records who runs it. |
| `forge_log(runId?, kind, title, detail?)` | Record a `decision` (with its reason), an `evidence` (real exit codes, QA verdict), or a `note` in the timeline. |
| `forge_complete_phase(runId?, summary, evidence, agentId?)` | Close the current phase with validated evidence, then advance. If it was the last phase, mark the run `done`. |
| `forge_timeline(runId?, write?)` | Render the run: Markdown with a Mermaid gantt + trackable JSON. Writes `.ai/forge/<runId>.md` and `.json` unless `write=false`. |
| `forge_tasks()` | List active runs β to resume from any session without re-reading context. |
| `forge_skills(phase?)` | List the full skills library, or filtered by domain if a phase is given. |
| `forge_skill(name)` | Return a specific skill's `SKILL.md` to load and apply. |
### The phase token
`forge_next` embeds a `[forge:<runId>:<phase>]` marker in the subagent prompt. It exists so the
`forge-flow` gate can check, when it sees an `Agent` call, that the prompt really is the current
phase's brief β without it, "I delegated this phase" would be self-report and an invented brief
would pass.
---
## The timeline
Every run keeps an append-only log of what actually happened: which subagent ran each phase and
how long it took, what was decided **and why**, and the real verification evidence. It lives in
SQLite next to the flow state, so it survives a session close or a compaction.
`forge_timeline` renders it two ways, because they are read differently:
- **Markdown with a Mermaid gantt** β to look at. GitHub and VS Code render it as-is, so no
tool or server is needed to see where the time went.
- **JSON** β to track. Stable and diffable between runs, so you can mechanically compare
whether the flow is getting better.
```mermaid
gantt
title Time per phase
dateFormat x
axisFormat %H:%M:%S
section Pipeline
Build (builder) :done, 1788703521000, 1788703941000
Deterministic gates (verifier) :done, 1788703941500, 1788704019500
QA - it works and holds (qa) :done, 1788704020000, 1788704280000
```
Both are written to `.ai/forge/<runId>.md` and `.ai/forge/<runId>.json` inside the project, so
the record is versioned alongside the code it documents.
---
## Install
Requires **Node β₯ 22.5** (for `node:sqlite`). Nothing else β no native build step, no
database to provision.
### On any machine (published package)
Add this to Claude Code's `.mcp.json` (project) or your user config. There is nothing to
install first: `npx` fetches it on the first run.
```json
{
"mcpServers": {
"forge": { "command": "npx", "args": ["-y", "@devrik-tools/forge-mcp"] }
}
}
```
Pin a version when you want the same one everywhere:
`"args": ["-y", "@devrik-tools/forge-mcp@1.1.0"]`.
Prefer it resident rather than fetched each time:
```bash
npm install -g @devrik-tools/forge-mcp
```
```json
{ "mcpServers": { "forge": { "command": "forge-mcp" } } }
```
Restart the session (or run `/mcp`) and check that `forge_tasks` answers.
### Checking what you actually have
`/mcp` lists forge with the version it announces in its handshake, which is read from the
installed `package.json` β so that number IS the installed version. (Before 1.1.1 it was
pinned to `0.1.0` and told you nothing; if you see `0.1.0`, you are on an older build no
matter what npm says.)
From a shell:
```bash
npm view @devrik-tools/forge-mcp version # what the registry has
npm ls -g @devrik-tools/forge-mcp # what this machine has, if installed globally
```
`npx` keeps its own cache, so a machine can run an older copy than the registry holds until
the cache turns over. `npx -y @devrik-tools/forge-mcp@latest` forces the current one.
**What does NOT travel with the install.** The state lives in a global SQLite file at
`~/.forge/forge-mcp.db`, which is per machine: a second PC starts with no runs, and a run
started on one machine is not visible on the other. Point `FORGE_MCP_DB` at a synced path if
you want them to share, and read the concurrency note below before you do β two machines
writing one SQLite file over a sync service is not the same as two processes on one disk.
The 128 skills ship inside the package, so a fresh machine needs no extra fetch for them β
at the price of size: 4.1 MB packed, 12.9 MB on disk, 901 files, 874 of them skills. `npx`
pays that once and caches it. That trade is the point of the design, not an oversight: forge
carries its own arsenal instead of depending on whatever happens to be installed.
### From a clone (to work on forge itself)
```bash
git clone https://github.com/DevRik99/forge-mcp
cd forge-mcp
npm install
npm run build
```
```json
{ "mcpServers": { "forge": { "command": "node", "args": ["dist/server.js"] } } }
```
---
## How a run resumes
The state lives in a single **global** SQLite DB at `~/.forge/forge-mcp.db` (override with
`FORGE_MCP_DB`), so every project shares one store and runs are told apart by their `cwd`.
Two tables:
- `runs`: one row per run (`id`, `request`, `cwd`, `current_phase`, `status`, timestamps).
- `phase_artifacts`: one row per closed phase (`run_id`, `phase`, `summary`, `closed_at`,
`started_at`, `agent_id`) β the real decision/artifact reported, not a boolean flag, plus
who ran it and how long it took.
- `timeline`: append-only, one row per event (`phase_started`, `phase_closed`, `decision`,
`evidence`, `note`) with its agent and timestamp.
After a lost session (close, compaction, next day), any new Claude session with this MCP
connected can:
1. Call `forge_tasks()` to see which runs are still active and in what phase.
2. Call `forge_next(runId)` to get the current phase's full `systemPrompt` again β Claude
does not need to remember anything; forge hands it back verbatim.
3. Read the closed phases' artifacts via `forge_status` so nothing already decided (e.g. in
`clarify`) is re-asked.
---
## Guarantees (and honest limits)
forge **enforces**: the phase order, closing every phase before finishing, and validated
evidence per phase (no fake `gates`/`qa`, no skipping user decisions or optional phases
without a reason). Under concurrency, closing a phase is atomic β a stale double-close is
rejected, not silently applied.
forge **cannot** stop you from editing the project *without* using it at all, and it cannot
stop the main agent from doing a phase itself β an MCP only sees its own tools, not your
`Edit`/`Write`/`Bash`. Trying to enforce from here would be a paper lock.
That half is the `forge-flow` gate's job in
[claude-gates](https://github.com/DevRik99/claude-gates), which *does* see those calls and can
tell whether they come from the main agent or from a subagent. The two pair like this:
| | forge-mcp | claude-gates |
|---|---|---|
| Sees | only its own tools | every `Edit`/`Write`/`Bash`/`Agent`, and **who** made it |
| Guarantees | the criterion is complete; no phase closes on fake evidence | the main agent does not touch the code; only a subagent does |
| Role | director β **never blocks** | lock β **knows nothing about phases** |
They are coupled by exactly one thing: the SQLite DB. The gate reads it, forge writes it,
neither imports the other's code. Full contract in [PROTOCOL.md](./PROTOCOL.md).
## License
MIT.
TDQS
Scored across 7 tools
Each tool has a distinct job: start a run, advance a phase, check progress, list runs, and load skill metadata/content. The only close pair, forge_next and forge_status, is separated by guidance vs. state, so an agent should not misselect.
All tools share the forge_ prefix and snake_case, making them predictable. However, the pattern mixes verbs (start, complete_phase) with bare nouns (status, tasks, skills, skill), so it is not a fully consistent verb_noun convention.
Seven tools is well-scoped for a pipeline-orchestration server. Each tool covers a necessary part of the run lifecycle or skill access without redundancy or bloat.
The core lifecycle is covered: start, advance, inspect status, list runs, and load skills. The only notable gap is the lack of an explicit cancel/abort operation for a run, though that may be intentionally unsupported by the guided pipeline model.