@composer-app/mcp
v0.0.4-beta.1
Published
Composer MCP
Maintainers
Readme
@composer-app/mcp
MCP server for Composer — a realtime collaborative markdown editor with first-class agent support.
The MCP server runs locally under an MCP-capable coding CLI (Claude Code today; Codex / OpenCode adapters are follow-up work) and connects to a Composer room as a standard Yjs peer. It exposes a small, typed tool surface the model uses to read the doc, react to mentions, post comments, and suggest edits — all through the same CRDT path humans use.
Install / setup
npx @composer-app/mcp@latest setupWrites ~/.composer/user.json (persistent machine identity +
self-chosen display name), registers composer-mcp with your coding
CLI, and installs the host-side SKILL prompt at
~/.claude/skills/composer/SKILL.md.
Restart your CLI once setup completes.
Tools
Read
composer_attach_room— attach to an existing room by ID and return a snapshot (full doc markdown, outline, version).composer_get_full_doc— fetch the current doc as markdown.composer_get_section— fetch a single section byheadingId.composer_get_thread— fetch a thread's body + every reply + anchored text + containing section markdown (for catch-up on conversations the model was tagged into mid-flight).composer_next_event— block for up totimeoutSec(default 600 s) waiting for a remote event. Returnsmentionortimeout.
The mention event carries the triggering text, the anchored doc
range, the containing section as markdown, and (new in 0.0.1-beta.4)
invokerUserId + invokerName — so the model can @mention the
invoker back without a client-side lookup. The reason field is one
of: "direct_mention", "active_thread", or "solo_room".
Write
composer_add_comment— comment anchored to text.composer_add_suggestion— propose a text replacement (lands as pending).composer_reply_comment/composer_reply_suggestion— reply on a thread.composer_resolve_thread— mark a thread resolved.composer_agent_status(new in 0.0.1-beta.4) — advance an in-flight ack's state, and atomically rewrite the ack's text on completion. See Progress status below.
Create / join rooms
composer_create_room— create a new doc, optionally seeded from a markdown file or inline string. Returns the browser URL so the invoking human can open it.composer_join_room— attach to an existing room by browser URL.
Agent reply states
Every agent-authored reply, comment, or suggestion can carry an
optional state field (new in 0.0.1-beta.4):
| State | Meaning | UI |
|---|---|---|
| thinking | Ack posted, no tool use yet | Violet status dot + name-row shimmer |
| working | Agent is using tools | Blue dot + indeterminate bar under row |
| replying | Final response is being assembled / streamed | Teal dot + blinking caret |
| ready | Terminal; record holds the final content (or a pointer to a standalone artifact) | Neutral grey dot (no indicator) |
The UI also derives a stalled view when the agent's awareness
presence drops while a non-ready state persists for > 60 s (4× the
MCP's 15 s heartbeat) — amber dot with a slower pulse and an inline
"Retry" affordance.
Legacy records and older browsers ignore the state field and render
the literal reply text — no crash, no broken layout.
Progress status
The model drives state transitions through the composer_agent_status
tool. The canonical lifecycle:
# Mention arrives
composer_next_event() → { kind: "mention", threadId, invokerUserId, invokerName, ... }
# 1. Acknowledge first
composer_reply_comment({
roomId, threadId,
text: "@<invokerName> — on it",
mentions: [invokerUserId],
state: "thinking",
}) // → { replyId }
# 2. Advance state as you work (optional but recommended for >2 s spans)
composer_agent_status({
roomId, threadId, replyId,
state: "working",
note: "reading section 3…",
})
# 3. Land the substantive answer
composer_add_suggestion({ roomId, fromThreadId: threadId, replacementText: "…" })
# 4. Atomically rewrite the ack to a pointer + mark ready
composer_agent_status({
roomId, threadId, replyId,
state: "ready",
text: "Posted a suggestion below.",
})Key invariants:
- Prune awareness before writing
readyto the CRDT. The handler enforces this internally; a mid-op crash leaves at worst an ephemeral anomaly, never a stuck persisted state masked by a stale heartbeat. - Self-heal on handler entry. Every call prunes awareness entries
whose target record is already
ready. Idempotent; converges on retries. - Silent intermediate transitions. Non-
readystate flips passsilent: trueto the activity-feed path, so the feed only sees the initial ack and the finalreadyrewrite.
See skill/SKILL.md — shipped with the package and
installed into your CLI's skill directory by setup — for the full
host-side behavior contract (when to ack vs skip, when suggestion IS
the reply, ask-then-auto-suggest reconciliation, multi-agent etiquette).
Environment
The MCP reads two env vars (set by setup when registering with your
CLI):
COMPOSER_SERVER_HOST— the Composer server host (e.g.,usecomposer.apporlocalhost:5173for local dev).COMPOSER_APP_BASE— the base URL of the Composer web app (e.g.,https://usecomposer.apporhttp://localhost:5173).
Identity is stored at ~/.composer/user.json with mode 0600.
Delete the file to reset; setup will prompt for a new name on next run.
Development
cd mcp
npm run typecheck # tsc --noEmit -p .
npm run build # tsup → dist/cli.js + dist/mcp.js
npm run test # vitest run (uses mcp/vitest.config.ts; 131 tests as of 0.0.1-beta.4)License
MIT
