@mcp-b/global 0.0.0-beta-20260109203913
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +1351 -0
- package/dist/index.d.ts +1061 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.iife.js +34 -0
- package/dist/index.js +1790 -0
- package/dist/index.js.map +1 -0
- package/package.json +96 -0
package/README.md
ADDED
|
@@ -0,0 +1,1351 @@
|
|
|
1
|
+
# @mcp-b/global
|
|
2
|
+
|
|
3
|
+
> W3C Web Model Context API polyfill - Let Claude, ChatGPT, Gemini, and other AI agents interact with your website
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/@mcp-b/global)
|
|
6
|
+
[](https://www.npmjs.com/package/@mcp-b/global)
|
|
7
|
+
[](https://opensource.org/licenses/MIT)
|
|
8
|
+
[](https://bundlephobia.com/package/@mcp-b/global)
|
|
9
|
+
[](https://github.com/nicolo-ribaudo/model-context-protocol-api)
|
|
10
|
+
|
|
11
|
+
📖 **[Full Documentation](https://docs.mcp-b.ai/packages/global)** | 🚀 **[Quick Start](https://docs.mcp-b.ai/quickstart)** | 🔧 **[Tool Registration](https://docs.mcp-b.ai/concepts/tool-registration)**
|
|
12
|
+
|
|
13
|
+
**@mcp-b/global** implements the [W3C Web Model Context API](https://github.com/nicolo-ribaudo/model-context-protocol-api) (`navigator.modelContext`) specification, allowing AI agents like Claude, ChatGPT, Gemini, Cursor, and Copilot to discover and call functions on your website.
|
|
14
|
+
|
|
15
|
+
## Why Use @mcp-b/global?
|
|
16
|
+
|
|
17
|
+
| Feature | Benefit |
|
|
18
|
+
|---------|---------|
|
|
19
|
+
| **W3C Standard** | Implements the emerging Web Model Context API specification |
|
|
20
|
+
| **Drop-in IIFE** | Add AI capabilities with a single `<script>` tag - no build step |
|
|
21
|
+
| **Native Chromium Support** | Auto-detects and uses native browser implementation when available |
|
|
22
|
+
| **Dual Transport** | Works with both same-window clients AND parent pages (iframe support) |
|
|
23
|
+
| **Two-Bucket System** | Manage app-level and component-level tools separately |
|
|
24
|
+
| **Works with Any AI** | Claude, ChatGPT, Gemini, Cursor, Copilot, and any MCP client |
|
|
25
|
+
|
|
26
|
+
## Use Cases
|
|
27
|
+
|
|
28
|
+
- **AI-Powered Websites**: Let AI agents search, filter, and interact with your web app
|
|
29
|
+
- **E-commerce Integration**: AI can search products, add to cart, checkout
|
|
30
|
+
- **SaaS Applications**: Expose your app's functionality to AI assistants
|
|
31
|
+
- **Content Management**: Let AI edit, publish, and organize content
|
|
32
|
+
- **Embedded Widgets**: AI tools accessible from parent pages via iframes
|
|
33
|
+
|
|
34
|
+
## 🚀 Quick Start
|
|
35
|
+
|
|
36
|
+
### Via IIFE Script Tag (Easiest - No Build Required)
|
|
37
|
+
|
|
38
|
+
The **IIFE (Immediately Invoked Function Expression)** version bundles everything into a single file and auto-initializes when loaded. Perfect for simple HTML pages or prototyping.
|
|
39
|
+
|
|
40
|
+
Add the script to your HTML `<head>`:
|
|
41
|
+
|
|
42
|
+
```html
|
|
43
|
+
<!DOCTYPE html>
|
|
44
|
+
<html>
|
|
45
|
+
<head>
|
|
46
|
+
<!-- IIFE version - bundles all dependencies, auto-initializes -->
|
|
47
|
+
<script src="https://unpkg.com/@mcp-b/global@latest/dist/index.iife.js"></script>
|
|
48
|
+
</head>
|
|
49
|
+
<body>
|
|
50
|
+
<h1>My AI-Powered App</h1>
|
|
51
|
+
|
|
52
|
+
<script>
|
|
53
|
+
// window.navigator.modelContext is already available!
|
|
54
|
+
// Register tools with AI agents
|
|
55
|
+
window.navigator.modelContext.provideContext({
|
|
56
|
+
tools: [
|
|
57
|
+
{
|
|
58
|
+
name: "get-page-title",
|
|
59
|
+
description: "Get the current page title",
|
|
60
|
+
inputSchema: {
|
|
61
|
+
type: "object",
|
|
62
|
+
properties: {}
|
|
63
|
+
},
|
|
64
|
+
async execute() {
|
|
65
|
+
return {
|
|
66
|
+
content: [{
|
|
67
|
+
type: "text",
|
|
68
|
+
text: document.title
|
|
69
|
+
}]
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
]
|
|
74
|
+
});
|
|
75
|
+
</script>
|
|
76
|
+
</body>
|
|
77
|
+
</html>
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
**What you get:**
|
|
81
|
+
- ✅ **Self-contained** - All dependencies bundled (285KB minified)
|
|
82
|
+
- ✅ **Auto-initializes** - `window.navigator.modelContext` ready immediately
|
|
83
|
+
- ✅ **No build step** - Just drop it in your HTML
|
|
84
|
+
- ✅ **Works everywhere** - Compatible with all modern browsers
|
|
85
|
+
- ✅ **Global access** - Also exposes `window.WebMCP` for advanced usage
|
|
86
|
+
|
|
87
|
+
### Via ES Module Script Tag
|
|
88
|
+
|
|
89
|
+
If you prefer ES modules and have a build system, use the ESM version:
|
|
90
|
+
|
|
91
|
+
```html
|
|
92
|
+
<!DOCTYPE html>
|
|
93
|
+
<html>
|
|
94
|
+
<head>
|
|
95
|
+
<!-- ESM version - smaller but requires module support -->
|
|
96
|
+
<script type="module">
|
|
97
|
+
import '@mcp-b/global';
|
|
98
|
+
|
|
99
|
+
// window.navigator.modelContext is now available
|
|
100
|
+
window.navigator.modelContext.provideContext({
|
|
101
|
+
tools: [/* your tools */]
|
|
102
|
+
});
|
|
103
|
+
</script>
|
|
104
|
+
</head>
|
|
105
|
+
<body>
|
|
106
|
+
<h1>My AI-Powered App</h1>
|
|
107
|
+
</body>
|
|
108
|
+
</html>
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
**Note:** The ESM version is smaller (~16KB) but doesn't bundle dependencies - it expects them to be available via your module system or CDN.
|
|
112
|
+
|
|
113
|
+
### Via NPM
|
|
114
|
+
|
|
115
|
+
For applications using a bundler (Vite, Webpack, etc.):
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
npm install @mcp-b/global
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
```javascript
|
|
122
|
+
import '@mcp-b/global';
|
|
123
|
+
|
|
124
|
+
// window.navigator.modelContext is now available
|
|
125
|
+
window.navigator.modelContext.provideContext({
|
|
126
|
+
tools: [/* your tools */]
|
|
127
|
+
});
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## ⚙️ Configuration
|
|
131
|
+
|
|
132
|
+
The polyfill exposes `initializeWebModelContext(options?: WebModelContextInitOptions)` to let you control transport behaviour. When you import `@mcp-b/global` as a module it auto-initializes by default, but you can customise or defer initialization:
|
|
133
|
+
|
|
134
|
+
- **Disable auto init**: Set `window.__webModelContextOptions = { autoInitialize: false }` before importing, then call `initializeWebModelContext()` manually.
|
|
135
|
+
- **Configure via script tag**: When using the IIFE build, pass options through data attributes:
|
|
136
|
+
```html
|
|
137
|
+
<script
|
|
138
|
+
src="https://unpkg.com/@mcp-b/global@latest/dist/index.iife.js"
|
|
139
|
+
data-webmcp-auto-initialize="false"
|
|
140
|
+
data-webmcp-allowed-origins="https://example.com,https://docs.example.com"
|
|
141
|
+
></script>
|
|
142
|
+
<!-- Later in the page -->
|
|
143
|
+
<script>
|
|
144
|
+
window.navigator.modelContext.provideContext({ tools: [] });
|
|
145
|
+
</script>
|
|
146
|
+
```
|
|
147
|
+
Use `data-webmcp-options='{"transport":{"tabServer":{"allowedOrigins":["https://example.com"]}}}'` for advanced JSON configuration.
|
|
148
|
+
- **Supported data attributes**
|
|
149
|
+
- `data-webmcp-auto-initialize="false"`: Skip automatic setup.
|
|
150
|
+
- `data-webmcp-allowed-origins="https://a.com,https://b.com"`: Override `tabServer.allowedOrigins`.
|
|
151
|
+
- `data-webmcp-channel-id="custom-channel"`: Set the Tab transport channel.
|
|
152
|
+
|
|
153
|
+
### Dual-Server Mode (Tab + Iframe)
|
|
154
|
+
|
|
155
|
+
By default, the global package runs **two MCP servers** that share the same tool registry:
|
|
156
|
+
|
|
157
|
+
1. **Tab Server** (`TabServerTransport`) - For same-window communication
|
|
158
|
+
2. **Iframe Server** (`IframeChildTransport`) - Auto-enabled when running in an iframe (when `window.parent !== window`)
|
|
159
|
+
|
|
160
|
+
Both servers expose the same tools (Bucket A + Bucket B), allowing your tools to be accessed from:
|
|
161
|
+
- Same-window clients (e.g., browser extension content scripts)
|
|
162
|
+
- Parent page (when running in an iframe)
|
|
163
|
+
|
|
164
|
+
**Example: Running in an Iframe**
|
|
165
|
+
|
|
166
|
+
When your app runs in an iframe, both servers are automatically enabled:
|
|
167
|
+
|
|
168
|
+
```ts
|
|
169
|
+
// In iframe: Auto-initializes with both servers
|
|
170
|
+
import '@mcp-b/global';
|
|
171
|
+
|
|
172
|
+
// Register tools - they're automatically available to:
|
|
173
|
+
// 1. Same-window clients (via TabServerTransport)
|
|
174
|
+
// 2. Parent page (via IframeChildTransport)
|
|
175
|
+
window.navigator.modelContext.provideContext({
|
|
176
|
+
tools: [
|
|
177
|
+
{
|
|
178
|
+
name: "iframe-action",
|
|
179
|
+
description: "Action from iframe",
|
|
180
|
+
inputSchema: { type: "object", properties: {} },
|
|
181
|
+
async execute() {
|
|
182
|
+
return {
|
|
183
|
+
content: [{ type: "text", text: "Hello from iframe!" }]
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
]
|
|
188
|
+
});
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
**Configure Iframe Server**
|
|
192
|
+
|
|
193
|
+
You can customize or disable the iframe server:
|
|
194
|
+
|
|
195
|
+
```ts
|
|
196
|
+
import { initializeWebModelContext } from '@mcp-b/global';
|
|
197
|
+
|
|
198
|
+
// Customize iframe server
|
|
199
|
+
initializeWebModelContext({
|
|
200
|
+
transport: {
|
|
201
|
+
iframeServer: {
|
|
202
|
+
allowedOrigins: ['https://parent-app.com'], // Only allow specific parent
|
|
203
|
+
channelId: 'custom-iframe-channel',
|
|
204
|
+
},
|
|
205
|
+
},
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
// Disable iframe server (only Tab server runs)
|
|
209
|
+
initializeWebModelContext({
|
|
210
|
+
transport: {
|
|
211
|
+
iframeServer: false, // Disable iframe server
|
|
212
|
+
},
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
// Disable tab server (only Iframe server runs)
|
|
216
|
+
initializeWebModelContext({
|
|
217
|
+
transport: {
|
|
218
|
+
tabServer: false, // Disable tab server
|
|
219
|
+
iframeServer: {
|
|
220
|
+
allowedOrigins: ['https://parent-app.com'],
|
|
221
|
+
},
|
|
222
|
+
},
|
|
223
|
+
});
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
**Custom Transport Factory**
|
|
227
|
+
|
|
228
|
+
Provide `transport.create` to supply any MCP `Transport` implementation instead of the built-in dual-server mode:
|
|
229
|
+
|
|
230
|
+
```ts
|
|
231
|
+
import { initializeWebModelContext } from '@mcp-b/global';
|
|
232
|
+
import { CustomTransport } from './my-transport';
|
|
233
|
+
|
|
234
|
+
initializeWebModelContext({
|
|
235
|
+
transport: {
|
|
236
|
+
create: () => new CustomTransport(),
|
|
237
|
+
},
|
|
238
|
+
});
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
## 🔄 Native Chromium API Support
|
|
242
|
+
|
|
243
|
+
This package **automatically detects and integrates** with Chromium's native Web Model Context API when available. No configuration needed - it just works!
|
|
244
|
+
|
|
245
|
+
### Automatic Detection & Integration
|
|
246
|
+
|
|
247
|
+
When you call `initializeWebModelContext()` (or when auto-initialization runs):
|
|
248
|
+
|
|
249
|
+
1. **Native API detected** (both `navigator.modelContext` and `navigator.modelContextTesting` present):
|
|
250
|
+
- Uses native Chromium implementation
|
|
251
|
+
- Creates MCP bridge and syncs tools automatically
|
|
252
|
+
- Registers callback to listen for native tool changes
|
|
253
|
+
- MCP clients stay synchronized with native tool registry
|
|
254
|
+
|
|
255
|
+
2. **No native API detected**:
|
|
256
|
+
- Installs full polyfill implementation
|
|
257
|
+
- Provides identical API surface
|
|
258
|
+
|
|
259
|
+
**Zero configuration required** - the package automatically adapts to your environment!
|
|
260
|
+
|
|
261
|
+
### Native API Features
|
|
262
|
+
|
|
263
|
+
When the native Chromium API is available, you get:
|
|
264
|
+
|
|
265
|
+
- ✅ **Automatic tool synchronization** - Tools registered in native API are synced to MCP bridge via `registerToolsChangedCallback()`
|
|
266
|
+
- ✅ **Iframe tool collection** - Native API automatically collects tools from embedded iframes (no manual transport setup needed)
|
|
267
|
+
- ✅ **MCP compatibility** - Your MCP clients (extensions, apps) continue to work seamlessly
|
|
268
|
+
- ✅ **Tool change notifications** - MCP servers receive `tools/list_changed` notifications automatically
|
|
269
|
+
- ✅ **Consistent API** - Same code works with both native and polyfill implementations
|
|
270
|
+
|
|
271
|
+
### How Tool Synchronization Works
|
|
272
|
+
|
|
273
|
+
The polyfill automatically registers a callback with the native API:
|
|
274
|
+
|
|
275
|
+
```typescript
|
|
276
|
+
// Happens automatically when native API is detected
|
|
277
|
+
navigator.modelContextTesting.registerToolsChangedCallback(() => {
|
|
278
|
+
// Syncs native tools → MCP bridge
|
|
279
|
+
// MCP clients receive tools/list_changed notification
|
|
280
|
+
});
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
This callback fires when:
|
|
284
|
+
- `navigator.modelContext.registerTool()` is called
|
|
285
|
+
- `navigator.modelContext.unregisterTool()` is called
|
|
286
|
+
- `navigator.modelContext.provideContext()` is called
|
|
287
|
+
- `navigator.modelContext.clearContext()` is called
|
|
288
|
+
- Tools are added from embedded iframes (native feature)
|
|
289
|
+
|
|
290
|
+
### Enabling Native API in Chromium
|
|
291
|
+
|
|
292
|
+
```bash
|
|
293
|
+
# Method 1: Launch with flag
|
|
294
|
+
chromium --enable-experimental-web-platform-features
|
|
295
|
+
|
|
296
|
+
# Method 2: Enable in chrome://flags
|
|
297
|
+
# Search for: "Experimental Web Platform Features"
|
|
298
|
+
# Set to: Enabled
|
|
299
|
+
# Restart browser
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
### Example: Using Native API
|
|
303
|
+
|
|
304
|
+
```typescript
|
|
305
|
+
import '@mcp-b/global';
|
|
306
|
+
|
|
307
|
+
// If native API is present, this delegates to navigator.modelContext:
|
|
308
|
+
window.navigator.modelContext.registerTool({
|
|
309
|
+
name: 'myTool',
|
|
310
|
+
description: 'My tool',
|
|
311
|
+
inputSchema: { type: 'object', properties: {} },
|
|
312
|
+
async execute() {
|
|
313
|
+
return { content: [{ type: 'text', text: 'Hello!' }] };
|
|
314
|
+
}
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
// Behind the scenes:
|
|
318
|
+
// 1. Tool registered in native Chromium registry
|
|
319
|
+
// 2. Callback fires (registerToolsChangedCallback)
|
|
320
|
+
// 3. Tool synced to MCP bridge
|
|
321
|
+
// 4. MCP clients notified (tools/list_changed)
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
### Iframe Tool Collection (Native Only)
|
|
325
|
+
|
|
326
|
+
When the native API is active, tools from embedded iframes are **automatically collected**:
|
|
327
|
+
|
|
328
|
+
```html
|
|
329
|
+
<!-- parent.html -->
|
|
330
|
+
<script type="module">
|
|
331
|
+
import '@mcp-b/global';
|
|
332
|
+
|
|
333
|
+
// Native API will collect tools from this page AND all iframes
|
|
334
|
+
navigator.modelContext.registerTool({
|
|
335
|
+
name: 'parent-tool',
|
|
336
|
+
description: 'Tool from parent page',
|
|
337
|
+
inputSchema: { type: 'object', properties: {} },
|
|
338
|
+
async execute() {
|
|
339
|
+
return { content: [{ type: 'text', text: 'Parent tool' }] };
|
|
340
|
+
}
|
|
341
|
+
});
|
|
342
|
+
</script>
|
|
343
|
+
|
|
344
|
+
<iframe src="child.html"></iframe>
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
```html
|
|
348
|
+
<!-- child.html -->
|
|
349
|
+
<script type="module">
|
|
350
|
+
import '@mcp-b/global';
|
|
351
|
+
|
|
352
|
+
// This tool is automatically visible in parent's registry (native feature)
|
|
353
|
+
navigator.modelContext.registerTool({
|
|
354
|
+
name: 'child-tool',
|
|
355
|
+
description: 'Tool from iframe',
|
|
356
|
+
inputSchema: { type: 'object', properties: {} },
|
|
357
|
+
async execute() {
|
|
358
|
+
return { content: [{ type: 'text', text: 'Child tool' }] };
|
|
359
|
+
}
|
|
360
|
+
});
|
|
361
|
+
</script>
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
With native API, `navigator.modelContextTesting.listTools()` in the parent will show **both** tools! The MCP bridge stays in sync automatically.
|
|
365
|
+
|
|
366
|
+
### Detection in Console
|
|
367
|
+
|
|
368
|
+
When you initialize the package, check the console logs:
|
|
369
|
+
|
|
370
|
+
```
|
|
371
|
+
✅ [Web Model Context] Native Chromium API detected
|
|
372
|
+
Using native implementation with MCP bridge synchronization
|
|
373
|
+
Native API will automatically collect tools from embedded iframes
|
|
374
|
+
✅ [Web Model Context] MCP bridge synced with native API
|
|
375
|
+
MCP clients will receive automatic tool updates from native registry
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
Or if polyfill is used:
|
|
379
|
+
|
|
380
|
+
```
|
|
381
|
+
[Web Model Context] Native API not detected, installing polyfill
|
|
382
|
+
✅ [Web Model Context] window.navigator.modelContext initialized successfully
|
|
383
|
+
[Model Context Testing] Installing polyfill
|
|
384
|
+
✅ [Model Context Testing] Polyfill installed at window.navigator.modelContextTesting
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
## 📖 API Reference
|
|
388
|
+
|
|
389
|
+
### Two-Bucket Tool Management System
|
|
390
|
+
|
|
391
|
+
This package uses a **two-bucket system** for tool management to support both app-level and component-level tools:
|
|
392
|
+
|
|
393
|
+
- **Bucket A (Base Tools)**: Registered via `provideContext()` - represents your app's core functionality
|
|
394
|
+
- **Bucket B (Dynamic Tools)**: Registered via `registerTool()` - component-scoped tools that persist across `provideContext()` calls
|
|
395
|
+
|
|
396
|
+
**Key behaviors:**
|
|
397
|
+
- ✅ `provideContext()` only clears Bucket A, leaving Bucket B intact
|
|
398
|
+
- ✅ `registerTool()` adds to Bucket B and persists across `provideContext()` calls
|
|
399
|
+
- ✅ Tool name collisions between buckets throw an error
|
|
400
|
+
- ✅ Cannot `unregister()` a tool that was registered via `provideContext()`
|
|
401
|
+
|
|
402
|
+
**Use case:** React components can use `registerTool()` in `useEffect()` to manage tool lifecycle independently of the app's base tools.
|
|
403
|
+
|
|
404
|
+
### `window.navigator.modelContext.provideContext(context)`
|
|
405
|
+
|
|
406
|
+
Register base/app-level tools (Bucket A). **This clears Bucket A only** and replaces with the provided array. Dynamic tools (Bucket B) registered via `registerTool()` are NOT affected.
|
|
407
|
+
|
|
408
|
+
**Parameters:**
|
|
409
|
+
- `context.tools` - Array of tool descriptors
|
|
410
|
+
|
|
411
|
+
**Example:**
|
|
412
|
+
|
|
413
|
+
```javascript
|
|
414
|
+
window.navigator.modelContext.provideContext({
|
|
415
|
+
tools: [
|
|
416
|
+
{
|
|
417
|
+
name: "add-todo",
|
|
418
|
+
description: "Add a new todo item to the list",
|
|
419
|
+
inputSchema: {
|
|
420
|
+
type: "object",
|
|
421
|
+
properties: {
|
|
422
|
+
text: {
|
|
423
|
+
type: "string",
|
|
424
|
+
description: "The todo item text"
|
|
425
|
+
},
|
|
426
|
+
priority: {
|
|
427
|
+
type: "string",
|
|
428
|
+
enum: ["low", "medium", "high"],
|
|
429
|
+
description: "Priority level"
|
|
430
|
+
}
|
|
431
|
+
},
|
|
432
|
+
required: ["text"]
|
|
433
|
+
},
|
|
434
|
+
async execute({ text, priority = "medium" }) {
|
|
435
|
+
// Add todo to your app
|
|
436
|
+
const todo = addTodoItem(text, priority);
|
|
437
|
+
|
|
438
|
+
return {
|
|
439
|
+
content: [{
|
|
440
|
+
type: "text",
|
|
441
|
+
text: `Added todo: "${text}" with ${priority} priority`
|
|
442
|
+
}]
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
]
|
|
447
|
+
});
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
### `window.navigator.modelContext.registerTool(tool)`
|
|
451
|
+
|
|
452
|
+
Register a single tool dynamically (Bucket B). Tools registered this way:
|
|
453
|
+
- ✅ Persist across `provideContext()` calls
|
|
454
|
+
- ✅ Perfect for component lifecycle management
|
|
455
|
+
- ✅ Can be unregistered via the returned `unregister()` function
|
|
456
|
+
- ❌ Cannot have the same name as a tool in Bucket A (provideContext)
|
|
457
|
+
|
|
458
|
+
**Parameters:**
|
|
459
|
+
- `tool` - A single tool descriptor
|
|
460
|
+
|
|
461
|
+
**Returns:**
|
|
462
|
+
- Object with `unregister()` function to remove the tool
|
|
463
|
+
|
|
464
|
+
**Example:**
|
|
465
|
+
|
|
466
|
+
```javascript
|
|
467
|
+
// Register a tool dynamically (Bucket B)
|
|
468
|
+
const registration = window.navigator.modelContext.registerTool({
|
|
469
|
+
name: "get-timestamp",
|
|
470
|
+
description: "Get the current timestamp",
|
|
471
|
+
inputSchema: {
|
|
472
|
+
type: "object",
|
|
473
|
+
properties: {}
|
|
474
|
+
},
|
|
475
|
+
async execute() {
|
|
476
|
+
return {
|
|
477
|
+
content: [{
|
|
478
|
+
type: "text",
|
|
479
|
+
text: new Date().toISOString()
|
|
480
|
+
}]
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
});
|
|
484
|
+
|
|
485
|
+
// Later, unregister the tool
|
|
486
|
+
registration.unregister();
|
|
487
|
+
|
|
488
|
+
// Note: You can call provideContext() and this tool will still be registered!
|
|
489
|
+
window.navigator.modelContext.provideContext({
|
|
490
|
+
tools: [/* other tools */]
|
|
491
|
+
});
|
|
492
|
+
// "get-timestamp" is still available because it's in Bucket B
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
### Tool Descriptor
|
|
496
|
+
|
|
497
|
+
Each tool must have:
|
|
498
|
+
|
|
499
|
+
| Property | Type | Description |
|
|
500
|
+
|----------|------|-------------|
|
|
501
|
+
| `name` | `string` | Unique identifier for the tool |
|
|
502
|
+
| `description` | `string` | Natural language description of what the tool does |
|
|
503
|
+
| `inputSchema` | `object` | JSON Schema defining input parameters |
|
|
504
|
+
| `outputSchema` | `object` | Optional JSON Schema defining structured output |
|
|
505
|
+
| `annotations` | `object` | Optional hints about tool behavior |
|
|
506
|
+
| `execute` | `function` | Async function that implements the tool logic |
|
|
507
|
+
|
|
508
|
+
### Output Schemas (Structured Output)
|
|
509
|
+
|
|
510
|
+
**Output schemas are essential for modern AI integrations.** Many AI providers compile tool definitions into TypeScript definitions, enabling the AI to generate type-safe responses. Without an output schema, AI agents can only return unstructured text.
|
|
511
|
+
|
|
512
|
+
**Benefits of output schemas:**
|
|
513
|
+
- **Type-safe responses** - AI generates structured JSON matching your schema
|
|
514
|
+
- **Better AI reasoning** - AI understands the expected output format
|
|
515
|
+
- **Client validation** - Responses are validated against the schema
|
|
516
|
+
- **IDE support** - TypeScript types inferred from schemas
|
|
517
|
+
|
|
518
|
+
#### Basic Output Schema Example
|
|
519
|
+
|
|
520
|
+
```javascript
|
|
521
|
+
window.navigator.modelContext.provideContext({
|
|
522
|
+
tools: [
|
|
523
|
+
{
|
|
524
|
+
name: "get-user-profile",
|
|
525
|
+
description: "Fetch a user's profile information",
|
|
526
|
+
inputSchema: {
|
|
527
|
+
type: "object",
|
|
528
|
+
properties: {
|
|
529
|
+
userId: { type: "string", description: "The user ID" }
|
|
530
|
+
},
|
|
531
|
+
required: ["userId"]
|
|
532
|
+
},
|
|
533
|
+
// Define the structured output format
|
|
534
|
+
outputSchema: {
|
|
535
|
+
type: "object",
|
|
536
|
+
properties: {
|
|
537
|
+
id: { type: "string", description: "User ID" },
|
|
538
|
+
name: { type: "string", description: "Display name" },
|
|
539
|
+
email: { type: "string", description: "Email address" },
|
|
540
|
+
createdAt: { type: "string", description: "ISO date string" }
|
|
541
|
+
},
|
|
542
|
+
required: ["id", "name", "email"]
|
|
543
|
+
},
|
|
544
|
+
async execute({ userId }) {
|
|
545
|
+
const user = await fetchUser(userId);
|
|
546
|
+
return {
|
|
547
|
+
content: [{ type: "text", text: `Found user: ${user.name}` }],
|
|
548
|
+
// Structured content matching the outputSchema
|
|
549
|
+
structuredContent: {
|
|
550
|
+
id: user.id,
|
|
551
|
+
name: user.name,
|
|
552
|
+
email: user.email,
|
|
553
|
+
createdAt: user.createdAt.toISOString()
|
|
554
|
+
}
|
|
555
|
+
};
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
]
|
|
559
|
+
});
|
|
560
|
+
```
|
|
561
|
+
|
|
562
|
+
#### Using Zod for Type-Safe Schemas
|
|
563
|
+
|
|
564
|
+
For TypeScript projects, you can use Zod schemas for both input and output validation. Zod schemas are automatically converted to JSON Schema:
|
|
565
|
+
|
|
566
|
+
```typescript
|
|
567
|
+
import { z } from 'zod';
|
|
568
|
+
|
|
569
|
+
window.navigator.modelContext.provideContext({
|
|
570
|
+
tools: [
|
|
571
|
+
{
|
|
572
|
+
name: "search-products",
|
|
573
|
+
description: "Search the product catalog",
|
|
574
|
+
inputSchema: {
|
|
575
|
+
query: z.string().describe("Search query"),
|
|
576
|
+
limit: z.number().min(1).max(100).default(10).describe("Max results"),
|
|
577
|
+
category: z.enum(["electronics", "clothing", "books"]).optional()
|
|
578
|
+
},
|
|
579
|
+
// Zod schema for output - provides TypeScript types
|
|
580
|
+
outputSchema: {
|
|
581
|
+
products: z.array(z.object({
|
|
582
|
+
id: z.string(),
|
|
583
|
+
name: z.string(),
|
|
584
|
+
price: z.number(),
|
|
585
|
+
inStock: z.boolean()
|
|
586
|
+
})),
|
|
587
|
+
total: z.number().describe("Total matching products"),
|
|
588
|
+
hasMore: z.boolean().describe("Whether more results exist")
|
|
589
|
+
},
|
|
590
|
+
async execute({ query, limit, category }) {
|
|
591
|
+
const results = await searchProducts({ query, limit, category });
|
|
592
|
+
return {
|
|
593
|
+
content: [{ type: "text", text: `Found ${results.total} products` }],
|
|
594
|
+
structuredContent: {
|
|
595
|
+
products: results.items,
|
|
596
|
+
total: results.total,
|
|
597
|
+
hasMore: results.total > limit
|
|
598
|
+
}
|
|
599
|
+
};
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
]
|
|
603
|
+
});
|
|
604
|
+
```
|
|
605
|
+
|
|
606
|
+
#### Complex Output Schema Example
|
|
607
|
+
|
|
608
|
+
For tools that return rich data structures:
|
|
609
|
+
|
|
610
|
+
```javascript
|
|
611
|
+
window.navigator.modelContext.provideContext({
|
|
612
|
+
tools: [
|
|
613
|
+
{
|
|
614
|
+
name: "analyze-code",
|
|
615
|
+
description: "Analyze code for issues and suggestions",
|
|
616
|
+
inputSchema: {
|
|
617
|
+
type: "object",
|
|
618
|
+
properties: {
|
|
619
|
+
code: { type: "string", description: "Source code to analyze" },
|
|
620
|
+
language: { type: "string", enum: ["javascript", "typescript", "python"] }
|
|
621
|
+
},
|
|
622
|
+
required: ["code", "language"]
|
|
623
|
+
},
|
|
624
|
+
outputSchema: {
|
|
625
|
+
type: "object",
|
|
626
|
+
properties: {
|
|
627
|
+
summary: {
|
|
628
|
+
type: "object",
|
|
629
|
+
properties: {
|
|
630
|
+
linesOfCode: { type: "number" },
|
|
631
|
+
complexity: { type: "string", enum: ["low", "medium", "high"] }
|
|
632
|
+
}
|
|
633
|
+
},
|
|
634
|
+
issues: {
|
|
635
|
+
type: "array",
|
|
636
|
+
items: {
|
|
637
|
+
type: "object",
|
|
638
|
+
properties: {
|
|
639
|
+
severity: { type: "string", enum: ["error", "warning", "info"] },
|
|
640
|
+
line: { type: "number" },
|
|
641
|
+
message: { type: "string" },
|
|
642
|
+
suggestion: { type: "string" }
|
|
643
|
+
},
|
|
644
|
+
required: ["severity", "line", "message"]
|
|
645
|
+
}
|
|
646
|
+
},
|
|
647
|
+
score: {
|
|
648
|
+
type: "number",
|
|
649
|
+
minimum: 0,
|
|
650
|
+
maximum: 100,
|
|
651
|
+
description: "Code quality score"
|
|
652
|
+
}
|
|
653
|
+
},
|
|
654
|
+
required: ["summary", "issues", "score"]
|
|
655
|
+
},
|
|
656
|
+
async execute({ code, language }) {
|
|
657
|
+
const analysis = await analyzeCode(code, language);
|
|
658
|
+
return {
|
|
659
|
+
content: [{ type: "text", text: `Quality score: ${analysis.score}/100` }],
|
|
660
|
+
structuredContent: analysis
|
|
661
|
+
};
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
]
|
|
665
|
+
});
|
|
666
|
+
```
|
|
667
|
+
|
|
668
|
+
### Tool Response Format
|
|
669
|
+
|
|
670
|
+
Tools must return an object with:
|
|
671
|
+
|
|
672
|
+
```typescript
|
|
673
|
+
{
|
|
674
|
+
content: [
|
|
675
|
+
{
|
|
676
|
+
type: "text", // or "image", "resource"
|
|
677
|
+
text: "Result..." // the response content
|
|
678
|
+
}
|
|
679
|
+
],
|
|
680
|
+
isError?: boolean // optional error flag
|
|
681
|
+
}
|
|
682
|
+
```
|
|
683
|
+
|
|
684
|
+
## 🎯 Complete Examples
|
|
685
|
+
|
|
686
|
+
### Todo List Application
|
|
687
|
+
|
|
688
|
+
```javascript
|
|
689
|
+
let todos = [];
|
|
690
|
+
|
|
691
|
+
window.navigator.modelContext.provideContext({
|
|
692
|
+
tools: [
|
|
693
|
+
{
|
|
694
|
+
name: "add-todo",
|
|
695
|
+
description: "Add a new todo item",
|
|
696
|
+
inputSchema: {
|
|
697
|
+
type: "object",
|
|
698
|
+
properties: {
|
|
699
|
+
text: { type: "string", description: "Todo text" }
|
|
700
|
+
},
|
|
701
|
+
required: ["text"]
|
|
702
|
+
},
|
|
703
|
+
async execute({ text }) {
|
|
704
|
+
const todo = { id: Date.now(), text, done: false };
|
|
705
|
+
todos.push(todo);
|
|
706
|
+
updateUI();
|
|
707
|
+
return {
|
|
708
|
+
content: [{ type: "text", text: `Added: "${text}"` }]
|
|
709
|
+
};
|
|
710
|
+
}
|
|
711
|
+
},
|
|
712
|
+
{
|
|
713
|
+
name: "list-todos",
|
|
714
|
+
description: "Get all todo items",
|
|
715
|
+
inputSchema: { type: "object", properties: {} },
|
|
716
|
+
async execute() {
|
|
717
|
+
const list = todos.map(t =>
|
|
718
|
+
`${t.done ? '✓' : '○'} ${t.text}`
|
|
719
|
+
).join('\n');
|
|
720
|
+
return {
|
|
721
|
+
content: [{ type: "text", text: list || "No todos" }]
|
|
722
|
+
};
|
|
723
|
+
}
|
|
724
|
+
},
|
|
725
|
+
{
|
|
726
|
+
name: "complete-todo",
|
|
727
|
+
description: "Mark a todo as complete",
|
|
728
|
+
inputSchema: {
|
|
729
|
+
type: "object",
|
|
730
|
+
properties: {
|
|
731
|
+
id: { type: "number", description: "Todo ID" }
|
|
732
|
+
},
|
|
733
|
+
required: ["id"]
|
|
734
|
+
},
|
|
735
|
+
async execute({ id }) {
|
|
736
|
+
const todo = todos.find(t => t.id === id);
|
|
737
|
+
if (!todo) {
|
|
738
|
+
return {
|
|
739
|
+
content: [{ type: "text", text: "Todo not found" }],
|
|
740
|
+
isError: true
|
|
741
|
+
};
|
|
742
|
+
}
|
|
743
|
+
todo.done = true;
|
|
744
|
+
updateUI();
|
|
745
|
+
return {
|
|
746
|
+
content: [{ type: "text", text: `Completed: "${todo.text}"` }]
|
|
747
|
+
};
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
]
|
|
751
|
+
});
|
|
752
|
+
|
|
753
|
+
function updateUI() {
|
|
754
|
+
// Update your UI
|
|
755
|
+
document.getElementById('todo-list').innerHTML =
|
|
756
|
+
todos.map(t => `<li>${t.done ? '✓' : ''} ${t.text}</li>`).join('');
|
|
757
|
+
}
|
|
758
|
+
```
|
|
759
|
+
|
|
760
|
+
### E-commerce Product Search
|
|
761
|
+
|
|
762
|
+
```javascript
|
|
763
|
+
window.navigator.modelContext.provideContext({
|
|
764
|
+
tools: [
|
|
765
|
+
{
|
|
766
|
+
name: "search-products",
|
|
767
|
+
description: "Search for products in the catalog",
|
|
768
|
+
inputSchema: {
|
|
769
|
+
type: "object",
|
|
770
|
+
properties: {
|
|
771
|
+
query: {
|
|
772
|
+
type: "string",
|
|
773
|
+
description: "Search query"
|
|
774
|
+
},
|
|
775
|
+
category: {
|
|
776
|
+
type: "string",
|
|
777
|
+
description: "Filter by category",
|
|
778
|
+
enum: ["electronics", "clothing", "books", "all"]
|
|
779
|
+
},
|
|
780
|
+
maxPrice: {
|
|
781
|
+
type: "number",
|
|
782
|
+
description: "Maximum price filter"
|
|
783
|
+
}
|
|
784
|
+
},
|
|
785
|
+
required: ["query"]
|
|
786
|
+
},
|
|
787
|
+
async execute({ query, category = "all", maxPrice }) {
|
|
788
|
+
const results = await searchProducts({
|
|
789
|
+
query,
|
|
790
|
+
category: category !== "all" ? category : undefined,
|
|
791
|
+
maxPrice
|
|
792
|
+
});
|
|
793
|
+
|
|
794
|
+
const summary = results.map(p =>
|
|
795
|
+
`${p.name} - $${p.price} (${p.category})`
|
|
796
|
+
).join('\n');
|
|
797
|
+
|
|
798
|
+
return {
|
|
799
|
+
content: [{
|
|
800
|
+
type: "text",
|
|
801
|
+
text: `Found ${results.length} products:\n${summary}`
|
|
802
|
+
}]
|
|
803
|
+
};
|
|
804
|
+
}
|
|
805
|
+
},
|
|
806
|
+
{
|
|
807
|
+
name: "add-to-cart",
|
|
808
|
+
description: "Add a product to the shopping cart",
|
|
809
|
+
inputSchema: {
|
|
810
|
+
type: "object",
|
|
811
|
+
properties: {
|
|
812
|
+
productId: { type: "string" },
|
|
813
|
+
quantity: { type: "number", default: 1 }
|
|
814
|
+
},
|
|
815
|
+
required: ["productId"]
|
|
816
|
+
},
|
|
817
|
+
async execute({ productId, quantity = 1 }) {
|
|
818
|
+
await addToCart(productId, quantity);
|
|
819
|
+
return {
|
|
820
|
+
content: [{
|
|
821
|
+
type: "text",
|
|
822
|
+
text: `Added ${quantity}x product ${productId} to cart`
|
|
823
|
+
}]
|
|
824
|
+
};
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
]
|
|
828
|
+
});
|
|
829
|
+
```
|
|
830
|
+
|
|
831
|
+
## 🔧 Dynamic Tool Registration (Component Lifecycle)
|
|
832
|
+
|
|
833
|
+
### React Component Example
|
|
834
|
+
|
|
835
|
+
Perfect for managing tools tied to component lifecycle:
|
|
836
|
+
|
|
837
|
+
```javascript
|
|
838
|
+
import { useEffect } from 'react';
|
|
839
|
+
|
|
840
|
+
function MyComponent() {
|
|
841
|
+
useEffect(() => {
|
|
842
|
+
// Register component-specific tool when component mounts (Bucket B)
|
|
843
|
+
const registration = window.navigator.modelContext.registerTool({
|
|
844
|
+
name: "component-action",
|
|
845
|
+
description: "Action specific to this component",
|
|
846
|
+
inputSchema: { type: "object", properties: {} },
|
|
847
|
+
async execute() {
|
|
848
|
+
// Access component state/methods here
|
|
849
|
+
return {
|
|
850
|
+
content: [{ type: "text", text: "Component action executed!" }]
|
|
851
|
+
};
|
|
852
|
+
}
|
|
853
|
+
});
|
|
854
|
+
|
|
855
|
+
// Cleanup: unregister when component unmounts
|
|
856
|
+
return () => {
|
|
857
|
+
registration.unregister();
|
|
858
|
+
};
|
|
859
|
+
}, []);
|
|
860
|
+
|
|
861
|
+
return <div>My Component</div>;
|
|
862
|
+
}
|
|
863
|
+
```
|
|
864
|
+
|
|
865
|
+
### Persistence Across provideContext() Calls
|
|
866
|
+
|
|
867
|
+
```javascript
|
|
868
|
+
// Step 1: Register base tools (Bucket A)
|
|
869
|
+
window.navigator.modelContext.provideContext({
|
|
870
|
+
tools: [
|
|
871
|
+
{ name: "base-tool-1", description: "Base tool", inputSchema: {}, async execute() {} }
|
|
872
|
+
]
|
|
873
|
+
});
|
|
874
|
+
// Tools: ["base-tool-1"]
|
|
875
|
+
|
|
876
|
+
// Step 2: Register dynamic tool (Bucket B)
|
|
877
|
+
const reg = window.navigator.modelContext.registerTool({
|
|
878
|
+
name: "dynamic-tool",
|
|
879
|
+
description: "Dynamic tool",
|
|
880
|
+
inputSchema: { type: "object", properties: {} },
|
|
881
|
+
async execute() {
|
|
882
|
+
return { content: [{ type: "text", text: "Dynamic!" }] };
|
|
883
|
+
}
|
|
884
|
+
});
|
|
885
|
+
// Tools: ["base-tool-1", "dynamic-tool"]
|
|
886
|
+
|
|
887
|
+
// Step 3: Update base tools via provideContext
|
|
888
|
+
window.navigator.modelContext.provideContext({
|
|
889
|
+
tools: [
|
|
890
|
+
{ name: "base-tool-2", description: "New base tool", inputSchema: {}, async execute() {} }
|
|
891
|
+
]
|
|
892
|
+
});
|
|
893
|
+
// Tools: ["base-tool-2", "dynamic-tool"]
|
|
894
|
+
// ✅ "dynamic-tool" persists! Only "base-tool-1" was cleared
|
|
895
|
+
|
|
896
|
+
// Step 4: Clean up dynamic tool
|
|
897
|
+
reg.unregister();
|
|
898
|
+
// Tools: ["base-tool-2"]
|
|
899
|
+
```
|
|
900
|
+
|
|
901
|
+
### Name Collision Protection
|
|
902
|
+
|
|
903
|
+
```javascript
|
|
904
|
+
// Register a base tool
|
|
905
|
+
window.navigator.modelContext.provideContext({
|
|
906
|
+
tools: [
|
|
907
|
+
{ name: "my-tool", description: "Base", inputSchema: {}, async execute() {} }
|
|
908
|
+
]
|
|
909
|
+
});
|
|
910
|
+
|
|
911
|
+
// This will throw an error!
|
|
912
|
+
try {
|
|
913
|
+
window.navigator.modelContext.registerTool({
|
|
914
|
+
name: "my-tool", // ❌ Name collision with Bucket A
|
|
915
|
+
description: "Dynamic",
|
|
916
|
+
inputSchema: {},
|
|
917
|
+
async execute() {}
|
|
918
|
+
});
|
|
919
|
+
} catch (error) {
|
|
920
|
+
console.error(error.message);
|
|
921
|
+
// Error: Tool name collision: "my-tool" is already registered via provideContext()
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
// Similarly, can't unregister a base tool
|
|
925
|
+
const baseToolList = window.navigator.modelContext.provideContext({
|
|
926
|
+
tools: [{ name: "base", description: "Base", inputSchema: {}, async execute() {} }]
|
|
927
|
+
});
|
|
928
|
+
|
|
929
|
+
// This will also throw an error!
|
|
930
|
+
try {
|
|
931
|
+
// Assuming we got a reference somehow
|
|
932
|
+
// registration.unregister(); would fail for a base tool
|
|
933
|
+
} catch (error) {
|
|
934
|
+
// Error: Cannot unregister tool "base": This tool was registered via provideContext()
|
|
935
|
+
}
|
|
936
|
+
```
|
|
937
|
+
|
|
938
|
+
## 🔧 Event-Based Tool Calls (Advanced)
|
|
939
|
+
|
|
940
|
+
For manifest-based or advanced scenarios, you can handle tool calls as events:
|
|
941
|
+
|
|
942
|
+
```javascript
|
|
943
|
+
window.navigator.modelContext.addEventListener('toolcall', async (event) => {
|
|
944
|
+
console.log(`Tool called: ${event.name}`, event.arguments);
|
|
945
|
+
|
|
946
|
+
if (event.name === "custom-tool") {
|
|
947
|
+
// Prevent default execution
|
|
948
|
+
event.preventDefault();
|
|
949
|
+
|
|
950
|
+
// Provide custom response
|
|
951
|
+
event.respondWith({
|
|
952
|
+
content: [{
|
|
953
|
+
type: "text",
|
|
954
|
+
text: "Custom response from event handler"
|
|
955
|
+
}]
|
|
956
|
+
});
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
// If not prevented, the tool's execute function will run normally
|
|
960
|
+
});
|
|
961
|
+
```
|
|
962
|
+
|
|
963
|
+
### Hybrid Approach
|
|
964
|
+
|
|
965
|
+
The API supports both approaches simultaneously:
|
|
966
|
+
|
|
967
|
+
1. **Event dispatched first** - `toolcall` event is fired
|
|
968
|
+
2. **Event can override** - Call `event.preventDefault()` and `event.respondWith()`
|
|
969
|
+
3. **Default execution** - If not prevented, the tool's `execute()` function runs
|
|
970
|
+
|
|
971
|
+
This allows flexibility for different use cases.
|
|
972
|
+
|
|
973
|
+
## 🏗️ Architecture
|
|
974
|
+
|
|
975
|
+
```
|
|
976
|
+
┌─────────────────┐
|
|
977
|
+
│ AI Agent │
|
|
978
|
+
│ (MCP Client) │
|
|
979
|
+
└────────┬────────┘
|
|
980
|
+
│ MCP Protocol
|
|
981
|
+
│ (JSON-RPC)
|
|
982
|
+
┌────────▼────────┐
|
|
983
|
+
│ MCP Server │
|
|
984
|
+
│ (Internal) │
|
|
985
|
+
└────────┬────────┘
|
|
986
|
+
│
|
|
987
|
+
┌────────▼───────────────────┐
|
|
988
|
+
│ navigator.modelContext │ ◄── Your app registers tools here
|
|
989
|
+
│ (This pkg) │
|
|
990
|
+
└────────────────────────────┘
|
|
991
|
+
```
|
|
992
|
+
|
|
993
|
+
This package:
|
|
994
|
+
1. Exposes `window.navigator.modelContext` API (W3C Web Model Context standard)
|
|
995
|
+
2. Internally creates an MCP Server
|
|
996
|
+
3. Bridges tool calls between the two protocols
|
|
997
|
+
4. Uses TabServerTransport for browser communication
|
|
998
|
+
|
|
999
|
+
## 🔍 Feature Detection
|
|
1000
|
+
|
|
1001
|
+
Check if the API is available:
|
|
1002
|
+
|
|
1003
|
+
```javascript
|
|
1004
|
+
if ("modelContext" in navigator) {
|
|
1005
|
+
// API is available
|
|
1006
|
+
navigator.modelContext.provideContext({ tools: [...] });
|
|
1007
|
+
} else {
|
|
1008
|
+
console.warn("Web Model Context API not available");
|
|
1009
|
+
}
|
|
1010
|
+
```
|
|
1011
|
+
|
|
1012
|
+
## 🐛 Debugging
|
|
1013
|
+
|
|
1014
|
+
In development mode, access the internal bridge:
|
|
1015
|
+
|
|
1016
|
+
```javascript
|
|
1017
|
+
if (window.__mcpBridge) {
|
|
1018
|
+
console.log("MCP Server:", window.__mcpBridge.server);
|
|
1019
|
+
console.log("Registered tools:", window.__mcpBridge.tools);
|
|
1020
|
+
}
|
|
1021
|
+
```
|
|
1022
|
+
|
|
1023
|
+
## 🧪 Testing API (`navigator.modelContextTesting`)
|
|
1024
|
+
|
|
1025
|
+
This package provides a **Model Context Testing API** at `window.navigator.modelContextTesting` for debugging and testing your tools during development.
|
|
1026
|
+
|
|
1027
|
+
### Native Support in Chromium
|
|
1028
|
+
|
|
1029
|
+
**IMPORTANT**: The `modelContextTesting` API is available natively in Chromium-based browsers when the experimental feature flag is enabled. This polyfill will detect and use the native implementation when available.
|
|
1030
|
+
|
|
1031
|
+
#### How to Enable Native API in Chromium:
|
|
1032
|
+
|
|
1033
|
+
**Option 1: Chrome Flags**
|
|
1034
|
+
1. Navigate to `chrome://flags`
|
|
1035
|
+
2. Search for "Experimental Web Platform Features"
|
|
1036
|
+
3. Enable the flag
|
|
1037
|
+
4. Restart your browser
|
|
1038
|
+
|
|
1039
|
+
**Option 2: Command Line**
|
|
1040
|
+
```bash
|
|
1041
|
+
# Launch Chrome/Edge with experimental features
|
|
1042
|
+
chrome --enable-experimental-web-platform-features
|
|
1043
|
+
```
|
|
1044
|
+
|
|
1045
|
+
**Detection**: When the native API is detected, you'll see this console message:
|
|
1046
|
+
```
|
|
1047
|
+
✅ [Model Context Testing] Native implementation detected (Chromium experimental feature)
|
|
1048
|
+
Using native window.navigator.modelContextTesting from browser
|
|
1049
|
+
```
|
|
1050
|
+
|
|
1051
|
+
### Polyfill Fallback
|
|
1052
|
+
|
|
1053
|
+
If the native API is not available, this package automatically provides a polyfill implementation with the same interface:
|
|
1054
|
+
|
|
1055
|
+
```
|
|
1056
|
+
[Model Context Testing] Native implementation not found, installing polyfill
|
|
1057
|
+
💡 To use the native implementation in Chromium:
|
|
1058
|
+
- Navigate to chrome://flags
|
|
1059
|
+
- Enable "Experimental Web Platform Features"
|
|
1060
|
+
- Or launch with: --enable-experimental-web-platform-features
|
|
1061
|
+
✅ [Model Context Testing] Polyfill installed at window.navigator.modelContextTesting
|
|
1062
|
+
```
|
|
1063
|
+
|
|
1064
|
+
### API Reference
|
|
1065
|
+
|
|
1066
|
+
#### `getToolCalls(): Array<ToolCall>`
|
|
1067
|
+
|
|
1068
|
+
Get a history of all tool calls made during the session.
|
|
1069
|
+
|
|
1070
|
+
```javascript
|
|
1071
|
+
// Register and call some tools
|
|
1072
|
+
window.navigator.modelContext.provideContext({
|
|
1073
|
+
tools: [{
|
|
1074
|
+
name: "greet",
|
|
1075
|
+
description: "Greet a user",
|
|
1076
|
+
inputSchema: {
|
|
1077
|
+
type: "object",
|
|
1078
|
+
properties: { name: { type: "string" } },
|
|
1079
|
+
required: ["name"]
|
|
1080
|
+
},
|
|
1081
|
+
async execute({ name }) {
|
|
1082
|
+
return { content: [{ type: "text", text: `Hello, ${name}!` }] };
|
|
1083
|
+
}
|
|
1084
|
+
}]
|
|
1085
|
+
});
|
|
1086
|
+
|
|
1087
|
+
// Simulate a tool call
|
|
1088
|
+
// (In practice, this would come from an AI agent)
|
|
1089
|
+
|
|
1090
|
+
// Later, inspect the tool call history
|
|
1091
|
+
const calls = window.navigator.modelContextTesting.getToolCalls();
|
|
1092
|
+
console.log(calls);
|
|
1093
|
+
// [
|
|
1094
|
+
// {
|
|
1095
|
+
// toolName: "greet",
|
|
1096
|
+
// arguments: { name: "Alice" },
|
|
1097
|
+
// timestamp: 1699123456789
|
|
1098
|
+
// }
|
|
1099
|
+
// ]
|
|
1100
|
+
```
|
|
1101
|
+
|
|
1102
|
+
#### `clearToolCalls(): void`
|
|
1103
|
+
|
|
1104
|
+
Clear the tool call history.
|
|
1105
|
+
|
|
1106
|
+
```javascript
|
|
1107
|
+
window.navigator.modelContextTesting.clearToolCalls();
|
|
1108
|
+
console.log(window.navigator.modelContextTesting.getToolCalls()); // []
|
|
1109
|
+
```
|
|
1110
|
+
|
|
1111
|
+
#### `setMockToolResponse(toolName: string, response: ToolResponse): void`
|
|
1112
|
+
|
|
1113
|
+
Set a mock response for a specific tool. When set, the tool's `execute()` function will be bypassed and the mock response will be returned instead.
|
|
1114
|
+
|
|
1115
|
+
```javascript
|
|
1116
|
+
// Mock the "greet" tool to always return a specific response
|
|
1117
|
+
window.navigator.modelContextTesting.setMockToolResponse("greet", {
|
|
1118
|
+
content: [{
|
|
1119
|
+
type: "text",
|
|
1120
|
+
text: "Mocked greeting!"
|
|
1121
|
+
}]
|
|
1122
|
+
});
|
|
1123
|
+
|
|
1124
|
+
// Now when the tool is called, it returns the mock response
|
|
1125
|
+
// (The execute function is never called)
|
|
1126
|
+
```
|
|
1127
|
+
|
|
1128
|
+
#### `clearMockToolResponse(toolName: string): void`
|
|
1129
|
+
|
|
1130
|
+
Remove the mock response for a specific tool.
|
|
1131
|
+
|
|
1132
|
+
```javascript
|
|
1133
|
+
window.navigator.modelContextTesting.clearMockToolResponse("greet");
|
|
1134
|
+
// Tool will now use its actual execute function
|
|
1135
|
+
```
|
|
1136
|
+
|
|
1137
|
+
#### `clearAllMockToolResponses(): void`
|
|
1138
|
+
|
|
1139
|
+
Remove all mock tool responses.
|
|
1140
|
+
|
|
1141
|
+
```javascript
|
|
1142
|
+
window.navigator.modelContextTesting.clearAllMockToolResponses();
|
|
1143
|
+
```
|
|
1144
|
+
|
|
1145
|
+
#### `getRegisteredTools(): Array<ToolDescriptor>`
|
|
1146
|
+
|
|
1147
|
+
Get the list of all currently registered tools (same as `modelContext.listTools()`).
|
|
1148
|
+
|
|
1149
|
+
```javascript
|
|
1150
|
+
const tools = window.navigator.modelContextTesting.getRegisteredTools();
|
|
1151
|
+
console.log(tools.map(t => t.name)); // ["greet", "add-todo", ...]
|
|
1152
|
+
```
|
|
1153
|
+
|
|
1154
|
+
#### `reset(): void`
|
|
1155
|
+
|
|
1156
|
+
Reset the entire testing state (clears tool call history and all mock responses).
|
|
1157
|
+
|
|
1158
|
+
```javascript
|
|
1159
|
+
window.navigator.modelContextTesting.reset();
|
|
1160
|
+
```
|
|
1161
|
+
|
|
1162
|
+
### Testing Workflow Example
|
|
1163
|
+
|
|
1164
|
+
Here's a complete example of using the testing API:
|
|
1165
|
+
|
|
1166
|
+
```javascript
|
|
1167
|
+
// 1. Register your tools
|
|
1168
|
+
window.navigator.modelContext.provideContext({
|
|
1169
|
+
tools: [
|
|
1170
|
+
{
|
|
1171
|
+
name: "add-todo",
|
|
1172
|
+
description: "Add a todo item",
|
|
1173
|
+
inputSchema: {
|
|
1174
|
+
type: "object",
|
|
1175
|
+
properties: { text: { type: "string" } },
|
|
1176
|
+
required: ["text"]
|
|
1177
|
+
},
|
|
1178
|
+
async execute({ text }) {
|
|
1179
|
+
// This would normally add to your app state
|
|
1180
|
+
return { content: [{ type: "text", text: `Added: ${text}` }] };
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
]
|
|
1184
|
+
});
|
|
1185
|
+
|
|
1186
|
+
// 2. Set up mocks for testing
|
|
1187
|
+
window.navigator.modelContextTesting.setMockToolResponse("add-todo", {
|
|
1188
|
+
content: [{ type: "text", text: "Mock: Todo added successfully" }]
|
|
1189
|
+
});
|
|
1190
|
+
|
|
1191
|
+
// 3. Simulate tool calls (or let AI agent call them)
|
|
1192
|
+
// The tool will return the mock response instead of executing
|
|
1193
|
+
|
|
1194
|
+
// 4. Inspect tool call history
|
|
1195
|
+
const calls = window.navigator.modelContextTesting.getToolCalls();
|
|
1196
|
+
console.log(`${calls.length} tool calls made`);
|
|
1197
|
+
calls.forEach(call => {
|
|
1198
|
+
console.log(`- ${call.toolName}`, call.arguments);
|
|
1199
|
+
});
|
|
1200
|
+
|
|
1201
|
+
// 5. Clean up after testing
|
|
1202
|
+
window.navigator.modelContextTesting.reset();
|
|
1203
|
+
```
|
|
1204
|
+
|
|
1205
|
+
### Integration Testing Example
|
|
1206
|
+
|
|
1207
|
+
Perfect for automated testing with frameworks like Jest, Vitest, or Playwright:
|
|
1208
|
+
|
|
1209
|
+
```javascript
|
|
1210
|
+
// test/model-context.test.js
|
|
1211
|
+
import { test, expect } from 'vitest';
|
|
1212
|
+
|
|
1213
|
+
test('todo tool creates correct response', async () => {
|
|
1214
|
+
// Arrange
|
|
1215
|
+
const mockResponse = {
|
|
1216
|
+
content: [{ type: "text", text: "Test todo added" }]
|
|
1217
|
+
};
|
|
1218
|
+
|
|
1219
|
+
window.navigator.modelContextTesting.setMockToolResponse(
|
|
1220
|
+
"add-todo",
|
|
1221
|
+
mockResponse
|
|
1222
|
+
);
|
|
1223
|
+
|
|
1224
|
+
// Act
|
|
1225
|
+
// Trigger your AI agent or directly call the tool via MCP
|
|
1226
|
+
// ...
|
|
1227
|
+
|
|
1228
|
+
// Assert
|
|
1229
|
+
const calls = window.navigator.modelContextTesting.getToolCalls();
|
|
1230
|
+
expect(calls).toHaveLength(1);
|
|
1231
|
+
expect(calls[0].toolName).toBe("add-todo");
|
|
1232
|
+
expect(calls[0].arguments).toEqual({ text: "Test item" });
|
|
1233
|
+
|
|
1234
|
+
// Cleanup
|
|
1235
|
+
window.navigator.modelContextTesting.reset();
|
|
1236
|
+
});
|
|
1237
|
+
```
|
|
1238
|
+
|
|
1239
|
+
### Browser Compatibility
|
|
1240
|
+
|
|
1241
|
+
| Browser | Native Support | Polyfill |
|
|
1242
|
+
|---------|---------------|----------|
|
|
1243
|
+
| Chrome/Edge (with flag) | ✅ Yes | N/A |
|
|
1244
|
+
| Chrome/Edge (default) | ❌ No | ✅ Yes |
|
|
1245
|
+
| Firefox | ❌ No | ✅ Yes |
|
|
1246
|
+
| Safari | ❌ No | ✅ Yes |
|
|
1247
|
+
| Other browsers | ❌ No | ✅ Yes |
|
|
1248
|
+
|
|
1249
|
+
The polyfill automatically detects and defers to the native implementation when available, ensuring forward compatibility as browsers adopt this standard.
|
|
1250
|
+
|
|
1251
|
+
## 📦 What's Included
|
|
1252
|
+
|
|
1253
|
+
- **Web Model Context API** - Standard `window.navigator.modelContext` interface
|
|
1254
|
+
- **Model Context Testing API** - `window.navigator.modelContextTesting` for debugging and testing (with native Chromium support detection)
|
|
1255
|
+
- **Dynamic Tool Registration** - `registerTool()` with `unregister()` function
|
|
1256
|
+
- **MCP Bridge** - Automatic bridging to Model Context Protocol
|
|
1257
|
+
- **Tab Transport** - Communication layer for browser contexts
|
|
1258
|
+
- **Event System** - Hybrid tool call handling
|
|
1259
|
+
- **TypeScript Types** - Full type definitions included
|
|
1260
|
+
|
|
1261
|
+
## 🔒 Security Considerations
|
|
1262
|
+
|
|
1263
|
+
### Origin Restrictions
|
|
1264
|
+
|
|
1265
|
+
By default, the MCP transport allows connections from any origin (`*`). For production, you should configure allowed origins:
|
|
1266
|
+
|
|
1267
|
+
```javascript
|
|
1268
|
+
// Future configuration API
|
|
1269
|
+
window.navigator.modelContext.configure({
|
|
1270
|
+
allowedOrigins: [
|
|
1271
|
+
'https://your-app.com',
|
|
1272
|
+
'https://trusted-agent.com'
|
|
1273
|
+
]
|
|
1274
|
+
});
|
|
1275
|
+
```
|
|
1276
|
+
|
|
1277
|
+
### Tool Validation
|
|
1278
|
+
|
|
1279
|
+
Always validate inputs in your tool implementations:
|
|
1280
|
+
|
|
1281
|
+
```javascript
|
|
1282
|
+
{
|
|
1283
|
+
name: "delete-item",
|
|
1284
|
+
description: "Delete an item",
|
|
1285
|
+
inputSchema: {
|
|
1286
|
+
type: "object",
|
|
1287
|
+
properties: {
|
|
1288
|
+
id: { type: "string", pattern: "^[a-zA-Z0-9]+$" }
|
|
1289
|
+
},
|
|
1290
|
+
required: ["id"]
|
|
1291
|
+
},
|
|
1292
|
+
async execute({ id }) {
|
|
1293
|
+
// Additional validation
|
|
1294
|
+
if (!isValidId(id)) {
|
|
1295
|
+
return {
|
|
1296
|
+
content: [{ type: "text", text: "Invalid ID" }],
|
|
1297
|
+
isError: true
|
|
1298
|
+
};
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1301
|
+
// Proceed with deletion
|
|
1302
|
+
await deleteItem(id);
|
|
1303
|
+
return {
|
|
1304
|
+
content: [{ type: "text", text: "Item deleted" }]
|
|
1305
|
+
};
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
```
|
|
1309
|
+
|
|
1310
|
+
## Frequently Asked Questions
|
|
1311
|
+
|
|
1312
|
+
### How do AI agents connect to my website?
|
|
1313
|
+
|
|
1314
|
+
AI agents connect through browser extensions or the `@mcp-b/chrome-devtools-mcp` server, which bridges desktop AI clients to browser-based tools.
|
|
1315
|
+
|
|
1316
|
+
### Do I need a build step?
|
|
1317
|
+
|
|
1318
|
+
No! Use the IIFE version with a single `<script>` tag. For bundler users, the ESM version is also available.
|
|
1319
|
+
|
|
1320
|
+
### Is this production-ready?
|
|
1321
|
+
|
|
1322
|
+
Yes! The polyfill handles tool registration, lifecycle management, and automatically uses native Chromium implementation when available.
|
|
1323
|
+
|
|
1324
|
+
### What about browser support?
|
|
1325
|
+
|
|
1326
|
+
Works in all modern browsers. Native API support is available in Chromium with experimental flags enabled.
|
|
1327
|
+
|
|
1328
|
+
## 🤝 Related Packages
|
|
1329
|
+
|
|
1330
|
+
- [`@mcp-b/transports`](https://docs.mcp-b.ai/packages/transports) - MCP transport implementations
|
|
1331
|
+
- [`@mcp-b/react-webmcp`](https://docs.mcp-b.ai/packages/react-webmcp) - React hooks for MCP
|
|
1332
|
+
- [`@mcp-b/extension-tools`](https://docs.mcp-b.ai/packages/extension-tools) - Chrome Extension API tools
|
|
1333
|
+
- [`@mcp-b/chrome-devtools-mcp`](https://docs.mcp-b.ai/packages/chrome-devtools-mcp) - Connect desktop AI agents to browser tools
|
|
1334
|
+
- [`@modelcontextprotocol/sdk`](https://www.npmjs.com/package/@modelcontextprotocol/sdk) - Official MCP SDK
|
|
1335
|
+
|
|
1336
|
+
## 📚 Resources
|
|
1337
|
+
|
|
1338
|
+
- [WebMCP Documentation](https://docs.mcp-b.ai)
|
|
1339
|
+
- [Web Model Context API Explainer](https://github.com/nicolo-ribaudo/model-context-protocol-api)
|
|
1340
|
+
- [Model Context Protocol Spec](https://modelcontextprotocol.io/)
|
|
1341
|
+
- [MCP GitHub Repository](https://github.com/modelcontextprotocol)
|
|
1342
|
+
|
|
1343
|
+
## 📝 License
|
|
1344
|
+
|
|
1345
|
+
MIT - see [LICENSE](../../LICENSE) for details
|
|
1346
|
+
|
|
1347
|
+
## 🙋 Support
|
|
1348
|
+
|
|
1349
|
+
- [GitHub Issues](https://github.com/WebMCP-org/npm-packages/issues)
|
|
1350
|
+
- [Documentation](https://docs.mcp-b.ai)
|
|
1351
|
+
- [Discord Community](https://discord.gg/a9fBR6Bw)
|