@mcp-b/global 1.2.1 → 1.3.0
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 +390 -0
- package/dist/index.d.ts +189 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.iife.js +32 -17
- package/dist/index.js +279 -189
- package/dist/index.js.map +1 -1
- package/package.json +21 -8
package/dist/index.js
CHANGED
|
@@ -1,88 +1,188 @@
|
|
|
1
1
|
import { IframeChildTransport, TabServerTransport } from "@mcp-b/transports";
|
|
2
2
|
import { CallToolRequestSchema, GetPromptRequestSchema, ListPromptsRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema, Server } from "@mcp-b/webmcp-ts-sdk";
|
|
3
|
-
import { jsonSchemaToZod } from "@
|
|
3
|
+
import { jsonSchemaToZod } from "@n8n/json-schema-to-zod";
|
|
4
4
|
import { z } from "zod";
|
|
5
5
|
import { zodToJsonSchema as zodToJsonSchema$1 } from "zod-to-json-schema";
|
|
6
6
|
|
|
7
|
-
//#region src/
|
|
7
|
+
//#region src/logger.ts
|
|
8
|
+
/**
|
|
9
|
+
* @license
|
|
10
|
+
* Copyright 2025 Google LLC
|
|
11
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
12
|
+
*/
|
|
8
13
|
/**
|
|
9
|
-
*
|
|
10
|
-
*
|
|
14
|
+
* Lightweight logging system for @mcp-b/global
|
|
15
|
+
*
|
|
16
|
+
* Design Decision: This implements a custom logger instead of using the 'debug'
|
|
17
|
+
* package to reduce bundle size and eliminate external dependencies in the
|
|
18
|
+
* browser build. The API is intentionally simpler, focusing on the specific
|
|
19
|
+
* needs of browser-based MCP implementations.
|
|
20
|
+
*
|
|
21
|
+
* Configuration via localStorage:
|
|
22
|
+
* - localStorage.setItem('WEBMCP_DEBUG', '*') - enable all debug logging
|
|
23
|
+
* - localStorage.setItem('WEBMCP_DEBUG', 'WebModelContext') - enable specific namespace
|
|
24
|
+
* - localStorage.setItem('WEBMCP_DEBUG', 'WebModelContext,NativeAdapter') - multiple namespaces
|
|
25
|
+
* - localStorage.setItem('WEBMCP_DEBUG', 'WebModelContext:') - enable namespace and sub-namespaces
|
|
26
|
+
* - localStorage.removeItem('WEBMCP_DEBUG') - disable debug logging (default)
|
|
27
|
+
*
|
|
28
|
+
* Environment Support:
|
|
29
|
+
* - Automatically detects localStorage availability
|
|
30
|
+
* - Gracefully degrades to "disabled" state when localStorage is inaccessible
|
|
31
|
+
* - Never throws errors from configuration checks (safe for private browsing mode)
|
|
32
|
+
*/
|
|
33
|
+
/** localStorage key for debug configuration */
|
|
34
|
+
const DEBUG_CONFIG_KEY = "WEBMCP_DEBUG";
|
|
35
|
+
/**
|
|
36
|
+
* Check if debug logging is enabled for a namespace
|
|
37
|
+
*
|
|
38
|
+
* Supports namespace hierarchy via colons. Setting 'WebModelContext' will match
|
|
39
|
+
* both 'WebModelContext' and 'WebModelContext:init', but NOT 'WebModelContextTesting'.
|
|
40
|
+
*/
|
|
41
|
+
function isDebugEnabled(namespace) {
|
|
42
|
+
if (typeof window === "undefined" || !window.localStorage) return false;
|
|
43
|
+
try {
|
|
44
|
+
const debugConfig = localStorage.getItem(DEBUG_CONFIG_KEY);
|
|
45
|
+
if (!debugConfig) return false;
|
|
46
|
+
if (debugConfig === "*") return true;
|
|
47
|
+
return debugConfig.split(",").map((p) => p.trim()).some((pattern) => namespace === pattern || namespace.startsWith(`${pattern}:`));
|
|
48
|
+
} catch (err) {
|
|
49
|
+
if (typeof console !== "undefined" && console.warn) {
|
|
50
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
51
|
+
console.warn(`[WebMCP] localStorage access failed, debug logging disabled: ${message}`);
|
|
52
|
+
}
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* No-op function for disabled log levels
|
|
58
|
+
*/
|
|
59
|
+
const noop = () => {};
|
|
60
|
+
/**
|
|
61
|
+
* Create a namespaced logger
|
|
62
|
+
*
|
|
63
|
+
* Uses .bind() to prepend namespace prefixes to console methods without manual
|
|
64
|
+
* string concatenation. Debug enablement is determined at logger creation time
|
|
65
|
+
* for performance - changes to localStorage after creation won't affect existing
|
|
66
|
+
* loggers. Refresh the page to apply new WEBMCP_DEBUG settings.
|
|
67
|
+
*
|
|
68
|
+
* @param namespace - Namespace for the logger (e.g., 'WebModelContext', 'NativeAdapter')
|
|
69
|
+
* @returns Logger instance with debug, info, warn, error methods
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* ```typescript
|
|
73
|
+
* const logger = createLogger('WebModelContext');
|
|
74
|
+
* logger.debug('Tool registered:', toolName); // Only shown if WEBMCP_DEBUG includes 'WebModelContext'
|
|
75
|
+
* logger.error('Execution failed:', error); // Always enabled
|
|
76
|
+
* ```
|
|
11
77
|
*/
|
|
78
|
+
function createLogger(namespace) {
|
|
79
|
+
const prefix = `[${namespace}]`;
|
|
80
|
+
const isDebug = isDebugEnabled(namespace);
|
|
81
|
+
const boundWarn = console.warn.bind(console, prefix);
|
|
82
|
+
const boundError = console.error.bind(console, prefix);
|
|
83
|
+
const boundLog = console.log.bind(console, prefix);
|
|
84
|
+
return {
|
|
85
|
+
warn: boundWarn,
|
|
86
|
+
error: boundError,
|
|
87
|
+
debug: isDebug ? boundLog : noop,
|
|
88
|
+
info: isDebug ? boundLog : noop
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
//#endregion
|
|
93
|
+
//#region src/validation.ts
|
|
94
|
+
const logger$2 = createLogger("WebModelContext");
|
|
95
|
+
const isRecord = (value) => typeof value === "object" && value !== null;
|
|
96
|
+
const stripSchemaMeta = (schema) => {
|
|
97
|
+
const { $schema: _,...rest } = schema;
|
|
98
|
+
return rest;
|
|
99
|
+
};
|
|
100
|
+
const isOptionalSchema = (schema) => {
|
|
101
|
+
const typeName = schema._def?.typeName;
|
|
102
|
+
return typeName === "ZodOptional" || typeName === "ZodDefault";
|
|
103
|
+
};
|
|
12
104
|
function isZodSchema(schema) {
|
|
13
|
-
if (
|
|
105
|
+
if (!isRecord(schema)) return false;
|
|
14
106
|
if ("type" in schema && typeof schema.type === "string") return false;
|
|
15
107
|
const values = Object.values(schema);
|
|
16
108
|
if (values.length === 0) return false;
|
|
17
|
-
return values.some((
|
|
109
|
+
return values.some((v) => isRecord(v) && "_def" in v);
|
|
110
|
+
}
|
|
111
|
+
function zodToJsonSchema(schema) {
|
|
112
|
+
const properties = {};
|
|
113
|
+
const required = [];
|
|
114
|
+
for (const [key, zodSchema] of Object.entries(schema)) {
|
|
115
|
+
properties[key] = stripSchemaMeta(zodToJsonSchema$1(zodSchema, {
|
|
116
|
+
strictUnions: true,
|
|
117
|
+
$refStrategy: "none"
|
|
118
|
+
}));
|
|
119
|
+
if (!isOptionalSchema(zodSchema)) required.push(key);
|
|
120
|
+
}
|
|
121
|
+
const result = {
|
|
122
|
+
type: "object",
|
|
123
|
+
properties
|
|
124
|
+
};
|
|
125
|
+
if (required.length > 0) result.required = required;
|
|
126
|
+
return result;
|
|
18
127
|
}
|
|
19
|
-
/**
|
|
20
|
-
* Convert JSON Schema to Zod validator
|
|
21
|
-
* Uses @composio/json-schema-to-zod for conversion
|
|
22
|
-
*/
|
|
23
128
|
function jsonSchemaToZod$1(jsonSchema) {
|
|
24
129
|
try {
|
|
25
130
|
return jsonSchemaToZod(jsonSchema);
|
|
26
131
|
} catch (error) {
|
|
27
|
-
|
|
132
|
+
logger$2.warn("jsonSchemaToZod failed:", error);
|
|
28
133
|
return z.object({}).passthrough();
|
|
29
134
|
}
|
|
30
135
|
}
|
|
31
|
-
/**
|
|
32
|
-
* Convert Zod schema object to JSON Schema
|
|
33
|
-
* Uses zod-to-json-schema package for comprehensive conversion
|
|
34
|
-
*
|
|
35
|
-
* @param schema - Record of Zod type definitions (e.g., { name: z.string(), age: z.number() })
|
|
36
|
-
* @returns JSON Schema object compatible with MCP InputSchema
|
|
37
|
-
*/
|
|
38
|
-
function zodToJsonSchema(schema) {
|
|
39
|
-
const { $schema: _,...rest } = zodToJsonSchema$1(z.object(schema), {
|
|
40
|
-
$refStrategy: "none",
|
|
41
|
-
target: "jsonSchema7"
|
|
42
|
-
});
|
|
43
|
-
return rest;
|
|
44
|
-
}
|
|
45
|
-
/**
|
|
46
|
-
* Normalize a schema to both JSON Schema and Zod formats
|
|
47
|
-
* Detects which format is provided and converts to the other
|
|
48
|
-
*/
|
|
49
136
|
function normalizeSchema(schema) {
|
|
50
|
-
if (isZodSchema(schema))
|
|
51
|
-
jsonSchema
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
137
|
+
if (isZodSchema(schema)) {
|
|
138
|
+
const jsonSchema = zodToJsonSchema(schema);
|
|
139
|
+
return {
|
|
140
|
+
jsonSchema,
|
|
141
|
+
zodValidator: jsonSchemaToZod$1(jsonSchema)
|
|
142
|
+
};
|
|
143
|
+
}
|
|
55
144
|
return {
|
|
56
|
-
jsonSchema,
|
|
57
|
-
zodValidator: jsonSchemaToZod$1(
|
|
145
|
+
jsonSchema: schema,
|
|
146
|
+
zodValidator: jsonSchemaToZod$1(schema)
|
|
58
147
|
};
|
|
59
148
|
}
|
|
60
|
-
/**
|
|
61
|
-
* Validate data with Zod schema and return formatted result
|
|
62
|
-
*/
|
|
63
149
|
function validateWithZod(data, validator) {
|
|
64
150
|
const result = validator.safeParse(data);
|
|
65
|
-
if (
|
|
66
|
-
success: false,
|
|
67
|
-
error: `Validation failed:\n${result.error.errors.map((err) => ` - ${err.path.join(".") || "root"}: ${err.message}`).join("\n")}`
|
|
68
|
-
};
|
|
69
|
-
return {
|
|
151
|
+
if (result.success) return {
|
|
70
152
|
success: true,
|
|
71
153
|
data: result.data
|
|
72
154
|
};
|
|
155
|
+
return {
|
|
156
|
+
success: false,
|
|
157
|
+
error: `Validation failed:\n${result.error.issues.map((err) => ` - ${err.path.join(".") || "root"}: ${err.message}`).join("\n")}`
|
|
158
|
+
};
|
|
73
159
|
}
|
|
74
160
|
|
|
75
161
|
//#endregion
|
|
76
162
|
//#region src/global.ts
|
|
163
|
+
const logger$1 = createLogger("WebModelContext");
|
|
164
|
+
const nativeLogger = createLogger("NativeAdapter");
|
|
165
|
+
const bridgeLogger = createLogger("MCPBridge");
|
|
166
|
+
const testingLogger = createLogger("ModelContextTesting");
|
|
167
|
+
/**
|
|
168
|
+
* Marker property name used to identify polyfill implementations.
|
|
169
|
+
* This constant ensures single source of truth for the marker used in
|
|
170
|
+
* both detection (detectNativeAPI) and definition (WebModelContextTesting).
|
|
171
|
+
*/
|
|
172
|
+
const POLYFILL_MARKER_PROPERTY = "__isWebMCPPolyfill";
|
|
77
173
|
/**
|
|
78
174
|
* Detect if the native Chromium Web Model Context API is available.
|
|
79
175
|
* Checks for both navigator.modelContext and navigator.modelContextTesting,
|
|
80
|
-
* and verifies they are native implementations (not polyfills)
|
|
81
|
-
*
|
|
176
|
+
* and verifies they are native implementations (not polyfills).
|
|
177
|
+
*
|
|
178
|
+
* Detection uses a marker property (`__isWebMCPPolyfill`) on the testing API
|
|
179
|
+
* to reliably distinguish polyfills from native implementations. This approach
|
|
180
|
+
* works correctly even when class names are minified in production builds.
|
|
82
181
|
*
|
|
83
182
|
* @returns Detection result with flags for native context and testing API availability
|
|
84
183
|
*/
|
|
85
184
|
function detectNativeAPI() {
|
|
185
|
+
/* c8 ignore next 2 */
|
|
86
186
|
if (typeof window === "undefined" || typeof navigator === "undefined") return {
|
|
87
187
|
hasNativeContext: false,
|
|
88
188
|
hasNativeTesting: false
|
|
@@ -90,10 +190,10 @@ function detectNativeAPI() {
|
|
|
90
190
|
const modelContext = navigator.modelContext;
|
|
91
191
|
const modelContextTesting = navigator.modelContextTesting;
|
|
92
192
|
if (!modelContext || !modelContextTesting) return {
|
|
93
|
-
hasNativeContext:
|
|
94
|
-
hasNativeTesting:
|
|
193
|
+
hasNativeContext: Boolean(modelContext),
|
|
194
|
+
hasNativeTesting: Boolean(modelContextTesting)
|
|
95
195
|
};
|
|
96
|
-
if (
|
|
196
|
+
if (POLYFILL_MARKER_PROPERTY in modelContextTesting && modelContextTesting[POLYFILL_MARKER_PROPERTY] === true) return {
|
|
97
197
|
hasNativeContext: false,
|
|
98
198
|
hasNativeTesting: false
|
|
99
199
|
};
|
|
@@ -133,7 +233,6 @@ var NativeModelContextAdapter = class {
|
|
|
133
233
|
this.nativeContext = nativeContext;
|
|
134
234
|
this.nativeTesting = nativeTesting;
|
|
135
235
|
this.nativeTesting.registerToolsChangedCallback(() => {
|
|
136
|
-
console.log("[Native Adapter] Tool change detected from native API");
|
|
137
236
|
this.syncToolsFromNative();
|
|
138
237
|
});
|
|
139
238
|
this.syncToolsFromNative();
|
|
@@ -150,7 +249,6 @@ var NativeModelContextAdapter = class {
|
|
|
150
249
|
this.syncInProgress = true;
|
|
151
250
|
try {
|
|
152
251
|
const nativeTools = this.nativeTesting.listTools();
|
|
153
|
-
console.log(`[Native Adapter] Syncing ${nativeTools.length} tools from native API`);
|
|
154
252
|
this.bridge.tools.clear();
|
|
155
253
|
for (const toolInfo of nativeTools) try {
|
|
156
254
|
const inputSchema = JSON.parse(toolInfo.inputSchema);
|
|
@@ -166,7 +264,7 @@ var NativeModelContextAdapter = class {
|
|
|
166
264
|
};
|
|
167
265
|
this.bridge.tools.set(toolInfo.name, validatedTool);
|
|
168
266
|
} catch (error) {
|
|
169
|
-
|
|
267
|
+
nativeLogger.error(`Failed to sync tool "${toolInfo.name}":`, error);
|
|
170
268
|
}
|
|
171
269
|
this.notifyMCPServers();
|
|
172
270
|
} finally {
|
|
@@ -220,26 +318,34 @@ var NativeModelContextAdapter = class {
|
|
|
220
318
|
}
|
|
221
319
|
/**
|
|
222
320
|
* Provides context (tools) to AI models via the native API.
|
|
223
|
-
*
|
|
321
|
+
* Converts Zod schemas to JSON Schema before passing to native API.
|
|
224
322
|
* Tool change callback will fire and trigger sync automatically.
|
|
225
323
|
*
|
|
226
324
|
* @param {ModelContextInput} context - Context containing tools to register
|
|
227
325
|
*/
|
|
228
326
|
provideContext(context) {
|
|
229
|
-
|
|
230
|
-
|
|
327
|
+
const { tools,...rest } = context;
|
|
328
|
+
const normalizedContext = { ...rest };
|
|
329
|
+
if (tools) normalizedContext.tools = tools.map((tool) => ({
|
|
330
|
+
...tool,
|
|
331
|
+
inputSchema: normalizeSchema(tool.inputSchema).jsonSchema
|
|
332
|
+
}));
|
|
333
|
+
this.nativeContext.provideContext(normalizedContext);
|
|
231
334
|
}
|
|
232
335
|
/**
|
|
233
336
|
* Registers a single tool dynamically via the native API.
|
|
234
|
-
*
|
|
337
|
+
* Converts Zod schemas to JSON Schema before passing to native API.
|
|
235
338
|
* Tool change callback will fire and trigger sync automatically.
|
|
236
339
|
*
|
|
237
340
|
* @param {ToolDescriptor} tool - The tool descriptor to register
|
|
238
341
|
* @returns {{unregister: () => void}} Object with unregister function
|
|
239
342
|
*/
|
|
240
343
|
registerTool(tool) {
|
|
241
|
-
|
|
242
|
-
|
|
344
|
+
const normalizedTool = {
|
|
345
|
+
...tool,
|
|
346
|
+
inputSchema: normalizeSchema(tool.inputSchema).jsonSchema
|
|
347
|
+
};
|
|
348
|
+
return this.nativeContext.registerTool(normalizedTool);
|
|
243
349
|
}
|
|
244
350
|
/**
|
|
245
351
|
* Unregisters a tool by name via the native API.
|
|
@@ -248,7 +354,6 @@ var NativeModelContextAdapter = class {
|
|
|
248
354
|
* @param {string} name - Name of the tool to unregister
|
|
249
355
|
*/
|
|
250
356
|
unregisterTool(name) {
|
|
251
|
-
console.log(`[Native Adapter] Delegating unregisterTool("${name}") to native API`);
|
|
252
357
|
this.nativeContext.unregisterTool(name);
|
|
253
358
|
}
|
|
254
359
|
/**
|
|
@@ -256,25 +361,25 @@ var NativeModelContextAdapter = class {
|
|
|
256
361
|
* Delegates to navigator.modelContext.clearContext().
|
|
257
362
|
*/
|
|
258
363
|
clearContext() {
|
|
259
|
-
console.log("[Native Adapter] Delegating clearContext to native API");
|
|
260
364
|
this.nativeContext.clearContext();
|
|
261
365
|
}
|
|
262
366
|
/**
|
|
263
367
|
* Executes a tool via the native API.
|
|
264
368
|
* Delegates to navigator.modelContextTesting.executeTool() with JSON string args.
|
|
369
|
+
* Note: skipValidation option is ignored - native API handles its own validation.
|
|
265
370
|
*
|
|
266
371
|
* @param {string} toolName - Name of the tool to execute
|
|
267
372
|
* @param {Record<string, unknown>} args - Arguments to pass to the tool
|
|
373
|
+
* @param {Object} [_options] - Execution options (ignored for native adapter)
|
|
268
374
|
* @returns {Promise<ToolResponse>} The tool's response in MCP format
|
|
269
375
|
* @internal
|
|
270
376
|
*/
|
|
271
|
-
async executeTool(toolName, args) {
|
|
272
|
-
console.log(`[Native Adapter] Executing tool "${toolName}" via native API`);
|
|
377
|
+
async executeTool(toolName, args, _options) {
|
|
273
378
|
try {
|
|
274
379
|
const result = await this.nativeTesting.executeTool(toolName, JSON.stringify(args));
|
|
275
380
|
return this.convertToToolResponse(result);
|
|
276
381
|
} catch (error) {
|
|
277
|
-
|
|
382
|
+
nativeLogger.error(`Error executing tool "${toolName}":`, error);
|
|
278
383
|
return {
|
|
279
384
|
content: [{
|
|
280
385
|
type: "text",
|
|
@@ -305,7 +410,7 @@ var NativeModelContextAdapter = class {
|
|
|
305
410
|
* This is a polyfill-only feature.
|
|
306
411
|
*/
|
|
307
412
|
registerResource(_resource) {
|
|
308
|
-
|
|
413
|
+
nativeLogger.warn("registerResource is not supported by native API");
|
|
309
414
|
return { unregister: () => {} };
|
|
310
415
|
}
|
|
311
416
|
/**
|
|
@@ -313,7 +418,7 @@ var NativeModelContextAdapter = class {
|
|
|
313
418
|
* Note: Native Chromium API does not yet support resources.
|
|
314
419
|
*/
|
|
315
420
|
unregisterResource(_uri) {
|
|
316
|
-
|
|
421
|
+
nativeLogger.warn("unregisterResource is not supported by native API");
|
|
317
422
|
}
|
|
318
423
|
/**
|
|
319
424
|
* Lists all registered resources.
|
|
@@ -343,7 +448,7 @@ var NativeModelContextAdapter = class {
|
|
|
343
448
|
* This is a polyfill-only feature.
|
|
344
449
|
*/
|
|
345
450
|
registerPrompt(_prompt) {
|
|
346
|
-
|
|
451
|
+
nativeLogger.warn("registerPrompt is not supported by native API");
|
|
347
452
|
return { unregister: () => {} };
|
|
348
453
|
}
|
|
349
454
|
/**
|
|
@@ -351,7 +456,7 @@ var NativeModelContextAdapter = class {
|
|
|
351
456
|
* Note: Native Chromium API does not yet support prompts.
|
|
352
457
|
*/
|
|
353
458
|
unregisterPrompt(_name) {
|
|
354
|
-
|
|
459
|
+
nativeLogger.warn("unregisterPrompt is not supported by native API");
|
|
355
460
|
}
|
|
356
461
|
/**
|
|
357
462
|
* Lists all registered prompts.
|
|
@@ -406,7 +511,6 @@ var NativeModelContextAdapter = class {
|
|
|
406
511
|
* This is handled by the polyfill.
|
|
407
512
|
*/
|
|
408
513
|
async createMessage(params) {
|
|
409
|
-
console.log("[Native Adapter] Requesting sampling from client");
|
|
410
514
|
const underlyingServer = this.bridge.tabServer.server;
|
|
411
515
|
if (!underlyingServer?.createMessage) throw new Error("Sampling is not supported: no connected client with sampling capability");
|
|
412
516
|
return underlyingServer.createMessage(params);
|
|
@@ -417,7 +521,6 @@ var NativeModelContextAdapter = class {
|
|
|
417
521
|
* This is handled by the polyfill.
|
|
418
522
|
*/
|
|
419
523
|
async elicitInput(params) {
|
|
420
|
-
console.log("[Native Adapter] Requesting elicitation from client");
|
|
421
524
|
const underlyingServer = this.bridge.tabServer.server;
|
|
422
525
|
if (!underlyingServer?.elicitInput) throw new Error("Elicitation is not supported: no connected client with elicitation capability");
|
|
423
526
|
return underlyingServer.elicitInput(params);
|
|
@@ -490,6 +593,15 @@ const RAPID_DUPLICATE_WINDOW_MS = 50;
|
|
|
490
593
|
* @implements {ModelContextTesting}
|
|
491
594
|
*/
|
|
492
595
|
var WebModelContextTesting = class {
|
|
596
|
+
/**
|
|
597
|
+
* Marker property to identify this as a polyfill implementation.
|
|
598
|
+
* Used by detectNativeAPI() to distinguish polyfill from native Chromium API.
|
|
599
|
+
* This approach works reliably even when class names are minified in production builds.
|
|
600
|
+
*
|
|
601
|
+
* @see POLYFILL_MARKER_PROPERTY - The constant defining this property name
|
|
602
|
+
* @see MayHavePolyfillMarker - The interface for type-safe detection
|
|
603
|
+
*/
|
|
604
|
+
[POLYFILL_MARKER_PROPERTY] = true;
|
|
493
605
|
toolCallHistory = [];
|
|
494
606
|
mockResponses = /* @__PURE__ */ new Map();
|
|
495
607
|
toolsChangedCallbacks = /* @__PURE__ */ new Set();
|
|
@@ -547,7 +659,7 @@ var WebModelContextTesting = class {
|
|
|
547
659
|
for (const callback of this.toolsChangedCallbacks) try {
|
|
548
660
|
callback();
|
|
549
661
|
} catch (error) {
|
|
550
|
-
|
|
662
|
+
testingLogger.error("Error in tools changed callback:", error);
|
|
551
663
|
}
|
|
552
664
|
}
|
|
553
665
|
/**
|
|
@@ -561,7 +673,6 @@ var WebModelContextTesting = class {
|
|
|
561
673
|
* @throws {Error} If the tool does not exist
|
|
562
674
|
*/
|
|
563
675
|
async executeTool(toolName, inputArgsJson) {
|
|
564
|
-
console.log(`[Model Context Testing] Executing tool: ${toolName}`);
|
|
565
676
|
let args;
|
|
566
677
|
try {
|
|
567
678
|
args = JSON.parse(inputArgsJson);
|
|
@@ -598,7 +709,6 @@ var WebModelContextTesting = class {
|
|
|
598
709
|
*/
|
|
599
710
|
registerToolsChangedCallback(callback) {
|
|
600
711
|
this.toolsChangedCallbacks.add(callback);
|
|
601
|
-
console.log("[Model Context Testing] Tools changed callback registered");
|
|
602
712
|
}
|
|
603
713
|
/**
|
|
604
714
|
* Gets all tool calls that have been recorded (polyfill extension).
|
|
@@ -613,7 +723,6 @@ var WebModelContextTesting = class {
|
|
|
613
723
|
*/
|
|
614
724
|
clearToolCalls() {
|
|
615
725
|
this.toolCallHistory = [];
|
|
616
|
-
console.log("[Model Context Testing] Tool call history cleared");
|
|
617
726
|
}
|
|
618
727
|
/**
|
|
619
728
|
* Sets a mock response for a specific tool (polyfill extension).
|
|
@@ -624,7 +733,6 @@ var WebModelContextTesting = class {
|
|
|
624
733
|
*/
|
|
625
734
|
setMockToolResponse(toolName, response) {
|
|
626
735
|
this.mockResponses.set(toolName, response);
|
|
627
|
-
console.log(`[Model Context Testing] Mock response set for tool: ${toolName}`);
|
|
628
736
|
}
|
|
629
737
|
/**
|
|
630
738
|
* Clears the mock response for a specific tool (polyfill extension).
|
|
@@ -633,14 +741,12 @@ var WebModelContextTesting = class {
|
|
|
633
741
|
*/
|
|
634
742
|
clearMockToolResponse(toolName) {
|
|
635
743
|
this.mockResponses.delete(toolName);
|
|
636
|
-
console.log(`[Model Context Testing] Mock response cleared for tool: ${toolName}`);
|
|
637
744
|
}
|
|
638
745
|
/**
|
|
639
746
|
* Clears all mock tool responses (polyfill extension).
|
|
640
747
|
*/
|
|
641
748
|
clearAllMockToolResponses() {
|
|
642
749
|
this.mockResponses.clear();
|
|
643
|
-
console.log("[Model Context Testing] All mock responses cleared");
|
|
644
750
|
}
|
|
645
751
|
/**
|
|
646
752
|
* Gets the current tools registered in the system (polyfill extension).
|
|
@@ -657,7 +763,6 @@ var WebModelContextTesting = class {
|
|
|
657
763
|
reset() {
|
|
658
764
|
this.clearToolCalls();
|
|
659
765
|
this.clearAllMockToolResponses();
|
|
660
|
-
console.log("[Model Context Testing] Testing state reset");
|
|
661
766
|
}
|
|
662
767
|
};
|
|
663
768
|
/**
|
|
@@ -763,14 +868,13 @@ var WebModelContext = class {
|
|
|
763
868
|
* @throws {Error} If a name/uri collides with existing dynamic items
|
|
764
869
|
*/
|
|
765
870
|
provideContext(context) {
|
|
766
|
-
const toolCount = context.tools?.length ?? 0;
|
|
767
|
-
const resourceCount = context.resources?.length ?? 0;
|
|
768
|
-
const promptCount = context.prompts?.length ?? 0;
|
|
769
|
-
console.log(`[Web Model Context] provideContext: ${toolCount} tools, ${resourceCount} resources, ${promptCount} prompts`);
|
|
770
871
|
this.provideContextTools.clear();
|
|
771
872
|
this.provideContextResources.clear();
|
|
772
873
|
this.provideContextPrompts.clear();
|
|
773
874
|
for (const tool of context.tools ?? []) {
|
|
875
|
+
if (tool.name.startsWith("_")) logger$1.warn(`⚠️ Warning: Tool name "${tool.name}" starts with underscore. This may cause compatibility issues with some MCP clients. Consider using a letter as the first character.`);
|
|
876
|
+
if (/^[0-9]/.test(tool.name)) logger$1.warn(`⚠️ Warning: Tool name "${tool.name}" starts with a number. This may cause compatibility issues. Consider using a letter as the first character.`);
|
|
877
|
+
if (tool.name.startsWith("-")) logger$1.warn(`⚠️ Warning: Tool name "${tool.name}" starts with hyphen. This may cause compatibility issues. Consider using a letter as the first character.`);
|
|
774
878
|
if (this.dynamicTools.has(tool.name)) throw new Error(`[Web Model Context] Tool name collision: "${tool.name}" is already registered via registerTool(). Please use a different name or unregister the dynamic tool first.`);
|
|
775
879
|
const { jsonSchema: inputJson, zodValidator: inputZod } = normalizeSchema(tool.inputSchema);
|
|
776
880
|
const normalizedOutput = tool.outputSchema ? normalizeSchema(tool.outputSchema) : null;
|
|
@@ -808,11 +912,11 @@ var WebModelContext = class {
|
|
|
808
912
|
* @private
|
|
809
913
|
*/
|
|
810
914
|
validateResource(resource) {
|
|
811
|
-
const templateParamRegex = /\{([^}]
|
|
915
|
+
const templateParamRegex = /\{([^}]{1,100})\}/g;
|
|
812
916
|
const templateParams = [];
|
|
813
917
|
for (const match of resource.uri.matchAll(templateParamRegex)) {
|
|
814
918
|
const paramName = match[1];
|
|
815
|
-
|
|
919
|
+
templateParams.push(paramName);
|
|
816
920
|
}
|
|
817
921
|
return {
|
|
818
922
|
uri: resource.uri,
|
|
@@ -853,11 +957,13 @@ var WebModelContext = class {
|
|
|
853
957
|
* @throws {Error} If tool name collides with existing tools
|
|
854
958
|
*/
|
|
855
959
|
registerTool(tool) {
|
|
856
|
-
|
|
960
|
+
if (tool.name.startsWith("_")) logger$1.warn(`⚠️ Warning: Tool name "${tool.name}" starts with underscore. This may cause compatibility issues with some MCP clients. Consider using a letter as the first character.`);
|
|
961
|
+
if (/^[0-9]/.test(tool.name)) logger$1.warn(`⚠️ Warning: Tool name "${tool.name}" starts with a number. This may cause compatibility issues. Consider using a letter as the first character.`);
|
|
962
|
+
if (tool.name.startsWith("-")) logger$1.warn(`⚠️ Warning: Tool name "${tool.name}" starts with hyphen. This may cause compatibility issues. Consider using a letter as the first character.`);
|
|
857
963
|
const now = Date.now();
|
|
858
964
|
const lastRegistration = this.toolRegistrationTimestamps.get(tool.name);
|
|
859
965
|
if (lastRegistration && now - lastRegistration < RAPID_DUPLICATE_WINDOW_MS) {
|
|
860
|
-
|
|
966
|
+
logger$1.warn(`Tool "${tool.name}" registered multiple times within ${RAPID_DUPLICATE_WINDOW_MS}ms. This is likely due to React Strict Mode double-mounting. Ignoring duplicate registration.`);
|
|
861
967
|
const existingUnregister = this.toolUnregisterFunctions.get(tool.name);
|
|
862
968
|
if (existingUnregister) return { unregister: existingUnregister };
|
|
863
969
|
}
|
|
@@ -880,10 +986,9 @@ var WebModelContext = class {
|
|
|
880
986
|
this.updateBridgeTools();
|
|
881
987
|
this.scheduleListChanged("tools");
|
|
882
988
|
const unregisterFn = () => {
|
|
883
|
-
console.log(`[Web Model Context] Unregistering tool: ${tool.name}`);
|
|
884
989
|
if (this.provideContextTools.has(tool.name)) throw new Error(`[Web Model Context] Cannot unregister tool "${tool.name}": This tool was registered via provideContext(). Use provideContext() to update the base tool set.`);
|
|
885
990
|
if (!this.dynamicTools.has(tool.name)) {
|
|
886
|
-
|
|
991
|
+
logger$1.warn(`Tool "${tool.name}" is not registered, ignoring unregister call`);
|
|
887
992
|
return;
|
|
888
993
|
}
|
|
889
994
|
this.dynamicTools.delete(tool.name);
|
|
@@ -904,11 +1009,10 @@ var WebModelContext = class {
|
|
|
904
1009
|
* @throws {Error} If resource URI collides with existing resources
|
|
905
1010
|
*/
|
|
906
1011
|
registerResource(resource) {
|
|
907
|
-
console.log(`[Web Model Context] Registering resource dynamically: ${resource.uri}`);
|
|
908
1012
|
const now = Date.now();
|
|
909
1013
|
const lastRegistration = this.resourceRegistrationTimestamps.get(resource.uri);
|
|
910
1014
|
if (lastRegistration && now - lastRegistration < RAPID_DUPLICATE_WINDOW_MS) {
|
|
911
|
-
|
|
1015
|
+
logger$1.warn(`Resource "${resource.uri}" registered multiple times within ${RAPID_DUPLICATE_WINDOW_MS}ms. This is likely due to React Strict Mode double-mounting. Ignoring duplicate registration.`);
|
|
912
1016
|
const existingUnregister = this.resourceUnregisterFunctions.get(resource.uri);
|
|
913
1017
|
if (existingUnregister) return { unregister: existingUnregister };
|
|
914
1018
|
}
|
|
@@ -920,10 +1024,9 @@ var WebModelContext = class {
|
|
|
920
1024
|
this.updateBridgeResources();
|
|
921
1025
|
this.scheduleListChanged("resources");
|
|
922
1026
|
const unregisterFn = () => {
|
|
923
|
-
console.log(`[Web Model Context] Unregistering resource: ${resource.uri}`);
|
|
924
1027
|
if (this.provideContextResources.has(resource.uri)) throw new Error(`[Web Model Context] Cannot unregister resource "${resource.uri}": This resource was registered via provideContext(). Use provideContext() to update the base resource set.`);
|
|
925
1028
|
if (!this.dynamicResources.has(resource.uri)) {
|
|
926
|
-
|
|
1029
|
+
logger$1.warn(`Resource "${resource.uri}" is not registered, ignoring unregister call`);
|
|
927
1030
|
return;
|
|
928
1031
|
}
|
|
929
1032
|
this.dynamicResources.delete(resource.uri);
|
|
@@ -942,11 +1045,10 @@ var WebModelContext = class {
|
|
|
942
1045
|
* @param {string} uri - URI of the resource to unregister
|
|
943
1046
|
*/
|
|
944
1047
|
unregisterResource(uri) {
|
|
945
|
-
console.log(`[Web Model Context] Unregistering resource: ${uri}`);
|
|
946
1048
|
const inProvideContext = this.provideContextResources.has(uri);
|
|
947
1049
|
const inDynamic = this.dynamicResources.has(uri);
|
|
948
1050
|
if (!inProvideContext && !inDynamic) {
|
|
949
|
-
|
|
1051
|
+
logger$1.warn(`Resource "${uri}" is not registered, ignoring unregister call`);
|
|
950
1052
|
return;
|
|
951
1053
|
}
|
|
952
1054
|
if (inProvideContext) this.provideContextResources.delete(uri);
|
|
@@ -995,11 +1097,10 @@ var WebModelContext = class {
|
|
|
995
1097
|
* @throws {Error} If prompt name collides with existing prompts
|
|
996
1098
|
*/
|
|
997
1099
|
registerPrompt(prompt) {
|
|
998
|
-
console.log(`[Web Model Context] Registering prompt dynamically: ${prompt.name}`);
|
|
999
1100
|
const now = Date.now();
|
|
1000
1101
|
const lastRegistration = this.promptRegistrationTimestamps.get(prompt.name);
|
|
1001
1102
|
if (lastRegistration && now - lastRegistration < RAPID_DUPLICATE_WINDOW_MS) {
|
|
1002
|
-
|
|
1103
|
+
logger$1.warn(`Prompt "${prompt.name}" registered multiple times within ${RAPID_DUPLICATE_WINDOW_MS}ms. This is likely due to React Strict Mode double-mounting. Ignoring duplicate registration.`);
|
|
1003
1104
|
const existingUnregister = this.promptUnregisterFunctions.get(prompt.name);
|
|
1004
1105
|
if (existingUnregister) return { unregister: existingUnregister };
|
|
1005
1106
|
}
|
|
@@ -1011,10 +1112,9 @@ var WebModelContext = class {
|
|
|
1011
1112
|
this.updateBridgePrompts();
|
|
1012
1113
|
this.scheduleListChanged("prompts");
|
|
1013
1114
|
const unregisterFn = () => {
|
|
1014
|
-
console.log(`[Web Model Context] Unregistering prompt: ${prompt.name}`);
|
|
1015
1115
|
if (this.provideContextPrompts.has(prompt.name)) throw new Error(`[Web Model Context] Cannot unregister prompt "${prompt.name}": This prompt was registered via provideContext(). Use provideContext() to update the base prompt set.`);
|
|
1016
1116
|
if (!this.dynamicPrompts.has(prompt.name)) {
|
|
1017
|
-
|
|
1117
|
+
logger$1.warn(`Prompt "${prompt.name}" is not registered, ignoring unregister call`);
|
|
1018
1118
|
return;
|
|
1019
1119
|
}
|
|
1020
1120
|
this.dynamicPrompts.delete(prompt.name);
|
|
@@ -1033,11 +1133,10 @@ var WebModelContext = class {
|
|
|
1033
1133
|
* @param {string} name - Name of the prompt to unregister
|
|
1034
1134
|
*/
|
|
1035
1135
|
unregisterPrompt(name) {
|
|
1036
|
-
console.log(`[Web Model Context] Unregistering prompt: ${name}`);
|
|
1037
1136
|
const inProvideContext = this.provideContextPrompts.has(name);
|
|
1038
1137
|
const inDynamic = this.dynamicPrompts.has(name);
|
|
1039
1138
|
if (!inProvideContext && !inDynamic) {
|
|
1040
|
-
|
|
1139
|
+
logger$1.warn(`Prompt "${name}" is not registered, ignoring unregister call`);
|
|
1041
1140
|
return;
|
|
1042
1141
|
}
|
|
1043
1142
|
if (inProvideContext) this.provideContextPrompts.delete(name);
|
|
@@ -1073,11 +1172,10 @@ var WebModelContext = class {
|
|
|
1073
1172
|
* @param {string} name - Name of the tool to unregister
|
|
1074
1173
|
*/
|
|
1075
1174
|
unregisterTool(name) {
|
|
1076
|
-
console.log(`[Web Model Context] Unregistering tool: ${name}`);
|
|
1077
1175
|
const inProvideContext = this.provideContextTools.has(name);
|
|
1078
1176
|
const inDynamic = this.dynamicTools.has(name);
|
|
1079
1177
|
if (!inProvideContext && !inDynamic) {
|
|
1080
|
-
|
|
1178
|
+
logger$1.warn(`Tool "${name}" is not registered, ignoring unregister call`);
|
|
1081
1179
|
return;
|
|
1082
1180
|
}
|
|
1083
1181
|
if (inProvideContext) this.provideContextTools.delete(name);
|
|
@@ -1094,7 +1192,6 @@ var WebModelContext = class {
|
|
|
1094
1192
|
* Removes all tools, resources, and prompts registered via provideContext() and register* methods.
|
|
1095
1193
|
*/
|
|
1096
1194
|
clearContext() {
|
|
1097
|
-
console.log("[Web Model Context] Clearing all context (tools, resources, prompts)");
|
|
1098
1195
|
this.provideContextTools.clear();
|
|
1099
1196
|
this.dynamicTools.clear();
|
|
1100
1197
|
this.toolRegistrationTimestamps.clear();
|
|
@@ -1124,7 +1221,6 @@ var WebModelContext = class {
|
|
|
1124
1221
|
this.bridge.tools.clear();
|
|
1125
1222
|
for (const [name, tool] of this.provideContextTools) this.bridge.tools.set(name, tool);
|
|
1126
1223
|
for (const [name, tool] of this.dynamicTools) this.bridge.tools.set(name, tool);
|
|
1127
|
-
console.log(`[Web Model Context] Updated bridge with ${this.provideContextTools.size} base tools + ${this.dynamicTools.size} dynamic tools = ${this.bridge.tools.size} total`);
|
|
1128
1224
|
}
|
|
1129
1225
|
/**
|
|
1130
1226
|
* Notifies all servers and testing callbacks that the tools list has changed.
|
|
@@ -1152,7 +1248,6 @@ var WebModelContext = class {
|
|
|
1152
1248
|
this.bridge.resources.clear();
|
|
1153
1249
|
for (const [uri, resource] of this.provideContextResources) this.bridge.resources.set(uri, resource);
|
|
1154
1250
|
for (const [uri, resource] of this.dynamicResources) this.bridge.resources.set(uri, resource);
|
|
1155
|
-
console.log(`[Web Model Context] Updated bridge with ${this.provideContextResources.size} base resources + ${this.dynamicResources.size} dynamic resources = ${this.bridge.resources.size} total`);
|
|
1156
1251
|
}
|
|
1157
1252
|
/**
|
|
1158
1253
|
* Notifies all servers that the resources list has changed.
|
|
@@ -1178,7 +1273,6 @@ var WebModelContext = class {
|
|
|
1178
1273
|
this.bridge.prompts.clear();
|
|
1179
1274
|
for (const [name, prompt] of this.provideContextPrompts) this.bridge.prompts.set(name, prompt);
|
|
1180
1275
|
for (const [name, prompt] of this.dynamicPrompts) this.bridge.prompts.set(name, prompt);
|
|
1181
|
-
console.log(`[Web Model Context] Updated bridge with ${this.provideContextPrompts.size} base prompts + ${this.dynamicPrompts.size} dynamic prompts = ${this.bridge.prompts.size} total`);
|
|
1182
1276
|
}
|
|
1183
1277
|
/**
|
|
1184
1278
|
* Notifies all servers that the prompts list has changed.
|
|
@@ -1221,7 +1315,7 @@ var WebModelContext = class {
|
|
|
1221
1315
|
break;
|
|
1222
1316
|
default: {
|
|
1223
1317
|
const _exhaustive = listType;
|
|
1224
|
-
|
|
1318
|
+
logger$1.error(`Unknown list type: ${_exhaustive}`);
|
|
1225
1319
|
}
|
|
1226
1320
|
}
|
|
1227
1321
|
});
|
|
@@ -1236,23 +1330,34 @@ var WebModelContext = class {
|
|
|
1236
1330
|
* @internal
|
|
1237
1331
|
*/
|
|
1238
1332
|
async readResource(uri) {
|
|
1239
|
-
console.log(`[Web Model Context] Reading resource: ${uri}`);
|
|
1240
1333
|
const staticResource = this.bridge.resources.get(uri);
|
|
1241
1334
|
if (staticResource && !staticResource.isTemplate) try {
|
|
1242
|
-
|
|
1335
|
+
let parsedUri;
|
|
1336
|
+
try {
|
|
1337
|
+
parsedUri = new URL(uri);
|
|
1338
|
+
} catch {
|
|
1339
|
+
parsedUri = new URL(`custom-scheme:///${encodeURIComponent(uri)}`);
|
|
1340
|
+
parsedUri.originalUri = uri;
|
|
1341
|
+
}
|
|
1243
1342
|
return await staticResource.read(parsedUri);
|
|
1244
1343
|
} catch (error) {
|
|
1245
|
-
|
|
1344
|
+
logger$1.error(`Error reading resource ${uri}:`, error);
|
|
1246
1345
|
throw error;
|
|
1247
1346
|
}
|
|
1248
1347
|
for (const resource of this.bridge.resources.values()) {
|
|
1249
1348
|
if (!resource.isTemplate) continue;
|
|
1250
1349
|
const params = this.matchUriTemplate(resource.uri, uri);
|
|
1251
1350
|
if (params) try {
|
|
1252
|
-
|
|
1351
|
+
let parsedUri;
|
|
1352
|
+
try {
|
|
1353
|
+
parsedUri = new URL(uri);
|
|
1354
|
+
} catch {
|
|
1355
|
+
parsedUri = new URL(`custom-scheme:///${encodeURIComponent(uri)}`);
|
|
1356
|
+
parsedUri.originalUri = uri;
|
|
1357
|
+
}
|
|
1253
1358
|
return await resource.read(parsedUri, params);
|
|
1254
1359
|
} catch (error) {
|
|
1255
|
-
|
|
1360
|
+
logger$1.error(`Error reading resource ${uri}:`, error);
|
|
1256
1361
|
throw error;
|
|
1257
1362
|
}
|
|
1258
1363
|
}
|
|
@@ -1282,8 +1387,7 @@ var WebModelContext = class {
|
|
|
1282
1387
|
const params = {};
|
|
1283
1388
|
for (let i = 0; i < paramNames.length; i++) {
|
|
1284
1389
|
const paramName = paramNames[i];
|
|
1285
|
-
|
|
1286
|
-
if (paramName !== void 0 && paramValue !== void 0) params[paramName] = paramValue;
|
|
1390
|
+
params[paramName] = match[i + 1];
|
|
1287
1391
|
}
|
|
1288
1392
|
return params;
|
|
1289
1393
|
}
|
|
@@ -1297,27 +1401,26 @@ var WebModelContext = class {
|
|
|
1297
1401
|
* @internal
|
|
1298
1402
|
*/
|
|
1299
1403
|
async getPrompt(name, args) {
|
|
1300
|
-
console.log(`[Web Model Context] Getting prompt: ${name}`);
|
|
1301
1404
|
const prompt = this.bridge.prompts.get(name);
|
|
1302
1405
|
if (!prompt) throw new Error(`Prompt not found: ${name}`);
|
|
1303
1406
|
if (prompt.argsValidator && args) {
|
|
1304
1407
|
const validation = validateWithZod(args, prompt.argsValidator);
|
|
1305
1408
|
if (!validation.success) {
|
|
1306
|
-
|
|
1409
|
+
logger$1.error(`Argument validation failed for prompt ${name}:`, validation.error);
|
|
1307
1410
|
throw new Error(`Argument validation error for prompt "${name}":\n${validation.error}`);
|
|
1308
1411
|
}
|
|
1309
1412
|
}
|
|
1310
1413
|
try {
|
|
1311
1414
|
return await prompt.get(args ?? {});
|
|
1312
1415
|
} catch (error) {
|
|
1313
|
-
|
|
1416
|
+
logger$1.error(`Error getting prompt ${name}:`, error);
|
|
1314
1417
|
throw error;
|
|
1315
1418
|
}
|
|
1316
1419
|
}
|
|
1317
1420
|
/**
|
|
1318
1421
|
* Executes a tool with validation and event dispatch.
|
|
1319
1422
|
* Follows this sequence:
|
|
1320
|
-
* 1. Validates input arguments against schema
|
|
1423
|
+
* 1. Validates input arguments against schema (unless skipValidation is true)
|
|
1321
1424
|
* 2. Records tool call in testing API (if available)
|
|
1322
1425
|
* 3. Checks for mock response (if testing)
|
|
1323
1426
|
* 4. Dispatches 'toolcall' event to listeners
|
|
@@ -1326,53 +1429,52 @@ var WebModelContext = class {
|
|
|
1326
1429
|
*
|
|
1327
1430
|
* @param {string} toolName - Name of the tool to execute
|
|
1328
1431
|
* @param {Record<string, unknown>} args - Arguments to pass to the tool
|
|
1432
|
+
* @param {Object} [options] - Execution options
|
|
1433
|
+
* @param {boolean} [options.skipValidation] - Skip input validation (used when MCP SDK already validated)
|
|
1329
1434
|
* @returns {Promise<ToolResponse>} The tool's response
|
|
1330
1435
|
* @throws {Error} If tool is not found
|
|
1331
1436
|
* @internal
|
|
1332
1437
|
*/
|
|
1333
|
-
async executeTool(toolName, args) {
|
|
1438
|
+
async executeTool(toolName, args, options) {
|
|
1334
1439
|
const tool = this.bridge.tools.get(toolName);
|
|
1335
1440
|
if (!tool) throw new Error(`Tool not found: ${toolName}`);
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1441
|
+
let validatedArgs;
|
|
1442
|
+
if (options?.skipValidation) validatedArgs = args;
|
|
1443
|
+
else {
|
|
1444
|
+
const validation = validateWithZod(args, tool.inputValidator);
|
|
1445
|
+
if (!validation.success) {
|
|
1446
|
+
logger$1.error(`Input validation failed for ${toolName}:`, validation.error);
|
|
1447
|
+
return {
|
|
1448
|
+
content: [{
|
|
1449
|
+
type: "text",
|
|
1450
|
+
text: `Input validation error for tool "${toolName}":\n${validation.error}`
|
|
1451
|
+
}],
|
|
1452
|
+
isError: true
|
|
1453
|
+
};
|
|
1454
|
+
}
|
|
1455
|
+
validatedArgs = validation.data;
|
|
1347
1456
|
}
|
|
1348
|
-
const validatedArgs = validation.data;
|
|
1349
1457
|
if (this.testingAPI) this.testingAPI.recordToolCall(toolName, validatedArgs);
|
|
1350
1458
|
if (this.testingAPI?.hasMockResponse(toolName)) {
|
|
1351
1459
|
const mockResponse = this.testingAPI.getMockResponse(toolName);
|
|
1352
|
-
if (mockResponse)
|
|
1353
|
-
console.log(`[Web Model Context] Returning mock response for tool: ${toolName}`);
|
|
1354
|
-
return mockResponse;
|
|
1355
|
-
}
|
|
1460
|
+
if (mockResponse) return mockResponse;
|
|
1356
1461
|
}
|
|
1357
1462
|
const event = new WebToolCallEvent(toolName, validatedArgs);
|
|
1358
1463
|
this.dispatchEvent(event);
|
|
1359
1464
|
if (event.defaultPrevented && event.hasResponse()) {
|
|
1360
1465
|
const response = event.getResponse();
|
|
1361
|
-
if (response)
|
|
1362
|
-
console.log(`[Web Model Context] Tool ${toolName} handled by event listener`);
|
|
1363
|
-
return response;
|
|
1364
|
-
}
|
|
1466
|
+
if (response) return response;
|
|
1365
1467
|
}
|
|
1366
|
-
console.log(`[Web Model Context] Executing tool: ${toolName}`);
|
|
1367
1468
|
try {
|
|
1368
1469
|
const response = await tool.execute(validatedArgs);
|
|
1369
1470
|
if (tool.outputValidator && response.structuredContent) {
|
|
1370
1471
|
const outputValidation = validateWithZod(response.structuredContent, tool.outputValidator);
|
|
1371
|
-
if (!outputValidation.success)
|
|
1472
|
+
if (!outputValidation.success) logger$1.warn(`Output validation failed for ${toolName}:`, outputValidation.error);
|
|
1372
1473
|
}
|
|
1474
|
+
if (response.metadata && typeof response.metadata === "object" && "willNavigate" in response.metadata) logger$1.info(`Tool "${toolName}" will trigger navigation`, response.metadata);
|
|
1373
1475
|
return response;
|
|
1374
1476
|
} catch (error) {
|
|
1375
|
-
|
|
1477
|
+
logger$1.error(`Error executing tool ${toolName}:`, error);
|
|
1376
1478
|
return {
|
|
1377
1479
|
content: [{
|
|
1378
1480
|
type: "text",
|
|
@@ -1406,7 +1508,6 @@ var WebModelContext = class {
|
|
|
1406
1508
|
* @returns {Promise<SamplingResult>} The LLM completion result
|
|
1407
1509
|
*/
|
|
1408
1510
|
async createMessage(params) {
|
|
1409
|
-
console.log("[Web Model Context] Requesting sampling from client");
|
|
1410
1511
|
const underlyingServer = this.bridge.tabServer.server;
|
|
1411
1512
|
if (!underlyingServer?.createMessage) throw new Error("Sampling is not supported: no connected client with sampling capability");
|
|
1412
1513
|
return underlyingServer.createMessage(params);
|
|
@@ -1419,7 +1520,6 @@ var WebModelContext = class {
|
|
|
1419
1520
|
* @returns {Promise<ElicitationResult>} The user's response
|
|
1420
1521
|
*/
|
|
1421
1522
|
async elicitInput(params) {
|
|
1422
|
-
console.log("[Web Model Context] Requesting elicitation from client");
|
|
1423
1523
|
const underlyingServer = this.bridge.tabServer.server;
|
|
1424
1524
|
if (!underlyingServer?.elicitInput) throw new Error("Elicitation is not supported: no connected client with elicitation capability");
|
|
1425
1525
|
return underlyingServer.elicitInput(params);
|
|
@@ -1434,16 +1534,13 @@ var WebModelContext = class {
|
|
|
1434
1534
|
* @returns {MCPBridge} The initialized MCP bridge
|
|
1435
1535
|
*/
|
|
1436
1536
|
function initializeMCPBridge(options) {
|
|
1437
|
-
console.log("[Web Model Context] Initializing MCP bridge");
|
|
1438
1537
|
const hostname = window.location.hostname || "localhost";
|
|
1439
1538
|
const transportOptions = options?.transport;
|
|
1440
1539
|
const setupServerHandlers = (server, bridge$1) => {
|
|
1441
1540
|
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
1442
|
-
console.log("[MCP Bridge] Handling list_tools request");
|
|
1443
1541
|
return { tools: bridge$1.modelContext.listTools() };
|
|
1444
1542
|
});
|
|
1445
1543
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
1446
|
-
console.log(`[MCP Bridge] Handling call_tool request: ${request.params.name}`);
|
|
1447
1544
|
const toolName = request.params.name;
|
|
1448
1545
|
const args = request.params.arguments || {};
|
|
1449
1546
|
try {
|
|
@@ -1454,40 +1551,35 @@ function initializeMCPBridge(options) {
|
|
|
1454
1551
|
...response.structuredContent && { structuredContent: response.structuredContent }
|
|
1455
1552
|
};
|
|
1456
1553
|
} catch (error) {
|
|
1457
|
-
|
|
1554
|
+
bridgeLogger.error(`Error calling tool ${toolName}:`, error);
|
|
1458
1555
|
throw error;
|
|
1459
1556
|
}
|
|
1460
1557
|
});
|
|
1461
1558
|
server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
1462
|
-
console.log("[MCP Bridge] Handling list_resources request");
|
|
1463
1559
|
return { resources: bridge$1.modelContext.listResources() };
|
|
1464
1560
|
});
|
|
1465
1561
|
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
1466
|
-
console.log(`[MCP Bridge] Handling read_resource request: ${request.params.uri}`);
|
|
1467
1562
|
try {
|
|
1468
1563
|
return await bridge$1.modelContext.readResource(request.params.uri);
|
|
1469
1564
|
} catch (error) {
|
|
1470
|
-
|
|
1565
|
+
bridgeLogger.error(`Error reading resource ${request.params.uri}:`, error);
|
|
1471
1566
|
throw error;
|
|
1472
1567
|
}
|
|
1473
1568
|
});
|
|
1474
1569
|
server.setRequestHandler(ListPromptsRequestSchema, async () => {
|
|
1475
|
-
console.log("[MCP Bridge] Handling list_prompts request");
|
|
1476
1570
|
return { prompts: bridge$1.modelContext.listPrompts() };
|
|
1477
1571
|
});
|
|
1478
1572
|
server.setRequestHandler(GetPromptRequestSchema, async (request) => {
|
|
1479
|
-
console.log(`[MCP Bridge] Handling get_prompt request: ${request.params.name}`);
|
|
1480
1573
|
try {
|
|
1481
1574
|
return await bridge$1.modelContext.getPrompt(request.params.name, request.params.arguments);
|
|
1482
1575
|
} catch (error) {
|
|
1483
|
-
|
|
1576
|
+
bridgeLogger.error(`Error getting prompt ${request.params.name}:`, error);
|
|
1484
1577
|
throw error;
|
|
1485
1578
|
}
|
|
1486
1579
|
});
|
|
1487
1580
|
};
|
|
1488
1581
|
const customTransport = transportOptions?.create?.();
|
|
1489
1582
|
if (customTransport) {
|
|
1490
|
-
console.log("[Web Model Context] Using custom transport");
|
|
1491
1583
|
const server = new Server({
|
|
1492
1584
|
name: hostname,
|
|
1493
1585
|
version: "1.0.0"
|
|
@@ -1507,10 +1599,8 @@ function initializeMCPBridge(options) {
|
|
|
1507
1599
|
bridge$1.modelContext = new WebModelContext(bridge$1);
|
|
1508
1600
|
setupServerHandlers(server, bridge$1);
|
|
1509
1601
|
server.connect(customTransport);
|
|
1510
|
-
console.log("[Web Model Context] MCP server connected with custom transport");
|
|
1511
1602
|
return bridge$1;
|
|
1512
1603
|
}
|
|
1513
|
-
console.log("[Web Model Context] Using dual-server mode");
|
|
1514
1604
|
const tabServerEnabled = transportOptions?.tabServer !== false;
|
|
1515
1605
|
const tabServer = new Server({
|
|
1516
1606
|
name: `${hostname}-tab`,
|
|
@@ -1537,12 +1627,10 @@ function initializeMCPBridge(options) {
|
|
|
1537
1627
|
...restTabServerOptions
|
|
1538
1628
|
});
|
|
1539
1629
|
tabServer.connect(tabTransport);
|
|
1540
|
-
console.log("[Web Model Context] Tab server connected");
|
|
1541
1630
|
}
|
|
1542
1631
|
const isInIframe = typeof window !== "undefined" && window.parent !== window;
|
|
1543
1632
|
const iframeServerConfig = transportOptions?.iframeServer;
|
|
1544
1633
|
if (iframeServerConfig !== false && (iframeServerConfig !== void 0 || isInIframe)) {
|
|
1545
|
-
console.log("[Web Model Context] Enabling iframe server");
|
|
1546
1634
|
const iframeServer = new Server({
|
|
1547
1635
|
name: `${hostname}-iframe`,
|
|
1548
1636
|
version: "1.0.0"
|
|
@@ -1559,7 +1647,6 @@ function initializeMCPBridge(options) {
|
|
|
1559
1647
|
});
|
|
1560
1648
|
iframeServer.connect(iframeTransport);
|
|
1561
1649
|
bridge.iframeServer = iframeServer;
|
|
1562
|
-
console.log("[Web Model Context] Iframe server connected");
|
|
1563
1650
|
}
|
|
1564
1651
|
return bridge;
|
|
1565
1652
|
}
|
|
@@ -1584,8 +1671,9 @@ function initializeMCPBridge(options) {
|
|
|
1584
1671
|
* ```
|
|
1585
1672
|
*/
|
|
1586
1673
|
function initializeWebModelContext(options) {
|
|
1674
|
+
/* c8 ignore next 4 */
|
|
1587
1675
|
if (typeof window === "undefined") {
|
|
1588
|
-
|
|
1676
|
+
logger$1.warn("Not in browser environment, skipping initialization");
|
|
1589
1677
|
return;
|
|
1590
1678
|
}
|
|
1591
1679
|
const effectiveOptions = options ?? window.__webModelContextOptions;
|
|
@@ -1594,12 +1682,12 @@ function initializeWebModelContext(options) {
|
|
|
1594
1682
|
const nativeContext = window.navigator.modelContext;
|
|
1595
1683
|
const nativeTesting = window.navigator.modelContextTesting;
|
|
1596
1684
|
if (!nativeContext || !nativeTesting) {
|
|
1597
|
-
|
|
1685
|
+
logger$1.error("Native API detection mismatch");
|
|
1598
1686
|
return;
|
|
1599
1687
|
}
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1688
|
+
logger$1.info("✅ Native Chromium API detected");
|
|
1689
|
+
logger$1.info(" Using native implementation with MCP bridge synchronization");
|
|
1690
|
+
logger$1.info(" Native API will automatically collect tools from embedded iframes");
|
|
1603
1691
|
try {
|
|
1604
1692
|
const bridge = initializeMCPBridge(effectiveOptions);
|
|
1605
1693
|
bridge.modelContext = new NativeModelContextAdapter(bridge, nativeContext, nativeTesting);
|
|
@@ -1609,29 +1697,29 @@ function initializeWebModelContext(options) {
|
|
|
1609
1697
|
writable: false,
|
|
1610
1698
|
configurable: true
|
|
1611
1699
|
});
|
|
1612
|
-
|
|
1613
|
-
|
|
1700
|
+
logger$1.info("✅ MCP bridge synced with native API");
|
|
1701
|
+
logger$1.info(" MCP clients will receive automatic tool updates from native registry");
|
|
1614
1702
|
} catch (error) {
|
|
1615
|
-
|
|
1703
|
+
logger$1.error("Failed to initialize native adapter:", error);
|
|
1616
1704
|
throw error;
|
|
1617
1705
|
}
|
|
1618
1706
|
return;
|
|
1619
1707
|
}
|
|
1620
1708
|
if (native.hasNativeContext && !native.hasNativeTesting) {
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1709
|
+
logger$1.warn("Partial native API detected");
|
|
1710
|
+
logger$1.warn(" navigator.modelContext exists but navigator.modelContextTesting is missing");
|
|
1711
|
+
logger$1.warn(" Cannot sync with native API. Please enable experimental features:");
|
|
1712
|
+
logger$1.warn(" - Navigate to chrome://flags");
|
|
1713
|
+
logger$1.warn(" - Enable \"Experimental Web Platform Features\"");
|
|
1714
|
+
logger$1.warn(" - Or launch with: --enable-experimental-web-platform-features");
|
|
1715
|
+
logger$1.warn(" Skipping initialization to avoid conflicts");
|
|
1628
1716
|
return;
|
|
1629
1717
|
}
|
|
1630
1718
|
if (window.navigator.modelContext) {
|
|
1631
|
-
|
|
1719
|
+
logger$1.warn("window.navigator.modelContext already exists, skipping initialization");
|
|
1632
1720
|
return;
|
|
1633
1721
|
}
|
|
1634
|
-
|
|
1722
|
+
logger$1.info("Native API not detected, installing polyfill");
|
|
1635
1723
|
try {
|
|
1636
1724
|
const bridge = initializeMCPBridge(effectiveOptions);
|
|
1637
1725
|
Object.defineProperty(window.navigator, "modelContext", {
|
|
@@ -1644,12 +1732,12 @@ function initializeWebModelContext(options) {
|
|
|
1644
1732
|
writable: false,
|
|
1645
1733
|
configurable: true
|
|
1646
1734
|
});
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1735
|
+
logger$1.info("✅ window.navigator.modelContext initialized successfully");
|
|
1736
|
+
testingLogger.info("Installing polyfill");
|
|
1737
|
+
testingLogger.info(" 💡 To use the native implementation in Chromium:");
|
|
1738
|
+
testingLogger.info(" - Navigate to chrome://flags");
|
|
1739
|
+
testingLogger.info(" - Enable \"Experimental Web Platform Features\"");
|
|
1740
|
+
testingLogger.info(" - Or launch with: --enable-experimental-web-platform-features");
|
|
1653
1741
|
const testingAPI = new WebModelContextTesting(bridge);
|
|
1654
1742
|
bridge.modelContextTesting = testingAPI;
|
|
1655
1743
|
bridge.modelContext.setTestingAPI(testingAPI);
|
|
@@ -1658,9 +1746,9 @@ function initializeWebModelContext(options) {
|
|
|
1658
1746
|
writable: false,
|
|
1659
1747
|
configurable: true
|
|
1660
1748
|
});
|
|
1661
|
-
|
|
1749
|
+
testingLogger.info("✅ Polyfill installed at window.navigator.modelContextTesting");
|
|
1662
1750
|
} catch (error) {
|
|
1663
|
-
|
|
1751
|
+
logger$1.error("Failed to initialize:", error);
|
|
1664
1752
|
throw error;
|
|
1665
1753
|
}
|
|
1666
1754
|
}
|
|
@@ -1677,21 +1765,23 @@ function initializeWebModelContext(options) {
|
|
|
1677
1765
|
* ```
|
|
1678
1766
|
*/
|
|
1679
1767
|
function cleanupWebModelContext() {
|
|
1768
|
+
/* c8 ignore next */
|
|
1680
1769
|
if (typeof window === "undefined") return;
|
|
1681
1770
|
if (window.__mcpBridge) try {
|
|
1682
1771
|
window.__mcpBridge.tabServer.close();
|
|
1683
1772
|
if (window.__mcpBridge.iframeServer) window.__mcpBridge.iframeServer.close();
|
|
1684
1773
|
} catch (error) {
|
|
1685
|
-
|
|
1774
|
+
logger$1.warn("Error closing MCP servers:", error);
|
|
1686
1775
|
}
|
|
1687
1776
|
delete window.navigator.modelContext;
|
|
1688
1777
|
delete window.navigator.modelContextTesting;
|
|
1689
1778
|
delete window.__mcpBridge;
|
|
1690
|
-
|
|
1779
|
+
logger$1.info("Cleaned up");
|
|
1691
1780
|
}
|
|
1692
1781
|
|
|
1693
1782
|
//#endregion
|
|
1694
1783
|
//#region src/index.ts
|
|
1784
|
+
const logger = createLogger("WebModelContext");
|
|
1695
1785
|
function mergeTransportOptions(base, override) {
|
|
1696
1786
|
if (!base) return override;
|
|
1697
1787
|
if (!override) return base;
|
|
@@ -1719,7 +1809,7 @@ function parseScriptTagOptions(script) {
|
|
|
1719
1809
|
if (dataset.webmcpOptions) try {
|
|
1720
1810
|
return JSON.parse(dataset.webmcpOptions);
|
|
1721
1811
|
} catch (error) {
|
|
1722
|
-
|
|
1812
|
+
logger.error("Invalid JSON in data-webmcp-options:", error);
|
|
1723
1813
|
return;
|
|
1724
1814
|
}
|
|
1725
1815
|
const options = {};
|
|
@@ -1762,10 +1852,10 @@ if (typeof window !== "undefined" && typeof document !== "undefined") {
|
|
|
1762
1852
|
try {
|
|
1763
1853
|
if (shouldAutoInitialize) initializeWebModelContext(mergedOptions);
|
|
1764
1854
|
} catch (error) {
|
|
1765
|
-
|
|
1855
|
+
logger.error("Auto-initialization failed:", error);
|
|
1766
1856
|
}
|
|
1767
1857
|
}
|
|
1768
1858
|
|
|
1769
1859
|
//#endregion
|
|
1770
|
-
export { cleanupWebModelContext, initializeWebModelContext, zodToJsonSchema };
|
|
1860
|
+
export { cleanupWebModelContext, createLogger, initializeWebModelContext, zodToJsonSchema };
|
|
1771
1861
|
//# sourceMappingURL=index.js.map
|