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.

python
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.

Diagram

Connect to external
MCP servers

Serve a small
tool set

Serve full capabilities /
recipes with auth

What do I want to do?

praisonaiagents[mcp]
MCP class (this page)

praisonai-code
praisonai serve mcp

praisonai-mcp
heavy host

This page covers the client layer. To serve your own agents, see the praisonai-mcp Package and The Three MCP Layers.

Diagram

MCP Flow

🤖 Agent

🔌 MCP Client

🖥️ MCP Server

🔧 Tools

Quick Start

  1. 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'.")
  2. 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.")
  3. 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 mcp package. Install praisonaiagents[mcp] (which now pins mcp>=1.20.0,<2) with pip install "praisonaiagents[mcp]". If you already have mcp 2.x installed, downgrade with pip install "mcp<2" — mcp 2.0.0 removed streamablehttp_client and 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).

  4. 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.

Diagram

Same MCP session, three ways to spell it

MCP(‘npx -y @pkg /tmp’)
all-in-one string

command=’npx’
args=[‘-y’, ‘@pkg’, ‘/tmp’]

MCP(‘npx -y @pkg’, args=[‘/tmp’])
string + args

MCP(command=’npx’,
args=[‘-y’, ‘@pkg’, ‘/tmp’])
fully separated

Pick the form that matches how the command reaches your code.

Diagram

As one string I paste from README

As a base command + list
of allowed paths / URLs

Programmatically constructed
from separate variables

How does the command
reach your code?

Form 1: MCP(‘cmd -flag arg’)

Form 2: MCP(‘cmd -flag’, args=[paths…])

Form 3: MCP(command=cmd, args=[…])

Form 1 — single string

python
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

python
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

python
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.

InputResulting cmdResulting 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

Diagram
ServerMCPAgentUserServerMCPAgentUserRequestConnect to serverInitialize + list toolsAvailable toolsTool definitionsCall toolExecute toolResultTool resultResponse
PhaseWhat happens
1. ConnectMCP client connects to the server on first use
2. DiscoverServer reports its available tools
3. ExecuteAgent calls tools via the MCP protocol
4. ReturnResults flow back through the MCP client to the agent

For string forms, the command is parsed with shlex before the server spawns.

Diagram
MCP stdio runnershlex.splitMCP.__init__Your codeMCP stdio runnershlex.splitMCP.__init__Your codeMCP(“npx -y @pkg”, args=[“/tmp”])split(“npx -y @pkg”)[“npx”, “-y”, “@pkg”]cmd = “npx”args = [“-y”, “@pkg”] + [“/tmp”]spawn(“npx”, [“-y”, “@pkg”, “/tmp”])tools discovered

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__:

ExceptionWhenWhat to do
TimeoutErrorThe 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.

Diagram
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 fix

Wrap construction if you want to degrade gracefully.

python
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.

python
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:

python
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

python
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

python
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.

python
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:

python
{"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:

python
{"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_TOOL hooks fire the same way as for a native-tool structured failure.
  • The LLM sees a structured error, not a text answer.
Diagram
MCP ServerMCPToolRunnerAgentMCP ServerMCPToolRunnerAgentalt[Transport failure (stdio)][Application failure (isError=True)][Success]call_tool(“get_file”, {…})session.call_tool(…)(queue timeout / exception){“error”: “MCP tool call timed out after N seconds”, “timeout”: True, “mcp_error”: True}CallToolResult(isError=True, content=[…]){“error”: “…”, “mcp_error”: True}CallToolResult(isError=False, content=[…])“<result text>”

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.

python
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):

TransportClassFile
Streamable HTTPHTTPStreamMCPToolpraisonaiagents/mcp/mcp_http_stream.py
SSESSEMCPToolpraisonaiagents/mcp/mcp_sse.py
WebSocketWebSocketMCPToolpraisonaiagents/mcp/mcp_websocket.py

Timeout (empty-message TimeoutError):

python
{"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):

python
{"error": "MCP tool '<name>' failed: <ExcType>: <msg>", "mcp_error": True}
FailureBeforeNow
Timeout"Error: " string, treated as successTimeout dict, retried as timeout
httpx.ConnectError"Error: " string, treated as successError 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.

Diagram

stdio

HTTP / SSE / WebSocket

🤖 Agent calls MCP tool

Which transport?

Runner: timeout dict, isError dict

Wrapper: structured error dict

Crash or dead runner: not retried

🔁 Retry policy + circuit breaker + ON_RETRY

✅ Recovered or reported

See Tool Retry Policy, Tool Circuit Breaker, Hook Events, and MCP Transports.


MCP Server Types

Diagram

Command line

Remote HTTP endpoint

Legacy SSE

What transport does the server use?

stdio\nnpx / python command

Streamable HTTP\nUnsupported markdown: link

SSE\nUnsupported markdown: link

Remote Streamable HTTP servers connect through the official MCP SDK client.

Diagram
Remote MCP ServerMCP HTTP-Stream ClientAgentUserRemote MCP ServerMCP HTTP-Stream ClientAgentUser“Check the weather in London”streamablehttp_client(url, headers, timeout)HTTP POST /mcp (initialize)session established (SSE stream)tool listcall weather_tool(location=”London”)HTTP POST /mcp (tools/call)resulttool result“London is 12°C, cloudy…”

Common Patterns

Pattern 1 — File management agent

python
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

python
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

python
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

python
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/sse

Best 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.


Related