@mcp-b/global 1.0.11 → 1.0.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +555 -326
- package/dist/index.d.ts +199 -16
- package/dist/index.d.ts.map +1 -0
- package/dist/index.iife.js +6 -0
- package/dist/index.js +431 -2
- package/dist/index.js.map +1 -1
- package/package.json +53 -49
- package/dist/.tsbuildinfo +0 -1
- package/dist/index.cjs +0 -3
- package/dist/index.cjs.map +0 -1
- package/dist/index.d.cts +0 -23
- package/dist/index.umd.js +0 -11
- package/dist/index.umd.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,429 +1,658 @@
|
|
|
1
1
|
# @mcp-b/global
|
|
2
2
|
|
|
3
|
+
> Web Model Context API polyfill - Implement `window.navigator.modelContext` for AI-powered web applications
|
|
4
|
+
|
|
3
5
|
[](https://www.npmjs.com/package/@mcp-b/global)
|
|
4
6
|
[](https://opensource.org/licenses/MIT)
|
|
5
7
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
The `@mcp-b/global` package offers the easiest way to integrate MCP-B (Model Context Protocol for Browsers) into any website. It requires no build tools or complex configuration—just add a script tag to expose AI tools that leverage your site's existing functionality.
|
|
9
|
-
|
|
10
|
-
## ✨ Features
|
|
11
|
-
|
|
12
|
-
- 🚀 **Zero-Config Setup**: Works instantly in any modern browser.
|
|
13
|
-
- 🏷️ **Script Tag Integration**: Ideal for CDN deployment via unpkg or similar services.
|
|
14
|
-
- 🔧 **Global API Exposure**: Automatically creates `window.mcp` upon loading.
|
|
15
|
-
- 📦 **Multi-Format Support**: Compatible with ESM, CommonJS, and UMD.
|
|
16
|
-
- 🎯 **TypeScript-Ready**: Includes comprehensive type definitions.
|
|
17
|
-
- 🌐 **Framework-Agnostic**: Seamlessly integrates with vanilla JS, React, Vue, Angular, or any other framework.
|
|
8
|
+
This package implements the [W3C Web Model Context API](https://github.com/webmachinelearning/webmcp) (`window.navigator.modelContext`) specification, bridging it to the Model Context Protocol (MCP) SDK. It allows web developers to expose JavaScript functions as "tools" that AI agents can discover and invoke.
|
|
18
9
|
|
|
19
10
|
## 🚀 Quick Start
|
|
20
11
|
|
|
21
|
-
###
|
|
12
|
+
### Via IIFE Script Tag (Easiest - No Build Required)
|
|
13
|
+
|
|
14
|
+
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.
|
|
22
15
|
|
|
23
|
-
Add
|
|
16
|
+
Add the script to your HTML `<head>`:
|
|
24
17
|
|
|
25
18
|
```html
|
|
26
19
|
<!DOCTYPE html>
|
|
27
|
-
<html
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
window.mcp.registerTool(
|
|
47
|
-
"getPageDetails",
|
|
48
|
-
{
|
|
49
|
-
title: "Retrieve Page Details",
|
|
50
|
-
description: "Fetches information about the current webpage",
|
|
20
|
+
<html>
|
|
21
|
+
<head>
|
|
22
|
+
<!-- IIFE version - bundles all dependencies, auto-initializes -->
|
|
23
|
+
<script src="https://unpkg.com/@mcp-b/global@latest/dist/index.iife.js"></script>
|
|
24
|
+
</head>
|
|
25
|
+
<body>
|
|
26
|
+
<h1>My AI-Powered App</h1>
|
|
27
|
+
|
|
28
|
+
<script>
|
|
29
|
+
// window.navigator.modelContext is already available!
|
|
30
|
+
// Register tools with AI agents
|
|
31
|
+
window.navigator.modelContext.provideContext({
|
|
32
|
+
tools: [
|
|
33
|
+
{
|
|
34
|
+
name: "get-page-title",
|
|
35
|
+
description: "Get the current page title",
|
|
36
|
+
inputSchema: {
|
|
37
|
+
type: "object",
|
|
38
|
+
properties: {}
|
|
51
39
|
},
|
|
52
|
-
async ()
|
|
40
|
+
async execute() {
|
|
53
41
|
return {
|
|
54
|
-
content: [
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
title: document.title,
|
|
59
|
-
url: window.location.href,
|
|
60
|
-
timestamp: new Date().toISOString(),
|
|
61
|
-
}),
|
|
62
|
-
},
|
|
63
|
-
],
|
|
42
|
+
content: [{
|
|
43
|
+
type: "text",
|
|
44
|
+
text: document.title
|
|
45
|
+
}]
|
|
64
46
|
};
|
|
65
47
|
}
|
|
66
|
-
|
|
48
|
+
}
|
|
49
|
+
]
|
|
50
|
+
});
|
|
51
|
+
</script>
|
|
52
|
+
</body>
|
|
53
|
+
</html>
|
|
54
|
+
```
|
|
67
55
|
|
|
68
|
-
|
|
69
|
-
|
|
56
|
+
**What you get:**
|
|
57
|
+
- ✅ **Self-contained** - All dependencies bundled (285KB minified)
|
|
58
|
+
- ✅ **Auto-initializes** - `window.navigator.modelContext` ready immediately
|
|
59
|
+
- ✅ **No build step** - Just drop it in your HTML
|
|
60
|
+
- ✅ **Works everywhere** - Compatible with all modern browsers
|
|
61
|
+
- ✅ **Global access** - Also exposes `window.WebMCP` for advanced usage
|
|
62
|
+
|
|
63
|
+
### Via ES Module Script Tag
|
|
70
64
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
65
|
+
If you prefer ES modules and have a build system, use the ESM version:
|
|
66
|
+
|
|
67
|
+
```html
|
|
68
|
+
<!DOCTYPE html>
|
|
69
|
+
<html>
|
|
70
|
+
<head>
|
|
71
|
+
<!-- ESM version - smaller but requires module support -->
|
|
72
|
+
<script type="module">
|
|
73
|
+
import '@mcp-b/global';
|
|
74
|
+
|
|
75
|
+
// window.navigator.modelContext is now available
|
|
76
|
+
window.navigator.modelContext.provideContext({
|
|
77
|
+
tools: [/* your tools */]
|
|
78
|
+
});
|
|
79
|
+
</script>
|
|
80
|
+
</head>
|
|
81
|
+
<body>
|
|
82
|
+
<h1>My AI-Powered App</h1>
|
|
83
|
+
</body>
|
|
75
84
|
</html>
|
|
76
85
|
```
|
|
77
86
|
|
|
78
|
-
|
|
87
|
+
**Note:** The ESM version is smaller (~16KB) but doesn't bundle dependencies - it expects them to be available via your module system or CDN.
|
|
88
|
+
|
|
89
|
+
### Via NPM
|
|
79
90
|
|
|
80
|
-
For
|
|
91
|
+
For applications using a bundler (Vite, Webpack, etc.):
|
|
81
92
|
|
|
82
93
|
```bash
|
|
83
|
-
npm install @mcp-b/global
|
|
94
|
+
npm install @mcp-b/global
|
|
84
95
|
```
|
|
85
96
|
|
|
86
|
-
```
|
|
87
|
-
import
|
|
88
|
-
import { z } from "zod";
|
|
97
|
+
```javascript
|
|
98
|
+
import '@mcp-b/global';
|
|
89
99
|
|
|
90
|
-
//
|
|
91
|
-
|
|
100
|
+
// window.navigator.modelContext is now available
|
|
101
|
+
window.navigator.modelContext.provideContext({
|
|
102
|
+
tools: [/* your tools */]
|
|
103
|
+
});
|
|
104
|
+
```
|
|
92
105
|
|
|
93
|
-
|
|
94
|
-
function initMCP() {
|
|
95
|
-
if (!window.mcp?.registerTool) {
|
|
96
|
-
return setTimeout(initMCP, 100);
|
|
97
|
-
}
|
|
106
|
+
## 📖 API Reference
|
|
98
107
|
|
|
99
|
-
|
|
100
|
-
"processMessage",
|
|
101
|
-
{
|
|
102
|
-
title: "Process User Message",
|
|
103
|
-
description: "Handles and responds to a message",
|
|
104
|
-
inputSchema: {
|
|
105
|
-
message: z.string().describe("The message to process"),
|
|
106
|
-
},
|
|
107
|
-
},
|
|
108
|
-
async ({ message }) => {
|
|
109
|
-
return {
|
|
110
|
-
content: [{ type: "text", text: `Processed: ${message}` }],
|
|
111
|
-
};
|
|
112
|
-
}
|
|
113
|
-
);
|
|
114
|
-
}
|
|
108
|
+
### Two-Bucket Tool Management System
|
|
115
109
|
|
|
116
|
-
|
|
117
|
-
```
|
|
110
|
+
This package uses a **two-bucket system** for tool management to support both app-level and component-level tools:
|
|
118
111
|
|
|
119
|
-
|
|
112
|
+
- **Bucket A (Base Tools)**: Registered via `provideContext()` - represents your app's core functionality
|
|
113
|
+
- **Bucket B (Dynamic Tools)**: Registered via `registerTool()` - component-scoped tools that persist across `provideContext()` calls
|
|
120
114
|
|
|
121
|
-
|
|
115
|
+
**Key behaviors:**
|
|
116
|
+
- ✅ `provideContext()` only clears Bucket A, leaving Bucket B intact
|
|
117
|
+
- ✅ `registerTool()` adds to Bucket B and persists across `provideContext()` calls
|
|
118
|
+
- ✅ Tool name collisions between buckets throw an error
|
|
119
|
+
- ✅ Cannot `unregister()` a tool that was registered via `provideContext()`
|
|
122
120
|
|
|
123
|
-
|
|
121
|
+
**Use case:** React components can use `registerTool()` in `useEffect()` to manage tool lifecycle independently of the app's base tools.
|
|
124
122
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
123
|
+
### `window.navigator.modelContext.provideContext(context)`
|
|
124
|
+
|
|
125
|
+
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.
|
|
126
|
+
|
|
127
|
+
**Parameters:**
|
|
128
|
+
- `context.tools` - Array of tool descriptors
|
|
129
|
+
|
|
130
|
+
**Example:**
|
|
131
|
+
|
|
132
|
+
```javascript
|
|
133
|
+
window.navigator.modelContext.provideContext({
|
|
134
|
+
tools: [
|
|
135
|
+
{
|
|
136
|
+
name: "add-todo",
|
|
137
|
+
description: "Add a new todo item to the list",
|
|
138
|
+
inputSchema: {
|
|
139
|
+
type: "object",
|
|
140
|
+
properties: {
|
|
141
|
+
text: {
|
|
142
|
+
type: "string",
|
|
143
|
+
description: "The todo item text"
|
|
144
|
+
},
|
|
145
|
+
priority: {
|
|
146
|
+
type: "string",
|
|
147
|
+
enum: ["low", "medium", "high"],
|
|
148
|
+
description: "Priority level"
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
required: ["text"]
|
|
152
|
+
},
|
|
153
|
+
async execute({ text, priority = "medium" }) {
|
|
154
|
+
// Add todo to your app
|
|
155
|
+
const todo = addTodoItem(text, priority);
|
|
156
|
+
|
|
157
|
+
return {
|
|
158
|
+
content: [{
|
|
159
|
+
type: "text",
|
|
160
|
+
text: `Added todo: "${text}" with ${priority} priority`
|
|
161
|
+
}]
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
]
|
|
166
|
+
});
|
|
129
167
|
```
|
|
130
168
|
|
|
131
|
-
###
|
|
169
|
+
### `window.navigator.modelContext.registerTool(tool)`
|
|
132
170
|
|
|
133
|
-
|
|
171
|
+
Register a single tool dynamically (Bucket B). Tools registered this way:
|
|
172
|
+
- ✅ Persist across `provideContext()` calls
|
|
173
|
+
- ✅ Perfect for component lifecycle management
|
|
174
|
+
- ✅ Can be unregistered via the returned `unregister()` function
|
|
175
|
+
- ❌ Cannot have the same name as a tool in Bucket A (provideContext)
|
|
134
176
|
|
|
135
|
-
|
|
177
|
+
**Parameters:**
|
|
178
|
+
- `tool` - A single tool descriptor
|
|
136
179
|
|
|
137
|
-
|
|
138
|
-
-
|
|
139
|
-
- **handler**: Async function that executes the tool logic and returns `{ content: [{ type: 'text', text: string }] }`.
|
|
180
|
+
**Returns:**
|
|
181
|
+
- Object with `unregister()` function to remove the tool
|
|
140
182
|
|
|
141
|
-
Example
|
|
183
|
+
**Example:**
|
|
142
184
|
|
|
143
|
-
```
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
"
|
|
148
|
-
{
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
inputSchema: { input: z.string() },
|
|
185
|
+
```javascript
|
|
186
|
+
// Register a tool dynamically (Bucket B)
|
|
187
|
+
const registration = window.navigator.modelContext.registerTool({
|
|
188
|
+
name: "get-timestamp",
|
|
189
|
+
description: "Get the current timestamp",
|
|
190
|
+
inputSchema: {
|
|
191
|
+
type: "object",
|
|
192
|
+
properties: {}
|
|
152
193
|
},
|
|
153
|
-
async (
|
|
154
|
-
return {
|
|
194
|
+
async execute() {
|
|
195
|
+
return {
|
|
196
|
+
content: [{
|
|
197
|
+
type: "text",
|
|
198
|
+
text: new Date().toISOString()
|
|
199
|
+
}]
|
|
200
|
+
};
|
|
155
201
|
}
|
|
156
|
-
);
|
|
157
|
-
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
// Later, unregister the tool
|
|
205
|
+
registration.unregister();
|
|
158
206
|
|
|
159
|
-
|
|
207
|
+
// Note: You can call provideContext() and this tool will still be registered!
|
|
208
|
+
window.navigator.modelContext.provideContext({
|
|
209
|
+
tools: [/* other tools */]
|
|
210
|
+
});
|
|
211
|
+
// "get-timestamp" is still available because it's in Bucket B
|
|
212
|
+
```
|
|
160
213
|
|
|
161
|
-
|
|
214
|
+
### Tool Descriptor
|
|
162
215
|
|
|
163
|
-
|
|
216
|
+
Each tool must have:
|
|
164
217
|
|
|
165
|
-
|
|
218
|
+
| Property | Type | Description |
|
|
219
|
+
|----------|------|-------------|
|
|
220
|
+
| `name` | `string` | Unique identifier for the tool |
|
|
221
|
+
| `description` | `string` | Natural language description of what the tool does |
|
|
222
|
+
| `inputSchema` | `object` | JSON Schema defining input parameters |
|
|
223
|
+
| `execute` | `function` | Async function that implements the tool logic |
|
|
166
224
|
|
|
167
|
-
###
|
|
225
|
+
### Tool Response Format
|
|
168
226
|
|
|
169
|
-
|
|
227
|
+
Tools must return an object with:
|
|
170
228
|
|
|
171
229
|
```typescript
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
async ({ num1, num2 }) => {
|
|
182
|
-
return { content: [{ type: "text", text: `${num1 + num2}` }] };
|
|
183
|
-
}
|
|
184
|
-
);
|
|
230
|
+
{
|
|
231
|
+
content: [
|
|
232
|
+
{
|
|
233
|
+
type: "text", // or "image", "resource"
|
|
234
|
+
text: "Result..." // the response content
|
|
235
|
+
}
|
|
236
|
+
],
|
|
237
|
+
isError?: boolean // optional error flag
|
|
238
|
+
}
|
|
185
239
|
```
|
|
186
240
|
|
|
187
|
-
##
|
|
241
|
+
## 🎯 Complete Examples
|
|
188
242
|
|
|
189
|
-
|
|
243
|
+
### Todo List Application
|
|
190
244
|
|
|
191
|
-
```
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
padding: 0.75rem;
|
|
214
|
-
border-radius: 0.5rem;
|
|
215
|
-
margin: 0.5rem 0;
|
|
245
|
+
```javascript
|
|
246
|
+
let todos = [];
|
|
247
|
+
|
|
248
|
+
window.navigator.modelContext.provideContext({
|
|
249
|
+
tools: [
|
|
250
|
+
{
|
|
251
|
+
name: "add-todo",
|
|
252
|
+
description: "Add a new todo item",
|
|
253
|
+
inputSchema: {
|
|
254
|
+
type: "object",
|
|
255
|
+
properties: {
|
|
256
|
+
text: { type: "string", description: "Todo text" }
|
|
257
|
+
},
|
|
258
|
+
required: ["text"]
|
|
259
|
+
},
|
|
260
|
+
async execute({ text }) {
|
|
261
|
+
const todo = { id: Date.now(), text, done: false };
|
|
262
|
+
todos.push(todo);
|
|
263
|
+
updateUI();
|
|
264
|
+
return {
|
|
265
|
+
content: [{ type: "text", text: `Added: "${text}"` }]
|
|
266
|
+
};
|
|
216
267
|
}
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
<script>
|
|
230
|
-
const { z } = window.Zod;
|
|
231
|
-
const todos = ["Demo Todo 1", "Demo Todo 2"];
|
|
232
|
-
|
|
233
|
-
function showFeedback(message) {
|
|
234
|
-
const feedback = document.createElement("div");
|
|
235
|
-
feedback.className = "ai-feedback";
|
|
236
|
-
feedback.textContent = `AI Action: ${message}`;
|
|
237
|
-
document.body.insertBefore(feedback, document.getElementById("todos"));
|
|
238
|
-
setTimeout(() => feedback.remove(), 3000);
|
|
268
|
+
},
|
|
269
|
+
{
|
|
270
|
+
name: "list-todos",
|
|
271
|
+
description: "Get all todo items",
|
|
272
|
+
inputSchema: { type: "object", properties: {} },
|
|
273
|
+
async execute() {
|
|
274
|
+
const list = todos.map(t =>
|
|
275
|
+
`${t.done ? '✓' : '○'} ${t.text}`
|
|
276
|
+
).join('\n');
|
|
277
|
+
return {
|
|
278
|
+
content: [{ type: "text", text: list || "No todos" }]
|
|
279
|
+
};
|
|
239
280
|
}
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
281
|
+
},
|
|
282
|
+
{
|
|
283
|
+
name: "complete-todo",
|
|
284
|
+
description: "Mark a todo as complete",
|
|
285
|
+
inputSchema: {
|
|
286
|
+
type: "object",
|
|
287
|
+
properties: {
|
|
288
|
+
id: { type: "number", description: "Todo ID" }
|
|
289
|
+
},
|
|
290
|
+
required: ["id"]
|
|
291
|
+
},
|
|
292
|
+
async execute({ id }) {
|
|
293
|
+
const todo = todos.find(t => t.id === id);
|
|
294
|
+
if (!todo) {
|
|
295
|
+
return {
|
|
296
|
+
content: [{ type: "text", text: "Todo not found" }],
|
|
297
|
+
isError: true
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
todo.done = true;
|
|
301
|
+
updateUI();
|
|
302
|
+
return {
|
|
303
|
+
content: [{ type: "text", text: `Completed: "${todo.text}"` }]
|
|
304
|
+
};
|
|
245
305
|
}
|
|
306
|
+
}
|
|
307
|
+
]
|
|
308
|
+
});
|
|
246
309
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
310
|
+
function updateUI() {
|
|
311
|
+
// Update your UI
|
|
312
|
+
document.getElementById('todo-list').innerHTML =
|
|
313
|
+
todos.map(t => `<li>${t.done ? '✓' : ''} ${t.text}</li>`).join('');
|
|
314
|
+
}
|
|
315
|
+
```
|
|
251
316
|
|
|
252
|
-
|
|
253
|
-
"addTodoItem",
|
|
254
|
-
{
|
|
255
|
-
title: "Add Todo",
|
|
256
|
-
description: "Adds a new todo item",
|
|
257
|
-
inputSchema: { text: z.string().describe("Todo text") },
|
|
258
|
-
},
|
|
259
|
-
async ({ text }) => {
|
|
260
|
-
todos.push(text);
|
|
261
|
-
showFeedback(`Added "${text}"`);
|
|
262
|
-
updateTodos();
|
|
263
|
-
return { content: [{ type: "text", text: `Added: ${text}` }] };
|
|
264
|
-
}
|
|
265
|
-
);
|
|
317
|
+
### E-commerce Product Search
|
|
266
318
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
319
|
+
```javascript
|
|
320
|
+
window.navigator.modelContext.provideContext({
|
|
321
|
+
tools: [
|
|
322
|
+
{
|
|
323
|
+
name: "search-products",
|
|
324
|
+
description: "Search for products in the catalog",
|
|
325
|
+
inputSchema: {
|
|
326
|
+
type: "object",
|
|
327
|
+
properties: {
|
|
328
|
+
query: {
|
|
329
|
+
type: "string",
|
|
330
|
+
description: "Search query"
|
|
272
331
|
},
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
);
|
|
278
|
-
|
|
279
|
-
window.mcp.registerTool(
|
|
280
|
-
"removeTodo",
|
|
281
|
-
{
|
|
282
|
-
title: "Remove Todo",
|
|
283
|
-
description: "Deletes a todo by index (1-based)",
|
|
284
|
-
inputSchema: { index: z.number().describe("Todo index") },
|
|
332
|
+
category: {
|
|
333
|
+
type: "string",
|
|
334
|
+
description: "Filter by category",
|
|
335
|
+
enum: ["electronics", "clothing", "books", "all"]
|
|
285
336
|
},
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
const removed = todos.splice(i, 1)[0];
|
|
290
|
-
showFeedback(`Removed "${removed}"`);
|
|
291
|
-
updateTodos();
|
|
292
|
-
return {
|
|
293
|
-
content: [{ type: "text", text: `Removed: ${removed}` }],
|
|
294
|
-
};
|
|
295
|
-
}
|
|
296
|
-
return {
|
|
297
|
-
content: [{ type: "text", text: `Invalid index: ${index}` }],
|
|
298
|
-
};
|
|
337
|
+
maxPrice: {
|
|
338
|
+
type: "number",
|
|
339
|
+
description: "Maximum price filter"
|
|
299
340
|
}
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
341
|
+
},
|
|
342
|
+
required: ["query"]
|
|
343
|
+
},
|
|
344
|
+
async execute({ query, category = "all", maxPrice }) {
|
|
345
|
+
const results = await searchProducts({
|
|
346
|
+
query,
|
|
347
|
+
category: category !== "all" ? category : undefined,
|
|
348
|
+
maxPrice
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
const summary = results.map(p =>
|
|
352
|
+
`${p.name} - $${p.price} (${p.category})`
|
|
353
|
+
).join('\n');
|
|
354
|
+
|
|
355
|
+
return {
|
|
356
|
+
content: [{
|
|
357
|
+
type: "text",
|
|
358
|
+
text: `Found ${results.length} products:\n${summary}`
|
|
359
|
+
}]
|
|
360
|
+
};
|
|
308
361
|
}
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
362
|
+
},
|
|
363
|
+
{
|
|
364
|
+
name: "add-to-cart",
|
|
365
|
+
description: "Add a product to the shopping cart",
|
|
366
|
+
inputSchema: {
|
|
367
|
+
type: "object",
|
|
368
|
+
properties: {
|
|
369
|
+
productId: { type: "string" },
|
|
370
|
+
quantity: { type: "number", default: 1 }
|
|
371
|
+
},
|
|
372
|
+
required: ["productId"]
|
|
373
|
+
},
|
|
374
|
+
async execute({ productId, quantity = 1 }) {
|
|
375
|
+
await addToCart(productId, quantity);
|
|
376
|
+
return {
|
|
377
|
+
content: [{
|
|
378
|
+
type: "text",
|
|
379
|
+
text: `Added ${quantity}x product ${productId} to cart`
|
|
380
|
+
}]
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
]
|
|
385
|
+
});
|
|
317
386
|
```
|
|
318
387
|
|
|
319
|
-
|
|
388
|
+
## 🔧 Dynamic Tool Registration (Component Lifecycle)
|
|
320
389
|
|
|
321
|
-
|
|
390
|
+
### React Component Example
|
|
322
391
|
|
|
323
|
-
|
|
324
|
-
2. Open your HTML file or site.
|
|
325
|
-
3. Use the extension's chat: Try "Add a todo: Buy milk" or "List all todos".
|
|
392
|
+
Perfect for managing tools tied to component lifecycle:
|
|
326
393
|
|
|
327
|
-
|
|
394
|
+
```javascript
|
|
395
|
+
import { useEffect } from 'react';
|
|
396
|
+
|
|
397
|
+
function MyComponent() {
|
|
398
|
+
useEffect(() => {
|
|
399
|
+
// Register component-specific tool when component mounts (Bucket B)
|
|
400
|
+
const registration = window.navigator.modelContext.registerTool({
|
|
401
|
+
name: "component-action",
|
|
402
|
+
description: "Action specific to this component",
|
|
403
|
+
inputSchema: { type: "object", properties: {} },
|
|
404
|
+
async execute() {
|
|
405
|
+
// Access component state/methods here
|
|
406
|
+
return {
|
|
407
|
+
content: [{ type: "text", text: "Component action executed!" }]
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
});
|
|
328
411
|
|
|
329
|
-
|
|
412
|
+
// Cleanup: unregister when component unmounts
|
|
413
|
+
return () => {
|
|
414
|
+
registration.unregister();
|
|
415
|
+
};
|
|
416
|
+
}, []);
|
|
330
417
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
- **Learning MCP-B**: Experiment with concepts in a simple environment.
|
|
418
|
+
return <div>My Component</div>;
|
|
419
|
+
}
|
|
420
|
+
```
|
|
335
421
|
|
|
336
|
-
|
|
422
|
+
### Persistence Across provideContext() Calls
|
|
337
423
|
|
|
338
|
-
|
|
424
|
+
```javascript
|
|
425
|
+
// Step 1: Register base tools (Bucket A)
|
|
426
|
+
window.navigator.modelContext.provideContext({
|
|
427
|
+
tools: [
|
|
428
|
+
{ name: "base-tool-1", description: "Base tool", inputSchema: {}, async execute() {} }
|
|
429
|
+
]
|
|
430
|
+
});
|
|
431
|
+
// Tools: ["base-tool-1"]
|
|
432
|
+
|
|
433
|
+
// Step 2: Register dynamic tool (Bucket B)
|
|
434
|
+
const reg = window.navigator.modelContext.registerTool({
|
|
435
|
+
name: "dynamic-tool",
|
|
436
|
+
description: "Dynamic tool",
|
|
437
|
+
inputSchema: { type: "object", properties: {} },
|
|
438
|
+
async execute() {
|
|
439
|
+
return { content: [{ type: "text", text: "Dynamic!" }] };
|
|
440
|
+
}
|
|
441
|
+
});
|
|
442
|
+
// Tools: ["base-tool-1", "dynamic-tool"]
|
|
443
|
+
|
|
444
|
+
// Step 3: Update base tools via provideContext
|
|
445
|
+
window.navigator.modelContext.provideContext({
|
|
446
|
+
tools: [
|
|
447
|
+
{ name: "base-tool-2", description: "New base tool", inputSchema: {}, async execute() {} }
|
|
448
|
+
]
|
|
449
|
+
});
|
|
450
|
+
// Tools: ["base-tool-2", "dynamic-tool"]
|
|
451
|
+
// ✅ "dynamic-tool" persists! Only "base-tool-1" was cleared
|
|
452
|
+
|
|
453
|
+
// Step 4: Clean up dynamic tool
|
|
454
|
+
reg.unregister();
|
|
455
|
+
// Tools: ["base-tool-2"]
|
|
456
|
+
```
|
|
339
457
|
|
|
340
|
-
|
|
341
|
-
- **ESM**: `dist/index.js` – Modern modules.
|
|
342
|
-
- **CommonJS**: `dist/index.cjs` – Node.js compatibility.
|
|
343
|
-
- **Types**: `dist/index.d.ts` – TypeScript support.
|
|
458
|
+
### Name Collision Protection
|
|
344
459
|
|
|
345
|
-
|
|
460
|
+
```javascript
|
|
461
|
+
// Register a base tool
|
|
462
|
+
window.navigator.modelContext.provideContext({
|
|
463
|
+
tools: [
|
|
464
|
+
{ name: "my-tool", description: "Base", inputSchema: {}, async execute() {} }
|
|
465
|
+
]
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
// This will throw an error!
|
|
469
|
+
try {
|
|
470
|
+
window.navigator.modelContext.registerTool({
|
|
471
|
+
name: "my-tool", // ❌ Name collision with Bucket A
|
|
472
|
+
description: "Dynamic",
|
|
473
|
+
inputSchema: {},
|
|
474
|
+
async execute() {}
|
|
475
|
+
});
|
|
476
|
+
} catch (error) {
|
|
477
|
+
console.error(error.message);
|
|
478
|
+
// Error: Tool name collision: "my-tool" is already registered via provideContext()
|
|
479
|
+
}
|
|
346
480
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
481
|
+
// Similarly, can't unregister a base tool
|
|
482
|
+
const baseToolList = window.navigator.modelContext.provideContext({
|
|
483
|
+
tools: [{ name: "base", description: "Base", inputSchema: {}, async execute() {} }]
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
// This will also throw an error!
|
|
487
|
+
try {
|
|
488
|
+
// Assuming we got a reference somehow
|
|
489
|
+
// registration.unregister(); would fail for a base tool
|
|
490
|
+
} catch (error) {
|
|
491
|
+
// Error: Cannot unregister tool "base": This tool was registered via provideContext()
|
|
492
|
+
}
|
|
350
493
|
```
|
|
351
494
|
|
|
495
|
+
## 🔧 Event-Based Tool Calls (Advanced)
|
|
496
|
+
|
|
497
|
+
For manifest-based or advanced scenarios, you can handle tool calls as events:
|
|
498
|
+
|
|
352
499
|
```javascript
|
|
353
|
-
|
|
354
|
-
|
|
500
|
+
window.navigator.modelContext.addEventListener('toolcall', async (event) => {
|
|
501
|
+
console.log(`Tool called: ${event.name}`, event.arguments);
|
|
502
|
+
|
|
503
|
+
if (event.name === "custom-tool") {
|
|
504
|
+
// Prevent default execution
|
|
505
|
+
event.preventDefault();
|
|
506
|
+
|
|
507
|
+
// Provide custom response
|
|
508
|
+
event.respondWith({
|
|
509
|
+
content: [{
|
|
510
|
+
type: "text",
|
|
511
|
+
text: "Custom response from event handler"
|
|
512
|
+
}]
|
|
513
|
+
});
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
// If not prevented, the tool's execute function will run normally
|
|
517
|
+
});
|
|
355
518
|
```
|
|
356
519
|
|
|
357
|
-
|
|
520
|
+
### Hybrid Approach
|
|
521
|
+
|
|
522
|
+
The API supports both approaches simultaneously:
|
|
358
523
|
|
|
359
|
-
|
|
524
|
+
1. **Event dispatched first** - `toolcall` event is fired
|
|
525
|
+
2. **Event can override** - Call `event.preventDefault()` and `event.respondWith()`
|
|
526
|
+
3. **Default execution** - If not prevented, the tool's `execute()` function runs
|
|
360
527
|
|
|
361
|
-
|
|
528
|
+
This allows flexibility for different use cases.
|
|
529
|
+
|
|
530
|
+
## 🏗️ Architecture
|
|
531
|
+
|
|
532
|
+
```
|
|
533
|
+
┌─────────────────┐
|
|
534
|
+
│ AI Agent │
|
|
535
|
+
│ (MCP Client) │
|
|
536
|
+
└────────┬────────┘
|
|
537
|
+
│ MCP Protocol
|
|
538
|
+
│ (JSON-RPC)
|
|
539
|
+
┌────────▼────────┐
|
|
540
|
+
│ MCP Server │
|
|
541
|
+
│ (Internal) │
|
|
542
|
+
└────────┬────────┘
|
|
543
|
+
│
|
|
544
|
+
┌────────▼───────────────────┐
|
|
545
|
+
│ navigator.modelContext │ ◄── Your app registers tools here
|
|
546
|
+
│ (This pkg) │
|
|
547
|
+
└────────────────────────────┘
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
This package:
|
|
551
|
+
1. Exposes `window.navigator.modelContext` API (W3C Web Model Context standard)
|
|
552
|
+
2. Internally creates an MCP Server
|
|
553
|
+
3. Bridges tool calls between the two protocols
|
|
554
|
+
4. Uses TabServerTransport for browser communication
|
|
555
|
+
|
|
556
|
+
## 🔍 Feature Detection
|
|
557
|
+
|
|
558
|
+
Check if the API is available:
|
|
362
559
|
|
|
363
560
|
```javascript
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
{
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
async () => {
|
|
371
|
-
try {
|
|
372
|
-
// Logic here
|
|
373
|
-
return { content: [{ type: "text", text: "Success!" }] };
|
|
374
|
-
} catch (err) {
|
|
375
|
-
return {
|
|
376
|
-
content: [{ type: "text", text: `Failed: ${err.message}` }],
|
|
377
|
-
isError: true,
|
|
378
|
-
};
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
);
|
|
561
|
+
if ("modelContext" in navigator) {
|
|
562
|
+
// API is available
|
|
563
|
+
navigator.modelContext.provideContext({ tools: [...] });
|
|
564
|
+
} else {
|
|
565
|
+
console.warn("Web Model Context API not available");
|
|
566
|
+
}
|
|
382
567
|
```
|
|
383
568
|
|
|
384
|
-
|
|
569
|
+
## 🐛 Debugging
|
|
385
570
|
|
|
386
|
-
|
|
571
|
+
In development mode, access the internal bridge:
|
|
387
572
|
|
|
388
573
|
```javascript
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
"adminTool",
|
|
393
|
-
{
|
|
394
|
-
title: "Admin Tool",
|
|
395
|
-
description: "Admin-only",
|
|
396
|
-
},
|
|
397
|
-
async () => {
|
|
398
|
-
/* ... */
|
|
399
|
-
}
|
|
400
|
-
);
|
|
401
|
-
}
|
|
574
|
+
if (window.__mcpBridge) {
|
|
575
|
+
console.log("MCP Server:", window.__mcpBridge.server);
|
|
576
|
+
console.log("Registered tools:", window.__mcpBridge.tools);
|
|
402
577
|
}
|
|
403
578
|
```
|
|
404
579
|
|
|
405
|
-
##
|
|
580
|
+
## 📦 What's Included
|
|
581
|
+
|
|
582
|
+
- **Web Model Context API** - Standard `window.navigator.modelContext` interface
|
|
583
|
+
- **Dynamic Tool Registration** - `registerTool()` with `unregister()` function
|
|
584
|
+
- **MCP Bridge** - Automatic bridging to Model Context Protocol
|
|
585
|
+
- **Tab Transport** - Communication layer for browser contexts
|
|
586
|
+
- **Event System** - Hybrid tool call handling
|
|
587
|
+
- **TypeScript Types** - Full type definitions included
|
|
588
|
+
|
|
589
|
+
## 🔒 Security Considerations
|
|
406
590
|
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
591
|
+
### Origin Restrictions
|
|
592
|
+
|
|
593
|
+
By default, the MCP transport allows connections from any origin (`*`). For production, you should configure allowed origins:
|
|
594
|
+
|
|
595
|
+
```javascript
|
|
596
|
+
// Future configuration API
|
|
597
|
+
window.navigator.modelContext.configure({
|
|
598
|
+
allowedOrigins: [
|
|
599
|
+
'https://your-app.com',
|
|
600
|
+
'https://trusted-agent.com'
|
|
601
|
+
]
|
|
602
|
+
});
|
|
603
|
+
```
|
|
604
|
+
|
|
605
|
+
### Tool Validation
|
|
606
|
+
|
|
607
|
+
Always validate inputs in your tool implementations:
|
|
608
|
+
|
|
609
|
+
```javascript
|
|
610
|
+
{
|
|
611
|
+
name: "delete-item",
|
|
612
|
+
description: "Delete an item",
|
|
613
|
+
inputSchema: {
|
|
614
|
+
type: "object",
|
|
615
|
+
properties: {
|
|
616
|
+
id: { type: "string", pattern: "^[a-zA-Z0-9]+$" }
|
|
617
|
+
},
|
|
618
|
+
required: ["id"]
|
|
619
|
+
},
|
|
620
|
+
async execute({ id }) {
|
|
621
|
+
// Additional validation
|
|
622
|
+
if (!isValidId(id)) {
|
|
623
|
+
return {
|
|
624
|
+
content: [{ type: "text", text: "Invalid ID" }],
|
|
625
|
+
isError: true
|
|
626
|
+
};
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
// Proceed with deletion
|
|
630
|
+
await deleteItem(id);
|
|
631
|
+
return {
|
|
632
|
+
content: [{ type: "text", text: "Item deleted" }]
|
|
633
|
+
};
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
```
|
|
411
637
|
|
|
412
|
-
##
|
|
638
|
+
## 🤝 Related Packages
|
|
413
639
|
|
|
414
|
-
- [
|
|
415
|
-
- [
|
|
416
|
-
- [
|
|
417
|
-
- [Documentation](https://mcp-b.ai): Full guides and specs.
|
|
640
|
+
- [`@mcp-b/transports`](../transports) - MCP transport implementations
|
|
641
|
+
- [`@mcp-b/mcp-react-hooks`](../mcp-react-hooks) - React hooks for MCP
|
|
642
|
+
- [`@modelcontextprotocol/sdk`](https://www.npmjs.com/package/@modelcontextprotocol/sdk) - Official MCP SDK
|
|
418
643
|
|
|
419
|
-
##
|
|
644
|
+
## 📚 Resources
|
|
420
645
|
|
|
421
|
-
|
|
646
|
+
- [Web Model Context API Explainer](https://github.com/webmachinelearning/webmcp)
|
|
647
|
+
- [Model Context Protocol Spec](https://modelcontextprotocol.io/)
|
|
648
|
+
- [Microsoft Edge Explainer](https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/WebModelContext/explainer.md)
|
|
422
649
|
|
|
423
|
-
##
|
|
650
|
+
## 📝 License
|
|
424
651
|
|
|
425
|
-
|
|
652
|
+
MIT - see [LICENSE](../../LICENSE) for details
|
|
426
653
|
|
|
427
|
-
|
|
654
|
+
## 🙋 Support
|
|
428
655
|
|
|
429
|
-
|
|
656
|
+
- [GitHub Issues](https://github.com/WebMCP-org/npm-packages/issues)
|
|
657
|
+
- [Documentation](https://docs.mcp-b.ai)
|
|
658
|
+
- [Discord Community](https://discord.gg/a9fBR6Bw)
|