@mcp-b/global 1.1.1 → 1.1.3-beta.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.d.ts +580 -50
- package/dist/index.d.ts.map +1 -1
- package/dist/index.iife.js +4 -4
- package/dist/index.js +586 -28
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
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
5
|
|
|
@@ -326,6 +326,75 @@ var NativeModelContextAdapter = class {
|
|
|
326
326
|
}));
|
|
327
327
|
}
|
|
328
328
|
/**
|
|
329
|
+
* Registers a resource dynamically.
|
|
330
|
+
* Note: Native Chromium API does not yet support resources.
|
|
331
|
+
* This is a polyfill-only feature.
|
|
332
|
+
*/
|
|
333
|
+
registerResource(_resource) {
|
|
334
|
+
console.warn("[Native Adapter] registerResource is not supported by native API");
|
|
335
|
+
return { unregister: () => {} };
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* Unregisters a resource by URI.
|
|
339
|
+
* Note: Native Chromium API does not yet support resources.
|
|
340
|
+
*/
|
|
341
|
+
unregisterResource(_uri) {
|
|
342
|
+
console.warn("[Native Adapter] unregisterResource is not supported by native API");
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* Lists all registered resources.
|
|
346
|
+
* Note: Native Chromium API does not yet support resources.
|
|
347
|
+
*/
|
|
348
|
+
listResources() {
|
|
349
|
+
return [];
|
|
350
|
+
}
|
|
351
|
+
/**
|
|
352
|
+
* Lists all resource templates.
|
|
353
|
+
* Note: Native Chromium API does not yet support resources.
|
|
354
|
+
*/
|
|
355
|
+
listResourceTemplates() {
|
|
356
|
+
return [];
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* Reads a resource by URI.
|
|
360
|
+
* Note: Native Chromium API does not yet support resources.
|
|
361
|
+
* @internal
|
|
362
|
+
*/
|
|
363
|
+
async readResource(_uri) {
|
|
364
|
+
throw new Error("[Native Adapter] readResource is not supported by native API");
|
|
365
|
+
}
|
|
366
|
+
/**
|
|
367
|
+
* Registers a prompt dynamically.
|
|
368
|
+
* Note: Native Chromium API does not yet support prompts.
|
|
369
|
+
* This is a polyfill-only feature.
|
|
370
|
+
*/
|
|
371
|
+
registerPrompt(_prompt) {
|
|
372
|
+
console.warn("[Native Adapter] registerPrompt is not supported by native API");
|
|
373
|
+
return { unregister: () => {} };
|
|
374
|
+
}
|
|
375
|
+
/**
|
|
376
|
+
* Unregisters a prompt by name.
|
|
377
|
+
* Note: Native Chromium API does not yet support prompts.
|
|
378
|
+
*/
|
|
379
|
+
unregisterPrompt(_name) {
|
|
380
|
+
console.warn("[Native Adapter] unregisterPrompt is not supported by native API");
|
|
381
|
+
}
|
|
382
|
+
/**
|
|
383
|
+
* Lists all registered prompts.
|
|
384
|
+
* Note: Native Chromium API does not yet support prompts.
|
|
385
|
+
*/
|
|
386
|
+
listPrompts() {
|
|
387
|
+
return [];
|
|
388
|
+
}
|
|
389
|
+
/**
|
|
390
|
+
* Gets a prompt with arguments.
|
|
391
|
+
* Note: Native Chromium API does not yet support prompts.
|
|
392
|
+
* @internal
|
|
393
|
+
*/
|
|
394
|
+
async getPrompt(_name, _args) {
|
|
395
|
+
throw new Error("[Native Adapter] getPrompt is not supported by native API");
|
|
396
|
+
}
|
|
397
|
+
/**
|
|
329
398
|
* Adds an event listener for tool call events.
|
|
330
399
|
* Delegates to the native API's addEventListener.
|
|
331
400
|
*
|
|
@@ -357,6 +426,28 @@ var NativeModelContextAdapter = class {
|
|
|
357
426
|
dispatchEvent(event) {
|
|
358
427
|
return this.nativeContext.dispatchEvent(event);
|
|
359
428
|
}
|
|
429
|
+
/**
|
|
430
|
+
* Request an LLM completion from the connected client.
|
|
431
|
+
* Note: Native Chromium API does not yet support sampling.
|
|
432
|
+
* This is handled by the polyfill.
|
|
433
|
+
*/
|
|
434
|
+
async createMessage(params) {
|
|
435
|
+
console.log("[Native Adapter] Requesting sampling from client");
|
|
436
|
+
const underlyingServer = this.bridge.tabServer.server;
|
|
437
|
+
if (!underlyingServer?.createMessage) throw new Error("Sampling is not supported: no connected client with sampling capability");
|
|
438
|
+
return underlyingServer.createMessage(params);
|
|
439
|
+
}
|
|
440
|
+
/**
|
|
441
|
+
* Request user input from the connected client.
|
|
442
|
+
* Note: Native Chromium API does not yet support elicitation.
|
|
443
|
+
* This is handled by the polyfill.
|
|
444
|
+
*/
|
|
445
|
+
async elicitInput(params) {
|
|
446
|
+
console.log("[Native Adapter] Requesting elicitation from client");
|
|
447
|
+
const underlyingServer = this.bridge.tabServer.server;
|
|
448
|
+
if (!underlyingServer?.elicitInput) throw new Error("Elicitation is not supported: no connected client with elicitation capability");
|
|
449
|
+
return underlyingServer.elicitInput(params);
|
|
450
|
+
}
|
|
360
451
|
};
|
|
361
452
|
/**
|
|
362
453
|
* ToolCallEvent implementation for the Web Model Context API.
|
|
@@ -612,8 +703,16 @@ var WebModelContext = class {
|
|
|
612
703
|
eventTarget;
|
|
613
704
|
provideContextTools;
|
|
614
705
|
dynamicTools;
|
|
615
|
-
|
|
616
|
-
|
|
706
|
+
provideContextResources;
|
|
707
|
+
dynamicResources;
|
|
708
|
+
provideContextPrompts;
|
|
709
|
+
dynamicPrompts;
|
|
710
|
+
toolRegistrationTimestamps;
|
|
711
|
+
resourceRegistrationTimestamps;
|
|
712
|
+
promptRegistrationTimestamps;
|
|
713
|
+
toolUnregisterFunctions;
|
|
714
|
+
resourceUnregisterFunctions;
|
|
715
|
+
promptUnregisterFunctions;
|
|
617
716
|
testingAPI;
|
|
618
717
|
/**
|
|
619
718
|
* Creates a new WebModelContext instance.
|
|
@@ -625,8 +724,16 @@ var WebModelContext = class {
|
|
|
625
724
|
this.eventTarget = new EventTarget();
|
|
626
725
|
this.provideContextTools = /* @__PURE__ */ new Map();
|
|
627
726
|
this.dynamicTools = /* @__PURE__ */ new Map();
|
|
628
|
-
this.
|
|
629
|
-
this.
|
|
727
|
+
this.toolRegistrationTimestamps = /* @__PURE__ */ new Map();
|
|
728
|
+
this.toolUnregisterFunctions = /* @__PURE__ */ new Map();
|
|
729
|
+
this.provideContextResources = /* @__PURE__ */ new Map();
|
|
730
|
+
this.dynamicResources = /* @__PURE__ */ new Map();
|
|
731
|
+
this.resourceRegistrationTimestamps = /* @__PURE__ */ new Map();
|
|
732
|
+
this.resourceUnregisterFunctions = /* @__PURE__ */ new Map();
|
|
733
|
+
this.provideContextPrompts = /* @__PURE__ */ new Map();
|
|
734
|
+
this.dynamicPrompts = /* @__PURE__ */ new Map();
|
|
735
|
+
this.promptRegistrationTimestamps = /* @__PURE__ */ new Map();
|
|
736
|
+
this.promptUnregisterFunctions = /* @__PURE__ */ new Map();
|
|
630
737
|
}
|
|
631
738
|
/**
|
|
632
739
|
* Sets the testing API instance.
|
|
@@ -668,17 +775,22 @@ var WebModelContext = class {
|
|
|
668
775
|
return this.eventTarget.dispatchEvent(event);
|
|
669
776
|
}
|
|
670
777
|
/**
|
|
671
|
-
* Provides context (tools) to AI models by registering base
|
|
672
|
-
* Clears and replaces all previously registered base
|
|
673
|
-
* dynamic
|
|
778
|
+
* Provides context (tools, resources, prompts) to AI models by registering base items (Bucket A).
|
|
779
|
+
* Clears and replaces all previously registered base items while preserving
|
|
780
|
+
* dynamic items registered via register* methods.
|
|
674
781
|
*
|
|
675
|
-
* @param {ModelContextInput} context - Context containing tools to register
|
|
676
|
-
* @throws {Error} If a
|
|
782
|
+
* @param {ModelContextInput} context - Context containing tools, resources, and prompts to register
|
|
783
|
+
* @throws {Error} If a name/uri collides with existing dynamic items
|
|
677
784
|
*/
|
|
678
785
|
provideContext(context) {
|
|
679
|
-
|
|
786
|
+
const toolCount = context.tools?.length ?? 0;
|
|
787
|
+
const resourceCount = context.resources?.length ?? 0;
|
|
788
|
+
const promptCount = context.prompts?.length ?? 0;
|
|
789
|
+
console.log(`[Web Model Context] provideContext: ${toolCount} tools, ${resourceCount} resources, ${promptCount} prompts`);
|
|
680
790
|
this.provideContextTools.clear();
|
|
681
|
-
|
|
791
|
+
this.provideContextResources.clear();
|
|
792
|
+
this.provideContextPrompts.clear();
|
|
793
|
+
for (const tool of context.tools ?? []) {
|
|
682
794
|
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
795
|
const { jsonSchema: inputJson, zodValidator: inputZod } = normalizeSchema(tool.inputSchema);
|
|
684
796
|
const normalizedOutput = tool.outputSchema ? normalizeSchema(tool.outputSchema) : null;
|
|
@@ -694,8 +806,63 @@ var WebModelContext = class {
|
|
|
694
806
|
};
|
|
695
807
|
this.provideContextTools.set(tool.name, validatedTool);
|
|
696
808
|
}
|
|
809
|
+
for (const resource of context.resources ?? []) {
|
|
810
|
+
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.`);
|
|
811
|
+
const validatedResource = this.validateResource(resource);
|
|
812
|
+
this.provideContextResources.set(resource.uri, validatedResource);
|
|
813
|
+
}
|
|
814
|
+
for (const prompt of context.prompts ?? []) {
|
|
815
|
+
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.`);
|
|
816
|
+
const validatedPrompt = this.validatePrompt(prompt);
|
|
817
|
+
this.provideContextPrompts.set(prompt.name, validatedPrompt);
|
|
818
|
+
}
|
|
697
819
|
this.updateBridgeTools();
|
|
820
|
+
this.updateBridgeResources();
|
|
821
|
+
this.updateBridgePrompts();
|
|
698
822
|
this.notifyToolsListChanged();
|
|
823
|
+
this.notifyResourcesListChanged();
|
|
824
|
+
this.notifyPromptsListChanged();
|
|
825
|
+
}
|
|
826
|
+
/**
|
|
827
|
+
* Validates and normalizes a resource descriptor.
|
|
828
|
+
* @private
|
|
829
|
+
*/
|
|
830
|
+
validateResource(resource) {
|
|
831
|
+
const templateParamRegex = /\{([^}]+)\}/g;
|
|
832
|
+
const templateParams = [];
|
|
833
|
+
for (const match of resource.uri.matchAll(templateParamRegex)) {
|
|
834
|
+
const paramName = match[1];
|
|
835
|
+
if (paramName) templateParams.push(paramName);
|
|
836
|
+
}
|
|
837
|
+
return {
|
|
838
|
+
uri: resource.uri,
|
|
839
|
+
name: resource.name,
|
|
840
|
+
description: resource.description,
|
|
841
|
+
mimeType: resource.mimeType,
|
|
842
|
+
read: resource.read,
|
|
843
|
+
isTemplate: templateParams.length > 0,
|
|
844
|
+
templateParams
|
|
845
|
+
};
|
|
846
|
+
}
|
|
847
|
+
/**
|
|
848
|
+
* Validates and normalizes a prompt descriptor.
|
|
849
|
+
* @private
|
|
850
|
+
*/
|
|
851
|
+
validatePrompt(prompt) {
|
|
852
|
+
let argsSchema;
|
|
853
|
+
let argsValidator;
|
|
854
|
+
if (prompt.argsSchema) {
|
|
855
|
+
const normalized = normalizeSchema(prompt.argsSchema);
|
|
856
|
+
argsSchema = normalized.jsonSchema;
|
|
857
|
+
argsValidator = normalized.zodValidator;
|
|
858
|
+
}
|
|
859
|
+
return {
|
|
860
|
+
name: prompt.name,
|
|
861
|
+
description: prompt.description,
|
|
862
|
+
argsSchema,
|
|
863
|
+
get: prompt.get,
|
|
864
|
+
argsValidator
|
|
865
|
+
};
|
|
699
866
|
}
|
|
700
867
|
/**
|
|
701
868
|
* Registers a single tool dynamically (Bucket B).
|
|
@@ -708,10 +875,10 @@ var WebModelContext = class {
|
|
|
708
875
|
registerTool(tool) {
|
|
709
876
|
console.log(`[Web Model Context] Registering tool dynamically: ${tool.name}`);
|
|
710
877
|
const now = Date.now();
|
|
711
|
-
const lastRegistration = this.
|
|
878
|
+
const lastRegistration = this.toolRegistrationTimestamps.get(tool.name);
|
|
712
879
|
if (lastRegistration && now - lastRegistration < RAPID_DUPLICATE_WINDOW_MS) {
|
|
713
880
|
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.
|
|
881
|
+
const existingUnregister = this.toolUnregisterFunctions.get(tool.name);
|
|
715
882
|
if (existingUnregister) return { unregister: existingUnregister };
|
|
716
883
|
}
|
|
717
884
|
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 +896,7 @@ var WebModelContext = class {
|
|
|
729
896
|
...normalizedOutput && { outputValidator: normalizedOutput.zodValidator }
|
|
730
897
|
};
|
|
731
898
|
this.dynamicTools.set(tool.name, validatedTool);
|
|
732
|
-
this.
|
|
899
|
+
this.toolRegistrationTimestamps.set(tool.name, now);
|
|
733
900
|
this.updateBridgeTools();
|
|
734
901
|
this.notifyToolsListChanged();
|
|
735
902
|
const unregisterFn = () => {
|
|
@@ -740,15 +907,186 @@ var WebModelContext = class {
|
|
|
740
907
|
return;
|
|
741
908
|
}
|
|
742
909
|
this.dynamicTools.delete(tool.name);
|
|
743
|
-
this.
|
|
744
|
-
this.
|
|
910
|
+
this.toolRegistrationTimestamps.delete(tool.name);
|
|
911
|
+
this.toolUnregisterFunctions.delete(tool.name);
|
|
745
912
|
this.updateBridgeTools();
|
|
746
913
|
this.notifyToolsListChanged();
|
|
747
914
|
};
|
|
748
|
-
this.
|
|
915
|
+
this.toolUnregisterFunctions.set(tool.name, unregisterFn);
|
|
916
|
+
return { unregister: unregisterFn };
|
|
917
|
+
}
|
|
918
|
+
/**
|
|
919
|
+
* Registers a single resource dynamically (Bucket B).
|
|
920
|
+
* Dynamic resources persist across provideContext() calls and can be independently managed.
|
|
921
|
+
*
|
|
922
|
+
* @param {ResourceDescriptor} resource - The resource descriptor to register
|
|
923
|
+
* @returns {{unregister: () => void}} Object with unregister function
|
|
924
|
+
* @throws {Error} If resource URI collides with existing resources
|
|
925
|
+
*/
|
|
926
|
+
registerResource(resource) {
|
|
927
|
+
console.log(`[Web Model Context] Registering resource dynamically: ${resource.uri}`);
|
|
928
|
+
const now = Date.now();
|
|
929
|
+
const lastRegistration = this.resourceRegistrationTimestamps.get(resource.uri);
|
|
930
|
+
if (lastRegistration && now - lastRegistration < RAPID_DUPLICATE_WINDOW_MS) {
|
|
931
|
+
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.`);
|
|
932
|
+
const existingUnregister = this.resourceUnregisterFunctions.get(resource.uri);
|
|
933
|
+
if (existingUnregister) return { unregister: existingUnregister };
|
|
934
|
+
}
|
|
935
|
+
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.`);
|
|
936
|
+
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.`);
|
|
937
|
+
const validatedResource = this.validateResource(resource);
|
|
938
|
+
this.dynamicResources.set(resource.uri, validatedResource);
|
|
939
|
+
this.resourceRegistrationTimestamps.set(resource.uri, now);
|
|
940
|
+
this.updateBridgeResources();
|
|
941
|
+
this.notifyResourcesListChanged();
|
|
942
|
+
const unregisterFn = () => {
|
|
943
|
+
console.log(`[Web Model Context] Unregistering resource: ${resource.uri}`);
|
|
944
|
+
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.`);
|
|
945
|
+
if (!this.dynamicResources.has(resource.uri)) {
|
|
946
|
+
console.warn(`[Web Model Context] Resource "${resource.uri}" is not registered, ignoring unregister call`);
|
|
947
|
+
return;
|
|
948
|
+
}
|
|
949
|
+
this.dynamicResources.delete(resource.uri);
|
|
950
|
+
this.resourceRegistrationTimestamps.delete(resource.uri);
|
|
951
|
+
this.resourceUnregisterFunctions.delete(resource.uri);
|
|
952
|
+
this.updateBridgeResources();
|
|
953
|
+
this.notifyResourcesListChanged();
|
|
954
|
+
};
|
|
955
|
+
this.resourceUnregisterFunctions.set(resource.uri, unregisterFn);
|
|
956
|
+
return { unregister: unregisterFn };
|
|
957
|
+
}
|
|
958
|
+
/**
|
|
959
|
+
* Unregisters a resource by URI.
|
|
960
|
+
* Can unregister resources from either Bucket A (provideContext) or Bucket B (registerResource).
|
|
961
|
+
*
|
|
962
|
+
* @param {string} uri - URI of the resource to unregister
|
|
963
|
+
*/
|
|
964
|
+
unregisterResource(uri) {
|
|
965
|
+
console.log(`[Web Model Context] Unregistering resource: ${uri}`);
|
|
966
|
+
const inProvideContext = this.provideContextResources.has(uri);
|
|
967
|
+
const inDynamic = this.dynamicResources.has(uri);
|
|
968
|
+
if (!inProvideContext && !inDynamic) {
|
|
969
|
+
console.warn(`[Web Model Context] Resource "${uri}" is not registered, ignoring unregister call`);
|
|
970
|
+
return;
|
|
971
|
+
}
|
|
972
|
+
if (inProvideContext) this.provideContextResources.delete(uri);
|
|
973
|
+
if (inDynamic) {
|
|
974
|
+
this.dynamicResources.delete(uri);
|
|
975
|
+
this.resourceRegistrationTimestamps.delete(uri);
|
|
976
|
+
this.resourceUnregisterFunctions.delete(uri);
|
|
977
|
+
}
|
|
978
|
+
this.updateBridgeResources();
|
|
979
|
+
this.notifyResourcesListChanged();
|
|
980
|
+
}
|
|
981
|
+
/**
|
|
982
|
+
* Lists all registered resources in MCP format.
|
|
983
|
+
* Returns static resources from both buckets (not templates).
|
|
984
|
+
*
|
|
985
|
+
* @returns {Resource[]} Array of resource descriptors
|
|
986
|
+
*/
|
|
987
|
+
listResources() {
|
|
988
|
+
return Array.from(this.bridge.resources.values()).filter((r) => !r.isTemplate).map((resource) => ({
|
|
989
|
+
uri: resource.uri,
|
|
990
|
+
name: resource.name,
|
|
991
|
+
description: resource.description,
|
|
992
|
+
mimeType: resource.mimeType
|
|
993
|
+
}));
|
|
994
|
+
}
|
|
995
|
+
/**
|
|
996
|
+
* Lists all registered resource templates.
|
|
997
|
+
* Returns only resources with URI templates (dynamic resources).
|
|
998
|
+
*
|
|
999
|
+
* @returns {Array<{uriTemplate: string, name: string, description?: string, mimeType?: string}>}
|
|
1000
|
+
*/
|
|
1001
|
+
listResourceTemplates() {
|
|
1002
|
+
return Array.from(this.bridge.resources.values()).filter((r) => r.isTemplate).map((resource) => ({
|
|
1003
|
+
uriTemplate: resource.uri,
|
|
1004
|
+
name: resource.name,
|
|
1005
|
+
...resource.description !== void 0 && { description: resource.description },
|
|
1006
|
+
...resource.mimeType !== void 0 && { mimeType: resource.mimeType }
|
|
1007
|
+
}));
|
|
1008
|
+
}
|
|
1009
|
+
/**
|
|
1010
|
+
* Registers a single prompt dynamically (Bucket B).
|
|
1011
|
+
* Dynamic prompts persist across provideContext() calls and can be independently managed.
|
|
1012
|
+
*
|
|
1013
|
+
* @param {PromptDescriptor} prompt - The prompt descriptor to register
|
|
1014
|
+
* @returns {{unregister: () => void}} Object with unregister function
|
|
1015
|
+
* @throws {Error} If prompt name collides with existing prompts
|
|
1016
|
+
*/
|
|
1017
|
+
registerPrompt(prompt) {
|
|
1018
|
+
console.log(`[Web Model Context] Registering prompt dynamically: ${prompt.name}`);
|
|
1019
|
+
const now = Date.now();
|
|
1020
|
+
const lastRegistration = this.promptRegistrationTimestamps.get(prompt.name);
|
|
1021
|
+
if (lastRegistration && now - lastRegistration < RAPID_DUPLICATE_WINDOW_MS) {
|
|
1022
|
+
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.`);
|
|
1023
|
+
const existingUnregister = this.promptUnregisterFunctions.get(prompt.name);
|
|
1024
|
+
if (existingUnregister) return { unregister: existingUnregister };
|
|
1025
|
+
}
|
|
1026
|
+
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.`);
|
|
1027
|
+
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.`);
|
|
1028
|
+
const validatedPrompt = this.validatePrompt(prompt);
|
|
1029
|
+
this.dynamicPrompts.set(prompt.name, validatedPrompt);
|
|
1030
|
+
this.promptRegistrationTimestamps.set(prompt.name, now);
|
|
1031
|
+
this.updateBridgePrompts();
|
|
1032
|
+
this.notifyPromptsListChanged();
|
|
1033
|
+
const unregisterFn = () => {
|
|
1034
|
+
console.log(`[Web Model Context] Unregistering prompt: ${prompt.name}`);
|
|
1035
|
+
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.`);
|
|
1036
|
+
if (!this.dynamicPrompts.has(prompt.name)) {
|
|
1037
|
+
console.warn(`[Web Model Context] Prompt "${prompt.name}" is not registered, ignoring unregister call`);
|
|
1038
|
+
return;
|
|
1039
|
+
}
|
|
1040
|
+
this.dynamicPrompts.delete(prompt.name);
|
|
1041
|
+
this.promptRegistrationTimestamps.delete(prompt.name);
|
|
1042
|
+
this.promptUnregisterFunctions.delete(prompt.name);
|
|
1043
|
+
this.updateBridgePrompts();
|
|
1044
|
+
this.notifyPromptsListChanged();
|
|
1045
|
+
};
|
|
1046
|
+
this.promptUnregisterFunctions.set(prompt.name, unregisterFn);
|
|
749
1047
|
return { unregister: unregisterFn };
|
|
750
1048
|
}
|
|
751
1049
|
/**
|
|
1050
|
+
* Unregisters a prompt by name.
|
|
1051
|
+
* Can unregister prompts from either Bucket A (provideContext) or Bucket B (registerPrompt).
|
|
1052
|
+
*
|
|
1053
|
+
* @param {string} name - Name of the prompt to unregister
|
|
1054
|
+
*/
|
|
1055
|
+
unregisterPrompt(name) {
|
|
1056
|
+
console.log(`[Web Model Context] Unregistering prompt: ${name}`);
|
|
1057
|
+
const inProvideContext = this.provideContextPrompts.has(name);
|
|
1058
|
+
const inDynamic = this.dynamicPrompts.has(name);
|
|
1059
|
+
if (!inProvideContext && !inDynamic) {
|
|
1060
|
+
console.warn(`[Web Model Context] Prompt "${name}" is not registered, ignoring unregister call`);
|
|
1061
|
+
return;
|
|
1062
|
+
}
|
|
1063
|
+
if (inProvideContext) this.provideContextPrompts.delete(name);
|
|
1064
|
+
if (inDynamic) {
|
|
1065
|
+
this.dynamicPrompts.delete(name);
|
|
1066
|
+
this.promptRegistrationTimestamps.delete(name);
|
|
1067
|
+
this.promptUnregisterFunctions.delete(name);
|
|
1068
|
+
}
|
|
1069
|
+
this.updateBridgePrompts();
|
|
1070
|
+
this.notifyPromptsListChanged();
|
|
1071
|
+
}
|
|
1072
|
+
/**
|
|
1073
|
+
* Lists all registered prompts in MCP format.
|
|
1074
|
+
* Returns prompts from both buckets.
|
|
1075
|
+
*
|
|
1076
|
+
* @returns {Prompt[]} Array of prompt descriptors
|
|
1077
|
+
*/
|
|
1078
|
+
listPrompts() {
|
|
1079
|
+
return Array.from(this.bridge.prompts.values()).map((prompt) => ({
|
|
1080
|
+
name: prompt.name,
|
|
1081
|
+
description: prompt.description,
|
|
1082
|
+
arguments: prompt.argsSchema?.properties ? Object.entries(prompt.argsSchema.properties).map(([name, schema]) => ({
|
|
1083
|
+
name,
|
|
1084
|
+
description: schema.description,
|
|
1085
|
+
required: prompt.argsSchema?.required?.includes(name) ?? false
|
|
1086
|
+
})) : void 0
|
|
1087
|
+
}));
|
|
1088
|
+
}
|
|
1089
|
+
/**
|
|
752
1090
|
* Unregisters a tool by name (Chromium native API).
|
|
753
1091
|
* Can unregister tools from either Bucket A (provideContext) or Bucket B (registerTool).
|
|
754
1092
|
*
|
|
@@ -765,24 +1103,36 @@ var WebModelContext = class {
|
|
|
765
1103
|
if (inProvideContext) this.provideContextTools.delete(name);
|
|
766
1104
|
if (inDynamic) {
|
|
767
1105
|
this.dynamicTools.delete(name);
|
|
768
|
-
this.
|
|
769
|
-
this.
|
|
1106
|
+
this.toolRegistrationTimestamps.delete(name);
|
|
1107
|
+
this.toolUnregisterFunctions.delete(name);
|
|
770
1108
|
}
|
|
771
1109
|
this.updateBridgeTools();
|
|
772
1110
|
this.notifyToolsListChanged();
|
|
773
1111
|
}
|
|
774
1112
|
/**
|
|
775
|
-
* Clears all registered
|
|
776
|
-
* Removes all tools registered via provideContext() and
|
|
1113
|
+
* Clears all registered context from both buckets (Chromium native API).
|
|
1114
|
+
* Removes all tools, resources, and prompts registered via provideContext() and register* methods.
|
|
777
1115
|
*/
|
|
778
1116
|
clearContext() {
|
|
779
|
-
console.log("[Web Model Context] Clearing all tools");
|
|
1117
|
+
console.log("[Web Model Context] Clearing all context (tools, resources, prompts)");
|
|
780
1118
|
this.provideContextTools.clear();
|
|
781
1119
|
this.dynamicTools.clear();
|
|
782
|
-
this.
|
|
783
|
-
this.
|
|
1120
|
+
this.toolRegistrationTimestamps.clear();
|
|
1121
|
+
this.toolUnregisterFunctions.clear();
|
|
1122
|
+
this.provideContextResources.clear();
|
|
1123
|
+
this.dynamicResources.clear();
|
|
1124
|
+
this.resourceRegistrationTimestamps.clear();
|
|
1125
|
+
this.resourceUnregisterFunctions.clear();
|
|
1126
|
+
this.provideContextPrompts.clear();
|
|
1127
|
+
this.dynamicPrompts.clear();
|
|
1128
|
+
this.promptRegistrationTimestamps.clear();
|
|
1129
|
+
this.promptUnregisterFunctions.clear();
|
|
784
1130
|
this.updateBridgeTools();
|
|
1131
|
+
this.updateBridgeResources();
|
|
1132
|
+
this.updateBridgePrompts();
|
|
785
1133
|
this.notifyToolsListChanged();
|
|
1134
|
+
this.notifyResourcesListChanged();
|
|
1135
|
+
this.notifyPromptsListChanged();
|
|
786
1136
|
}
|
|
787
1137
|
/**
|
|
788
1138
|
* Updates the bridge tools map with merged tools from both buckets.
|
|
@@ -814,6 +1164,146 @@ var WebModelContext = class {
|
|
|
814
1164
|
if (this.testingAPI && "notifyToolsChanged" in this.testingAPI) this.testingAPI.notifyToolsChanged();
|
|
815
1165
|
}
|
|
816
1166
|
/**
|
|
1167
|
+
* Updates the bridge resources map with merged resources from both buckets.
|
|
1168
|
+
*
|
|
1169
|
+
* @private
|
|
1170
|
+
*/
|
|
1171
|
+
updateBridgeResources() {
|
|
1172
|
+
this.bridge.resources.clear();
|
|
1173
|
+
for (const [uri, resource] of this.provideContextResources) this.bridge.resources.set(uri, resource);
|
|
1174
|
+
for (const [uri, resource] of this.dynamicResources) this.bridge.resources.set(uri, resource);
|
|
1175
|
+
console.log(`[Web Model Context] Updated bridge with ${this.provideContextResources.size} base resources + ${this.dynamicResources.size} dynamic resources = ${this.bridge.resources.size} total`);
|
|
1176
|
+
}
|
|
1177
|
+
/**
|
|
1178
|
+
* Notifies all servers that the resources list has changed.
|
|
1179
|
+
*
|
|
1180
|
+
* @private
|
|
1181
|
+
*/
|
|
1182
|
+
notifyResourcesListChanged() {
|
|
1183
|
+
if (this.bridge.tabServer.notification) this.bridge.tabServer.notification({
|
|
1184
|
+
method: "notifications/resources/list_changed",
|
|
1185
|
+
params: {}
|
|
1186
|
+
});
|
|
1187
|
+
if (this.bridge.iframeServer?.notification) this.bridge.iframeServer.notification({
|
|
1188
|
+
method: "notifications/resources/list_changed",
|
|
1189
|
+
params: {}
|
|
1190
|
+
});
|
|
1191
|
+
}
|
|
1192
|
+
/**
|
|
1193
|
+
* Updates the bridge prompts map with merged prompts from both buckets.
|
|
1194
|
+
*
|
|
1195
|
+
* @private
|
|
1196
|
+
*/
|
|
1197
|
+
updateBridgePrompts() {
|
|
1198
|
+
this.bridge.prompts.clear();
|
|
1199
|
+
for (const [name, prompt] of this.provideContextPrompts) this.bridge.prompts.set(name, prompt);
|
|
1200
|
+
for (const [name, prompt] of this.dynamicPrompts) this.bridge.prompts.set(name, prompt);
|
|
1201
|
+
console.log(`[Web Model Context] Updated bridge with ${this.provideContextPrompts.size} base prompts + ${this.dynamicPrompts.size} dynamic prompts = ${this.bridge.prompts.size} total`);
|
|
1202
|
+
}
|
|
1203
|
+
/**
|
|
1204
|
+
* Notifies all servers that the prompts list has changed.
|
|
1205
|
+
*
|
|
1206
|
+
* @private
|
|
1207
|
+
*/
|
|
1208
|
+
notifyPromptsListChanged() {
|
|
1209
|
+
if (this.bridge.tabServer.notification) this.bridge.tabServer.notification({
|
|
1210
|
+
method: "notifications/prompts/list_changed",
|
|
1211
|
+
params: {}
|
|
1212
|
+
});
|
|
1213
|
+
if (this.bridge.iframeServer?.notification) this.bridge.iframeServer.notification({
|
|
1214
|
+
method: "notifications/prompts/list_changed",
|
|
1215
|
+
params: {}
|
|
1216
|
+
});
|
|
1217
|
+
}
|
|
1218
|
+
/**
|
|
1219
|
+
* Reads a resource by URI (internal use only by MCP bridge).
|
|
1220
|
+
* Handles both static resources and URI templates.
|
|
1221
|
+
*
|
|
1222
|
+
* @param {string} uri - The URI of the resource to read
|
|
1223
|
+
* @returns {Promise<{contents: ResourceContents[]}>} The resource contents
|
|
1224
|
+
* @throws {Error} If resource is not found
|
|
1225
|
+
* @internal
|
|
1226
|
+
*/
|
|
1227
|
+
async readResource(uri) {
|
|
1228
|
+
console.log(`[Web Model Context] Reading resource: ${uri}`);
|
|
1229
|
+
const staticResource = this.bridge.resources.get(uri);
|
|
1230
|
+
if (staticResource && !staticResource.isTemplate) try {
|
|
1231
|
+
const parsedUri = new URL(uri);
|
|
1232
|
+
return await staticResource.read(parsedUri);
|
|
1233
|
+
} catch (error) {
|
|
1234
|
+
console.error(`[Web Model Context] Error reading resource ${uri}:`, error);
|
|
1235
|
+
throw error;
|
|
1236
|
+
}
|
|
1237
|
+
for (const resource of this.bridge.resources.values()) {
|
|
1238
|
+
if (!resource.isTemplate) continue;
|
|
1239
|
+
const params = this.matchUriTemplate(resource.uri, uri);
|
|
1240
|
+
if (params) try {
|
|
1241
|
+
const parsedUri = new URL(uri);
|
|
1242
|
+
return await resource.read(parsedUri, params);
|
|
1243
|
+
} catch (error) {
|
|
1244
|
+
console.error(`[Web Model Context] Error reading resource ${uri}:`, error);
|
|
1245
|
+
throw error;
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
throw new Error(`Resource not found: ${uri}`);
|
|
1249
|
+
}
|
|
1250
|
+
/**
|
|
1251
|
+
* Matches a URI against a URI template and extracts parameters.
|
|
1252
|
+
*
|
|
1253
|
+
* @param {string} template - The URI template (e.g., "file://{path}")
|
|
1254
|
+
* @param {string} uri - The actual URI to match
|
|
1255
|
+
* @returns {Record<string, string> | null} Extracted parameters or null if no match
|
|
1256
|
+
* @private
|
|
1257
|
+
*/
|
|
1258
|
+
matchUriTemplate(template, uri) {
|
|
1259
|
+
const paramNames = [];
|
|
1260
|
+
let regexPattern = template.replace(/[.*+?^${}()|[\]\\]/g, (char) => {
|
|
1261
|
+
if (char === "{" || char === "}") return char;
|
|
1262
|
+
return `\\${char}`;
|
|
1263
|
+
});
|
|
1264
|
+
regexPattern = regexPattern.replace(/\{([^}]+)\}/g, (_, paramName) => {
|
|
1265
|
+
paramNames.push(paramName);
|
|
1266
|
+
return "(.+)";
|
|
1267
|
+
});
|
|
1268
|
+
const regex = /* @__PURE__ */ new RegExp(`^${regexPattern}$`);
|
|
1269
|
+
const match = uri.match(regex);
|
|
1270
|
+
if (!match) return null;
|
|
1271
|
+
const params = {};
|
|
1272
|
+
for (let i = 0; i < paramNames.length; i++) {
|
|
1273
|
+
const paramName = paramNames[i];
|
|
1274
|
+
const paramValue = match[i + 1];
|
|
1275
|
+
if (paramName !== void 0 && paramValue !== void 0) params[paramName] = paramValue;
|
|
1276
|
+
}
|
|
1277
|
+
return params;
|
|
1278
|
+
}
|
|
1279
|
+
/**
|
|
1280
|
+
* Gets a prompt with arguments (internal use only by MCP bridge).
|
|
1281
|
+
*
|
|
1282
|
+
* @param {string} name - Name of the prompt
|
|
1283
|
+
* @param {Record<string, unknown>} args - Arguments to pass to the prompt
|
|
1284
|
+
* @returns {Promise<{messages: PromptMessage[]}>} The prompt messages
|
|
1285
|
+
* @throws {Error} If prompt is not found
|
|
1286
|
+
* @internal
|
|
1287
|
+
*/
|
|
1288
|
+
async getPrompt(name, args) {
|
|
1289
|
+
console.log(`[Web Model Context] Getting prompt: ${name}`);
|
|
1290
|
+
const prompt = this.bridge.prompts.get(name);
|
|
1291
|
+
if (!prompt) throw new Error(`Prompt not found: ${name}`);
|
|
1292
|
+
if (prompt.argsValidator && args) {
|
|
1293
|
+
const validation = validateWithZod(args, prompt.argsValidator);
|
|
1294
|
+
if (!validation.success) {
|
|
1295
|
+
console.error(`[Web Model Context] Argument validation failed for prompt ${name}:`, validation.error);
|
|
1296
|
+
throw new Error(`Argument validation error for prompt "${name}":\n${validation.error}`);
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
try {
|
|
1300
|
+
return await prompt.get(args ?? {});
|
|
1301
|
+
} catch (error) {
|
|
1302
|
+
console.error(`[Web Model Context] Error getting prompt ${name}:`, error);
|
|
1303
|
+
throw error;
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
1306
|
+
/**
|
|
817
1307
|
* Executes a tool with validation and event dispatch.
|
|
818
1308
|
* Follows this sequence:
|
|
819
1309
|
* 1. Validates input arguments against schema
|
|
@@ -897,6 +1387,32 @@ var WebModelContext = class {
|
|
|
897
1387
|
...tool.annotations && { annotations: tool.annotations }
|
|
898
1388
|
}));
|
|
899
1389
|
}
|
|
1390
|
+
/**
|
|
1391
|
+
* Request an LLM completion from the connected client.
|
|
1392
|
+
* This sends a sampling request to the connected MCP client.
|
|
1393
|
+
*
|
|
1394
|
+
* @param {SamplingRequestParams} params - Parameters for the sampling request
|
|
1395
|
+
* @returns {Promise<SamplingResult>} The LLM completion result
|
|
1396
|
+
*/
|
|
1397
|
+
async createMessage(params) {
|
|
1398
|
+
console.log("[Web Model Context] Requesting sampling from client");
|
|
1399
|
+
const underlyingServer = this.bridge.tabServer.server;
|
|
1400
|
+
if (!underlyingServer?.createMessage) throw new Error("Sampling is not supported: no connected client with sampling capability");
|
|
1401
|
+
return underlyingServer.createMessage(params);
|
|
1402
|
+
}
|
|
1403
|
+
/**
|
|
1404
|
+
* Request user input from the connected client.
|
|
1405
|
+
* This sends an elicitation request to the connected MCP client.
|
|
1406
|
+
*
|
|
1407
|
+
* @param {ElicitationParams} params - Parameters for the elicitation request
|
|
1408
|
+
* @returns {Promise<ElicitationResult>} The user's response
|
|
1409
|
+
*/
|
|
1410
|
+
async elicitInput(params) {
|
|
1411
|
+
console.log("[Web Model Context] Requesting elicitation from client");
|
|
1412
|
+
const underlyingServer = this.bridge.tabServer.server;
|
|
1413
|
+
if (!underlyingServer?.elicitInput) throw new Error("Elicitation is not supported: no connected client with elicitation capability");
|
|
1414
|
+
return underlyingServer.elicitInput(params);
|
|
1415
|
+
}
|
|
900
1416
|
};
|
|
901
1417
|
/**
|
|
902
1418
|
* Initializes the MCP bridge with dual-server support.
|
|
@@ -930,6 +1446,32 @@ function initializeMCPBridge(options) {
|
|
|
930
1446
|
throw error;
|
|
931
1447
|
}
|
|
932
1448
|
});
|
|
1449
|
+
server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
1450
|
+
console.log("[MCP Bridge] Handling list_resources request");
|
|
1451
|
+
return { resources: bridge$1.modelContext.listResources() };
|
|
1452
|
+
});
|
|
1453
|
+
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
1454
|
+
console.log(`[MCP Bridge] Handling read_resource request: ${request.params.uri}`);
|
|
1455
|
+
try {
|
|
1456
|
+
return await bridge$1.modelContext.readResource(request.params.uri);
|
|
1457
|
+
} catch (error) {
|
|
1458
|
+
console.error(`[MCP Bridge] Error reading resource ${request.params.uri}:`, error);
|
|
1459
|
+
throw error;
|
|
1460
|
+
}
|
|
1461
|
+
});
|
|
1462
|
+
server.setRequestHandler(ListPromptsRequestSchema, async () => {
|
|
1463
|
+
console.log("[MCP Bridge] Handling list_prompts request");
|
|
1464
|
+
return { prompts: bridge$1.modelContext.listPrompts() };
|
|
1465
|
+
});
|
|
1466
|
+
server.setRequestHandler(GetPromptRequestSchema, async (request) => {
|
|
1467
|
+
console.log(`[MCP Bridge] Handling get_prompt request: ${request.params.name}`);
|
|
1468
|
+
try {
|
|
1469
|
+
return await bridge$1.modelContext.getPrompt(request.params.name, request.params.arguments);
|
|
1470
|
+
} catch (error) {
|
|
1471
|
+
console.error(`[MCP Bridge] Error getting prompt ${request.params.name}:`, error);
|
|
1472
|
+
throw error;
|
|
1473
|
+
}
|
|
1474
|
+
});
|
|
933
1475
|
};
|
|
934
1476
|
const customTransport = transportOptions?.create?.();
|
|
935
1477
|
if (customTransport) {
|
|
@@ -937,10 +1479,16 @@ function initializeMCPBridge(options) {
|
|
|
937
1479
|
const server = new Server({
|
|
938
1480
|
name: hostname,
|
|
939
1481
|
version: "1.0.0"
|
|
940
|
-
}, { capabilities: {
|
|
1482
|
+
}, { capabilities: {
|
|
1483
|
+
tools: { listChanged: true },
|
|
1484
|
+
resources: { listChanged: true },
|
|
1485
|
+
prompts: { listChanged: true }
|
|
1486
|
+
} });
|
|
941
1487
|
const bridge$1 = {
|
|
942
1488
|
tabServer: server,
|
|
943
1489
|
tools: /* @__PURE__ */ new Map(),
|
|
1490
|
+
resources: /* @__PURE__ */ new Map(),
|
|
1491
|
+
prompts: /* @__PURE__ */ new Map(),
|
|
944
1492
|
modelContext: void 0,
|
|
945
1493
|
isInitialized: true
|
|
946
1494
|
};
|
|
@@ -955,10 +1503,16 @@ function initializeMCPBridge(options) {
|
|
|
955
1503
|
const tabServer = new Server({
|
|
956
1504
|
name: `${hostname}-tab`,
|
|
957
1505
|
version: "1.0.0"
|
|
958
|
-
}, { capabilities: {
|
|
1506
|
+
}, { capabilities: {
|
|
1507
|
+
tools: { listChanged: true },
|
|
1508
|
+
resources: { listChanged: true },
|
|
1509
|
+
prompts: { listChanged: true }
|
|
1510
|
+
} });
|
|
959
1511
|
const bridge = {
|
|
960
1512
|
tabServer,
|
|
961
1513
|
tools: /* @__PURE__ */ new Map(),
|
|
1514
|
+
resources: /* @__PURE__ */ new Map(),
|
|
1515
|
+
prompts: /* @__PURE__ */ new Map(),
|
|
962
1516
|
modelContext: void 0,
|
|
963
1517
|
isInitialized: true
|
|
964
1518
|
};
|
|
@@ -980,7 +1534,11 @@ function initializeMCPBridge(options) {
|
|
|
980
1534
|
const iframeServer = new Server({
|
|
981
1535
|
name: `${hostname}-iframe`,
|
|
982
1536
|
version: "1.0.0"
|
|
983
|
-
}, { capabilities: {
|
|
1537
|
+
}, { capabilities: {
|
|
1538
|
+
tools: { listChanged: true },
|
|
1539
|
+
resources: { listChanged: true },
|
|
1540
|
+
prompts: { listChanged: true }
|
|
1541
|
+
} });
|
|
984
1542
|
setupServerHandlers(iframeServer, bridge);
|
|
985
1543
|
const { allowedOrigins,...restIframeServerOptions } = typeof iframeServerConfig === "object" ? iframeServerConfig : {};
|
|
986
1544
|
const iframeTransport = new IframeChildTransport({
|