# 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:

```json
{
  "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:

```json
{
  "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:

```json
{
  "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:

```json
{
  "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:

```json
{
  "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:

```json
{
  "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_reason` before 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:

- `start` at the beginning of a run
- `heartbeat` during long runs
- `snapshot` for bounded repo state
- `activity` for session-scoped counters and line deltas
- `publish_context` when there is useful handoff context
- `get_context` before taking related work
- `revoke_context` when a published context should be withdrawn
- `end` when 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`:

```json
{
  "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.
