Skip to content

Commit a26013b

Browse files
feat: add WebMCP polyfill — navigator.modelContext tools for AI agents
Registers list_skills, get_skill, and search_skills with navigator.modelContext so AI agents can call them as tools regardless of browser: Chrome 146+ uses the native WebMCP API, all others pick up the inline polyfill installed by this script. - public/webmcp-setup.js — self-contained polyfill + tool registration Uses the existing /.well-known/skills/ static API endpoints (no canister or @dfinity/webmcp dependency required). When PR #148 is deployed and @dfinity/webmcp is published, the execute() functions can be upgraded to use ICWebMCP for full Candid canister calls with certified responses. - src/layouts/BaseLayout.astro <link rel="modelcontext" href="/.well-known/webmcp.json"> — signals Chrome 146+ to load the manifest automatically. <script src="/webmcp-setup.js" defer> — loads the polyfill on every page. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 384eb3d commit a26013b

2 files changed

Lines changed: 125 additions & 0 deletions

File tree

public/webmcp-setup.js

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
/**
2+
* IC Skills WebMCP setup — registers list_skills, get_skill, and search_skills
3+
* with navigator.modelContext so AI agents (Chrome 146+, @dfinity/webmcp
4+
* polyfill, WebMCP-aware browser extensions) can call them as tools.
5+
*
6+
* This script uses the site's existing /.well-known/skills/ static endpoints,
7+
* so it works without a deployed skills canister. When the skills canister
8+
* (PR #148) is deployed and @dfinity/webmcp is published to npm, this script
9+
* can be replaced with the full ICWebMCP integration from webmcp.json.
10+
*
11+
* Discovery: <link rel="modelcontext" href="/.well-known/webmcp.json"> in the
12+
* page head signals Chrome 146+ to load the manifest automatically.
13+
*/
14+
(function () {
15+
"use strict";
16+
17+
// Install navigator.modelContext polyfill for non-Chrome-146+ environments.
18+
if (typeof navigator !== "undefined" && !navigator.modelContext) {
19+
var _registry = new Map();
20+
Object.defineProperty(navigator, "modelContext", {
21+
value: {
22+
registerTool: function (tool) {
23+
_registry.set(tool.name, tool);
24+
return Promise.resolve();
25+
},
26+
unregisterTool: function (name) {
27+
_registry.delete(name);
28+
return Promise.resolve();
29+
},
30+
_registry: _registry,
31+
},
32+
writable: true,
33+
configurable: true,
34+
});
35+
}
36+
37+
if (typeof navigator === "undefined" || !navigator.modelContext) return;
38+
39+
var ctx = navigator.modelContext;
40+
41+
ctx.registerTool({
42+
name: "list_skills",
43+
description:
44+
"List all available Internet Computer skill topics with their " +
45+
"names, titles, descriptions, and categories.",
46+
inputSchema: {
47+
type: "object",
48+
properties: {},
49+
additionalProperties: false,
50+
},
51+
execute: function () {
52+
return fetch("/.well-known/skills/index.json").then(function (r) {
53+
return r.json();
54+
});
55+
},
56+
});
57+
58+
ctx.registerTool({
59+
name: "get_skill",
60+
description:
61+
'Get the full documentation for a specific IC skill by name ' +
62+
'(e.g. "motoko", "asset-canister", "internet-identity"). ' +
63+
"Returns the complete SKILL.md content. Returns null if not found.",
64+
inputSchema: {
65+
type: "object",
66+
properties: {
67+
name: {
68+
type: "string",
69+
description:
70+
'Skill name in lowercase-hyphenated format, ' +
71+
'e.g. "motoko", "ckbtc", "https-outcalls", "webmcp"',
72+
},
73+
},
74+
required: ["name"],
75+
additionalProperties: false,
76+
},
77+
execute: function (params) {
78+
var url =
79+
"/.well-known/skills/" +
80+
encodeURIComponent(params.name) +
81+
"/SKILL.md";
82+
return fetch(url).then(function (r) {
83+
return r.ok ? r.text() : null;
84+
});
85+
},
86+
});
87+
88+
ctx.registerTool({
89+
name: "search_skills",
90+
description:
91+
"Search Internet Computer skill documentation by keyword. " +
92+
"Matches against skill names and descriptions. " +
93+
"Returns summaries of matching skills.",
94+
inputSchema: {
95+
type: "object",
96+
properties: {
97+
query: {
98+
type: "string",
99+
description: "Keyword to match against skill names and descriptions",
100+
},
101+
},
102+
required: ["query"],
103+
additionalProperties: false,
104+
},
105+
execute: function (params) {
106+
return fetch("/.well-known/skills/index.json")
107+
.then(function (r) {
108+
return r.json();
109+
})
110+
.then(function (data) {
111+
var q = String(params.query).toLowerCase();
112+
return {
113+
skills: data.skills.filter(function (s) {
114+
return (
115+
s.name.toLowerCase().includes(q) ||
116+
s.description.toLowerCase().includes(q)
117+
);
118+
}),
119+
};
120+
});
121+
},
122+
});
123+
})();

src/layouts/BaseLayout.astro

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ const defaultJsonLd = JSON.stringify({
7373
<meta name="twitter:image" content={ogImage}>
7474

7575
<link rel="alternate" type="text/plain" title="LLMs.txt" href="/llms.txt">
76+
<link rel="modelcontext" href="/.well-known/webmcp.json">
7677

7778
<meta name="theme-color" content="#000000">
7879
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
@@ -86,6 +87,7 @@ const defaultJsonLd = JSON.stringify({
8687
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600;700&family=JetBrains+Mono:wght@400;700&display=swap" rel="stylesheet">
8788

8889
<script is:inline src="/matomo.js" async></script>
90+
<script is:inline src="/webmcp-setup.js" defer></script>
8991
</head>
9092
<body>
9193
<script is:inline>

0 commit comments

Comments
 (0)