---
title: "Model Context Protocol - Pilot Docs"
description: "MCP server for AI agent integration"
url: "https://docs.calinora.io/features/mcp/"
---

# Model Context Protocol

Pilot includes a built-in MCP (Model Context Protocol) server that exposes 56 tools for external AI agents to interact with your Kafka cluster.

## Overview

MCP is an open protocol that allows AI agents (like Claude Desktop, custom agents, or other MCP-compatible tools) to discover and invoke operations on your cluster programmatically. The MCP server is enabled by default.

```bash
PILOT_MCP_ENABLED=true  # Enabled by default
```

## Tool Categories

### Read Tools (30+ tools)

Read-only operations that inspect cluster state:

| Tool | Description |
| - | - |
| `get_cluster_overview` | Cluster overview: brokers, topics, partitions, controller |
| `get_cluster_health` | Health: URPs, offline partitions, unavailable counts |
| `get_partition_activity` | Partition activity rates and sizes |
| `get_broker_racks` | Broker rack assignments |
| `get_topic_config` | Topic configuration |
| `search_topics` | Search topics by name |
| `search_topics_by_config` | Search topics by config values |
| `list_acls` | List ACL bindings |
| `list_consumer_groups` | List consumer groups |
| `describe_consumer_group` | Group details with members |
| `get_consumer_group_lag` | Per-partition consumer lag |
| `browse_messages` | Browse messages in a topic |
| `get_topic_watermarks` | Partition watermarks |
| `list_quotas` | List client quotas |
| `list_proposals` | List rebalance proposals |
| `generate_proposal` | Read the current rebalance candidate and authoritative readiness decision |
| `get_cluster_logdirs` | Log directory info |
| `what_if_simulate` | Simulate cluster scenarios |
| `blast_radius_analyze` | Analyze failure impact |
| `search_audit_log` | Search audit events |

### Mutate Tools (20+ tools)

Operations that modify cluster state (require approval):

| Tool | Description |
| - | - |
| `update_topic_config` | Update topic configuration |
| `bulk_update_topic_config` | Update config for multiple topics |
| `create_acls` | Create ACL bindings |
| `delete_acls` | Delete ACL bindings |
| `reset_consumer_group_offsets` | Reset consumer offsets |
| `delete_consumer_group` | Delete a consumer group |
| `update_quota` | Set quota values |
| `delete_quota` | Delete quota keys |
| `redistribute_topic` | Redistribute topic partitions |
| `redistribute_partition` | Reassign a single partition |
| `bulk_reassign_partitions` | Bulk partition reassignment |
| `cancel_reassignment` | Cancel active reassignment |
| `apply_proposal` | Apply a rebalance proposal. Refused with `status: "blocked"` and a `reason` when `decision.applicationAllowed` is false, before approval and again when approved. MCP has no `force` override |
| `set_broker_maintenance` | Enter broker maintenance |
| `remove_broker_maintenance` | Exit broker maintenance |
| `preferred_leader_election` | Trigger PLE |
| `move_broker_logdirs` | Move log directories |
| `revert_audit_event` | Revert an audited change. A reassignment revert is refused, with nothing moved, when a partition changed since the audited change, so later moves are not undone. Pilot’s own `selfheal.apply` events cannot be reverted |

### Meta Tools (3 tools)

Approval workflow management:

| Tool | Description |
| - | - |
| `approve_action` | Approve a pending mutation |
| `reject_action` | Reject a pending mutation |
| `list_pending_actions` | List pending approvals |

## Output Limits and Drill-Down

List-shaped tools return bounded summaries by default so results stay small enough for LLM context windows. Complete data remains available through drill-down parameters, dedicated detail tools, and the REST API.

### Result Envelope

Bounded list results wrap their rows in a common envelope:

| Key | Meaning |
| - | - |
| `count` | Rows returned in this response |
| `total` | Rows available before the limit was applied |
| `truncated` | `true` when rows were dropped |
| `hint` | Present only when truncated; names the parameter or tool to use for a narrower query |

Per-row field sets are stable and envelope keys are additive-only; tools that predate the envelope keep historical key names such as `totalTopics`, `totalPartitions`, and `historyTotal`. Aggregate figures (totals, rollups, health counts) stay complete even when rows are truncated.

### Limits per Tool

`limit` is optional everywhere it appears. Omitted or `0` selects the default; values above the maximum are clamped to the maximum. Rows marked “fixed” have no `limit` parameter.

| Tool | Rows key | Default | Max | Order |
| - | - | - | - | - |
| `get_cluster_overview` | `topics` | 100 | 500 | Partition count desc, then name |
| `get_cluster_health` | `notRackAwareDetails` | 20 | fixed | - |
| `get_partition_activity` (plain) | `partitions` | 50 | 200 | Topic asc, partition asc |
| `get_partition_activity` (`aggregate="topic"`) | `topics` | 20 | 100 | Sort key desc |
| `get_partition_activity` (`sortBy` only) | `partitions` | 50 | 200 | Sort key desc |
| `search_topics` | `topics` | 100 | 1000 | Name asc |
| `list_acls` | `bindings` | 100 | 500 | Resource type, resource name, principal |
| `list_consumer_groups` | `groups` | 100 | 500 | Group ID asc |
| `get_consumer_group_lag` | `topPartitions` | 20 | 200 | Lag desc |
| `get_topic_watermarks` | `partitions` | 100 | 1000 | Partition asc |
| `get_cluster_logdirs` (summary) | `topics` | 20 | 100 | Size desc |
| `get_cluster_logdirs` (detail) | `partitions` | 50 | 200 | Size desc |
| `list_reassignments` | `history` (embedded) | 10 | fixed | Newest first |
| `get_reassignment_history` | `history` | 20 | 100 | Newest first |
| `get_partition_optimization` | `recommendations` | 20 | 100 | High > Medium > Low, then topic |
| `search_audit_log` | `events` | 50 | 200 | Newest first |
| `browse_messages` | `messages` | 20 | 50 | Seek order |
| `get_proposal`, `generate_proposal` | `reassignments` (embedded) | 50 | fixed | - |

Proposal tools return the backend’s authoritative `decision`, shared with Overview and proposal review. Use `decision.applicationAllowed` and `nextAction` to determine whether a candidate can be reviewed for application. Move counts, raw optimizer status, storage status `ready` and confidence scores do not override it. `generate_proposal` reads the server-selected current candidate without starting a separate calculation. Its `candidateAvailable` flag means a candidate exists, not that it can be applied. List entries each include their own decision; historical proposals are not current recommendations.

`list_proposals` always returns summary rows; `get_proposal` and `generate_proposal` omit broker activity scores, cluster snapshots, every broker’s fair share (`fairShare.shares`), each load’s oversized partitions and the optimizer’s search diagnostics (`movement.search`), with `totalReassignments` and `reassignmentsTruncated` reporting the full size of the embedded list (complete data via the REST API). `fairShare.loads` is keyed by load name. `topicSpread.atLimit` is keyed by pair, for example `orders on broker 1`, and the topic cap’s pair and excess-leader counts (`overCap`, `projectedOverCap`, `excessLeaders`, `projectedExcessLeaders`) are left out; they stay in the REST API and the UI. Both return the per-metric `balance` breakdown instead: imbalance, whether the metric counts for balancing or has [no traffic](https://docs.calinora.io/features/proposals/#no-traffic), largest per-broker difference, and busiest and quietest broker.

### Drill-Down Parameters

Parameters that switch summary output to detail, or narrow it:

| Tool | Parameter | Effect |
| - | - | - |
| `get_cluster_logdirs` | `topic`, `brokerId` | Switch from broker and topic summaries to per-partition replica rows; combinable |
| `get_partition_activity` | `aggregate`, `sortBy`, `topic`, `brokerId` | Aggregate per topic, rank by rate or size, or filter rows |
| `get_consumer_group_lag` | `topic` | Scope rollups and partition rows to one topic |
| `get_partition_optimization` | `priority` | Only `High`, `Medium`, or `Low` recommendations |
| `get_default_configs` | `keyFilter` | Case-insensitive substring filter on config keys |

For full detail on a single item, use the paired detail tool instead of raising the limit: `describe_consumer_group` for members and assignments, `get_proposal` for a full proposal, `get_reassignment_by_id` for one reassignment record, `get_reassignment_history` for older history.

## Approval Workflow

All mutate tools require explicit approval before execution:

1. An AI agent calls a mutate tool
2. Pilot queues the action and returns a pending approval token
3. A **distinct** identity approves or rejects the action
4. If approved, the mutation executes and the result is returned

This ensures no AI agent can make unsupervised changes to your cluster.

### Distinct Approver

Approval requires an identity different from the one that requested the mutation. An MCP client cannot approve its own pending action: a different identity, such as a second MCP client or token, must call `approve_action`. The Pilot UI and the assistant do not show MCP pending actions. The action stays pending until a distinct identity approves it or it expires. An unauthenticated client cannot self-approve either.

## Execution Parity

MCP-driven mutations run through the same execution path as the HTTP API:

- **Same safety gating.** Hard-health blockers (controller unhealthy, majority of brokers unavailable, sustained ISR shrink) block the mutation and are not overridable. Drains and reassignments are blocked while a rolling restart is in progress. See [Apply-Time Safety Gating](https://docs.calinora.io/features/reassignments/#apply-time-safety-gating).
- **Throttled and concurrency-capped.** Reassignments apply the same replication throttle and per-broker move cap as the HTTP path, so an MCP-driven move cannot saturate the cluster.
- **Single-flight.** A server-wide mutation slot keeps MCP and HTTP from stacking uncoordinated reassignment load.
- **Run to completion.** MCP reassignment actions execute fully and report `status: "completed"` rather than returning while the move is still in flight.
- **Audited the same way.** Mutations are recorded in the [audit log](https://docs.calinora.io/configuration/audit-logging/#event-sources) with method `MCP`, the approver in `userId` and the requester in `requestedBy`. Reassignment tools record one row per partition when the moves are submitted and add an event with status `500` if the execution then fails. Other tools add an event with status `500` when the change itself fails. Quota events carry the entity path used by the HTTP API, so MCP quota changes can be reverted from the audit log.

## Authentication

When [authentication](https://docs.calinora.io/configuration/authentication/) is enabled, the MCP endpoint requires a valid Bearer token. Use a **Personal Access Token (PAT)** for long-lived, revocable access. PATs work with all Pilot API endpoints, not just MCP - see [Personal Access Tokens](https://docs.calinora.io/configuration/authentication/#personal-access-tokens-pats) for details.

> **Prerequisites:** PATs require `AUTH_PAT_ENABLED=true` and a stable `AUTH_PAT_HASH_SECRET` to be configured on the server. See [PAT Configuration](https://docs.calinora.io/configuration/authentication/#configuration) for details.

### Creating a Token

1. Log into the Pilot UI via your OAuth provider
2. Click your avatar (top right) -> **Access Tokens** -> **New token**
3. Choose a name, scope (`read` or `write`), and optional expiry
4. Copy the `pat_...` token (shown only once)

### Scopes

| Scope | Permissions |
| - | - |
| `read` | Read-only tools (cluster overview, topic configs, consumer groups, etc.) plus `list_pending_actions` |
| `write` | All tools including mutations (reassignments, ACLs, quotas, maintenance, etc.) |

Mutate tools always require the approval workflow regardless of scope.

Scope is enforced per tool on the MCP endpoint, not by HTTP method. A `read` token is refused `approve_action` and `reject_action` as well as every mutate tool, so it cannot release or cancel a mutation queued by someone else. It can still call `list_pending_actions` to see what is queued.

## Integration

The MCP endpoint is available at:

```
POST https://your-pilot-host/api/v1/mcp
```

### GitHub Copilot (VS Code)

Add to `.vscode/mcp.json`:

```json
{
  "servers": {
    "pilot": {
      "type": "http",
      "url": "https://pilot.example.com/api/v1/mcp",
      "headers": {
        "Authorization": "Bearer ${input:pilot-token}"
      }
    }
  }
}
```

VS Code will prompt for the token on first use and store it securely.

### Warp Terminal

Add to Warp’s MCP server configuration:

```json
{
  "mcpServers": {
    "pilot": {
      "serverUrl": "https://pilot.example.com/api/v1/mcp",
      "headers": {
        "Authorization": "Bearer pat_your_token_here"
      }
    }
  }
}
```

### Claude Desktop

Add to your Claude Desktop MCP configuration:

```json
{
  "mcpServers": {
    "pilot": {
      "type": "url",
      "url": "https://pilot.example.com/api/v1/mcp",
      "headers": {
        "Authorization": "Bearer pat_your_token_here"
      }
    }
  }
}
```

### Any MCP Client

The endpoint accepts standard MCP JSON-RPC messages over HTTP POST. Include the `Authorization: Bearer <pat_token>` header with each request when authentication is enabled. The server supports Streamable HTTP transport and advertises all available tools with their descriptions, input schemas, and categories.
