Expose an Agent or Chain as an MCP Server¶
Since 0.6.0, any Agent or Chain can be exposed as an MCP (Model Context Protocol) server. Once exposed, any MCP-compatible runtime — Claude Desktop, Cursor, Continue, Zed, or another fastaiagent agent via MCPTool — can invoke it as a tool.
The complement to MCP Tools (which lets fastaiagent consume MCP servers), this page covers the serve side.
Install¶
The mcp Python package is an optional extra — importing FastAIAgentMCPServer before installing it raises a clear ImportError with install instructions.
Quick start — expose an Agent over stdio¶
# my_agent.py
from fastaiagent import Agent, LLMClient
agent = Agent(
name="research_assistant",
system_prompt="Research the user's question and summarize concisely.",
llm=LLMClient(provider="openai", model="gpt-4o"),
)
if __name__ == "__main__":
import asyncio
asyncio.run(agent.as_mcp_server(transport="stdio").run())
Run it as a one-shot:
Or via the CLI:
Register with Claude Desktop¶
Edit ~/Library/Application Support/Claude/claude_desktop_config.json (macOS) or the equivalent on your platform:
{
"mcpServers": {
"research-assistant": {
"command": "python",
"args": ["/absolute/path/to/my_agent.py"]
}
}
}
Restart Claude Desktop. The research_assistant tool is now available — Claude will call it for research-shaped questions and show the output in the conversation.
Register with Cursor / Continue / Zed¶
These MCP clients accept the same stdio command/args pattern. See the client's own MCP config docs; the important line is the command + args that runs your Python file.
API¶
Agent.as_mcp_server(...)¶
agent.as_mcp_server(
transport="stdio",
expose_tools=False,
expose_system_prompt=True,
tool_name=None,
tool_description=None,
) -> FastAIAgentMCPServer
| Parameter | Default | Description |
|---|---|---|
transport |
"stdio" |
Only "stdio" ships in 0.6.0. "sse" / "streamable-http" are tracked as follow-ups. |
expose_tools |
False |
If True, each of the agent's own tools is also listed as an individual MCP tool. Default keeps the surface to one primary tool. |
expose_system_prompt |
True |
Expose the system prompt via MCP's Prompts mechanism so clients can introspect "what kind of agent is this?". |
tool_name |
None |
Override the primary tool name. Default sanitizes agent.name to [A-Za-z0-9_]. |
tool_description |
None |
Override the primary tool description. Default includes the first line of the system prompt. |
Returns a FastAIAgentMCPServer — call await server.run() (or use asyncio.run(server.run())) to start the stdio loop. The method blocks until stdin closes (which is how local MCP hosts signal shutdown).
Chain.as_mcp_server(...)¶
Same shape as Agent.as_mcp_server, minus the expose_tools / expose_system_prompt flags (chains have neither concept).
The chain is exposed as a single MCP tool that takes {"input": "..."} and returns the chain's final output as text.
CLI — fastaiagent mcp serve¶
<target>is either a file path or a dotted module path, followed by:attr_name:path/to/my_agent.py:agentmypkg.agents:research_bot--transportdefaults tostdio.--expose-toolsturns on individual tool surfacing.--nameoverrides the primary tool name.
What the MCP client sees¶
| MCP primitive | What it contains |
|---|---|
Tool (one, or more with expose_tools=True) |
The primary tool takes {"input": "..."} and returns the agent's final output as text. When expose_tools=True, each of the agent's own tools is also listed with its original JSON Schema. |
Prompt (one, when expose_system_prompt=True) |
Named <tool>_system, contains the agent's resolved system prompt. Useful for clients that show "what does this server do?" tooltips. |
| Resources | Not currently exposed. Tracked as a follow-up — map LocalKB namespaces to MCP resources. |
Approvals, pauses and governance¶
MCP has no way to pause a call and resume it later, so a run that stops for a decision cannot answer the client. Since 1.78.0 the server says so instead of answering with nothing:
- The primary tool. If the agent (or chain) pauses — a
managed approval policy or an
interrupt()in one of its tools — the call returns an MCP error (isError: true) naming the pause and itsexecution_id. Nothing after the pause ran. The pause stays open: whoever operates the server resolves it withagent.aresume(execution_id, resume_value=Resume(...))orfastaiagent resume <execution_id> --runner module:attr, and the plane's pending-run record closes when they do. An agent with no checkpointer cannot save the pause, and the error says that too. The paused call's arguments are never put in the error. - Inner tools (
expose_tools=True). A client that calls one of a governed agent's tools by name skips the agent loop, so the server asks the plane's policy itself before running it. An allowed call runs; a denied one is refused with the policy's reason; one that needs approval is refused, because a direct call has no run to pause (call the agent's primary tool instead); if the plane cannot answer, the call is refused. Refusals are MCP errors. An agent with noagent_id, or a tool no approval policy covers, runs as before.
Changed in 1.78.0
Before 1.78.0 a paused run came back as an empty successful answer ("",
or the text "null" for a chain), and an inner tool called by name ran with
no governance at all — a tool the plane denies, or holds for approval,
ran anyway.
Example — agent with KB, memory, and tools — as one MCP server¶
Everything composes:
from fastaiagent import Agent, LLMClient
from fastaiagent.agent import ComposableMemory, AgentMemory, SummaryBlock
from fastaiagent.kb import LocalKB
llm = LLMClient(provider="openai", model="gpt-4o")
kb = LocalKB(name="product-docs")
kb.add("docs/")
agent = Agent(
name="support_bot",
system_prompt="Answer product questions from the KB. Escalate if unsure.",
llm=llm,
tools=[kb.as_tool()],
memory=ComposableMemory(
blocks=[SummaryBlock(llm=llm, keep_last=10, summarize_every=5)],
primary=AgentMemory(max_messages=20),
),
)
if __name__ == "__main__":
import asyncio
asyncio.run(agent.as_mcp_server(transport="stdio").run())
From Claude Desktop this looks like one tool called support_bot. Claude calls it, the agent runs its KB-search tool internally, summarizes with the memory block it has configured, and returns text. Claude never sees the internal complexity.
Troubleshooting¶
ImportError: mcp— you haven't installed the extra.pip install 'fastaiagent[mcp-server]'.- Claude Desktop doesn't list the server — ensure the
commandpath is absolute andpythonresolves to an interpreter that hasfastaiagentinstalled. Use an absolutepythonpath if you use multiple environments. - Agent output looks blank — the primary tool requires a non-empty
inputargument. Empty input returns an explicit error string. - Need to see what the server will expose before shipping — call
server.describe()to get a plain dict summary of the tools and prompts, without starting the stdio loop.
Not yet implemented (tracked for follow-up)¶
transport="sse"andtransport="streamable-http"— the MCP spec's two remote transports. Currently raiseNotImplementedErroronrun().- MCP resources — mapping
LocalKBnamespaces to MCP resources so clients can browse them. - Auth middleware on remote transports — will compose with
AgentMiddleware.
Next Steps¶
- MCP Tools — consume MCP servers from a fastaiagent agent
- Tools Overview — the rest of the tool system
- Agents — the agents you're exposing