MCP (Model Context Protocol)¶
reyn speaks MCP in both directions: it can call out to external MCP servers (as a client), and it can expose its own agents to external LLM clients (as a server). The two roles are distinct and both are implemented.
What is MCP¶
MCP is a JSON-RPC protocol for AI agents to connect to "servers" that expose tools. The spec is published by Anthropic at modelcontextprotocol.io. Many official server implementations exist (filesystem, git, github, fetch, brave-search); third parties ship dozens more. A server advertises its tool list (tools/list) and executes calls (tools/call); the agent stays generic.
The point: your skill says "call the read_text_file tool on the filesystem server", not "shell out to cat". Swapping the backend is a config change, not a code change.
Two roles Reyn plays¶
| Role | Direction | How |
|---|---|---|
| MCP client — Reyn calls external servers | Outbound | The mcp Control IR op + permissions.mcp: declaration in a phase. A skill says "call this tool on this server"; the OS dispatches via MCPClient (stdio / http / sse). Example: a skill reads files through the filesystem MCP server. |
| MCP server — external clients call Reyn | Inbound | reyn mcp serve --project . launches Reyn as a JSON-RPC server. Claude Code, Cursor, OpenAI Agents SDK, or any MCP-aware client can then call INTO Reyn's agents using two tools: list_agents() and send_to_agent(agent_name, message). |
The rest of this page covers each role in turn.
Quick start: from zero to working MCP in three commands¶
The recommended first-time flow uses reyn mcp install — no manual YAML editing required:
# 1. Discover available servers
reyn mcp search "github"
# 2. Install (handles config + credentials + permission gate)
reyn mcp install io.github.modelcontextprotocol/server-github
# 3. Start using it immediately
reyn chat
> このリポジトリの最近の PR を一覧して
reyn mcp install fetches the server manifest from the MCP registry, checks that the required runtime (npx, uvx, etc.) is installed, prompts for any credentials (storing them securely in ~/.reyn/secrets.env), and writes the mcp.servers.* entry into your config with ${VAR} references for secrets — all in one step.
For servers not in the registry (including Anthropic's official reference servers like @modelcontextprotocol/server-filesystem), use --source:
reyn mcp install --source npm:@modelcontextprotocol/server-filesystem
reyn mcp install --source pypi:mcp-server-fetch
reyn mcp install --source docker:mcp/playwright
reyn mcp install --source https://github.com/modelcontextprotocol/servers/tree/main/src/filesystem
--source skips the registry fetch and resolves install metadata from the source specifier directly. Permission gate, credentials, config write, and audit event are identical to the registry path.
For the full reyn mcp CLI reference, see Reference: reyn mcp.
Quick start: try MCP from reyn chat (manual config path)¶
If you prefer to configure a server manually (or are adding a server not in the public registry), add it directly to reyn.yaml. reyn chat exposes three router tools that work automatically once the server is configured:
| Tool | What it does |
|---|---|
list_mcp_servers |
Returns the names of all servers configured in reyn.yaml |
list_mcp_tools(server) |
Returns the tools exposed by one server |
call_mcp_tool(server, tool, args) |
Invokes a tool on a server, returning its result |
The LLM router can call these directly during a chat turn. Typical first-time flow:
# 1. Add a server entry to reyn.yaml (one-time)
mcp:
servers:
filesystem:
type: stdio
command: npx
args: ["-y", "@modelcontextprotocol/server-filesystem", "."]
# 2. Pre-approve in reyn.yaml or accept the prompt on first use
permissions:
mcp:
filesystem: allow
# 3. Just chat
reyn chat
> このディレクトリにある README.md を要約して
The router invokes list_mcp_tools → call_mcp_tool automatically; no permissions.mcp: declaration in any skill is required. Skill authoring is for when you want to formalize a recurring workflow (= phase graph, validation, retry policy) — not a prerequisite to using MCP. The deep-dive below is for that case; if you only need ad-hoc invocation, you can stop reading here.
Role 1: MCP client — Reyn calls external servers¶
When a skill needs an external tool, the flow is:
phase frontmatter LLM emits Control IR OS dispatches
permissions: → {kind: mcp, → MCPClient
mcp: [filesystem] server: filesystem, (stdio | http | sse)
tool: read_text_file,
args: {path: ...}}
- The skill's phase declares
permissions.mcp: [server_name]in frontmatter — without this, the runtime refuses every call to that server. - The LLM emits an
mcpControl IR op:{server, tool, args}. It cannot invent server names; only servers configured inreyn.yamland declared in the phase's permissions are reachable. - The OS resolves the server's transport (
stdio,http,sse), dispatches viaMCPClient, and returns the tool result to the phase loop. - Every call emits events —
mcp_calledbefore,mcp_completed(ormcp_failed) after. The audit trail is identical to any other op.
The boundary is sharp on purpose: skills describe what they want, the OS decides how to get it. Adding a new MCP server doesn't touch any OS code (P7).
Transport choice (stdio vs HTTP)¶
Most official MCP servers are local processes you launch over stdio. A few hosted services expose HTTP endpoints. SSE transport is reserved for a future release.
| Transport | When | How reyn launches it |
|---|---|---|
stdio |
Local CLI server (most official servers — filesystem, git, github, fetch) |
Spawns command with args and env; speaks JSON-RPC over stdin/stdout |
http |
Hosted service (your own backend, an org-internal tool registry) | POSTs to url with headers; reuses one session per run |
sse |
Streaming HTTP variant; rare | Same as http plus an event stream |
Pick stdio for anything you npx or pip install locally. Pick http when the server is operated by someone else and you've been handed a URL.
Configuration¶
MCP servers are declared under mcp.servers: in reyn.yaml. Every entry has a type; the rest depends on the transport.
# reyn.yaml
mcp:
servers:
# stdio: local process, speaks JSON-RPC over stdin/stdout
filesystem:
type: stdio
command: npx
args: ["-y", "@modelcontextprotocol/server-filesystem", "."]
env:
# Optional. ${VAR} expands from os.environ at startup.
FS_LOG_LEVEL: "info"
# http: hosted server, JSON-RPC over Streamable HTTP
internal_tools:
type: http
url: https://tools.example.internal/mcp
headers:
Authorization: "Bearer ${INTERNAL_TOOLS_TOKEN}"
| Field | stdio | http | Description |
|---|---|---|---|
type |
required | required | stdio | http | sse |
command |
required | — | Executable to spawn (e.g., npx, python, an absolute path) |
args |
optional | — | Argument list passed to command |
env |
optional | — | Extra environment variables for the spawned process |
url |
— | required | Endpoint URL |
headers |
— | optional | Static headers; values support ${VAR} expansion |
${VAR} expansion resolves from os.environ (which is pre-loaded from ~/.reyn/secrets.env at startup — see Concepts: secret handling). Missing variables expand to "" and emit a warning — never a hard error, so a missing optional token doesn't crash the run.
The ${VAR} syntax works in all YAML string fields, not just mcp.servers. This means models.<name>.api_key, litellm.api_base, and future fields all use the same mechanism. See Reference: reyn.yaml — ${VAR} interpolation for the full picture.
API keys and tokens belong in ~/.reyn/secrets.env (managed via reyn secret set), referenced as ${VAR} in reyn.yaml — never as literal values inline. See Concepts: secret handling.
Security model¶
MCP operations are gated at two points:
Install-time gate: permissions.mcp_install¶
Before any MCP server can be added to the configuration, the mcp_install permission gate fires. This applies to reyn mcp install and to any mcp_install Control IR op emitted by a skill. The default is ask — an interactive prompt on first install.
Enterprise teams can set permissions.mcp_install: deny in a project-scoped reyn.yaml to prevent all server additions, or combine mcp_install: allow with a private registry to enforce "approved servers only" policy:
# enterprise reyn.yaml — team-wide policy
mcp:
registries:
- https://mcp-registry.internal.acme.com/ # private registry (approved servers only)
- https://registry.modelcontextprotocol.io/ # public fallback
permissions:
mcp_install: allow # team can install, but only from the private registry
See Concepts: permission model — mcp_install for the full scope-tier interaction and enterprise use cases.
Runtime gate: permissions.mcp¶
MCP tool calls cross two checks before they leave the process:
- Phase declaration. A phase MUST list each server it intends to use under
permissions.mcpin its frontmatter. The runtime callsrequire_mcp(decl, server, ...); ifserver not in decl.mcp, the call fails with a clear error pointing at the missing declaration. - Approval. Like every other capability, the first invocation per skill prompts (
y/j/r/N). Persistent approvals land in.reyn/approvals.yamlkeyed by<skill>/mcp.<server>. Pre-approve project-wide withpermissions.mcp: allowinreyn.yamlif you trust the project broadly.
This matches reyn's general permission model — see permission-model.md. One skill's MCP approval doesn't leak to another skill, and a sub-skill invoked via run_skill has to ask for its own permissions.
Three audit events are emitted per call:
| Event | When | Payload |
|---|---|---|
mcp_called |
Before the request leaves the process | server, tool, args |
mcp_completed |
On normal return | server, tool, is_error |
mcp_failed |
On transport / protocol error | server, tool, error |
Filter for them with reyn events tail | grep mcp_ or grep '"mcp_called"' .reyn/events.jsonl.
The first stdlib skill: read_local_files¶
read_local_files is the canonical example of a skill built around an MCP server. It pairs with the filesystem MCP server to read files inside a project, summarise sections, and answer questions about the contents — everything a vanilla cat would do, except routed through the permission system and the audit log.
Treat it as the template to copy when authoring your own MCP-backed skill: declare permissions.mcp: [filesystem] in the phase, emit mcp ops with tool: read_text_file (or whatever the server advertises), and let the OS handle the rest.
See the reference page for phase shapes and the how-to for a full quickstart.
Role 2: MCP server — external clients call Reyn¶
When you run reyn mcp serve, Reyn becomes an MCP server. External MCP-aware clients — Claude Code, Cursor, OpenAI Agents SDK, or anything that speaks the MCP protocol — can then submit messages to your Reyn agents as if they were just another MCP tool.
Starting the server¶
--project points at the directory containing reyn.yaml. Because MCP clients typically spawn the server process with cwd=/, this flag is required in most client configs — the server has no other way to locate your project. --timeout (default 60 s) controls how long send_to_agent blocks before returning a partial reply; the agent keeps working in the background.
The server speaks JSON-RPC over stdio. There is no port. The MCP client launches the process itself and owns the transport.
Tools exposed¶
Two tools are registered:
| Tool | Signature | What it does |
|---|---|---|
list_agents |
() |
Returns a JSON array of {name, role} objects — one entry per agent declared in reyn.yaml. |
send_to_agent |
(agent_name, message) |
Submits one user-style message to the named agent and blocks (up to --timeout seconds) for the final reply text. Returns {reply, partial, agent}. If partial=true, the agent is still working; call again to receive more. |
Multi-turn continuity is preserved: each agent's ChatSession keeps its history.jsonl between calls, so a conversation that starts in Claude Code can be resumed from reyn chat — or vice versa.
What "via MCP" means for your skills¶
External clients see agents, not the skill graph. From the outside, there are only two operations: list agents and send a message. The OS contract still applies on Reyn's side: permissions are checked, events are emitted, and all the normal validation runs. Skills can be approved non-interactively if permissions: allow is set in reyn.yaml (the MCP server runs without a human at stdin, so interactive prompts would block indefinitely).
This is part of Reyn's "talks-out + talked-to" multi-agent surface. See multi-agent.md for how agents relate to each other within a single Reyn process.
What MCP is NOT for¶
MCP is the right tool for external capability access. Don't reach for it when:
- You need heavy compute. Use a Python preprocessor (
pythonop). MCP calls cross a process boundary on every invocation; an inline NumPy step is much faster. - You're encoding a reusable workflow. That's a skill, not an MCP server. Use
skill_builderto author a new skill, not a new MCP tool. - You want cross-agent messaging. Use
messages_to_agentsand topology rules. MCP doesn't model agent identity or chains. - You need state across invocations. MCP servers can be stateless or stateful, but reyn treats each call as independent. Persistent state belongs in the workspace.
If you find yourself wishing MCP could do one of these, you're at the wrong layer.
See also¶
- How-to: use an MCP server — quickstart with the filesystem server
- Reference:
reyn mcp— full CLI reference forsearch,install,list,remove,set-secret,clear-secret - Reference:
reyn secret— universal secret management - Concepts: secret handling —
~/.reyn/secrets.envand${VAR}interpolation - Reference:
read_local_files— the first stdlib MCP skill - Reference:
reyn.yaml— fullmcp.servers:schema - Concepts: permission model —
mcp_installandpermissions.mcp - modelcontextprotocol.io — the spec, server registry, official SDKs