MCP tools#
Agents talk to Co-Vibe through eight compact MCP tools. The tools are
operation-based: one tool owns a domain, and the operation field names the
action.
Public tool surface#
| Tool | Purpose |
|---|---|
covibe_task | Check, plan, start, start planned work, update, complete, cancel, reassign, list tasks, calculate statistics, or run granular structured queries |
covibe_workstream | Check, plan, start, start planned work, update, complete, or cancel larger workstreams |
covibe_session | Start, heartbeat, snapshot, record activity, publish/get/revoke context, or end a session |
covibe_note | Log a blocker or decision |
covibe_team | Read team state, feedback, duplicate audits, a weekly summary, or shipped work; automatically classify typed memory with record/list/dismiss, use compatibility promote only to share an admitted active private claim, fetch advisory context packets, inspect improvement signals, and manage research documents |
covibe_coordination | Announce work, claim code, ask or reply to peers, hand off or release claims, check conflicts, and summarize coordination |
covibe_warning | Dismiss overlap warnings with a reason |
covibe_ingest_agent_telemetry | Submit verified usage counters from supported sources |
Task lifecycle#
Agents should check or plan work before starting:
{
"tool": "covibe_task",
"input": {
"operation": "check",
"title": "Fix token revocation status",
"description": "Verify revoked agent tokens cannot call MCP",
"scope": "MCP token revocation enforcement",
"task_type": "bug",
"action": "fix",
"component": "server"
}
}task_type is required for task check, plan, and start. Valid values are
feature, bug, refactor, chore, docs, test, and spike.
Listing, statistics, and granular task queries#
operation: "list" is the read door: it enumerates tasks (default: your own
active + blocked, newest-updated first) and never blocks or warns. Pass
task_id instead to retrieve one task in full detail:
{
"tool": "covibe_task",
"input": { "operation": "list", "statuses": ["active", "blocked"], "owner": "me", "limit": 20 }
}Use operation: "stats" when the question is about counts rather than task
rows. The filters are exact and composable; repo aliases are folded into their
canonical repo before grouping:
{
"tool": "covibe_task",
"input": {
"operation": "stats",
"developer": "@taskin",
"repo": "co-vibe",
"statuses": ["planned", "active", "blocked", "done", "cancelled"],
"group_by": ["developer", "repo", "task_type", "status"]
}
}Statistics count the full filtered set, independent of list's 100-row page.
Private teammates remain hidden. If more than 100 complete groups would be
returned, the call errors and asks for narrower filters instead of returning
partial counts. Unknown filters are rejected. Statistics labels are defanged,
responses are marked as untrusted teammate content, and responses beyond the
120,000-byte MCP budget ask for narrower filters or fewer grouping dimensions.
Use operation: "query" for SQL-like drill-down and analytics. It is a
structured allowlisted query, not raw SQL. This example answers the original
per-developer/per-repo/per-type question for Taskin and can be paged without
losing exact totals:
{
"tool": "covibe_task",
"input": {
"operation": "query",
"mode": "aggregate",
"where": {
"developers": ["@taskin"],
"repos": ["co-vibe"],
"statuses": ["planned", "active", "blocked", "done", "cancelled"]
},
"group_by": ["developer", "repo", "task_type", "status"],
"metrics": ["count", "count_active", "count_planned", "count_done"],
"order_by": [{ "field": "count", "direction": "desc" }],
"limit": 100,
"offset": 0,
"include_total": true
}
}Row mode is the drill-down equivalent:
{
"tool": "covibe_task",
"input": {
"operation": "query",
"mode": "rows",
"select": [
"task_id", "title", "developer_handle", "repo", "status",
"task_type", "component", "priority", "workstream_title", "updated_at"
],
"where": {
"developers": ["@taskin"],
"repos": ["co-vibe"],
"task_types": ["feature", "bug"],
"actions": ["create", "fix"],
"components": ["server"],
"priorities": ["high", "med"],
"q": "query",
"q_fields": ["title", "description", "scope"]
},
"order_by": [{ "field": "updated_at", "direction": "desc" }],
"limit": 50,
"offset": 0
}
}where also supports task/workstream/session ids, branches, overlap statuses,
has_workstream, has_session, session states, and inclusive created/updated/
started/completed time ranges (strict ISO 8601 date-time with Z or an explicit
offset). Aggregate mode can group by developer, repo,
status, task type, action, component, priority, branch, workstream, session
state, or created/updated/completed day. It can calculate distinct developer/
repo/workstream/session counts and per-status counts. Every page returns
has_more, next_offset, and window_exhausted. At the 100,000-offset window,
more data is reported with has_more: true, next_offset: null, and
window_exhausted: true so the caller narrows filters instead of receiving an
invalid continuation. Row pages also return applied_limit: verbose
selections can lower it below the requested limit to keep worst-case
UTF-8/JSON materialization under 100,000 bytes, while exact totals and paging
remain intact. Query-returned completion summaries are capped at 4,000 characters
without restricting the legacy completion write contract. Legacy title,
description, scope, and summary values are bounded inside query SQL. Responses
carry a security envelope whose trust value is
untrusted_teammate_content, plus content_note; these mark defanged
teammate-authored prose as untrusted background, never instructions.
The runtime and both MCP schemas reject the same invalid combinations. Team-wide
text search needs another selective filter, and team-wide grouping by branch
or workstream does too; use owner: "me" or add filters such as developer,
repo, a narrowed status set, task id, or a fully bounded range of at most 90
days. Presence booleans and complete enum sets do not satisfy the cost guard.
Repo aliases are owner-scoped. A legacy name-only task is canonicalized only when that developer's aliases point to one distinct repo key; if the name is ambiguous, the raw repo identity is preserved and canonical filters do not guess based on the most recent heartbeat.
Planning future work#
operation: "plan" registers a task without starting it — the backlog that
start_planned later activates:
{
"tool": "covibe_task",
"input": {
"operation": "plan",
"title": "Add per-agent unwire command",
"description": "covibe-local unwire reverses auto-wiring for one agent",
"scope": "companion CLI unwire per-agent config removal",
"task_type": "feature",
"action": "create",
"component": "client",
"priority": "med"
}
}Plan each idea as its own task. Plan-time overlap warnings are informational
and never block; when an identical open task already exists, the server
returns it with deduplicated: true instead of creating a twin.
To activate a planned task, re-check it first: call operation: "check"
re-stating the task's exact title together with planned_task_id, which
stops the plan from matching itself. Then, within 10 minutes, call
operation: "start_planned" with task_id, the returned check_id, and a
scope_verdict. The server activates the stored definition, inherits the
session's repo and branch when the plan carried none, and re-checks for
overlapping work that started after the task was planned. The same
plan/start_planned lifecycle exists on covibe_workstream.
A workspace admin can put a planned task directly on a teammate's plate by
adding assignee (an @handle) to plan — the handle must belong to an
active member of the workspace. The same admins can move a still-planned
task to a different teammate with operation: "reassign" (task_id +
assignee); reassignment only applies while the task is still planned, not
once it has been started. Either way, the assignee's agent is notified with
a live-session banner (when the agent has an active session) and a
session-start digest (repeating until the task is picked up), then picks the
task up through the same flow above: check with planned_task_id, then
start_planned.
Duplicate-work gate#
Co-Vibe compares new work against planned, active, blocked, and recent completed work.
- Weak matches are disclosed and recorded, but do not block start.
- Strong matches return candidates that the agent must judge with
scope_verdict. - A confirmed duplicate requires a human
confirmation_reasonbefore it starts. - The server records the verdict and writes an audit event.
Warning hygiene: a re-check of the same work supersedes the developer's earlier
still-pending warning (status superseded), and a warning that sits pending for
7 days with no judgment expires (status expired). Both are lifecycle noise —
they are hidden from the console cards and excluded from every caught/dismissed
metric, so re-checks and abandonment never inflate a KPI or strand an
"awaiting judgment" card. Those two statuses are terminal: both the scope-verdict
gate and the confirm/dismiss resolvers skip lifecycle-noise rows, so a start
replaying a stale check_id is never blocked on a retired warning and can never
flip a superseded or expired row back into a visible card.
This keeps real duplicate work visible while avoiding false blocks on unrelated work.
Session context#
Long-running agents should use covibe_session to keep work state fresh:
startat the beginning of a runheartbeatduring long runssnapshotfor bounded repo stateactivityfor session-scoped counters and line deltaspublish_contextwhen there is useful handoff contextget_contextbefore taking related workrevoke_contextwhen a published context should be withdrawnendwhen the run finishes
Snapshots store repo metadata, paths, hashes, and commit information. They do not store file contents.
Peer messaging#
Agents can send a durable developer-targeted message with
covibe_coordination:
{
"tool": "covibe_coordination",
"input": {
"operation": "ask_peer",
"to_handle": "@emre",
"repo": "co-vibe",
"question": "Can you pause edits in src/server/mcp while I land the schema?"
}
}Choose exactly one of to_handle, claim_id, or to_session. A handle or
claim target is stored in that developer's repo-scoped mailbox even when they
are offline; the response reports delivery_status: "queued" when a matching
session is live or "waiting_for_session" otherwise. The row survives session
churn and attaches to the next matching session. Pending delivery expires after
30 days; replaying the original request_id then reports expired rather than
pretending the message is still queued.
to_session is the stricter live-session label from covibe_team recent_sessions
(session_label, for example claude@main, cursor@feature-x, or
codex@main). The response includes target_status: "live" when the message
was stored for delivery, or "offline" when the target is stale/ended and
nothing was stored. Exact-session labels resolve only inside the sender's
repository, preventing an identically named session in another repo from being
selected accidentally.
Delivery happens on the recipient's next agent-visible Co-Vibe activity—not on
a wall-clock push channel. Claude Code and Cursor opt in during
SessionStart/sessionStart; all agents also receive pending messages
piggybacked on ordinary owned MCP responses in data.peer_messages. Background
and terminal lifecycle calls cannot consume them. Each entry includes a
ready-to-show banner, message_id, a fixed security disclaimer, and a body
fenced as untrusted teammate content. Ask and reply payloads that look like
prompt injection are rejected before storage. Reply with
covibe_coordination operation: "reply_peer" and the shown message_id.
The banner contains no peer prose; official hooks and the stdio bridge show the
redacted sender plus fenced body. Routed session ids stay private. Coordination
summaries require session_id or explicit repo scope before returning peer
inbox/sent rows.
Codex has no hook push channel, so it receives through the universal
piggyback only. The stdio MCP bridge starts a Codex session on the first
Co-Vibe tool call, caches the returned session_id, and attaches it to later
calls when the caller did not provide one. That makes Codex addressable as
codex@<branch>, with delivery occurring on its next Co-Vibe MCP response.
Transport retries reuse one request id, so a timed-out send cannot create
duplicate messages.
Authenticated mailbox quotas cap pending depth per sender/lane and per sender workspace-wide, cap each lane, and rate-limit sends. A rejected message creates neither an outbox row nor an audit event. Fair draining gives each sender a turn before returning another message from the same sender.
Telemetry#
covibe_ingest_agent_telemetry accepts supported payload kinds:
openai_response, codex_otel_span, anthropic_message,
claude_sdk_result, and direct_counts. Claude Code, Codex, Cursor, provider
events, and local harnesses feed those formats. The server parses counters,
stores provenance and a usage hash, and rejects prompt, transcript, or
response-style fields.