Cosmo Realtime SDK
Capabilities

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.

CodeRaised when
not_a_filethe config path is not a file
cannot_readthe file exists but cannot be read
invalid_jsonthe text does not parse as JSON
missing_serversthe document parses but has no mcpServers object
invalid_server_entrya server's value is not an object
missing_commanda server has no command
invalid_argsargs is not an array of strings and whole numbers
invalid_envenv is not an object of string values
invalid_cwdcwd is not a string
duplicate_server_nametwo servers share a name
extra_not_installedPython only — mcp= was used without pip install 'cosmo-ai-sdk[mcp]'
connection_faileda connected server stopped answering — its subprocess died, or the connection dropped or timed out
invalid_responsethe server replied with a payload the SDK cannot use
server_errorthe server returned a JSON-RPC error
tool_errora 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.

On this page