MCP Integration
Connect agents to any MCP server for filesystem, database, and API tools
MCP (Model Context Protocol) lets agents connect to external tool servers, instantly adding file system access, database queries, API integrations, and more.
from praisonaiagents import Agent, MCP
agent = Agent(
name="FileAgent",
instructions="You help users manage their files.",
tools=MCP("npx -y @modelcontextprotocol/server-filesystem /tmp"),
)
agent.start("List all files in the /tmp directory.")The user asks to inspect the filesystem; the agent calls MCP tools on the connected server.
Which MCP package do I need?
Three packages cover three MCP roles — connecting, light serving, and full hosting.
This page covers the client layer. To serve your own agents, see the praisonai-mcp Package and The Three MCP Layers.
Quick Start
- Connect to an MCP server
python from praisonaiagents import Agent, MCP agent = Agent( instructions="You can read and write files.", tools=MCP("npx -y @modelcontextprotocol/server-filesystem /tmp"), ) agent.start("Create a file called notes.txt with the text 'Hello World'.") - SSE-based MCP server
python from praisonaiagents import Agent, MCP agent = Agent( instructions="You can search and retrieve web content.", tools=MCP("https://mcp.example.com/sse"), ) agent.start("Search for the latest news about AI.") - Streamable HTTP MCP server
python from praisonaiagents import Agent, MCP # Streamable HTTP endpoint — auto-detected from the URL agent = Agent( name="RemoteToolAgent", instructions="Use the remote MCP tools to answer the user.", tools=MCP( "https://api.example.com/mcp", headers={"Authorization": "Bearer YOUR_TOKEN"}, timeout=60, ), ) agent.start("List the tools you have and use them to check the current weather in London.")Requires a compatible
mcppackage. Installpraisonaiagents[mcp](which now pinsmcp>=1.20.0,<2) withpip install "praisonaiagents[mcp]". If you already havemcp2.x installed, downgrade withpip install "mcp<2"—mcp2.0.0 removedstreamablehttp_clientand the Streamable HTTP transport will not work on it (PraisonAI #5706).Pass the full endpoint URL your server exposes. Bare-host URLs are no longer rewritten to
/mcp(see PraisonAI #3032). - Multiple MCP servers
python from praisonaiagents import Agent, MCP filesystem_tools = MCP("npx -y @modelcontextprotocol/server-filesystem /home/user") database_tools = MCP("npx -y @modelcontextprotocol/server-sqlite /db/app.sqlite") agent = Agent( instructions="You manage files and database records.", tools=[*filesystem_tools, *database_tools], ) agent.start("Read the config file and update the database with its settings.")
Three equivalent forms
MCP(...) accepts the command in three equivalent forms — pick whichever reads best for your stdio server.
Pick the form that matches how the command reaches your code.
Form 1 — single string
from praisonaiagents import Agent, MCP
agent = Agent(
name="FS",
instructions="You manage files.",
tools=MCP("npx -y @modelcontextprotocol/server-filesystem /tmp"),
)
agent.start("List files under /tmp")Form 2 — string + args
from praisonaiagents import Agent, MCP
allowed_dirs = ["/tmp", "/var/logs"]
agent = Agent(
name="FS",
instructions="You manage files.",
tools=MCP("npx -y @modelcontextprotocol/server-filesystem", args=allowed_dirs),
)
agent.start("List files in the allowed directories")Form 3 — fully separated
from praisonaiagents import Agent, MCP
import os
agent = Agent(
name="GitHub",
instructions="You manage GitHub issues.",
tools=MCP(
command="npx",
args=["-y", "@modelcontextprotocol/server-github"],
env={"GITHUB_PERSONAL_ACCESS_TOKEN": os.environ["GITHUB_TOKEN"]},
),
)
agent.start("List my open issues")Merge rules
A multi-token string is split with shlex: the first token becomes the command, and the remaining tokens are prepended to any explicit args.
| Input | Resulting cmd | Resulting args |
|---|---|---|
MCP("npx -y @pkg /tmp") | "npx" | ["-y", "@pkg", "/tmp"] |
MCP("npx -y @pkg", args=["/tmp"]) | "npx" | ["-y", "@pkg", "/tmp"] |
MCP(command="npx", args=["-y", "@pkg", "/tmp"]) | "npx" | ["-y", "@pkg", "/tmp"] |
MCP("/usr/bin/python", args=["app.py"]) | "/usr/bin/python" | ["app.py"] |
MCP('"/opt/my app"', args=["run"]) | "/opt/my app" | ["run"] |
How It Works
| Phase | What happens |
|---|---|
| 1. Connect | MCP client connects to the server on first use |
| 2. Discover | Server reports its available tools |
| 3. Execute | Agent calls tools via the MCP protocol |
| 4. Return | Results flow back through the MCP client to the agent |
For string forms, the command is parsed with shlex before the server spawns.
Errors on construction
Since PraisonAI #4423 the stdio MCP(...) client fails closed instead of returning an empty tool list. Three errors can surface from MCP.__init__:
| Exception | When | What to do |
|---|---|---|
TimeoutError | The MCP server did not finish the handshake within the effective timeout= (60s default, 180s for cold-start launchers). | Increase timeout=, or diagnose why the subprocess never responded (Node not installed, wrong npx shim on Windows, offline -y install). |
RuntimeError: MCP initialization failed: ... | The runner surfaced an init error from the server. | Read the wrapped error; usually a wrong command, missing package, or a server that crashed on startup. On Windows, a wrapped fileno error (RuntimeError: MCP initialization failed: ... fileno) specifically meant the stdio subprocess transport could not spawn because MCPToolRunner‘s background thread used a SelectorEventLoop. Fixed in PraisonAI #5601 — upgrade praisonaiagents to pick up the automatic ProactorEventLoop switch on Windows. |
RuntimeError: MCP server produced 0 tools ... | The server initialized but listed no tools. Most common on Windows when npx is not the .cmd shim. | On Windows: MCP(command="npx.cmd", args=["-y", "@pkg"]). Or switch to a Python MCP server: MCP(command=sys.executable, args=["server.py"]). |
An explicit allowed_tools / disabled_tools filter that legitimately removes every tool is not an error and does not raise.
Pick the right recovery based on which error you see.
graph TB
Q{Which error<br/>from MCP?}
Q -->|TimeoutError| T[Raise timeout=<br/>or check Node / npx]
Q -->|RuntimeError:<br/>init failed| I[Read wrapped error<br/>fix command / package<br/><br/>Windows '... fileno' → upgrade<br/>praisonaiagents #5601]
Q -->|RuntimeError:<br/>0 tools| Z{On Windows?}
Z -->|Yes| W[Use command='npx.cmd']
Z -->|No| P[Use command=sys.executable<br/>args=['server.py']]
classDef question fill:#F59E0B,stroke:#7C90A0,color:#fff
classDef timeout fill:#8B0000,stroke:#7C90A0,color:#fff
classDef fix fill:#10B981,stroke:#7C90A0,color:#fff
class Q,Z question
class T timeout
class I,W,P fixWrap construction if you want to degrade gracefully.
from praisonaiagents import Agent, MCP
try:
tools = MCP("uvx mcp-server-time", timeout=30)
except (TimeoutError, RuntimeError) as e:
print(f"MCP unavailable: {e}")
tools = []
agent = Agent(instructions="You are a helpful assistant.", tools=tools)
agent.start("What is the current time?")Timeout
The effective init timeout depends on the command and whether you passed timeout= explicitly.
from praisonaiagents import Agent, MCP
# Cold-start launcher — no explicit timeout, effective init timeout is 180s
agent = Agent(
instructions="You manage files.",
tools=MCP("npx -y @modelcontextprotocol/server-filesystem /tmp"),
)
# Explicit timeout always wins, even for a launcher
agent = Agent(
instructions="You manage files.",
tools=MCP("npx -y @modelcontextprotocol/server-filesystem /tmp", timeout=300),
)
# Non-launcher stdio command — plain 60s default
agent = Agent(
instructions="You manage files.",
tools=MCP("python /path/to/server.py"),
)Forcing a tight timeout on a launcher raises with a copy-pasteable pre-warm hint:
from praisonaiagents import MCP
try:
tools = MCP("uvx mcp-server-time", timeout=5)
except TimeoutError as e:
print(e)
# MCP initialization timed out after 5 seconds
# (command='uvx' args=['mcp-server-time']). This looks like a first-run
# package download; pre-warm it once with `uvx mcp-server-time` (wait for
# the server to start), or pass a larger timeout, e.g. MCP(..., timeout=300).Init error
from praisonaiagents import MCP
try:
tools = MCP("python /path/does/not/exist.py", timeout=30)
except RuntimeError as e:
print(e)
# MCP initialization failed: <underlying error>
# (command='python' args=['/path/does/not/exist.py']).Timed-out calls are dropped
When a tool call exceeds timeout=, the agent gets {"error": ..., "timeout": True} and the queued call is cancelled, so it will not execute later (PraisonAI #5772). See MCP Transports → Timeouts.
Zero tools on Windows
import sys
from praisonaiagents import Agent, MCP
# Old shape that used to silently return zero tools on Windows:
# tools = MCP("npx -y @modelcontextprotocol/server-filesystem /tmp") # now raises
# Windows fix — pass the .cmd shim explicitly:
tools = MCP(command="npx.cmd", args=["-y", "@modelcontextprotocol/server-filesystem", "/tmp"])
# Or use a Python MCP server (works on every platform):
tools = MCP(command=sys.executable, args=["path/to/server.py"])
agent = Agent(instructions="Use the filesystem tools.", tools=tools)
agent.start("List the files in /tmp.")Intentional empty via filter
A filter that removes every tool is a valid empty config and does not raise.
from praisonaiagents import MCP
# Server advertises tools, but the caller disables all of them.
tools = MCP(
"npx -y @modelcontextprotocol/server-filesystem /tmp",
disabled_tools=["read_file", "write_file", "list_directory"],
)
assert list(tools) == []On Windows, see Windows SDK Quickstart → Troubleshooting. For transport details, see MCP Transports.
Application-level tool failures (isError)
An MCP server can report that a tool call completed but failed at the application level by setting isError=True on the result.
Since PR #5320, the client-side MCP runner in praisonaiagents.mcp reads CallToolResult.isError and, when it is True, returns a structured error dict instead of the raw content text:
{"error": "<server-reported error text>", "mcp_error": True}If the server reports isError=True with an empty content, the dict falls back to a stable message so retry/hook logic never sees {"error": ""} and mistakes it for a success:
{"error": "MCP tool 'get_file' reported an error (isError=True)", "mcp_error": True}Before PR #5320 the same failure was returned to the agent as an ordinary tool output string — the LLM might report the operation “succeeded” based on the wording of the error, retry policies never engaged (no exception, no error dict), and AFTER_TOOL hooks gating on ToolResult.error never fired. Now:
- Retry / circuit-breaker logic classifies the call as a failure.
AFTER_TOOLhooks fire the same way as for a native-tool structured failure.- The LLM sees a structured error, not a text answer.
Transport-level failures on remote MCP tools
Since PraisonAI PR #5849 (fixes #5848), remote MCP tools return the same structured errors as stdio, so retry, circuit breaker, and ON_RETRY engage automatically.
from praisonaiagents import Agent, MCP
agent = Agent(
name="RemoteToolAgent",
instructions="Use the remote MCP tools to answer the user.",
tools=MCP("https://api.example.com/mcp"),
)
# A transient ConnectError or timeout is retried automatically
agent.start("What's the latest reading?")Affected classes (see the SDK reference):
| Transport | Class | File |
|---|---|---|
| Streamable HTTP | HTTPStreamMCPTool | praisonaiagents/mcp/mcp_http_stream.py |
| SSE | SSEMCPTool | praisonaiagents/mcp/mcp_sse.py |
| WebSocket | WebSocketMCPTool | praisonaiagents/mcp/mcp_websocket.py |
Timeout (empty-message TimeoutError):
{"error": "MCP tool call timed out after <timeout> seconds", "timeout": True, "mcp_error": True}Any other transport failure (falls back to just the exception type when the message is empty):
{"error": "MCP tool '<name>' failed: <ExcType>: <msg>", "mcp_error": True}| Failure | Before | Now |
|---|---|---|
| Timeout | "Error: " string, treated as success | Timeout dict, retried as timeout |
httpx.ConnectError | "Error: " string, treated as success | Error dict, retried as connection_error |
httpx.ReadTimeout() | "Error: " string, treated as success | "MCP tool 'lookup' failed: ReadTimeout" |
Timed-out calls also cancel the pending future, so the abandoned request stops running.
See Tool Retry Policy, Tool Circuit Breaker, Hook Events, and MCP Transports.
MCP Server Types
Remote Streamable HTTP servers connect through the official MCP SDK client.
Common Patterns
Pattern 1 — File management agent
from praisonaiagents import Agent, MCP
agent = Agent(
name="FileManager",
instructions="You help users organize and manage their files efficiently.",
tools=MCP("npx -y @modelcontextprotocol/server-filesystem /home/user/documents"),
)
response = agent.start("Find all PDF files and create a summary list.")
print(response)Pattern 2 — Database agent
from praisonaiagents import Agent, MCP
agent = Agent(
name="DBAgent",
instructions="You query and analyze database records.",
tools=MCP("npx -y @modelcontextprotocol/server-sqlite /path/to/database.sqlite"),
)
agent.start("Show me all users who signed up in the last 30 days.")Pattern 3 — GitHub integration
from praisonaiagents import Agent, MCP
import os
agent = Agent(
name="GitHubAgent",
instructions="You help manage GitHub repositories and issues.",
tools=MCP(
"npx -y @modelcontextprotocol/server-github",
env={"GITHUB_PERSONAL_ACCESS_TOKEN": os.environ["GITHUB_TOKEN"]},
),
)
agent.start("List the 5 most recent open issues in my repository.")Debug Logs
import logging
from praisonaiagents.mcp import MCP
logging.basicConfig()
logging.getLogger("mcp-sse").setLevel(logging.DEBUG)
MCP("https://svc:pw@mcp.example.com/sse")
# DEBUG mcp-sse: Connecting to MCP server at https://[REDACTED]@mcp.example.com/sseBest Practices
Use official MCP servers
Start with the official @modelcontextprotocol npm packages for common integrations (filesystem, SQLite, GitHub, etc.). They’re well-tested and actively maintained.
Scope filesystem access
When using the filesystem MCP server, pass the narrowest directory path you need. Giving access to / or /home when you only need /app/data creates unnecessary risk.
Environment variables for credentials
Never hardcode API keys or tokens in your code. Pass them as environment variables to the MCP server via the env parameter, and source them from your environment or secrets manager.
Combine multiple servers
Agents can use tools from multiple MCP servers simultaneously. Unpack each server’s tools with *MCP(...) and combine them into a single tools list for maximum capability. Loading via load_mcp_tools auto-namespaces each server’s tools (e.g. filesystem_search, github_search) so overlapping names never collide.
Handle server availability
MCP(...) fails closed since PraisonAI #4423: a stdio server that times out, errors on init, or lists zero tools raises TimeoutError or RuntimeError at construction. Wrap the constructor in try/except (TimeoutError, RuntimeError) when your app should keep running without those tools, and log the wrapped command / args / init_error in the error message for diagnosis.