A lightweight Rust SDK for implementing Model Context Protocol (MCP) servers on the Internet Computer.
This SDK is specifically designed for the IC canister runtime, using the Streamable HTTP transport and focusing on the core tools capability. It allows developers to quickly expose canister functions as MCP tools for AI models to interact with.
We support the llms.txt convention for making documentation available to large language models and the applications that make use of them.
- llms/llms-full.txt : complete documentation for SDK
- Protocol Version: Implements the
2025-03-26&2025-06-18MCP specification versions. - Target Runtime: Built exclusively for the Internet Computer (no
tokiodependency). - Transport: Supports the official Streamable HTTP transport.
- Capabilities:
- ✅
tools(tools/list,tools/call)
- ✅
- Utilities:
- ✅
ping
- ✅
- No maintained sessions. Also no two-way communication between server and client. You should be aware of HTTP response size limitation on IC environment when designing and implementing tools.
- Your API key can be seen by nodes in subnet
[dependencies]
ic-rmcp = { git = "https://github.com/ByteSmithLabs/ic-rmcp", tag = "v0.3.0" }
Create a struct for your server and implement the ic_rmcp::Handler trait. This is where you define your server's logic for listing and calling tools.
The SDK provides default implementations, so you only need to override the methods you want to support.
use ic_rmcp::{model::*, schema_for_type, Error, Handler, Server, Context};
struct MyServer;
impl Handler for MyServer {
fn get_info(&self, _: Context) -> ServerInfo {
ServerInfo {
capabilities: ServerCapabilities::builder().enable_tools().build(),
server_info: Implementation {
name: "My MCP server".to_string(),
version: "1.0.0".to_string(),
},
..Default::default()
}
}
async fn list_tools(&self,_: Context, _: Option<PaginatedRequestParam>) -> Result<ListToolsResult, Error> {
Ok(ListToolsResult {
next_cursor: None,
tools: vec![
Tool::new(
"foo",
"A foo tool",
schema_for_type::<EmptyObject>(),
)
],
})
}
async fn call_tool(&self,_: Context, requests: CallToolRequestParam) -> Result<CallToolResult, Error> {
match requests.name.as_ref() {
"foo" => {
Ok(CallToolResult::success(
Content::text("Call foo tool successfully").into_contents(),
))
}
_ => Err(Error::invalid_params("not found tool", None)),
}
}
}Use the standard http_request and http_request_update canister endpoints. The Server trait is automatically implemented on your Handler, giving you access to the appropriate handle method. See more at HTTP Gateway on Internet Computer
use ic_cdk::{init, query, update};
use ic_http_certification::{HttpRequest, HttpResponse, StatusCode};
// A constant for a simple API key auth
const API_KEY: &str = "a-secret-api-key";
// Tool results are dynamic. Hence need subnet concensus.
#[query]
fn http_request(_: HttpRequest) -> HttpResponse {
HttpResponse::builder()
.with_status_code(StatusCode::OK)
.with_upgrade(true)
.build()
}
#[update]
async fn http_request_update(req: HttpRequest<'_>) -> HttpResponse<'_> {
MyServer {}
.handle(&req, |headers| -> bool {
headers
.iter()
.any(|(k, v)| k == "x-api-key" && *v == API_KEY.with_borrow(|k| k.clone()))
})
.await
}About OAuth: See our Clock MCP server example to learn about how to set up your MCP server with OAuth.
Access your MCP server after deployment at: https://<CANISTER_ID>.icp0.io/mcp