@mcp-b/global 1.1.2 → 1.1.3-beta.2

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,7 +1,8 @@
1
1
  import { IframeChildTransport, TabServerTransport } from "@mcp-b/transports";
2
- import { CallToolRequestSchema, ListToolsRequestSchema, Server } from "@mcp-b/webmcp-ts-sdk";
2
+ import { CallToolRequestSchema, GetPromptRequestSchema, ListPromptsRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema, Server } from "@mcp-b/webmcp-ts-sdk";
3
3
  import { jsonSchemaToZod } from "@composio/json-schema-to-zod";
4
4
  import { z } from "zod";
5
+ import { zodToJsonSchema as zodToJsonSchema$1 } from "zod-to-json-schema";
5
6
 
6
7
  //#region src/validation.ts
7
8
  /**
@@ -29,44 +30,17 @@ function jsonSchemaToZod$1(jsonSchema) {
29
30
  }
30
31
  /**
31
32
  * Convert Zod schema object to JSON Schema
32
- * Based on react-webmcp implementation
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
33
37
  */
34
38
  function zodToJsonSchema(schema) {
35
- const properties = {};
36
- const required = [];
37
- for (const [key, zodType] of Object.entries(schema)) {
38
- const description = zodType.description || void 0;
39
- let type = "string";
40
- let enumValues;
41
- let items;
42
- if (zodType instanceof z.ZodString) type = "string";
43
- else if (zodType instanceof z.ZodNumber) type = "number";
44
- else if (zodType instanceof z.ZodBoolean) type = "boolean";
45
- else if (zodType instanceof z.ZodArray) {
46
- type = "array";
47
- const elementType = zodType.element;
48
- if (elementType instanceof z.ZodString) items = { type: "string" };
49
- else if (elementType instanceof z.ZodNumber) items = { type: "number" };
50
- else if (elementType instanceof z.ZodBoolean) items = { type: "boolean" };
51
- else items = { type: "string" };
52
- } else if (zodType instanceof z.ZodObject) type = "object";
53
- else if (zodType instanceof z.ZodEnum) {
54
- type = "string";
55
- const enumDef = zodType._def;
56
- if (enumDef?.values) enumValues = enumDef.values;
57
- }
58
- const propertySchema = { type };
59
- if (description) propertySchema.description = description;
60
- if (enumValues) propertySchema.enum = enumValues;
61
- if (items) propertySchema.items = items;
62
- properties[key] = propertySchema;
63
- if (!zodType.isOptional()) required.push(key);
64
- }
65
- return {
66
- type: "object",
67
- properties,
68
- ...required.length > 0 && { required }
69
- };
39
+ const { $schema: _,...rest } = zodToJsonSchema$1(z.object(schema), {
40
+ $refStrategy: "none",
41
+ target: "jsonSchema7"
42
+ });
43
+ return rest;
70
44
  }
71
45
  /**
72
46
  * Normalize a schema to both JSON Schema and Zod formats
@@ -326,6 +300,75 @@ var NativeModelContextAdapter = class {
326
300
  }));
327
301
  }
328
302
  /**
303
+ * Registers a resource dynamically.
304
+ * Note: Native Chromium API does not yet support resources.
305
+ * This is a polyfill-only feature.
306
+ */
307
+ registerResource(_resource) {
308
+ console.warn("[Native Adapter] registerResource is not supported by native API");
309
+ return { unregister: () => {} };
310
+ }
311
+ /**
312
+ * Unregisters a resource by URI.
313
+ * Note: Native Chromium API does not yet support resources.
314
+ */
315
+ unregisterResource(_uri) {
316
+ console.warn("[Native Adapter] unregisterResource is not supported by native API");
317
+ }
318
+ /**
319
+ * Lists all registered resources.
320
+ * Note: Native Chromium API does not yet support resources.
321
+ */
322
+ listResources() {
323
+ return [];
324
+ }
325
+ /**
326
+ * Lists all resource templates.
327
+ * Note: Native Chromium API does not yet support resources.
328
+ */
329
+ listResourceTemplates() {
330
+ return [];
331
+ }
332
+ /**
333
+ * Reads a resource by URI.
334
+ * Note: Native Chromium API does not yet support resources.
335
+ * @internal
336
+ */
337
+ async readResource(_uri) {
338
+ throw new Error("[Native Adapter] readResource is not supported by native API");
339
+ }
340
+ /**
341
+ * Registers a prompt dynamically.
342
+ * Note: Native Chromium API does not yet support prompts.
343
+ * This is a polyfill-only feature.
344
+ */
345
+ registerPrompt(_prompt) {
346
+ console.warn("[Native Adapter] registerPrompt is not supported by native API");
347
+ return { unregister: () => {} };
348
+ }
349
+ /**
350
+ * Unregisters a prompt by name.
351
+ * Note: Native Chromium API does not yet support prompts.
352
+ */
353
+ unregisterPrompt(_name) {
354
+ console.warn("[Native Adapter] unregisterPrompt is not supported by native API");
355
+ }
356
+ /**
357
+ * Lists all registered prompts.
358
+ * Note: Native Chromium API does not yet support prompts.
359
+ */
360
+ listPrompts() {
361
+ return [];
362
+ }
363
+ /**
364
+ * Gets a prompt with arguments.
365
+ * Note: Native Chromium API does not yet support prompts.
366
+ * @internal
367
+ */
368
+ async getPrompt(_name, _args) {
369
+ throw new Error("[Native Adapter] getPrompt is not supported by native API");
370
+ }
371
+ /**
329
372
  * Adds an event listener for tool call events.
330
373
  * Delegates to the native API's addEventListener.
331
374
  *
@@ -357,6 +400,28 @@ var NativeModelContextAdapter = class {
357
400
  dispatchEvent(event) {
358
401
  return this.nativeContext.dispatchEvent(event);
359
402
  }
403
+ /**
404
+ * Request an LLM completion from the connected client.
405
+ * Note: Native Chromium API does not yet support sampling.
406
+ * This is handled by the polyfill.
407
+ */
408
+ async createMessage(params) {
409
+ console.log("[Native Adapter] Requesting sampling from client");
410
+ const underlyingServer = this.bridge.tabServer.server;
411
+ if (!underlyingServer?.createMessage) throw new Error("Sampling is not supported: no connected client with sampling capability");
412
+ return underlyingServer.createMessage(params);
413
+ }
414
+ /**
415
+ * Request user input from the connected client.
416
+ * Note: Native Chromium API does not yet support elicitation.
417
+ * This is handled by the polyfill.
418
+ */
419
+ async elicitInput(params) {
420
+ console.log("[Native Adapter] Requesting elicitation from client");
421
+ const underlyingServer = this.bridge.tabServer.server;
422
+ if (!underlyingServer?.elicitInput) throw new Error("Elicitation is not supported: no connected client with elicitation capability");
423
+ return underlyingServer.elicitInput(params);
424
+ }
360
425
  };
361
426
  /**
362
427
  * ToolCallEvent implementation for the Web Model Context API.
@@ -612,8 +677,16 @@ var WebModelContext = class {
612
677
  eventTarget;
613
678
  provideContextTools;
614
679
  dynamicTools;
615
- registrationTimestamps;
616
- unregisterFunctions;
680
+ provideContextResources;
681
+ dynamicResources;
682
+ provideContextPrompts;
683
+ dynamicPrompts;
684
+ toolRegistrationTimestamps;
685
+ resourceRegistrationTimestamps;
686
+ promptRegistrationTimestamps;
687
+ toolUnregisterFunctions;
688
+ resourceUnregisterFunctions;
689
+ promptUnregisterFunctions;
617
690
  testingAPI;
618
691
  /**
619
692
  * Creates a new WebModelContext instance.
@@ -625,8 +698,16 @@ var WebModelContext = class {
625
698
  this.eventTarget = new EventTarget();
626
699
  this.provideContextTools = /* @__PURE__ */ new Map();
627
700
  this.dynamicTools = /* @__PURE__ */ new Map();
628
- this.registrationTimestamps = /* @__PURE__ */ new Map();
629
- this.unregisterFunctions = /* @__PURE__ */ new Map();
701
+ this.toolRegistrationTimestamps = /* @__PURE__ */ new Map();
702
+ this.toolUnregisterFunctions = /* @__PURE__ */ new Map();
703
+ this.provideContextResources = /* @__PURE__ */ new Map();
704
+ this.dynamicResources = /* @__PURE__ */ new Map();
705
+ this.resourceRegistrationTimestamps = /* @__PURE__ */ new Map();
706
+ this.resourceUnregisterFunctions = /* @__PURE__ */ new Map();
707
+ this.provideContextPrompts = /* @__PURE__ */ new Map();
708
+ this.dynamicPrompts = /* @__PURE__ */ new Map();
709
+ this.promptRegistrationTimestamps = /* @__PURE__ */ new Map();
710
+ this.promptUnregisterFunctions = /* @__PURE__ */ new Map();
630
711
  }
631
712
  /**
632
713
  * Sets the testing API instance.
@@ -668,17 +749,22 @@ var WebModelContext = class {
668
749
  return this.eventTarget.dispatchEvent(event);
669
750
  }
670
751
  /**
671
- * Provides context (tools) to AI models by registering base tools (Bucket A).
672
- * Clears and replaces all previously registered base tools while preserving
673
- * dynamic tools registered via registerTool().
752
+ * Provides context (tools, resources, prompts) to AI models by registering base items (Bucket A).
753
+ * Clears and replaces all previously registered base items while preserving
754
+ * dynamic items registered via register* methods.
674
755
  *
675
- * @param {ModelContextInput} context - Context containing tools to register
676
- * @throws {Error} If a tool name collides with an existing dynamic tool
756
+ * @param {ModelContextInput} context - Context containing tools, resources, and prompts to register
757
+ * @throws {Error} If a name/uri collides with existing dynamic items
677
758
  */
678
759
  provideContext(context) {
679
- console.log(`[Web Model Context] Registering ${context.tools.length} tools via provideContext`);
760
+ const toolCount = context.tools?.length ?? 0;
761
+ const resourceCount = context.resources?.length ?? 0;
762
+ const promptCount = context.prompts?.length ?? 0;
763
+ console.log(`[Web Model Context] provideContext: ${toolCount} tools, ${resourceCount} resources, ${promptCount} prompts`);
680
764
  this.provideContextTools.clear();
681
- for (const tool of context.tools) {
765
+ this.provideContextResources.clear();
766
+ this.provideContextPrompts.clear();
767
+ for (const tool of context.tools ?? []) {
682
768
  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.`);
683
769
  const { jsonSchema: inputJson, zodValidator: inputZod } = normalizeSchema(tool.inputSchema);
684
770
  const normalizedOutput = tool.outputSchema ? normalizeSchema(tool.outputSchema) : null;
@@ -694,8 +780,63 @@ var WebModelContext = class {
694
780
  };
695
781
  this.provideContextTools.set(tool.name, validatedTool);
696
782
  }
783
+ for (const resource of context.resources ?? []) {
784
+ if (this.dynamicResources.has(resource.uri)) throw new Error(`[Web Model Context] Resource URI collision: "${resource.uri}" is already registered via registerResource(). Please use a different URI or unregister the dynamic resource first.`);
785
+ const validatedResource = this.validateResource(resource);
786
+ this.provideContextResources.set(resource.uri, validatedResource);
787
+ }
788
+ for (const prompt of context.prompts ?? []) {
789
+ if (this.dynamicPrompts.has(prompt.name)) throw new Error(`[Web Model Context] Prompt name collision: "${prompt.name}" is already registered via registerPrompt(). Please use a different name or unregister the dynamic prompt first.`);
790
+ const validatedPrompt = this.validatePrompt(prompt);
791
+ this.provideContextPrompts.set(prompt.name, validatedPrompt);
792
+ }
697
793
  this.updateBridgeTools();
794
+ this.updateBridgeResources();
795
+ this.updateBridgePrompts();
698
796
  this.notifyToolsListChanged();
797
+ this.notifyResourcesListChanged();
798
+ this.notifyPromptsListChanged();
799
+ }
800
+ /**
801
+ * Validates and normalizes a resource descriptor.
802
+ * @private
803
+ */
804
+ validateResource(resource) {
805
+ const templateParamRegex = /\{([^}]+)\}/g;
806
+ const templateParams = [];
807
+ for (const match of resource.uri.matchAll(templateParamRegex)) {
808
+ const paramName = match[1];
809
+ if (paramName) templateParams.push(paramName);
810
+ }
811
+ return {
812
+ uri: resource.uri,
813
+ name: resource.name,
814
+ description: resource.description,
815
+ mimeType: resource.mimeType,
816
+ read: resource.read,
817
+ isTemplate: templateParams.length > 0,
818
+ templateParams
819
+ };
820
+ }
821
+ /**
822
+ * Validates and normalizes a prompt descriptor.
823
+ * @private
824
+ */
825
+ validatePrompt(prompt) {
826
+ let argsSchema;
827
+ let argsValidator;
828
+ if (prompt.argsSchema) {
829
+ const normalized = normalizeSchema(prompt.argsSchema);
830
+ argsSchema = normalized.jsonSchema;
831
+ argsValidator = normalized.zodValidator;
832
+ }
833
+ return {
834
+ name: prompt.name,
835
+ description: prompt.description,
836
+ argsSchema,
837
+ get: prompt.get,
838
+ argsValidator
839
+ };
699
840
  }
700
841
  /**
701
842
  * Registers a single tool dynamically (Bucket B).
@@ -708,10 +849,10 @@ var WebModelContext = class {
708
849
  registerTool(tool) {
709
850
  console.log(`[Web Model Context] Registering tool dynamically: ${tool.name}`);
710
851
  const now = Date.now();
711
- const lastRegistration = this.registrationTimestamps.get(tool.name);
852
+ const lastRegistration = this.toolRegistrationTimestamps.get(tool.name);
712
853
  if (lastRegistration && now - lastRegistration < RAPID_DUPLICATE_WINDOW_MS) {
713
854
  console.warn(`[Web Model Context] 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.`);
714
- const existingUnregister = this.unregisterFunctions.get(tool.name);
855
+ const existingUnregister = this.toolUnregisterFunctions.get(tool.name);
715
856
  if (existingUnregister) return { unregister: existingUnregister };
716
857
  }
717
858
  if (this.provideContextTools.has(tool.name)) throw new Error(`[Web Model Context] Tool name collision: "${tool.name}" is already registered via provideContext(). Please use a different name or update your provideContext() call.`);
@@ -729,7 +870,7 @@ var WebModelContext = class {
729
870
  ...normalizedOutput && { outputValidator: normalizedOutput.zodValidator }
730
871
  };
731
872
  this.dynamicTools.set(tool.name, validatedTool);
732
- this.registrationTimestamps.set(tool.name, now);
873
+ this.toolRegistrationTimestamps.set(tool.name, now);
733
874
  this.updateBridgeTools();
734
875
  this.notifyToolsListChanged();
735
876
  const unregisterFn = () => {
@@ -740,15 +881,186 @@ var WebModelContext = class {
740
881
  return;
741
882
  }
742
883
  this.dynamicTools.delete(tool.name);
743
- this.registrationTimestamps.delete(tool.name);
744
- this.unregisterFunctions.delete(tool.name);
884
+ this.toolRegistrationTimestamps.delete(tool.name);
885
+ this.toolUnregisterFunctions.delete(tool.name);
745
886
  this.updateBridgeTools();
746
887
  this.notifyToolsListChanged();
747
888
  };
748
- this.unregisterFunctions.set(tool.name, unregisterFn);
889
+ this.toolUnregisterFunctions.set(tool.name, unregisterFn);
749
890
  return { unregister: unregisterFn };
750
891
  }
751
892
  /**
893
+ * Registers a single resource dynamically (Bucket B).
894
+ * Dynamic resources persist across provideContext() calls and can be independently managed.
895
+ *
896
+ * @param {ResourceDescriptor} resource - The resource descriptor to register
897
+ * @returns {{unregister: () => void}} Object with unregister function
898
+ * @throws {Error} If resource URI collides with existing resources
899
+ */
900
+ registerResource(resource) {
901
+ console.log(`[Web Model Context] Registering resource dynamically: ${resource.uri}`);
902
+ const now = Date.now();
903
+ const lastRegistration = this.resourceRegistrationTimestamps.get(resource.uri);
904
+ if (lastRegistration && now - lastRegistration < RAPID_DUPLICATE_WINDOW_MS) {
905
+ console.warn(`[Web Model Context] 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.`);
906
+ const existingUnregister = this.resourceUnregisterFunctions.get(resource.uri);
907
+ if (existingUnregister) return { unregister: existingUnregister };
908
+ }
909
+ if (this.provideContextResources.has(resource.uri)) throw new Error(`[Web Model Context] Resource URI collision: "${resource.uri}" is already registered via provideContext(). Please use a different URI or update your provideContext() call.`);
910
+ if (this.dynamicResources.has(resource.uri)) throw new Error(`[Web Model Context] Resource URI collision: "${resource.uri}" is already registered via registerResource(). Please unregister it first or use a different URI.`);
911
+ const validatedResource = this.validateResource(resource);
912
+ this.dynamicResources.set(resource.uri, validatedResource);
913
+ this.resourceRegistrationTimestamps.set(resource.uri, now);
914
+ this.updateBridgeResources();
915
+ this.notifyResourcesListChanged();
916
+ const unregisterFn = () => {
917
+ console.log(`[Web Model Context] Unregistering resource: ${resource.uri}`);
918
+ 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.`);
919
+ if (!this.dynamicResources.has(resource.uri)) {
920
+ console.warn(`[Web Model Context] Resource "${resource.uri}" is not registered, ignoring unregister call`);
921
+ return;
922
+ }
923
+ this.dynamicResources.delete(resource.uri);
924
+ this.resourceRegistrationTimestamps.delete(resource.uri);
925
+ this.resourceUnregisterFunctions.delete(resource.uri);
926
+ this.updateBridgeResources();
927
+ this.notifyResourcesListChanged();
928
+ };
929
+ this.resourceUnregisterFunctions.set(resource.uri, unregisterFn);
930
+ return { unregister: unregisterFn };
931
+ }
932
+ /**
933
+ * Unregisters a resource by URI.
934
+ * Can unregister resources from either Bucket A (provideContext) or Bucket B (registerResource).
935
+ *
936
+ * @param {string} uri - URI of the resource to unregister
937
+ */
938
+ unregisterResource(uri) {
939
+ console.log(`[Web Model Context] Unregistering resource: ${uri}`);
940
+ const inProvideContext = this.provideContextResources.has(uri);
941
+ const inDynamic = this.dynamicResources.has(uri);
942
+ if (!inProvideContext && !inDynamic) {
943
+ console.warn(`[Web Model Context] Resource "${uri}" is not registered, ignoring unregister call`);
944
+ return;
945
+ }
946
+ if (inProvideContext) this.provideContextResources.delete(uri);
947
+ if (inDynamic) {
948
+ this.dynamicResources.delete(uri);
949
+ this.resourceRegistrationTimestamps.delete(uri);
950
+ this.resourceUnregisterFunctions.delete(uri);
951
+ }
952
+ this.updateBridgeResources();
953
+ this.notifyResourcesListChanged();
954
+ }
955
+ /**
956
+ * Lists all registered resources in MCP format.
957
+ * Returns static resources from both buckets (not templates).
958
+ *
959
+ * @returns {Resource[]} Array of resource descriptors
960
+ */
961
+ listResources() {
962
+ return Array.from(this.bridge.resources.values()).filter((r) => !r.isTemplate).map((resource) => ({
963
+ uri: resource.uri,
964
+ name: resource.name,
965
+ description: resource.description,
966
+ mimeType: resource.mimeType
967
+ }));
968
+ }
969
+ /**
970
+ * Lists all registered resource templates.
971
+ * Returns only resources with URI templates (dynamic resources).
972
+ *
973
+ * @returns {Array<{uriTemplate: string, name: string, description?: string, mimeType?: string}>}
974
+ */
975
+ listResourceTemplates() {
976
+ return Array.from(this.bridge.resources.values()).filter((r) => r.isTemplate).map((resource) => ({
977
+ uriTemplate: resource.uri,
978
+ name: resource.name,
979
+ ...resource.description !== void 0 && { description: resource.description },
980
+ ...resource.mimeType !== void 0 && { mimeType: resource.mimeType }
981
+ }));
982
+ }
983
+ /**
984
+ * Registers a single prompt dynamically (Bucket B).
985
+ * Dynamic prompts persist across provideContext() calls and can be independently managed.
986
+ *
987
+ * @param {PromptDescriptor} prompt - The prompt descriptor to register
988
+ * @returns {{unregister: () => void}} Object with unregister function
989
+ * @throws {Error} If prompt name collides with existing prompts
990
+ */
991
+ registerPrompt(prompt) {
992
+ console.log(`[Web Model Context] Registering prompt dynamically: ${prompt.name}`);
993
+ const now = Date.now();
994
+ const lastRegistration = this.promptRegistrationTimestamps.get(prompt.name);
995
+ if (lastRegistration && now - lastRegistration < RAPID_DUPLICATE_WINDOW_MS) {
996
+ console.warn(`[Web Model Context] 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.`);
997
+ const existingUnregister = this.promptUnregisterFunctions.get(prompt.name);
998
+ if (existingUnregister) return { unregister: existingUnregister };
999
+ }
1000
+ if (this.provideContextPrompts.has(prompt.name)) throw new Error(`[Web Model Context] Prompt name collision: "${prompt.name}" is already registered via provideContext(). Please use a different name or update your provideContext() call.`);
1001
+ if (this.dynamicPrompts.has(prompt.name)) throw new Error(`[Web Model Context] Prompt name collision: "${prompt.name}" is already registered via registerPrompt(). Please unregister it first or use a different name.`);
1002
+ const validatedPrompt = this.validatePrompt(prompt);
1003
+ this.dynamicPrompts.set(prompt.name, validatedPrompt);
1004
+ this.promptRegistrationTimestamps.set(prompt.name, now);
1005
+ this.updateBridgePrompts();
1006
+ this.notifyPromptsListChanged();
1007
+ const unregisterFn = () => {
1008
+ console.log(`[Web Model Context] Unregistering prompt: ${prompt.name}`);
1009
+ 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.`);
1010
+ if (!this.dynamicPrompts.has(prompt.name)) {
1011
+ console.warn(`[Web Model Context] Prompt "${prompt.name}" is not registered, ignoring unregister call`);
1012
+ return;
1013
+ }
1014
+ this.dynamicPrompts.delete(prompt.name);
1015
+ this.promptRegistrationTimestamps.delete(prompt.name);
1016
+ this.promptUnregisterFunctions.delete(prompt.name);
1017
+ this.updateBridgePrompts();
1018
+ this.notifyPromptsListChanged();
1019
+ };
1020
+ this.promptUnregisterFunctions.set(prompt.name, unregisterFn);
1021
+ return { unregister: unregisterFn };
1022
+ }
1023
+ /**
1024
+ * Unregisters a prompt by name.
1025
+ * Can unregister prompts from either Bucket A (provideContext) or Bucket B (registerPrompt).
1026
+ *
1027
+ * @param {string} name - Name of the prompt to unregister
1028
+ */
1029
+ unregisterPrompt(name) {
1030
+ console.log(`[Web Model Context] Unregistering prompt: ${name}`);
1031
+ const inProvideContext = this.provideContextPrompts.has(name);
1032
+ const inDynamic = this.dynamicPrompts.has(name);
1033
+ if (!inProvideContext && !inDynamic) {
1034
+ console.warn(`[Web Model Context] Prompt "${name}" is not registered, ignoring unregister call`);
1035
+ return;
1036
+ }
1037
+ if (inProvideContext) this.provideContextPrompts.delete(name);
1038
+ if (inDynamic) {
1039
+ this.dynamicPrompts.delete(name);
1040
+ this.promptRegistrationTimestamps.delete(name);
1041
+ this.promptUnregisterFunctions.delete(name);
1042
+ }
1043
+ this.updateBridgePrompts();
1044
+ this.notifyPromptsListChanged();
1045
+ }
1046
+ /**
1047
+ * Lists all registered prompts in MCP format.
1048
+ * Returns prompts from both buckets.
1049
+ *
1050
+ * @returns {Prompt[]} Array of prompt descriptors
1051
+ */
1052
+ listPrompts() {
1053
+ return Array.from(this.bridge.prompts.values()).map((prompt) => ({
1054
+ name: prompt.name,
1055
+ description: prompt.description,
1056
+ arguments: prompt.argsSchema?.properties ? Object.entries(prompt.argsSchema.properties).map(([name, schema]) => ({
1057
+ name,
1058
+ description: schema.description,
1059
+ required: prompt.argsSchema?.required?.includes(name) ?? false
1060
+ })) : void 0
1061
+ }));
1062
+ }
1063
+ /**
752
1064
  * Unregisters a tool by name (Chromium native API).
753
1065
  * Can unregister tools from either Bucket A (provideContext) or Bucket B (registerTool).
754
1066
  *
@@ -765,24 +1077,36 @@ var WebModelContext = class {
765
1077
  if (inProvideContext) this.provideContextTools.delete(name);
766
1078
  if (inDynamic) {
767
1079
  this.dynamicTools.delete(name);
768
- this.registrationTimestamps.delete(name);
769
- this.unregisterFunctions.delete(name);
1080
+ this.toolRegistrationTimestamps.delete(name);
1081
+ this.toolUnregisterFunctions.delete(name);
770
1082
  }
771
1083
  this.updateBridgeTools();
772
1084
  this.notifyToolsListChanged();
773
1085
  }
774
1086
  /**
775
- * Clears all registered tools from both buckets (Chromium native API).
776
- * Removes all tools registered via provideContext() and registerTool().
1087
+ * Clears all registered context from both buckets (Chromium native API).
1088
+ * Removes all tools, resources, and prompts registered via provideContext() and register* methods.
777
1089
  */
778
1090
  clearContext() {
779
- console.log("[Web Model Context] Clearing all tools");
1091
+ console.log("[Web Model Context] Clearing all context (tools, resources, prompts)");
780
1092
  this.provideContextTools.clear();
781
1093
  this.dynamicTools.clear();
782
- this.registrationTimestamps.clear();
783
- this.unregisterFunctions.clear();
1094
+ this.toolRegistrationTimestamps.clear();
1095
+ this.toolUnregisterFunctions.clear();
1096
+ this.provideContextResources.clear();
1097
+ this.dynamicResources.clear();
1098
+ this.resourceRegistrationTimestamps.clear();
1099
+ this.resourceUnregisterFunctions.clear();
1100
+ this.provideContextPrompts.clear();
1101
+ this.dynamicPrompts.clear();
1102
+ this.promptRegistrationTimestamps.clear();
1103
+ this.promptUnregisterFunctions.clear();
784
1104
  this.updateBridgeTools();
1105
+ this.updateBridgeResources();
1106
+ this.updateBridgePrompts();
785
1107
  this.notifyToolsListChanged();
1108
+ this.notifyResourcesListChanged();
1109
+ this.notifyPromptsListChanged();
786
1110
  }
787
1111
  /**
788
1112
  * Updates the bridge tools map with merged tools from both buckets.
@@ -814,6 +1138,146 @@ var WebModelContext = class {
814
1138
  if (this.testingAPI && "notifyToolsChanged" in this.testingAPI) this.testingAPI.notifyToolsChanged();
815
1139
  }
816
1140
  /**
1141
+ * Updates the bridge resources map with merged resources from both buckets.
1142
+ *
1143
+ * @private
1144
+ */
1145
+ updateBridgeResources() {
1146
+ this.bridge.resources.clear();
1147
+ for (const [uri, resource] of this.provideContextResources) this.bridge.resources.set(uri, resource);
1148
+ for (const [uri, resource] of this.dynamicResources) this.bridge.resources.set(uri, resource);
1149
+ console.log(`[Web Model Context] Updated bridge with ${this.provideContextResources.size} base resources + ${this.dynamicResources.size} dynamic resources = ${this.bridge.resources.size} total`);
1150
+ }
1151
+ /**
1152
+ * Notifies all servers that the resources list has changed.
1153
+ *
1154
+ * @private
1155
+ */
1156
+ notifyResourcesListChanged() {
1157
+ if (this.bridge.tabServer.notification) this.bridge.tabServer.notification({
1158
+ method: "notifications/resources/list_changed",
1159
+ params: {}
1160
+ });
1161
+ if (this.bridge.iframeServer?.notification) this.bridge.iframeServer.notification({
1162
+ method: "notifications/resources/list_changed",
1163
+ params: {}
1164
+ });
1165
+ }
1166
+ /**
1167
+ * Updates the bridge prompts map with merged prompts from both buckets.
1168
+ *
1169
+ * @private
1170
+ */
1171
+ updateBridgePrompts() {
1172
+ this.bridge.prompts.clear();
1173
+ for (const [name, prompt] of this.provideContextPrompts) this.bridge.prompts.set(name, prompt);
1174
+ for (const [name, prompt] of this.dynamicPrompts) this.bridge.prompts.set(name, prompt);
1175
+ console.log(`[Web Model Context] Updated bridge with ${this.provideContextPrompts.size} base prompts + ${this.dynamicPrompts.size} dynamic prompts = ${this.bridge.prompts.size} total`);
1176
+ }
1177
+ /**
1178
+ * Notifies all servers that the prompts list has changed.
1179
+ *
1180
+ * @private
1181
+ */
1182
+ notifyPromptsListChanged() {
1183
+ if (this.bridge.tabServer.notification) this.bridge.tabServer.notification({
1184
+ method: "notifications/prompts/list_changed",
1185
+ params: {}
1186
+ });
1187
+ if (this.bridge.iframeServer?.notification) this.bridge.iframeServer.notification({
1188
+ method: "notifications/prompts/list_changed",
1189
+ params: {}
1190
+ });
1191
+ }
1192
+ /**
1193
+ * Reads a resource by URI (internal use only by MCP bridge).
1194
+ * Handles both static resources and URI templates.
1195
+ *
1196
+ * @param {string} uri - The URI of the resource to read
1197
+ * @returns {Promise<{contents: ResourceContents[]}>} The resource contents
1198
+ * @throws {Error} If resource is not found
1199
+ * @internal
1200
+ */
1201
+ async readResource(uri) {
1202
+ console.log(`[Web Model Context] Reading resource: ${uri}`);
1203
+ const staticResource = this.bridge.resources.get(uri);
1204
+ if (staticResource && !staticResource.isTemplate) try {
1205
+ const parsedUri = new URL(uri);
1206
+ return await staticResource.read(parsedUri);
1207
+ } catch (error) {
1208
+ console.error(`[Web Model Context] Error reading resource ${uri}:`, error);
1209
+ throw error;
1210
+ }
1211
+ for (const resource of this.bridge.resources.values()) {
1212
+ if (!resource.isTemplate) continue;
1213
+ const params = this.matchUriTemplate(resource.uri, uri);
1214
+ if (params) try {
1215
+ const parsedUri = new URL(uri);
1216
+ return await resource.read(parsedUri, params);
1217
+ } catch (error) {
1218
+ console.error(`[Web Model Context] Error reading resource ${uri}:`, error);
1219
+ throw error;
1220
+ }
1221
+ }
1222
+ throw new Error(`Resource not found: ${uri}`);
1223
+ }
1224
+ /**
1225
+ * Matches a URI against a URI template and extracts parameters.
1226
+ *
1227
+ * @param {string} template - The URI template (e.g., "file://{path}")
1228
+ * @param {string} uri - The actual URI to match
1229
+ * @returns {Record<string, string> | null} Extracted parameters or null if no match
1230
+ * @private
1231
+ */
1232
+ matchUriTemplate(template, uri) {
1233
+ const paramNames = [];
1234
+ let regexPattern = template.replace(/[.*+?^${}()|[\]\\]/g, (char) => {
1235
+ if (char === "{" || char === "}") return char;
1236
+ return `\\${char}`;
1237
+ });
1238
+ regexPattern = regexPattern.replace(/\{([^}]+)\}/g, (_, paramName) => {
1239
+ paramNames.push(paramName);
1240
+ return "(.+)";
1241
+ });
1242
+ const regex = /* @__PURE__ */ new RegExp(`^${regexPattern}$`);
1243
+ const match = uri.match(regex);
1244
+ if (!match) return null;
1245
+ const params = {};
1246
+ for (let i = 0; i < paramNames.length; i++) {
1247
+ const paramName = paramNames[i];
1248
+ const paramValue = match[i + 1];
1249
+ if (paramName !== void 0 && paramValue !== void 0) params[paramName] = paramValue;
1250
+ }
1251
+ return params;
1252
+ }
1253
+ /**
1254
+ * Gets a prompt with arguments (internal use only by MCP bridge).
1255
+ *
1256
+ * @param {string} name - Name of the prompt
1257
+ * @param {Record<string, unknown>} args - Arguments to pass to the prompt
1258
+ * @returns {Promise<{messages: PromptMessage[]}>} The prompt messages
1259
+ * @throws {Error} If prompt is not found
1260
+ * @internal
1261
+ */
1262
+ async getPrompt(name, args) {
1263
+ console.log(`[Web Model Context] Getting prompt: ${name}`);
1264
+ const prompt = this.bridge.prompts.get(name);
1265
+ if (!prompt) throw new Error(`Prompt not found: ${name}`);
1266
+ if (prompt.argsValidator && args) {
1267
+ const validation = validateWithZod(args, prompt.argsValidator);
1268
+ if (!validation.success) {
1269
+ console.error(`[Web Model Context] Argument validation failed for prompt ${name}:`, validation.error);
1270
+ throw new Error(`Argument validation error for prompt "${name}":\n${validation.error}`);
1271
+ }
1272
+ }
1273
+ try {
1274
+ return await prompt.get(args ?? {});
1275
+ } catch (error) {
1276
+ console.error(`[Web Model Context] Error getting prompt ${name}:`, error);
1277
+ throw error;
1278
+ }
1279
+ }
1280
+ /**
817
1281
  * Executes a tool with validation and event dispatch.
818
1282
  * Follows this sequence:
819
1283
  * 1. Validates input arguments against schema
@@ -897,6 +1361,32 @@ var WebModelContext = class {
897
1361
  ...tool.annotations && { annotations: tool.annotations }
898
1362
  }));
899
1363
  }
1364
+ /**
1365
+ * Request an LLM completion from the connected client.
1366
+ * This sends a sampling request to the connected MCP client.
1367
+ *
1368
+ * @param {SamplingRequestParams} params - Parameters for the sampling request
1369
+ * @returns {Promise<SamplingResult>} The LLM completion result
1370
+ */
1371
+ async createMessage(params) {
1372
+ console.log("[Web Model Context] Requesting sampling from client");
1373
+ const underlyingServer = this.bridge.tabServer.server;
1374
+ if (!underlyingServer?.createMessage) throw new Error("Sampling is not supported: no connected client with sampling capability");
1375
+ return underlyingServer.createMessage(params);
1376
+ }
1377
+ /**
1378
+ * Request user input from the connected client.
1379
+ * This sends an elicitation request to the connected MCP client.
1380
+ *
1381
+ * @param {ElicitationParams} params - Parameters for the elicitation request
1382
+ * @returns {Promise<ElicitationResult>} The user's response
1383
+ */
1384
+ async elicitInput(params) {
1385
+ console.log("[Web Model Context] Requesting elicitation from client");
1386
+ const underlyingServer = this.bridge.tabServer.server;
1387
+ if (!underlyingServer?.elicitInput) throw new Error("Elicitation is not supported: no connected client with elicitation capability");
1388
+ return underlyingServer.elicitInput(params);
1389
+ }
900
1390
  };
901
1391
  /**
902
1392
  * Initializes the MCP bridge with dual-server support.
@@ -930,6 +1420,32 @@ function initializeMCPBridge(options) {
930
1420
  throw error;
931
1421
  }
932
1422
  });
1423
+ server.setRequestHandler(ListResourcesRequestSchema, async () => {
1424
+ console.log("[MCP Bridge] Handling list_resources request");
1425
+ return { resources: bridge$1.modelContext.listResources() };
1426
+ });
1427
+ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
1428
+ console.log(`[MCP Bridge] Handling read_resource request: ${request.params.uri}`);
1429
+ try {
1430
+ return await bridge$1.modelContext.readResource(request.params.uri);
1431
+ } catch (error) {
1432
+ console.error(`[MCP Bridge] Error reading resource ${request.params.uri}:`, error);
1433
+ throw error;
1434
+ }
1435
+ });
1436
+ server.setRequestHandler(ListPromptsRequestSchema, async () => {
1437
+ console.log("[MCP Bridge] Handling list_prompts request");
1438
+ return { prompts: bridge$1.modelContext.listPrompts() };
1439
+ });
1440
+ server.setRequestHandler(GetPromptRequestSchema, async (request) => {
1441
+ console.log(`[MCP Bridge] Handling get_prompt request: ${request.params.name}`);
1442
+ try {
1443
+ return await bridge$1.modelContext.getPrompt(request.params.name, request.params.arguments);
1444
+ } catch (error) {
1445
+ console.error(`[MCP Bridge] Error getting prompt ${request.params.name}:`, error);
1446
+ throw error;
1447
+ }
1448
+ });
933
1449
  };
934
1450
  const customTransport = transportOptions?.create?.();
935
1451
  if (customTransport) {
@@ -937,10 +1453,16 @@ function initializeMCPBridge(options) {
937
1453
  const server = new Server({
938
1454
  name: hostname,
939
1455
  version: "1.0.0"
940
- }, { capabilities: { tools: { listChanged: true } } });
1456
+ }, { capabilities: {
1457
+ tools: { listChanged: true },
1458
+ resources: { listChanged: true },
1459
+ prompts: { listChanged: true }
1460
+ } });
941
1461
  const bridge$1 = {
942
1462
  tabServer: server,
943
1463
  tools: /* @__PURE__ */ new Map(),
1464
+ resources: /* @__PURE__ */ new Map(),
1465
+ prompts: /* @__PURE__ */ new Map(),
944
1466
  modelContext: void 0,
945
1467
  isInitialized: true
946
1468
  };
@@ -955,10 +1477,16 @@ function initializeMCPBridge(options) {
955
1477
  const tabServer = new Server({
956
1478
  name: `${hostname}-tab`,
957
1479
  version: "1.0.0"
958
- }, { capabilities: { tools: { listChanged: true } } });
1480
+ }, { capabilities: {
1481
+ tools: { listChanged: true },
1482
+ resources: { listChanged: true },
1483
+ prompts: { listChanged: true }
1484
+ } });
959
1485
  const bridge = {
960
1486
  tabServer,
961
1487
  tools: /* @__PURE__ */ new Map(),
1488
+ resources: /* @__PURE__ */ new Map(),
1489
+ prompts: /* @__PURE__ */ new Map(),
962
1490
  modelContext: void 0,
963
1491
  isInitialized: true
964
1492
  };
@@ -980,7 +1508,11 @@ function initializeMCPBridge(options) {
980
1508
  const iframeServer = new Server({
981
1509
  name: `${hostname}-iframe`,
982
1510
  version: "1.0.0"
983
- }, { capabilities: { tools: { listChanged: true } } });
1511
+ }, { capabilities: {
1512
+ tools: { listChanged: true },
1513
+ resources: { listChanged: true },
1514
+ prompts: { listChanged: true }
1515
+ } });
984
1516
  setupServerHandlers(iframeServer, bridge);
985
1517
  const { allowedOrigins,...restIframeServerOptions } = typeof iframeServerConfig === "object" ? iframeServerConfig : {};
986
1518
  const iframeTransport = new IframeChildTransport({
@@ -1197,5 +1729,5 @@ if (typeof window !== "undefined" && typeof document !== "undefined") {
1197
1729
  }
1198
1730
 
1199
1731
  //#endregion
1200
- export { cleanupWebModelContext, initializeWebModelContext };
1732
+ export { cleanupWebModelContext, initializeWebModelContext, zodToJsonSchema };
1201
1733
  //# sourceMappingURL=index.js.map