
# MCP Integration

The Model Context Protocol (MCP) is an open standard for connecting AI assistants to tools and data. Sovrium speaks MCP in **both directions**:

- **Server mode** — external AI clients (Claude Desktop, Claude Code, ChatGPT Dev Mode, Cursor) connect to Sovrium and call generated tools: table CRUD, manual-automation invocation, and action-template execution.
- **Client mode** — Sovrium agents call tools hosted on _external_ MCP servers (web search, document fetch, code execution) as part of their workflows.

The two modes are configured independently and serve opposite roles.

| Mode   | Controlled by  | Where                               | LLM required on Sovrium?                    |
| ------ | -------------- | ----------------------------------- | ------------------------------------------- |
| Server | Operator (env) | `MCP_ENABLED`, `MCP_TRANSPORT`, …   | No — the LLM lives at the connecting client |
| Client | Schema author  | An agent's `mcp.allowedTools` block | Yes — `AI_PROVIDER` set                     |

:::callout
**Server mode needs no `AI_PROVIDER`.** When Sovrium is the MCP _server_, the LLM runs at the connecting client. The platform only exposes tools; it does not call a model. `AI_PROVIDER` is only required for client mode (and the rest of the AI layer).
:::

## Server Mode

### Enablement

The MCP server is **disabled by default**. The operator opts in via env vars; the server only mounts the `/mcp` route when `MCP_ENABLED=true`.

| Variable                    | Description                                                                      | Default                  |
| --------------------------- | -------------------------------------------------------------------------------- | ------------------------ |
| `MCP_ENABLED`               | Master switch — mounts the MCP route only when `true`.                           | `false`                  |
| `MCP_TRANSPORT`             | `streamable-http` for remote clients, `stdio` for local IDE integration.         | `streamable-http`        |
| `MCP_MOUNT_PATH`            | Route prefix when transport is `streamable-http`.                                | `/mcp`                   |
| `MCP_AUTH_STRATEGY`         | `oauth2` (when `app.auth` is configured) or `token`.                             | `oauth2` when auth wired |
| `MCP_TOKEN_ADMIN`           | Bearer token granting the admin role (≥32 chars; `openssl rand -hex 32`).        | —                        |
| `MCP_TOKEN_MEMBER`          | Bearer token granting the member role (≥32 chars).                               | —                        |
| `MCP_TOKEN_VIEWER`          | Bearer token granting the viewer role (≥32 chars).                               | —                        |
| `MCP_RATE_LIMIT_PER_MINUTE` | Per-token requests per minute.                                                   | `60`                     |
| `MCP_RATE_LIMIT_PER_DAY`    | Per-token requests per day.                                                      | `5000`                   |
| `MCP_AUDIT_ENABLED`         | Log every tool call to `system.ai_tool_calls` + the activity stream.             | `true`                   |
| `MCP_EXPOSE_INTERNALS`      | Expose read-only auth/system tables to the admin role.                           | `true`                   |
| `MCP_CONFIRM_DESTRUCTIVE`   | Compile `destructiveHint=true` onto delete tools and non-idempotent automations. | `true`                   |

```bash
MCP_ENABLED=true
MCP_TRANSPORT=streamable-http
MCP_AUTH_STRATEGY=token
MCP_TOKEN_ADMIN=$(openssl rand -hex 32)
MCP_TOKEN_VIEWER=$(openssl rand -hex 32)
```

### Connecting a Client

Once the server is mounted (`MCP_ENABLED=true`), point an MCP-capable client at the mount path. With `MCP_TRANSPORT=streamable-http` the server exposes one JSON-RPC endpoint at `MCP_MOUNT_PATH` — `https://your-app.example.com/mcp` by default.

Most clients (Claude Desktop, Claude Code, Cursor, ChatGPT Dev Mode) read an `mcpServers` block:

```json
{
  "mcpServers": {
    "sovrium": {
      "url": "https://your-app.example.com/mcp",
      "transport": "http"
    }
  }
}
```

**Token auth.** In `token` mode, send the role bearer token on every request. The connecting token's role drives RBAC — a `viewer` token only ever sees read/list tools.

```text
Authorization: Bearer <MCP_TOKEN_ADMIN>
```

**OAuth2 auth.** When `app.auth` is configured (`oauth2` is then the default), register a client once via dynamic client registration, then complete the OAuth flow:

```bash
curl -X POST 'https://your-app.example.com/api/auth/oauth2/register' \
  --header 'Content-Type: application/json' \
  --data '{
    "client_name": "Sovrium MCP — Claude",
    "redirect_uris": ["https://your-app.example.com/oauth/callback"],
    "grant_types": ["authorization_code", "refresh_token"],
    "token_endpoint_auth_method": "client_secret_post"
  }'
```

An unauthenticated request returns `401` with a `WWW-Authenticate: Bearer` discovery header, so compliant clients can start the flow on their own.

**Local IDE (stdio).** For a local integration, set `MCP_TRANSPORT=stdio`; the client launches the Sovrium binary as a child process and speaks MCP over stdin/stdout (the HTTP `/mcp` route is not mounted in this mode).

**Verify the connection.** A minimal handshake is `initialize`, then `tools/list`:

```bash
curl -X POST 'https://your-app.example.com/mcp' \
  --header 'Authorization: Bearer <MCP_TOKEN_ADMIN>' \
  --header 'Content-Type: application/json' \
  --data '{ "jsonrpc": "2.0", "id": 1, "method": "tools/list" }'
```

The response lists exactly the tools your role can call — the table CRUD, actions, and automations you marked with `aiAccess`.

### Exposed Surfaces

When mounted, the server generates tools from four surfaces — all gated by RBAC and `aiAccess`:

```
/mcp ─── tools/list ───▶ filtered by connecting role
            │
   ┌────────┴───────────────────────────────────┐
   │ 1. User tables       {app}_{table}_{op}     │  CRUD
   │ 2. Manual automations {app}_automation_{name}│  invocation
   │ 3. Action templates   {app}_action_{name}    │  execution
   │ 4. Admin internals    {app}_auth_*_read/_list │  read-only, denylisted secrets
   └─────────────────────────────────────────────┘
            │
   Audit → system.ai_tool_calls   Rate-limit → per token / OAuth client_id
```

### Declaring AI-Eligible Entities

A schema author marks an entity eligible for MCP exposure with `aiAccess`. This declares _intent_; the operator still has to enable the server with `MCP_ENABLED`. `aiAccess` applies identically to `app.tables[]`, manual-trigger `app.automations[]`, and `app.actions[]`.

It takes two forms — a boolean shorthand or a rich config object:

```yaml
# Boolean shorthand — enable with defaults.
tables:
  - name: contacts
    aiAccess: true
```

```yaml
# Rich config — supplying any object IS the enable signal (no `enabled` field).
tables:
  - name: contacts
    aiAccess:
      description: Customer contacts. Use this when the user asks about people.
      operations: [read, list, create, update]
      fieldExposure: permissioned
      annotations:
        readOnly: false
        destructive: false
```

| Property              | Description                                                                                        |
| --------------------- | -------------------------------------------------------------------------------------------------- |
| `description`         | Hand-written tool description shown to the AI client — the biggest lever for steering AI behavior. |
| `operations`          | Subset of `read`, `list`, `create`, `update`, `delete` to expose (tables default to all five).     |
| `fieldExposure`       | `permissioned` (default — only fields the role can read/write), `all`, or `whitelist`.             |
| `whitelistFields`     | Fields to expose when `fieldExposure: whitelist` (required and non-empty in that mode).            |
| `annotations`         | MCP risk hints — see below.                                                                        |
| `requireConfirmation` | Force `destructiveHint=true` regardless of operation type (e.g. for email-sending automations).    |

### Tool Risk Annotations

Annotations compile into MCP tool definitions so clients can decide whether to auto-approve a call or require confirmation. They map directly to the MCP spec:

| Annotation    | MCP hint          | Meaning                                                               |
| ------------- | ----------------- | --------------------------------------------------------------------- |
| `readOnly`    | `readOnlyHint`    | Tool only reads data; safe to auto-approve.                           |
| `destructive` | `destructiveHint` | Performs destructive operations; clients should require confirmation. |
| `idempotent`  | `idempotentHint`  | Calling twice with the same args is safe (no duplicate side effects). |
| `openWorld`   | `openWorldHint`   | Reaches outside the local app (external API, network call).           |

Sensible defaults are derived from the operation type when annotations are unset.

### Authentication

| Strategy | When                              | How                                                                                        |
| -------- | --------------------------------- | ------------------------------------------------------------------------------------------ |
| `token`  | Default when `app.auth` is absent | Static per-role bearer tokens (`MCP_TOKEN_ADMIN/MEMBER/VIEWER`). At least one must be set. |
| `oauth2` | Default when `app.auth` is wired  | OAuth 2.1 against Better Auth's OAuth-server plugin.                                       |

The connecting credential's role drives RBAC: a `viewer` token can only read/list even when an entity's `aiAccess.operations` allows writes. Startup validation rejects `MCP_AUTH_STRATEGY=token` with no token set, and `oauth2` when `app.auth` is not configured.

### RBAC, Audit & Rate Limiting

- **RBAC** — MCP clients are governed by the same role permissions and row-level rules as human sessions. There is no AI bypass.
- **Audit** — every tool call is logged to `system.ai_tool_calls` and the activity stream when `MCP_AUDIT_ENABLED` is `true` (the default).
- **Rate limiting** — enforced per token (or OAuth `client_id`); over-limit requests return `429` with standard rate-limit headers.

### Admin Internals

When `MCP_EXPOSE_INTERNALS=true` (the default), the admin role gets read-only tools over the `auth` and `system` schema tables (`{app}_auth_*_read/_list`, `{app}_system_*_read/_list`), with secret columns denylisted. Set it to `false` to remove all internal tools from `tools/list`, even for admins.

## Client Mode

Sovrium agents can consume tools from external MCP servers. An agent's `mcp` block defines the allowlist of external tool names it may invoke.

```yaml
agents:
  - name: research-agent
    role: analyst
    systemPrompt: Research topics using approved external tools.
    mcp:
      allowedTools: [web_search, fetch_url]
```

| Property           | Description                                          |
| ------------------ | ---------------------------------------------------- |
| `mcp.allowedTools` | External MCP tool names the agent is allowed to use. |

This complements the agent's internal `tools` allowlist: `tools` scopes what the agent can do _inside_ Sovrium, while `mcp.allowedTools` scopes which _external_ MCP tools it may call.

## Related Pages

- [AI Overview](/en/docs/ai-overview) — the full AI ecosystem and config philosophy.
- [AI Agents](/en/docs/ai-agents) — agents that act as MCP clients.
- [Tables Overview](/en/docs/tables-overview) — the `aiAccess` table property.
- [Actions](/en/docs/automation-actions-overview) — action templates exposed as MCP tools.
- [Roles & RBAC](/en/docs/auth-roles-rbac) — the permissions MCP always enforces.
- [Environment Variables](/en/docs/env-vars) — full `MCP_*` reference.
