@mcp-b/global 2.0.3-canary.4 → 2.0.5
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/README.md +335 -1521
- package/dist/index.d.ts +14 -1074
- package/dist/index.d.ts.map +1 -1
- package/dist/index.iife.js +6 -13
- package/dist/index.js +113 -1814
- package/dist/index.js.map +1 -1
- package/package.json +11 -11
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
[](https://bundlephobia.com/package/@mcp-b/global)
|
|
9
9
|
[](https://github.com/nicolo-ribaudo/model-context-protocol-api)
|
|
10
10
|
|
|
11
|
-
|
|
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
12
|
|
|
13
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
14
|
|
|
@@ -20,53 +20,38 @@
|
|
|
20
20
|
| **Drop-in IIFE** | Add AI capabilities with a single `<script>` tag - no build step |
|
|
21
21
|
| **Native Chromium Support** | Auto-detects and uses native browser implementation when available |
|
|
22
22
|
| **Dual Transport** | Works with both same-window clients AND parent pages (iframe support) |
|
|
23
|
-
| **
|
|
23
|
+
| **Strict Core Semantics** | `provideContext()` replaces tool context and `registerTool()` is name-based |
|
|
24
24
|
| **Works with Any AI** | Claude, ChatGPT, Gemini, Cursor, Copilot, and any MCP client |
|
|
25
25
|
|
|
26
|
-
##
|
|
26
|
+
## Package Selection
|
|
27
27
|
|
|
28
|
-
-
|
|
29
|
-
-
|
|
30
|
-
-
|
|
31
|
-
- **Content Management**: Let AI edit, publish, and organize content
|
|
32
|
-
- **Embedded Widgets**: AI tools accessible from parent pages via iframes
|
|
28
|
+
- Use `@mcp-b/webmcp-types` when you only need strict WebMCP type definitions.
|
|
29
|
+
- Use `@mcp-b/webmcp-polyfill` when you only need strict WebMCP runtime polyfill behavior.
|
|
30
|
+
- Use `@mcp-b/global` when you want MCPB integration features (bridge transport, prompts/resources, testing helpers, extension APIs).
|
|
33
31
|
|
|
34
|
-
##
|
|
32
|
+
## Quick Start
|
|
35
33
|
|
|
36
|
-
### Via IIFE Script Tag (
|
|
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>`:
|
|
34
|
+
### Via IIFE Script Tag (No Build Required)
|
|
41
35
|
|
|
42
36
|
```html
|
|
43
37
|
<!DOCTYPE html>
|
|
44
38
|
<html>
|
|
45
39
|
<head>
|
|
46
|
-
<!-- IIFE version - bundles all dependencies, auto-initializes -->
|
|
47
40
|
<script src="https://unpkg.com/@mcp-b/global@latest/dist/index.iife.js"></script>
|
|
48
41
|
</head>
|
|
49
42
|
<body>
|
|
50
43
|
<h1>My AI-Powered App</h1>
|
|
51
44
|
|
|
52
45
|
<script>
|
|
53
|
-
|
|
54
|
-
// Register tools with AI agents
|
|
55
|
-
window.navigator.modelContext.provideContext({
|
|
46
|
+
navigator.modelContext.provideContext({
|
|
56
47
|
tools: [
|
|
57
48
|
{
|
|
58
49
|
name: "get-page-title",
|
|
59
50
|
description: "Get the current page title",
|
|
60
|
-
inputSchema: {
|
|
61
|
-
type: "object",
|
|
62
|
-
properties: {}
|
|
63
|
-
},
|
|
51
|
+
inputSchema: { type: "object", properties: {} },
|
|
64
52
|
async execute() {
|
|
65
53
|
return {
|
|
66
|
-
content: [{
|
|
67
|
-
type: "text",
|
|
68
|
-
text: document.title
|
|
69
|
-
}]
|
|
54
|
+
content: [{ type: "text", text: document.title }]
|
|
70
55
|
};
|
|
71
56
|
}
|
|
72
57
|
}
|
|
@@ -77,43 +62,23 @@ Add the script to your HTML `<head>`:
|
|
|
77
62
|
</html>
|
|
78
63
|
```
|
|
79
64
|
|
|
80
|
-
**
|
|
81
|
-
-
|
|
82
|
-
-
|
|
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
|
|
65
|
+
- **Self-contained** - All dependencies bundled (285KB minified)
|
|
66
|
+
- **Auto-initializes** - `navigator.modelContext` ready immediately
|
|
67
|
+
- **No build step** - Just drop it in your HTML
|
|
86
68
|
|
|
87
|
-
### Via ES Module
|
|
88
|
-
|
|
89
|
-
If you prefer ES modules and have a build system, use the ESM version:
|
|
69
|
+
### Via ES Module
|
|
90
70
|
|
|
91
71
|
```html
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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>
|
|
72
|
+
<script type="module">
|
|
73
|
+
import '@mcp-b/global';
|
|
74
|
+
navigator.modelContext.provideContext({ tools: [/* your tools */] });
|
|
75
|
+
</script>
|
|
109
76
|
```
|
|
110
77
|
|
|
111
|
-
|
|
78
|
+
The ESM version is smaller (~16KB) but doesn't bundle dependencies.
|
|
112
79
|
|
|
113
80
|
### Via NPM
|
|
114
81
|
|
|
115
|
-
For applications using a bundler (Vite, Webpack, etc.):
|
|
116
|
-
|
|
117
82
|
```bash
|
|
118
83
|
npm install @mcp-b/global
|
|
119
84
|
```
|
|
@@ -121,1621 +86,470 @@ npm install @mcp-b/global
|
|
|
121
86
|
```javascript
|
|
122
87
|
import '@mcp-b/global';
|
|
123
88
|
|
|
124
|
-
|
|
125
|
-
window.navigator.modelContext.provideContext({
|
|
89
|
+
navigator.modelContext.provideContext({
|
|
126
90
|
tools: [/* your tools */]
|
|
127
91
|
});
|
|
128
92
|
```
|
|
129
93
|
|
|
130
|
-
##
|
|
131
|
-
|
|
132
|
-
The Web Model Context API follows the same patterns as other browser APIs. Here's how to use it as a traditional web standard:
|
|
133
|
-
|
|
134
|
-
### Basic Pattern (Vanilla JavaScript)
|
|
135
|
-
|
|
136
|
-
```html
|
|
137
|
-
<!DOCTYPE html>
|
|
138
|
-
<html>
|
|
139
|
-
<head>
|
|
140
|
-
<title>Web Model Context API Example</title>
|
|
141
|
-
<script src="https://unpkg.com/@mcp-b/global@latest/dist/index.iife.js"></script>
|
|
142
|
-
</head>
|
|
143
|
-
<body>
|
|
144
|
-
<h1>Counter App</h1>
|
|
145
|
-
<p>Count: <span id="count">0</span></p>
|
|
146
|
-
<button id="increment">+</button>
|
|
147
|
-
<button id="decrement">-</button>
|
|
148
|
-
|
|
149
|
-
<script>
|
|
150
|
-
// State
|
|
151
|
-
let count = 0;
|
|
152
|
-
|
|
153
|
-
// DOM elements
|
|
154
|
-
const countEl = document.getElementById('count');
|
|
155
|
-
const incrementBtn = document.getElementById('increment');
|
|
156
|
-
const decrementBtn = document.getElementById('decrement');
|
|
157
|
-
|
|
158
|
-
// Update UI
|
|
159
|
-
function updateUI() {
|
|
160
|
-
countEl.textContent = count;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
// Button handlers
|
|
164
|
-
incrementBtn.addEventListener('click', () => { count++; updateUI(); });
|
|
165
|
-
decrementBtn.addEventListener('click', () => { count--; updateUI(); });
|
|
166
|
-
|
|
167
|
-
// Feature detection (like navigator.geolocation)
|
|
168
|
-
if ('modelContext' in navigator) {
|
|
169
|
-
// Register tools with the Web Model Context API
|
|
170
|
-
navigator.modelContext.provideContext({
|
|
171
|
-
tools: [
|
|
172
|
-
{
|
|
173
|
-
name: 'counter_get',
|
|
174
|
-
description: 'Get the current counter value',
|
|
175
|
-
inputSchema: { type: 'object', properties: {} },
|
|
176
|
-
execute: async () => ({
|
|
177
|
-
content: [{ type: 'text', text: String(count) }]
|
|
178
|
-
})
|
|
179
|
-
},
|
|
180
|
-
{
|
|
181
|
-
name: 'counter_set',
|
|
182
|
-
description: 'Set the counter to a specific value',
|
|
183
|
-
inputSchema: {
|
|
184
|
-
type: 'object',
|
|
185
|
-
properties: {
|
|
186
|
-
value: { type: 'number', description: 'The new counter value' }
|
|
187
|
-
},
|
|
188
|
-
required: ['value']
|
|
189
|
-
},
|
|
190
|
-
execute: async ({ value }) => {
|
|
191
|
-
count = value;
|
|
192
|
-
updateUI();
|
|
193
|
-
return {
|
|
194
|
-
content: [{ type: 'text', text: `Counter set to ${count}` }]
|
|
195
|
-
};
|
|
196
|
-
}
|
|
197
|
-
},
|
|
198
|
-
{
|
|
199
|
-
name: 'counter_increment',
|
|
200
|
-
description: 'Increment the counter by a specified amount',
|
|
201
|
-
inputSchema: {
|
|
202
|
-
type: 'object',
|
|
203
|
-
properties: {
|
|
204
|
-
amount: { type: 'number', description: 'Amount to increment by', default: 1 }
|
|
205
|
-
}
|
|
206
|
-
},
|
|
207
|
-
execute: async ({ amount = 1 }) => {
|
|
208
|
-
count += amount;
|
|
209
|
-
updateUI();
|
|
210
|
-
return {
|
|
211
|
-
content: [{ type: 'text', text: `Counter incremented to ${count}` }]
|
|
212
|
-
};
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
]
|
|
216
|
-
});
|
|
217
|
-
|
|
218
|
-
console.log('Web Model Context API: Tools registered');
|
|
219
|
-
} else {
|
|
220
|
-
console.warn('Web Model Context API not supported');
|
|
221
|
-
}
|
|
222
|
-
</script>
|
|
223
|
-
</body>
|
|
224
|
-
</html>
|
|
225
|
-
```
|
|
226
|
-
|
|
227
|
-
### Single Tool Registration Pattern
|
|
228
|
-
|
|
229
|
-
Like `navigator.permissions.query()`, you can register tools one at a time:
|
|
94
|
+
## API Reference
|
|
230
95
|
|
|
231
|
-
|
|
232
|
-
// Feature detection
|
|
233
|
-
if ('modelContext' in navigator) {
|
|
234
|
-
// Register a single tool (returns an object with unregister method)
|
|
235
|
-
const registration = navigator.modelContext.registerTool({
|
|
236
|
-
name: 'get_page_info',
|
|
237
|
-
description: 'Get information about the current page',
|
|
238
|
-
inputSchema: { type: 'object', properties: {} },
|
|
239
|
-
execute: async () => ({
|
|
240
|
-
content: [{
|
|
241
|
-
type: 'text',
|
|
242
|
-
text: JSON.stringify({
|
|
243
|
-
title: document.title,
|
|
244
|
-
url: location.href,
|
|
245
|
-
timestamp: new Date().toISOString()
|
|
246
|
-
}, null, 2)
|
|
247
|
-
}]
|
|
248
|
-
})
|
|
249
|
-
});
|
|
96
|
+
### Functions
|
|
250
97
|
|
|
251
|
-
|
|
252
|
-
// registration.unregister();
|
|
253
|
-
}
|
|
254
|
-
```
|
|
98
|
+
#### `initializeWebModelContext(options?)`
|
|
255
99
|
|
|
256
|
-
|
|
100
|
+
Initializes the global adapter. Replaces `navigator.modelContext` with a `BrowserMcpServer` instance that bridges WebMCP tools to the MCP protocol layer.
|
|
257
101
|
|
|
258
|
-
|
|
102
|
+
```typescript
|
|
103
|
+
import { initializeWebModelContext } from '@mcp-b/global';
|
|
259
104
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
// Optionally intercept and provide custom response
|
|
267
|
-
if (event.name === 'custom_handler') {
|
|
268
|
-
event.preventDefault();
|
|
269
|
-
event.respondWith({
|
|
270
|
-
content: [{ type: 'text', text: 'Custom response' }]
|
|
271
|
-
});
|
|
272
|
-
}
|
|
273
|
-
});
|
|
274
|
-
}
|
|
105
|
+
initializeWebModelContext({
|
|
106
|
+
transport: {
|
|
107
|
+
tabServer: { allowedOrigins: ['https://example.com'] },
|
|
108
|
+
},
|
|
109
|
+
nativeModelContextBehavior: 'preserve',
|
|
110
|
+
});
|
|
275
111
|
```
|
|
276
112
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
<!DOCTYPE html>
|
|
283
|
-
<html lang="en">
|
|
284
|
-
<head>
|
|
285
|
-
<meta charset="UTF-8">
|
|
286
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
287
|
-
<title>WebMCP Demo</title>
|
|
288
|
-
<script src="https://unpkg.com/@mcp-b/global@latest/dist/index.iife.js"></script>
|
|
289
|
-
<style>
|
|
290
|
-
body { font-family: system-ui; max-width: 600px; margin: 2rem auto; padding: 0 1rem; }
|
|
291
|
-
.card { border: 1px solid #ddd; border-radius: 8px; padding: 1rem; margin: 1rem 0; }
|
|
292
|
-
button { padding: 0.5rem 1rem; margin: 0.25rem; cursor: pointer; }
|
|
293
|
-
#log { font-family: monospace; font-size: 0.85rem; background: #f5f5f5; padding: 1rem; max-height: 200px; overflow-y: auto; }
|
|
294
|
-
</style>
|
|
295
|
-
</head>
|
|
296
|
-
<body>
|
|
297
|
-
<h1>🤖 WebMCP Demo</h1>
|
|
113
|
+
**Behavior:**
|
|
114
|
+
- Only operates in browser environments
|
|
115
|
+
- Idempotent - calling multiple times is a no-op after first initialization
|
|
116
|
+
- Preserves native `navigator.modelContext` by default (configurable)
|
|
117
|
+
- Auto-called on import unless `window.__webModelContextOptions.autoInitialize` is `false`
|
|
298
118
|
|
|
299
|
-
|
|
300
|
-
<h2>Notes App</h2>
|
|
301
|
-
<input type="text" id="noteInput" placeholder="Enter a note..." style="width: 100%; padding: 0.5rem; box-sizing: border-box;">
|
|
302
|
-
<button id="addNote">Add Note</button>
|
|
303
|
-
<ul id="notesList"></ul>
|
|
304
|
-
</div>
|
|
119
|
+
#### `cleanupWebModelContext()`
|
|
305
120
|
|
|
306
|
-
|
|
307
|
-
<h3>Tool Call Log</h3>
|
|
308
|
-
<div id="log">Waiting for AI tool calls...</div>
|
|
309
|
-
</div>
|
|
121
|
+
Tears down the adapter and restores `navigator.modelContext` to its original state. Allows re-initialization.
|
|
310
122
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
const notes = [];
|
|
314
|
-
|
|
315
|
-
// DOM elements
|
|
316
|
-
const noteInput = document.getElementById('noteInput');
|
|
317
|
-
const addNoteBtn = document.getElementById('addNote');
|
|
318
|
-
const notesList = document.getElementById('notesList');
|
|
319
|
-
const logEl = document.getElementById('log');
|
|
320
|
-
|
|
321
|
-
// UI functions
|
|
322
|
-
function renderNotes() {
|
|
323
|
-
notesList.innerHTML = notes.map((note, i) =>
|
|
324
|
-
`<li>${note} <button onclick="deleteNote(${i})">×</button></li>`
|
|
325
|
-
).join('');
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
function log(message) {
|
|
329
|
-
const time = new Date().toLocaleTimeString();
|
|
330
|
-
logEl.innerHTML = `[${time}] ${message}\n` + logEl.innerHTML;
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
// User interactions
|
|
334
|
-
addNoteBtn.addEventListener('click', () => {
|
|
335
|
-
if (noteInput.value.trim()) {
|
|
336
|
-
notes.push(noteInput.value.trim());
|
|
337
|
-
noteInput.value = '';
|
|
338
|
-
renderNotes();
|
|
339
|
-
}
|
|
340
|
-
});
|
|
123
|
+
```typescript
|
|
124
|
+
import { cleanupWebModelContext, initializeWebModelContext } from '@mcp-b/global';
|
|
341
125
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
};
|
|
126
|
+
initializeWebModelContext();
|
|
127
|
+
// ... use tools ...
|
|
128
|
+
cleanupWebModelContext();
|
|
346
129
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
navigator.modelContext.provideContext({
|
|
350
|
-
tools: [
|
|
351
|
-
{
|
|
352
|
-
name: 'notes_list',
|
|
353
|
-
description: 'Get all notes',
|
|
354
|
-
inputSchema: { type: 'object', properties: {} },
|
|
355
|
-
execute: async () => {
|
|
356
|
-
log('🔧 notes_list called');
|
|
357
|
-
return {
|
|
358
|
-
content: [{
|
|
359
|
-
type: 'text',
|
|
360
|
-
text: notes.length ? notes.map((n, i) => `${i + 1}. ${n}`).join('\n') : 'No notes yet'
|
|
361
|
-
}]
|
|
362
|
-
};
|
|
363
|
-
}
|
|
364
|
-
},
|
|
365
|
-
{
|
|
366
|
-
name: 'notes_add',
|
|
367
|
-
description: 'Add a new note',
|
|
368
|
-
inputSchema: {
|
|
369
|
-
type: 'object',
|
|
370
|
-
properties: {
|
|
371
|
-
text: { type: 'string', description: 'The note text' }
|
|
372
|
-
},
|
|
373
|
-
required: ['text']
|
|
374
|
-
},
|
|
375
|
-
execute: async ({ text }) => {
|
|
376
|
-
log(`🔧 notes_add called: "${text}"`);
|
|
377
|
-
notes.push(text);
|
|
378
|
-
renderNotes();
|
|
379
|
-
return {
|
|
380
|
-
content: [{ type: 'text', text: `Added note: "${text}"` }]
|
|
381
|
-
};
|
|
382
|
-
}
|
|
383
|
-
},
|
|
384
|
-
{
|
|
385
|
-
name: 'notes_delete',
|
|
386
|
-
description: 'Delete a note by index (1-based)',
|
|
387
|
-
inputSchema: {
|
|
388
|
-
type: 'object',
|
|
389
|
-
properties: {
|
|
390
|
-
index: { type: 'number', description: 'Note index (1-based)' }
|
|
391
|
-
},
|
|
392
|
-
required: ['index']
|
|
393
|
-
},
|
|
394
|
-
execute: async ({ index }) => {
|
|
395
|
-
log(`🔧 notes_delete called: index ${index}`);
|
|
396
|
-
if (index < 1 || index > notes.length) {
|
|
397
|
-
return { content: [{ type: 'text', text: 'Invalid index' }], isError: true };
|
|
398
|
-
}
|
|
399
|
-
const deleted = notes.splice(index - 1, 1)[0];
|
|
400
|
-
renderNotes();
|
|
401
|
-
return {
|
|
402
|
-
content: [{ type: 'text', text: `Deleted: "${deleted}"` }]
|
|
403
|
-
};
|
|
404
|
-
}
|
|
405
|
-
},
|
|
406
|
-
{
|
|
407
|
-
name: 'notes_clear',
|
|
408
|
-
description: 'Delete all notes',
|
|
409
|
-
inputSchema: { type: 'object', properties: {} },
|
|
410
|
-
execute: async () => {
|
|
411
|
-
log('🔧 notes_clear called');
|
|
412
|
-
const count = notes.length;
|
|
413
|
-
notes.length = 0;
|
|
414
|
-
renderNotes();
|
|
415
|
-
return {
|
|
416
|
-
content: [{ type: 'text', text: `Cleared ${count} notes` }]
|
|
417
|
-
};
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
]
|
|
421
|
-
});
|
|
422
|
-
|
|
423
|
-
log('✅ Web Model Context API initialized');
|
|
424
|
-
log('📋 Tools: notes_list, notes_add, notes_delete, notes_clear');
|
|
425
|
-
} else {
|
|
426
|
-
log('❌ Web Model Context API not available');
|
|
427
|
-
}
|
|
428
|
-
</script>
|
|
429
|
-
</body>
|
|
430
|
-
</html>
|
|
130
|
+
// Can re-initialize after cleanup
|
|
131
|
+
initializeWebModelContext();
|
|
431
132
|
```
|
|
432
133
|
|
|
433
|
-
|
|
434
|
-
- **Feature detection** using `'modelContext' in navigator`
|
|
435
|
-
- **Tool registration** via `navigator.modelContext.provideContext()`
|
|
436
|
-
- **Standard input schemas** following JSON Schema specification
|
|
437
|
-
- **Async execute functions** returning MCP-compatible responses
|
|
438
|
-
- **Real-time UI updates** when AI agents call tools
|
|
439
|
-
|
|
440
|
-
## ⚙️ Configuration
|
|
441
|
-
|
|
442
|
-
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:
|
|
443
|
-
|
|
444
|
-
- **Disable auto init**: Set `window.__webModelContextOptions = { autoInitialize: false }` before importing, then call `initializeWebModelContext()` manually.
|
|
445
|
-
- **Configure via script tag**: When using the IIFE build, pass options through data attributes:
|
|
446
|
-
```html
|
|
447
|
-
<script
|
|
448
|
-
src="https://unpkg.com/@mcp-b/global@latest/dist/index.iife.js"
|
|
449
|
-
data-webmcp-auto-initialize="false"
|
|
450
|
-
data-webmcp-allowed-origins="https://example.com,https://docs.example.com"
|
|
451
|
-
></script>
|
|
452
|
-
<!-- Later in the page -->
|
|
453
|
-
<script>
|
|
454
|
-
window.navigator.modelContext.provideContext({ tools: [] });
|
|
455
|
-
</script>
|
|
456
|
-
```
|
|
457
|
-
Use `data-webmcp-options='{"transport":{"tabServer":{"allowedOrigins":["https://example.com"]}}}'` for advanced JSON configuration.
|
|
458
|
-
- **Supported data attributes**
|
|
459
|
-
- `data-webmcp-auto-initialize="false"`: Skip automatic setup.
|
|
460
|
-
- `data-webmcp-allowed-origins="https://a.com,https://b.com"`: Override `tabServer.allowedOrigins`.
|
|
461
|
-
- `data-webmcp-channel-id="custom-channel"`: Set the Tab transport channel.
|
|
462
|
-
|
|
463
|
-
### Dual-Server Mode (Tab + Iframe)
|
|
134
|
+
### `navigator.modelContext` Methods
|
|
464
135
|
|
|
465
|
-
|
|
136
|
+
After initialization, `navigator.modelContext` exposes these methods:
|
|
466
137
|
|
|
467
|
-
|
|
468
|
-
2. **Iframe Server** (`IframeChildTransport`) - Auto-enabled when running in an iframe (when `window.parent !== window`)
|
|
138
|
+
#### `provideContext(options?)`
|
|
469
139
|
|
|
470
|
-
|
|
471
|
-
- Same-window clients (e.g., browser extension content scripts)
|
|
472
|
-
- Parent page (when running in an iframe)
|
|
140
|
+
Replaces all currently registered tools with a new set. This is an atomic replacement - all previous tools are removed first.
|
|
473
141
|
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
When your app runs in an iframe, both servers are automatically enabled:
|
|
477
|
-
|
|
478
|
-
```ts
|
|
479
|
-
// In iframe: Auto-initializes with both servers
|
|
480
|
-
import '@mcp-b/global';
|
|
481
|
-
|
|
482
|
-
// Register tools - they're automatically available to:
|
|
483
|
-
// 1. Same-window clients (via TabServerTransport)
|
|
484
|
-
// 2. Parent page (via IframeChildTransport)
|
|
485
|
-
window.navigator.modelContext.provideContext({
|
|
142
|
+
```typescript
|
|
143
|
+
navigator.modelContext.provideContext({
|
|
486
144
|
tools: [
|
|
487
145
|
{
|
|
488
|
-
name:
|
|
489
|
-
description:
|
|
490
|
-
inputSchema: {
|
|
146
|
+
name: 'search-products',
|
|
147
|
+
description: 'Search the product catalog by query',
|
|
148
|
+
inputSchema: {
|
|
149
|
+
type: 'object',
|
|
150
|
+
properties: {
|
|
151
|
+
query: { type: 'string', description: 'Search query' },
|
|
152
|
+
limit: { type: 'integer', description: 'Max results' },
|
|
153
|
+
},
|
|
154
|
+
required: ['query'],
|
|
155
|
+
},
|
|
156
|
+
async execute(args) {
|
|
157
|
+
const results = await searchProducts(args.query, args.limit ?? 10);
|
|
158
|
+
return {
|
|
159
|
+
content: [{ type: 'text', text: JSON.stringify(results) }],
|
|
160
|
+
};
|
|
161
|
+
},
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
name: 'get-cart',
|
|
165
|
+
description: 'Get the current shopping cart contents',
|
|
166
|
+
inputSchema: { type: 'object', properties: {} },
|
|
491
167
|
async execute() {
|
|
492
168
|
return {
|
|
493
|
-
content: [{ type:
|
|
169
|
+
content: [{ type: 'text', text: JSON.stringify(getCart()) }],
|
|
494
170
|
};
|
|
495
|
-
}
|
|
496
|
-
}
|
|
497
|
-
]
|
|
171
|
+
},
|
|
172
|
+
},
|
|
173
|
+
],
|
|
498
174
|
});
|
|
499
175
|
```
|
|
500
176
|
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
You can customize or disable the iframe server:
|
|
177
|
+
#### `registerTool(tool)`
|
|
504
178
|
|
|
505
|
-
|
|
506
|
-
import { initializeWebModelContext } from '@mcp-b/global';
|
|
179
|
+
Registers a single tool. The tool name must be unique - throws if a tool with the same name already exists.
|
|
507
180
|
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
181
|
+
```typescript
|
|
182
|
+
navigator.modelContext.registerTool({
|
|
183
|
+
name: 'add-to-cart',
|
|
184
|
+
description: 'Add a product to the shopping cart',
|
|
185
|
+
inputSchema: {
|
|
186
|
+
type: 'object',
|
|
187
|
+
properties: {
|
|
188
|
+
productId: { type: 'string' },
|
|
189
|
+
quantity: { type: 'integer' },
|
|
514
190
|
},
|
|
191
|
+
required: ['productId'],
|
|
515
192
|
},
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
iframeServer: false, // Disable iframe server
|
|
522
|
-
},
|
|
523
|
-
});
|
|
524
|
-
|
|
525
|
-
// Disable tab server (only Iframe server runs)
|
|
526
|
-
initializeWebModelContext({
|
|
527
|
-
transport: {
|
|
528
|
-
tabServer: false, // Disable tab server
|
|
529
|
-
iframeServer: {
|
|
530
|
-
allowedOrigins: ['https://parent-app.com'],
|
|
531
|
-
},
|
|
193
|
+
async execute(args) {
|
|
194
|
+
const item = await addToCart(args.productId, args.quantity ?? 1);
|
|
195
|
+
return {
|
|
196
|
+
content: [{ type: 'text', text: `Added ${item.name} to cart` }],
|
|
197
|
+
};
|
|
532
198
|
},
|
|
533
199
|
});
|
|
534
200
|
```
|
|
535
201
|
|
|
536
|
-
|
|
202
|
+
#### `unregisterTool(name)`
|
|
537
203
|
|
|
538
|
-
|
|
204
|
+
Removes a tool by name.
|
|
539
205
|
|
|
540
|
-
```
|
|
541
|
-
|
|
542
|
-
|
|
206
|
+
```typescript
|
|
207
|
+
navigator.modelContext.unregisterTool('add-to-cart');
|
|
208
|
+
```
|
|
543
209
|
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
210
|
+
#### `clearContext()`
|
|
211
|
+
|
|
212
|
+
Removes all registered tools.
|
|
213
|
+
|
|
214
|
+
```typescript
|
|
215
|
+
navigator.modelContext.clearContext();
|
|
549
216
|
```
|
|
550
217
|
|
|
551
|
-
|
|
218
|
+
#### `listTools()`
|
|
219
|
+
|
|
220
|
+
Returns metadata for all registered tools (without execute functions).
|
|
221
|
+
|
|
222
|
+
```typescript
|
|
223
|
+
const tools = navigator.modelContext.listTools();
|
|
224
|
+
// [{ name: 'search-products', description: '...', inputSchema: {...} }, ...]
|
|
225
|
+
```
|
|
552
226
|
|
|
553
|
-
|
|
227
|
+
#### `callTool(params)`
|
|
554
228
|
|
|
555
|
-
|
|
229
|
+
Executes a registered tool by name.
|
|
556
230
|
|
|
557
|
-
|
|
231
|
+
```typescript
|
|
232
|
+
const result = await navigator.modelContext.callTool({
|
|
233
|
+
name: 'search-products',
|
|
234
|
+
arguments: { query: 'laptop', limit: 5 },
|
|
235
|
+
});
|
|
236
|
+
// { content: [{ type: 'text', text: '...' }] }
|
|
237
|
+
```
|
|
558
238
|
|
|
559
|
-
|
|
560
|
-
- Uses native Chromium implementation
|
|
561
|
-
- Creates MCP bridge and syncs tools automatically
|
|
562
|
-
- Registers callback to listen for native tool changes
|
|
563
|
-
- MCP clients stay synchronized with native tool registry
|
|
239
|
+
### Tool Descriptor
|
|
564
240
|
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
241
|
+
| Property | Type | Required | Description |
|
|
242
|
+
|----------|------|----------|-------------|
|
|
243
|
+
| `name` | `string` | Yes | Unique identifier for the tool |
|
|
244
|
+
| `description` | `string` | Yes | Natural language description of what the tool does |
|
|
245
|
+
| `inputSchema` | `InputSchema` | No | JSON Schema describing accepted input. Defaults to `{ type: 'object', properties: {} }` |
|
|
246
|
+
| `outputSchema` | `InputSchema` | No | JSON Schema describing the output payload shape |
|
|
247
|
+
| `annotations` | `ToolAnnotations` | No | Hints about tool behavior for LLM planners |
|
|
248
|
+
| `execute` | `(args, client) => Promise<ToolResponse>` | Yes | Async function implementing the tool logic |
|
|
568
249
|
|
|
569
|
-
|
|
250
|
+
### Tool Response Format
|
|
570
251
|
|
|
571
|
-
|
|
252
|
+
Tools return a `ToolResponse` object:
|
|
572
253
|
|
|
573
|
-
|
|
254
|
+
```typescript
|
|
255
|
+
// Success
|
|
256
|
+
{
|
|
257
|
+
content: [{ type: 'text', text: 'Result here' }]
|
|
258
|
+
}
|
|
574
259
|
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
260
|
+
// Error
|
|
261
|
+
{
|
|
262
|
+
content: [{ type: 'text', text: 'Something went wrong' }],
|
|
263
|
+
isError: true
|
|
264
|
+
}
|
|
265
|
+
```
|
|
580
266
|
|
|
581
|
-
|
|
267
|
+
## Configuration
|
|
582
268
|
|
|
583
|
-
|
|
269
|
+
### `WebModelContextInitOptions`
|
|
584
270
|
|
|
585
271
|
```typescript
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
272
|
+
interface WebModelContextInitOptions {
|
|
273
|
+
transport?: TransportConfiguration;
|
|
274
|
+
autoInitialize?: boolean;
|
|
275
|
+
nativeModelContextBehavior?: 'preserve' | 'patch';
|
|
276
|
+
installTestingShim?: boolean | 'always' | 'if-missing';
|
|
277
|
+
}
|
|
591
278
|
```
|
|
592
279
|
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
-
|
|
280
|
+
| Option | Default | Description |
|
|
281
|
+
|--------|---------|-------------|
|
|
282
|
+
| `transport` | Auto-detect | Transport layer configuration (tab server and/or iframe) |
|
|
283
|
+
| `autoInitialize` | `true` | Whether to auto-initialize on import |
|
|
284
|
+
| `nativeModelContextBehavior` | `'preserve'` | `'preserve'` keeps native implementation untouched. `'patch'` replaces it with a BrowserMcpServer that mirrors to the native object |
|
|
285
|
+
| `installTestingShim` | `'if-missing'` | Controls `navigator.modelContextTesting` installation. Only installs when not already present natively |
|
|
599
286
|
|
|
600
|
-
###
|
|
287
|
+
### Transport Configuration
|
|
601
288
|
|
|
602
|
-
|
|
603
|
-
# Method 1: Launch with flag
|
|
604
|
-
chromium --enable-experimental-web-platform-features
|
|
289
|
+
The transport is auto-selected based on context:
|
|
605
290
|
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
291
|
+
```typescript
|
|
292
|
+
interface TransportConfiguration {
|
|
293
|
+
tabServer?: Partial<TabServerTransportOptions> | false;
|
|
294
|
+
iframeServer?: Partial<IframeChildTransportOptions> | false;
|
|
295
|
+
}
|
|
610
296
|
```
|
|
611
297
|
|
|
612
|
-
|
|
298
|
+
- **In an iframe**: Uses `IframeChildTransport` to communicate with the parent page
|
|
299
|
+
- **In the main window**: Uses `TabServerTransport` for cross-tab communication
|
|
300
|
+
- Set either to `false` to disable it
|
|
613
301
|
|
|
614
302
|
```typescript
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
description: 'My tool',
|
|
621
|
-
inputSchema: { type: 'object', properties: {} },
|
|
622
|
-
async execute() {
|
|
623
|
-
return { content: [{ type: 'text', text: 'Hello!' }] };
|
|
624
|
-
}
|
|
303
|
+
// Restrict to specific origins
|
|
304
|
+
initializeWebModelContext({
|
|
305
|
+
transport: {
|
|
306
|
+
tabServer: { allowedOrigins: ['https://myapp.com'] },
|
|
307
|
+
},
|
|
625
308
|
});
|
|
626
309
|
|
|
627
|
-
//
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
310
|
+
// Disable tab transport (iframe only)
|
|
311
|
+
initializeWebModelContext({
|
|
312
|
+
transport: {
|
|
313
|
+
tabServer: false,
|
|
314
|
+
},
|
|
315
|
+
});
|
|
632
316
|
```
|
|
633
317
|
|
|
634
|
-
###
|
|
318
|
+
### Auto-Initialization
|
|
635
319
|
|
|
636
|
-
|
|
320
|
+
The package auto-initializes on import in browser environments. To customize before initialization:
|
|
637
321
|
|
|
638
322
|
```html
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
description: 'Tool from parent page',
|
|
647
|
-
inputSchema: { type: 'object', properties: {} },
|
|
648
|
-
async execute() {
|
|
649
|
-
return { content: [{ type: 'text', text: 'Parent tool' }] };
|
|
650
|
-
}
|
|
651
|
-
});
|
|
323
|
+
<script>
|
|
324
|
+
window.__webModelContextOptions = {
|
|
325
|
+
autoInitialize: true,
|
|
326
|
+
transport: {
|
|
327
|
+
tabServer: { allowedOrigins: ['https://myapp.com'] },
|
|
328
|
+
},
|
|
329
|
+
};
|
|
652
330
|
</script>
|
|
653
|
-
|
|
654
|
-
<iframe src="child.html"></iframe>
|
|
331
|
+
<script src="https://unpkg.com/@mcp-b/global@latest/dist/index.iife.js"></script>
|
|
655
332
|
```
|
|
656
333
|
|
|
657
|
-
|
|
658
|
-
<!-- child.html -->
|
|
659
|
-
<script type="module">
|
|
660
|
-
import '@mcp-b/global';
|
|
334
|
+
To prevent auto-initialization:
|
|
661
335
|
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
});
|
|
336
|
+
```html
|
|
337
|
+
<script>
|
|
338
|
+
window.__webModelContextOptions = { autoInitialize: false };
|
|
339
|
+
</script>
|
|
340
|
+
<script src="https://unpkg.com/@mcp-b/global@latest/dist/index.iife.js"></script>
|
|
341
|
+
<script>
|
|
342
|
+
// Initialize manually later
|
|
343
|
+
MCPB.initializeWebModelContext();
|
|
671
344
|
</script>
|
|
672
345
|
```
|
|
673
346
|
|
|
674
|
-
|
|
347
|
+
## Testing
|
|
675
348
|
|
|
676
|
-
|
|
349
|
+
`navigator.modelContextTesting` provides a testing shim that stays in sync with registered tools:
|
|
677
350
|
|
|
678
|
-
|
|
351
|
+
```typescript
|
|
352
|
+
// List registered tools
|
|
353
|
+
const tools = navigator.modelContextTesting?.listTools();
|
|
354
|
+
// [{ name: 'search-products', description: '...', inputSchema: '...' }]
|
|
679
355
|
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
MCP clients will receive automatic tool updates from native registry
|
|
356
|
+
// Execute a tool (input args as JSON string)
|
|
357
|
+
const result = await navigator.modelContextTesting?.executeTool(
|
|
358
|
+
'search-products',
|
|
359
|
+
'{"query": "laptop"}'
|
|
360
|
+
);
|
|
686
361
|
```
|
|
687
362
|
|
|
688
|
-
|
|
363
|
+
## Feature Detection
|
|
689
364
|
|
|
365
|
+
```javascript
|
|
366
|
+
if ('modelContext' in navigator) {
|
|
367
|
+
navigator.modelContext.provideContext({ tools: [...] });
|
|
368
|
+
}
|
|
690
369
|
```
|
|
691
|
-
[Web Model Context] Native API not detected, installing polyfill
|
|
692
|
-
✅ [Web Model Context] window.navigator.modelContext initialized successfully
|
|
693
|
-
[Model Context Testing] Installing polyfill
|
|
694
|
-
✅ [Model Context Testing] Polyfill installed at window.navigator.modelContextTesting
|
|
695
|
-
```
|
|
696
|
-
|
|
697
|
-
## 📖 API Reference
|
|
698
|
-
|
|
699
|
-
### Two-Bucket Tool Management System
|
|
700
|
-
|
|
701
|
-
This package uses a **two-bucket system** for tool management to support both app-level and component-level tools:
|
|
702
|
-
|
|
703
|
-
- **Bucket A (Base Tools)**: Registered via `provideContext()` - represents your app's core functionality
|
|
704
|
-
- **Bucket B (Dynamic Tools)**: Registered via `registerTool()` - component-scoped tools that persist across `provideContext()` calls
|
|
705
|
-
|
|
706
|
-
**Key behaviors:**
|
|
707
|
-
- ✅ `provideContext()` only clears Bucket A, leaving Bucket B intact
|
|
708
|
-
- ✅ `registerTool()` adds to Bucket B and persists across `provideContext()` calls
|
|
709
|
-
- ✅ Tool name collisions between buckets throw an error
|
|
710
|
-
- ✅ Cannot `unregister()` a tool that was registered via `provideContext()`
|
|
711
|
-
|
|
712
|
-
**Use case:** React components can use `registerTool()` in `useEffect()` to manage tool lifecycle independently of the app's base tools.
|
|
713
|
-
|
|
714
|
-
### `window.navigator.modelContext.provideContext(context)`
|
|
715
370
|
|
|
716
|
-
|
|
371
|
+
## Examples
|
|
717
372
|
|
|
718
|
-
|
|
719
|
-
- `context.tools` - Array of tool descriptors
|
|
373
|
+
### E-commerce: Product Search and Cart
|
|
720
374
|
|
|
721
|
-
|
|
375
|
+
```typescript
|
|
376
|
+
import '@mcp-b/global';
|
|
722
377
|
|
|
723
|
-
|
|
724
|
-
window.navigator.modelContext.provideContext({
|
|
378
|
+
navigator.modelContext.provideContext({
|
|
725
379
|
tools: [
|
|
726
380
|
{
|
|
727
|
-
name:
|
|
728
|
-
description:
|
|
381
|
+
name: 'search-products',
|
|
382
|
+
description: 'Search products by keyword, category, or price range',
|
|
729
383
|
inputSchema: {
|
|
730
|
-
type:
|
|
384
|
+
type: 'object',
|
|
731
385
|
properties: {
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
},
|
|
736
|
-
priority: {
|
|
737
|
-
type: "string",
|
|
738
|
-
enum: ["low", "medium", "high"],
|
|
739
|
-
description: "Priority level"
|
|
740
|
-
}
|
|
386
|
+
query: { type: 'string', description: 'Search terms' },
|
|
387
|
+
category: { type: 'string', description: 'Product category' },
|
|
388
|
+
maxPrice: { type: 'number', description: 'Maximum price filter' },
|
|
741
389
|
},
|
|
742
|
-
required: [
|
|
390
|
+
required: ['query'],
|
|
743
391
|
},
|
|
744
|
-
async execute(
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
content: [{
|
|
750
|
-
type: "text",
|
|
751
|
-
text: `Added todo: "${text}" with ${priority} priority`
|
|
752
|
-
}]
|
|
753
|
-
};
|
|
754
|
-
}
|
|
755
|
-
}
|
|
756
|
-
]
|
|
757
|
-
});
|
|
758
|
-
```
|
|
759
|
-
|
|
760
|
-
### `window.navigator.modelContext.registerTool(tool)`
|
|
761
|
-
|
|
762
|
-
Register a single tool dynamically (Bucket B). Tools registered this way:
|
|
763
|
-
- ✅ Persist across `provideContext()` calls
|
|
764
|
-
- ✅ Perfect for component lifecycle management
|
|
765
|
-
- ✅ Can be unregistered via the returned `unregister()` function
|
|
766
|
-
- ❌ Cannot have the same name as a tool in Bucket A (provideContext)
|
|
767
|
-
|
|
768
|
-
**Parameters:**
|
|
769
|
-
- `tool` - A single tool descriptor
|
|
770
|
-
|
|
771
|
-
**Returns:**
|
|
772
|
-
- Object with `unregister()` function to remove the tool
|
|
773
|
-
|
|
774
|
-
**Example:**
|
|
775
|
-
|
|
776
|
-
```javascript
|
|
777
|
-
// Register a tool dynamically (Bucket B)
|
|
778
|
-
const registration = window.navigator.modelContext.registerTool({
|
|
779
|
-
name: "get-timestamp",
|
|
780
|
-
description: "Get the current timestamp",
|
|
781
|
-
inputSchema: {
|
|
782
|
-
type: "object",
|
|
783
|
-
properties: {}
|
|
784
|
-
},
|
|
785
|
-
async execute() {
|
|
786
|
-
return {
|
|
787
|
-
content: [{
|
|
788
|
-
type: "text",
|
|
789
|
-
text: new Date().toISOString()
|
|
790
|
-
}]
|
|
791
|
-
};
|
|
792
|
-
}
|
|
793
|
-
});
|
|
794
|
-
|
|
795
|
-
// Later, unregister the tool
|
|
796
|
-
registration.unregister();
|
|
797
|
-
|
|
798
|
-
// Note: You can call provideContext() and this tool will still be registered!
|
|
799
|
-
window.navigator.modelContext.provideContext({
|
|
800
|
-
tools: [/* other tools */]
|
|
801
|
-
});
|
|
802
|
-
// "get-timestamp" is still available because it's in Bucket B
|
|
803
|
-
```
|
|
804
|
-
|
|
805
|
-
### Tool Descriptor
|
|
806
|
-
|
|
807
|
-
Each tool must have:
|
|
808
|
-
|
|
809
|
-
| Property | Type | Description |
|
|
810
|
-
|----------|------|-------------|
|
|
811
|
-
| `name` | `string` | Unique identifier for the tool |
|
|
812
|
-
| `description` | `string` | Natural language description of what the tool does |
|
|
813
|
-
| `inputSchema` | `object` | JSON Schema defining input parameters |
|
|
814
|
-
| `outputSchema` | `object` | Optional JSON Schema defining structured output |
|
|
815
|
-
| `annotations` | `object` | Optional hints about tool behavior |
|
|
816
|
-
| `execute` | `function` | Async function that implements the tool logic |
|
|
817
|
-
|
|
818
|
-
### Output Schemas (Structured Output)
|
|
819
|
-
|
|
820
|
-
**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.
|
|
821
|
-
|
|
822
|
-
**Benefits of output schemas:**
|
|
823
|
-
- **Type-safe responses** - AI generates structured JSON matching your schema
|
|
824
|
-
- **Better AI reasoning** - AI understands the expected output format
|
|
825
|
-
- **Client validation** - Responses are validated against the schema
|
|
826
|
-
- **IDE support** - TypeScript types inferred from schemas
|
|
827
|
-
|
|
828
|
-
#### Basic Output Schema Example
|
|
829
|
-
|
|
830
|
-
```javascript
|
|
831
|
-
window.navigator.modelContext.provideContext({
|
|
832
|
-
tools: [
|
|
392
|
+
async execute(args) {
|
|
393
|
+
const results = await fetch(`/api/products?q=${args.query}&cat=${args.category ?? ''}&max=${args.maxPrice ?? ''}`);
|
|
394
|
+
return { content: [{ type: 'text', text: await results.text() }] };
|
|
395
|
+
},
|
|
396
|
+
},
|
|
833
397
|
{
|
|
834
|
-
name:
|
|
835
|
-
description:
|
|
398
|
+
name: 'add-to-cart',
|
|
399
|
+
description: 'Add a product to the shopping cart',
|
|
836
400
|
inputSchema: {
|
|
837
|
-
type:
|
|
401
|
+
type: 'object',
|
|
838
402
|
properties: {
|
|
839
|
-
|
|
403
|
+
productId: { type: 'string' },
|
|
404
|
+
quantity: { type: 'integer' },
|
|
840
405
|
},
|
|
841
|
-
required: [
|
|
406
|
+
required: ['productId'],
|
|
842
407
|
},
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
email: { type: "string", description: "Email address" },
|
|
850
|
-
createdAt: { type: "string", description: "ISO date string" }
|
|
851
|
-
},
|
|
852
|
-
required: ["id", "name", "email"]
|
|
408
|
+
async execute(args) {
|
|
409
|
+
await fetch('/api/cart', {
|
|
410
|
+
method: 'POST',
|
|
411
|
+
body: JSON.stringify({ productId: args.productId, quantity: args.quantity ?? 1 }),
|
|
412
|
+
});
|
|
413
|
+
return { content: [{ type: 'text', text: `Added to cart` }] };
|
|
853
414
|
},
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
return {
|
|
857
|
-
content: [{ type: "text", text: `Found user: ${user.name}` }],
|
|
858
|
-
// Structured content matching the outputSchema
|
|
859
|
-
structuredContent: {
|
|
860
|
-
id: user.id,
|
|
861
|
-
name: user.name,
|
|
862
|
-
email: user.email,
|
|
863
|
-
createdAt: user.createdAt.toISOString()
|
|
864
|
-
}
|
|
865
|
-
};
|
|
866
|
-
}
|
|
867
|
-
}
|
|
868
|
-
]
|
|
415
|
+
},
|
|
416
|
+
],
|
|
869
417
|
});
|
|
870
418
|
```
|
|
871
419
|
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
For TypeScript projects, you can use Zod schemas for both input and output validation. Zod schemas are automatically converted to JSON Schema:
|
|
420
|
+
### Dynamic Tool Registration
|
|
875
421
|
|
|
876
422
|
```typescript
|
|
877
|
-
import
|
|
423
|
+
import '@mcp-b/global';
|
|
878
424
|
|
|
879
|
-
|
|
425
|
+
// Start with base tools
|
|
426
|
+
navigator.modelContext.provideContext({
|
|
880
427
|
tools: [
|
|
881
428
|
{
|
|
882
|
-
name:
|
|
883
|
-
description:
|
|
884
|
-
inputSchema: {
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
category: z.enum(["electronics", "clothing", "books"]).optional()
|
|
888
|
-
},
|
|
889
|
-
// Zod schema for output - provides TypeScript types
|
|
890
|
-
outputSchema: {
|
|
891
|
-
products: z.array(z.object({
|
|
892
|
-
id: z.string(),
|
|
893
|
-
name: z.string(),
|
|
894
|
-
price: z.number(),
|
|
895
|
-
inStock: z.boolean()
|
|
896
|
-
})),
|
|
897
|
-
total: z.number().describe("Total matching products"),
|
|
898
|
-
hasMore: z.boolean().describe("Whether more results exist")
|
|
429
|
+
name: 'get-user',
|
|
430
|
+
description: 'Get current user info',
|
|
431
|
+
inputSchema: { type: 'object', properties: {} },
|
|
432
|
+
async execute() {
|
|
433
|
+
return { content: [{ type: 'text', text: JSON.stringify(currentUser) }] };
|
|
899
434
|
},
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
return {
|
|
903
|
-
content: [{ type: "text", text: `Found ${results.total} products` }],
|
|
904
|
-
structuredContent: {
|
|
905
|
-
products: results.items,
|
|
906
|
-
total: results.total,
|
|
907
|
-
hasMore: results.total > limit
|
|
908
|
-
}
|
|
909
|
-
};
|
|
910
|
-
}
|
|
911
|
-
}
|
|
912
|
-
]
|
|
435
|
+
},
|
|
436
|
+
],
|
|
913
437
|
});
|
|
438
|
+
|
|
439
|
+
// Add tools dynamically based on user role
|
|
440
|
+
if (currentUser.isAdmin) {
|
|
441
|
+
navigator.modelContext.registerTool({
|
|
442
|
+
name: 'delete-user',
|
|
443
|
+
description: 'Delete a user account (admin only)',
|
|
444
|
+
inputSchema: {
|
|
445
|
+
type: 'object',
|
|
446
|
+
properties: { userId: { type: 'string' } },
|
|
447
|
+
required: ['userId'],
|
|
448
|
+
},
|
|
449
|
+
async execute(args) {
|
|
450
|
+
await fetch(`/api/users/${args.userId}`, { method: 'DELETE' });
|
|
451
|
+
return { content: [{ type: 'text', text: 'User deleted' }] };
|
|
452
|
+
},
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// Remove tools when permissions change
|
|
457
|
+
function onLogout() {
|
|
458
|
+
navigator.modelContext.clearContext();
|
|
459
|
+
}
|
|
914
460
|
```
|
|
915
461
|
|
|
916
|
-
|
|
462
|
+
### Form Interaction
|
|
917
463
|
|
|
918
|
-
|
|
464
|
+
```typescript
|
|
465
|
+
import '@mcp-b/global';
|
|
919
466
|
|
|
920
|
-
|
|
921
|
-
window.navigator.modelContext.provideContext({
|
|
467
|
+
navigator.modelContext.provideContext({
|
|
922
468
|
tools: [
|
|
923
469
|
{
|
|
924
|
-
name:
|
|
925
|
-
description:
|
|
470
|
+
name: 'fill-contact-form',
|
|
471
|
+
description: 'Fill the contact form with provided details',
|
|
926
472
|
inputSchema: {
|
|
927
|
-
type:
|
|
473
|
+
type: 'object',
|
|
928
474
|
properties: {
|
|
929
|
-
|
|
930
|
-
|
|
475
|
+
name: { type: 'string' },
|
|
476
|
+
email: { type: 'string' },
|
|
477
|
+
message: { type: 'string' },
|
|
931
478
|
},
|
|
932
|
-
required: [
|
|
479
|
+
required: ['name', 'email', 'message'],
|
|
933
480
|
},
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
properties: {
|
|
940
|
-
linesOfCode: { type: "number" },
|
|
941
|
-
complexity: { type: "string", enum: ["low", "medium", "high"] }
|
|
942
|
-
}
|
|
943
|
-
},
|
|
944
|
-
issues: {
|
|
945
|
-
type: "array",
|
|
946
|
-
items: {
|
|
947
|
-
type: "object",
|
|
948
|
-
properties: {
|
|
949
|
-
severity: { type: "string", enum: ["error", "warning", "info"] },
|
|
950
|
-
line: { type: "number" },
|
|
951
|
-
message: { type: "string" },
|
|
952
|
-
suggestion: { type: "string" }
|
|
953
|
-
},
|
|
954
|
-
required: ["severity", "line", "message"]
|
|
955
|
-
}
|
|
956
|
-
},
|
|
957
|
-
score: {
|
|
958
|
-
type: "number",
|
|
959
|
-
minimum: 0,
|
|
960
|
-
maximum: 100,
|
|
961
|
-
description: "Code quality score"
|
|
962
|
-
}
|
|
963
|
-
},
|
|
964
|
-
required: ["summary", "issues", "score"]
|
|
481
|
+
async execute(args) {
|
|
482
|
+
document.querySelector('#name').value = args.name;
|
|
483
|
+
document.querySelector('#email').value = args.email;
|
|
484
|
+
document.querySelector('#message').value = args.message;
|
|
485
|
+
return { content: [{ type: 'text', text: 'Form filled' }] };
|
|
965
486
|
},
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
487
|
+
},
|
|
488
|
+
{
|
|
489
|
+
name: 'submit-form',
|
|
490
|
+
description: 'Submit the contact form',
|
|
491
|
+
inputSchema: { type: 'object', properties: {} },
|
|
492
|
+
async execute() {
|
|
493
|
+
document.querySelector('#contact-form').submit();
|
|
494
|
+
return { content: [{ type: 'text', text: 'Form submitted' }] };
|
|
495
|
+
},
|
|
496
|
+
},
|
|
497
|
+
],
|
|
975
498
|
});
|
|
976
499
|
```
|
|
977
500
|
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
Tools must return an object with:
|
|
981
|
-
|
|
982
|
-
```typescript
|
|
983
|
-
{
|
|
984
|
-
content: [
|
|
985
|
-
{
|
|
986
|
-
type: "text", // or "image", "resource"
|
|
987
|
-
text: "Result..." // the response content
|
|
988
|
-
}
|
|
989
|
-
],
|
|
990
|
-
isError?: boolean // optional error flag
|
|
991
|
-
}
|
|
992
|
-
```
|
|
993
|
-
|
|
994
|
-
## 🎯 Complete Examples
|
|
995
|
-
|
|
996
|
-
### Todo List Application
|
|
997
|
-
|
|
998
|
-
```javascript
|
|
999
|
-
let todos = [];
|
|
1000
|
-
|
|
1001
|
-
window.navigator.modelContext.provideContext({
|
|
1002
|
-
tools: [
|
|
1003
|
-
{
|
|
1004
|
-
name: "add-todo",
|
|
1005
|
-
description: "Add a new todo item",
|
|
1006
|
-
inputSchema: {
|
|
1007
|
-
type: "object",
|
|
1008
|
-
properties: {
|
|
1009
|
-
text: { type: "string", description: "Todo text" }
|
|
1010
|
-
},
|
|
1011
|
-
required: ["text"]
|
|
1012
|
-
},
|
|
1013
|
-
async execute({ text }) {
|
|
1014
|
-
const todo = { id: Date.now(), text, done: false };
|
|
1015
|
-
todos.push(todo);
|
|
1016
|
-
updateUI();
|
|
1017
|
-
return {
|
|
1018
|
-
content: [{ type: "text", text: `Added: "${text}"` }]
|
|
1019
|
-
};
|
|
1020
|
-
}
|
|
1021
|
-
},
|
|
1022
|
-
{
|
|
1023
|
-
name: "list-todos",
|
|
1024
|
-
description: "Get all todo items",
|
|
1025
|
-
inputSchema: { type: "object", properties: {} },
|
|
1026
|
-
async execute() {
|
|
1027
|
-
const list = todos.map(t =>
|
|
1028
|
-
`${t.done ? '✓' : '○'} ${t.text}`
|
|
1029
|
-
).join('\n');
|
|
1030
|
-
return {
|
|
1031
|
-
content: [{ type: "text", text: list || "No todos" }]
|
|
1032
|
-
};
|
|
1033
|
-
}
|
|
1034
|
-
},
|
|
1035
|
-
{
|
|
1036
|
-
name: "complete-todo",
|
|
1037
|
-
description: "Mark a todo as complete",
|
|
1038
|
-
inputSchema: {
|
|
1039
|
-
type: "object",
|
|
1040
|
-
properties: {
|
|
1041
|
-
id: { type: "number", description: "Todo ID" }
|
|
1042
|
-
},
|
|
1043
|
-
required: ["id"]
|
|
1044
|
-
},
|
|
1045
|
-
async execute({ id }) {
|
|
1046
|
-
const todo = todos.find(t => t.id === id);
|
|
1047
|
-
if (!todo) {
|
|
1048
|
-
return {
|
|
1049
|
-
content: [{ type: "text", text: "Todo not found" }],
|
|
1050
|
-
isError: true
|
|
1051
|
-
};
|
|
1052
|
-
}
|
|
1053
|
-
todo.done = true;
|
|
1054
|
-
updateUI();
|
|
1055
|
-
return {
|
|
1056
|
-
content: [{ type: "text", text: `Completed: "${todo.text}"` }]
|
|
1057
|
-
};
|
|
1058
|
-
}
|
|
1059
|
-
}
|
|
1060
|
-
]
|
|
1061
|
-
});
|
|
1062
|
-
|
|
1063
|
-
function updateUI() {
|
|
1064
|
-
// Update your UI
|
|
1065
|
-
document.getElementById('todo-list').innerHTML =
|
|
1066
|
-
todos.map(t => `<li>${t.done ? '✓' : ''} ${t.text}</li>`).join('');
|
|
1067
|
-
}
|
|
1068
|
-
```
|
|
1069
|
-
|
|
1070
|
-
### E-commerce Product Search
|
|
1071
|
-
|
|
1072
|
-
```javascript
|
|
1073
|
-
window.navigator.modelContext.provideContext({
|
|
1074
|
-
tools: [
|
|
1075
|
-
{
|
|
1076
|
-
name: "search-products",
|
|
1077
|
-
description: "Search for products in the catalog",
|
|
1078
|
-
inputSchema: {
|
|
1079
|
-
type: "object",
|
|
1080
|
-
properties: {
|
|
1081
|
-
query: {
|
|
1082
|
-
type: "string",
|
|
1083
|
-
description: "Search query"
|
|
1084
|
-
},
|
|
1085
|
-
category: {
|
|
1086
|
-
type: "string",
|
|
1087
|
-
description: "Filter by category",
|
|
1088
|
-
enum: ["electronics", "clothing", "books", "all"]
|
|
1089
|
-
},
|
|
1090
|
-
maxPrice: {
|
|
1091
|
-
type: "number",
|
|
1092
|
-
description: "Maximum price filter"
|
|
1093
|
-
}
|
|
1094
|
-
},
|
|
1095
|
-
required: ["query"]
|
|
1096
|
-
},
|
|
1097
|
-
async execute({ query, category = "all", maxPrice }) {
|
|
1098
|
-
const results = await searchProducts({
|
|
1099
|
-
query,
|
|
1100
|
-
category: category !== "all" ? category : undefined,
|
|
1101
|
-
maxPrice
|
|
1102
|
-
});
|
|
1103
|
-
|
|
1104
|
-
const summary = results.map(p =>
|
|
1105
|
-
`${p.name} - $${p.price} (${p.category})`
|
|
1106
|
-
).join('\n');
|
|
1107
|
-
|
|
1108
|
-
return {
|
|
1109
|
-
content: [{
|
|
1110
|
-
type: "text",
|
|
1111
|
-
text: `Found ${results.length} products:\n${summary}`
|
|
1112
|
-
}]
|
|
1113
|
-
};
|
|
1114
|
-
}
|
|
1115
|
-
},
|
|
1116
|
-
{
|
|
1117
|
-
name: "add-to-cart",
|
|
1118
|
-
description: "Add a product to the shopping cart",
|
|
1119
|
-
inputSchema: {
|
|
1120
|
-
type: "object",
|
|
1121
|
-
properties: {
|
|
1122
|
-
productId: { type: "string" },
|
|
1123
|
-
quantity: { type: "number", default: 1 }
|
|
1124
|
-
},
|
|
1125
|
-
required: ["productId"]
|
|
1126
|
-
},
|
|
1127
|
-
async execute({ productId, quantity = 1 }) {
|
|
1128
|
-
await addToCart(productId, quantity);
|
|
1129
|
-
return {
|
|
1130
|
-
content: [{
|
|
1131
|
-
type: "text",
|
|
1132
|
-
text: `Added ${quantity}x product ${productId} to cart`
|
|
1133
|
-
}]
|
|
1134
|
-
};
|
|
1135
|
-
}
|
|
1136
|
-
}
|
|
1137
|
-
]
|
|
1138
|
-
});
|
|
1139
|
-
```
|
|
1140
|
-
|
|
1141
|
-
## 🔧 Dynamic Tool Registration (Component Lifecycle)
|
|
1142
|
-
|
|
1143
|
-
### React Component Example
|
|
1144
|
-
|
|
1145
|
-
Perfect for managing tools tied to component lifecycle:
|
|
1146
|
-
|
|
1147
|
-
```javascript
|
|
1148
|
-
import { useEffect } from 'react';
|
|
1149
|
-
|
|
1150
|
-
function MyComponent() {
|
|
1151
|
-
useEffect(() => {
|
|
1152
|
-
// Register component-specific tool when component mounts (Bucket B)
|
|
1153
|
-
const registration = window.navigator.modelContext.registerTool({
|
|
1154
|
-
name: "component-action",
|
|
1155
|
-
description: "Action specific to this component",
|
|
1156
|
-
inputSchema: { type: "object", properties: {} },
|
|
1157
|
-
async execute() {
|
|
1158
|
-
// Access component state/methods here
|
|
1159
|
-
return {
|
|
1160
|
-
content: [{ type: "text", text: "Component action executed!" }]
|
|
1161
|
-
};
|
|
1162
|
-
}
|
|
1163
|
-
});
|
|
1164
|
-
|
|
1165
|
-
// Cleanup: unregister when component unmounts
|
|
1166
|
-
return () => {
|
|
1167
|
-
registration.unregister();
|
|
1168
|
-
};
|
|
1169
|
-
}, []);
|
|
1170
|
-
|
|
1171
|
-
return <div>My Component</div>;
|
|
1172
|
-
}
|
|
1173
|
-
```
|
|
1174
|
-
|
|
1175
|
-
### Persistence Across provideContext() Calls
|
|
1176
|
-
|
|
1177
|
-
```javascript
|
|
1178
|
-
// Step 1: Register base tools (Bucket A)
|
|
1179
|
-
window.navigator.modelContext.provideContext({
|
|
1180
|
-
tools: [
|
|
1181
|
-
{ name: "base-tool-1", description: "Base tool", inputSchema: {}, async execute() {} }
|
|
1182
|
-
]
|
|
1183
|
-
});
|
|
1184
|
-
// Tools: ["base-tool-1"]
|
|
1185
|
-
|
|
1186
|
-
// Step 2: Register dynamic tool (Bucket B)
|
|
1187
|
-
const reg = window.navigator.modelContext.registerTool({
|
|
1188
|
-
name: "dynamic-tool",
|
|
1189
|
-
description: "Dynamic tool",
|
|
1190
|
-
inputSchema: { type: "object", properties: {} },
|
|
1191
|
-
async execute() {
|
|
1192
|
-
return { content: [{ type: "text", text: "Dynamic!" }] };
|
|
1193
|
-
}
|
|
1194
|
-
});
|
|
1195
|
-
// Tools: ["base-tool-1", "dynamic-tool"]
|
|
1196
|
-
|
|
1197
|
-
// Step 3: Update base tools via provideContext
|
|
1198
|
-
window.navigator.modelContext.provideContext({
|
|
1199
|
-
tools: [
|
|
1200
|
-
{ name: "base-tool-2", description: "New base tool", inputSchema: {}, async execute() {} }
|
|
1201
|
-
]
|
|
1202
|
-
});
|
|
1203
|
-
// Tools: ["base-tool-2", "dynamic-tool"]
|
|
1204
|
-
// ✅ "dynamic-tool" persists! Only "base-tool-1" was cleared
|
|
1205
|
-
|
|
1206
|
-
// Step 4: Clean up dynamic tool
|
|
1207
|
-
reg.unregister();
|
|
1208
|
-
// Tools: ["base-tool-2"]
|
|
1209
|
-
```
|
|
1210
|
-
|
|
1211
|
-
### Name Collision Protection
|
|
1212
|
-
|
|
1213
|
-
```javascript
|
|
1214
|
-
// Register a base tool
|
|
1215
|
-
window.navigator.modelContext.provideContext({
|
|
1216
|
-
tools: [
|
|
1217
|
-
{ name: "my-tool", description: "Base", inputSchema: {}, async execute() {} }
|
|
1218
|
-
]
|
|
1219
|
-
});
|
|
1220
|
-
|
|
1221
|
-
// This will throw an error!
|
|
1222
|
-
try {
|
|
1223
|
-
window.navigator.modelContext.registerTool({
|
|
1224
|
-
name: "my-tool", // ❌ Name collision with Bucket A
|
|
1225
|
-
description: "Dynamic",
|
|
1226
|
-
inputSchema: {},
|
|
1227
|
-
async execute() {}
|
|
1228
|
-
});
|
|
1229
|
-
} catch (error) {
|
|
1230
|
-
console.error(error.message);
|
|
1231
|
-
// Error: Tool name collision: "my-tool" is already registered via provideContext()
|
|
1232
|
-
}
|
|
1233
|
-
|
|
1234
|
-
// Similarly, can't unregister a base tool
|
|
1235
|
-
const baseToolList = window.navigator.modelContext.provideContext({
|
|
1236
|
-
tools: [{ name: "base", description: "Base", inputSchema: {}, async execute() {} }]
|
|
1237
|
-
});
|
|
1238
|
-
|
|
1239
|
-
// This will also throw an error!
|
|
1240
|
-
try {
|
|
1241
|
-
// Assuming we got a reference somehow
|
|
1242
|
-
// registration.unregister(); would fail for a base tool
|
|
1243
|
-
} catch (error) {
|
|
1244
|
-
// Error: Cannot unregister tool "base": This tool was registered via provideContext()
|
|
1245
|
-
}
|
|
1246
|
-
```
|
|
1247
|
-
|
|
1248
|
-
## 🔧 Event-Based Tool Calls (Advanced)
|
|
1249
|
-
|
|
1250
|
-
For manifest-based or advanced scenarios, you can handle tool calls as events:
|
|
1251
|
-
|
|
1252
|
-
```javascript
|
|
1253
|
-
window.navigator.modelContext.addEventListener('toolcall', async (event) => {
|
|
1254
|
-
console.log(`Tool called: ${event.name}`, event.arguments);
|
|
1255
|
-
|
|
1256
|
-
if (event.name === "custom-tool") {
|
|
1257
|
-
// Prevent default execution
|
|
1258
|
-
event.preventDefault();
|
|
1259
|
-
|
|
1260
|
-
// Provide custom response
|
|
1261
|
-
event.respondWith({
|
|
1262
|
-
content: [{
|
|
1263
|
-
type: "text",
|
|
1264
|
-
text: "Custom response from event handler"
|
|
1265
|
-
}]
|
|
1266
|
-
});
|
|
1267
|
-
}
|
|
1268
|
-
|
|
1269
|
-
// If not prevented, the tool's execute function will run normally
|
|
1270
|
-
});
|
|
1271
|
-
```
|
|
1272
|
-
|
|
1273
|
-
### Hybrid Approach
|
|
1274
|
-
|
|
1275
|
-
The API supports both approaches simultaneously:
|
|
1276
|
-
|
|
1277
|
-
1. **Event dispatched first** - `toolcall` event is fired
|
|
1278
|
-
2. **Event can override** - Call `event.preventDefault()` and `event.respondWith()`
|
|
1279
|
-
3. **Default execution** - If not prevented, the tool's `execute()` function runs
|
|
1280
|
-
|
|
1281
|
-
This allows flexibility for different use cases.
|
|
1282
|
-
|
|
1283
|
-
## 🏗️ Architecture
|
|
1284
|
-
|
|
1285
|
-
```
|
|
1286
|
-
┌─────────────────┐
|
|
1287
|
-
│ AI Agent │
|
|
1288
|
-
│ (MCP Client) │
|
|
1289
|
-
└────────┬────────┘
|
|
1290
|
-
│ MCP Protocol
|
|
1291
|
-
│ (JSON-RPC)
|
|
1292
|
-
┌────────▼────────┐
|
|
1293
|
-
│ MCP Server │
|
|
1294
|
-
│ (Internal) │
|
|
1295
|
-
└────────┬────────┘
|
|
1296
|
-
│
|
|
1297
|
-
┌────────▼───────────────────┐
|
|
1298
|
-
│ navigator.modelContext │ ◄── Your app registers tools here
|
|
1299
|
-
│ (This pkg) │
|
|
1300
|
-
└────────────────────────────┘
|
|
1301
|
-
```
|
|
1302
|
-
|
|
1303
|
-
This package:
|
|
1304
|
-
1. Exposes `window.navigator.modelContext` API (W3C Web Model Context standard)
|
|
1305
|
-
2. Internally creates an MCP Server
|
|
1306
|
-
3. Bridges tool calls between the two protocols
|
|
1307
|
-
4. Uses TabServerTransport for browser communication
|
|
1308
|
-
|
|
1309
|
-
## 🔍 Feature Detection
|
|
1310
|
-
|
|
1311
|
-
Check if the API is available:
|
|
1312
|
-
|
|
1313
|
-
```javascript
|
|
1314
|
-
if ("modelContext" in navigator) {
|
|
1315
|
-
// API is available
|
|
1316
|
-
navigator.modelContext.provideContext({ tools: [...] });
|
|
1317
|
-
} else {
|
|
1318
|
-
console.warn("Web Model Context API not available");
|
|
1319
|
-
}
|
|
1320
|
-
```
|
|
1321
|
-
|
|
1322
|
-
## 🐛 Debugging
|
|
1323
|
-
|
|
1324
|
-
### Enable Debug Logging
|
|
1325
|
-
|
|
1326
|
-
The @mcp-b/global library includes a lightweight logging system that can be enabled in the browser console. By default, the console is kept clean (only errors and warnings are shown). You can enable detailed debug logging when troubleshooting:
|
|
1327
|
-
|
|
1328
|
-
```javascript
|
|
1329
|
-
// Enable all debug logging
|
|
1330
|
-
localStorage.setItem('WEBMCP_DEBUG', '*');
|
|
1331
|
-
|
|
1332
|
-
// Enable specific namespaces
|
|
1333
|
-
localStorage.setItem('WEBMCP_DEBUG', 'WebModelContext');
|
|
1334
|
-
localStorage.setItem('WEBMCP_DEBUG', 'NativeAdapter,MCPBridge');
|
|
1335
|
-
|
|
1336
|
-
// Refresh the page to apply changes
|
|
1337
|
-
location.reload();
|
|
1338
|
-
```
|
|
1339
|
-
|
|
1340
|
-
To disable debug logging:
|
|
1341
|
-
|
|
1342
|
-
```javascript
|
|
1343
|
-
localStorage.removeItem('WEBMCP_DEBUG');
|
|
1344
|
-
location.reload();
|
|
1345
|
-
```
|
|
1346
|
-
|
|
1347
|
-
**Available Namespaces:**
|
|
1348
|
-
- `WebModelContext` - Main polyfill implementation
|
|
1349
|
-
- `NativeAdapter` - Native Chromium API adapter
|
|
1350
|
-
- `MCPBridge` - MCP server and transport setup
|
|
1351
|
-
- `ModelContextTesting` - Testing API operations
|
|
1352
|
-
|
|
1353
|
-
**Log Levels:**
|
|
1354
|
-
- **Error** (always shown): Critical failures and exceptions
|
|
1355
|
-
- **Warn** (always shown): Compatibility warnings and potential issues
|
|
1356
|
-
- **Info** (debug mode only): Initialization and setup progress
|
|
1357
|
-
- **Debug** (debug mode only): Detailed operation traces
|
|
1358
|
-
|
|
1359
|
-
### Access Internal Bridge
|
|
1360
|
-
|
|
1361
|
-
In development mode, access the internal bridge:
|
|
1362
|
-
|
|
1363
|
-
```javascript
|
|
1364
|
-
if (window.__mcpBridge) {
|
|
1365
|
-
console.log("MCP Server:", window.__mcpBridge.server);
|
|
1366
|
-
console.log("Registered tools:", window.__mcpBridge.tools);
|
|
1367
|
-
}
|
|
1368
|
-
```
|
|
1369
|
-
|
|
1370
|
-
## 🧪 Testing API (`navigator.modelContextTesting`)
|
|
1371
|
-
|
|
1372
|
-
This package provides a **Model Context Testing API** at `window.navigator.modelContextTesting` for debugging and testing your tools during development.
|
|
1373
|
-
|
|
1374
|
-
### Native Support in Chromium
|
|
1375
|
-
|
|
1376
|
-
**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.
|
|
1377
|
-
|
|
1378
|
-
#### How to Enable Native API in Chromium:
|
|
1379
|
-
|
|
1380
|
-
**Option 1: Chrome Flags**
|
|
1381
|
-
1. Navigate to `chrome://flags`
|
|
1382
|
-
2. Search for "Experimental Web Platform Features"
|
|
1383
|
-
3. Enable the flag
|
|
1384
|
-
4. Restart your browser
|
|
1385
|
-
|
|
1386
|
-
**Option 2: Command Line**
|
|
1387
|
-
```bash
|
|
1388
|
-
# Launch Chrome/Edge with experimental features
|
|
1389
|
-
chrome --enable-experimental-web-platform-features
|
|
1390
|
-
```
|
|
1391
|
-
|
|
1392
|
-
**Detection**: When the native API is detected, you'll see this console message:
|
|
1393
|
-
```
|
|
1394
|
-
✅ [Model Context Testing] Native implementation detected (Chromium experimental feature)
|
|
1395
|
-
Using native window.navigator.modelContextTesting from browser
|
|
1396
|
-
```
|
|
1397
|
-
|
|
1398
|
-
### Polyfill Fallback
|
|
1399
|
-
|
|
1400
|
-
If the native API is not available, this package automatically provides a polyfill implementation with the same interface:
|
|
1401
|
-
|
|
1402
|
-
```
|
|
1403
|
-
[Model Context Testing] Native implementation not found, installing polyfill
|
|
1404
|
-
💡 To use the native implementation in Chromium:
|
|
1405
|
-
- Navigate to chrome://flags
|
|
1406
|
-
- Enable "Experimental Web Platform Features"
|
|
1407
|
-
- Or launch with: --enable-experimental-web-platform-features
|
|
1408
|
-
✅ [Model Context Testing] Polyfill installed at window.navigator.modelContextTesting
|
|
1409
|
-
```
|
|
1410
|
-
|
|
1411
|
-
### API Reference
|
|
1412
|
-
|
|
1413
|
-
#### `getToolCalls(): Array<ToolCall>`
|
|
1414
|
-
|
|
1415
|
-
Get a history of all tool calls made during the session.
|
|
1416
|
-
|
|
1417
|
-
```javascript
|
|
1418
|
-
// Register and call some tools
|
|
1419
|
-
window.navigator.modelContext.provideContext({
|
|
1420
|
-
tools: [{
|
|
1421
|
-
name: "greet",
|
|
1422
|
-
description: "Greet a user",
|
|
1423
|
-
inputSchema: {
|
|
1424
|
-
type: "object",
|
|
1425
|
-
properties: { name: { type: "string" } },
|
|
1426
|
-
required: ["name"]
|
|
1427
|
-
},
|
|
1428
|
-
async execute({ name }) {
|
|
1429
|
-
return { content: [{ type: "text", text: `Hello, ${name}!` }] };
|
|
1430
|
-
}
|
|
1431
|
-
}]
|
|
1432
|
-
});
|
|
1433
|
-
|
|
1434
|
-
// Simulate a tool call
|
|
1435
|
-
// (In practice, this would come from an AI agent)
|
|
1436
|
-
|
|
1437
|
-
// Later, inspect the tool call history
|
|
1438
|
-
const calls = window.navigator.modelContextTesting.getToolCalls();
|
|
1439
|
-
console.log(calls);
|
|
1440
|
-
// [
|
|
1441
|
-
// {
|
|
1442
|
-
// toolName: "greet",
|
|
1443
|
-
// arguments: { name: "Alice" },
|
|
1444
|
-
// timestamp: 1699123456789
|
|
1445
|
-
// }
|
|
1446
|
-
// ]
|
|
1447
|
-
```
|
|
1448
|
-
|
|
1449
|
-
#### `clearToolCalls(): void`
|
|
1450
|
-
|
|
1451
|
-
Clear the tool call history.
|
|
1452
|
-
|
|
1453
|
-
```javascript
|
|
1454
|
-
window.navigator.modelContextTesting.clearToolCalls();
|
|
1455
|
-
console.log(window.navigator.modelContextTesting.getToolCalls()); // []
|
|
1456
|
-
```
|
|
1457
|
-
|
|
1458
|
-
#### `setMockToolResponse(toolName: string, response: ToolResponse): void`
|
|
1459
|
-
|
|
1460
|
-
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.
|
|
1461
|
-
|
|
1462
|
-
```javascript
|
|
1463
|
-
// Mock the "greet" tool to always return a specific response
|
|
1464
|
-
window.navigator.modelContextTesting.setMockToolResponse("greet", {
|
|
1465
|
-
content: [{
|
|
1466
|
-
type: "text",
|
|
1467
|
-
text: "Mocked greeting!"
|
|
1468
|
-
}]
|
|
1469
|
-
});
|
|
1470
|
-
|
|
1471
|
-
// Now when the tool is called, it returns the mock response
|
|
1472
|
-
// (The execute function is never called)
|
|
1473
|
-
```
|
|
1474
|
-
|
|
1475
|
-
#### `clearMockToolResponse(toolName: string): void`
|
|
1476
|
-
|
|
1477
|
-
Remove the mock response for a specific tool.
|
|
1478
|
-
|
|
1479
|
-
```javascript
|
|
1480
|
-
window.navigator.modelContextTesting.clearMockToolResponse("greet");
|
|
1481
|
-
// Tool will now use its actual execute function
|
|
1482
|
-
```
|
|
1483
|
-
|
|
1484
|
-
#### `clearAllMockToolResponses(): void`
|
|
1485
|
-
|
|
1486
|
-
Remove all mock tool responses.
|
|
1487
|
-
|
|
1488
|
-
```javascript
|
|
1489
|
-
window.navigator.modelContextTesting.clearAllMockToolResponses();
|
|
1490
|
-
```
|
|
1491
|
-
|
|
1492
|
-
#### `getRegisteredTools(): Array<ToolDescriptor>`
|
|
1493
|
-
|
|
1494
|
-
Get the list of all currently registered tools (same as `modelContext.listTools()`).
|
|
1495
|
-
|
|
1496
|
-
```javascript
|
|
1497
|
-
const tools = window.navigator.modelContextTesting.getRegisteredTools();
|
|
1498
|
-
console.log(tools.map(t => t.name)); // ["greet", "add-todo", ...]
|
|
1499
|
-
```
|
|
1500
|
-
|
|
1501
|
-
#### `reset(): void`
|
|
1502
|
-
|
|
1503
|
-
Reset the entire testing state (clears tool call history and all mock responses).
|
|
1504
|
-
|
|
1505
|
-
```javascript
|
|
1506
|
-
window.navigator.modelContextTesting.reset();
|
|
1507
|
-
```
|
|
1508
|
-
|
|
1509
|
-
### Testing Workflow Example
|
|
1510
|
-
|
|
1511
|
-
Here's a complete example of using the testing API:
|
|
1512
|
-
|
|
1513
|
-
```javascript
|
|
1514
|
-
// 1. Register your tools
|
|
1515
|
-
window.navigator.modelContext.provideContext({
|
|
1516
|
-
tools: [
|
|
1517
|
-
{
|
|
1518
|
-
name: "add-todo",
|
|
1519
|
-
description: "Add a todo item",
|
|
1520
|
-
inputSchema: {
|
|
1521
|
-
type: "object",
|
|
1522
|
-
properties: { text: { type: "string" } },
|
|
1523
|
-
required: ["text"]
|
|
1524
|
-
},
|
|
1525
|
-
async execute({ text }) {
|
|
1526
|
-
// This would normally add to your app state
|
|
1527
|
-
return { content: [{ type: "text", text: `Added: ${text}` }] };
|
|
1528
|
-
}
|
|
1529
|
-
}
|
|
1530
|
-
]
|
|
1531
|
-
});
|
|
1532
|
-
|
|
1533
|
-
// 2. Set up mocks for testing
|
|
1534
|
-
window.navigator.modelContextTesting.setMockToolResponse("add-todo", {
|
|
1535
|
-
content: [{ type: "text", text: "Mock: Todo added successfully" }]
|
|
1536
|
-
});
|
|
1537
|
-
|
|
1538
|
-
// 3. Simulate tool calls (or let AI agent call them)
|
|
1539
|
-
// The tool will return the mock response instead of executing
|
|
1540
|
-
|
|
1541
|
-
// 4. Inspect tool call history
|
|
1542
|
-
const calls = window.navigator.modelContextTesting.getToolCalls();
|
|
1543
|
-
console.log(`${calls.length} tool calls made`);
|
|
1544
|
-
calls.forEach(call => {
|
|
1545
|
-
console.log(`- ${call.toolName}`, call.arguments);
|
|
1546
|
-
});
|
|
1547
|
-
|
|
1548
|
-
// 5. Clean up after testing
|
|
1549
|
-
window.navigator.modelContextTesting.reset();
|
|
1550
|
-
```
|
|
1551
|
-
|
|
1552
|
-
### Integration Testing Example
|
|
1553
|
-
|
|
1554
|
-
Perfect for automated testing with frameworks like Jest, Vitest, or Playwright:
|
|
1555
|
-
|
|
1556
|
-
```javascript
|
|
1557
|
-
// test/model-context.test.js
|
|
1558
|
-
import { test, expect } from 'vitest';
|
|
1559
|
-
|
|
1560
|
-
test('todo tool creates correct response', async () => {
|
|
1561
|
-
// Arrange
|
|
1562
|
-
const mockResponse = {
|
|
1563
|
-
content: [{ type: "text", text: "Test todo added" }]
|
|
1564
|
-
};
|
|
1565
|
-
|
|
1566
|
-
window.navigator.modelContextTesting.setMockToolResponse(
|
|
1567
|
-
"add-todo",
|
|
1568
|
-
mockResponse
|
|
1569
|
-
);
|
|
1570
|
-
|
|
1571
|
-
// Act
|
|
1572
|
-
// Trigger your AI agent or directly call the tool via MCP
|
|
1573
|
-
// ...
|
|
1574
|
-
|
|
1575
|
-
// Assert
|
|
1576
|
-
const calls = window.navigator.modelContextTesting.getToolCalls();
|
|
1577
|
-
expect(calls).toHaveLength(1);
|
|
1578
|
-
expect(calls[0].toolName).toBe("add-todo");
|
|
1579
|
-
expect(calls[0].arguments).toEqual({ text: "Test item" });
|
|
1580
|
-
|
|
1581
|
-
// Cleanup
|
|
1582
|
-
window.navigator.modelContextTesting.reset();
|
|
1583
|
-
});
|
|
1584
|
-
```
|
|
1585
|
-
|
|
1586
|
-
### Browser Compatibility
|
|
501
|
+
## Browser Compatibility
|
|
1587
502
|
|
|
1588
503
|
| Browser | Native Support | Polyfill |
|
|
1589
504
|
|---------|---------------|----------|
|
|
1590
|
-
| Chrome/Edge (with flag) |
|
|
1591
|
-
| Chrome/Edge (default) |
|
|
1592
|
-
| Firefox |
|
|
1593
|
-
| Safari |
|
|
1594
|
-
| Other browsers | ❌ No | ✅ Yes |
|
|
1595
|
-
|
|
1596
|
-
The polyfill automatically detects and defers to the native implementation when available, ensuring forward compatibility as browsers adopt this standard.
|
|
505
|
+
| Chrome/Edge (with flag) | Yes | N/A |
|
|
506
|
+
| Chrome/Edge (default) | No | Yes |
|
|
507
|
+
| Firefox | No | Yes |
|
|
508
|
+
| Safari | No | Yes |
|
|
1597
509
|
|
|
1598
510
|
## Zod Version Compatibility
|
|
1599
511
|
|
|
1600
|
-
This package supports **Zod 3.25+**
|
|
512
|
+
This package supports **Zod 3.25.76+** (3.x only). JSON Schema is also supported if you prefer not to use Zod.
|
|
1601
513
|
|
|
1602
|
-
|
|
1603
|
-
import { z } from 'zod';
|
|
514
|
+
## Type Exports
|
|
1604
515
|
|
|
1605
|
-
|
|
1606
|
-
tools: [{
|
|
1607
|
-
name: "my-tool",
|
|
1608
|
-
inputSchema: {
|
|
1609
|
-
name: z.string().describe('User name'),
|
|
1610
|
-
age: z.number().min(0)
|
|
1611
|
-
},
|
|
1612
|
-
async execute({ name, age }) {
|
|
1613
|
-
return { content: [{ type: "text", text: `Hello, ${name}!` }] };
|
|
1614
|
-
}
|
|
1615
|
-
}]
|
|
1616
|
-
});
|
|
1617
|
-
```
|
|
1618
|
-
|
|
1619
|
-
### JSON Schema Alternative
|
|
516
|
+
All types are re-exported for TypeScript consumers:
|
|
1620
517
|
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
}
|
|
1637
|
-
}]
|
|
1638
|
-
});
|
|
1639
|
-
```
|
|
1640
|
-
|
|
1641
|
-
## 📦 What's Included
|
|
1642
|
-
|
|
1643
|
-
- **Web Model Context API** - Standard `window.navigator.modelContext` interface
|
|
1644
|
-
- **Model Context Testing API** - `window.navigator.modelContextTesting` for debugging and testing (with native Chromium support detection)
|
|
1645
|
-
- **Dynamic Tool Registration** - `registerTool()` with `unregister()` function
|
|
1646
|
-
- **MCP Bridge** - Automatic bridging to Model Context Protocol
|
|
1647
|
-
- **Tab Transport** - Communication layer for browser contexts
|
|
1648
|
-
- **Event System** - Hybrid tool call handling
|
|
1649
|
-
- **TypeScript Types** - Full type definitions included
|
|
1650
|
-
|
|
1651
|
-
## 🔒 Security Considerations
|
|
1652
|
-
|
|
1653
|
-
### Origin Restrictions
|
|
1654
|
-
|
|
1655
|
-
By default, the MCP transport allows connections from any origin (`*`). For production, you should configure allowed origins:
|
|
1656
|
-
|
|
1657
|
-
```javascript
|
|
1658
|
-
// Future configuration API
|
|
1659
|
-
window.navigator.modelContext.configure({
|
|
1660
|
-
allowedOrigins: [
|
|
1661
|
-
'https://your-app.com',
|
|
1662
|
-
'https://trusted-agent.com'
|
|
1663
|
-
]
|
|
1664
|
-
});
|
|
1665
|
-
```
|
|
1666
|
-
|
|
1667
|
-
### Tool Validation
|
|
1668
|
-
|
|
1669
|
-
Always validate inputs in your tool implementations:
|
|
1670
|
-
|
|
1671
|
-
```javascript
|
|
1672
|
-
{
|
|
1673
|
-
name: "delete-item",
|
|
1674
|
-
description: "Delete an item",
|
|
1675
|
-
inputSchema: {
|
|
1676
|
-
type: "object",
|
|
1677
|
-
properties: {
|
|
1678
|
-
id: { type: "string", pattern: "^[a-zA-Z0-9]+$" }
|
|
1679
|
-
},
|
|
1680
|
-
required: ["id"]
|
|
1681
|
-
},
|
|
1682
|
-
async execute({ id }) {
|
|
1683
|
-
// Additional validation
|
|
1684
|
-
if (!isValidId(id)) {
|
|
1685
|
-
return {
|
|
1686
|
-
content: [{ type: "text", text: "Invalid ID" }],
|
|
1687
|
-
isError: true
|
|
1688
|
-
};
|
|
1689
|
-
}
|
|
1690
|
-
|
|
1691
|
-
// Proceed with deletion
|
|
1692
|
-
await deleteItem(id);
|
|
1693
|
-
return {
|
|
1694
|
-
content: [{ type: "text", text: "Item deleted" }]
|
|
1695
|
-
};
|
|
1696
|
-
}
|
|
1697
|
-
}
|
|
518
|
+
```typescript
|
|
519
|
+
import type {
|
|
520
|
+
CallToolResult,
|
|
521
|
+
InputSchema,
|
|
522
|
+
ModelContext,
|
|
523
|
+
ModelContextCore,
|
|
524
|
+
ModelContextOptions,
|
|
525
|
+
NativeModelContextBehavior,
|
|
526
|
+
ToolAnnotations,
|
|
527
|
+
ToolDescriptor,
|
|
528
|
+
ToolListItem,
|
|
529
|
+
ToolResponse,
|
|
530
|
+
TransportConfiguration,
|
|
531
|
+
WebModelContextInitOptions,
|
|
532
|
+
} from '@mcp-b/global';
|
|
1698
533
|
```
|
|
1699
534
|
|
|
1700
|
-
##
|
|
1701
|
-
|
|
1702
|
-
### How do AI agents connect to my website?
|
|
535
|
+
## Tool Routing Contract
|
|
1703
536
|
|
|
1704
|
-
|
|
537
|
+
- MCP `tools/list`, `tools/call`, and tool list update notifications are sourced from `navigator.modelContextTesting`.
|
|
538
|
+
- `@mcp-b/global` requires `navigator.modelContextTesting` to be available at initialization time.
|
|
1705
539
|
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
No! Use the IIFE version with a single `<script>` tag. For bundler users, the ESM version is also available.
|
|
1709
|
-
|
|
1710
|
-
### Is this production-ready?
|
|
1711
|
-
|
|
1712
|
-
Yes! The polyfill handles tool registration, lifecycle management, and automatically uses native Chromium implementation when available.
|
|
1713
|
-
|
|
1714
|
-
### What about browser support?
|
|
1715
|
-
|
|
1716
|
-
Works in all modern browsers. Native API support is available in Chromium with experimental flags enabled.
|
|
1717
|
-
|
|
1718
|
-
## 🤝 Related Packages
|
|
540
|
+
## Related Packages
|
|
1719
541
|
|
|
1720
542
|
- [`@mcp-b/transports`](https://docs.mcp-b.ai/packages/transports) - MCP transport implementations
|
|
1721
543
|
- [`@mcp-b/react-webmcp`](https://docs.mcp-b.ai/packages/react-webmcp) - React hooks for MCP
|
|
1722
544
|
- [`@mcp-b/extension-tools`](https://docs.mcp-b.ai/packages/extension-tools) - Chrome Extension API tools
|
|
1723
545
|
- [`@mcp-b/chrome-devtools-mcp`](https://docs.mcp-b.ai/packages/chrome-devtools-mcp) - Connect desktop AI agents to browser tools
|
|
1724
|
-
- [`@modelcontextprotocol/sdk`](https://www.npmjs.com/package/@modelcontextprotocol/sdk) - Official MCP SDK
|
|
1725
546
|
|
|
1726
|
-
##
|
|
547
|
+
## Resources
|
|
1727
548
|
|
|
1728
549
|
- [WebMCP Documentation](https://docs.mcp-b.ai)
|
|
1729
550
|
- [Web Model Context API Explainer](https://github.com/nicolo-ribaudo/model-context-protocol-api)
|
|
1730
551
|
- [Model Context Protocol Spec](https://modelcontextprotocol.io/)
|
|
1731
|
-
- [MCP GitHub Repository](https://github.com/modelcontextprotocol)
|
|
1732
552
|
|
|
1733
|
-
##
|
|
553
|
+
## License
|
|
1734
554
|
|
|
1735
555
|
MIT - see [LICENSE](../../LICENSE) for details
|
|
1736
|
-
|
|
1737
|
-
## 🙋 Support
|
|
1738
|
-
|
|
1739
|
-
- [GitHub Issues](https://github.com/WebMCP-org/npm-packages/issues)
|
|
1740
|
-
- [Documentation](https://docs.mcp-b.ai)
|
|
1741
|
-
- [Discord Community](https://discord.gg/a9fBR6Bw)
|