feat(webmcp): WebMCP support for the Internet Computer#9708
Open
dfinityianblenke wants to merge 14 commits intomasterfrom
Open
feat(webmcp): WebMCP support for the Internet Computer#9708dfinityianblenke wants to merge 14 commits intomasterfrom
dfinityianblenke wants to merge 14 commits intomasterfrom
Conversation
…st generation Implements Phase 1 of the WebMCP integration: a Rust codegen crate that parses .did files and generates WebMCP tool manifests (webmcp.json) and browser registration scripts (webmcp.js) for AI agent discovery.
…t coverage Add a clap-based CLI for generating webmcp.json/webmcp.js from .did files. Fix stack overflow on recursive Candid types (e.g., ICRC-1 Value) by tracking visited type names during schema generation. Add integration tests covering 6 complex .did files (ICRC-1, NNS Governance, SNS Swap, CMC, Management Canister, ckBTC Minter).
Implements Phase 2: browser-side library bridging navigator.modelContext to Internet Computer canisters via @dfinity/agent. Modules: - types.ts: shared interfaces + navigator.modelContext type declarations - manifest.ts: fetch and validate /.well-known/webmcp.json - candid-json.ts: JSON <-> Candid encoding/decoding for all IDL types - agent-bridge.ts: map tool execute() to agent query/call - tool-registry.ts: register/unregister tools with navigator.modelContext - auth.ts: scoped Internet Identity delegation for agent auth - ic-webmcp.ts: ICWebMCP class tying all modules together - index.ts: public API barrel export
…ckage 52 tests across 4 modules: manifest fetch/validation, candid-json type roundtrips (opt, blob, record, variant, bigint), tool-registry mock of navigator.modelContext, ICWebMCP class lifecycle. Also fixes opt/variant JSON decoding to be type-aware, correctly unwrapping Candid array representation of opt into null/value.
…e, demo dapp dfx.json config parsing (rs/webmcp/codegen/src/dfx_config.rs): - configs_from_dfx_json(): parse webmcp sections from dfx.json into Configs - load_canister_ids(): load .dfx/<network>/canister_ids.json for principal injection - 8 unit tests covering parsing, defaults, multi-canister, ID injection CLI dfx subcommand (main.rs): - ic-webmcp-codegen dfx --dfx-json dfx.json --out-dir .webmcp/ - Auto-discovers canister_ids.json at conventional dfx paths - Generates <canister>.webmcp.json + <canister>.webmcp.js per enabled canister Asset canister middleware (rs/webmcp/asset-middleware/): - handle_webmcp_request(): HTTP handler producing correct CORS headers - ic_assets_config(): generate .ic-assets.json5 for the asset canister - Handles GET, OPTIONS, query strings, case-insensitive methods - 8 unit tests Demo dapp (rs/webmcp/demo/): - backend.did: e-commerce canister (products, cart, checkout) - dfx.json: full webmcp config with descriptions, auth, certified queries
README.md files (IC convention for tools and npm packages): - rs/webmcp/README.md: architecture overview, pipeline diagram, all components - rs/webmcp/codegen/README.md: CLI usage, both subcommands, dfx.json config reference, Candid→JSON Schema mapping table, library API examples - packages/ic-webmcp/README.md: quick start, config reference, auth/delegation, full API reference, manifest format, browser requirements Rust inline docs (IC convention for library crates): - codegen/src/lib.rs: expanded //! crate docs with two runnable examples (from .did and from dfx.json), module listing - codegen/src/config.rs: /// on every field of Config, doc example on from_did_file(), explaining fallbacks and agent interaction - asset-middleware/src/lib.rs: expanded //! docs with runnable doctest showing handle_webmcp_request() usage All doctests compile and pass (3 in codegen, 2 in asset-middleware).
… crates rs/webmcp/codegen/BUILD.bazel: - rust_library :ic_webmcp_codegen (src/ excluding main.rs) - rust_binary :ic-webmcp-codegen (src/main.rs) - rust_test :unit_tests (inline #[cfg(test)] via crate =) - rust_test :integration_tests (tests/integration_test.rs) with data entries for all 7 .did fixtures from across the IC repo rs/webmcp/asset-middleware/BUILD.bazel: - rust_library :ic_webmcp_asset_middleware - rust_test :unit_tests (inline #[cfg(test)] via crate =) Also updates integration_test.rs to detect Bazel's TEST_SRCDIR env var and resolve runfiles paths correctly, while keeping the existing CARGO_MANIFEST_DIR fallback for cargo test. bazel query //rs/webmcp/... confirms all 6 targets visible and all 7 .did data dependencies resolve.
Implements the backend canister for rs/webmcp/demo/ matching the Candid interface in backend.did. Per-caller cart state using Principal as key; products seeded at init time. src/lib.rs: - Product, CartItem, Cart, AddToCartResult, CheckoutResult types - Thread-local State (products, carts BTreeMap, order counter) - init_products(): seeds 4 products (1 out-of-stock to demonstrate) - list_products(), get_product(), get_cart(caller), add_to_cart(caller, item), remove_from_cart(caller, id), checkout(caller) - Functions take explicit Principal to avoid ic0 host dependency in tests - 13 unit tests covering all methods, error paths, and per-caller isolation src/main.rs: - #[ic_cdk::init/query/update] entry points passing msg_caller() through Also: - dfx.json updated to type: rust with package: demo-backend - assets/index.html minimal frontend loading webmcp.js - README.md with deploy and test instructions
Contributor
There was a problem hiding this comment.
Pull request overview
Adds end-to-end WebMCP (Web Model Context Protocol) support for the Internet Computer: Rust codegen to turn Candid into WebMCP manifests + registration scripts, a browser TypeScript bridge to navigator.modelContext, an asset-serving helper for correct CORS/headers, and a runnable demo canister.
Changes:
- Introduces
ic-webmcp-codegen(Rust) to parse.did/dfx.jsonand generatewebmcp.json+webmcp.js. - Adds
ic-webmcp-asset-middleware(Rust) helpers to serve WebMCP endpoints with required headers /.ic-assets.json5generation. - Adds
@dfinity/webmcp(TypeScript) to fetch manifests and register/call canister tools vianavigator.modelContext, plus a demo canister and docs.
Reviewed changes
Copilot reviewed 44 out of 46 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| rs/webmcp/README.md | Top-level WebMCP documentation and component overview. |
| rs/webmcp/IMPLEMENTATION_PLAN.md | Detailed architecture/plan and intended deliverables. |
| rs/webmcp/demo/src/main.rs | Demo canister entry points wiring msg_caller() to backend logic. |
| rs/webmcp/demo/src/lib.rs | Demo e-commerce business logic + unit tests. |
| rs/webmcp/demo/README.md | Demo usage instructions and local run steps. |
| rs/webmcp/demo/dfx.json | Demo dfx.json including webmcp config block. |
| rs/webmcp/demo/Cargo.toml | Demo crate configuration (bin + lib). |
| rs/webmcp/demo/backend.did | Demo Candid interface used by codegen. |
| rs/webmcp/demo/assets/index.html | Minimal demo frontend that loads /webmcp.js. |
| rs/webmcp/codegen/tests/integration_test.rs | Integration tests against real .did fixtures (ledger/NNS/etc). |
| rs/webmcp/codegen/src/schema_mapper.rs | Candid → JSON Schema mapping implementation + unit tests. |
| rs/webmcp/codegen/src/manifest.rs | Manifest generation (webmcp.json) from parsed interfaces + config. |
| rs/webmcp/codegen/src/main.rs | CLI (did / dfx) to write manifests and JS scripts. |
| rs/webmcp/codegen/src/lib.rs | Library exports and crate-level documentation. |
| rs/webmcp/codegen/src/js_emitter.rs | Generator for webmcp.js registration script. |
| rs/webmcp/codegen/src/did_parser.rs | .did parsing into method definitions. |
| rs/webmcp/codegen/src/dfx_config.rs | dfx.json parsing into per-canister codegen Config. |
| rs/webmcp/codegen/src/config.rs | Config struct for controlling generation. |
| rs/webmcp/codegen/README.md | Codegen usage docs and config reference. |
| rs/webmcp/codegen/Cargo.toml | ic-webmcp-codegen crate definition/deps. |
| rs/webmcp/codegen/BUILD.bazel | Bazel targets for codegen lib/bin/tests. |
| rs/webmcp/asset-middleware/src/lib.rs | HTTP handler + .ic-assets.json5 generator + tests. |
| rs/webmcp/asset-middleware/Cargo.toml | ic-webmcp-asset-middleware crate definition/deps. |
| rs/webmcp/asset-middleware/BUILD.bazel | Bazel targets for asset middleware. |
| packages/ic-webmcp/vitest.config.ts | Vitest configuration for the TS package. |
| packages/ic-webmcp/tsconfig.test.json | TS config for tests/typechecking. |
| packages/ic-webmcp/tsconfig.json | TS build configuration for the library. |
| packages/ic-webmcp/src/types.ts | Shared types for manifest/tools + navigator.modelContext typings. |
| packages/ic-webmcp/src/tool-registry.ts | Tool registration/unregistration against navigator.modelContext. |
| packages/ic-webmcp/src/tests/tool-registry.test.ts | Tests for tool registry behaviors including auth gating. |
| packages/ic-webmcp/src/tests/manifest.test.ts | Tests for manifest fetch/validation. |
| packages/ic-webmcp/src/tests/ic-webmcp.test.ts | Tests for ICWebMCP lifecycle (register/unregister/etc). |
| packages/ic-webmcp/src/tests/candid-json.test.ts | Tests for JSON↔Candid conversions. |
| packages/ic-webmcp/src/manifest.ts | Manifest fetching and basic validation. |
| packages/ic-webmcp/src/index.ts | Public exports surface. |
| packages/ic-webmcp/src/ic-webmcp.ts | High-level ICWebMCP class orchestrating manifest/agent/tool registration. |
| packages/ic-webmcp/src/candid-json.ts | JSON params ↔ Candid binary encode/decode utilities. |
| packages/ic-webmcp/src/auth.ts | Scoped delegation helpers (Internet Identity delegation chain). |
| packages/ic-webmcp/src/agent-bridge.ts | Tool execution bridge (Actor/raw agent call paths). |
| packages/ic-webmcp/README.md | Package docs and usage guidance. |
| packages/ic-webmcp/package.json | NPM package definition, scripts, deps/peers. |
| packages/ic-webmcp/package-lock.json | Lockfile for the TS package dependencies. |
| packages/ic-webmcp/.gitignore | Ignores build outputs and node_modules. |
| Cargo.toml | Adds new Rust crates to workspace members. |
| Cargo.lock | Adds lock entries for new Rust crates. |
| .claude/instructions/webmcp.md | Branch-specific implementation context notes. |
Files not reviewed (1)
- packages/ic-webmcp/package-lock.json: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
71d5d91 to
c443134
Compare
…ddress Copilot PR review feedback, align with IC repo conventions, fix four security issues identified in branch audit, certified query verification and navigator.modelContext polyfill, certified query verification and navigator.modelContext polyfill, address CI failures
BUILD.bazel (rs/webmcp/demo/):
- rust_library :demo_backend_lib (src/lib.rs — testable without IC host)
- rust_canister :demo_backend_canister (src/main.rs, wasm32, with .did)
- rust_test :unit_tests (inline #[cfg(test)])
- bazel query //rs/webmcp/demo/... confirms wasm artifact + didfile targets
Stable memory (pre_upgrade / post_upgrade):
- StableState { carts, next_order_id } serialised via Candid to stable memory
- pre_upgrade: ic_cdk::storage::stable_save((stable,))
- post_upgrade: ic_cdk::storage::stable_restore() then restore_stable_state()
- Products are re-seeded from constants; only mutable state persists
- All 13 unit tests still pass, clippy clean
manifest.rs — positional args now have required + additionalProperties: false
Multi-arg methods generated arg0/arg1/... but omitted required[], making
agents think all args were optional. Now marks non-opt args as required
and adds additionalProperties: false.
js_emitter.rs — fix auto-init for ES module script tags
document.currentScript is null for <script type="module">, so the
previous guard never fired. Replaced with unconditional top-level await
which runs when the module is first evaluated.
agent-bridge.ts — fix executeRawCall ignoring params
Previously encoded IDL.encode([], []) regardless of params, silently
sending wrong Candid for any tool with inputs. Now throws a clear error
requiring an idlFactory for methods that take arguments.
agent-bridge.ts — fix buildActorArgs passing [{}] for zero-arg methods
Empty params object {} was treated as a single record argument, causing
Actor calls to fail with an argument-count mismatch. Now returns [] for
empty params so zero-arg methods call correctly.
agent-bridge.ts — accurately report certified: false
The certified flag was described but never backed by BLS verification.
Now explicitly returns certified: false with a TODO comment noting where
readState()-based verification should be implemented.
demo/dfx.json — add remove_from_cart to expose_methods
remove_from_cart was in require_auth, descriptions, and param_descriptions
but absent from expose_methods, so codegen silently dropped it.
Cargo.toml (all three crates):
- version/authors/edition/description/documentation now use workspace
inheritance (version.workspace = true etc.) matching the ic-crypto-sha2
and icrc1-ledger patterns
- serde_json = "1" and anyhow = "1" changed to { workspace = true }
- clap, pretty_assertions, tempfile similarly moved to workspace = true
lib.rs (codegen + asset-middleware):
- Added #![forbid(unsafe_code)] and #![deny(clippy::unwrap_used)] matching
the lint attributes used in rs/crypto/* and other IC library crates
BUILD.bazel (all three crates):
- Added ALIASES = {} and aliases = ALIASES, to all rust_library /
rust_binary / rust_test / rust_canister rules (matches rs/crypto/sha2,
rs/nns/cmc and other crates)
- Added compile_data = [":backend.did"] and proc_macro_deps = [] to
the demo rust_canister target
Cargo Lint Linux — clippy::option_as_ref_deref:
dfx_config.rs:222: use .as_deref() instead of
.as_ref().map(|v| v.as_slice())
DFINITY-capitalization-check:
demo/src/lib.rs: 'Dfinity Sticker Pack' -> 'DFINITY Sticker Pack'
Bazel visibility error:
//rs/types/management_canister_types:tests/ic.did is not publicly
exported; removed from integration_tests data list. The test already
has a graceful skip for missing fixtures.
auth.ts — empty delegation targets (High)
createScopedDelegation now throws if targets is empty. An empty
targets list produces an unrestricted IC delegation valid for ALL
canisters, not just the intended one. getDelegationTargets always
populates targets from the canister ID + manifest, so legitimate
callers are unaffected.
candid-json.ts — prototype pollution (Medium)
toJsonValue (untyped fallback) now skips __proto__, constructor,
and prototype keys when iterating decoded Candid records to prevent
Object.prototype mutation.
candid-json.ts — integer range validation (Medium)
Fixed-width integer types (Nat8/16/32/64, Int8/16/32/64) now throw
RangeError when values fall outside the valid range. Previously,
Nat32(4294967296) silently accepted out-of-range inputs.
js_emitter.rs — esm.sh CDN supply chain risk (High)
Replaced hardcoded 'https://esm.sh/@dfinity/webmcp' with a local
package import '@dfinity/webmcp' and added a prominent security
warning in the generated script explaining the CDN risk and
requiring developers to use their own bundled copy in production.
Also adds security.test.ts: 14 tests covering all four fixes.
Certified query verification (certified-response.ts, agent-bridge.ts):
- wrapCertifiedResponse(): extracts NodeSignature metadata from query
responses. @dfinity/agent verifies signatures by default
(verifyQuerySignatures: true), so if execution reaches this function
the response is already cryptographically verified by the subnet nodes.
- readCertifiedData(): full BLS certificate path via agent.readState() +
Certificate.create() for canisters using ic0.certified_data_set().
- executeToolCall now returns CertifiedQueryResult (certified: true +
signatures[]) for tools marked certified: true instead of certified: false.
- executeCertifiedQuery(): raw agent.query() path for certified tools to
preserve the signatures array that Actor calls would discard.
navigator.modelContext polyfill (polyfill.ts):
- installPolyfill(): installs in-memory shim when native API is absent
(non-Chrome browsers, Node.js, test runners). No-op if already present.
- clearRegistry(): clears registered tools (useful in tests).
- getOpenAITools(): exports tools in OpenAI function calling format.
- getAnthropicTools(): exports tools in Anthropic tool use format.
- getLangChainTools(): exports tools as LangChain DynamicStructuredTool defs
with func() that JSON-stringifies results.
- dispatchToolCall(): routes a named tool call from any framework back to
the registered execute() callback.
Certified query verification (certified-response.ts, agent-bridge.ts):
- wrapCertifiedResponse(): extracts NodeSignature metadata from query
responses. @dfinity/agent verifies signatures by default
(verifyQuerySignatures: true), so if execution reaches this function
the response is already cryptographically verified by the subnet nodes.
- readCertifiedData(): full BLS certificate path via agent.readState() +
Certificate.create() for canisters using ic0.certified_data_set().
- executeToolCall now returns CertifiedQueryResult (certified: true +
signatures[]) for tools marked certified: true instead of certified: false.
- executeCertifiedQuery(): raw agent.query() path for certified tools to
preserve the signatures array that Actor calls would discard.
navigator.modelContext polyfill (polyfill.ts):
- installPolyfill(): installs in-memory shim when native API is absent
(non-Chrome browsers, Node.js, test runners). No-op if already present.
- clearRegistry(): clears registered tools (useful in tests).
- getOpenAITools(): exports tools in OpenAI function calling format.
- getAnthropicTools(): exports tools in Anthropic tool use format.
- getLangChainTools(): exports tools as LangChain DynamicStructuredTool defs
with func() that JSON-stringifies results.
- dispatchToolCall(): routes a named tool call from any framework back to
the registered execute() callback.
Tests: 78 passing (was 66). New: polyfill.test.ts (12 tests).
c443134 to
e6b2eaf
Compare
The integration test calls serde_json::to_string_pretty() directly. Cargo resolves it transitively, but Bazel requires explicit deps. Fixes Bazel Test arm64-darwin/linux compilation failure.
1d28272 to
caa0f04
Compare
dfinityianblenke
added a commit
to dfinity/icskills
that referenced
this pull request
Apr 2, 2026
Adds a new skill covering the full WebMCP stack: - Candid-to-JSON-Schema codegen via ic-webmcp-codegen - Browser tool registration via navigator.modelContext - dfx.json webmcp config section - CORS headers via .ic-assets.json5 - Internet Identity scoped delegation for agents - Polyfill with OpenAI/Anthropic/LangChain adapters - 10 common pitfalls Evaluation: 4 output evals (add to canister, auth tools, polyfill, manifest gen) + 8 should-trigger / 6 should-not-trigger queries. This is related to the ic repo PR dfinity/ic#9708
Update user-facing READMEs to reference the current IC tooling: - demo/README.md: dfx start/deploy → icp start/deploy - packages/ic-webmcp/README.md: @dfinity/agent → @icp-sdk/core, @dfinity/auth-client → @icp-sdk/auth - rs/webmcp/README.md: @dfinity/agent → @icp-sdk/core/agent, fix stale esm.sh import and document.currentScript in example Does NOT rename the codegen dfx subcommand or dfx_config.rs module — those describe the config file format the tool actually reads today. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…/core All TypeScript imports migrated to the current IC SDK packages: - @dfinity/agent → @icp-sdk/core/agent - @dfinity/candid → @icp-sdk/core/candid - @dfinity/principal → @icp-sdk/core/principal - @dfinity/identity → @icp-sdk/core/identity API changes in @icp-sdk/core v5 addressed: - IDL.encode() returns Uint8Array (was ArrayBuffer) - IDL.decode() takes Uint8Array (was ArrayBuffer) - Certificate.lookup → Certificate.lookup_path - Certificate.create: canisterId → principal: { canisterId } - lookupResultToBuffer returns Uint8Array | undefined - readState paths take Uint8Array[][] (was ArrayBuffer[][]) package.json: - dependencies: @icp-sdk/core ^5.2.1, @icp-sdk/auth ^5.0.0 - removed: @dfinity/agent, @dfinity/candid, @dfinity/identity, @dfinity/principal - peerDependencies: @icp-sdk/core >= 5.0.0 All 78 tests pass. Changes strictly within packages/ic-webmcp/. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This PR adds WebMCP (Web Model Context Protocol) support for the Internet Computer, enabling IC canisters to expose their Candid interfaces as structured tools discoverable by AI agents via
navigator.modelContext(Chrome 146+).WebMCP is a W3C browser standard that lets websites register callable tools for AI agents. The IC is a natural fit: Candid interfaces already define structured schemas, certified queries provide verifiable responses, and Internet Identity enables scoped agent authentication.
Deliverables
rs/webmcp/codegen/(ic-webmcp-codegen) — Rust CLI + library that parses.didfiles and generateswebmcp.jsonmanifests andwebmcp.jsregistration scripts. Supports both single.didfiles anddfx.jsonproject-level generation.packages/ic-webmcp/(@dfinity/webmcp) — TypeScript browser library bridgingnavigator.modelContextto@dfinity/agent. Handles manifest fetching, tool registration, JSON↔Candid encoding, and Internet Identity scoped delegation.rs/webmcp/asset-middleware/(ic-webmcp-asset-middleware) — Rust helpers for serving the manifest from any IC canister with correct CORS headers, plus.ic-assets.json5generation for the asset canister.rs/webmcp/demo/— Runnable e-commerce demo canister (products, per-caller cart, checkout) with a completedfx.jsonwebmcpconfig.How it works
dfx.json integration
{ "canisters": { "backend": { "type": "rust", "candid": "backend.did", "webmcp": { "name": "My DApp", "expose_methods": ["get_items", "checkout"], "require_auth": ["checkout"], "certified_queries": ["get_items"], "descriptions": { "get_items": "List available items", "checkout": "Complete the purchase" } } } } }Test plan
cargo test -p ic-webmcp-codegen— 25 unit tests + 5 integration tests against real.didfiles (ICP ledger, ICRC-1, NNS governance, SNS swap, CMC, ckBTC minter, management canister)cargo test -p ic-webmcp-asset-middleware— 8 unit tests for HTTP handler and CORS header generationcargo test -p demo-backend— 13 unit tests for the demo e-commerce canistercd packages/ic-webmcp && npm test— 52 Vitest tests (manifest fetching, Candid↔JSON roundtrip for all IDL types, tool registry, ICWebMCP class lifecycle)bazel query //rs/webmcp/...— confirms all 6 Bazel targets visible with correct data dependenciesic-webmcp-codegen did --did rs/ledger_suite/icp/ledger.did --no-js— generates valid manifestic-webmcp-codegen dfx --dfx-json rs/webmcp/demo/dfx.json --out-dir /tmp/— generates manifest from demo dfx.json