MCP servers' tools
The Model Context Protocol is how tool servers are shared. CriogenAI's MCP client gives an agent the tools of any MCP server: a process your app starts (stdio) or a server at a URL (Streamable HTTP). The client runs in the Rust engine, so it behaves the same in every language.
Use a server#
# A server this app starts (stdio): any MCP server, in any language.
files = await mcp.stdio("npx", ["-y", "@modelcontextprotocol/server-filesystem", "./notes"], prefix="files")
# A server at a URL (Streamable HTTP), with a token in a header.
docs = await mcp.http(
"https://mcp.example.com/mcp",
headers={"Authorization": f"Bearer {os.environ['DOCS_MCP_TOKEN']}"},
prefix="docs",
)
agent = Agent(
"openai:gpt-5-mini",
tools=[files, docs.tool("docs_search")], # a server = all its tools, or pick some
approve=["files_write_file"], # MCP tools work with approvals like any tool
)
result = await agent.run("Summarize what ./notes says about the Q3 launch.")
print(result.text)
await files.close()
await docs.close()// A server this app starts (stdio): any MCP server, in any language.
const files = await mcp.stdio("npx", ["-y", "@modelcontextprotocol/server-filesystem", "./notes"], { prefix: "files" });
// A server at a URL (Streamable HTTP), with a token in a header.
const docs = await mcp.http("https://mcp.example.com/mcp", {
headers: { Authorization: `Bearer ${process.env.DOCS_MCP_TOKEN}` },
prefix: "docs",
});
const agent = new Agent({
model: openai("gpt-5-mini"),
tools: [files, docs.tool("docs_search")], // a server = all its tools, or pick some
approve: ["files_write_file"], // MCP tools work with approvals like any tool
});
const result = await agent.run("Summarize what ./notes says about the Q3 launch.");
console.log(result.text);
await files.close();
await docs.close();use criogenai_agent::{Agent, mcp::McpServer};
use criogenai_providers::{Endpoint, ProviderModel};
// A server this app starts (stdio): any MCP server, in any language.
let files = McpServer::stdio(
"npx",
["-y", "@modelcontextprotocol/server-filesystem", "./notes"],
)
.prefix("files")
.connect()
.await?;
// A server at a URL (Streamable HTTP), with a token in a header.
let token = std::env::var("DOCS_MCP_TOKEN")?;
let docs = McpServer::http("https://mcp.example.com/mcp")
.header("Authorization", format!("Bearer {token}"))
.prefix("docs")
.only(["search"])
.connect()
.await?;
let model = ProviderModel::new(
Endpoint::openai(std::env::var("OPENAI_API_KEY")?),
"gpt-5-mini",
)?;
let agent = Agent::builder(model)
.tools(files.tools())
.tools(docs.tools())
.build()?;
let result = agent
.run("Summarize what ./notes says about the Q3 launch.")
.await?;
println!("{}", result.text().unwrap_or_default());
files.close().await;- Pass a server to
toolsfor all its tools, or pick some withserver.tool(name)(Rust:.only([...])beforeconnect). prefixkeeps two servers apart: the agent seesfiles_read_file. Names are also mapped to what model APIs accept (.becomes_, at most 64 characters); the server still gets its own name. Two tools that would map to the same name fail withmcp_tool_name.- MCP tools are ordinary tools: they work with approvals, timeouts, limits, hooks, streaming and the audit trail.
- A stdio server inherits your environment plus
env. If it exits, the next call starts it again.close()stops it (stdin closed, killed after 2 s). - In the browser (
criogenai/web),mcp.httpworks;mcp.stdiothrowsunsupported.
Call a tool yourself#
async with await mcp.stdio("uvx", ["mcp-server-time"]) as time:
print([t.name for t in time.tools], time.info["protocol_version"])
print(await time.call("get_current_time", {"timezone": "Europe/Lisbon"}))const time = await mcp.stdio("uvx", ["mcp-server-time"]);
console.log(time.tools.map((t) => t.name), time.info.protocolVersion);
console.log(await time.call("get_current_time", { timezone: "Europe/Lisbon" }));
await time.close();A call returns the tool's text, its JSON value (structuredContent), or its
content blocks. A tool that reports a failure (isError) raises a
ToolError with its message: inside an agent, the model sees it and can try
again.
What a server can and cannot do#
The client uses tools only. A server cannot sample your model, read your files or ask your users anything through CriogenAI. What it says is data:
- Its behaviour hints decide nothing by default.
destructiveHintis kept (it only makes CriogenAI more careful);readOnlyHintandidempotentHintwould make failed calls retried, so they count only withtrust_annotations/trustAnnotations. - Every message read is limited in size (4 MiB by default), every call in time (60 s), the number of tools (1 000) and the depth and size of their schemas are capped, and descriptions are cut to 8 000 characters.
- Header and environment values (tokens) never appear in an error, a log or
a
repr. Headers go overhttps, or plainhttponly to this computer.
Versions#
CriogenAI speaks MCP 2026-07-28 (stateless, server/discover) and falls
back to the initialize handshake for servers from 2025-11-25 and earlier,
so current and older servers both work. Not supported yet: resources,
prompts, sampling, elicitation, OAuth sign-in (pass a token in a header),
and serving your own tools as an MCP server.
Errors#
| Code | When |
|---|---|
mcp_connect | The server could not be started or reached, or refused the credentials. |
mcp_protocol | It answered something the client cannot use, or a version CriogenAI does not speak. |
mcp_tool_name | Two of its tools map to the same name: set a prefix or leave one out. |
mcp_tool_schema | A tool's input schema is not an object schema, or is too large or deep. |
mcp_timeout | It was not ready within the connect timeout (30 s). |
mcp_closed | It stopped, or the server object was closed. |