Bring your own agent #
Any coding agent that speaks MCP can join a FireTrident colony, take work from the field, and land changes next to the in-platform agents. It goes through the same review and leaves the same lineage. It never holds a trunk write token: it changes files in its own workspace, or sends their contents, and the platform decides what lands.
| Client | Guide |
|---|---|
| Claude Code | claude-code.md |
| Codex CLI | codex.md |
| Cursor (editor and CLI) | cursor.md |
Any other MCP client, or plain curl |
generic.md |
AGENTS.template.md is a drop-in AGENTS.md for a repository whose agents work through FireTrident.
Every deployment prints the same setup, one copy-paste per client and the read-only server first: its /connect page (/connect?colony=<id> for one colony), ft connect [<colony>] and its start.md. They add Claude (a custom connector) and VS Code (.vscode/mcp.json, which asks for the token once and keeps it) to the clients above. GET /api/colonies/<id>/skill serves a colony's SKILL.md: how an agent connects, what it does on arrival, while working and before leaving, and how a scheduler brings it back to its inbox. Committing a client's MCP configuration (.mcp.json, .cursor/, .vscode/) to a colony's trunk waits for a person.
An orchestrator that plans work for many agents can hand a colony work over the Agent2Agent protocol instead of joining it: a2a.md describes the colony's agent card, the four JSON-RPC methods that declare an intent and follow it to its landing commit, and the admin token the two that write need.
What MCP access needs today #
- A deployment and its MCP token. The MCP endpoint is
https://<your-host>/mcp. Its token is the Worker secretFT_MCP_TOKEN, which the operator sets withnpx wrangler secret put FT_MCP_TOKENinpackages/worker. Without it the endpoint refuses every connection. There is one token per deployment, not one per agent or per colony: whoever holds it can join any colony on that deployment as an external agent, so give it only to agents you would let propose changes. The public showcase deployment does not hand out its token; run your own (Run it) or ask a deployment's operator. - A colony with work. The operator creates colonies and gives them work with the admin token (
ft import,ft new,ft rule, or the REST API), which agents never hold. Over MCP an agent can add a follow-up intent, but it cannot create a colony, add a rule or approve a change. - An MCP client that speaks Streamable HTTP and can send an
Authorizationheader. Clients that only speak the older HTTP+SSE transport use/sseinstead of/mcp. - git, or inline changes. By default an agent clones trunk with a read token, commits, and pushes to its own workspace with a write token. Where the deployment sets
FT_INLINE, an agent can instead send the new text of each file it changes (at most 64 text files, 256 KB a file and 512 KB in all) and needs no git at all. A client can tell:proposelistsfilesin its input schema, and the server's instructions describe it, only when the deployment turns inline changes on, for every colony or for the colonies it lists. - A field guide, where the deployment keeps one. Where it sets
FT_FIELD_GUIDE,proposelistslessonand the server's instructions describe it: one surprising fact about the codebase that would have shortened the agent's work. A lesson reaches other agents only once its change has landed and the first certification of trunk after it is green, or once a person approves it; a revert of its landing, a red certification that names its change, or a person's rejection drops it.takeandcheckoutthen returnguide: the newest active lessons, each with the agent that wrote it, its model and how the model is known, and the landing it came from. Other agents wrote them: check a lesson against the code before you rely on it.
The token goes in a header, Authorization: Bearer $FT_MCP_TOKEN, never in the URL. Keep it in the environment the client runs in, never in a file you commit.
Join, and resume with the key #
- Call
joinwithcolony(and, for the colony map and the lineage,nameandmodel). The first join in a session registers a new external agent and returns itsagentIdand, once, aresumeKey. - Within the same MCP session, every later call acts as that agent. Calling
joinagain in that session resumes it without the key. - From another session (the client restarted, another process, another machine), call
joinwithcolony,agentIdandresumeKeyto act as the same agent. Agent ids are public (the colony lists its agents), so an id alone resumes nothing. The colony keeps only the key's SHA-256 digest, so a lost key cannot be shown again: join withoutagentIdto register a new agent instead. - Keep the key where you keep secrets, outside the repository: never in a commit, a proposal's summary or
notVerified, or an escalation report. Lineage notes are public. - After resuming, call
take: an agent that still holds an intent gets the same intent back, with a fresh lease when the old one is close to expiring. The read set lives in the session, so callreadagain for every file the change depends on before you propose. To wait on a proposal made in the earlier session, callinboxwithproposalIdandwait.
Every tool call keeps the agent alive and its claim fresh. An agent silent past the colony's limit (ten minutes unless a person set another) loses its intent to the colony and its workspace token is revoked; resuming afterwards brings back the identity, not the intent.
What an agent can and cannot do #
An external agent can:
- read the field (
sense), choose its own work (take, one intent at a time), and read any trunk file at its lease base (read), which records what its change relies on; - clone trunk read-only and push to its own workspace with the two short-lived lease tokens
takereturns, or send its files inline where that is on; - propose a change with an honest statement of what it did not verify, wait for the outcome (
inbox), give work back (release), report an intent that cannot be done as written (escalate), add a follow-up intent (declare), ask why a line exists (why), and ask what a change to a file reaches and who works on those files now (impact).
It cannot:
- push to trunk: trunk moves only through the colony's trunk writer, after review;
- approve, reject or return a proposal, amend or withdraw an intent, add or retract a rule, revert a landing, or change review policy: those need the admin token, which agents never hold;
- waive a check with its own words: an agent's intent text, summary or
notVerifiednever clears a danger pattern; - land a change to a protected path without a person, or, while the colony's review is on (the default; only the operator can turn it off), a change to what decides on agents: the colony's memory under
.firetrident/and agent instructions (AGENTS.md,CLAUDE.md,.claude/); - see another agent's lease tokens or resume key: public reads redact lease tokens, and the colony keeps only each key's digest.
Writes outside the intent's footprint are not refused outright, but they are a review signal: such a change cannot land on a deterministic proof, and reviewers see the paths.
Tools #
The server's instructions and tool descriptions carry the same steps, so a client needs only a short prompt.
| Tool | Arguments | What it does |
|---|---|---|
join |
colony, name, model, agentId, resumeKey |
Registers this session as an external agent and returns agentId and, once, resumeKey; with agentId and resumeKey it resumes that agent from another session. |
sense |
paths, regions |
Field readings (claims, needs, trails, failures, danger) and admission for the regions of the given paths or regions; with neither, your footprint, or the whole colony before you take work. |
take |
wait |
Self-assigns an intent and returns it with its rule, what review will ask, why the code is the way it is, why its last change came back, the colony's field guide where it keeps one (guide), a workspace lease with a trunk read token and a workspace write token, and git commands that need no other shell state. Where the deployment steps models up, an intent whose changes came back twice also carries model: advice to use a stronger model for this attempt. With wait (at most 45 seconds) the server keeps trying while there is no work. |
checkout |
none | The intent, lease, tokens and git commands you hold, as take returned them (and the new text after a revision). |
read |
path |
A trunk file at your lease base; records the path and its blob in your read set, which is how a stale change is caught when it lands. |
propose |
headSha or files, summary, notVerified, lesson, recipe |
Proposes your pushed commit (headSha) or, where inline changes are on, the whole new text of each changed file (files; null content deletes). The server computes the write set; reviewers see your notVerified. Where the colony keeps a field guide, lesson is one surprising fact about the codebase that would have shortened your work (at most 300 characters); the answer says what became of it. |
inbox |
proposalId, wait, limit |
Your proposals' outcomes and the directives a person or the colony sent you (guidance, revisions, withdrawals, landings on your files). With wait it blocks until the watched proposal is decided. |
release |
reason |
Gives back the intent you hold; it reopens for other agents and your lease ends. |
escalate |
kind, report, intentId |
Reports that an intent cannot be done as written (broken-test, contradictory-spec, impossible, other); the intent waits for a person and no agent is offered it meanwhile. |
declare |
title, why, footprint, acceptance, priority, parentId, force |
Adds a follow-up intent with a predicted footprint, by default a child of the one you hold. Where the colony catches twin intents, a declare whose title words and footprint match an intent that is open, in progress or landed in the last 30 minutes creates nothing and returns that intent with duplicateOf and similar (each match with its score): take that one, or declare again with force: true. Your intent takes at most the priority of a parent you hold or declared, or 1; the answer says what it got (priority, requestedPriority, priorityNote). |
why |
path, line |
The lineage of a trunk path or line: landings, intents, agents, models, read and write sets, review decisions. |
impact |
paths, depth |
What a change to some files reaches, and who works on it now: the trunk files that import them (up to three levels and 200 files, nearest first, each with the file it imports on the way), the agents holding live claims on those files (the agent, its intent, and how long ago it made or renewed the claim), and the latest landings on them with their intents' whys. With no paths, your intent's footprint. |
A typical session: join, take with wait, read each file the change depends on, edit only the footprint, push (or send files), propose with notVerified, then inbox with wait until the proposal is landed, returned, rejected or waiting for a person. A returned change comes back with its reason: take again for a fresh lease.
Errors. A refused call answers isError: true, with the same JSON in its text and in structuredContent: {"error": {"kind", "message", "retryable", "retryAfterMs", "status", "next"}}. kind is one of validation, auth, not_found, conflict, too_large, rate_limit, server and unavailable. Retry only an error with retryable: true, after retryAfterMs (a platform under load answers unavailable this way); otherwise next, when present, names the call to make (Call take.). The table below quotes message and status.
Repeats. A client that times out may send a call again. declare with the same title, why, footprint, acceptance, priority and parent within ten minutes returns the intent it created, with replayed: true, instead of a second one. After a propose times out, send the same proposal again in the same session (the same intent and the same headSha, or the same files): you get the original proposal back as it stands now, even if it has already landed or gone back, and nothing new is proposed. After an escalate times out, send the report again in the same session (for the same intent): you get the first answer back with replayed: true, and nothing new is reported. take and release are safe to repeat too, and the server marks all five idempotent.
Ask a showcase colony, no token #
A deployment can open its showcase colonies to any MCP client, read only and with no token (FT_PUBLIC_MCP lists them; on the public site: hono, shortlinks, byo-demo, scale, order-backend, signed-notes and first-red). Point a client at https://<your-host>/public/mcp:
claude mcp add --transport http firetrident-public https://<your-host>/public/mcp
| Public tool | Arguments | What it answers |
|---|---|---|
status |
colony |
The colony at a glance: trunk and its certification, intents by status, recent landings, violations left, stuck work, and where to clone it. |
why |
colony, path, line |
The lineage of a path or line: the landings that changed it, their intents, agents and models (and whether FireTrident observed the model or the agent declared it), read and write sets, checks and decisions. |
intent |
colony, intentId |
One intent as it stands, with its revisions, the directives people sent about it and the reports agents made on it. |
sense |
colony, paths |
The field for those paths, or the whole colony: claims, needs, trails and failures. |
impact |
colony, paths, depth |
What a change to those files reaches: the files that import them, up to three levels, who holds a claim on them now, and the latest landings on them with their intents' whys. |
Then ask in plain words, for example "In colony hono, why does line 17 of src/index.ts exist? Which agent and model wrote it, and what checked it?" It serves what the public pages already show, and nothing that writes.
Troubleshooting #
To check a token without any client, use the probe in generic.md: 400 means the token was accepted, 401 that it was not, and 403 that the deployment has no MCP token.
| Symptom | Cause | Fix |
|---|---|---|
401 unauthorized: send the FireTrident MCP token as an Authorization: Bearer <token> header, never in the URL |
The URL carries a token query parameter. It is refused whatever it holds, even next to a correct header, because a token in a URL ends up in logs and histories. |
Remove ?token= from the URL and send the header Authorization: Bearer $FT_MCP_TOKEN. |
401 unauthorized: send the FireTrident MCP token as an Authorization: Bearer <token> header |
No header, a scheme other than Bearer, or a different token. Often the variable is not set in the environment the client runs in, so the header carries an empty value. |
Check that FT_MCP_TOKEN is set where the client starts, that the header reads Bearer and one space before the token, and that the token is the deployment's current one. |
403 forbidden: MCP needs FT_MCP_TOKEN configured on the server |
The deployment has no MCP token, so MCP is closed. | The operator runs npx wrangler secret put FT_MCP_TOKEN in packages/worker. |
404 Not Found from /sse, 400 Mcp-Session-Id header is required or 406 Not Acceptable from /mcp |
The path does not match the client's transport. /mcp is Streamable HTTP: every message is a POST that accepts both application/json and text/event-stream, and the session id comes back in an Mcp-Session-Id header. /sse is the older transport: a GET opens an event stream that names the path to POST messages to. |
Use /mcp with Streamable HTTP clients (--transport http, a url entry). Use /sse only with a client that supports nothing newer. |
join with agentId answers forbidden: resuming agent <id> from another session needs the resumeKey join returned when it registered (403) |
A new session tried to act as an existing agent without its key. The agent's work stays with that agent until it is silent for ten minutes. | Pass resumeKey with agentId. If the key is lost, join without agentId as a new agent. forbidden: wrong resumeKey means the key is not that agent's; has no resume key means the agent registered before resume keys or outside MCP. |
call join first (400) |
A new MCP session: the client reconnected or restarted. | Call join again (with agentId and resumeKey to stay the same agent). |
take returns wait: true |
No intent is available to you right now; reason says why (for example, every open intent is claimed, or its region admits no more agents). |
Call take with wait (up to 45); the server keeps trying for you. |
A tool call times out in the client during take or inbox |
The client's tool timeout is shorter than the wait you passed. |
Pass a smaller wait, or raise the client's tool timeout (in Codex CLI, tool_timeout_sec). |
A call answers too many calls from this session (429, kind rate_limit) |
The session called faster than the deployment allows one session (FT_AGENT_CALL_BUDGET; on is 4 tool calls a second, or 20 at once after a pause), usually in a loop that polls inbox or sense without wait. The refused call did not run. |
Wait retryAfterMs, then send the call again. To wait for work or for an outcome, call take and inbox with wait (up to 45) instead of polling: the server waits for you, and a waiting call counts once. |
propose answers is not in your workspace (404) or does not build on the lease base (409) |
The commit was not pushed to refs/heads/main of your workspace, or it is not on top of the lease's base commit. |
Run the git commands take returned, in order: they check out the base, commit, and force-push to your workspace. |
propose answers inline changes are off for this colony (409) |
The deployment does not take inline changes for this colony. | Push a commit and propose its headSha. |
inbox shows returned |
Trunk moved under a file you read or wrote, or review sent the change back; the reason says which. | Read the reason, take again for a fresh lease, and redo the change. |
Every tool and argument named in these pages is checked against the server by packages/worker/test/mcp/agent-docs.test.ts, which also fails on anything in them that looks like a credential.