droid-mcp
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@droid-mcp start a droid session to fix the failing tests"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
droid-mcp
An MCP server that lets any MCP client (Droid, Claude Code, Cursor, other agents) find, start, steer and read Factory Droid sessions, including mission/workstream orchestrators and their agents. It reads local session files, runs its own sessions through @factory/droid-sdk, and reaches sessions held elsewhere through the Factory REST API when an API key is available.
Status: early (0.1.0). Tested on macOS with droid CLI 1.10.0, @factory/droid-sdk 0.11.0 and Node 26 (engines requires Node >= 24). The only platform-specific code is the Keychain lookup for FACTORY_API_KEY, which runs only on darwin; elsewhere the key must come from the environment. Linux is untested.
How the tools fit together
Discover Start or steer Follow and approve Read or stop
droid_find ─┐ droid_start ─┐ droid_wait ── pending prompt? ──► droid_respond
droid_models ─┴──► droid_send ─┴─► turn ─► droid_wait ── state "idle" ─────► droid_result
runs in droid_cancel
backgroundTerms used below:
Workstream (mission): a multi-agent Factory session: one orchestrator plus child agent sessions, tagged
mission-v2.Own session: a session currently running inside this server, either started with
droid_startor resumed locally bydroid_send. It stays own until it is closed.Autonomy: what the agent in a session may do without asking:
off<low<medium<high.Pending prompt: a permission request or AskUser question waiting for
droid_respond(listed inattention).
Related MCP server: Open Science MCP
Access at a glance
Three backends, and the credential each needs:
Backend | What it is | Credential |
Local files |
| None |
| A droid session the server spawns through |
|
Factory REST API |
|
|
Helpers: git diff HEAD (diffs), rg (transcript search), droid exec --help (model list).
What each tool can do:
Tool | Reads | Can change | Backend | Credential | Cost/side effects |
| Session headers and settings; transcripts when | Nothing | Local files ( | None; | No model tokens |
| Built-in models, aliases and wildcards; | Nothing |
| None | No model tokens |
| Model list, | Creates a session; the agent edits files in |
|
| Spends model tokens; writes |
| Target session settings, model list, | Sends a prompt as the user; changes model, tools and autonomy; may resume a local session | Own child process, Factory REST API, or a new | Own session's existing connection; | Spends model tokens; writes |
| New events, local transcript messages (user and assistant text only), or REST messages and pending prompts | Nothing | In-memory event log, local transcript, Factory REST API | None for local; | No model tokens; polls every 1 s (local file) or 2 s (REST) |
| Pending prompts | Answers or cancels a permission prompt or AskUser question | Own child process or Factory REST API | None for own sessions; | Lets a paused turn continue, which spends tokens |
| None | Interrupts a turn; closes an own session | Own child process or Factory REST API | None for own sessions; | Stops token spend |
| Latest result text; | Nothing | In-memory result, local transcript, Factory REST API, | None for local; | No model tokens |
"Read-only" means the tool is registered with readOnlyHint: true (src/tools.ts).
Which backend a call uses depends on the target session (src/route.ts, src/tools.ts):
Target |
|
|
|
|
Own session | Child process | In-memory events ( | Last result; | Child process; |
Local standalone session | With a key: REST. Without: local resume (below) | Local transcript ( | Local transcript; | REST (needs key); |
Local workstream session | REST (needs key); never resumed locally | With a key: REST ( | Local transcript; | REST (needs key); |
Not on this machine | REST (needs key) | REST (needs key) | REST text; no diff | REST (needs key); |
Local resume happens only without a key, only with allowLocalResume: true, and only for a standalone session that has readable settings, is not archived, is not a subagent, was not modified in the last 30 minutes, and has a recorded autonomy within the cap. The resumed session becomes an own session.
What the agent can and cannot touch
Filesystem
droid_start, local resume anddroid_resultdiffs require acwdinsideDROID_MCP_ROOTS(colon-separated, default~/repos). Both the cwd and each root go throughrealpath, so symlinks are resolved before the prefix check, and a missing cwd is refused (src/config.ts).DROID_MCP_ROOTSlimits where the server starts or diffs work. It is not a read sandbox:Operation
Limited to
DROID_MCP_ROOTS?droid_startcwd, local resume,droid_resultdiffYes
droid_startfork source (the source's own cwd)No
droid_find,droid_wait,droid_resulttext (any local transcript)No
REST sends,
droid_respond,droid_cancel(any session the key can reach)No
Files read:
~/.factory/sessions/*/*.jsonland their.settings.jsonfiles (src/catalog.ts),~/.factory/settings.json(src/models.ts), and~/.droid-mcp/state.json(src/runs.ts).The only file the server writes is
~/.droid-mcp/state.json, through a temporary file in the same directory and a rename. The directory is created with mode0700and the file with0600(src/runs.ts).Droid writes its own session files. The agent inside a session starts in its cwd, but autonomy, not the roots list, decides what its commands can touch.
Autonomy
Levels are
off,low,medium,high. A start withoutautonomyusesoff(src/config.ts).DROID_MCP_MAX_AUTONOMYsets the cap (defaulthigh). An unrecognized value sets the cap tooff. Requests above the cap are refused (src/config.ts).A local resume refuses a session whose recorded autonomy is above the cap, and checks again after loading it (
src/route.ts,src/runs.ts). A fork does not check the source's autonomy; the fork gets the requested autonomy (defaultoff).Over REST, the requested autonomy is capped, but the session's existing autonomy is not read or checked (
src/tools.ts).Permission prompt answers are filtered by
allowedDecisions:proceed_always*,proceed_edit,proceed_auto_runandproceed_new_sessionare never offered, andproceed_auto_run_<level>/proceed_new_session_<level>only when the level is within the cap. This applies to own sessions and REST answers (src/config.ts,src/runs.ts,src/tools.ts).No parameter maps to
--skip-permissions-unsafe. The only extradroid execargument the server adds is--worktree [branch](src/runs.ts).
Models and settings
droid_start and droid_send share these optional settings: model, reasoningEffort, specModel, specReasoningEffort, useSpec, mission, workerModel, workerReasoningEffort, validatorModel, validatorReasoningEffort, tags, worktree, onlyTools, addTools, removeTools, appendSystemPrompt, fork.
Validation, before anything runs (src/settings.ts, src/models.ts):
Every model id must be a built-in id, a custom id from
~/.factory/settings.json, a tier alias or a version wildcard. Unknown ids are refused with up to five close matches.An effort is checked against the model's supported list when that model is given (
specReasoningEffortagainstspecModel, falling back tomodel).Worker and validator settings require
mission: true;missioncannot be combined withuseSpec;missionrequires autonomyhigh(the requested autonomy, or the session's current one for own and local sessions).
Where each setting is applied:
Setting | New session | Fork | Own session send / local resume | REST send |
|
| Same |
|
|
|
| Same |
| Ignored (still validated, so |
| At session creation |
|
|
|
| At creation, plus | On the fork, plus | Added to the session's tags, keeping existing tag metadata | Ignored |
|
| Same | Ignored | Ignored |
| Appended to the | Refused | Ignored | Ignored |
| Loads the source locally and forks it. Only checks that the source is on disk and not open in this server; the local-resume guards above do not apply | n/a | Ignored | Ignored |
Ignored settings come back in the ignored field of the droid_send result; REST sends also return applied (src/tools.ts, src/settings.ts). useSpec: false and mission: false do nothing: neither switches a session back to normal mode.
Concurrency limits
At most 3 turns running or waiting on a prompt at once, and one per repo. The repo is the nearest ancestor of the cwd that contains
.git(src/runs.ts,src/catalog.ts).A pending permission or AskUser prompt is cancelled after 30 minutes (
src/runs.ts).mode: "interrupt"waits up to 15 s for the running turn to stop; if it does not, nothing is sent (src/runs.ts).Each own session keeps its last 1000 events;
droid_waitsetsgap: truewhen older events were dropped (src/runs.ts).
Idempotency keys
droid_start and droid_send require an idempotencyKey (1 to 200 characters). Keys are stored in ~/.droid-mcp/state.json for 7 days (src/runs.ts):
A retry with a finished key returns the first result without running again. For
droid_startthe session state in the reply is current.A key is marked pending before the action runs. If the action was refused before any side effect, the key is dropped and can be reused. If it failed after it may have taken effect, the key stays pending and a retry is refused.
Each save merges with the file on disk, so several server processes can share it.
HTTP mode
--http <host:port>requiresDROID_MCP_TOKEN. Every request must sendAuthorization: Bearer <token>, compared withtimingSafeEqual(src/main.ts).Any request with an
Originheader is rejected with 403, so browsers cannot call the server (src/main.ts).DROID_MCP_TOKENis deleted from the process environment at startup, so spawned sessions do not inherit it (src/main.ts).Each request gets a fresh, stateless MCP server; sessions and runs are shared across requests (
src/main.ts).
What it never does
Never returns
FACTORY_API_KEYor custom-model API keys. Custom models are reduced to an allowlist of fields, andbaseUrlis reduced to its host (src/models.ts).Never offers "always allow" permission options (
src/config.ts).Never closes a session that is not an own session;
droid_cancelwithscope: "session"is refused for those (src/tools.ts).Never resumes a workstream session, or one whose settings it cannot read, through
droid_send(src/route.ts).droid_startwithforkdoes load its source locally without these checks.Never runs commands other than
droid exec(through the SDK),droid exec --help,git -C <cwd> diff HEAD,rgand, on macOS,security find-generic-password(src/models.ts,src/tools.ts,src/catalog.ts,src/config.ts).Has no tool that deletes sessions or files (
src/tools.ts).
How it works
flowchart LR
client[MCP client] -->|stdio or HTTP| server[droid-mcp]
server -->|read-only| files[(Local session files<br/>~/.factory/sessions<br/>~/.factory/settings.json)]
server -->|"@factory/droid-sdk"| exec[droid exec child process]
exec -->|writes| files
server -->|FACTORY_API_KEY| rest[Factory REST API<br/>api.factory.ai]
rest --> daemons[Factory cloud and desktop daemons<br/>holding the session]
server --> helpers[git diff HEAD, rg,<br/>droid exec --help]Sessions this server starts live in memory. When the stdio client disconnects, or the process gets SIGINT or SIGTERM, the server closes all of them. After a restart they are ordinary local sessions.
Setup
Requirements: Node >= 24, the droid CLI (logged in, or FACTORY_API_KEY), git and rg on the PATH the MCP client gives the server. Many clients start servers with a minimal PATH; pass one explicitly if droid or rg is not found.
cd /path/to/droid-mcp
npm installDroid:
droid mcp add droid-mcp node /path/to/droid-mcp/src/main.ts --env PATH=/path/to/bin:/usr/bin:/binClaude Code:
claude mcp add droid-mcp -e PATH=/path/to/bin:/usr/bin:/bin -- node /path/to/droid-mcp/src/main.tsCursor (~/.cursor/mcp.json):
{
"mcpServers": {
"droid-mcp": {
"command": "node",
"args": ["/path/to/droid-mcp/src/main.ts"],
"env": { "PATH": "/path/to/bin:/usr/bin:/bin" }
}
}
}HTTP mode
DROID_MCP_TOKEN=<token> node src/main.ts --http 127.0.0.1:37999Use http://127.0.0.1:37999/mcp as the endpoint (the server answers on any path) with the header Authorization: Bearer <token>:
droid mcp add droid-mcp http://127.0.0.1:37999/mcp --type http --header "Authorization: Bearer <token>"
claude mcp add --transport http droid-mcp http://127.0.0.1:37999/mcp -H "Authorization: Bearer <token>"To reach it from other machines on a tailnet, put it behind tailscale serve:
tailscale serve --bg --https=8443 http://127.0.0.1:37999The endpoint is then https://<your-host>.<tailnet>.ts.net:8443/mcp. The bearer token is still required.
Configuration
Variable | Default | Effect |
| unset | Enables the REST backend, and is used for spawned sessions instead of the CLI login |
|
| Colon-separated directories a session cwd must resolve inside |
|
| Autonomy cap: |
| unset | Bearer token; required with |
On macOS, when FACTORY_API_KEY is not set, the server looks for it in the Keychain:
security add-generic-password -s droid-mcp -a FACTORY_API_KEY -w # prompts for the keyThe key is read once at startup; restart the server after changing it.
Tool reference
All results are JSON text, trimmed to about 8 KB; event text is clipped at 4000 characters with truncated: true.
Tool | Key parameters | Notes |
|
| Groups workstreams (orchestrator first, then agents) and standalone sessions by repo. |
|
| Built-in ids with supported efforts, custom models, tier aliases, version wildcards and the default model. |
|
| Returns |
|
| Returns |
|
| Without |
|
|
|
|
|
|
|
| Paged by byte offset; pass |
seq is a cursor whose meaning depends on the source: an event number for sessions this server runs, a byte offset into the transcript for other local sessions, and a message sequence number for REST sessions. Treat it as opaque and pass nextSeq back as afterSeq.
Development
npm run typecheck # tsc --noEmit
npm test # node --test "test/*.test.ts"
node scripts/smoke.ts [scratchDir] [idleSessionIdToResume]scripts/smoke.ts is a live end-to-end check over a real stdio MCP client. It spends model tokens. It starts the server with DROID_MCP_MAX_AUTONOMY=medium and FACTORY_API_KEY removed from its environment, then:
checks the tool list,
droid_findgrouping, title and content search, and read-onlydroid_wait/droid_resulton a local orchestrator;checks refusals: sending to a workstream without a key, an unknown session, a cwd outside the roots, autonomy above the cap;
starts a session in the scratch dir (default
~/repos/droid-mcp-scratch, which must be insideDROID_MCP_ROOTS) at autonomyoff, approves its permission prompt, and checks the created file, the result text, idempotent start and send, the diff and closing;with a second argument, resumes that idle local session with
allowLocalResume.
It needs at least one local workstream session with its orchestrator, and a transcript containing the text droid_find. It refuses to run when a key is in the Keychain: the refusal checks send to a real workstream, and with a key those sends would go through REST and actually post.
Known limits
REST settings updates (
updateSession) are not verified against the live API.Sessions started with
worktreerun in a worktree outside the allowed roots, sodroid_resultrefuses their diffs.A fork keeps its source session loaded until the fork is closed.
Mission mode is untested live.
REST
droid_waitassumesgetMessagesreturns the newest messages (see the comment insrc/tools.ts).Sessions on other machines are reachable by REST, but
droid_findlists only local session files (plus the REST child summary for local workstreams).droid_waiton a local transcript cannot see pending prompts or tool events.After an own session is closed,
droid_waitanddroid_resultkeep reading its in-memory state. If a laterdroid_sendreaches it over REST, they do not see the new turn.Linux is untested.
Security notes
Treat
DROID_MCP_TOKENlike a password: it grants every tool, including starting sessions.Autonomy
highlets droid run arbitrary commands, starting from a cwd inside the allowed roots but not confined to it. LowerDROID_MCP_MAX_AUTONOMYif clients should not be able to grant it.REST sends act as the owner of
FACTORY_API_KEY, on any session that key can reach.Prompts are sent as the user; the session sees them as the user's own messages.
When a key is found, the SDK passes it to spawned
droid execprocesses asFACTORY_API_KEY, so commands an agent runs in those sessions can read it.
License
MIT. See LICENSE.
Available Tools
8 toolsdroid_cancelA
Cancel a session's running turn (scope 'turn'), or close a session this server started (scope 'session').
Inputs: scope (required), session (required).
droid_findBRead-only
Find the user's Factory Droid sessions on this machine, grouped into workstreams (orchestrator + agents) and standalone sessions by repo. Paged with cursor.
Inputs: cwd, limit, query, scope, +4 more.
droid_modelsARead-only
List the models droid can run: built-in ids with their reasoning efforts, BYOK custom models from settings, tier aliases and version wildcards. filter matches id, name or provider.
Inputs: filter, include.
droid_respondB
Answer a pending permission prompt (decision = one of the offered option values, optional comment) or AskUser question set (answers, one per question), or cancel it.
Inputs: session (required), requestId (required), cancel, answers, +2 more.
droid_resultBRead-only
Read a session's latest result text and/or the git diff in its cwd, paged by byte offset (use nextOffset to continue).
Inputs: session (required), offset, include, maxBytes.
droid_sendA
Send a message into a session as the user. Sessions this server started take it directly; others need FACTORY_API_KEY (REST) or, for idle standalone local sessions only, allowLocalResume. mode 'interrupt' stops the running turn first. Settings and autonomy change the session before this prompt; settings a route cannot apply come back in ignored. Pick a model with droid_models: a built-in id (claude-opus-5-5), a BYOK custom id from ~/.factory/settings.json (custom::), a tier alias (anthropic-latest-premium) or a version wildcard (claude-opus-*). reasoningEffort must be one the model supports. mission needs autonomy 'high'. Unknown models and invalid combinations are refused before anything runs.
Inputs: prompt (required), session (required), idempotencyKey (required), fork, +19 more.
droid_startA
Start a new Droid session in cwd (must be inside the allowed roots) and send it the first prompt. Retrying with the same idempotencyKey returns the same session. Pick a model with droid_models: a built-in id (claude-opus-5-5), a BYOK custom id from ~/.factory/settings.json (custom::), a tier alias (anthropic-latest-premium) or a version wildcard (claude-opus-*). reasoningEffort must be one the model supports. mission needs autonomy 'high'. Unknown models and invalid combinations are refused before anything runs.
Inputs: cwd (required), prompt (required), idempotencyKey (required), fork, +17 more.
droid_waitARead-only
Follow a session: returns new events after afterSeq, waiting up to waitMs (max 50000) for something to happen. Without afterSeq, returns the latest events at once. Use nextSeq as the next afterSeq.
Inputs: session (required), waitMs, afterSeq, maxEvents.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 8 tools
Each tool maps to a distinct phase of the session lifecycle (find, start, send, wait, respond, result, models, cancel). The only mild overlaps are droid_send vs droid_respond (both inject input) and droid_wait vs droid_result (both surface output), but descriptions make the boundary clear enough.
All names share the droid_ prefix and a short suffix, which is predictable and readable. Minor deviation: most suffixes are verbs (find, send, wait, start, cancel) but result and models are nouns.
Eight tools is a tight, well-scoped set for agent-session orchestration, with each tool earning its place. No redundant or filler tools.
The surface covers discovery, startup, prompting, streaming, permission handling, result/diff reading, model discovery, and teardown via droid_cancel scope 'session'. Minor gaps: no dedicated tool to inspect or change session settings/autonomy outside of droid_send, and no way to apply or export the diff produced by droid_result.
Maintenance
Related MCP Connectors
Hosted MCP memory and agent control plane for durable conversations, jobs, and operations.
Start and manage Reason cloud coding agents, projects and sessions from any MCP client.
Public MCP for agent verification, work discovery and governed interoperability.
- aXDeprecatedio.github.ax-platform
Agent-native collaboration network: orchestrate a team of long-running agents from any MCP client.
Related MCP Servers
- AlicenseNot gradedqualityBmaintenanceEnables agent clients to safely connect to tools and execution resources through MCP with authorization, approvals, audit, chat-context isolation, SSH/Docker access, and long-running command session tracking.MIT
- AlicenseAqualityCmaintenanceEnables MCP clients to control the local Open Science research application, including creating projects, starting or continuing research sessions, inspecting progress and plans, canceling runs, and reading generated artifacts.12Apache 2.0
- AlicenseAqualityCmaintenanceEnables lifecycle management of Devin ACP sessions as subagents over MCP, supporting parallel dispatch, blocking coordination, mid-flight steering, permission adjudication, and crash-resilient session persistence.127 npm1MIT
- AlicenseNot gradedqualityCmaintenanceEnables MCP-compatible clients to inspect local OpenClaw agents and sessions, and optionally request controlled work from a preconfigured agent.MIT