MCP integration
Talon connects external Model Context Protocol servers to personas. Each persona gets its own set of MCP servers. A server bound to one persona is invisible to another.
Adding an MCP server
Section titled “Adding an MCP server”npx talonctl add-mcp --persona assistant --name brave-search --command "npx @modelcontextprotocol/server-brave-search"This adds the server to the persona’s config in talond.yaml:
personas: - name: assistant mcp: - name: brave-search command: npx @modelcontextprotocol/server-brave-search env: BRAVE_API_KEY: ${BRAVE_API_KEY}The server starts when the persona’s agent runner initialises and stops when it shuts down.
Transport
Section titled “Transport”Talon supports three MCP transport types:
| Transport | Description |
|---|---|
stdio | Spawn a child process, communicate over stdin/stdout (default) |
sse | Connect to a remote server via Server-Sent Events |
http | Connect to a remote server via HTTP |
stdio (default)
Section titled “stdio (default)”mcp: - name: my-server command: node /path/to/server/index.js args: - --verbose env: API_KEY: ${MY_SERVER_API_KEY}SSE / HTTP
Section titled “SSE / HTTP”For remote MCP servers, use url instead of command. Custom headers support environment variable substitution:
mcp: - name: remote-server transport: sse url: https://mcp.example.com/sse headers: Authorization: Bearer ${MCP_API_KEY} X-Custom-Header: my-value| Config field | Required | Description |
|---|---|---|
name | Yes | Identifier used in logs and capability labels |
command | Yes (stdio) | Command to spawn the server process |
args | No | Additional arguments (stdio only) |
transport | No | stdio, sse, or http (default: stdio) |
url | Yes (sse/http) | Remote server URL |
headers | No | Custom HTTP headers — values support ${ENV_VAR} substitution |
env | No | Environment variables passed to the server process (stdio only) |
auth | No | Dynamic auth config — resolved by the daemon before reaching providers (see below) |
OAuth for remote MCP servers
Section titled “OAuth for remote MCP servers”Remote MCP servers that require authentication can use Talon-managed OAuth 2.0. The daemon handles token acquisition, storage, and refresh — providers never see the auth field.
Configuring OAuth
Section titled “Configuring OAuth”Add an auth block with kind: 'oauth2' and a tokenStore identifier:
mcp: - name: glean transport: http url: https://contentful-be.glean.com/mcp/default auth: kind: oauth2 tokenStore: glean/default| Field | Description |
|---|---|
kind | 'oauth2' (future schemes like basic or mtls may be added) |
tokenStore | Filesystem-safe identifier for the cached token bundle. Resolves to <dataDir>/mcp-auth/<tokenStore>.json |
The resolveMcpServers() step runs once per agent run, just before the server list reaches the provider. It materializes the access token into an Authorization: Bearer <token> header, merges it with any caller-set headers, and strips the auth field entirely.
Authenticating with talonctl auth-mcp
Section titled “Authenticating with talonctl auth-mcp”Run the one-time OAuth dance:
# Interactive — opens your local browsernpx talonctl auth-mcp <skill>:<server>
# Headless — prints URL and callback instructions (useful over SSH)npx talonctl auth-mcp <skill>:<server> --headlessThe CLI performs RFC 9728 discovery → RFC 7591 dynamic client registration → PKCE authorization code flow → token exchange. The callback listener binds 127.0.0.1.
Token storage and refresh
Section titled “Token storage and refresh”- Tokens persist at
<dataDir>/mcp-auth/<skill>/<server>.jsonwith mode0600 - Atomic temp+rename writes prevent corruption
- Access tokens are refreshed within 60 seconds of expiry using the cached
refresh_token - Concurrent refresh attempts for the same token store are coalesced — the refresh token is never burned by parallel exchanges
Security
Section titled “Security”- Token files are owner-read/write only (
0600) - The
authfield is resolved and stripped inside the daemon before reaching any provider CLI - Unsupported
auth.kindvalues are rejected at startup with a clear error
Per-persona binding
Section titled “Per-persona binding”Each persona declares its own mcp list. The servers are loaded during persona initialization and are only available to that persona’s agents.
personas: - name: researcher mcp: - name: brave-search command: npx @modelcontextprotocol/server-brave-search - name: coder mcp: - name: github command: npx @modelcontextprotocol/server-githubresearcher sees brave-search tools. coder sees github tools. Neither sees the other’s servers.
Rate limiting
Section titled “Rate limiting”Each MCP server has a token-bucket rate limiter. The daemon checks the bucket before forwarding any tool call. If the bucket is empty, the call is held until tokens are available.
mcp: - name: brave-search command: npx @modelcontextprotocol/server-brave-search rateLimit: requestsPerMinute: 60This prevents runaway agents from burning through API quotas on expensive external services.
Internal MCP servers
Section titled “Internal MCP servers”Talon uses the __talond_ prefix for its internal MCP servers:
| Server | Purpose |
|---|---|
__talond_host_tools | The 9 built-in host tools (schedule, memory, channel, persona_send, persona_list, etc.) |
__talond_skill_loader | Lazy skill loading via skill_load |
User-defined servers with the __talond_ prefix are rejected at startup. Don’t use it.
Capability policies
Section titled “Capability policies”MCP tool calls pass through the same capability filter as host tools. The persona must have a matching capability grant before an MCP tool is forwarded to the external server.
Skills can declare MCP server definitions in skills/<name>/mcp/*.json. When a skill is loaded, its servers are automatically bound to the persona.