@mcp-b/global 0.0.0-beta-20260109203913 → 0.0.0-canary-20260126004552
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 +42 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.iife.js +33 -18
- package/dist/index.js +254 -170
- package/dist/index.js.map +1 -1
- package/package.json +21 -8
package/dist/index.js
CHANGED
|
@@ -1,79 +1,200 @@
|
|
|
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
|
+
*/
|
|
13
|
+
/**
|
|
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
|
+
}
|
|
8
56
|
/**
|
|
9
|
-
*
|
|
10
|
-
* or a JSON Schema object
|
|
57
|
+
* No-op function for disabled log levels
|
|
11
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
|
+
* ```
|
|
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 nativeToJsonSchema = z.toJSONSchema;
|
|
96
|
+
const hasNativeToJSONSchema = typeof nativeToJsonSchema === "function";
|
|
97
|
+
const isRecord = (value) => typeof value === "object" && value !== null;
|
|
98
|
+
const isZod4Schema = (schema) => isRecord(schema) && "_zod" in schema;
|
|
99
|
+
const isZod3Schema = (schema) => isRecord(schema) && "_def" in schema && !("_zod" in schema);
|
|
100
|
+
const stripSchemaMeta = (schema) => {
|
|
101
|
+
const { $schema: _,...rest } = schema;
|
|
102
|
+
return rest;
|
|
103
|
+
};
|
|
104
|
+
const isOptionalSchema = (schema) => {
|
|
105
|
+
const typeName = schema._def?.typeName;
|
|
106
|
+
return typeName === "ZodOptional" || typeName === "ZodDefault";
|
|
107
|
+
};
|
|
12
108
|
function isZodSchema(schema) {
|
|
13
|
-
if (
|
|
109
|
+
if (!isRecord(schema)) return false;
|
|
14
110
|
if ("type" in schema && typeof schema.type === "string") return false;
|
|
15
111
|
const values = Object.values(schema);
|
|
16
112
|
if (values.length === 0) return false;
|
|
17
|
-
return values.some((
|
|
113
|
+
return values.some((value) => isZod4Schema(value) || isZod3Schema(value));
|
|
114
|
+
}
|
|
115
|
+
const hasZod4Schemas = (schema) => Object.values(schema).some((value) => isZod4Schema(value));
|
|
116
|
+
const tryNativeZodToJsonSchema = (schema) => {
|
|
117
|
+
if (!hasZod4Schemas(schema) || !hasNativeToJSONSchema) return null;
|
|
118
|
+
try {
|
|
119
|
+
return stripSchemaMeta(nativeToJsonSchema(z.object(schema), { target: "draft-7" }));
|
|
120
|
+
} catch (error) {
|
|
121
|
+
logger$2.warn("z.toJSONSchema failed, falling back to zod-to-json-schema:", error);
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
const fallbackZodToJsonSchema = (schema) => {
|
|
126
|
+
const properties = {};
|
|
127
|
+
const required = [];
|
|
128
|
+
for (const [key, zodSchema] of Object.entries(schema)) {
|
|
129
|
+
properties[key] = stripSchemaMeta(zodToJsonSchema$1(zodSchema, {
|
|
130
|
+
strictUnions: true,
|
|
131
|
+
$refStrategy: "none"
|
|
132
|
+
}));
|
|
133
|
+
if (!isOptionalSchema(zodSchema)) required.push(key);
|
|
134
|
+
}
|
|
135
|
+
const result = {
|
|
136
|
+
type: "object",
|
|
137
|
+
properties
|
|
138
|
+
};
|
|
139
|
+
if (required.length > 0) result.required = required;
|
|
140
|
+
return result;
|
|
141
|
+
};
|
|
142
|
+
function zodToJsonSchema(schema) {
|
|
143
|
+
const nativeSchema = tryNativeZodToJsonSchema(schema);
|
|
144
|
+
if (nativeSchema) return nativeSchema;
|
|
145
|
+
try {
|
|
146
|
+
return fallbackZodToJsonSchema(schema);
|
|
147
|
+
} catch (error) {
|
|
148
|
+
logger$2.warn("zodToJsonSchema failed:", error);
|
|
149
|
+
return {
|
|
150
|
+
type: "object",
|
|
151
|
+
properties: {}
|
|
152
|
+
};
|
|
153
|
+
}
|
|
18
154
|
}
|
|
19
|
-
/**
|
|
20
|
-
* Convert JSON Schema to Zod validator
|
|
21
|
-
* Uses @composio/json-schema-to-zod for conversion
|
|
22
|
-
*/
|
|
23
155
|
function jsonSchemaToZod$1(jsonSchema) {
|
|
24
156
|
try {
|
|
25
157
|
return jsonSchemaToZod(jsonSchema);
|
|
26
158
|
} catch (error) {
|
|
27
|
-
|
|
159
|
+
logger$2.warn("jsonSchemaToZod failed:", error);
|
|
28
160
|
return z.object({}).passthrough();
|
|
29
161
|
}
|
|
30
162
|
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
-
*/
|
|
163
|
+
const buildZodValidator = (schema, jsonSchema) => {
|
|
164
|
+
if (hasZod4Schemas(schema) && hasNativeToJSONSchema) return z.object(schema);
|
|
165
|
+
return jsonSchemaToZod$1(jsonSchema);
|
|
166
|
+
};
|
|
49
167
|
function normalizeSchema(schema) {
|
|
50
|
-
if (isZodSchema(schema))
|
|
51
|
-
jsonSchema
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
168
|
+
if (isZodSchema(schema)) {
|
|
169
|
+
const jsonSchema = zodToJsonSchema(schema);
|
|
170
|
+
return {
|
|
171
|
+
jsonSchema,
|
|
172
|
+
zodValidator: buildZodValidator(schema, jsonSchema)
|
|
173
|
+
};
|
|
174
|
+
}
|
|
55
175
|
return {
|
|
56
|
-
jsonSchema,
|
|
57
|
-
zodValidator: jsonSchemaToZod$1(
|
|
176
|
+
jsonSchema: schema,
|
|
177
|
+
zodValidator: jsonSchemaToZod$1(schema)
|
|
58
178
|
};
|
|
59
179
|
}
|
|
60
|
-
/**
|
|
61
|
-
* Validate data with Zod schema and return formatted result
|
|
62
|
-
*/
|
|
63
180
|
function validateWithZod(data, validator) {
|
|
64
181
|
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 {
|
|
182
|
+
if (result.success) return {
|
|
70
183
|
success: true,
|
|
71
184
|
data: result.data
|
|
72
185
|
};
|
|
186
|
+
return {
|
|
187
|
+
success: false,
|
|
188
|
+
error: `Validation failed:\n${result.error.issues.map((err) => ` - ${err.path.join(".") || "root"}: ${err.message}`).join("\n")}`
|
|
189
|
+
};
|
|
73
190
|
}
|
|
74
191
|
|
|
75
192
|
//#endregion
|
|
76
193
|
//#region src/global.ts
|
|
194
|
+
const logger$1 = createLogger("WebModelContext");
|
|
195
|
+
const nativeLogger = createLogger("NativeAdapter");
|
|
196
|
+
const bridgeLogger = createLogger("MCPBridge");
|
|
197
|
+
const testingLogger = createLogger("ModelContextTesting");
|
|
77
198
|
/**
|
|
78
199
|
* Marker property name used to identify polyfill implementations.
|
|
79
200
|
* This constant ensures single source of truth for the marker used in
|
|
@@ -92,6 +213,7 @@ const POLYFILL_MARKER_PROPERTY = "__isWebMCPPolyfill";
|
|
|
92
213
|
* @returns Detection result with flags for native context and testing API availability
|
|
93
214
|
*/
|
|
94
215
|
function detectNativeAPI() {
|
|
216
|
+
/* c8 ignore next 2 */
|
|
95
217
|
if (typeof window === "undefined" || typeof navigator === "undefined") return {
|
|
96
218
|
hasNativeContext: false,
|
|
97
219
|
hasNativeTesting: false
|
|
@@ -99,8 +221,8 @@ function detectNativeAPI() {
|
|
|
99
221
|
const modelContext = navigator.modelContext;
|
|
100
222
|
const modelContextTesting = navigator.modelContextTesting;
|
|
101
223
|
if (!modelContext || !modelContextTesting) return {
|
|
102
|
-
hasNativeContext:
|
|
103
|
-
hasNativeTesting:
|
|
224
|
+
hasNativeContext: Boolean(modelContext),
|
|
225
|
+
hasNativeTesting: Boolean(modelContextTesting)
|
|
104
226
|
};
|
|
105
227
|
if (POLYFILL_MARKER_PROPERTY in modelContextTesting && modelContextTesting[POLYFILL_MARKER_PROPERTY] === true) return {
|
|
106
228
|
hasNativeContext: false,
|
|
@@ -142,7 +264,6 @@ var NativeModelContextAdapter = class {
|
|
|
142
264
|
this.nativeContext = nativeContext;
|
|
143
265
|
this.nativeTesting = nativeTesting;
|
|
144
266
|
this.nativeTesting.registerToolsChangedCallback(() => {
|
|
145
|
-
console.log("[Native Adapter] Tool change detected from native API");
|
|
146
267
|
this.syncToolsFromNative();
|
|
147
268
|
});
|
|
148
269
|
this.syncToolsFromNative();
|
|
@@ -159,7 +280,6 @@ var NativeModelContextAdapter = class {
|
|
|
159
280
|
this.syncInProgress = true;
|
|
160
281
|
try {
|
|
161
282
|
const nativeTools = this.nativeTesting.listTools();
|
|
162
|
-
console.log(`[Native Adapter] Syncing ${nativeTools.length} tools from native API`);
|
|
163
283
|
this.bridge.tools.clear();
|
|
164
284
|
for (const toolInfo of nativeTools) try {
|
|
165
285
|
const inputSchema = JSON.parse(toolInfo.inputSchema);
|
|
@@ -175,7 +295,7 @@ var NativeModelContextAdapter = class {
|
|
|
175
295
|
};
|
|
176
296
|
this.bridge.tools.set(toolInfo.name, validatedTool);
|
|
177
297
|
} catch (error) {
|
|
178
|
-
|
|
298
|
+
nativeLogger.error(`Failed to sync tool "${toolInfo.name}":`, error);
|
|
179
299
|
}
|
|
180
300
|
this.notifyMCPServers();
|
|
181
301
|
} finally {
|
|
@@ -235,7 +355,6 @@ var NativeModelContextAdapter = class {
|
|
|
235
355
|
* @param {ModelContextInput} context - Context containing tools to register
|
|
236
356
|
*/
|
|
237
357
|
provideContext(context) {
|
|
238
|
-
console.log("[Native Adapter] Delegating provideContext to native API");
|
|
239
358
|
this.nativeContext.provideContext(context);
|
|
240
359
|
}
|
|
241
360
|
/**
|
|
@@ -247,7 +366,6 @@ var NativeModelContextAdapter = class {
|
|
|
247
366
|
* @returns {{unregister: () => void}} Object with unregister function
|
|
248
367
|
*/
|
|
249
368
|
registerTool(tool) {
|
|
250
|
-
console.log(`[Native Adapter] Delegating registerTool("${tool.name}") to native API`);
|
|
251
369
|
return this.nativeContext.registerTool(tool);
|
|
252
370
|
}
|
|
253
371
|
/**
|
|
@@ -257,7 +375,6 @@ var NativeModelContextAdapter = class {
|
|
|
257
375
|
* @param {string} name - Name of the tool to unregister
|
|
258
376
|
*/
|
|
259
377
|
unregisterTool(name) {
|
|
260
|
-
console.log(`[Native Adapter] Delegating unregisterTool("${name}") to native API`);
|
|
261
378
|
this.nativeContext.unregisterTool(name);
|
|
262
379
|
}
|
|
263
380
|
/**
|
|
@@ -265,7 +382,6 @@ var NativeModelContextAdapter = class {
|
|
|
265
382
|
* Delegates to navigator.modelContext.clearContext().
|
|
266
383
|
*/
|
|
267
384
|
clearContext() {
|
|
268
|
-
console.log("[Native Adapter] Delegating clearContext to native API");
|
|
269
385
|
this.nativeContext.clearContext();
|
|
270
386
|
}
|
|
271
387
|
/**
|
|
@@ -278,12 +394,11 @@ var NativeModelContextAdapter = class {
|
|
|
278
394
|
* @internal
|
|
279
395
|
*/
|
|
280
396
|
async executeTool(toolName, args) {
|
|
281
|
-
console.log(`[Native Adapter] Executing tool "${toolName}" via native API`);
|
|
282
397
|
try {
|
|
283
398
|
const result = await this.nativeTesting.executeTool(toolName, JSON.stringify(args));
|
|
284
399
|
return this.convertToToolResponse(result);
|
|
285
400
|
} catch (error) {
|
|
286
|
-
|
|
401
|
+
nativeLogger.error(`Error executing tool "${toolName}":`, error);
|
|
287
402
|
return {
|
|
288
403
|
content: [{
|
|
289
404
|
type: "text",
|
|
@@ -314,7 +429,7 @@ var NativeModelContextAdapter = class {
|
|
|
314
429
|
* This is a polyfill-only feature.
|
|
315
430
|
*/
|
|
316
431
|
registerResource(_resource) {
|
|
317
|
-
|
|
432
|
+
nativeLogger.warn("registerResource is not supported by native API");
|
|
318
433
|
return { unregister: () => {} };
|
|
319
434
|
}
|
|
320
435
|
/**
|
|
@@ -322,7 +437,7 @@ var NativeModelContextAdapter = class {
|
|
|
322
437
|
* Note: Native Chromium API does not yet support resources.
|
|
323
438
|
*/
|
|
324
439
|
unregisterResource(_uri) {
|
|
325
|
-
|
|
440
|
+
nativeLogger.warn("unregisterResource is not supported by native API");
|
|
326
441
|
}
|
|
327
442
|
/**
|
|
328
443
|
* Lists all registered resources.
|
|
@@ -352,7 +467,7 @@ var NativeModelContextAdapter = class {
|
|
|
352
467
|
* This is a polyfill-only feature.
|
|
353
468
|
*/
|
|
354
469
|
registerPrompt(_prompt) {
|
|
355
|
-
|
|
470
|
+
nativeLogger.warn("registerPrompt is not supported by native API");
|
|
356
471
|
return { unregister: () => {} };
|
|
357
472
|
}
|
|
358
473
|
/**
|
|
@@ -360,7 +475,7 @@ var NativeModelContextAdapter = class {
|
|
|
360
475
|
* Note: Native Chromium API does not yet support prompts.
|
|
361
476
|
*/
|
|
362
477
|
unregisterPrompt(_name) {
|
|
363
|
-
|
|
478
|
+
nativeLogger.warn("unregisterPrompt is not supported by native API");
|
|
364
479
|
}
|
|
365
480
|
/**
|
|
366
481
|
* Lists all registered prompts.
|
|
@@ -415,7 +530,6 @@ var NativeModelContextAdapter = class {
|
|
|
415
530
|
* This is handled by the polyfill.
|
|
416
531
|
*/
|
|
417
532
|
async createMessage(params) {
|
|
418
|
-
console.log("[Native Adapter] Requesting sampling from client");
|
|
419
533
|
const underlyingServer = this.bridge.tabServer.server;
|
|
420
534
|
if (!underlyingServer?.createMessage) throw new Error("Sampling is not supported: no connected client with sampling capability");
|
|
421
535
|
return underlyingServer.createMessage(params);
|
|
@@ -426,7 +540,6 @@ var NativeModelContextAdapter = class {
|
|
|
426
540
|
* This is handled by the polyfill.
|
|
427
541
|
*/
|
|
428
542
|
async elicitInput(params) {
|
|
429
|
-
console.log("[Native Adapter] Requesting elicitation from client");
|
|
430
543
|
const underlyingServer = this.bridge.tabServer.server;
|
|
431
544
|
if (!underlyingServer?.elicitInput) throw new Error("Elicitation is not supported: no connected client with elicitation capability");
|
|
432
545
|
return underlyingServer.elicitInput(params);
|
|
@@ -565,7 +678,7 @@ var WebModelContextTesting = class {
|
|
|
565
678
|
for (const callback of this.toolsChangedCallbacks) try {
|
|
566
679
|
callback();
|
|
567
680
|
} catch (error) {
|
|
568
|
-
|
|
681
|
+
testingLogger.error("Error in tools changed callback:", error);
|
|
569
682
|
}
|
|
570
683
|
}
|
|
571
684
|
/**
|
|
@@ -579,7 +692,6 @@ var WebModelContextTesting = class {
|
|
|
579
692
|
* @throws {Error} If the tool does not exist
|
|
580
693
|
*/
|
|
581
694
|
async executeTool(toolName, inputArgsJson) {
|
|
582
|
-
console.log(`[Model Context Testing] Executing tool: ${toolName}`);
|
|
583
695
|
let args;
|
|
584
696
|
try {
|
|
585
697
|
args = JSON.parse(inputArgsJson);
|
|
@@ -616,7 +728,6 @@ var WebModelContextTesting = class {
|
|
|
616
728
|
*/
|
|
617
729
|
registerToolsChangedCallback(callback) {
|
|
618
730
|
this.toolsChangedCallbacks.add(callback);
|
|
619
|
-
console.log("[Model Context Testing] Tools changed callback registered");
|
|
620
731
|
}
|
|
621
732
|
/**
|
|
622
733
|
* Gets all tool calls that have been recorded (polyfill extension).
|
|
@@ -631,7 +742,6 @@ var WebModelContextTesting = class {
|
|
|
631
742
|
*/
|
|
632
743
|
clearToolCalls() {
|
|
633
744
|
this.toolCallHistory = [];
|
|
634
|
-
console.log("[Model Context Testing] Tool call history cleared");
|
|
635
745
|
}
|
|
636
746
|
/**
|
|
637
747
|
* Sets a mock response for a specific tool (polyfill extension).
|
|
@@ -642,7 +752,6 @@ var WebModelContextTesting = class {
|
|
|
642
752
|
*/
|
|
643
753
|
setMockToolResponse(toolName, response) {
|
|
644
754
|
this.mockResponses.set(toolName, response);
|
|
645
|
-
console.log(`[Model Context Testing] Mock response set for tool: ${toolName}`);
|
|
646
755
|
}
|
|
647
756
|
/**
|
|
648
757
|
* Clears the mock response for a specific tool (polyfill extension).
|
|
@@ -651,14 +760,12 @@ var WebModelContextTesting = class {
|
|
|
651
760
|
*/
|
|
652
761
|
clearMockToolResponse(toolName) {
|
|
653
762
|
this.mockResponses.delete(toolName);
|
|
654
|
-
console.log(`[Model Context Testing] Mock response cleared for tool: ${toolName}`);
|
|
655
763
|
}
|
|
656
764
|
/**
|
|
657
765
|
* Clears all mock tool responses (polyfill extension).
|
|
658
766
|
*/
|
|
659
767
|
clearAllMockToolResponses() {
|
|
660
768
|
this.mockResponses.clear();
|
|
661
|
-
console.log("[Model Context Testing] All mock responses cleared");
|
|
662
769
|
}
|
|
663
770
|
/**
|
|
664
771
|
* Gets the current tools registered in the system (polyfill extension).
|
|
@@ -675,7 +782,6 @@ var WebModelContextTesting = class {
|
|
|
675
782
|
reset() {
|
|
676
783
|
this.clearToolCalls();
|
|
677
784
|
this.clearAllMockToolResponses();
|
|
678
|
-
console.log("[Model Context Testing] Testing state reset");
|
|
679
785
|
}
|
|
680
786
|
};
|
|
681
787
|
/**
|
|
@@ -781,14 +887,13 @@ var WebModelContext = class {
|
|
|
781
887
|
* @throws {Error} If a name/uri collides with existing dynamic items
|
|
782
888
|
*/
|
|
783
889
|
provideContext(context) {
|
|
784
|
-
const toolCount = context.tools?.length ?? 0;
|
|
785
|
-
const resourceCount = context.resources?.length ?? 0;
|
|
786
|
-
const promptCount = context.prompts?.length ?? 0;
|
|
787
|
-
console.log(`[Web Model Context] provideContext: ${toolCount} tools, ${resourceCount} resources, ${promptCount} prompts`);
|
|
788
890
|
this.provideContextTools.clear();
|
|
789
891
|
this.provideContextResources.clear();
|
|
790
892
|
this.provideContextPrompts.clear();
|
|
791
893
|
for (const tool of context.tools ?? []) {
|
|
894
|
+
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.`);
|
|
895
|
+
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.`);
|
|
896
|
+
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.`);
|
|
792
897
|
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.`);
|
|
793
898
|
const { jsonSchema: inputJson, zodValidator: inputZod } = normalizeSchema(tool.inputSchema);
|
|
794
899
|
const normalizedOutput = tool.outputSchema ? normalizeSchema(tool.outputSchema) : null;
|
|
@@ -826,11 +931,11 @@ var WebModelContext = class {
|
|
|
826
931
|
* @private
|
|
827
932
|
*/
|
|
828
933
|
validateResource(resource) {
|
|
829
|
-
const templateParamRegex = /\{([^}]
|
|
934
|
+
const templateParamRegex = /\{([^}]{1,100})\}/g;
|
|
830
935
|
const templateParams = [];
|
|
831
936
|
for (const match of resource.uri.matchAll(templateParamRegex)) {
|
|
832
937
|
const paramName = match[1];
|
|
833
|
-
|
|
938
|
+
templateParams.push(paramName);
|
|
834
939
|
}
|
|
835
940
|
return {
|
|
836
941
|
uri: resource.uri,
|
|
@@ -871,11 +976,13 @@ var WebModelContext = class {
|
|
|
871
976
|
* @throws {Error} If tool name collides with existing tools
|
|
872
977
|
*/
|
|
873
978
|
registerTool(tool) {
|
|
874
|
-
|
|
979
|
+
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.`);
|
|
980
|
+
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.`);
|
|
981
|
+
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.`);
|
|
875
982
|
const now = Date.now();
|
|
876
983
|
const lastRegistration = this.toolRegistrationTimestamps.get(tool.name);
|
|
877
984
|
if (lastRegistration && now - lastRegistration < RAPID_DUPLICATE_WINDOW_MS) {
|
|
878
|
-
|
|
985
|
+
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.`);
|
|
879
986
|
const existingUnregister = this.toolUnregisterFunctions.get(tool.name);
|
|
880
987
|
if (existingUnregister) return { unregister: existingUnregister };
|
|
881
988
|
}
|
|
@@ -898,10 +1005,9 @@ var WebModelContext = class {
|
|
|
898
1005
|
this.updateBridgeTools();
|
|
899
1006
|
this.scheduleListChanged("tools");
|
|
900
1007
|
const unregisterFn = () => {
|
|
901
|
-
console.log(`[Web Model Context] Unregistering tool: ${tool.name}`);
|
|
902
1008
|
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.`);
|
|
903
1009
|
if (!this.dynamicTools.has(tool.name)) {
|
|
904
|
-
|
|
1010
|
+
logger$1.warn(`Tool "${tool.name}" is not registered, ignoring unregister call`);
|
|
905
1011
|
return;
|
|
906
1012
|
}
|
|
907
1013
|
this.dynamicTools.delete(tool.name);
|
|
@@ -922,11 +1028,10 @@ var WebModelContext = class {
|
|
|
922
1028
|
* @throws {Error} If resource URI collides with existing resources
|
|
923
1029
|
*/
|
|
924
1030
|
registerResource(resource) {
|
|
925
|
-
console.log(`[Web Model Context] Registering resource dynamically: ${resource.uri}`);
|
|
926
1031
|
const now = Date.now();
|
|
927
1032
|
const lastRegistration = this.resourceRegistrationTimestamps.get(resource.uri);
|
|
928
1033
|
if (lastRegistration && now - lastRegistration < RAPID_DUPLICATE_WINDOW_MS) {
|
|
929
|
-
|
|
1034
|
+
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.`);
|
|
930
1035
|
const existingUnregister = this.resourceUnregisterFunctions.get(resource.uri);
|
|
931
1036
|
if (existingUnregister) return { unregister: existingUnregister };
|
|
932
1037
|
}
|
|
@@ -938,10 +1043,9 @@ var WebModelContext = class {
|
|
|
938
1043
|
this.updateBridgeResources();
|
|
939
1044
|
this.scheduleListChanged("resources");
|
|
940
1045
|
const unregisterFn = () => {
|
|
941
|
-
console.log(`[Web Model Context] Unregistering resource: ${resource.uri}`);
|
|
942
1046
|
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.`);
|
|
943
1047
|
if (!this.dynamicResources.has(resource.uri)) {
|
|
944
|
-
|
|
1048
|
+
logger$1.warn(`Resource "${resource.uri}" is not registered, ignoring unregister call`);
|
|
945
1049
|
return;
|
|
946
1050
|
}
|
|
947
1051
|
this.dynamicResources.delete(resource.uri);
|
|
@@ -960,11 +1064,10 @@ var WebModelContext = class {
|
|
|
960
1064
|
* @param {string} uri - URI of the resource to unregister
|
|
961
1065
|
*/
|
|
962
1066
|
unregisterResource(uri) {
|
|
963
|
-
console.log(`[Web Model Context] Unregistering resource: ${uri}`);
|
|
964
1067
|
const inProvideContext = this.provideContextResources.has(uri);
|
|
965
1068
|
const inDynamic = this.dynamicResources.has(uri);
|
|
966
1069
|
if (!inProvideContext && !inDynamic) {
|
|
967
|
-
|
|
1070
|
+
logger$1.warn(`Resource "${uri}" is not registered, ignoring unregister call`);
|
|
968
1071
|
return;
|
|
969
1072
|
}
|
|
970
1073
|
if (inProvideContext) this.provideContextResources.delete(uri);
|
|
@@ -1013,11 +1116,10 @@ var WebModelContext = class {
|
|
|
1013
1116
|
* @throws {Error} If prompt name collides with existing prompts
|
|
1014
1117
|
*/
|
|
1015
1118
|
registerPrompt(prompt) {
|
|
1016
|
-
console.log(`[Web Model Context] Registering prompt dynamically: ${prompt.name}`);
|
|
1017
1119
|
const now = Date.now();
|
|
1018
1120
|
const lastRegistration = this.promptRegistrationTimestamps.get(prompt.name);
|
|
1019
1121
|
if (lastRegistration && now - lastRegistration < RAPID_DUPLICATE_WINDOW_MS) {
|
|
1020
|
-
|
|
1122
|
+
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.`);
|
|
1021
1123
|
const existingUnregister = this.promptUnregisterFunctions.get(prompt.name);
|
|
1022
1124
|
if (existingUnregister) return { unregister: existingUnregister };
|
|
1023
1125
|
}
|
|
@@ -1029,10 +1131,9 @@ var WebModelContext = class {
|
|
|
1029
1131
|
this.updateBridgePrompts();
|
|
1030
1132
|
this.scheduleListChanged("prompts");
|
|
1031
1133
|
const unregisterFn = () => {
|
|
1032
|
-
console.log(`[Web Model Context] Unregistering prompt: ${prompt.name}`);
|
|
1033
1134
|
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.`);
|
|
1034
1135
|
if (!this.dynamicPrompts.has(prompt.name)) {
|
|
1035
|
-
|
|
1136
|
+
logger$1.warn(`Prompt "${prompt.name}" is not registered, ignoring unregister call`);
|
|
1036
1137
|
return;
|
|
1037
1138
|
}
|
|
1038
1139
|
this.dynamicPrompts.delete(prompt.name);
|
|
@@ -1051,11 +1152,10 @@ var WebModelContext = class {
|
|
|
1051
1152
|
* @param {string} name - Name of the prompt to unregister
|
|
1052
1153
|
*/
|
|
1053
1154
|
unregisterPrompt(name) {
|
|
1054
|
-
console.log(`[Web Model Context] Unregistering prompt: ${name}`);
|
|
1055
1155
|
const inProvideContext = this.provideContextPrompts.has(name);
|
|
1056
1156
|
const inDynamic = this.dynamicPrompts.has(name);
|
|
1057
1157
|
if (!inProvideContext && !inDynamic) {
|
|
1058
|
-
|
|
1158
|
+
logger$1.warn(`Prompt "${name}" is not registered, ignoring unregister call`);
|
|
1059
1159
|
return;
|
|
1060
1160
|
}
|
|
1061
1161
|
if (inProvideContext) this.provideContextPrompts.delete(name);
|
|
@@ -1091,11 +1191,10 @@ var WebModelContext = class {
|
|
|
1091
1191
|
* @param {string} name - Name of the tool to unregister
|
|
1092
1192
|
*/
|
|
1093
1193
|
unregisterTool(name) {
|
|
1094
|
-
console.log(`[Web Model Context] Unregistering tool: ${name}`);
|
|
1095
1194
|
const inProvideContext = this.provideContextTools.has(name);
|
|
1096
1195
|
const inDynamic = this.dynamicTools.has(name);
|
|
1097
1196
|
if (!inProvideContext && !inDynamic) {
|
|
1098
|
-
|
|
1197
|
+
logger$1.warn(`Tool "${name}" is not registered, ignoring unregister call`);
|
|
1099
1198
|
return;
|
|
1100
1199
|
}
|
|
1101
1200
|
if (inProvideContext) this.provideContextTools.delete(name);
|
|
@@ -1112,7 +1211,6 @@ var WebModelContext = class {
|
|
|
1112
1211
|
* Removes all tools, resources, and prompts registered via provideContext() and register* methods.
|
|
1113
1212
|
*/
|
|
1114
1213
|
clearContext() {
|
|
1115
|
-
console.log("[Web Model Context] Clearing all context (tools, resources, prompts)");
|
|
1116
1214
|
this.provideContextTools.clear();
|
|
1117
1215
|
this.dynamicTools.clear();
|
|
1118
1216
|
this.toolRegistrationTimestamps.clear();
|
|
@@ -1142,7 +1240,6 @@ var WebModelContext = class {
|
|
|
1142
1240
|
this.bridge.tools.clear();
|
|
1143
1241
|
for (const [name, tool] of this.provideContextTools) this.bridge.tools.set(name, tool);
|
|
1144
1242
|
for (const [name, tool] of this.dynamicTools) this.bridge.tools.set(name, tool);
|
|
1145
|
-
console.log(`[Web Model Context] Updated bridge with ${this.provideContextTools.size} base tools + ${this.dynamicTools.size} dynamic tools = ${this.bridge.tools.size} total`);
|
|
1146
1243
|
}
|
|
1147
1244
|
/**
|
|
1148
1245
|
* Notifies all servers and testing callbacks that the tools list has changed.
|
|
@@ -1170,7 +1267,6 @@ var WebModelContext = class {
|
|
|
1170
1267
|
this.bridge.resources.clear();
|
|
1171
1268
|
for (const [uri, resource] of this.provideContextResources) this.bridge.resources.set(uri, resource);
|
|
1172
1269
|
for (const [uri, resource] of this.dynamicResources) this.bridge.resources.set(uri, resource);
|
|
1173
|
-
console.log(`[Web Model Context] Updated bridge with ${this.provideContextResources.size} base resources + ${this.dynamicResources.size} dynamic resources = ${this.bridge.resources.size} total`);
|
|
1174
1270
|
}
|
|
1175
1271
|
/**
|
|
1176
1272
|
* Notifies all servers that the resources list has changed.
|
|
@@ -1196,7 +1292,6 @@ var WebModelContext = class {
|
|
|
1196
1292
|
this.bridge.prompts.clear();
|
|
1197
1293
|
for (const [name, prompt] of this.provideContextPrompts) this.bridge.prompts.set(name, prompt);
|
|
1198
1294
|
for (const [name, prompt] of this.dynamicPrompts) this.bridge.prompts.set(name, prompt);
|
|
1199
|
-
console.log(`[Web Model Context] Updated bridge with ${this.provideContextPrompts.size} base prompts + ${this.dynamicPrompts.size} dynamic prompts = ${this.bridge.prompts.size} total`);
|
|
1200
1295
|
}
|
|
1201
1296
|
/**
|
|
1202
1297
|
* Notifies all servers that the prompts list has changed.
|
|
@@ -1239,7 +1334,7 @@ var WebModelContext = class {
|
|
|
1239
1334
|
break;
|
|
1240
1335
|
default: {
|
|
1241
1336
|
const _exhaustive = listType;
|
|
1242
|
-
|
|
1337
|
+
logger$1.error(`Unknown list type: ${_exhaustive}`);
|
|
1243
1338
|
}
|
|
1244
1339
|
}
|
|
1245
1340
|
});
|
|
@@ -1254,23 +1349,34 @@ var WebModelContext = class {
|
|
|
1254
1349
|
* @internal
|
|
1255
1350
|
*/
|
|
1256
1351
|
async readResource(uri) {
|
|
1257
|
-
console.log(`[Web Model Context] Reading resource: ${uri}`);
|
|
1258
1352
|
const staticResource = this.bridge.resources.get(uri);
|
|
1259
1353
|
if (staticResource && !staticResource.isTemplate) try {
|
|
1260
|
-
|
|
1354
|
+
let parsedUri;
|
|
1355
|
+
try {
|
|
1356
|
+
parsedUri = new URL(uri);
|
|
1357
|
+
} catch {
|
|
1358
|
+
parsedUri = new URL(`custom-scheme:///${encodeURIComponent(uri)}`);
|
|
1359
|
+
parsedUri.originalUri = uri;
|
|
1360
|
+
}
|
|
1261
1361
|
return await staticResource.read(parsedUri);
|
|
1262
1362
|
} catch (error) {
|
|
1263
|
-
|
|
1363
|
+
logger$1.error(`Error reading resource ${uri}:`, error);
|
|
1264
1364
|
throw error;
|
|
1265
1365
|
}
|
|
1266
1366
|
for (const resource of this.bridge.resources.values()) {
|
|
1267
1367
|
if (!resource.isTemplate) continue;
|
|
1268
1368
|
const params = this.matchUriTemplate(resource.uri, uri);
|
|
1269
1369
|
if (params) try {
|
|
1270
|
-
|
|
1370
|
+
let parsedUri;
|
|
1371
|
+
try {
|
|
1372
|
+
parsedUri = new URL(uri);
|
|
1373
|
+
} catch {
|
|
1374
|
+
parsedUri = new URL(`custom-scheme:///${encodeURIComponent(uri)}`);
|
|
1375
|
+
parsedUri.originalUri = uri;
|
|
1376
|
+
}
|
|
1271
1377
|
return await resource.read(parsedUri, params);
|
|
1272
1378
|
} catch (error) {
|
|
1273
|
-
|
|
1379
|
+
logger$1.error(`Error reading resource ${uri}:`, error);
|
|
1274
1380
|
throw error;
|
|
1275
1381
|
}
|
|
1276
1382
|
}
|
|
@@ -1300,8 +1406,7 @@ var WebModelContext = class {
|
|
|
1300
1406
|
const params = {};
|
|
1301
1407
|
for (let i = 0; i < paramNames.length; i++) {
|
|
1302
1408
|
const paramName = paramNames[i];
|
|
1303
|
-
|
|
1304
|
-
if (paramName !== void 0 && paramValue !== void 0) params[paramName] = paramValue;
|
|
1409
|
+
params[paramName] = match[i + 1];
|
|
1305
1410
|
}
|
|
1306
1411
|
return params;
|
|
1307
1412
|
}
|
|
@@ -1315,20 +1420,19 @@ var WebModelContext = class {
|
|
|
1315
1420
|
* @internal
|
|
1316
1421
|
*/
|
|
1317
1422
|
async getPrompt(name, args) {
|
|
1318
|
-
console.log(`[Web Model Context] Getting prompt: ${name}`);
|
|
1319
1423
|
const prompt = this.bridge.prompts.get(name);
|
|
1320
1424
|
if (!prompt) throw new Error(`Prompt not found: ${name}`);
|
|
1321
1425
|
if (prompt.argsValidator && args) {
|
|
1322
1426
|
const validation = validateWithZod(args, prompt.argsValidator);
|
|
1323
1427
|
if (!validation.success) {
|
|
1324
|
-
|
|
1428
|
+
logger$1.error(`Argument validation failed for prompt ${name}:`, validation.error);
|
|
1325
1429
|
throw new Error(`Argument validation error for prompt "${name}":\n${validation.error}`);
|
|
1326
1430
|
}
|
|
1327
1431
|
}
|
|
1328
1432
|
try {
|
|
1329
1433
|
return await prompt.get(args ?? {});
|
|
1330
1434
|
} catch (error) {
|
|
1331
|
-
|
|
1435
|
+
logger$1.error(`Error getting prompt ${name}:`, error);
|
|
1332
1436
|
throw error;
|
|
1333
1437
|
}
|
|
1334
1438
|
}
|
|
@@ -1351,10 +1455,9 @@ var WebModelContext = class {
|
|
|
1351
1455
|
async executeTool(toolName, args) {
|
|
1352
1456
|
const tool = this.bridge.tools.get(toolName);
|
|
1353
1457
|
if (!tool) throw new Error(`Tool not found: ${toolName}`);
|
|
1354
|
-
console.log(`[Web Model Context] Validating input for tool: ${toolName}`);
|
|
1355
1458
|
const validation = validateWithZod(args, tool.inputValidator);
|
|
1356
1459
|
if (!validation.success) {
|
|
1357
|
-
|
|
1460
|
+
logger$1.error(`Input validation failed for ${toolName}:`, validation.error);
|
|
1358
1461
|
return {
|
|
1359
1462
|
content: [{
|
|
1360
1463
|
type: "text",
|
|
@@ -1367,31 +1470,24 @@ var WebModelContext = class {
|
|
|
1367
1470
|
if (this.testingAPI) this.testingAPI.recordToolCall(toolName, validatedArgs);
|
|
1368
1471
|
if (this.testingAPI?.hasMockResponse(toolName)) {
|
|
1369
1472
|
const mockResponse = this.testingAPI.getMockResponse(toolName);
|
|
1370
|
-
if (mockResponse)
|
|
1371
|
-
console.log(`[Web Model Context] Returning mock response for tool: ${toolName}`);
|
|
1372
|
-
return mockResponse;
|
|
1373
|
-
}
|
|
1473
|
+
if (mockResponse) return mockResponse;
|
|
1374
1474
|
}
|
|
1375
1475
|
const event = new WebToolCallEvent(toolName, validatedArgs);
|
|
1376
1476
|
this.dispatchEvent(event);
|
|
1377
1477
|
if (event.defaultPrevented && event.hasResponse()) {
|
|
1378
1478
|
const response = event.getResponse();
|
|
1379
|
-
if (response)
|
|
1380
|
-
console.log(`[Web Model Context] Tool ${toolName} handled by event listener`);
|
|
1381
|
-
return response;
|
|
1382
|
-
}
|
|
1479
|
+
if (response) return response;
|
|
1383
1480
|
}
|
|
1384
|
-
console.log(`[Web Model Context] Executing tool: ${toolName}`);
|
|
1385
1481
|
try {
|
|
1386
1482
|
const response = await tool.execute(validatedArgs);
|
|
1387
1483
|
if (tool.outputValidator && response.structuredContent) {
|
|
1388
1484
|
const outputValidation = validateWithZod(response.structuredContent, tool.outputValidator);
|
|
1389
|
-
if (!outputValidation.success)
|
|
1485
|
+
if (!outputValidation.success) logger$1.warn(`Output validation failed for ${toolName}:`, outputValidation.error);
|
|
1390
1486
|
}
|
|
1391
|
-
if (response.metadata && "willNavigate" in response.metadata)
|
|
1487
|
+
if (response.metadata && typeof response.metadata === "object" && "willNavigate" in response.metadata) logger$1.info(`Tool "${toolName}" will trigger navigation`, response.metadata);
|
|
1392
1488
|
return response;
|
|
1393
1489
|
} catch (error) {
|
|
1394
|
-
|
|
1490
|
+
logger$1.error(`Error executing tool ${toolName}:`, error);
|
|
1395
1491
|
return {
|
|
1396
1492
|
content: [{
|
|
1397
1493
|
type: "text",
|
|
@@ -1425,7 +1521,6 @@ var WebModelContext = class {
|
|
|
1425
1521
|
* @returns {Promise<SamplingResult>} The LLM completion result
|
|
1426
1522
|
*/
|
|
1427
1523
|
async createMessage(params) {
|
|
1428
|
-
console.log("[Web Model Context] Requesting sampling from client");
|
|
1429
1524
|
const underlyingServer = this.bridge.tabServer.server;
|
|
1430
1525
|
if (!underlyingServer?.createMessage) throw new Error("Sampling is not supported: no connected client with sampling capability");
|
|
1431
1526
|
return underlyingServer.createMessage(params);
|
|
@@ -1438,7 +1533,6 @@ var WebModelContext = class {
|
|
|
1438
1533
|
* @returns {Promise<ElicitationResult>} The user's response
|
|
1439
1534
|
*/
|
|
1440
1535
|
async elicitInput(params) {
|
|
1441
|
-
console.log("[Web Model Context] Requesting elicitation from client");
|
|
1442
1536
|
const underlyingServer = this.bridge.tabServer.server;
|
|
1443
1537
|
if (!underlyingServer?.elicitInput) throw new Error("Elicitation is not supported: no connected client with elicitation capability");
|
|
1444
1538
|
return underlyingServer.elicitInput(params);
|
|
@@ -1453,16 +1547,13 @@ var WebModelContext = class {
|
|
|
1453
1547
|
* @returns {MCPBridge} The initialized MCP bridge
|
|
1454
1548
|
*/
|
|
1455
1549
|
function initializeMCPBridge(options) {
|
|
1456
|
-
console.log("[Web Model Context] Initializing MCP bridge");
|
|
1457
1550
|
const hostname = window.location.hostname || "localhost";
|
|
1458
1551
|
const transportOptions = options?.transport;
|
|
1459
1552
|
const setupServerHandlers = (server, bridge$1) => {
|
|
1460
1553
|
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
1461
|
-
console.log("[MCP Bridge] Handling list_tools request");
|
|
1462
1554
|
return { tools: bridge$1.modelContext.listTools() };
|
|
1463
1555
|
});
|
|
1464
1556
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
1465
|
-
console.log(`[MCP Bridge] Handling call_tool request: ${request.params.name}`);
|
|
1466
1557
|
const toolName = request.params.name;
|
|
1467
1558
|
const args = request.params.arguments || {};
|
|
1468
1559
|
try {
|
|
@@ -1473,40 +1564,35 @@ function initializeMCPBridge(options) {
|
|
|
1473
1564
|
...response.structuredContent && { structuredContent: response.structuredContent }
|
|
1474
1565
|
};
|
|
1475
1566
|
} catch (error) {
|
|
1476
|
-
|
|
1567
|
+
bridgeLogger.error(`Error calling tool ${toolName}:`, error);
|
|
1477
1568
|
throw error;
|
|
1478
1569
|
}
|
|
1479
1570
|
});
|
|
1480
1571
|
server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
1481
|
-
console.log("[MCP Bridge] Handling list_resources request");
|
|
1482
1572
|
return { resources: bridge$1.modelContext.listResources() };
|
|
1483
1573
|
});
|
|
1484
1574
|
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
1485
|
-
console.log(`[MCP Bridge] Handling read_resource request: ${request.params.uri}`);
|
|
1486
1575
|
try {
|
|
1487
1576
|
return await bridge$1.modelContext.readResource(request.params.uri);
|
|
1488
1577
|
} catch (error) {
|
|
1489
|
-
|
|
1578
|
+
bridgeLogger.error(`Error reading resource ${request.params.uri}:`, error);
|
|
1490
1579
|
throw error;
|
|
1491
1580
|
}
|
|
1492
1581
|
});
|
|
1493
1582
|
server.setRequestHandler(ListPromptsRequestSchema, async () => {
|
|
1494
|
-
console.log("[MCP Bridge] Handling list_prompts request");
|
|
1495
1583
|
return { prompts: bridge$1.modelContext.listPrompts() };
|
|
1496
1584
|
});
|
|
1497
1585
|
server.setRequestHandler(GetPromptRequestSchema, async (request) => {
|
|
1498
|
-
console.log(`[MCP Bridge] Handling get_prompt request: ${request.params.name}`);
|
|
1499
1586
|
try {
|
|
1500
1587
|
return await bridge$1.modelContext.getPrompt(request.params.name, request.params.arguments);
|
|
1501
1588
|
} catch (error) {
|
|
1502
|
-
|
|
1589
|
+
bridgeLogger.error(`Error getting prompt ${request.params.name}:`, error);
|
|
1503
1590
|
throw error;
|
|
1504
1591
|
}
|
|
1505
1592
|
});
|
|
1506
1593
|
};
|
|
1507
1594
|
const customTransport = transportOptions?.create?.();
|
|
1508
1595
|
if (customTransport) {
|
|
1509
|
-
console.log("[Web Model Context] Using custom transport");
|
|
1510
1596
|
const server = new Server({
|
|
1511
1597
|
name: hostname,
|
|
1512
1598
|
version: "1.0.0"
|
|
@@ -1526,10 +1612,8 @@ function initializeMCPBridge(options) {
|
|
|
1526
1612
|
bridge$1.modelContext = new WebModelContext(bridge$1);
|
|
1527
1613
|
setupServerHandlers(server, bridge$1);
|
|
1528
1614
|
server.connect(customTransport);
|
|
1529
|
-
console.log("[Web Model Context] MCP server connected with custom transport");
|
|
1530
1615
|
return bridge$1;
|
|
1531
1616
|
}
|
|
1532
|
-
console.log("[Web Model Context] Using dual-server mode");
|
|
1533
1617
|
const tabServerEnabled = transportOptions?.tabServer !== false;
|
|
1534
1618
|
const tabServer = new Server({
|
|
1535
1619
|
name: `${hostname}-tab`,
|
|
@@ -1556,12 +1640,10 @@ function initializeMCPBridge(options) {
|
|
|
1556
1640
|
...restTabServerOptions
|
|
1557
1641
|
});
|
|
1558
1642
|
tabServer.connect(tabTransport);
|
|
1559
|
-
console.log("[Web Model Context] Tab server connected");
|
|
1560
1643
|
}
|
|
1561
1644
|
const isInIframe = typeof window !== "undefined" && window.parent !== window;
|
|
1562
1645
|
const iframeServerConfig = transportOptions?.iframeServer;
|
|
1563
1646
|
if (iframeServerConfig !== false && (iframeServerConfig !== void 0 || isInIframe)) {
|
|
1564
|
-
console.log("[Web Model Context] Enabling iframe server");
|
|
1565
1647
|
const iframeServer = new Server({
|
|
1566
1648
|
name: `${hostname}-iframe`,
|
|
1567
1649
|
version: "1.0.0"
|
|
@@ -1578,7 +1660,6 @@ function initializeMCPBridge(options) {
|
|
|
1578
1660
|
});
|
|
1579
1661
|
iframeServer.connect(iframeTransport);
|
|
1580
1662
|
bridge.iframeServer = iframeServer;
|
|
1581
|
-
console.log("[Web Model Context] Iframe server connected");
|
|
1582
1663
|
}
|
|
1583
1664
|
return bridge;
|
|
1584
1665
|
}
|
|
@@ -1603,8 +1684,9 @@ function initializeMCPBridge(options) {
|
|
|
1603
1684
|
* ```
|
|
1604
1685
|
*/
|
|
1605
1686
|
function initializeWebModelContext(options) {
|
|
1687
|
+
/* c8 ignore next 4 */
|
|
1606
1688
|
if (typeof window === "undefined") {
|
|
1607
|
-
|
|
1689
|
+
logger$1.warn("Not in browser environment, skipping initialization");
|
|
1608
1690
|
return;
|
|
1609
1691
|
}
|
|
1610
1692
|
const effectiveOptions = options ?? window.__webModelContextOptions;
|
|
@@ -1613,12 +1695,12 @@ function initializeWebModelContext(options) {
|
|
|
1613
1695
|
const nativeContext = window.navigator.modelContext;
|
|
1614
1696
|
const nativeTesting = window.navigator.modelContextTesting;
|
|
1615
1697
|
if (!nativeContext || !nativeTesting) {
|
|
1616
|
-
|
|
1698
|
+
logger$1.error("Native API detection mismatch");
|
|
1617
1699
|
return;
|
|
1618
1700
|
}
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1701
|
+
logger$1.info("✅ Native Chromium API detected");
|
|
1702
|
+
logger$1.info(" Using native implementation with MCP bridge synchronization");
|
|
1703
|
+
logger$1.info(" Native API will automatically collect tools from embedded iframes");
|
|
1622
1704
|
try {
|
|
1623
1705
|
const bridge = initializeMCPBridge(effectiveOptions);
|
|
1624
1706
|
bridge.modelContext = new NativeModelContextAdapter(bridge, nativeContext, nativeTesting);
|
|
@@ -1628,29 +1710,29 @@ function initializeWebModelContext(options) {
|
|
|
1628
1710
|
writable: false,
|
|
1629
1711
|
configurable: true
|
|
1630
1712
|
});
|
|
1631
|
-
|
|
1632
|
-
|
|
1713
|
+
logger$1.info("✅ MCP bridge synced with native API");
|
|
1714
|
+
logger$1.info(" MCP clients will receive automatic tool updates from native registry");
|
|
1633
1715
|
} catch (error) {
|
|
1634
|
-
|
|
1716
|
+
logger$1.error("Failed to initialize native adapter:", error);
|
|
1635
1717
|
throw error;
|
|
1636
1718
|
}
|
|
1637
1719
|
return;
|
|
1638
1720
|
}
|
|
1639
1721
|
if (native.hasNativeContext && !native.hasNativeTesting) {
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1722
|
+
logger$1.warn("Partial native API detected");
|
|
1723
|
+
logger$1.warn(" navigator.modelContext exists but navigator.modelContextTesting is missing");
|
|
1724
|
+
logger$1.warn(" Cannot sync with native API. Please enable experimental features:");
|
|
1725
|
+
logger$1.warn(" - Navigate to chrome://flags");
|
|
1726
|
+
logger$1.warn(" - Enable \"Experimental Web Platform Features\"");
|
|
1727
|
+
logger$1.warn(" - Or launch with: --enable-experimental-web-platform-features");
|
|
1728
|
+
logger$1.warn(" Skipping initialization to avoid conflicts");
|
|
1647
1729
|
return;
|
|
1648
1730
|
}
|
|
1649
1731
|
if (window.navigator.modelContext) {
|
|
1650
|
-
|
|
1732
|
+
logger$1.warn("window.navigator.modelContext already exists, skipping initialization");
|
|
1651
1733
|
return;
|
|
1652
1734
|
}
|
|
1653
|
-
|
|
1735
|
+
logger$1.info("Native API not detected, installing polyfill");
|
|
1654
1736
|
try {
|
|
1655
1737
|
const bridge = initializeMCPBridge(effectiveOptions);
|
|
1656
1738
|
Object.defineProperty(window.navigator, "modelContext", {
|
|
@@ -1663,12 +1745,12 @@ function initializeWebModelContext(options) {
|
|
|
1663
1745
|
writable: false,
|
|
1664
1746
|
configurable: true
|
|
1665
1747
|
});
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1748
|
+
logger$1.info("✅ window.navigator.modelContext initialized successfully");
|
|
1749
|
+
testingLogger.info("Installing polyfill");
|
|
1750
|
+
testingLogger.info(" 💡 To use the native implementation in Chromium:");
|
|
1751
|
+
testingLogger.info(" - Navigate to chrome://flags");
|
|
1752
|
+
testingLogger.info(" - Enable \"Experimental Web Platform Features\"");
|
|
1753
|
+
testingLogger.info(" - Or launch with: --enable-experimental-web-platform-features");
|
|
1672
1754
|
const testingAPI = new WebModelContextTesting(bridge);
|
|
1673
1755
|
bridge.modelContextTesting = testingAPI;
|
|
1674
1756
|
bridge.modelContext.setTestingAPI(testingAPI);
|
|
@@ -1677,9 +1759,9 @@ function initializeWebModelContext(options) {
|
|
|
1677
1759
|
writable: false,
|
|
1678
1760
|
configurable: true
|
|
1679
1761
|
});
|
|
1680
|
-
|
|
1762
|
+
testingLogger.info("✅ Polyfill installed at window.navigator.modelContextTesting");
|
|
1681
1763
|
} catch (error) {
|
|
1682
|
-
|
|
1764
|
+
logger$1.error("Failed to initialize:", error);
|
|
1683
1765
|
throw error;
|
|
1684
1766
|
}
|
|
1685
1767
|
}
|
|
@@ -1696,21 +1778,23 @@ function initializeWebModelContext(options) {
|
|
|
1696
1778
|
* ```
|
|
1697
1779
|
*/
|
|
1698
1780
|
function cleanupWebModelContext() {
|
|
1781
|
+
/* c8 ignore next */
|
|
1699
1782
|
if (typeof window === "undefined") return;
|
|
1700
1783
|
if (window.__mcpBridge) try {
|
|
1701
1784
|
window.__mcpBridge.tabServer.close();
|
|
1702
1785
|
if (window.__mcpBridge.iframeServer) window.__mcpBridge.iframeServer.close();
|
|
1703
1786
|
} catch (error) {
|
|
1704
|
-
|
|
1787
|
+
logger$1.warn("Error closing MCP servers:", error);
|
|
1705
1788
|
}
|
|
1706
1789
|
delete window.navigator.modelContext;
|
|
1707
1790
|
delete window.navigator.modelContextTesting;
|
|
1708
1791
|
delete window.__mcpBridge;
|
|
1709
|
-
|
|
1792
|
+
logger$1.info("Cleaned up");
|
|
1710
1793
|
}
|
|
1711
1794
|
|
|
1712
1795
|
//#endregion
|
|
1713
1796
|
//#region src/index.ts
|
|
1797
|
+
const logger = createLogger("WebModelContext");
|
|
1714
1798
|
function mergeTransportOptions(base, override) {
|
|
1715
1799
|
if (!base) return override;
|
|
1716
1800
|
if (!override) return base;
|
|
@@ -1738,7 +1822,7 @@ function parseScriptTagOptions(script) {
|
|
|
1738
1822
|
if (dataset.webmcpOptions) try {
|
|
1739
1823
|
return JSON.parse(dataset.webmcpOptions);
|
|
1740
1824
|
} catch (error) {
|
|
1741
|
-
|
|
1825
|
+
logger.error("Invalid JSON in data-webmcp-options:", error);
|
|
1742
1826
|
return;
|
|
1743
1827
|
}
|
|
1744
1828
|
const options = {};
|
|
@@ -1781,10 +1865,10 @@ if (typeof window !== "undefined" && typeof document !== "undefined") {
|
|
|
1781
1865
|
try {
|
|
1782
1866
|
if (shouldAutoInitialize) initializeWebModelContext(mergedOptions);
|
|
1783
1867
|
} catch (error) {
|
|
1784
|
-
|
|
1868
|
+
logger.error("Auto-initialization failed:", error);
|
|
1785
1869
|
}
|
|
1786
1870
|
}
|
|
1787
1871
|
|
|
1788
1872
|
//#endregion
|
|
1789
|
-
export { cleanupWebModelContext, initializeWebModelContext, zodToJsonSchema };
|
|
1873
|
+
export { cleanupWebModelContext, createLogger, initializeWebModelContext, zodToJsonSchema };
|
|
1790
1874
|
//# sourceMappingURL=index.js.map
|