Agor MCP Server

Anything a user can do in Agor, an agent can do too. The same daemon that serves the UI’s REST and WebSocket APIs also exposes a built-in Model Context Protocol (MCP) server, a structured automation surface that turns every session into a self-aware orchestrator.
This is the foundational layer: teammates, scheduled prompts, cards, artifacts, and the message gateway are all built on top of it.
The other doorway: if you’re driving Agor from outside an agent (TS/JS scripts, custom dashboards, internal services), reach for the official
@agor-live/clientinstead. Same daemon, same types, REST + Socket.IO + a reactive session API.
Key Ideas
- Agents as first-class users: Every MCP tool wraps a FeathersJS service. Sessions, branches, boards, zones, users, environments. Anything you can click or reach via the
agorCLI is available as JSON-RPC. - Self-aware sessions: Each session is auto-issued a scoped MCP token, so the agent already knows which session, branch, and board it’s in, and can act on that context.
- No separate server: MCP is part of the daemon. Nothing to install, nothing extra to run.
- Remote access: External agents and dashboards can connect over HTTP to drive Agor (create boards, branches, or even invite new users).
MCP is enabled by default and every session already has credentials injected. No setup required for in-Agor agents.
Capabilities At a Glance
| Domain | What agents can do |
|---|---|
| Sessions | Introspect the current session, list siblings, spawn child sessions with prompts, archive/complete work |
| Repositories | List repositories, clone remote repos, register local repos, manage repo metadata |
| Branches & Boards | List or fetch branches, create new ones, assign them to boards, update metadata, move cards, request policy-approved cleanup |
| Board Zones | Create zones on boards, update zone properties (position, size, color), manage zone triggers |
| Tasks & Reports | Fetch task history, cancel selected queued tasks, reorder pending work, generate reports |
| Users & Teams | Create users, update profiles or avatars, assign roles, invite collaborators |
| Context Resources | Enumerate context files, load concept docs, ingest repo metadata |
Because tools route through FeathersJS services, every action emits the same events the UI listens to. Real-time updates appear instantly for everyone watching the board.
Managing a Session’s pending queue
An orchestrator can revise a child’s pending work while its current Task keeps
running. Discover these tools in the sessions domain (search for queued):
agor_tasks_list({ sessionId, status: "queued" })returns pending Tasks in dispatch order, including fulltask_id,queue_position, and prompt text. FollownextOffsetwhilehasMoreto read the complete queue.agor_tasks_cancel_queued({ sessionId, taskIds })removes selected queued Tasks in one atomic batch. Every ID must still be queued in that Session; otherwise none are removed. This is removal, not a stopped/failed execution: no completion callbacks or reports are generated.agor_tasks_reorder_queued({ sessionId, expectedTaskIds, taskIds })changes dispatch order.expectedTaskIdsmust equal the complete current queue in its observed order;taskIdsmust be an exact permutation. No duplicate, missing, foreign-Session, or nonqueued IDs are accepted. Empty lists are valid only for an empty queue.
Always name the target Session explicitly; the caller’s current Session is not an implicit target. Task lists use full UUIDs returned by the list tool. Both mutations require workspace Member access and the existing Branch Manager capability used by Task deletion (normal administrator policy still applies). Prompt access alone is insufficient. Delegated Session credentials retain the acting user’s permissions; child ancestry grants no additional rights, and no operation crosses the authenticated tenant boundary.
For example, after listing a child’s queue as [A, B, C] (replace these letters
with the returned full Task UUIDs):
// New data invalidates B. Active work is untouched.
agor_tasks_cancel_queued({ sessionId: childId, taskIds: [B] });
// Using the resulting queue [A, C], prioritize C.
agor_tasks_reorder_queued({
sessionId: childId,
expectedTaskIds: [A, C],
taskIds: [C, A],
});Both return { session_id, queue: [{ task_id, queue_position }], cancelled_task_ids }, authoritative at commit, not a reservation. Admission,
dispatch, cancellation, or another reorder may change it immediately afterward.
A stale order or ineligible ID produces an actionable conflict (HTTP 409,
TASK_QUEUE_CONFLICT in the service error data). Reread the queue and reconsider
priorities before retrying; do not silently reuse the old snapshot. Malformed
or duplicate IDs are invalid input (HTTP 400).
Only queued Tasks can change. Dispatching, running, permission/input waits,
stopping, and finished Tasks are never stopped, edited, or deleted by these
tools. New prompts append after the reordered tail. Failure-held queues remain
held: these tools do not resume a Session. Existing realtime Task events update
the UI; no separate queue UI or pause control is needed.
The shared public Tasks service exposes the same operations as cancelQueued
and reorderQueued, with session_id, task_ids, and (for reorder)
expected_task_ids. Generic Task patches remain executor-only and
queue_position remains server-owned.
Interrupt obsolete work and supply urgent information
Use the existing prompt, queue-management, and stop tools in that order. This is not an atomic stop-and-clear operation, nor an in-place injection into the running Task. Each tool retains its own authorization requirements.
- Capture the original active Task ID from
agor_tasks_listfor the child before changing its queue. Keep this ID (originalTaskId) for the conditional Stop; do not replace it with a successor’s ID. While the child is still active, callagor_sessions_promptwithmode: "continue"and updated instructions. Inspect the result: continue queues behind active work, but if the child has already become promptable, the update may dispatch immediately. Do not assume it is still queued. - Inspect/re-read the pending queue with
agor_tasks_list({ sessionId: childId, status: "queued" }), following all pages. Cancel obsolete queued IDs withagor_tasks_cancel_queuedas appropriate. Use the resulting queue or re-read it after further changes. - Move the update to the front with
agor_tasks_reorder_queued:expectedTaskIdsis the exact observed order;taskIdscontains the same IDs with the update first. Handle any conflict by re-reading and reassessing, not by blindly retrying the old snapshot. - ONLY THEN call
agor_sessions_stop({ sessionId: childId, expectedTaskId: originalTaskId, reason: "New data invalidates the current task; prioritized updated instructions" }). Stop preserves and drains the queue. Stopping before rearranging it risks dispatching stale work.
For example, if the update returned Task ID U and a fresh queue read showed
[A, B, U], where B is obsolete (letters stand for full Task UUIDs):
// First read the child's active Task and retain its ID as originalTaskId.
agor_tasks_list({ sessionId: childId });
// Then, while the child is active:
agor_sessions_prompt({
sessionId: childId,
mode: 'continue',
prompt: 'New evidence invalidates the current approach. Preserve existing edits and ...',
});
// Confirm the returned update U is queued; read the complete current queue.
agor_tasks_list({ sessionId: childId, status: 'queued' });
// Only if that read shows [A, B, U], and B is still obsolete:
agor_tasks_cancel_queued({ sessionId: childId, taskIds: [B] });
// Only if the resulting/current queue is [A, U]:
agor_tasks_reorder_queued({
sessionId: childId,
expectedTaskIds: [A, U],
taskIds: [U, A],
});
// Only after successful rearrangement and reassessing the current active work:
agor_sessions_stop({
sessionId: childId,
expectedTaskId: originalTaskId,
reason: 'Obsolete approach; update U prioritized',
});After verified termination, the prioritized update can run as the next turn, provided it is still the queue head. Delivery is not guaranteed instantaneous. An accepted/pending stop is not confirmed termination; inspect the stop outcome and re-read Session/Task state rather than treating acceptance as proof that the old executor has stopped. Existing edits made by the running Task are preserved: stopping does not roll them back.
There is no atomic guarantee across these separate calls. Active work may
finish, another task may dispatch, or another caller may change the queue
between any steps—even between a successful reorder and stop. On unexpected
dispatch or queue changes, re-read and reconsider which work should be stopped
before proceeding. The optional expectedTaskId accepts a full UUID or short
ID and restricts Stop to the original execution. If the original completed and
U started, Stop returns condition_changed without terminating U. On that
mismatch, never fall back to an unconditional Stop or substitute U’s ID.
An already-idle Session remains an idempotent already_idle result. Queue tools
cannot cancel or reorder a Task that has already left queued.
Progressive tool discovery
When mcp_tool_search is enabled (the default), tools/list intentionally
returns only three stable facade tools:
agor_search_toolsbrowses domains or searches concise tool summaries.agor_get_tool_detailsreturns the exact schema for one selected tool.agor_execute_toolinvokes the selected domain tool with validated arguments.
The MCP 2026-07-28 revision adds server/discover for protocol capabilities
and standard cache hints for tool lists. It does not replace Agor’s semantic
catalog search, domain filters, annotation filters, or detail-on-demand flow.
Agor keeps those features while using the standard discovery response and
private 60-second cache hints for its deterministic visible catalog.
Self-Aware Agents Inside Agor
When you launch a session in Agor, the SDK config includes the internal MCP server automatically:
{
"type": "http",
"url": "http://localhost:3030/mcp",
"headers": {
"Authorization": "Bearer <mcp_token>"
}
}The daemon requires the token in the Authorization: Bearer header. Query-
string tokens are rejected. Don’t put secrets in URLs, where they’d leak
into access logs and browser history.
From there agents can:
- Discover their current session and branch context.
- Spawn subsessions to fan out work (e.g., spin up multiple coders to process a checklist).
- Inspect the board layout to decide where to place new branches or sessions.
- Update their own user profile to signal who is speaking.
This self-awareness is what lets agents behave like team members instead of detached copilots.
Automation Examples
Branch materialization is asynchronous by default. For the most discoverable one-call flow, an
orchestrator can pass waitForReady: true (and optionally waitTimeoutMs) to
agor_branches_create, then create a session only when _readiness.outcome is "ready".
For a retry-safe flow—particularly when a non-idempotent create request might be replayed—use:
agor_branches_createand retain the returned branch ID.agor_branches_wait_for_ready; repeat after a structured timeout when necessary.- Call
agor_sessions_createonly when_readiness.outcomeis"ready".
Both wait paths check immediately and then once per second. The default wait is 45 seconds and a single call is capped at 5 minutes. Values longer than the MCP client’s request deadline require a matching client or host timeout. After a timeout, keep the returned branch ID and safely call the read-only wait tool again. Losing a waited create response does not cancel the already-created branch, so do not blindly replay branch creation.
- Repository bootstrap: “Clone this GitHub repo, create a branch off
main, and start the dev environment automatically.” - Zone-based workflows: “Create a ‘Human PR Review’ zone on the board, positioned based on existing zones, with a trigger that prompts for review feedback.”
- Mass subsession fan-out: “Read
context/js-to-ts-refactor, split the file list, create a subsession per chunk, and report back when done.” - Bulk account provisioning: Paste a CSV of emails; the agent calls MCP to create users and invite them automatically.
- Issue triage pipeline: Source GitHub issues by label, triage them, and create branches linked to each issue, dropping them onto the active board as cards.
- Environment orchestration: Start/stop branch environments via MCP before delegating work to collaborators or other agents.
Because MCP calls are just JSON-RPC, complex workflows can be scripted once and reused by any tool that speaks the protocol.
agor_repos_import_environment exposes the repository editor’s admin-only
.agor.yml import. For its replacement semantics and the import/render/lifecycle
sequence, see the agent configuration workflow.
Environment MCP tools call the same branch environment service as the UI. If an
operator sets execution.managed_envs_execution_mode: webhook-only, MCP
agor_environment_start, agor_environment_stop, agor_environment_logs, and
agor_environment_nuke reject rendered shell commands and only invoke explicit
HTTP(S) webhook URLs. See Environments: webhook-only mode.
External Agent Access
External clients connect to the same HTTP MCP endpoint. Use the hosted URL
https://<your-agor-host>/mcp, or http://<daemon-host>:3030/mcp for a
default self-hosted daemon. In Agor, open User Settings → Personal API Keys,
create a key, copy it once, and store it in a password manager or your shell’s
secret-loading mechanism:
export AGOR_API_KEY='…'Agor authenticates personal keys with the X-API-Key header. Never paste a
real key into a command, committed config, issue, or chat transcript.
Claude Code
This user-scoped registration works for the hosted sandbox:
claude mcp add -s user -t http agor-sandbox https://agor.sandbox.preset.zone/mcp \
-H 'X-API-Key: ${AGOR_API_KEY}'The single quotes are important: your shell passes the placeholder rather than
the secret. Claude Code stores ${AGOR_API_KEY} in its user configuration and
expands it when it loads the MCP server, so the variable must also exist in the
environment that launches Claude Code.
Use -s local (the default) for private configuration in only the current
project, or -s project only when the shareable .mcp.json contains the
placeholder—not a secret. Check the registration with:
claude mcp get agor-sandboxCodex
Codex supports environment-backed HTTP headers in ~/.codex/config.toml:
[mcp_servers.agor]
url = "https://agor.sandbox.preset.zone/mcp"
env_http_headers = { "X-API-Key" = "AGOR_API_KEY" }env_http_headers maps the header to an environment-variable name; do not
use http_headers with the literal key. The global file is shared by Codex CLI,
the IDE extension, and the Codex app. For one trusted repository, put the same
table in .codex/config.toml instead. Restart the client after changing its
environment or configuration, then use /mcp or codex mcp list to inspect
the connection.
Optional session context
An external personal API key identifies you but does not imply an Agor session.
Most tools accept explicit IDs. To make current-session tools work, add
X-Agor-Session-Id: <session-id> as another header. The session must be visible
to your user. Do not copy Agor’s short-lived internal session JWT into an
external client; those tokens are injected automatically into sessions that
Agor launches.
That is the key distinction: inside an Agor session, the endpoint, scoped JWT, tenant, user, and session context are attached automatically. From an external client, you configure the endpoint and personal API key yourself, and session context is absent unless you add it.
Built-in transport contract
Agor’s built-in endpoint uses the stable TypeScript MCP SDK v2 in a dual-era, stateless request/response configuration:
- Modern
2026-07-28clients use the handshake-free per-request metadata contract. They may callserver/discover; ordinary RPC results are bounded JSON and include standard cache hints where required. - Initialization-era clients through
2025-11-25continue to useinitializeandnotifications/initialized. The compatibility path may return one bounded, request-scoped SSE response for a legacy request. It does not create or retain a transport session. - Neither era receives
Mcp-Session-Id. Agor does not retain a transport Map or timer, open a standalone server event stream, or send transport-level progress, logging, subscription, or tool-list-change notifications. - Authenticated
GET /mcpandDELETE /mcpreturn405 Method Not Allowed(requests still pass the normal authentication boundary first). Streamable HTTP clients may optimistically tryGET; compatible clients treat405as the server declining that optional stream. - Authentication, trusted tenant identity, current user data, and optional
X-Agor-Session-Idaccess are reconstructed and authorized on every request.
This contract applies only to Agor’s built-in endpoint. MCP servers that users configure under Settings → MCP Servers remain separate external services; Agor still passes their configured stdio, Streamable HTTP, or legacy SSE transports directly to the selected executor.
Version selection is protocol-driven rather than an Agor-specific client
switch. V2 clients can probe with server/discover and select the modern era;
older clients send initialize and are served by the stateless compatibility
arm. Both paths use the same authenticated server factory and tool definitions.
Smoke test and troubleshooting
Ask the client to call agor_search_tools with no arguments, then
agor_boards_list with { "limit": 1 }. A successful response should show
tool domains and one accessible board page.
For positioned branches/cards, use agor_boards_get with includeEntities: true.
Optional entityZoneId, entityType, entitiesLimit, and entitiesSkip filter
and page the authorized entities. Archived branches are excluded before counting
and paging unless includeArchived: true; card entities are preserved. Omitting
the limit retains the existing all-matched-entities behavior, so prefer an explicit
limit for large boards. entities_pagination.total counts the filtered visible
entities, not just the returned page.
The execution facade reports malformed target-tool arguments with
code: "invalid_tool_arguments", validation_stage: "tool_input", and bounded
field/code issues. Fetch agor_get_tool_details, correct the indicated fields,
and retry. A service_validation_failed response instead identifies downstream
service validation; it does not by itself mean the agent supplied a bad payload.
If the payload matches the published tool schema, report the tool/service mismatch
rather than retrying unchanged. These failures retain the error/tool fields
and MCP isError: true; diagnostic issues omit input values. Direct tool calls
retain the MCP SDK’s native input-validation errors.
- 401: confirm
AGOR_API_KEYexists in the environment that launched the client, the header is exactlyX-API-Key, and the key has not been revoked. Re-register if the client stored a literal secret or an unexpanded shell expression. - Proxy or TLS errors: verify the
/mcpURL is reachable from the client, configure its supportedHTTP_PROXY/HTTPS_PROXYsettings, and install your organization’s CA rather than disabling certificate verification. - Unsupported transport or interpolation: use Streamable HTTP (not stdio or legacy SSE). If a client cannot resolve environment-backed custom headers, use a supported secret store or a local proxy that injects the header; do not commit the key as a static header.
Client syntax is based on the current official Claude Code MCP documentation and Codex MCP documentation .
Multi-tenant authentication boundary
The default multi_tenancy.mode: static behavior is unchanged: MCP requests
use multi_tenancy.static_tenant_id, and clients do not send a tenant header.
In hosted required_from_auth deployments:
- Internal MCP session JWTs carry a signed tenant binding. The daemon verifies that binding before it reads the session or user, and a token cannot be replayed with another tenant header.
- Personal API keys are opaque and do not contain a signed tenant. Using them
at
/mcptherefore requiresmulti_tenancy.trusted_header; the trusted reverse proxy must remove any client-supplied value and set the header from its authenticated routing decision. It must send exactly one tenant value; duplicate or comma/list-valued tenant headers are rejected. Do not expose the daemon directly when this mode relies on a trusted header. - If more than one tenant signal is present, all signals must agree. Missing or conflicting identity fails closed before tenant-owned authentication data is queried.
- The built-in endpoint retains no transport context. Every POST resolves the
tenant, authenticates and reloads the user, and authorizes any optional Agor
Session independently. An
Mcp-Session-Idis never a source of identity or authority.
Tenant identity is ambient for the MCP operation, but it does not hold a database transaction open. Individual service and repository calls use short tenant-scoped units of work.
Best Practices
- Keep permission policies tight. MCP calls respect Agor’s permission system. Configure approvals so agents only do what the team expects.
- Design idempotent workflows. Make repeated calls safe; agents may retry when handling errors.
- Log agent activity. Sessions capture every MCP action they trigger, making reviews and audits straightforward.
- Reuse concept files. Agents can load
context/docs via MCP, ensuring automations stay aligned with team conventions.
MCP Tokens
MCP session tokens are short-lived JWTs (aud agor:mcp:internal) embedding
the session (sub), authenticated caller (uid), tenant (tid), a
per-issuance ID (jti), and an expiry (exp). A still-valid token may be
reused from a cache keyed by tenant, session, and user; otherwise GET /sessions/:id or POST /sessions mints a new one.
There is no revocation mechanics, no per-jti ledger, no session-generation
counter. The authorised blast radius of a leak is bounded by exp (default
24h). Validation additionally rejects tokens whose session has been deleted
from the signed tenant. Tokens issued before the tenant binding was introduced
are rejected and are replaced the next time the session is fetched or created.
These session tokens are for daemon-to-executor use; external clients should
use personal API keys rather than treating this JWT format as a general-purpose
authentication protocol.
Access gating
Because an MCP token binds uid to the authorized caller and lets the bearer
act as that user on the MCP channel, it is only issued to callers who are
allowed to receive a token for the session:
GET /sessions/:idfirst applies normal session and branch authorization, then issues a caller-scoped token to any authenticatedmember+or to the executor’s service identity (role: 'service', used when spawning the child process). The caller need not be the session creator: MCP tools continue to act as that caller and enforce their normal authorization checks.- For
POST /sessions, the caller is the creator by construction, somember+is the only gate here. - Callers with the account role viewer never receive an
mcp_tokenon either path. They cannot prompt via REST either, so MCP would be useless for them regardless.
Config knobs
execution:
# Token lifetime — keep short to cap the damage from a leak.
mcp_token_expiration_ms: 86400000 # 24h (default)Troubleshooting
If an external client receives an authentication error, check that it is using an Agor personal API key—not a browser login token or another provider’s API key—and sending it through a supported authentication header.
Agor manages internal session tokens automatically. If authentication errors persist in an Agor-launched session, contact your administrator. Do not share tokens or paste them into logs or support messages.
Related Reading
- Sessions & Trees (how agents spawn, fork, and ask side questions through MCP)
- Teammates (long-lived AI teammates that drive Agor through MCP)
- Scheduler (cron-style triggers that fire prompts via the same surface)
- TypeScript Client (the non-agent doorway: drive Agor from JS/TS apps, scripts, and services)
- Architecture: Agor as an MCP Server
- SDK Comparison
Branch cleanup
agor_branches_clean({ branchId }) requests the repository-approved cleanup command.
It requires branch Manager/owner authority and write access; disabled policy,
effective protection, outstanding tasks/uploads, and active environments block it.
The default command is git clean -fdX (ignored files only), but cleanup is disabled
until a repository administrator enables it. Custom command settings are retained
but cannot execute until descendant containment is supported.
The response is { branch_id, operation_id, status: "accepted" }. Inspect the
branch’s workspace_operation for completion or a safe failure/unknown outcome;
acceptance does not prove files changed. There is no force, command/path override,
dry-run, or built-in age threshold. Never automatically retry an unknown operation.
See Branch cleanup for runtime limits and recovery.