MCP servers
Give the agent tools from any Model Context Protocol server — filesystem, databases, SaaS connectors — without writing handlers.
The Model Context Protocol is an open standard for exposing tools to AI agents. If a capability already exists as an MCP server — file system access, a database, an internal service — you can attach it to a realtime agent directly instead of hand-writing client tools.
MCP is available in the Python and Swift SDKs today (stdio servers — the SDK spawns the server as a subprocess and proxies calls). TypeScript doesn't ship MCP support yet; in the browser, expose the capability as a server tool or client tool instead.
Attach servers (Python)
Install the extra — pip install 'cosmo-ai-sdk[mcp]' — then pass a config file or inline server definitions:
from pathlib import Path
from cosmo_ai import RealtimeClient
client = RealtimeClient(api_key=os.environ["COSMO_API_KEY"])
agent = client.agent(
instructions="You can use the connected MCP tools to help the user.",
mcp=Path("./mcp.json"), # standard .mcp.json format
){
"mcpServers": {
"files": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "./data"]
}
}
}Or define servers in code with McpStdioServer:
from cosmo_ai.mcp import McpStdioServer
files = McpStdioServer(
name="files",
command="npx",
args=["-y", "@modelcontextprotocol/server-filesystem", "./data"],
)
agent = client.agent(instructions="…", mcp=[files])At agent.start() the SDK launches each server, lists its tools, and registers them for the session. Servers stay alive for the whole session — including across reconnects — and are shut down at session end.
Attach servers (Swift)
Swift takes the same servers through the same argument. A config file arrives through .configFile(_:), and inline servers compose with it using +:
let client = try RealtimeClient()
// From a .mcp.json …
let agent = try client.agent(mcp: .configFile(configURL))
// … inline, or both together.
let agent = try client.agent(mcp: [
McpStdioServer(name: "files", command: "npx", args: ["-y", "@modelcontextprotocol/server-filesystem", "./data"])
])
let agent = try client.agent(mcp: .configFile(configURL) + [inlineServer])The MCPExample target in the HelloRealtime example is a complete program that loads .mcp.json and proxies calls.
Use namespaced tool names
MCP tools join the session's tool surface under mcp__<server>__<tool> — a file system server named files contributes mcp__files__read_file, and that's the name you'll see in tool-call events and hook matchers:
@hooks.pre_tool_use(matcher="mcp__files__write_*")
def read_only(ctx):
return PreToolUseResult(permission="deny", reason="filesystem is read-only in calls")The namespacing prevents collisions between servers and makes provenance obvious in logs and tool timelines.
Write argument values as strings
A whole number in args is accepted and passed through in decimal, so an unquoted port works:
{ "mcpServers": { "srv": { "command": "srv", "args": ["--port", 8080] } } }Anything else numeric is rejected with invalid_args. A fractional number and an integer beyond 64 bits have no spelling the SDKs agree on — 1.0 and 1 are indistinguishable once decoded, and a larger integer reaches the process in scientific notation. Quote the value and the text passes through untouched.
Errors
Every MCP failure raises McpError, and its code names which one it was. Misconfiguration fails fast — when the agent is built (client.agent(...)), not at start() and not mid-call:
from cosmo_ai.mcp import McpError, McpErrorCode
try:
agent = client.agent(mcp="./mcp.json")
except McpError as err:
if err.code is McpErrorCode.NOT_A_FILE:
...do {
let agent = try client.agent(mcp: .configFile(url))
} catch let err as McpError where err.code == .missingCommand {
...
}Match on code; message is written for a human and is not part of the contract.
| Code | Raised when |
|---|---|
not_a_file | the config path is not a file |
cannot_read | the file exists but cannot be read |
invalid_json | the text does not parse as JSON |
missing_servers | the document parses but has no mcpServers object |
invalid_server_entry | a server's value is not an object |
missing_command | a server has no command |
invalid_args | args is not an array of strings and whole numbers |
invalid_env | env is not an object of string values |
invalid_cwd | cwd is not a string |
duplicate_server_name | two servers share a name |
extra_not_installed | Python only — mcp= was used without pip install 'cosmo-ai-sdk[mcp]' |
connection_failed | a connected server stopped answering — its subprocess died, or the connection dropped or timed out |
invalid_response | the server replied with a payload the SDK cannot use |
server_error | the server returned a JSON-RPC error |
tool_error | a tool reported a failure |
Handle a server that fails to start
Startup is isolated per server: if one cannot be launched, or fails to initialize or list its tools, the SDK logs a warning and continues with the rest. The session starts without that server's tools rather than failing — one bad entry in a shared .mcp.json doesn't take the call down with it. Nothing is thrown, so there is no McpError to catch; check your logs for realtime.mcp.server_connect_failed if a tool you expected is missing.
The one startup failure that does propagate is a missing [mcp] extra in Python, since no server can start without it.
Once a server is connected, failures do reach the caller: a server that dies mid-session fails its tools' calls with connection_failed, surfaced to the model as tool errors, without ending the session.
MCP servers run with your process's permissions, and their tools run whatever the model asks within the server's capabilities. Attach servers you trust, scope them narrowly (for example, point a file system server at one directory), and use PreToolUse hooks for guarantees the server itself doesn't provide.