@mcp-b/global 1.5.0 → 1.6.1

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 CHANGED
@@ -8,7 +8,7 @@
8
8
  [![Bundle Size](https://img.shields.io/badge/IIFE-285KB-blue?style=flat-square)](https://bundlephobia.com/package/@mcp-b/global)
9
9
  [![W3C](https://img.shields.io/badge/W3C-Web_Model_Context-005A9C?style=flat-square)](https://github.com/nicolo-ribaudo/model-context-protocol-api)
10
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)**
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
- | **Two-Bucket System** | Manage app-level and component-level tools separately |
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
- ## Use Cases
26
+ ## Package Selection
27
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
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
- ## 🚀 Quick Start
32
+ ## Quick Start
35
33
 
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>`:
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
- // window.navigator.modelContext is already available!
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
- **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
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 Script Tag
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
- <!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>
72
+ <script type="module">
73
+ import '@mcp-b/global';
74
+ navigator.modelContext.provideContext({ tools: [/* your tools */] });
75
+ </script>
109
76
  ```
110
77
 
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.
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,1654 +86,470 @@ npm install @mcp-b/global
121
86
  ```javascript
122
87
  import '@mcp-b/global';
123
88
 
124
- // window.navigator.modelContext is now available
125
- window.navigator.modelContext.provideContext({
89
+ navigator.modelContext.provideContext({
126
90
  tools: [/* your tools */]
127
91
  });
128
92
  ```
129
93
 
130
- ## 📜 Traditional Web Standard Usage
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
- ```javascript
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
- // Later, unregister if needed (e.g., when component unmounts)
252
- // registration.unregister();
253
- }
254
- ```
98
+ #### `initializeWebModelContext(options?)`
255
99
 
256
- ### Event-Driven Pattern
100
+ Initializes the global adapter. Replaces `navigator.modelContext` with a `BrowserMcpServer` instance that bridges WebMCP tools to the MCP protocol layer.
257
101
 
258
- Similar to other DOM events, you can listen for tool calls:
102
+ ```typescript
103
+ import { initializeWebModelContext } from '@mcp-b/global';
259
104
 
260
- ```javascript
261
- if ('modelContext' in navigator) {
262
- // Listen for tool calls (like 'message' or 'click' events)
263
- navigator.modelContext.addEventListener('toolcall', (event) => {
264
- console.log(`Tool "${event.name}" called with:`, event.arguments);
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
- ### Complete Standalone Example
278
-
279
- Save this as `index.html` and open in a browser:
280
-
281
- ```html
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
- <div class="card">
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
- <div class="card">
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
- <script>
312
- // Application state
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
- window.deleteNote = (index) => {
343
- notes.splice(index, 1);
344
- renderNotes();
345
- };
126
+ initializeWebModelContext();
127
+ // ... use tools ...
128
+ cleanupWebModelContext();
346
129
 
347
- // Web Model Context API - Register tools for AI agents
348
- if ('modelContext' in navigator) {
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
- This example demonstrates:
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
- By default, the global package runs **two MCP servers** that share the same tool registry:
136
+ After initialization, `navigator.modelContext` exposes these methods:
466
137
 
467
- 1. **Tab Server** (`TabServerTransport`) - For same-window communication
468
- 2. **Iframe Server** (`IframeChildTransport`) - Auto-enabled when running in an iframe (when `window.parent !== window`)
138
+ #### `provideContext(options?)`
469
139
 
470
- Both servers expose the same tools (Bucket A + Bucket B), allowing your tools to be accessed from:
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
- **Example: Running in an Iframe**
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: "iframe-action",
489
- description: "Action from iframe",
490
- inputSchema: { type: "object", properties: {} },
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: "text", text: "Hello from iframe!" }]
169
+ content: [{ type: 'text', text: JSON.stringify(getCart()) }],
494
170
  };
495
- }
496
- }
497
- ]
171
+ },
172
+ },
173
+ ],
498
174
  });
499
175
  ```
500
176
 
501
- **Configure Iframe Server**
502
-
503
- You can customize or disable the iframe server:
177
+ #### `registerTool(tool)`
504
178
 
505
- ```ts
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
- // Customize iframe server
509
- initializeWebModelContext({
510
- transport: {
511
- iframeServer: {
512
- allowedOrigins: ['https://parent-app.com'], // Only allow specific parent
513
- channelId: 'custom-iframe-channel',
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
- // Disable iframe server (only Tab server runs)
519
- initializeWebModelContext({
520
- transport: {
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
- **Custom Transport Factory**
202
+ #### `unregisterTool(name)`
537
203
 
538
- Provide `transport.create` to supply any MCP `Transport` implementation instead of the built-in dual-server mode:
204
+ Removes a tool by name.
539
205
 
540
- ```ts
541
- import { initializeWebModelContext } from '@mcp-b/global';
542
- import { CustomTransport } from './my-transport';
206
+ ```typescript
207
+ navigator.modelContext.unregisterTool('add-to-cart');
208
+ ```
543
209
 
544
- initializeWebModelContext({
545
- transport: {
546
- create: () => new CustomTransport(),
547
- },
548
- });
210
+ #### `clearContext()`
211
+
212
+ Removes all registered tools.
213
+
214
+ ```typescript
215
+ navigator.modelContext.clearContext();
549
216
  ```
550
217
 
551
- ## 🔄 Native Chromium API Support
218
+ #### `listTools()`
552
219
 
553
- This package **automatically detects and integrates** with Chromium's native Web Model Context API when available. No configuration needed - it just works!
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
+ ```
554
226
 
555
- For standards/source tracking and future conformance planning, see `./WEBMCP-CONFORMANCE-REFERENCES.md`.
227
+ #### `callTool(params)`
556
228
 
557
- ### Automatic Detection & Integration
229
+ Executes a registered tool by name.
558
230
 
559
- When you call `initializeWebModelContext()` (or when auto-initialization runs):
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
+ ```
560
238
 
561
- 1. **Native API detected** (both `navigator.modelContext` and `navigator.modelContextTesting` present):
562
- - Uses native Chromium implementation
563
- - Creates MCP bridge and syncs tools automatically
564
- - Registers callback to listen for native tool changes
565
- - MCP clients stay synchronized with native tool registry
239
+ ### Tool Descriptor
566
240
 
567
- 2. **No native API detected**:
568
- - Installs full polyfill implementation
569
- - Provides identical API surface
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 |
570
249
 
571
- **Zero configuration required** - the package automatically adapts to your environment!
250
+ ### Tool Response Format
572
251
 
573
- ### Native API Features
252
+ Tools return a `ToolResponse` object:
574
253
 
575
- When the native Chromium API is available, you get:
254
+ ```typescript
255
+ // Success
256
+ {
257
+ content: [{ type: 'text', text: 'Result here' }]
258
+ }
576
259
 
577
- - ✅ **Automatic tool synchronization** - Tools registered in native API are synced to MCP bridge via `registerToolsChangedCallback()`
578
- - ✅ **Iframe tool collection** - Native API automatically collects tools from embedded iframes (no manual transport setup needed)
579
- - **MCP compatibility** - Your MCP clients (extensions, apps) continue to work seamlessly
580
- - ✅ **Tool change notifications** - MCP servers receive `tools/list_changed` notifications automatically
581
- - ✅ **Consistent API** - Same code works with both native and polyfill implementations
260
+ // Error
261
+ {
262
+ content: [{ type: 'text', text: 'Something went wrong' }],
263
+ isError: true
264
+ }
265
+ ```
582
266
 
583
- ### How Tool Synchronization Works
267
+ ## Configuration
584
268
 
585
- The polyfill automatically registers a callback with the native API:
269
+ ### `WebModelContextInitOptions`
586
270
 
587
271
  ```typescript
588
- // Happens automatically when native API is detected
589
- navigator.modelContextTesting.registerToolsChangedCallback(() => {
590
- // Syncs native tools → MCP bridge
591
- // MCP clients receive tools/list_changed notification
592
- });
272
+ interface WebModelContextInitOptions {
273
+ transport?: TransportConfiguration;
274
+ autoInitialize?: boolean;
275
+ nativeModelContextBehavior?: 'preserve' | 'patch';
276
+ installTestingShim?: boolean | 'always' | 'if-missing';
277
+ }
593
278
  ```
594
279
 
595
- This callback fires when:
596
- - `navigator.modelContext.registerTool()` is called
597
- - `navigator.modelContext.unregisterTool()` is called
598
- - `navigator.modelContext.provideContext()` is called
599
- - `navigator.modelContext.clearContext()` is called
600
- - Tools are added from embedded iframes (native feature)
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 |
601
286
 
602
- ### Enabling Native API in Chromium
287
+ ### Transport Configuration
603
288
 
604
- ```bash
605
- # Method 1: Launch with flag
606
- chromium --enable-experimental-web-platform-features
289
+ The transport is auto-selected based on context:
607
290
 
608
- # Method 2: Enable in chrome://flags
609
- # Search for: "Experimental Web Platform Features"
610
- # Set to: Enabled
611
- # Restart browser
291
+ ```typescript
292
+ interface TransportConfiguration {
293
+ tabServer?: Partial<TabServerTransportOptions> | false;
294
+ iframeServer?: Partial<IframeChildTransportOptions> | false;
295
+ }
612
296
  ```
613
297
 
614
- ### Example: Using Native API
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
615
301
 
616
302
  ```typescript
617
- import '@mcp-b/global';
618
-
619
- // If native API is present, this delegates to navigator.modelContext:
620
- window.navigator.modelContext.registerTool({
621
- name: 'myTool',
622
- description: 'My tool',
623
- inputSchema: { type: 'object', properties: {} },
624
- async execute() {
625
- return { content: [{ type: 'text', text: 'Hello!' }] };
626
- }
303
+ // Restrict to specific origins
304
+ initializeWebModelContext({
305
+ transport: {
306
+ tabServer: { allowedOrigins: ['https://myapp.com'] },
307
+ },
627
308
  });
628
309
 
629
- // Behind the scenes:
630
- // 1. Tool registered in native Chromium registry
631
- // 2. Callback fires (registerToolsChangedCallback)
632
- // 3. Tool synced to MCP bridge
633
- // 4. MCP clients notified (tools/list_changed)
310
+ // Disable tab transport (iframe only)
311
+ initializeWebModelContext({
312
+ transport: {
313
+ tabServer: false,
314
+ },
315
+ });
634
316
  ```
635
317
 
636
- ### Iframe Tool Collection (Native Only)
318
+ ### Auto-Initialization
637
319
 
638
- When the native API is active, tools from embedded iframes are **automatically collected**:
320
+ The package auto-initializes on import in browser environments. To customize before initialization:
639
321
 
640
322
  ```html
641
- <!-- parent.html -->
642
- <script type="module">
643
- import '@mcp-b/global';
644
-
645
- // Native API will collect tools from this page AND all iframes
646
- navigator.modelContext.registerTool({
647
- name: 'parent-tool',
648
- description: 'Tool from parent page',
649
- inputSchema: { type: 'object', properties: {} },
650
- async execute() {
651
- return { content: [{ type: 'text', text: 'Parent tool' }] };
652
- }
653
- });
323
+ <script>
324
+ window.__webModelContextOptions = {
325
+ autoInitialize: true,
326
+ transport: {
327
+ tabServer: { allowedOrigins: ['https://myapp.com'] },
328
+ },
329
+ };
654
330
  </script>
655
-
656
- <iframe src="child.html"></iframe>
331
+ <script src="https://unpkg.com/@mcp-b/global@latest/dist/index.iife.js"></script>
657
332
  ```
658
333
 
659
- ```html
660
- <!-- child.html -->
661
- <script type="module">
662
- import '@mcp-b/global';
334
+ To prevent auto-initialization:
663
335
 
664
- // This tool is automatically visible in parent's registry (native feature)
665
- navigator.modelContext.registerTool({
666
- name: 'child-tool',
667
- description: 'Tool from iframe',
668
- inputSchema: { type: 'object', properties: {} },
669
- async execute() {
670
- return { content: [{ type: 'text', text: 'Child tool' }] };
671
- }
672
- });
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();
673
344
  </script>
674
345
  ```
675
346
 
676
- With native API, `navigator.modelContextTesting.listTools()` in the parent will show **both** tools! The MCP bridge stays in sync automatically.
347
+ ## Testing
677
348
 
678
- ### Detection in Console
349
+ `navigator.modelContextTesting` provides a testing shim that stays in sync with registered tools:
679
350
 
680
- When you initialize the package, check the console logs:
351
+ ```typescript
352
+ // List registered tools
353
+ const tools = navigator.modelContextTesting?.listTools();
354
+ // [{ name: 'search-products', description: '...', inputSchema: '...' }]
681
355
 
682
- ```
683
- [Web Model Context] Native Chromium API detected
684
- Using native implementation with MCP bridge synchronization
685
- Native API will automatically collect tools from embedded iframes
686
- ✅ [Web Model Context] MCP bridge synced with native API
687
- 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
+ );
688
361
  ```
689
362
 
690
- Or if polyfill is used:
363
+ ## Feature Detection
691
364
 
365
+ ```javascript
366
+ if ('modelContext' in navigator) {
367
+ navigator.modelContext.provideContext({ tools: [...] });
368
+ }
692
369
  ```
693
- [Web Model Context] Native API not detected, installing polyfill
694
- ✅ [Web Model Context] window.navigator.modelContext initialized successfully
695
- [Model Context Testing] Installing polyfill
696
- ✅ [Model Context Testing] Polyfill installed at window.navigator.modelContextTesting
697
- ```
698
-
699
- ## 📖 API Reference
700
-
701
- ### Two-Bucket Tool Management System
702
-
703
- This package uses a **two-bucket system** for tool management to support both app-level and component-level tools:
704
-
705
- - **Bucket A (Base Tools)**: Registered via `provideContext()` - represents your app's core functionality
706
- - **Bucket B (Dynamic Tools)**: Registered via `registerTool()` - component-scoped tools that persist across `provideContext()` calls
707
-
708
- **Key behaviors:**
709
- - ✅ `provideContext()` only clears Bucket A, leaving Bucket B intact
710
- - ✅ `registerTool()` adds to Bucket B and persists across `provideContext()` calls
711
- - ✅ Tool name collisions between buckets throw an error
712
- - ✅ Cannot `unregister()` a tool that was registered via `provideContext()`
713
-
714
- **Use case:** React components can use `registerTool()` in `useEffect()` to manage tool lifecycle independently of the app's base tools.
715
-
716
- ### `window.navigator.modelContext.provideContext(context)`
717
370
 
718
- 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.
371
+ ## Examples
719
372
 
720
- **Parameters:**
721
- - `context.tools` - Array of tool descriptors
373
+ ### E-commerce: Product Search and Cart
722
374
 
723
- **Example:**
375
+ ```typescript
376
+ import '@mcp-b/global';
724
377
 
725
- ```javascript
726
- window.navigator.modelContext.provideContext({
378
+ navigator.modelContext.provideContext({
727
379
  tools: [
728
380
  {
729
- name: "add-todo",
730
- description: "Add a new todo item to the list",
381
+ name: 'search-products',
382
+ description: 'Search products by keyword, category, or price range',
731
383
  inputSchema: {
732
- type: "object",
384
+ type: 'object',
733
385
  properties: {
734
- text: {
735
- type: "string",
736
- description: "The todo item text"
737
- },
738
- priority: {
739
- type: "string",
740
- enum: ["low", "medium", "high"],
741
- description: "Priority level"
742
- }
386
+ query: { type: 'string', description: 'Search terms' },
387
+ category: { type: 'string', description: 'Product category' },
388
+ maxPrice: { type: 'number', description: 'Maximum price filter' },
743
389
  },
744
- required: ["text"]
390
+ required: ['query'],
745
391
  },
746
- async execute({ text, priority = "medium" }) {
747
- // Add todo to your app
748
- const todo = addTodoItem(text, priority);
749
-
750
- return {
751
- content: [{
752
- type: "text",
753
- text: `Added todo: "${text}" with ${priority} priority`
754
- }]
755
- };
756
- }
757
- }
758
- ]
759
- });
760
- ```
761
-
762
- ### `window.navigator.modelContext.registerTool(tool)`
763
-
764
- Register a single tool dynamically (Bucket B). Tools registered this way:
765
- - ✅ Persist across `provideContext()` calls
766
- - ✅ Perfect for component lifecycle management
767
- - ✅ Can be unregistered via the returned `unregister()` function
768
- - ❌ Cannot have the same name as a tool in Bucket A (provideContext)
769
-
770
- **Parameters:**
771
- - `tool` - A single tool descriptor
772
-
773
- **Returns:**
774
- - Object with `unregister()` function to remove the tool
775
-
776
- **Example:**
777
-
778
- ```javascript
779
- // Register a tool dynamically (Bucket B)
780
- const registration = window.navigator.modelContext.registerTool({
781
- name: "get-timestamp",
782
- description: "Get the current timestamp",
783
- inputSchema: {
784
- type: "object",
785
- properties: {}
786
- },
787
- async execute() {
788
- return {
789
- content: [{
790
- type: "text",
791
- text: new Date().toISOString()
792
- }]
793
- };
794
- }
795
- });
796
-
797
- // Later, unregister the tool
798
- registration.unregister();
799
-
800
- // Note: You can call provideContext() and this tool will still be registered!
801
- window.navigator.modelContext.provideContext({
802
- tools: [/* other tools */]
803
- });
804
- // "get-timestamp" is still available because it's in Bucket B
805
- ```
806
-
807
- ### Tool Descriptor
808
-
809
- Each tool must have:
810
-
811
- | Property | Type | Description |
812
- |----------|------|-------------|
813
- | `name` | `string` | Unique identifier for the tool |
814
- | `description` | `string` | Natural language description of what the tool does |
815
- | `inputSchema` | `object` | JSON Schema defining input parameters |
816
- | `outputSchema` | `object` | Optional JSON Schema defining structured output |
817
- | `annotations` | `object` | Optional hints about tool behavior |
818
- | `execute` | `function` | Async function that implements the tool logic |
819
-
820
- ### Output Schemas (Structured Output)
821
-
822
- **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.
823
-
824
- **Benefits of output schemas:**
825
- - **Type-safe responses** - AI generates structured JSON matching your schema
826
- - **Better AI reasoning** - AI understands the expected output format
827
- - **Client validation** - Responses are validated against the schema
828
- - **IDE support** - TypeScript types inferred from schemas
829
-
830
- #### Basic Output Schema Example
831
-
832
- ```javascript
833
- window.navigator.modelContext.provideContext({
834
- 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
+ },
835
397
  {
836
- name: "get-user-profile",
837
- description: "Fetch a user's profile information",
398
+ name: 'add-to-cart',
399
+ description: 'Add a product to the shopping cart',
838
400
  inputSchema: {
839
- type: "object",
401
+ type: 'object',
840
402
  properties: {
841
- userId: { type: "string", description: "The user ID" }
403
+ productId: { type: 'string' },
404
+ quantity: { type: 'integer' },
842
405
  },
843
- required: ["userId"]
406
+ required: ['productId'],
844
407
  },
845
- // Define the structured output format
846
- outputSchema: {
847
- type: "object",
848
- properties: {
849
- id: { type: "string", description: "User ID" },
850
- name: { type: "string", description: "Display name" },
851
- email: { type: "string", description: "Email address" },
852
- createdAt: { type: "string", description: "ISO date string" }
853
- },
854
- 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` }] };
855
414
  },
856
- async execute({ userId }) {
857
- const user = await fetchUser(userId);
858
- return {
859
- content: [{ type: "text", text: `Found user: ${user.name}` }],
860
- // Structured content matching the outputSchema
861
- structuredContent: {
862
- id: user.id,
863
- name: user.name,
864
- email: user.email,
865
- createdAt: user.createdAt.toISOString()
866
- }
867
- };
868
- }
869
- }
870
- ]
415
+ },
416
+ ],
871
417
  });
872
418
  ```
873
419
 
874
- #### Using Zod for Type-Safe Schemas
875
-
876
- 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
877
421
 
878
422
  ```typescript
879
- import { z } from 'zod';
423
+ import '@mcp-b/global';
880
424
 
881
- window.navigator.modelContext.provideContext({
425
+ // Start with base tools
426
+ navigator.modelContext.provideContext({
882
427
  tools: [
883
428
  {
884
- name: "search-products",
885
- description: "Search the product catalog",
886
- inputSchema: {
887
- query: z.string().describe("Search query"),
888
- limit: z.number().min(1).max(100).default(10).describe("Max results"),
889
- category: z.enum(["electronics", "clothing", "books"]).optional()
890
- },
891
- // Zod schema for output - provides TypeScript types
892
- outputSchema: {
893
- products: z.array(z.object({
894
- id: z.string(),
895
- name: z.string(),
896
- price: z.number(),
897
- inStock: z.boolean()
898
- })),
899
- total: z.number().describe("Total matching products"),
900
- 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) }] };
901
434
  },
902
- async execute({ query, limit, category }) {
903
- const results = await searchProducts({ query, limit, category });
904
- return {
905
- content: [{ type: "text", text: `Found ${results.total} products` }],
906
- structuredContent: {
907
- products: results.items,
908
- total: results.total,
909
- hasMore: results.total > limit
910
- }
911
- };
912
- }
913
- }
914
- ]
435
+ },
436
+ ],
915
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
+ }
916
460
  ```
917
461
 
918
- #### Complex Output Schema Example
462
+ ### Form Interaction
919
463
 
920
- For tools that return rich data structures:
464
+ ```typescript
465
+ import '@mcp-b/global';
921
466
 
922
- ```javascript
923
- window.navigator.modelContext.provideContext({
467
+ navigator.modelContext.provideContext({
924
468
  tools: [
925
469
  {
926
- name: "analyze-code",
927
- description: "Analyze code for issues and suggestions",
470
+ name: 'fill-contact-form',
471
+ description: 'Fill the contact form with provided details',
928
472
  inputSchema: {
929
- type: "object",
473
+ type: 'object',
930
474
  properties: {
931
- code: { type: "string", description: "Source code to analyze" },
932
- language: { type: "string", enum: ["javascript", "typescript", "python"] }
475
+ name: { type: 'string' },
476
+ email: { type: 'string' },
477
+ message: { type: 'string' },
933
478
  },
934
- required: ["code", "language"]
479
+ required: ['name', 'email', 'message'],
935
480
  },
936
- outputSchema: {
937
- type: "object",
938
- properties: {
939
- summary: {
940
- type: "object",
941
- properties: {
942
- linesOfCode: { type: "number" },
943
- complexity: { type: "string", enum: ["low", "medium", "high"] }
944
- }
945
- },
946
- issues: {
947
- type: "array",
948
- items: {
949
- type: "object",
950
- properties: {
951
- severity: { type: "string", enum: ["error", "warning", "info"] },
952
- line: { type: "number" },
953
- message: { type: "string" },
954
- suggestion: { type: "string" }
955
- },
956
- required: ["severity", "line", "message"]
957
- }
958
- },
959
- score: {
960
- type: "number",
961
- minimum: 0,
962
- maximum: 100,
963
- description: "Code quality score"
964
- }
965
- },
966
- 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' }] };
967
486
  },
968
- async execute({ code, language }) {
969
- const analysis = await analyzeCode(code, language);
970
- return {
971
- content: [{ type: "text", text: `Quality score: ${analysis.score}/100` }],
972
- structuredContent: analysis
973
- };
974
- }
975
- }
976
- ]
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
+ ],
977
498
  });
978
499
  ```
979
500
 
980
- ### Tool Response Format
981
-
982
- Tools must return an object with:
983
-
984
- ```typescript
985
- {
986
- content: [
987
- {
988
- type: "text", // or "image", "resource"
989
- text: "Result..." // the response content
990
- }
991
- ],
992
- isError?: boolean // optional error flag
993
- }
994
- ```
995
-
996
- ## 🎯 Complete Examples
997
-
998
- ### Todo List Application
999
-
1000
- ```javascript
1001
- let todos = [];
1002
-
1003
- window.navigator.modelContext.provideContext({
1004
- tools: [
1005
- {
1006
- name: "add-todo",
1007
- description: "Add a new todo item",
1008
- inputSchema: {
1009
- type: "object",
1010
- properties: {
1011
- text: { type: "string", description: "Todo text" }
1012
- },
1013
- required: ["text"]
1014
- },
1015
- async execute({ text }) {
1016
- const todo = { id: Date.now(), text, done: false };
1017
- todos.push(todo);
1018
- updateUI();
1019
- return {
1020
- content: [{ type: "text", text: `Added: "${text}"` }]
1021
- };
1022
- }
1023
- },
1024
- {
1025
- name: "list-todos",
1026
- description: "Get all todo items",
1027
- inputSchema: { type: "object", properties: {} },
1028
- async execute() {
1029
- const list = todos.map(t =>
1030
- `${t.done ? '✓' : '○'} ${t.text}`
1031
- ).join('\n');
1032
- return {
1033
- content: [{ type: "text", text: list || "No todos" }]
1034
- };
1035
- }
1036
- },
1037
- {
1038
- name: "complete-todo",
1039
- description: "Mark a todo as complete",
1040
- inputSchema: {
1041
- type: "object",
1042
- properties: {
1043
- id: { type: "number", description: "Todo ID" }
1044
- },
1045
- required: ["id"]
1046
- },
1047
- async execute({ id }) {
1048
- const todo = todos.find(t => t.id === id);
1049
- if (!todo) {
1050
- return {
1051
- content: [{ type: "text", text: "Todo not found" }],
1052
- isError: true
1053
- };
1054
- }
1055
- todo.done = true;
1056
- updateUI();
1057
- return {
1058
- content: [{ type: "text", text: `Completed: "${todo.text}"` }]
1059
- };
1060
- }
1061
- }
1062
- ]
1063
- });
1064
-
1065
- function updateUI() {
1066
- // Update your UI
1067
- document.getElementById('todo-list').innerHTML =
1068
- todos.map(t => `<li>${t.done ? '✓' : ''} ${t.text}</li>`).join('');
1069
- }
1070
- ```
1071
-
1072
- ### E-commerce Product Search
1073
-
1074
- ```javascript
1075
- window.navigator.modelContext.provideContext({
1076
- tools: [
1077
- {
1078
- name: "search-products",
1079
- description: "Search for products in the catalog",
1080
- inputSchema: {
1081
- type: "object",
1082
- properties: {
1083
- query: {
1084
- type: "string",
1085
- description: "Search query"
1086
- },
1087
- category: {
1088
- type: "string",
1089
- description: "Filter by category",
1090
- enum: ["electronics", "clothing", "books", "all"]
1091
- },
1092
- maxPrice: {
1093
- type: "number",
1094
- description: "Maximum price filter"
1095
- }
1096
- },
1097
- required: ["query"]
1098
- },
1099
- async execute({ query, category = "all", maxPrice }) {
1100
- const results = await searchProducts({
1101
- query,
1102
- category: category !== "all" ? category : undefined,
1103
- maxPrice
1104
- });
1105
-
1106
- const summary = results.map(p =>
1107
- `${p.name} - $${p.price} (${p.category})`
1108
- ).join('\n');
1109
-
1110
- return {
1111
- content: [{
1112
- type: "text",
1113
- text: `Found ${results.length} products:\n${summary}`
1114
- }]
1115
- };
1116
- }
1117
- },
1118
- {
1119
- name: "add-to-cart",
1120
- description: "Add a product to the shopping cart",
1121
- inputSchema: {
1122
- type: "object",
1123
- properties: {
1124
- productId: { type: "string" },
1125
- quantity: { type: "number", default: 1 }
1126
- },
1127
- required: ["productId"]
1128
- },
1129
- async execute({ productId, quantity = 1 }) {
1130
- await addToCart(productId, quantity);
1131
- return {
1132
- content: [{
1133
- type: "text",
1134
- text: `Added ${quantity}x product ${productId} to cart`
1135
- }]
1136
- };
1137
- }
1138
- }
1139
- ]
1140
- });
1141
- ```
1142
-
1143
- ## 🔧 Dynamic Tool Registration (Component Lifecycle)
1144
-
1145
- ### React Component Example
1146
-
1147
- Perfect for managing tools tied to component lifecycle:
1148
-
1149
- ```javascript
1150
- import { useEffect } from 'react';
1151
-
1152
- function MyComponent() {
1153
- useEffect(() => {
1154
- // Register component-specific tool when component mounts (Bucket B)
1155
- const registration = window.navigator.modelContext.registerTool({
1156
- name: "component-action",
1157
- description: "Action specific to this component",
1158
- inputSchema: { type: "object", properties: {} },
1159
- async execute() {
1160
- // Access component state/methods here
1161
- return {
1162
- content: [{ type: "text", text: "Component action executed!" }]
1163
- };
1164
- }
1165
- });
1166
-
1167
- // Cleanup: unregister when component unmounts
1168
- return () => {
1169
- registration.unregister();
1170
- };
1171
- }, []);
1172
-
1173
- return <div>My Component</div>;
1174
- }
1175
- ```
1176
-
1177
- ### Persistence Across provideContext() Calls
1178
-
1179
- ```javascript
1180
- // Step 1: Register base tools (Bucket A)
1181
- window.navigator.modelContext.provideContext({
1182
- tools: [
1183
- { name: "base-tool-1", description: "Base tool", inputSchema: {}, async execute() {} }
1184
- ]
1185
- });
1186
- // Tools: ["base-tool-1"]
1187
-
1188
- // Step 2: Register dynamic tool (Bucket B)
1189
- const reg = window.navigator.modelContext.registerTool({
1190
- name: "dynamic-tool",
1191
- description: "Dynamic tool",
1192
- inputSchema: { type: "object", properties: {} },
1193
- async execute() {
1194
- return { content: [{ type: "text", text: "Dynamic!" }] };
1195
- }
1196
- });
1197
- // Tools: ["base-tool-1", "dynamic-tool"]
1198
-
1199
- // Step 3: Update base tools via provideContext
1200
- window.navigator.modelContext.provideContext({
1201
- tools: [
1202
- { name: "base-tool-2", description: "New base tool", inputSchema: {}, async execute() {} }
1203
- ]
1204
- });
1205
- // Tools: ["base-tool-2", "dynamic-tool"]
1206
- // ✅ "dynamic-tool" persists! Only "base-tool-1" was cleared
1207
-
1208
- // Step 4: Clean up dynamic tool
1209
- reg.unregister();
1210
- // Tools: ["base-tool-2"]
1211
- ```
1212
-
1213
- ### Name Collision Protection
1214
-
1215
- ```javascript
1216
- // Register a base tool
1217
- window.navigator.modelContext.provideContext({
1218
- tools: [
1219
- { name: "my-tool", description: "Base", inputSchema: {}, async execute() {} }
1220
- ]
1221
- });
1222
-
1223
- // This will throw an error!
1224
- try {
1225
- window.navigator.modelContext.registerTool({
1226
- name: "my-tool", // ❌ Name collision with Bucket A
1227
- description: "Dynamic",
1228
- inputSchema: {},
1229
- async execute() {}
1230
- });
1231
- } catch (error) {
1232
- console.error(error.message);
1233
- // Error: Tool name collision: "my-tool" is already registered via provideContext()
1234
- }
1235
-
1236
- // Similarly, can't unregister a base tool
1237
- const baseToolList = window.navigator.modelContext.provideContext({
1238
- tools: [{ name: "base", description: "Base", inputSchema: {}, async execute() {} }]
1239
- });
1240
-
1241
- // This will also throw an error!
1242
- try {
1243
- // Assuming we got a reference somehow
1244
- // registration.unregister(); would fail for a base tool
1245
- } catch (error) {
1246
- // Error: Cannot unregister tool "base": This tool was registered via provideContext()
1247
- }
1248
- ```
1249
-
1250
- ## 🔧 Event-Based Tool Calls (Advanced)
1251
-
1252
- For manifest-based or advanced scenarios, you can handle tool calls as events:
1253
-
1254
- ```javascript
1255
- window.navigator.modelContext.addEventListener('toolcall', async (event) => {
1256
- console.log(`Tool called: ${event.name}`, event.arguments);
1257
-
1258
- if (event.name === "custom-tool") {
1259
- // Prevent default execution
1260
- event.preventDefault();
1261
-
1262
- // Provide custom response
1263
- event.respondWith({
1264
- content: [{
1265
- type: "text",
1266
- text: "Custom response from event handler"
1267
- }]
1268
- });
1269
- }
1270
-
1271
- // If not prevented, the tool's execute function will run normally
1272
- });
1273
- ```
1274
-
1275
- ### Hybrid Approach
1276
-
1277
- The API supports both approaches simultaneously:
1278
-
1279
- 1. **Event dispatched first** - `toolcall` event is fired
1280
- 2. **Event can override** - Call `event.preventDefault()` and `event.respondWith()`
1281
- 3. **Default execution** - If not prevented, the tool's `execute()` function runs
1282
-
1283
- This allows flexibility for different use cases.
1284
-
1285
- ## 🏗️ Architecture
1286
-
1287
- ```
1288
- ┌─────────────────┐
1289
- │ AI Agent │
1290
- │ (MCP Client) │
1291
- └────────┬────────┘
1292
- │ MCP Protocol
1293
- │ (JSON-RPC)
1294
- ┌────────▼────────┐
1295
- │ MCP Server │
1296
- │ (Internal) │
1297
- └────────┬────────┘
1298
-
1299
- ┌────────▼───────────────────┐
1300
- │ navigator.modelContext │ ◄── Your app registers tools here
1301
- │ (This pkg) │
1302
- └────────────────────────────┘
1303
- ```
1304
-
1305
- This package:
1306
- 1. Exposes `window.navigator.modelContext` API (W3C Web Model Context standard)
1307
- 2. Internally creates an MCP Server
1308
- 3. Bridges tool calls between the two protocols
1309
- 4. Uses TabServerTransport for browser communication
1310
-
1311
- ## 🔍 Feature Detection
1312
-
1313
- Check if the API is available:
1314
-
1315
- ```javascript
1316
- if ("modelContext" in navigator) {
1317
- // API is available
1318
- navigator.modelContext.provideContext({ tools: [...] });
1319
- } else {
1320
- console.warn("Web Model Context API not available");
1321
- }
1322
- ```
1323
-
1324
- ## 🐛 Debugging
1325
-
1326
- ### Enable Debug Logging
1327
-
1328
- 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:
1329
-
1330
- ```javascript
1331
- // Enable all debug logging
1332
- localStorage.setItem('WEBMCP_DEBUG', '*');
1333
-
1334
- // Enable specific namespaces
1335
- localStorage.setItem('WEBMCP_DEBUG', 'WebModelContext');
1336
- localStorage.setItem('WEBMCP_DEBUG', 'NativeAdapter,MCPBridge');
1337
-
1338
- // Refresh the page to apply changes
1339
- location.reload();
1340
- ```
1341
-
1342
- To disable debug logging:
1343
-
1344
- ```javascript
1345
- localStorage.removeItem('WEBMCP_DEBUG');
1346
- location.reload();
1347
- ```
1348
-
1349
- **Available Namespaces:**
1350
- - `WebModelContext` - Main polyfill implementation
1351
- - `NativeAdapter` - Native Chromium API adapter
1352
- - `MCPBridge` - MCP server and transport setup
1353
- - `ModelContextTesting` - Testing API operations
1354
-
1355
- **Log Levels:**
1356
- - **Error** (always shown): Critical failures and exceptions
1357
- - **Warn** (always shown): Compatibility warnings and potential issues
1358
- - **Info** (debug mode only): Initialization and setup progress
1359
- - **Debug** (debug mode only): Detailed operation traces
1360
-
1361
- ### Access Internal Bridge
1362
-
1363
- In development mode, access the internal bridge:
1364
-
1365
- ```javascript
1366
- if (window.__mcpBridge) {
1367
- console.log("MCP Server:", window.__mcpBridge.server);
1368
- console.log("Registered tools:", window.__mcpBridge.tools);
1369
- }
1370
- ```
1371
-
1372
- ## 🧪 Testing API (`navigator.modelContextTesting`)
1373
-
1374
- This package provides a **Model Context Testing API** at `window.navigator.modelContextTesting` for debugging and testing your tools during development.
1375
-
1376
- > [!WARNING]
1377
- > `navigator.modelContextTesting` is deprecated and kept for compatibility.
1378
- > For in-page consumers, use `navigator.modelContext.callTool({ name, arguments })` and
1379
- > `navigator.modelContext.addEventListener("toolschanged", ...)`.
1380
-
1381
- ### Unified Consumer API (Recommended)
1382
-
1383
- ```javascript
1384
- // Execute tools with object args (no JSON stringification)
1385
- const result = await navigator.modelContext.callTool({
1386
- name: "greet",
1387
- arguments: { name: "Alice" }
1388
- });
1389
-
1390
- // React to tool list changes
1391
- navigator.modelContext.addEventListener("toolschanged", () => {
1392
- console.log("Tools changed:", navigator.modelContext.listTools());
1393
- });
1394
- ```
1395
-
1396
- ### Testing Helpers Module
1397
-
1398
- Use `@mcp-b/global/testing` to avoid depending on global-only testing extensions directly:
1399
-
1400
- ```javascript
1401
- import { createTestHelper } from "@mcp-b/global/testing";
1402
-
1403
- const testing = createTestHelper();
1404
- await testing.executeTool("greet", { name: "Alice" });
1405
- ```
1406
-
1407
- ### Native Support in Chromium
1408
-
1409
- **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.
1410
-
1411
- #### How to Enable Native API in Chromium:
1412
-
1413
- **Option 1: Chrome Flags**
1414
- 1. Navigate to `chrome://flags`
1415
- 2. Search for "Experimental Web Platform Features"
1416
- 3. Enable the flag
1417
- 4. Restart your browser
1418
-
1419
- **Option 2: Command Line**
1420
- ```bash
1421
- # Launch Chrome/Edge with experimental features
1422
- chrome --enable-experimental-web-platform-features
1423
- ```
1424
-
1425
- **Detection**: When the native API is detected, you'll see this console message:
1426
- ```
1427
- ✅ [Model Context Testing] Native implementation detected (Chromium experimental feature)
1428
- Using native window.navigator.modelContextTesting from browser
1429
- ```
1430
-
1431
- ### Polyfill Fallback
1432
-
1433
- If the native API is not available, this package automatically provides a polyfill implementation with the same interface:
1434
-
1435
- ```
1436
- [Model Context Testing] Native implementation not found, installing polyfill
1437
- 💡 To use the native implementation in Chromium:
1438
- - Navigate to chrome://flags
1439
- - Enable "Experimental Web Platform Features"
1440
- - Or launch with: --enable-experimental-web-platform-features
1441
- ✅ [Model Context Testing] Polyfill installed at window.navigator.modelContextTesting
1442
- ```
1443
-
1444
- ### API Reference
1445
-
1446
- #### `getToolCalls(): Array<ToolCall>`
1447
-
1448
- Get a history of all tool calls made during the session.
1449
-
1450
- ```javascript
1451
- // Register and call some tools
1452
- window.navigator.modelContext.provideContext({
1453
- tools: [{
1454
- name: "greet",
1455
- description: "Greet a user",
1456
- inputSchema: {
1457
- type: "object",
1458
- properties: { name: { type: "string" } },
1459
- required: ["name"]
1460
- },
1461
- async execute({ name }) {
1462
- return { content: [{ type: "text", text: `Hello, ${name}!` }] };
1463
- }
1464
- }]
1465
- });
1466
-
1467
- // Simulate a tool call
1468
- // (In practice, this would come from an AI agent)
1469
-
1470
- // Later, inspect the tool call history
1471
- const calls = window.navigator.modelContextTesting.getToolCalls();
1472
- console.log(calls);
1473
- // [
1474
- // {
1475
- // toolName: "greet",
1476
- // arguments: { name: "Alice" },
1477
- // timestamp: 1699123456789
1478
- // }
1479
- // ]
1480
- ```
1481
-
1482
- #### `clearToolCalls(): void`
1483
-
1484
- Clear the tool call history.
1485
-
1486
- ```javascript
1487
- window.navigator.modelContextTesting.clearToolCalls();
1488
- console.log(window.navigator.modelContextTesting.getToolCalls()); // []
1489
- ```
1490
-
1491
- #### `setMockToolResponse(toolName: string, response: ToolResponse): void`
1492
-
1493
- 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.
1494
-
1495
- ```javascript
1496
- // Mock the "greet" tool to always return a specific response
1497
- window.navigator.modelContextTesting.setMockToolResponse("greet", {
1498
- content: [{
1499
- type: "text",
1500
- text: "Mocked greeting!"
1501
- }]
1502
- });
1503
-
1504
- // Now when the tool is called, it returns the mock response
1505
- // (The execute function is never called)
1506
- ```
1507
-
1508
- #### `clearMockToolResponse(toolName: string): void`
1509
-
1510
- Remove the mock response for a specific tool.
1511
-
1512
- ```javascript
1513
- window.navigator.modelContextTesting.clearMockToolResponse("greet");
1514
- // Tool will now use its actual execute function
1515
- ```
1516
-
1517
- #### `clearAllMockToolResponses(): void`
1518
-
1519
- Remove all mock tool responses.
1520
-
1521
- ```javascript
1522
- window.navigator.modelContextTesting.clearAllMockToolResponses();
1523
- ```
1524
-
1525
- #### `getRegisteredTools(): Array<ToolDescriptor>`
1526
-
1527
- Get the list of all currently registered tools (same as `modelContext.listTools()`).
1528
-
1529
- ```javascript
1530
- const tools = window.navigator.modelContextTesting.getRegisteredTools();
1531
- console.log(tools.map(t => t.name)); // ["greet", "add-todo", ...]
1532
- ```
1533
-
1534
- #### `reset(): void`
1535
-
1536
- Reset the entire testing state (clears tool call history and all mock responses).
1537
-
1538
- ```javascript
1539
- window.navigator.modelContextTesting.reset();
1540
- ```
1541
-
1542
- ### Testing Workflow Example
1543
-
1544
- Here's a complete example of using the testing API:
1545
-
1546
- ```javascript
1547
- // 1. Register your tools
1548
- window.navigator.modelContext.provideContext({
1549
- tools: [
1550
- {
1551
- name: "add-todo",
1552
- description: "Add a todo item",
1553
- inputSchema: {
1554
- type: "object",
1555
- properties: { text: { type: "string" } },
1556
- required: ["text"]
1557
- },
1558
- async execute({ text }) {
1559
- // This would normally add to your app state
1560
- return { content: [{ type: "text", text: `Added: ${text}` }] };
1561
- }
1562
- }
1563
- ]
1564
- });
1565
-
1566
- // 2. Set up mocks for testing
1567
- window.navigator.modelContextTesting.setMockToolResponse("add-todo", {
1568
- content: [{ type: "text", text: "Mock: Todo added successfully" }]
1569
- });
1570
-
1571
- // 3. Simulate tool calls (or let AI agent call them)
1572
- // The tool will return the mock response instead of executing
1573
-
1574
- // 4. Inspect tool call history
1575
- const calls = window.navigator.modelContextTesting.getToolCalls();
1576
- console.log(`${calls.length} tool calls made`);
1577
- calls.forEach(call => {
1578
- console.log(`- ${call.toolName}`, call.arguments);
1579
- });
1580
-
1581
- // 5. Clean up after testing
1582
- window.navigator.modelContextTesting.reset();
1583
- ```
1584
-
1585
- ### Integration Testing Example
1586
-
1587
- Perfect for automated testing with frameworks like Jest, Vitest, or Playwright:
1588
-
1589
- ```javascript
1590
- // test/model-context.test.js
1591
- import { test, expect } from 'vitest';
1592
-
1593
- test('todo tool creates correct response', async () => {
1594
- // Arrange
1595
- const mockResponse = {
1596
- content: [{ type: "text", text: "Test todo added" }]
1597
- };
1598
-
1599
- window.navigator.modelContextTesting.setMockToolResponse(
1600
- "add-todo",
1601
- mockResponse
1602
- );
1603
-
1604
- // Act
1605
- // Trigger your AI agent or directly call the tool via MCP
1606
- // ...
1607
-
1608
- // Assert
1609
- const calls = window.navigator.modelContextTesting.getToolCalls();
1610
- expect(calls).toHaveLength(1);
1611
- expect(calls[0].toolName).toBe("add-todo");
1612
- expect(calls[0].arguments).toEqual({ text: "Test item" });
1613
-
1614
- // Cleanup
1615
- window.navigator.modelContextTesting.reset();
1616
- });
1617
- ```
1618
-
1619
- ### Browser Compatibility
501
+ ## Browser Compatibility
1620
502
 
1621
503
  | Browser | Native Support | Polyfill |
1622
504
  |---------|---------------|----------|
1623
- | Chrome/Edge (with flag) | Yes | N/A |
1624
- | Chrome/Edge (default) | No | Yes |
1625
- | Firefox | No | Yes |
1626
- | Safari | No | Yes |
1627
- | Other browsers | ❌ No | ✅ Yes |
1628
-
1629
- 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 |
1630
509
 
1631
510
  ## Zod Version Compatibility
1632
511
 
1633
- This package supports **Zod 3.25+** and **Zod 4.x**. Simply use the standard import:
512
+ This package supports **Zod 3.25.76+** (3.x only). JSON Schema is also supported if you prefer not to use Zod.
1634
513
 
1635
- ```typescript
1636
- import { z } from 'zod';
514
+ ## Type Exports
1637
515
 
1638
- window.navigator.modelContext.provideContext({
1639
- tools: [{
1640
- name: "my-tool",
1641
- inputSchema: {
1642
- name: z.string().describe('User name'),
1643
- age: z.number().min(0)
1644
- },
1645
- async execute({ name, age }) {
1646
- return { content: [{ type: "text", text: `Hello, ${name}!` }] };
1647
- }
1648
- }]
1649
- });
1650
- ```
1651
-
1652
- ### JSON Schema Alternative
516
+ All types are re-exported for TypeScript consumers:
1653
517
 
1654
- JSON Schema is also supported if you prefer not to use Zod:
1655
-
1656
- ```javascript
1657
- window.navigator.modelContext.provideContext({
1658
- tools: [{
1659
- name: "my-tool",
1660
- inputSchema: {
1661
- type: "object",
1662
- properties: {
1663
- name: { type: "string" }
1664
- },
1665
- required: ["name"]
1666
- },
1667
- async execute({ name }) {
1668
- return { content: [{ type: "text", text: `Hello, ${name}!` }] };
1669
- }
1670
- }]
1671
- });
1672
- ```
1673
-
1674
- ## 📦 What's Included
1675
-
1676
- - **Web Model Context API** - Standard `window.navigator.modelContext` interface
1677
- - **Model Context Testing API** - `window.navigator.modelContextTesting` for debugging and testing (with native Chromium support detection)
1678
- - **Dynamic Tool Registration** - `registerTool()` with `unregister()` function
1679
- - **MCP Bridge** - Automatic bridging to Model Context Protocol
1680
- - **Tab Transport** - Communication layer for browser contexts
1681
- - **Event System** - Hybrid tool call handling
1682
- - **TypeScript Types** - Full type definitions included
1683
-
1684
- ## 🔒 Security Considerations
1685
-
1686
- ### Origin Restrictions
1687
-
1688
- By default, the MCP transport allows connections from any origin (`*`). For production, you should configure allowed origins:
1689
-
1690
- ```javascript
1691
- // Future configuration API
1692
- window.navigator.modelContext.configure({
1693
- allowedOrigins: [
1694
- 'https://your-app.com',
1695
- 'https://trusted-agent.com'
1696
- ]
1697
- });
1698
- ```
1699
-
1700
- ### Tool Validation
1701
-
1702
- Always validate inputs in your tool implementations:
1703
-
1704
- ```javascript
1705
- {
1706
- name: "delete-item",
1707
- description: "Delete an item",
1708
- inputSchema: {
1709
- type: "object",
1710
- properties: {
1711
- id: { type: "string", pattern: "^[a-zA-Z0-9]+$" }
1712
- },
1713
- required: ["id"]
1714
- },
1715
- async execute({ id }) {
1716
- // Additional validation
1717
- if (!isValidId(id)) {
1718
- return {
1719
- content: [{ type: "text", text: "Invalid ID" }],
1720
- isError: true
1721
- };
1722
- }
1723
-
1724
- // Proceed with deletion
1725
- await deleteItem(id);
1726
- return {
1727
- content: [{ type: "text", text: "Item deleted" }]
1728
- };
1729
- }
1730
- }
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';
1731
533
  ```
1732
534
 
1733
- ## Frequently Asked Questions
535
+ ## Tool Routing Contract
1734
536
 
1735
- ### How do AI agents connect to my website?
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.
1736
539
 
1737
- AI agents connect through browser extensions or the `@mcp-b/chrome-devtools-mcp` server, which bridges desktop AI clients to browser-based tools.
1738
-
1739
- ### Do I need a build step?
1740
-
1741
- No! Use the IIFE version with a single `<script>` tag. For bundler users, the ESM version is also available.
1742
-
1743
- ### Is this production-ready?
1744
-
1745
- Yes! The polyfill handles tool registration, lifecycle management, and automatically uses native Chromium implementation when available.
1746
-
1747
- ### What about browser support?
1748
-
1749
- Works in all modern browsers. Native API support is available in Chromium with experimental flags enabled.
1750
-
1751
- ## 🤝 Related Packages
540
+ ## Related Packages
1752
541
 
1753
542
  - [`@mcp-b/transports`](https://docs.mcp-b.ai/packages/transports) - MCP transport implementations
1754
543
  - [`@mcp-b/react-webmcp`](https://docs.mcp-b.ai/packages/react-webmcp) - React hooks for MCP
1755
544
  - [`@mcp-b/extension-tools`](https://docs.mcp-b.ai/packages/extension-tools) - Chrome Extension API tools
1756
545
  - [`@mcp-b/chrome-devtools-mcp`](https://docs.mcp-b.ai/packages/chrome-devtools-mcp) - Connect desktop AI agents to browser tools
1757
- - [`@modelcontextprotocol/sdk`](https://www.npmjs.com/package/@modelcontextprotocol/sdk) - Official MCP SDK
1758
546
 
1759
- ## 📚 Resources
547
+ ## Resources
1760
548
 
1761
549
  - [WebMCP Documentation](https://docs.mcp-b.ai)
1762
550
  - [Web Model Context API Explainer](https://github.com/nicolo-ribaudo/model-context-protocol-api)
1763
551
  - [Model Context Protocol Spec](https://modelcontextprotocol.io/)
1764
- - [MCP GitHub Repository](https://github.com/modelcontextprotocol)
1765
552
 
1766
- ## 📝 License
553
+ ## License
1767
554
 
1768
555
  MIT - see [LICENSE](../../LICENSE) for details
1769
-
1770
- ## 🙋 Support
1771
-
1772
- - [GitHub Issues](https://github.com/WebMCP-org/npm-packages/issues)
1773
- - [Documentation](https://docs.mcp-b.ai)
1774
- - [Discord Community](https://discord.gg/a9fBR6Bw)