@zvndev/circular-mcp
v0.2.1
Published
Dependency-free stdio MCP server for the Circular Agent API — plan work into Circular as tracked tasks and subtasks.
Downloads
754
Maintainers
Readme
circular-mcp — Circular over MCP
A dependency-free stdio MCP server that exposes the Circular Agent API as tools any MCP client can call. Point Claude Code, the Codex CLI/app, Cursor, or any stdio MCP client at it and your agent can list issues, create tasks and subtasks, pull run context, and — the headline — turn a plan into a tracked task tree in one call.
Same philosophy as the circular CLI: plain Node ≥ 20, no npm dependencies. It
shares the CLI's HTTP client, so the two surfaces never drift.
Tools
All tools are prefixed circular_:
| Tool | What it does |
|---|---|
| circular_get_next_work | Pull work. Up to limit candidate issues (highest priority first), each with its process: the team's playbook, the step ladder with completion state, and the definition of done. Excludes done, cancelled, and blocked issues. Does not claim. |
| circular_complete_step | Tick one ACTION step complete with proof. REVIEW steps are refused with 403 by design. |
| circular_list_issues | List issues; filter by status, assigneeId, or parentId ("none" = roots only). |
| circular_get_issue | Full detail for one issue (description, comments, activity, parent, subtasks). |
| circular_create_issue | Create one issue; parentId makes it a subtask; ownerParticipantId can assign an agent owner. |
| circular_update_issue | Update status / priority / title / assignee / agent owner. |
| circular_comment_issue | Post a comment (proof of work, handoff notes). |
| circular_get_context | Pull the exact context a run would receive for a project/issue, now including the issue's process (playbook + steps). |
| circular_save_plan | Save a free-text plan doc that feeds future context. |
| circular_plan_tasks | Batch plan → tasks: one atomic call creates a plan doc + parent issues + subtasks, returns every id + identifier (ENG-N). Caps: ≤50 tasks, ≤20 subtasks each. |
| circular_list_projects | Discover the projects this team works in (for the projectId the tools above need). |
| circular_focus | The person's own Focus queue: every track, its name, and its items in order. |
| circular_focus_next | The head of every track: one candidate each, skipping finished work. |
| circular_focus_task | One task by id, <identifier> in any case, or bare number, with its queue position and what is behind it in its track. |
Your own Focus
Focus is the queue a person arranges for themselves: parallel tracks, each holding tasks in order. The three tools above read it with the login this server is running under, so a session can start on what that person actually queued instead of asking them to name a task.
A Focus belongs to a person, so the credential rule is strict. A browser
sign-in or a personal login of your own (circular login) may read that
person's own queue. A team API key is refused with 403, because a team key is
not a person and "me" would silently resolve to whoever minted it. Reading is
all that is on offer: arranging, reordering and marking stay in the app with a
real sign-in.
A bare number that more than one team uses comes back as a list of candidates rather than a guess. Ask which one.
next on a task is the first unfinished task behind it in the same track, and
never that track's head. A task with nothing behind it has next: null, which
means the track is clear, not that it starts again from the top.
circular_focus_task answers a reference whether or not it is queued: a task
outside the queue is found by one bounded read of the team the reference names,
and comes back with focus: null because it has no queue position. Either way
the task itself is fetched, so the description is whole and the process steps
are there to record proof on.
Local Desktop mode
Use circular-mcp --local when an agent is running beside Circular Desktop and
only needs read-only context from that running app. Local mode exposes a
separate tool set (circular_local_context, circular_local_sessions,
circular_local_focus, circular_local_next, circular_local_task and
circular_local_focus_on) and does not load managed browser logins,
CIRCULAR_API_KEY, workspace/team ids, or cloud HTTP configuration.
circular_local_task and circular_local_focus_on take an internal id, an
<identifier> in any case, or a bare number. circular_local_focus_on answers
with the task, its place in the queue, and the task to offer once it is
finished, so a skill has one call rather than three. Desktop is worth
preferring when it is running: it is the only source that knows which tasks
other live sessions are already holding.
Desktop advertises the socket through CIRCULAR_LOCAL_ENDPOINT plus
CIRCULAR_LOCAL_TOKEN, which must be set together, or through
<desktop-data-dir>/local-context.json with
{ "version": 1, "endpoint": "...", "token": "...", "pid": 123 }.
CIRCULAR_LOCAL_THREAD_ID is passed through when present.
{
"mcpServers": {
"circular-local": {
"command": "npx",
"args": ["--yes", "@zvndev/circular-mcp", "--local"]
}
}
}The planning loop: circular_get_context → plan → circular_plan_tasks →
hand each returned ENG-N to a sub-agent → circular_update_issue /
circular_comment_issue to track progress and post proof.
The execution loop: circular_get_next_work → pick a candidate and reserve it
with circular_claim_issue_work → move ordinary product status with
circular_update_issue status=in_progress → work through the playbook →
circular_complete_step with proof per step → release the reservation with
circular_release_issue_work_claim → circular_update_issue status=done.
Read situation.disposition before acting
Every candidate comes back with a situation saying where you stand on it,
so an agent never has to probe the API and read refusals to find out:
| disposition | What to do |
|---|---|
| ready_for_you | The open step is an ACTION step you may complete. Proceed |
| waiting_on_human_review | Parked on a REVIEW step. No API key can tick it. Skip |
| waiting_on_automation | An AUTOMATION step, ticked by its own signal. Skip |
| assigned_to_someone_else | The open step names another person, team, or agent. Skip |
| process_complete | Every step ticked; it only needs closing |
| no_process | No ladder. Do the work, comment, set it to done |
| ladder_unreadable | Steps cannot be parsed. Do the work only if it is otherwise clear, then comment |
situation.currentStep carries the open step's id, kind, assignment,
assignedTo, canComplete, and a refusal reason when you may not complete it.
The response's actor block tells you who Circular thinks you are, including
canCompleteReviewSteps, which is always false for an API key.
Full reference, including every refusal and the stricter per-step sign-off route:
docs/AGENT_INTEGRATION.md.
Agent ownership
Human assignment and agent ownership are separate fields. assigneeId names the
human assignee; ownerParticipantId names the agent participant that owns the
work. Creating or updating with ownerParticipantId uses the same API field as
the web app, so existing workspace/team permission checks still apply. On
circular_update_issue, omit ownerParticipantId to preserve the current agent
owner, or send ownerParticipantId: null to deliberately clear it. Clearing the
agent owner does not remove human collaborators.
Processes: how a team's business logic reaches your agent
A process is a reusable definition of how one kind of work gets done: an
ordered set of steps plus a markdown playbook, mapped to issues by label (or a
workspace default). circular_get_next_work and circular_get_context hand it
to you with the issue, so you never have to guess a team's conventions.
Steps come in three kinds:
- ACTION: you do it, then tick it with
circular_complete_stepand real evidence inproof(test output, a diff summary, a link). - REVIEW: a human gate.
circular_complete_stepanswers 403 for these, always: an agent may never sign off its own review. Enrolled runner connections cannot mark done with open stored REVIEW or AUTOMATION steps, even when paused. Legacy callers retain the configured legacy process-gate policy (currently off); an unticked review is never an agent approval. Post proof/commentary. - AUTOMATION: ticked by its own CI/GitHub signal, not by hand.
circular_get_next_work deliberately does not reserve the issue it returns.
Reservations live in circular_claim_issue_work, keyed by a stable request id
for safe retry after a lost response. Several candidates come back so two agents
pulling at the same moment can pick another candidate if a reservation loses.
MCP tools versus an always-on computer
Adding this stdio connector makes tools available to an agent. It does not start a provider, register a worker, or install a background service. Circular's cloud durably queues assignments, mentions and workflow events. An explicitly enrolled foreground local runner polls its addressed deliveries and launches the installed Codex or Claude CLI on that computer, one job at a time. Its connection and provider subscription stay private to that computer.
Use the public connection docs after
browser login (or importing an admin-created workspace-agent connection), linking
the project repository, and connecting the local checkout. Keep circular runner
start --connection CONNECTION_ID running; an OS service is a separate explicit
operator setup, never an automatic side effect of installing MCP or Desktop.
runner register --allow-circular-tools explicitly permits unattended use of
the runner's generated Circular MCP tools. --allow-write separately permits
local code edits within the grant/profile ceiling. Neither flag grants access
to unrelated MCP servers or lets an agent approve human-only steps. A runner
works its already leased delivery rather than pulling unrelated next-work
candidates. Registration requires the explicit Circular tool opt-in to avoid
provider approval prompts stranding headless work. Use an interactive agent if
you do not want unattended tool calls.
Authentication
Identical to the CLI. The normal path is managed browser sign-in:
npx -y @zvndev/circular-cli login --runtime codexThat command opens Circular, asks the signed-in human to approve a named agent,
then stores the managed connection locally in ~/.circular/connections.json.
No API key has to be copied into an agent prompt or MCP config.
For long-running MCP processes, pin the connection id returned by login:
CIRCULAR_CONNECTION_ID=key_or_connection_id_from_loginOwners/admins can still provide an explicit team API key (circ_tk_…) for
ongoing workspace agents or legacy automation. MCP clients inject these through
the server's env block:
CIRCULAR_API_KEY=circ_tk_xxx
CIRCULAR_WORKSPACE_ID=ws_xxx
CIRCULAR_TEAM_ID=team_xxx
CIRCULAR_BASE_URL=https://gocircular.dev # optional; default~/.circular/config.json also works as a fallback for explicit keys. An
explicit key always wins over managed login; a Desktop-bound agent identity does
not borrow a managed or global fallback credential.
Minting a team API key
In Circular, go to Team → Access → API keys and create a key. Optionally
bind it to an agent participant so every write (issues, comments, activity)
is attributed to that agent. Copy the circ_tk_… value — it is shown once.
Registration
The published package exposes the circular-mcp binary through npx. For
local source development, replace the npx -y @zvndev/circular-mcp command
below with node /ABS/PATH/mcp/bin/circular-mcp.mjs.
Claude Code
claude mcp add circular \
-e CIRCULAR_CONNECTION_ID=key_or_connection_id_from_login \
-- npx -y @zvndev/circular-mcpThen, in a session, the tools are available as mcp__circular__*, e.g.:
claude -p "use the circular MCP tools to list issues" --allowedTools "mcp__circular__*"Codex CLI / app
Via the CLI:
codex mcp add circular \
--env CIRCULAR_CONNECTION_ID=key_or_connection_id_from_login \
-- npx -y @zvndev/circular-mcpOr add the block directly to ~/.codex/config.toml:
[mcp_servers.circular]
command = "npx"
args = ["-y", "@zvndev/circular-mcp"]
env = { CIRCULAR_API_KEY = "circ_tk_xxx", CIRCULAR_WORKSPACE_ID = "ws_xxx", CIRCULAR_TEAM_ID = "team_xxx" }For a managed connection, use:
env = { CIRCULAR_CONNECTION_ID = "key_or_connection_id_from_login" }Verify with codex mcp list / codex mcp get circular.
Cursor
Add to ~/.cursor/mcp.json (global) or .cursor/mcp.json (per-project):
{
"mcpServers": {
"circular": {
"command": "npx",
"args": ["-y", "@zvndev/circular-mcp"],
"env": {
"CIRCULAR_API_KEY": "circ_tk_xxx",
"CIRCULAR_WORKSPACE_ID": "ws_xxx",
"CIRCULAR_TEAM_ID": "team_xxx"
}
}
}
}Generic stdio MCP client
Any client that speaks the MCP stdio transport uses the same shape:
{
"command": "npx",
"args": ["-y", "@zvndev/circular-mcp"],
"env": {
"CIRCULAR_API_KEY": "circ_tk_xxx",
"CIRCULAR_WORKSPACE_ID": "ws_xxx",
"CIRCULAR_TEAM_ID": "team_xxx"
}
}Agent skill
Registering the server gives an agent the tools; the circular-planning
skill (skills/circular-planning/) gives it the discipline — when to pull
context, how to materialize a plan with circular_plan_tasks, and how to
delegate one ENG-N per sub-agent with proof-of-work. Install it with
npm run install-agent-skill (repo root). See skills/circular-planning/README.md.
Protocol notes
- Transport: MCP stdio — newline-delimited JSON-RPC 2.0 (one message per line on stdin/stdout; logs go to stderr).
- Implements
initialize,notifications/initialized,tools/list,tools/call, andping. Protocol version negotiated atinitialize(defaults to2025-06-18). - Tool execution errors (e.g. an API 4xx) are returned in-band as a
tools/callresult withisError: true, not as a JSON-RPC protocol error, so the agent can read and react to them.
Development
npm test # node --test — exercises the JSON-RPC dispatcher + tool registry
# Drive a live session by hand:
echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18"}}
{"jsonrpc":"2.0","id":2,"method":"tools/list"}' | CIRCULAR_API_KEY=circ_tk_xxx \
CIRCULAR_WORKSPACE_ID=ws_xxx CIRCULAR_TEAM_ID=team_xxx node bin/circular-mcp.mjs