Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
14 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -529,6 +529,9 @@ members = [
"rs/utils/thread",
"rs/utils/validate_eq",
"rs/utils/validate_eq_derive",
"rs/webmcp/asset-middleware",
"rs/webmcp/codegen",
"rs/webmcp/demo",
"rs/validator",
"rs/validator/http_request_arbitrary",
"rs/validator/http_request_test_utils",
Expand Down
3 changes: 3 additions & 0 deletions packages/ic-webmcp/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
node_modules/
dist/
*.tsbuildinfo
221 changes: 221 additions & 0 deletions packages/ic-webmcp/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
# @dfinity/webmcp

Browser-side library that bridges the [WebMCP](https://webmcp.link/) standard to Internet Computer canisters via [`@icp-sdk/core`](https://js.icp.build/core/).

WebMCP (Web Model Context Protocol) is a W3C browser API (Chrome 146+) that lets websites expose structured, callable tools to AI agents via `navigator.modelContext`. This package makes any IC canister discoverable and callable by AI agents with a few lines of code.

## Installation

```bash
npm install @dfinity/webmcp
```

## Quick Start

```html
<!-- In your frontend's index.html -->
<script type="module" src="/webmcp.js"></script>
```

The `webmcp.js` script is auto-generated by [`ic-webmcp-codegen`](../../rs/webmcp/codegen) from your canister's `.did` file and handles everything automatically.

For manual control:

```typescript
import { ICWebMCP } from "@dfinity/webmcp";

const webmcp = new ICWebMCP({
// Reads /.well-known/webmcp.json by default
manifestUrl: "/.well-known/webmcp.json",
host: "https://icp-api.io",
});

// Fetch manifest and register all tools with navigator.modelContext
await webmcp.registerAll();
```

## How It Works

```
AI Agent (Chrome 146+)
└── navigator.modelContext.callTool("icrc1_balance_of", { owner: "..." })
@dfinity/webmcp (this package)
├── Fetches /.well-known/webmcp.json (tool manifest)
├── Registers tools with navigator.modelContext
├── Maps tool calls → @icp-sdk/core/agent query/call
└── Returns JSON results to the agent
IC Canister (via HTTPS boundary node)
```

## Configuration

```typescript
const webmcp = new ICWebMCP({
// URL to fetch the manifest from. Default: '/.well-known/webmcp.json'
manifestUrl?: string;

// Override the canister ID from the manifest
canisterId?: string;

// IC replica host. Default: 'https://icp-api.io'
host?: string;

// Pre-existing identity (e.g., from Internet Identity login)
identity?: Identity;

// Called when a tool that requires_auth is invoked anonymously.
// Should trigger II login and return the authenticated identity.
onAuthRequired?: () => Promise<Identity>;
});
```

## Authentication with Internet Identity

Tools marked `requires_auth: true` in the manifest need an authenticated identity. Use `onAuthRequired` to trigger an Internet Identity login flow:

```typescript
import { ICWebMCP } from "@dfinity/webmcp";
import { AuthClient } from "@icp-sdk/auth";

const authClient = await AuthClient.create();

const webmcp = new ICWebMCP({
onAuthRequired: async () => {
await authClient.login({
identityProvider: "https://identity.ic0.app",
});
return authClient.getIdentity();
},
});

await webmcp.registerAll();
```

### Scoped Delegations

For tighter security, create a short-lived, canister-scoped delegation instead of using the full identity:

```typescript
import { ICWebMCP, createScopedDelegation } from "@dfinity/webmcp";

// After II login, create a delegation restricted to this canister
const webmcp = new ICWebMCP({ identity: iiIdentity });
await webmcp.registerAll();

// Create a 1-hour delegation scoped to the backend canister only
const agentIdentity = await webmcp.createAgentDelegation({
maxTtlSeconds: 3600,
});
webmcp.setIdentity(agentIdentity);
```

## API Reference

### `ICWebMCP`

Main class. Import and instantiate with an optional config object.

#### `registerAll(): Promise<void>`

Fetches the manifest and registers all tools with `navigator.modelContext`.

#### `registerTool(name: string): Promise<void>`

Register a single tool by name (manifest must already be loaded via `registerAll()`).

#### `unregisterAll(): Promise<void>`

Unregister all previously registered tools. Useful for cleanup on logout.

#### `setIdentity(identity: Identity): void`

Update the identity used for canister calls (e.g., after II login).

#### `setIdlFactory(factory: IDL.InterfaceFactory): void`

Provide a generated IDL factory for typed Actor-based calls. If not set, calls use raw Candid encoding.

#### `createAgentDelegation(opts?): Promise<Identity>`

Create a scoped delegation identity for agent authentication. Requires an authenticated identity to be set first.

#### `getAgent(): HttpAgent`

Returns the underlying `@icp-sdk/core/agent` HttpAgent (available after `registerAll()`).

#### `getManifest(): WebMCPManifest`

Returns the loaded manifest (available after `registerAll()`).

### Low-level exports

```typescript
import {
fetchManifest, // Fetch and validate a webmcp.json manifest
jsonToCandid, // Encode JSON params into Candid binary
candidToJson, // Decode Candid binary into JSON
executeToolCall, // Execute a single tool call via agent
registerTool, // Register one tool with navigator.modelContext
unregisterTool, // Unregister one tool
registerAllTools, // Register an array of tools
unregisterAllTools, // Unregister an array of tools
createScopedDelegation, // Create a time-limited, canister-scoped delegation
getDelegationTargets, // Resolve delegation targets from manifest + canister ID
} from "@dfinity/webmcp";
```

## WebMCP Manifest Format

The manifest (`/.well-known/webmcp.json`) is auto-generated by `ic-webmcp-codegen`. Its structure:

```json
{
"schema_version": "1.0",
"canister": {
"id": "ryjl3-tyaaa-aaaaa-aaaba-cai",
"name": "ICP Ledger",
"description": "ICP token ledger implementing ICRC-1/2/3"
},
"tools": [
{
"name": "icrc1_balance_of",
"description": "Get the token balance of an account",
"canister_method": "icrc1_balance_of",
"method_type": "query",
"certified": true,
"inputSchema": {
"type": "object",
"properties": {
"owner": { "type": "string", "description": "Principal ID" }
}
}
},
{
"name": "icrc1_transfer",
"canister_method": "icrc1_transfer",
"method_type": "update",
"requires_auth": true,
"inputSchema": { ... }
}
],
"authentication": {
"type": "internet-identity",
"delegation_targets": ["ryjl3-tyaaa-aaaaa-aaaba-cai"]
}
}
```

## Browser Requirements

`navigator.modelContext` is available in Chrome 146+ with the WebMCP origin trial or flag enabled. For other browsers or agent frameworks, a polyfill can intercept calls before they reach the browser API.

## Related

- [`ic-webmcp-codegen`](../../rs/webmcp/codegen) — Rust CLI that generates `webmcp.json` and `webmcp.js` from `.did` files
- [`ic-webmcp-asset-middleware`](../../rs/webmcp/asset-middleware) — Rust helpers for serving the manifest with correct CORS headers from a canister
- [WebMCP Specification](https://webmcp.link/)
- [`@icp-sdk/core`](https://js.icp.build/core/)
Loading
Loading