wikis / Prime Agent / wiki / concepts / mcp-integrations.md view as markdown report a mistake
MCP Integrations
Definition
An MCP integration connects an external service (e.g. Linear, Notion) to Prime Agent over the Model Context Protocol. 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 (see 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:
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:
{
"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:
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)
- Skill ships installed but disabled (excluded from prompt, not imported) — no credentials exist.
- User logs in; credentials land in
auth.jsonundermcp:<server>. - A resource reload (automatic after
/login//mcp login, or manual/reload) detects credentials, enables the skill, kernel installs + imports it. - 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>.oauthvsbearerTokenEnvVar— mutually exclusive auth strategies, each with differentNotEnabledrecovery instructions.serverclass attribute — must match both themcpServerskey and theauth.jsoncredential id (mcp:<server>).- Kernel import name — equals the
servervalue; collides with any unrelated same-named PyPI package on a customPRIME_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
linearentry inmcpServerswith a customurl) does not reuse the previously stored official OAuth credential — for security, onlybearerTokenEnvVarauthenticates 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
/reloadleaves the model unable to see the newly enabled skill.
Related Concepts
- skills — MCP integrations are implemented as Python-backed skills; this page assumes that skill packaging model.
- extensions — the broader extensibility system that skills and integrations sit alongside.
Sources
- raw/github_doc-packages-coding-agent-docs-mcp-integrations-md.md
