@mcp-b/global 0.0.0-beta-20260109203913

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