MCP¶
Model Context Protocol (MCP) lets an Agent
use tools supplied by servers, such as filesystems, browsers, and databases.
The MCP plugin connects to a server and converts those capabilities into
ordinary Tools.
The plugin supports the MCP Python SDK 1.18+ and 2.x.
from lovia import Agent
from lovia.plugins.mcp import MCP, MCPServerStdio
agent = Agent(
name="assistant",
model="<model>",
plugins=[
MCP(MCPServerStdio(name="web", command="uvx", args=["mcp-server-fetch"]))
],
)
The mcp dependency is imported only when a connection is actually made,
so lovia.plugins.mcp is always safe to import; a missing package raises
UserError with the install hint at open time.
Servers¶
Two transports, both frozen keyword-only configs:
MCPServerStdio(command="uvx", args=["mcp-server-fetch"], env=None, name="web")
MCPServerStreamableHTTP(url="https://mcp.example.com/mcp", headers=None, name="api")
Shared options (on either config):
| Option | Default | Effect |
|---|---|---|
name |
None |
prefixes the server's tools: name="web" → web__fetch |
include_tools / exclude_tools |
None |
allowlist / denylist of raw tool names |
needs_approval |
False |
bool or predicate — gates every tool from this server through the normal approval flow |
retries / timeout / max_output_chars / result_renderer |
None |
per-tool policies, applied to each converted tool (Tools) |
auto_reconnect |
True |
reopen a dead connection and retry the call once |
close_after_run |
True |
close the connection when the run ends |
MCP(a, b, ...) takes any number of servers; prefixes keep their tool
names from colliding (a clash is reported at run start like any other
duplicate tool name).
Web research with Parallel¶
Parallel Search MCP
provides web search and page fetching over Streamable HTTP, with no account or
API key required for its free tier. Anonymous search uses Fast mode; free usage
has rate limits. Install lovia[mcp] as above and configure your
model environment variables, then run this
as a Python script:
from lovia import Agent, model_from_env
from lovia.plugins.mcp import MCP, MCPServerStreamableHTTP
server = MCPServerStreamableHTTP(
name="parallel",
url="https://search.parallel.ai/mcp",
headers={"User-Agent": "lovia-parallel-mcp"},
)
agent = Agent(
name="researcher",
model=model_from_env(),
instructions="Search for sources, fetch relevant pages, and cite their URLs.",
plugins=[MCP(server)],
)
result = agent.run_sync(
"Find the latest stable Python release and summarize its official release notes."
)
print(result.output)
The plugin exposes parallel__web_search and parallel__web_fetch. Search finds
candidate sources; fetch reads selected URLs. Only agents given this plugin use
the server. From async code, use await Runner.run(agent, ...) instead of
run_sync().
Connection lifecycle¶
Per-run (default). Passing a server config means each run opens the
connection in plugin setup() and closes it at run end — stateless and
robust, at the cost of a subprocess/handshake per run.
Persistent. For many runs against one server, open a session yourself
and pass the live connection — MCPServerLike is satisfied by both configs
and connections:
server = MCPServerStdio(name="web", command="uvx", args=["mcp-server-fetch"])
async with server.session() as conn: # opened once
agent = Agent(name="assistant", model="<model>", plugins=[MCP(conn)])
await Runner.run(agent, "Fetch https://example.com and summarize it.")
await Runner.run(agent, "Now fetch the RFC index.") # same connection
The run never closes a connection you opened (close_after_run is False
on a live MCPConnection); its lifetime is the async with block. One
persistent connection serves sequential runs — concurrent runs over a
single MCP session are unsupported; give each concurrent worker its own.
How MCP tools behave¶
- Tool schemas are normalized into ordinary lovia
Tools (normalize_schemarepairs loose ones) — they validate, render, truncate, and appear in streaming events exactly like native tools. A customresult_rendereron the server receives the rawMCPToolResult; the default rendering isrender_mcp_content. - Failures split in two. A protocol-level tool failure (the server
answered with
isError) is rendered back to the model as[tool error] ...so it can self-correct — not raised. A transport/connection failure raisesMCPError(carryingtool_name), which ends the call like any tool exception. - Tool results may carry resources: text is inlined; images/audio become
size-stamped placeholders (never raw base64); resource links become
[resource link: uri]lines. - Scope is deliberately tools-only. MCP prompts, resource browsing, sampling, OAuth, and subscriptions are non-goals; the plugin does one thing.
Deferring large servers¶
A server with dozens of tools costs its full schema catalog in every prompt,
even when the model uses none of them. Set defer=True and the server's
tools stay out of the tool list; the agent instead gets one shared pair —
search_mcp_tools (keyword lookup returning name, description, and
parameters schema) and call_mcp_tool (invoke by exact name) — plus one
system-prompt line naming the deferred tools, so the model knows they exist
without paying for their schemas:
plugins=[MCP(
MCPServerStdio(command="...", name="github", defer=True), # 60 tools
MCPServerStdio(command="..."), # 3 tools, direct
)]
Deferral changes when schemas enter the context, not how tools run: a
deferred call is fully delegated, so the server's needs_approval,
retries, timeout, result_renderer, and max_output_chars apply
exactly as if the tool were exposed directly. Names must stay unambiguous —
a deferred name colliding with another deferred or exposed tool in the same
plugin raises UserError at setup (give servers a name= prefix).
Sharp edges¶
auto_reconnectmeans at-least-once. A call that died mid-flight is retried once on a fresh connection — a non-idempotent side effect (create_ticket) can happen twice. Setauto_reconnect=Falseon servers with mutating tools, and let the model see the error instead.- MCP tools run in parallel by default, like every tool. A server
whose tools mutate shared state has no barrier protection — wrap the
risky ones via
include_toolssplit across two server entries, or gate them withneeds_approval. needs_approvalis per server, not per tool. Splitting one server into twoMCPServerentries (same command, differentinclude_tools) is the idiom for "read tools free, write tools gated".- stdio servers inherit your process environment unless you pass
env=; there is nocwdoption — launch via a wrapper script when a server needs a working directory.
See also¶
- Plugins — the mechanism underneath
- Tools — everything a converted MCP tool inherits
- Tool approval — gating server tools
- Example:
24_mcp.py