Skip to content

Guides

Integrate with Claude Code (MCP)

Expose Alter-managed credentials to Claude Code via a Model Context Protocol server.

By the end of this guide, Claude Code can call third-party APIs through Alter using an MCP server. Tool calls in the conversation become agent.request() calls under the hood, scoped to a managed agent, audited per-tool.

The flow:

  1. An operator provisions an MCP-bound agent in Alter.
  2. The MCP server (built with alter-sdk’s MCP helpers) runs locally or in a container, holding only the agent’s API key.
  3. Claude Code is configured to talk to the MCP server.
  4. Each tool call is bound to the agent identity in the audit log.
  • An app and an agent created for the MCP server.
  • Claude Code installed locally (claude --version).
  • Python 3.10+ to run the MCP server.

In the developer portal, create a new agent named claude-code-mcp. Mint a per-agent key from Agents → claude-code-mcp → Keys (per-agent keys currently mint with the legacy alter_key_… prefix) and store it where the MCP process will read it. Bind whichever providers the MCP server should be able to reach:

  • For user-owned data, have the user run a Connect session with agent=<this_agent_id> to delegate access.
  • For operator-owned credentials, issue managed-secret grants to the agent.
Terminal window
pip install 'alter-sdk[mcp]'
server.py
import os
from fastmcp import FastMCP
from alter_sdk import Agent
from alter_sdk.mcp import AlterContext, AlterMCP
mcp = FastMCP("alter-tools")
agent = Agent(api_key=os.environ["AGENT_API_KEY"])
alter = AlterMCP(agent)
@mcp.tool()
@alter.tool(provider="google")
async def list_events(ctx: AlterContext, query: str) -> dict:
"""List the user's Google Calendar events matching a query."""
response = await ctx.request(
"GET",
"https://www.googleapis.com/calendar/v3/calendars/primary/events",
query_params={"q": query, "maxResults": "10"},
)
return response.json()
@mcp.tool()
@alter.tool(provider="slack")
async def send_slack(ctx: AlterContext, channel: str, text: str) -> dict:
"""Post a message to a Slack channel."""
response = await ctx.request(
"POST",
"https://slack.com/api/chat.postMessage",
json={"channel": channel, "text": text},
)
return response.json()
if __name__ == "__main__":
mcp.run()

AlterMCP(agent) wraps an SDK client and exposes the @alter.tool(...) decorator. Stack it under FastMCP’s own @mcp.tool() to register the function as an MCP tool. The @alter.tool decorator injects an AlterContext parameter (hidden from the generated MCP tool schema); tool bodies call ctx.request(...) and the provider, audit context, and agent attribution are forwarded automatically. Missing-grant and insufficient-scope errors are translated into MCP-friendly responses (with a Connect URL when re-authorization is required).

Claude Code reads project-scoped MCP servers from a .mcp.json file in the project root (the same file alter init creates when it registers the Alter onboarding MCP server). Add an entry for the server:

{
"mcpServers": {
"alter": {
"command": "python",
"args": ["/absolute/path/to/server.py"],
"env": {
"AGENT_API_KEY": "alter_key_..."
}
}
}
}

Restart Claude Code. The tools appear in the conversation.

In Claude Code, ask the model to use the tools:

Find my next three calendar events and send a summary to #standup.

Claude calls list_events, then send_slack. Each call appears in the audit log under the claude-code-mcp agent, with the tool name (list_events, send_slack) recorded in the context.

For a single-user MCP install, the simplest setup is one Alter agent per Claude Code installation, with delegations from that one user. The agent only ever needs that one user’s grants, so there’s no per-call user disambiguation.

For multi-tenant MCP deployments (one server, many users), wire user_token_getter and pass the calling user’s identity through the MCP request context.

The @alter.tool() decorator automatically records the tool name in the audit context. To add more (run ID, conversation ID, custom tags), wrap the tool body in an agent.trace() block:

@alter.tool(provider="google")
async def list_events(ctx: AlterContext, query: str) -> dict:
async with agent.trace(run_id=conversation_id):
response = await ctx.request(
"GET",
"https://www.googleapis.com/calendar/v3/calendars/primary/events",
query_params={"q": query},
)
return response.json()

For tools that mutate state (sending email, posting to Slack, refunding a charge), configure an approval policy on the agent so destructive calls pause for explicit approval. See Add human-in-the-loop approvals.

SymptomLikely causeFix
Tools don’t appear in Claude CodeMCP server failed to start.Check Claude Code’s MCP logs; run python server.py directly to surface errors.
NoDelegatedGrantError on a tool callThe agent has no delegation for the provider.Run a Connect session with agent=<this_agent_id> to delegate.
AgentRevokedErrorOperator revoked the agent.Provision a new agent and update the MCP env var.
Conversation context not in auditagent.trace() not used inside tool bodies.Wrap tool bodies in an agent.trace() block.