> ## Documentation Index
> Fetch the complete documentation index at: https://internal.september.wtf/llms.txt
> Use this file to discover all available pages before exploring further.

# MCP connectors

> Connecting external services via the Model Context Protocol so the agent can act outside the sandbox.

[MCP (Model Context Protocol)](https://modelcontextprotocol.io) is the
standard the Engine uses to talk to external tool providers. Each
"connector" is an MCP server — locally or remotely hosted — that
exposes a set of actions to the agent. Slack, Gmail, Notion, Linear,
GitHub: anything with an MCP server can be connected.

This page covers what MCP looks like to your application, how to
connect a server, and how the resulting tools behave.

## The model's view

Once connected, MCP actions appear in the agent's tool catalog like any
other tool:

* `slack.send_message`
* `gmail.read_thread`
* `linear.create_issue`
* `github.create_pr`

The agent doesn't know they're MCP. It calls them by name and gets
results.

## Lifecycle of a connection

```mermaid theme={null}
flowchart TB
    A["1. user: connect Slack"]
    B["2. POST /assets/connect<br/>{server_name: 'slack'}"]
    C["3. Engine returns<br/>auth_url (OAuth flow)"]
    D["4. user visits auth_url,<br/>grants access"]
    E["5. provider redirects to<br/>OAUTH_REDIRECT_BASE_URL/assets/oauth/callback"]
    F["6. Engine exchanges code<br/>for tokens, stores encrypted"]
    G["7. Slack actions register<br/>in the catalog automatically"]
    H["8. agent can now call<br/>slack.* tools"]
    A --> B --> C --> D --> E --> F --> G --> H
```

For static-credential servers (API key instead of OAuth), step 3
returns "connected" immediately and you skip steps 4–6.

## Listing servers

```bash theme={null}
curl -fsS "$ENGINE_URL/assets/servers" \
  -H "X-Engine-Key: $KEY"
```

Returns the catalog of servers the Engine can connect to:

```json theme={null}
[
  {
    "server_name": "slack",
    "display_name": "Slack",
    "transport": "sse",
    "auth_method": "oauth",
    "status": "available"
  },
  ...
]
```

The catalog is configured in the engine repo. Adding a new server type
involves a JSON entry plus (if it's a custom server) the MCP server
itself.

## Initiating a connection

```bash theme={null}
curl -X POST "$ENGINE_URL/assets/connect" \
  -H "X-Engine-Key: $KEY" \
  -H "Content-Type: application/json" \
  -d '{"server_name": "slack"}'
```

For OAuth servers:

```json theme={null}
{
  "ref_id": "conn-abc123",
  "status": "pending_oauth",
  "auth_url": "https://slack.com/oauth/v2/authorize?..."
}
```

Direct the user to `auth_url`. After they grant access, the provider
redirects to `<OAUTH_REDIRECT_BASE_URL>/assets/oauth/callback`, which
completes the flow.

For static-credential servers:

```json theme={null}
{
  "ref_id": "conn-abc123",
  "status": "connected"
}
```

The Engine stores the credential, encrypted.

## Listing connections

```bash theme={null}
curl -fsS "$ENGINE_URL/assets/connections" \
  -H "X-Engine-Key: $KEY"
```

Returns the user's active connections:

```json theme={null}
[
  {
    "ref_id": "conn-abc123",
    "server_name": "slack",
    "status": "connected",
    "scopes": ["channels:read", "chat:write"],
    "expires_at": "2026-05-27T..."
  }
]
```

## Disconnecting

```bash theme={null}
curl -X DELETE "$ENGINE_URL/assets/connections/conn-abc123" \
  -H "X-Engine-Key: $KEY"
```

Revokes the stored credentials and removes the connection. Subsequent
agent calls to that server's actions error with
`MCP_CONNECTION_FAILED`.

## What MCP actions look like to the model

Each action surfaces as a tool with the server's prefix in the name:

```json theme={null}
{
  "name": "slack.send_message",
  "description": "Send a message to a Slack channel.",
  "input_schema": {
    "type": "object",
    "properties": {
      "channel": { "type": "string", "description": "Channel ID." },
      "text": { "type": "string", "description": "Message text." }
    },
    "required": ["channel", "text"]
  }
}
```

The description and schema come from the MCP server. If the description
is bad, the agent uses the tool poorly — and you can't easily fix it
without modifying the server.

## Credentials

MCP credentials live in `connections.credentials`, encrypted at rest
with Fernet using `AD_ENCRYPTION_KEY`. They're only decrypted when the
Engine makes an outbound call to the MCP server. They never appear in:

* The prompt to the LLM.
* The SSE stream.
* The Engine's logs.

If you set `ENGINE_LOG_LEVEL=DEBUG`, you'll see MCP request/response
traces, but the credentials are still redacted.

## Refresh and circuit breaker

OAuth tokens expire. The refresh scheduler
(`asset_directory/refresh_scheduler.py`) refreshes them in the
background before they expire. If refresh fails (the user revoked
access, the provider's API changed), the connection moves to `expired`
and tool calls error with `MCP_TOKEN_EXPIRED`.

The circuit breaker (`asset_directory/circuit_breaker.py`) tracks
failure rates per connection. If a connection fails repeatedly, the
breaker opens and rejects subsequent calls for a cooldown period. This
prevents one broken connector from generating runaway error traffic.

## Per-user vs. per-tenant

Each Engine instance is single-user, so its connections are per-user by
definition. The MCP credentials in this Engine's brain belong to this
user. Multi-user setups run multiple Engines, each with its own brain
and its own connection set.

## Adding a new MCP server type

To add a server the Engine doesn't know about yet:

1. Add a JSON entry to the catalog with the server's metadata
   (name, transport, auth method, OAuth config).
2. If the server is custom (you wrote it), point the Engine at its
   transport endpoint.
3. Hot-reload with `POST /admin/reload-catalog`.
4. The new server appears in `GET /assets/servers`.

For OAuth servers, register the Engine's callback URL
(`<OAUTH_REDIRECT_BASE_URL>/assets/oauth/callback`) in the provider's
OAuth app settings.

## Pitfalls

* **OAuth callback URL mismatch.** If `OAUTH_REDIRECT_BASE_URL` doesn't
  match what's registered with the provider, the OAuth flow fails with
  a `redirect_uri_mismatch` error.
* **Encryption key rotation.** Rotating `AD_ENCRYPTION_KEY` without a
  re-encrypt step invalidates every stored credential. Plan rotation
  with a migration.
* **Scope drift.** A connection grants specific scopes. If the agent
  needs a scope the connection doesn't have, the MCP call fails. Keep
  scopes in mind when defining the agent's expected workflow.
* **Server-side rate limits.** MCP servers (especially Slack, Gmail)
  have their own rate limits. The circuit breaker mitigates the worst
  cases but doesn't eliminate them. Design for retry.

## See also

* [Asset Directory endpoints](../../api-reference/endpoints/assets) —
  full API surface for MCP management.
* [Permissions](../permissions) — how MCP calls interact with the
  permission system.
* [Threat model](../../../operations/security/threat-model) — what we
  defend against in MCP integrations.
