#wasi #mcp #browser

turbomcp-wasm

WebAssembly bindings for TurboMCP - MCP client for browsers and WASI environments

6 releases

Uses new Rust 2024

3.0.0-exp.2 Jan 13, 2026
3.0.0-exp Jan 12, 2026
3.0.0-beta.3 Jan 22, 2026
3.0.0-beta.2 Jan 21, 2026
3.0.0-beta.1 Jan 19, 2026

#157 in WebAssembly

MIT license

305KB
6K SLoC

turbomcp-wasm

WebAssembly bindings for TurboMCP - MCP client and server for browsers, edge environments, and WASI runtimes.

Features

Client (Browser & WASI)

  • Browser Support: Full MCP client using Fetch API and WebSocket
  • Type-Safe: All MCP types available in JavaScript/TypeScript
  • Async/Await: Modern Promise-based API
  • Small Binary: Optimized for minimal bundle size (~50-200KB)

Server (wasm-server feature)

  • Edge MCP Servers: Build MCP servers running on Cloudflare Workers and other edge platforms
  • Ergonomic API: Just write async functions - inspired by axum's IntoResponse pattern
  • Type-Safe Handlers: Automatic JSON schema generation from Rust types
  • Idiomatic Error Handling: Full ? operator support
  • Zero Tokio: Uses wasm-bindgen-futures for async, no tokio runtime needed
  • Full MCP Protocol: Tools, resources, prompts, and all standard MCP methods

Installation

Client (NPM)

npm install turbomcp-wasm

Server (Rust)

[dependencies]
turbomcp-wasm = { version = "3.0", default-features = false, features = ["wasm-server"] }
worker = "0.7"
serde = { version = "1.0", features = ["derive"] }
schemars = "1.0"
getrandom = { version = "0.3", features = ["wasm_js"] }

Client Usage

Browser (ES Modules)

import init, { McpClient } from 'turbomcp-wasm';

async function main() {
  // Initialize WASM module
  await init();

  // Create client
  const client = new McpClient("https://api.example.com/mcp")
    .withAuth("your-api-token")
    .withTimeout(30000);

  // Initialize session
  await client.initialize();

  // List available tools
  const tools = await client.listTools();
  console.log("Tools:", tools);

  // Call a tool
  const result = await client.callTool("my_tool", {
    param1: "value1",
    param2: 42
  });
  console.log("Result:", result);
}

main().catch(console.error);

TypeScript

import init, { McpClient, Tool, Resource, Content } from 'turbomcp-wasm';

async function main(): Promise<void> {
  await init();

  const client = new McpClient("https://api.example.com/mcp");
  await client.initialize();

  const tools: Tool[] = await client.listTools();
  const resources: Resource[] = await client.listResources();
}

Server Usage (Cloudflare Workers)

Basic Server - Ergonomic API

use turbomcp_wasm::wasm_server::*;
use worker::*;
use serde::Deserialize;

#[derive(Deserialize, schemars::JsonSchema)]
struct HelloArgs {
    name: String,
}

#[derive(Deserialize, schemars::JsonSchema)]
struct AddArgs {
    a: i64,
    b: i64,
}

// Just write async functions - return any type implementing IntoToolResponse!
async fn hello(args: HelloArgs) -> String {
    format!("Hello, {}!", args.name)
}

async fn add(args: AddArgs) -> String {
    format!("{}", args.a + args.b)
}

#[event(fetch)]
async fn fetch(req: Request, _env: Env, _ctx: Context) -> Result<Response> {
    let server = McpServer::builder("my-mcp-server", "1.0.0")
        .description("My MCP server running on Cloudflare Workers")
        .tool("hello", "Say hello to someone", hello)
        .tool("add", "Add two numbers", add)
        .build();

    server.handle(req).await
}

With Error Handling

use turbomcp_wasm::wasm_server::*;

#[derive(Deserialize, schemars::JsonSchema)]
struct DivideArgs {
    a: f64,
    b: f64,
}

// Use Result for error handling - errors automatically become tool errors
async fn divide(args: DivideArgs) -> Result<String, ToolError> {
    if args.b == 0.0 {
        return Err(ToolError::new("Cannot divide by zero"));
    }
    Ok(format!("{}", args.a / args.b))
}

// Use the ? operator for automatic error propagation
async fn fetch_data(args: FetchArgs) -> Result<Json<Data>, ToolError> {
    let response = fetch(&args.url).await?;  // ? just works!
    let data: Data = response.json().await?;
    Ok(Json(data))
}

Return Type Flexibility

use turbomcp_wasm::wasm_server::*;

// Return String
async fn simple(args: Args) -> String {
    "Hello!".into()
}

// Return JSON
async fn json_response(args: Args) -> Json<MyData> {
    Json(MyData { value: 42 })
}

// Return Result with automatic error handling
async fn fallible(args: Args) -> Result<String, ToolError> {
    let data = some_operation()?;
    Ok(format!("Got: {}", data))
}

// Return ToolResult for full control
async fn full_control(args: Args) -> ToolResult {
    ToolResult::text("Direct response")
}

// Return () for empty success
async fn void_response(_: Args) -> () {
    // Do something with side effects
}

// Return Option
async fn optional(args: Args) -> Option<String> {
    if args.enabled {
        Some("Enabled".into())
    } else {
        None  // Returns "No result"
    }
}

With Resources and Prompts

use turbomcp_wasm::wasm_server::*;
use worker::*;

#[event(fetch)]
async fn fetch(req: Request, _env: Env, _ctx: Context) -> Result<Response> {
    let server = McpServer::builder("full-server", "1.0.0")
        // Tools - ergonomic API
        .tool("search", "Search the database", search_handler)

        // Static resource
        .resource(
            "config://settings",
            "Application Settings",
            "Current application configuration",
            |_uri| async move {
                ResourceResult::json("config://settings", &serde_json::json!({
                    "theme": "dark",
                    "language": "en"
                }))
            },
        )

        // Dynamic resource template
        .resource_template(
            "user://{id}",
            "User Profile",
            "Get user profile by ID",
            |uri| async move {
                let id = uri.split('/').last().unwrap_or("unknown");
                Ok(ResourceResult::text(&uri, format!("User {}", id)))
            },
        )

        // Prompt with no arguments
        .prompt_no_args(
            "greeting",
            "Generate a greeting",
            || async move {
                PromptResult::user("Hello! How can I help?")
            },
        )

        .build();

    server.handle(req).await
}

API Reference

Client Methods

Method Description
withAuth(token: string) Add Bearer token authentication
withHeader(key: string, value: string) Add custom header
withTimeout(ms: number) Set request timeout
initialize() Initialize MCP session
isInitialized() Check if session is initialized
getServerInfo() Get server implementation info
getServerCapabilities() Get server capabilities
listTools() List available tools
callTool(name: string, args?: object) Call a tool
listResources() List available resources
readResource(uri: string) Read a resource
listResourceTemplates() List resource templates
listPrompts() List available prompts
getPrompt(name: string, args?: object) Get a prompt
ping() Ping the server

Server Builder Methods

Method Description
builder(name, version) Create new server builder
description(text) Set server description
instructions(text) Set server instructions
tool(name, desc, handler) Register tool (ergonomic API)
tool_no_args(name, desc, handler) Register tool without arguments
tool_raw(name, desc, handler) Register tool with raw JSON args
resource(uri, name, desc, handler) Register static resource
resource_template(uri, name, desc, handler) Register resource template
prompt(name, desc, handler) Register prompt with typed args
prompt_no_args(name, desc, handler) Register prompt without args
build() Build the server

Return Types (IntoToolResponse)

Type Behavior
String, &str Returns as text content
Json<T> Serializes to JSON text
ToolResult Full control over response
Result<T, E> Ok becomes success, Err becomes error
() Empty success response
Option<T> None returns "No result"
(A, B) Combines multiple contents

Error Types

From Type Conversion
std::io::Error Auto-converts to ToolError
serde_json::Error Auto-converts to ToolError
String, &str Direct message
Box<dyn Error> Auto-converts to ToolError

Binary Size

Configuration Size
Core types only ~50KB
+ JSON serialization ~100KB
+ HTTP client ~200KB
wasm-server feature ~536KB

Browser Compatibility

  • Chrome 89+
  • Firefox 89+
  • Safari 15+
  • Edge 89+

Requires support for:

  • WebAssembly
  • Fetch API
  • ES2020 (async/await)

WASI Support

WASI Preview 2 support for running in server-side WASM runtimes:

  • Wasmtime 29+
  • WasmEdge
  • Wasmer

License

MIT

Dependencies

~2.2–7MB
~125K SLoC