@mcp-b/global 1.2.2-beta.0 → 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/dist/index.js CHANGED
@@ -1,6 +1,8 @@
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 { z } from "zod/v4";
3
+ import { jsonSchemaToZod } from "@n8n/json-schema-to-zod";
4
+ import { z } from "zod";
5
+ import { zodToJsonSchema as zodToJsonSchema$1 } from "zod-to-json-schema";
4
6
 
5
7
  //#region src/logger.ts
6
8
  /**
@@ -90,71 +92,70 @@ function createLogger(namespace) {
90
92
  //#endregion
91
93
  //#region src/validation.ts
92
94
  const logger$2 = createLogger("WebModelContext");
93
- /**
94
- * Detect if a schema is a Zod schema object (Record<string, ZodType>)
95
- * or a JSON Schema object.
96
- *
97
- * Uses duck-typing to detect Zod 4 schemas by checking for the `_zod` property.
98
- */
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
+ };
99
104
  function isZodSchema(schema) {
100
- if (typeof schema !== "object" || schema === null) return false;
105
+ if (!isRecord(schema)) return false;
101
106
  if ("type" in schema && typeof schema.type === "string") return false;
102
107
  const values = Object.values(schema);
103
108
  if (values.length === 0) return false;
104
- return values.some((val) => val != null && typeof val === "object" && "_zod" in val);
109
+ return values.some((v) => isRecord(v) && "_def" in v);
105
110
  }
106
- /**
107
- * Convert JSON Schema to Zod validator
108
- * Uses Zod 4's native z.fromJSONSchema() for conversion
109
- */
110
- function jsonSchemaToZod(jsonSchema) {
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;
127
+ }
128
+ function jsonSchemaToZod$1(jsonSchema) {
111
129
  try {
112
- return z.fromJSONSchema(jsonSchema);
130
+ return jsonSchemaToZod(jsonSchema);
113
131
  } catch (error) {
114
- logger$2.warn("Failed to convert JSON Schema to Zod:", error);
132
+ logger$2.warn("jsonSchemaToZod failed:", error);
115
133
  return z.object({}).passthrough();
116
134
  }
117
135
  }
118
- /**
119
- * Convert Zod schema object to JSON Schema
120
- * Uses Zod 4's native z.toJSONSchema() for conversion
121
- *
122
- * @param schema - Record of Zod type definitions (e.g., { name: z.string(), age: z.number() })
123
- * @returns JSON Schema object compatible with MCP InputSchema
124
- */
125
- function zodToJsonSchema(schema) {
126
- const zodObject = z.object(schema);
127
- const { $schema: _,...rest } = z.toJSONSchema(zodObject);
128
- return rest;
129
- }
130
- /**
131
- * Normalize a schema to both JSON Schema and Zod formats
132
- * Detects which format is provided and converts to the other
133
- */
134
136
  function normalizeSchema(schema) {
135
- if (isZodSchema(schema)) return {
136
- jsonSchema: zodToJsonSchema(schema),
137
- zodValidator: z.object(schema)
138
- };
139
- const jsonSchema = schema;
137
+ if (isZodSchema(schema)) {
138
+ const jsonSchema = zodToJsonSchema(schema);
139
+ return {
140
+ jsonSchema,
141
+ zodValidator: jsonSchemaToZod$1(jsonSchema)
142
+ };
143
+ }
140
144
  return {
141
- jsonSchema,
142
- zodValidator: jsonSchemaToZod(jsonSchema)
145
+ jsonSchema: schema,
146
+ zodValidator: jsonSchemaToZod$1(schema)
143
147
  };
144
148
  }
145
- /**
146
- * Validate data with Zod schema and return formatted result
147
- */
148
149
  function validateWithZod(data, validator) {
149
150
  const result = validator.safeParse(data);
150
- if (!result.success) return {
151
- success: false,
152
- error: `Validation failed:\n${result.error.issues.map((err) => ` - ${err.path.join(".") || "root"}: ${err.message}`).join("\n")}`
153
- };
154
- return {
151
+ if (result.success) return {
155
152
  success: true,
156
153
  data: result.data
157
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
+ };
158
159
  }
159
160
 
160
161
  //#endregion
@@ -181,6 +182,7 @@ const POLYFILL_MARKER_PROPERTY = "__isWebMCPPolyfill";
181
182
  * @returns Detection result with flags for native context and testing API availability
182
183
  */
183
184
  function detectNativeAPI() {
185
+ /* c8 ignore next 2 */
184
186
  if (typeof window === "undefined" || typeof navigator === "undefined") return {
185
187
  hasNativeContext: false,
186
188
  hasNativeTesting: false
@@ -188,8 +190,8 @@ function detectNativeAPI() {
188
190
  const modelContext = navigator.modelContext;
189
191
  const modelContextTesting = navigator.modelContextTesting;
190
192
  if (!modelContext || !modelContextTesting) return {
191
- hasNativeContext: false,
192
- hasNativeTesting: false
193
+ hasNativeContext: Boolean(modelContext),
194
+ hasNativeTesting: Boolean(modelContextTesting)
193
195
  };
194
196
  if (POLYFILL_MARKER_PROPERTY in modelContextTesting && modelContextTesting[POLYFILL_MARKER_PROPERTY] === true) return {
195
197
  hasNativeContext: false,
@@ -258,7 +260,7 @@ var NativeModelContextAdapter = class {
258
260
  const result = await this.nativeTesting.executeTool(toolInfo.name, JSON.stringify(args));
259
261
  return this.convertToToolResponse(result);
260
262
  },
261
- inputValidator: jsonSchemaToZod(inputSchema)
263
+ inputValidator: jsonSchemaToZod$1(inputSchema)
262
264
  };
263
265
  this.bridge.tools.set(toolInfo.name, validatedTool);
264
266
  } catch (error) {
@@ -316,24 +318,34 @@ var NativeModelContextAdapter = class {
316
318
  }
317
319
  /**
318
320
  * Provides context (tools) to AI models via the native API.
319
- * Delegates to navigator.modelContext.provideContext().
321
+ * Converts Zod schemas to JSON Schema before passing to native API.
320
322
  * Tool change callback will fire and trigger sync automatically.
321
323
  *
322
324
  * @param {ModelContextInput} context - Context containing tools to register
323
325
  */
324
326
  provideContext(context) {
325
- this.nativeContext.provideContext(context);
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);
326
334
  }
327
335
  /**
328
336
  * Registers a single tool dynamically via the native API.
329
- * Delegates to navigator.modelContext.registerTool().
337
+ * Converts Zod schemas to JSON Schema before passing to native API.
330
338
  * Tool change callback will fire and trigger sync automatically.
331
339
  *
332
340
  * @param {ToolDescriptor} tool - The tool descriptor to register
333
341
  * @returns {{unregister: () => void}} Object with unregister function
334
342
  */
335
343
  registerTool(tool) {
336
- return this.nativeContext.registerTool(tool);
344
+ const normalizedTool = {
345
+ ...tool,
346
+ inputSchema: normalizeSchema(tool.inputSchema).jsonSchema
347
+ };
348
+ return this.nativeContext.registerTool(normalizedTool);
337
349
  }
338
350
  /**
339
351
  * Unregisters a tool by name via the native API.
@@ -354,13 +366,15 @@ var NativeModelContextAdapter = class {
354
366
  /**
355
367
  * Executes a tool via the native API.
356
368
  * Delegates to navigator.modelContextTesting.executeTool() with JSON string args.
369
+ * Note: skipValidation option is ignored - native API handles its own validation.
357
370
  *
358
371
  * @param {string} toolName - Name of the tool to execute
359
372
  * @param {Record<string, unknown>} args - Arguments to pass to the tool
373
+ * @param {Object} [_options] - Execution options (ignored for native adapter)
360
374
  * @returns {Promise<ToolResponse>} The tool's response in MCP format
361
375
  * @internal
362
376
  */
363
- async executeTool(toolName, args) {
377
+ async executeTool(toolName, args, _options) {
364
378
  try {
365
379
  const result = await this.nativeTesting.executeTool(toolName, JSON.stringify(args));
366
380
  return this.convertToToolResponse(result);
@@ -898,11 +912,11 @@ var WebModelContext = class {
898
912
  * @private
899
913
  */
900
914
  validateResource(resource) {
901
- const templateParamRegex = /\{([^}]+)\}/g;
915
+ const templateParamRegex = /\{([^}]{1,100})\}/g;
902
916
  const templateParams = [];
903
917
  for (const match of resource.uri.matchAll(templateParamRegex)) {
904
918
  const paramName = match[1];
905
- if (paramName) templateParams.push(paramName);
919
+ templateParams.push(paramName);
906
920
  }
907
921
  return {
908
922
  uri: resource.uri,
@@ -1318,7 +1332,13 @@ var WebModelContext = class {
1318
1332
  async readResource(uri) {
1319
1333
  const staticResource = this.bridge.resources.get(uri);
1320
1334
  if (staticResource && !staticResource.isTemplate) try {
1321
- const parsedUri = new URL(uri);
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
+ }
1322
1342
  return await staticResource.read(parsedUri);
1323
1343
  } catch (error) {
1324
1344
  logger$1.error(`Error reading resource ${uri}:`, error);
@@ -1328,7 +1348,13 @@ var WebModelContext = class {
1328
1348
  if (!resource.isTemplate) continue;
1329
1349
  const params = this.matchUriTemplate(resource.uri, uri);
1330
1350
  if (params) try {
1331
- const parsedUri = new URL(uri);
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
+ }
1332
1358
  return await resource.read(parsedUri, params);
1333
1359
  } catch (error) {
1334
1360
  logger$1.error(`Error reading resource ${uri}:`, error);
@@ -1361,8 +1387,7 @@ var WebModelContext = class {
1361
1387
  const params = {};
1362
1388
  for (let i = 0; i < paramNames.length; i++) {
1363
1389
  const paramName = paramNames[i];
1364
- const paramValue = match[i + 1];
1365
- if (paramName !== void 0 && paramValue !== void 0) params[paramName] = paramValue;
1390
+ params[paramName] = match[i + 1];
1366
1391
  }
1367
1392
  return params;
1368
1393
  }
@@ -1395,7 +1420,7 @@ var WebModelContext = class {
1395
1420
  /**
1396
1421
  * Executes a tool with validation and event dispatch.
1397
1422
  * Follows this sequence:
1398
- * 1. Validates input arguments against schema
1423
+ * 1. Validates input arguments against schema (unless skipValidation is true)
1399
1424
  * 2. Records tool call in testing API (if available)
1400
1425
  * 3. Checks for mock response (if testing)
1401
1426
  * 4. Dispatches 'toolcall' event to listeners
@@ -1404,25 +1429,31 @@ var WebModelContext = class {
1404
1429
  *
1405
1430
  * @param {string} toolName - Name of the tool to execute
1406
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)
1407
1434
  * @returns {Promise<ToolResponse>} The tool's response
1408
1435
  * @throws {Error} If tool is not found
1409
1436
  * @internal
1410
1437
  */
1411
- async executeTool(toolName, args) {
1438
+ async executeTool(toolName, args, options) {
1412
1439
  const tool = this.bridge.tools.get(toolName);
1413
1440
  if (!tool) throw new Error(`Tool not found: ${toolName}`);
1414
- const validation = validateWithZod(args, tool.inputValidator);
1415
- if (!validation.success) {
1416
- logger$1.error(`Input validation failed for ${toolName}:`, validation.error);
1417
- return {
1418
- content: [{
1419
- type: "text",
1420
- text: `Input validation error for tool "${toolName}":\n${validation.error}`
1421
- }],
1422
- isError: true
1423
- };
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;
1424
1456
  }
1425
- const validatedArgs = validation.data;
1426
1457
  if (this.testingAPI) this.testingAPI.recordToolCall(toolName, validatedArgs);
1427
1458
  if (this.testingAPI?.hasMockResponse(toolName)) {
1428
1459
  const mockResponse = this.testingAPI.getMockResponse(toolName);
@@ -1640,6 +1671,7 @@ function initializeMCPBridge(options) {
1640
1671
  * ```
1641
1672
  */
1642
1673
  function initializeWebModelContext(options) {
1674
+ /* c8 ignore next 4 */
1643
1675
  if (typeof window === "undefined") {
1644
1676
  logger$1.warn("Not in browser environment, skipping initialization");
1645
1677
  return;
@@ -1733,6 +1765,7 @@ function initializeWebModelContext(options) {
1733
1765
  * ```
1734
1766
  */
1735
1767
  function cleanupWebModelContext() {
1768
+ /* c8 ignore next */
1736
1769
  if (typeof window === "undefined") return;
1737
1770
  if (window.__mcpBridge) try {
1738
1771
  window.__mcpBridge.tabServer.close();