---
title: "MCP Integrations"
type: concept
tags: [mcp, area/mcp, audience/developer, scope/advanced, status/well-established]
created: 2026-08-06
updated: 2026-08-06
sources: ["raw/github_doc-packages-coding-agent-docs-mcp-integrations-md.md"]
confidence: high
prime_agent_version: "v0.7.0"
---

# MCP Integrations

## Definition

An MCP integration connects an external service (e.g. Linear, Notion) to Prime Agent over the [Model Context Protocol](https://modelcontextprotocol.io). Distinctively, Prime Agent does **not** expose MCP servers as new agent tools — consistent with its single-tool design, each integration is instead a [Python-backed skill](#related-concepts) (see [[concepts/skills]]) that the model imports and calls directly from the persistent IPython kernel, e.g. `import linear; issues = await linear.list_issues(team="Engineering")`.

## How It Works

### Architecture

The MCP connection runs **inside the IPython kernel** via the official `mcp` Python SDK. The host process's only responsibilities are interactive login (browser OAuth) and minting/refreshing credentials into `auth.json`. This means the tool surface the LLM sees is whatever Python functions the skill package exposes, not a fixed MCP-shaped tool schema.

### Using a built-in integration

Built-in integrations (Linear, Notion) ship **disabled**. Logging in enables them: open `/login`, switch to **MCP Connections**, pick the integration, and complete OAuth in the browser (`/mcp login <name>` does the same from the CLI). Once connected, the integration's skill becomes visible to the model and is auto-imported into the kernel. `/mcp` lists integrations and connection status; `/mcp logout <name>` disconnects. Credentials are stored once in `auth.json` under `mcp:<name>`; enablement is derived purely from whether valid credentials exist (no separate on/off switch). If you log in mid-turn, the reload is deferred — run `/reload` after the turn to activate the integration.

### How a call works

The tool set is defined by the **server**, not the skill — the model must discover before calling, never assume names or schemas:

```python
import linear
for tool in await linear.list_tools():
    print(tool["name"], "-", tool["description"])
help(linear.list_issues)          # populated once list_tools() has run
result = await linear.list_issues(team="Engineering")
```

Every tool method is `async`. Results are already Python-native (dict/string/content-block list) — no manual `json.loads`. A tool whose name isn't a valid Python identifier (e.g. `notion-search`) is invoked via the escape hatch `await notion.call_tool("notion-search", {...})`. Calling an integration with no credentials raises `NotEnabled`; a tool call that returns an error result raises `McpToolError`.

### Authoring a custom integration

An integration is a Python skill package whose module subclasses `McpIntegration` (imported from `rlm`). Two steps:

**1. Declare the server** under `mcpServers` in `settings.json`:

```jsonc
{
  "mcpServers": {
    "acme": { "type": "http", "url": "https://mcp.acme.com/mcp", "oauth": true }
  }
}
```

Only remote `"http"` servers are currently supported — `stdio` (local-subprocess) entries are dropped by the host. HTTP fields: `type` (must be `"http"`), `url`, `oauth` (browser OAuth, requires dynamic client registration support), `bearerTokenEnvVar` (static token instead of OAuth), `headers` (extra static HTTP headers), `enabled` (force-disable even with valid credentials).

**2. Ship the skill package** — a normal Python-backed skill directory (`SKILL.md`, `pyproject.toml` depending on `mcp`/`httpx`/`prime-agent-runtime`, `src/acme/__init__.py`). Minimal implementation:

```python
from rlm import McpIntegration

class Acme(McpIntegration):
    server = "acme"                      # matches mcpServers key / auth.json `mcp:acme`
    url = "https://mcp.acme.com/mcp"

acme = Acme()

_RESERVED = {"run", "__wrapped__", "__call__"}
def __getattr__(name):
    if name.startswith("_") or name in _RESERVED:
        raise AttributeError(name)
    return getattr(acme, name)
```

The base class handles: connecting via the `mcp` SDK, resolving URL/headers from `mcpServers` config, injecting and refreshing the bearer token from `auth.json`, and binding the server's tools as async methods automatically. The `_RESERVED`/`__getattr__` pattern forwards bare module access (`import acme; await acme.<tool>(...)`) to the instance without accidentally making the module itself look like a callable skill (which would break kernel bootstrap tool dispatch).

**Authentication modes**: OAuth (`"oauth": true`) — user runs `/login` → MCP Connections, or `/mcp login acme`; requires OAuth 2.1 dynamic client registration (RFC 7591) support server-side; servers needing a pre-registered client ID aren't supported this way. Static bearer token (`bearerTokenEnvVar`) — no login step; "connected" whenever the env var is set; the subclass sets the matching `bearer_token_env`.

### The `McpIntegration` API

Class attributes: `server: str` (required — the `mcpServers` key / `auth.json` credential id), `url: str | None` (required unless overriding `_open_session`), `bearer_token_env: str | None`. Methods: `await list_tools()` (also populates `help()` docstrings), `await call_tool(name, arguments={})` (escape hatch for non-identifier tool names), and auto-bound `integration.<tool>(**kwargs)`. Exceptions (both importable from `rlm`): `NotEnabled`, `McpToolError`.

### Enable-by-login lifecycle (built-in integrations only)

1. Skill ships installed but disabled (excluded from prompt, not imported) — no credentials exist.
2. User logs in; credentials land in `auth.json` under `mcp:<server>`.
3. A resource reload (automatic after `/login`/`/mcp login`, or manual `/reload`) detects credentials, enables the skill, kernel installs + imports it.
4. Logout (or losing credentials) disables it again.

**User-authored integrations are not auth-gated this way** — a skill dropped into a skills directory loads immediately regardless of `auth.json`, and simply raises `NotEnabled` at call time until credentials exist. Its `SKILL.md` should tell the model how to connect on `NotEnabled`, matching the configured auth mode: for OAuth, point to `/mcp login <server>`; for bearer token, point to setting the env var (never to `/mcp login`, which has no provider for bearer-only servers and reports "Unknown MCP integration").

## Key Parameters

- **`mcpServers.<name>.type`** — must be `"http"`; other transports are dropped.
- **`mcpServers.<name>.oauth` vs `bearerTokenEnvVar`** — mutually exclusive auth strategies, each with different `NotEnabled` recovery instructions.
- **`server` class attribute** — must match both the `mcpServers` key and the `auth.json` credential id (`mcp:<server>`).
- **Kernel import name** — equals the `server` value; collides with any unrelated same-named PyPI package on a custom `PRIME_AGENT_KERNEL_PYTHON`.

## When To Use

- Connecting a SaaS product (issue tracker, docs tool, CRM) that already exposes a remote MCP server, without inventing a bespoke tool schema.
- Wrapping the same MCP server used by other clients so Prime Agent and, say, an editor's MCP client share one backend implementation.
- Overriding a built-in integration's endpoint (e.g. testing against a staging Linear/Notion-compatible MCP server) while keeping the official skill's UX.

## Risks & Pitfalls

- Overriding a built-in name (e.g. declaring your own `linear` entry in `mcpServers` with a custom `url`) does **not** reuse the previously stored official OAuth credential — for security, only `bearerTokenEnvVar` authenticates such an override; OAuth credentials are never honored for a catalog-name override.
- On a custom `PRIME_AGENT_KERNEL_PYTHON`, `import <server-name>` can silently resolve to an unrelated PyPI package of the same name instead of your integration; use the default managed kernel venv to avoid this.
- OAuth provider registration is process-global; in a multi-session daemon, a user-declared server unique to one daemon session re-registers on that session's next reload.
- Tool names and argument schemas come from the server and can change — hardcoding them instead of calling `list_tools()`/`help()` will silently break when the server updates.
- Logging in mid-turn does not immediately activate the integration; forgetting the follow-up `/reload` leaves the model unable to see the newly enabled skill.

## Related Concepts

- [[concepts/skills]] — MCP integrations are implemented as Python-backed skills; this page assumes that skill packaging model.
- [[concepts/extensions]] — the broader extensibility system that skills and integrations sit alongside.

## Sources

- raw/github_doc-packages-coding-agent-docs-mcp-integrations-md.md
