MCP Tools
An MCP server offers tools over a standard protocol. Give the agent a server and its tools sit beside your local tools: the model sees one set of tools, and the runtime routes each call to where it lives. By the end of this page you will have written an MCP server, connected an agent to it, and read which server answered each call. OmniCoreAgent uses the official MCP Python SDK 2.x (mcp>=2.2.0,<3, installed
with it) and negotiates protocol version 2025-11-25.
Every output on this page is what the code printed when it was run. The
model’s wording will differ on your run.
Your first MCP server and agent
1
Write a server
A stdio server is one file. Save it as In
notes_server.py:mcp 2.x the server class is MCPServer; 1.x called it FastMCP.2
Connect an agent
Save this next to it and run it from that folder:Each call names its provider (
mcp) and its server. The last line is from
the run’s header, which records every configured server’s state when the
run started.How it works
- Connecting.
connect_mcp_servers()starts every stdio server and connects every HTTP server at once. Each does the MCP handshake and lists all its tools (every page) withinconnect_timeout. A server that fails does not stop the others: its reason is recorded, and the agent runs without its tools. OmniServe connects on startup. - The name is the identity. The
nameyou configure is what routing, governance, telemetry and readiness use. What a server reports about itself (its name and version) is kept as metadata only, since the server controls it. A server without anamegets a stable one derived from its command or host, such aspython_c51c4f; naming each server is clearer. - Calling. A call goes to the server that offered the tool, under
call_timeoutif you set one and always under the agent’stool_call_timeout. If two servers offer a tool with the same name, the model is shown each under its own unambiguous alias. - Closing.
agent.cleanup()(orcleanup_mcp_servers()) closes every connection and stops stdio server processes.
What the model receives
- Structured content, when the server returns it, is the result. A text
block that only repeats it is dropped; other blocks, such as an image, are
kept beside it. A scalar the server wrapped as
{"result": value}is returned as the value. A Python server sends structured content when the tool’s return type is typed (aTypedDict, a Pydantic model,dict[str, int],int);note_statsabove is annotated as a baredict, so the SDK sent it as JSON text instead. - A single text block is returned as text; several blocks as a list, with
their MCP field names (such as
mimeType). - A tool that reports failure (
isError) is an error result. - A protocol error is an error result that keeps its JSON-RPC code, such
as
MCP error -32602: ...for invalid arguments, orMCP error -32001whencall_timeoutexpires. The code is also in the result’s data asmcp_error.
When a connection drops
If a session drops mid-run (a stdio server exits, or an HTTP server restarts and no longer knows the session), the next call reconnects once and is retried. If reconnecting fails, the call returns an error naming both causes, and the server is marked disconnected with its last error. Each reconnect is recorded on the tool call it affected.Remote servers: Streamable HTTP and SSE
The same server can serve HTTP. Replace the last line ofnotes_server.py
with server.run("streamable-http", port=8765), start it, and point the agent
at its URL:
connect_mcp_servers(), its entry in the run’s header read:
streamable-http, the spelling the MCP SDK uses, is accepted too. An SSE
server takes the same shape with "transport_type": "sse", its URL (usually
ending in /sse), and optionally sse_read_timeout.
OAuth
With"auth": {"method": "oauth"} on an HTTP server, the MCP SDK runs the
OAuth authorization code flow with PKCE: server metadata discovery, dynamic
client registration, and token exchange. When a login is needed, OmniCoreAgent
starts a small callback server on 127.0.0.1, opens the authorization URL in
the browser, and waits for the redirect without blocking other work.
When the authorization server includes its issuer (
iss) in the redirect, it
is checked, so a redirect claiming a different authorization server is
rejected. Tokens are kept in memory for the life of the process.
Options
Each server’s settings are checked when the agent is created. A setting that does nothing for its transport, an unknown setting, a wrong type or a non-positive timeout raises aValueError naming the server and the setting.
mcp_tools is an argument of OmniCoreAgent itself; see the
OmniCoreAgent reference for the agent’s
MCP methods.
Governance
With governance on, a server is authorized by its configured name before any process starts or connection opens (capabilitymcp.server.start for stdio,
mcp.server.connect for HTTP), and each call is authorized as tool.mcp.call
with the target mcp_server and tool_name.
What the built-in profiles do with MCP:
Connecting happens outside any run, so an ask there cannot pause: it fails
with
ApprovalRequiredError. An ask also outranks an allow rule you add. For a
server you trust, remove those asks and allow it by name:
Readiness and telemetry
- The run’s header (
trajectory["harness"]["mcp_servers"]) lists every configured server with its status (connected,disconnected,failedornot_connected), reported name and version, protocol version, tool count, reconnects and last error. It never includes commands, arguments, environment, URLs or headers, which can carry credentials. - Each MCP call in the trajectory names its
provider(mcp) andserver, and lists any reconnect. See Observability. - OmniServe’s
GET /readyreportsmcp_connected(every configured server connected) andmcp_servers, each server’s status and last error, so one failed server among several is visible (OmniServe).
When things go wrong
The agent does not use the server's tools
The agent does not use the server's tools
Either Run the server’s command yourself to see why it exits. A relative path in
connect_mcp_servers() was not called, or the server failed to
connect. connect_mcp_servers() does not raise for a server that fails;
the run goes on without its tools, and its header says why. With a
misspelt script name (notez_server.py), Python’s own
can't open file ... No such file or directory went to stderr, and
printing the status, the response and trajectory["harness"]["mcp_servers"]
gave:args is resolved from the working directory, or from cwd if you set it.ValueError: MCP server 'notes': ...
ValueError: MCP server 'notes': ...
A setting is missing, unknown, or does not apply to the transport. These
are raised when the agent is created:Two servers with the same
name raise
ValueError: Duplicate MCP tool names: notes.ApprovalRequiredError: Matched ask policy rule.
ApprovalRequiredError: Matched ask policy rule.
Governance is on with
interactive-dev, which asks before starting a
server, and there is no run to pause while connecting, so
connect_mcp_servers() raises. Allow the server by name, as in
Governance.The server's process cannot find its API key
The server's process cannot find its API key
A stdio server does not inherit your environment: it gets a small safe
baseline. Pass what it needs explicitly with
"env": {"NAME": value}.Next
Local tools
Your Python functions, beside the server’s tools.
Many tools
Servers with many tools: let the model search them.
Security model
What a policy can decide, and what it cannot.
Serve it
Connect servers at startup, and report them in
/ready.