@mcp-fe/mcp-worker 0.1.11 → 0.2.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/LICENSE +27 -29
- package/docs/index.md +1 -0
- package/docs/multi-tab.md +637 -637
- package/docs/native-webmcp.md +232 -0
- package/docs/project-structure.md +172 -172
- package/docs/tab-manager.md +150 -150
- package/index.js +356 -59
- package/mcp-service-worker.js +43 -2
- package/mcp-shared-worker.js +43 -2
- package/package.json +1 -1
- package/src/client/index.d.ts +2 -0
- package/src/client/index.d.ts.map +1 -1
- package/src/client/web-mcp-adapter.d.ts +97 -0
- package/src/client/web-mcp-adapter.d.ts.map +1 -0
- package/src/client/web-mcp-types.d.ts +122 -0
- package/src/client/web-mcp-types.d.ts.map +1 -0
- package/src/client/worker-client.d.ts +31 -0
- package/src/client/worker-client.d.ts.map +1 -1
- package/src/worker/mcp-controller.d.ts +5 -0
- package/src/worker/mcp-controller.d.ts.map +1 -1
package/index.js
CHANGED
|
@@ -208,6 +208,197 @@ var ToolRegistry = class {
|
|
|
208
208
|
}
|
|
209
209
|
};
|
|
210
210
|
|
|
211
|
+
// libs/mcp-worker/src/client/web-mcp-adapter.ts
|
|
212
|
+
var WebMcpAdapter = class _WebMcpAdapter {
|
|
213
|
+
/** Tracks names of tools currently registered via WebMCP API */
|
|
214
|
+
registeredTools = /* @__PURE__ */ new Set();
|
|
215
|
+
/** Whether the adapter is enabled (enabled by default — auto-detects browser support) */
|
|
216
|
+
enabled = true;
|
|
217
|
+
// --------------------------------------------------------------------------
|
|
218
|
+
// Feature detection
|
|
219
|
+
// --------------------------------------------------------------------------
|
|
220
|
+
/**
|
|
221
|
+
* Check if the browser exposes the WebMCP API (`navigator.modelContext`).
|
|
222
|
+
*
|
|
223
|
+
* Safe to call in any environment (SSR, workers, older browsers).
|
|
224
|
+
*
|
|
225
|
+
* @see https://webmachinelearning.github.io/webmcp/#navigator-extension
|
|
226
|
+
*/
|
|
227
|
+
static isSupported() {
|
|
228
|
+
try {
|
|
229
|
+
return typeof navigator !== "undefined" && "modelContext" in navigator && navigator.modelContext != null && typeof navigator.modelContext.registerTool === "function";
|
|
230
|
+
} catch {
|
|
231
|
+
return false;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Convenience: instance-level check that also respects the `enabled` flag.
|
|
236
|
+
*/
|
|
237
|
+
isAvailable() {
|
|
238
|
+
return this.enabled && _WebMcpAdapter.isSupported();
|
|
239
|
+
}
|
|
240
|
+
// --------------------------------------------------------------------------
|
|
241
|
+
// Configuration
|
|
242
|
+
// --------------------------------------------------------------------------
|
|
243
|
+
/**
|
|
244
|
+
* Enable or disable the WebMCP adapter.
|
|
245
|
+
*
|
|
246
|
+
* When disabled, `registerTool` / `unregisterTool` become silent no-ops
|
|
247
|
+
* even if the browser supports the API.
|
|
248
|
+
*/
|
|
249
|
+
setEnabled(value) {
|
|
250
|
+
this.enabled = value;
|
|
251
|
+
logger.log(`[WebMcpAdapter] Enabled: ${value}`);
|
|
252
|
+
}
|
|
253
|
+
// --------------------------------------------------------------------------
|
|
254
|
+
// Registration
|
|
255
|
+
// --------------------------------------------------------------------------
|
|
256
|
+
/**
|
|
257
|
+
* Register a tool with the WebMCP API (`navigator.modelContext.registerTool()`).
|
|
258
|
+
*
|
|
259
|
+
* Silently returns if the API is unavailable or the adapter is disabled.
|
|
260
|
+
* Per spec, `registerTool()` throws if a tool with the same name already exists,
|
|
261
|
+
* so we unregister first if needed (idempotent update).
|
|
262
|
+
*
|
|
263
|
+
* @param name - Tool name
|
|
264
|
+
* @param description - Human-readable description (required by spec)
|
|
265
|
+
* @param inputSchema - JSON Schema for tool inputs
|
|
266
|
+
* @param handler - Async handler executed in the main thread
|
|
267
|
+
* @param options - Additional MCP tool options (annotations, …)
|
|
268
|
+
*/
|
|
269
|
+
registerTool(name, description, inputSchema, handler, options) {
|
|
270
|
+
if (!this.isAvailable()) {
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
if (this.registeredTools.has(name)) {
|
|
274
|
+
this.unregisterTool(name);
|
|
275
|
+
}
|
|
276
|
+
try {
|
|
277
|
+
const modelContext = navigator.modelContext;
|
|
278
|
+
const tool = {
|
|
279
|
+
name,
|
|
280
|
+
description: description ?? name,
|
|
281
|
+
inputSchema,
|
|
282
|
+
execute: async (input) => {
|
|
283
|
+
return handler(input);
|
|
284
|
+
},
|
|
285
|
+
annotations: this.mapAnnotations(options?.annotations)
|
|
286
|
+
};
|
|
287
|
+
modelContext.registerTool(tool);
|
|
288
|
+
this.registeredTools.add(name);
|
|
289
|
+
logger.log(
|
|
290
|
+
`[WebMcpAdapter] Registered tool '${name}' via navigator.modelContext`
|
|
291
|
+
);
|
|
292
|
+
} catch (error) {
|
|
293
|
+
logger.warn(`[WebMcpAdapter] Failed to register '${name}':`, error);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* Unregister a tool from the WebMCP API (`navigator.modelContext.unregisterTool()`).
|
|
298
|
+
*
|
|
299
|
+
* Safe to call even if the tool was never registered via WebMCP.
|
|
300
|
+
*
|
|
301
|
+
* @param name - Tool name to unregister
|
|
302
|
+
* @returns `true` if the tool was found and unregistered, `false` otherwise
|
|
303
|
+
*/
|
|
304
|
+
unregisterTool(name) {
|
|
305
|
+
if (!this.registeredTools.has(name)) {
|
|
306
|
+
return false;
|
|
307
|
+
}
|
|
308
|
+
try {
|
|
309
|
+
if (_WebMcpAdapter.isSupported()) {
|
|
310
|
+
const modelContext = navigator.modelContext;
|
|
311
|
+
modelContext.unregisterTool(name);
|
|
312
|
+
}
|
|
313
|
+
this.registeredTools.delete(name);
|
|
314
|
+
logger.log(
|
|
315
|
+
`[WebMcpAdapter] Unregistered tool '${name}' from navigator.modelContext`
|
|
316
|
+
);
|
|
317
|
+
return true;
|
|
318
|
+
} catch (error) {
|
|
319
|
+
logger.warn(`[WebMcpAdapter] Failed to unregister '${name}':`, error);
|
|
320
|
+
this.registeredTools.delete(name);
|
|
321
|
+
return false;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* Unregister ALL tools registered via WebMCP.
|
|
326
|
+
*
|
|
327
|
+
* Uses `navigator.modelContext.clearContext()` when available (per spec, clears
|
|
328
|
+
* all tools at once), otherwise falls back to individual unregisterTool calls.
|
|
329
|
+
*/
|
|
330
|
+
clearAll() {
|
|
331
|
+
if (this.registeredTools.size === 0)
|
|
332
|
+
return;
|
|
333
|
+
logger.log(`[WebMcpAdapter] Clearing ${this.registeredTools.size} tool(s)`);
|
|
334
|
+
try {
|
|
335
|
+
if (_WebMcpAdapter.isSupported()) {
|
|
336
|
+
const modelContext = navigator.modelContext;
|
|
337
|
+
modelContext.clearContext();
|
|
338
|
+
}
|
|
339
|
+
} catch (error) {
|
|
340
|
+
logger.warn(
|
|
341
|
+
"[WebMcpAdapter] clearContext() failed, falling back to individual unregister:",
|
|
342
|
+
error
|
|
343
|
+
);
|
|
344
|
+
const names = Array.from(this.registeredTools);
|
|
345
|
+
names.forEach((name) => {
|
|
346
|
+
try {
|
|
347
|
+
if (_WebMcpAdapter.isSupported()) {
|
|
348
|
+
navigator.modelContext.unregisterTool(name);
|
|
349
|
+
}
|
|
350
|
+
} catch {
|
|
351
|
+
}
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
this.registeredTools.clear();
|
|
355
|
+
}
|
|
356
|
+
// --------------------------------------------------------------------------
|
|
357
|
+
// Query
|
|
358
|
+
// --------------------------------------------------------------------------
|
|
359
|
+
/**
|
|
360
|
+
* Check if a tool is registered via WebMCP.
|
|
361
|
+
*/
|
|
362
|
+
isRegistered(name) {
|
|
363
|
+
return this.registeredTools.has(name);
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* Get names of all tools registered via WebMCP.
|
|
367
|
+
*/
|
|
368
|
+
getRegisteredTools() {
|
|
369
|
+
return Array.from(this.registeredTools);
|
|
370
|
+
}
|
|
371
|
+
// --------------------------------------------------------------------------
|
|
372
|
+
// Mapping helpers
|
|
373
|
+
// --------------------------------------------------------------------------
|
|
374
|
+
/**
|
|
375
|
+
* Map internal ToolAnnotations to WebMCP ToolAnnotations.
|
|
376
|
+
* The spec currently only defines `readOnlyHint`.
|
|
377
|
+
*/
|
|
378
|
+
mapAnnotations(annotations) {
|
|
379
|
+
if (!annotations)
|
|
380
|
+
return void 0;
|
|
381
|
+
return {
|
|
382
|
+
readOnlyHint: annotations.readOnlyHint
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
/**
|
|
386
|
+
* Convert internal ToolDefinition to ModelContextTool format (utility).
|
|
387
|
+
* Requires an execute callback to be provided separately.
|
|
388
|
+
*/
|
|
389
|
+
static toModelContextTool(tool, handler) {
|
|
390
|
+
return {
|
|
391
|
+
name: tool.name,
|
|
392
|
+
description: tool.description ?? tool.name,
|
|
393
|
+
inputSchema: tool.inputSchema,
|
|
394
|
+
execute: async (input) => {
|
|
395
|
+
return handler(input);
|
|
396
|
+
},
|
|
397
|
+
annotations: tool.annotations ? { readOnlyHint: tool.annotations.readOnlyHint } : void 0
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
};
|
|
401
|
+
|
|
211
402
|
// libs/mcp-worker/src/client/worker-client.ts
|
|
212
403
|
var WorkerClient = class _WorkerClient {
|
|
213
404
|
// Configurable worker script URLs (defaults kept for backward compatibility)
|
|
@@ -229,6 +420,8 @@ var WorkerClient = class _WorkerClient {
|
|
|
229
420
|
initResolvers = [];
|
|
230
421
|
// Tool registry for managing tool lifecycle
|
|
231
422
|
toolRegistry = new ToolRegistry();
|
|
423
|
+
// WebMCP adapter for browser-level tool registration via navigator.modelContext
|
|
424
|
+
webMcpAdapter = new WebMcpAdapter();
|
|
232
425
|
// Tab tracking for multi-tab support
|
|
233
426
|
tabId;
|
|
234
427
|
static TAB_ID_STORAGE_KEY = "mcp_fe_tab_id";
|
|
@@ -339,6 +532,7 @@ var WorkerClient = class _WorkerClient {
|
|
|
339
532
|
logger.log(
|
|
340
533
|
`[WorkerClient] Page unloading, cleaning up ${toolNames.length} tool(s)`
|
|
341
534
|
);
|
|
535
|
+
this.webMcpAdapter.clearAll();
|
|
342
536
|
toolNames.forEach((toolName) => {
|
|
343
537
|
const existing = this.toolRegistry.getInfo(toolName);
|
|
344
538
|
if (!existing)
|
|
@@ -383,6 +577,14 @@ var WorkerClient = class _WorkerClient {
|
|
|
383
577
|
this.serviceWorkerUrl = opts.serviceWorkerUrl;
|
|
384
578
|
if (opts.backendWsUrl)
|
|
385
579
|
this.backendWsUrl = opts.backendWsUrl;
|
|
580
|
+
if (opts.enableWebMcp !== void 0) {
|
|
581
|
+
this.webMcpAdapter.setEnabled(opts.enableWebMcp);
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
if (this.webMcpAdapter.isAvailable()) {
|
|
585
|
+
logger.log(
|
|
586
|
+
"[WorkerClient] WebMCP: auto-enabled (navigator.modelContext detected)"
|
|
587
|
+
);
|
|
386
588
|
}
|
|
387
589
|
if (this.initPromise) {
|
|
388
590
|
return this.initPromise.then(async () => {
|
|
@@ -459,6 +661,7 @@ var WorkerClient = class _WorkerClient {
|
|
|
459
661
|
const details = this.toolRegistry.getDetails(toolName);
|
|
460
662
|
if (!details)
|
|
461
663
|
return;
|
|
664
|
+
const handler = this.toolRegistry.getHandler(toolName);
|
|
462
665
|
try {
|
|
463
666
|
await this.registerToolInWorker(
|
|
464
667
|
details.name,
|
|
@@ -473,6 +676,22 @@ var WorkerClient = class _WorkerClient {
|
|
|
473
676
|
title: details.title
|
|
474
677
|
}
|
|
475
678
|
);
|
|
679
|
+
if (handler) {
|
|
680
|
+
this.webMcpAdapter.registerTool(
|
|
681
|
+
details.name,
|
|
682
|
+
details.description,
|
|
683
|
+
details.inputSchema,
|
|
684
|
+
handler,
|
|
685
|
+
{
|
|
686
|
+
outputSchema: details.outputSchema,
|
|
687
|
+
annotations: details.annotations,
|
|
688
|
+
execution: details.execution,
|
|
689
|
+
_meta: details._meta,
|
|
690
|
+
icons: details.icons,
|
|
691
|
+
title: details.title
|
|
692
|
+
}
|
|
693
|
+
);
|
|
694
|
+
}
|
|
476
695
|
} catch (error) {
|
|
477
696
|
logger.error(
|
|
478
697
|
`[WorkerClient] Failed to sync tool '${toolName}' to worker:`,
|
|
@@ -604,78 +823,87 @@ var WorkerClient = class _WorkerClient {
|
|
|
604
823
|
}
|
|
605
824
|
}
|
|
606
825
|
async initServiceWorkerFallback() {
|
|
607
|
-
if ("serviceWorker" in navigator) {
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
}
|
|
826
|
+
if (!("serviceWorker" in navigator)) {
|
|
827
|
+
throw new Error("Neither SharedWorker nor ServiceWorker is supported");
|
|
828
|
+
}
|
|
829
|
+
try {
|
|
830
|
+
const existingRegistration = await navigator.serviceWorker.getRegistration();
|
|
831
|
+
if (existingRegistration) {
|
|
832
|
+
this.serviceWorkerRegistration = existingRegistration;
|
|
833
|
+
this.workerType = "service";
|
|
834
|
+
logger.info("[WorkerClient] Using existing ServiceWorker registration");
|
|
835
|
+
} else {
|
|
618
836
|
this.serviceWorkerRegistration = await navigator.serviceWorker.register(
|
|
619
837
|
this.serviceWorkerUrl
|
|
620
838
|
);
|
|
621
839
|
this.workerType = "service";
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
return;
|
|
636
|
-
}
|
|
637
|
-
this.handleToolCall(
|
|
638
|
-
data.toolName,
|
|
639
|
-
data.args,
|
|
640
|
-
data.callId
|
|
641
|
-
).catch((error) => {
|
|
642
|
-
logger.error(
|
|
643
|
-
"[WorkerClient] Failed to handle tool call:",
|
|
644
|
-
error
|
|
645
|
-
);
|
|
646
|
-
});
|
|
840
|
+
logger.info("[WorkerClient] Using MCP ServiceWorker (fallback)");
|
|
841
|
+
}
|
|
842
|
+
navigator.serviceWorker.addEventListener(
|
|
843
|
+
"message",
|
|
844
|
+
(ev) => {
|
|
845
|
+
try {
|
|
846
|
+
const data = ev.data;
|
|
847
|
+
if (data && data.type === "CONNECTION_STATUS") {
|
|
848
|
+
const connected = !!data.connected;
|
|
849
|
+
this.connectionStatusCallbacks.forEach((cb) => {
|
|
850
|
+
try {
|
|
851
|
+
cb(connected);
|
|
852
|
+
} catch {
|
|
647
853
|
}
|
|
648
|
-
}
|
|
854
|
+
});
|
|
855
|
+
} else if (data && data.type === "CALL_TOOL") {
|
|
856
|
+
const targetTabId = data.targetTabId;
|
|
857
|
+
if (targetTabId && targetTabId !== this.tabId) {
|
|
858
|
+
logger.log(
|
|
859
|
+
`[WorkerClient] Ignoring CALL_TOOL (not for this tab): ${data.toolName}`,
|
|
860
|
+
{ targetTabId, myTabId: this.tabId }
|
|
861
|
+
);
|
|
862
|
+
return;
|
|
863
|
+
}
|
|
864
|
+
this.handleToolCall(
|
|
865
|
+
data.toolName,
|
|
866
|
+
data.args,
|
|
867
|
+
data.callId
|
|
868
|
+
).catch((error) => {
|
|
649
869
|
logger.error(
|
|
650
|
-
"[WorkerClient]
|
|
870
|
+
"[WorkerClient] Failed to handle tool call:",
|
|
651
871
|
error
|
|
652
872
|
);
|
|
653
|
-
}
|
|
873
|
+
});
|
|
654
874
|
}
|
|
655
|
-
)
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
type: "INIT",
|
|
661
|
-
backendUrl: this.backendWsUrl
|
|
662
|
-
};
|
|
663
|
-
if (this.pendingAuthToken)
|
|
664
|
-
initMsg["token"] = this.pendingAuthToken;
|
|
665
|
-
if (this.serviceWorkerRegistration.active) {
|
|
666
|
-
this.serviceWorkerRegistration.active.postMessage(initMsg);
|
|
667
|
-
} else if ("serviceWorker" in navigator && navigator.serviceWorker.controller) {
|
|
668
|
-
navigator.serviceWorker.controller.postMessage(initMsg);
|
|
875
|
+
} catch (error) {
|
|
876
|
+
logger.error(
|
|
877
|
+
"[WorkerClient] Error processing ServiceWorker message:",
|
|
878
|
+
error
|
|
879
|
+
);
|
|
669
880
|
}
|
|
881
|
+
}
|
|
882
|
+
);
|
|
883
|
+
try {
|
|
884
|
+
const initMsg = {
|
|
885
|
+
type: "INIT",
|
|
886
|
+
backendUrl: this.backendWsUrl
|
|
887
|
+
};
|
|
888
|
+
if (this.pendingAuthToken)
|
|
889
|
+
initMsg["token"] = this.pendingAuthToken;
|
|
890
|
+
const sendInit = (worker) => {
|
|
891
|
+
worker.postMessage(initMsg);
|
|
670
892
|
this.pendingAuthToken = null;
|
|
671
|
-
}
|
|
893
|
+
};
|
|
894
|
+
if (this.serviceWorkerRegistration.active) {
|
|
895
|
+
sendInit(this.serviceWorkerRegistration.active);
|
|
896
|
+
} else {
|
|
897
|
+
navigator.serviceWorker.ready.then((reg) => {
|
|
898
|
+
if (reg.active)
|
|
899
|
+
sendInit(reg.active);
|
|
900
|
+
});
|
|
672
901
|
}
|
|
673
|
-
} catch
|
|
674
|
-
logger.error("[WorkerClient] Failed to register ServiceWorker:", error);
|
|
675
|
-
throw error;
|
|
902
|
+
} catch {
|
|
676
903
|
}
|
|
677
|
-
}
|
|
678
|
-
|
|
904
|
+
} catch (error) {
|
|
905
|
+
logger.error("[WorkerClient] Failed to register ServiceWorker:", error);
|
|
906
|
+
throw error;
|
|
679
907
|
}
|
|
680
908
|
}
|
|
681
909
|
// Low-level request that expects a reply via MessageChannel
|
|
@@ -1003,6 +1231,13 @@ var WorkerClient = class _WorkerClient {
|
|
|
1003
1231
|
}
|
|
1004
1232
|
if (isNew) {
|
|
1005
1233
|
await this.registerToolInWorker(name, description, inputSchema, options);
|
|
1234
|
+
this.webMcpAdapter.registerTool(
|
|
1235
|
+
name,
|
|
1236
|
+
description,
|
|
1237
|
+
inputSchema,
|
|
1238
|
+
handler,
|
|
1239
|
+
options
|
|
1240
|
+
);
|
|
1006
1241
|
}
|
|
1007
1242
|
}
|
|
1008
1243
|
/**
|
|
@@ -1060,6 +1295,7 @@ var WorkerClient = class _WorkerClient {
|
|
|
1060
1295
|
"UNREGISTER_TOOL",
|
|
1061
1296
|
{ name, tabId: this.tabId }
|
|
1062
1297
|
);
|
|
1298
|
+
this.webMcpAdapter.unregisterTool(name);
|
|
1063
1299
|
logger.log(`[WorkerClient] Unregistered tool '${name}'`);
|
|
1064
1300
|
return workerResult?.success ?? false;
|
|
1065
1301
|
}
|
|
@@ -1096,6 +1332,66 @@ var WorkerClient = class _WorkerClient {
|
|
|
1096
1332
|
isToolRegistered(toolName) {
|
|
1097
1333
|
return this.toolRegistry.isRegistered(toolName);
|
|
1098
1334
|
}
|
|
1335
|
+
// --------------------------------------------------------------------------
|
|
1336
|
+
// WebMCP API
|
|
1337
|
+
// --------------------------------------------------------------------------
|
|
1338
|
+
/**
|
|
1339
|
+
* Check if the browser supports the WebMCP API (`navigator.modelContext`).
|
|
1340
|
+
*/
|
|
1341
|
+
static isWebMcpSupported() {
|
|
1342
|
+
return WebMcpAdapter.isSupported();
|
|
1343
|
+
}
|
|
1344
|
+
/**
|
|
1345
|
+
* Check if WebMCP is both enabled AND supported.
|
|
1346
|
+
*/
|
|
1347
|
+
isWebMcpAvailable() {
|
|
1348
|
+
return this.webMcpAdapter.isAvailable();
|
|
1349
|
+
}
|
|
1350
|
+
/**
|
|
1351
|
+
* Enable or disable WebMCP registration at runtime.
|
|
1352
|
+
* When enabled and the browser supports it, newly registered tools will also
|
|
1353
|
+
* be advertised via `navigator.modelContext`. Existing tools are synced immediately.
|
|
1354
|
+
*/
|
|
1355
|
+
setWebMcpEnabled(enabled) {
|
|
1356
|
+
this.webMcpAdapter.setEnabled(enabled);
|
|
1357
|
+
if (enabled && WebMcpAdapter.isSupported() && this.isInitialized) {
|
|
1358
|
+
const toolNames = this.toolRegistry.getRegisteredTools();
|
|
1359
|
+
toolNames.forEach((toolName) => {
|
|
1360
|
+
const details = this.toolRegistry.getDetails(toolName);
|
|
1361
|
+
const handler = this.toolRegistry.getHandler(toolName);
|
|
1362
|
+
if (!details || !handler)
|
|
1363
|
+
return;
|
|
1364
|
+
this.webMcpAdapter.registerTool(
|
|
1365
|
+
details.name,
|
|
1366
|
+
details.description,
|
|
1367
|
+
details.inputSchema,
|
|
1368
|
+
handler,
|
|
1369
|
+
{
|
|
1370
|
+
outputSchema: details.outputSchema,
|
|
1371
|
+
annotations: details.annotations,
|
|
1372
|
+
execution: details.execution,
|
|
1373
|
+
_meta: details._meta,
|
|
1374
|
+
icons: details.icons,
|
|
1375
|
+
title: details.title
|
|
1376
|
+
}
|
|
1377
|
+
);
|
|
1378
|
+
});
|
|
1379
|
+
} else if (!enabled) {
|
|
1380
|
+
this.webMcpAdapter.clearAll();
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1383
|
+
/**
|
|
1384
|
+
* Check if a specific tool is registered via the WebMCP API.
|
|
1385
|
+
*/
|
|
1386
|
+
isToolRegisteredViaWebMcp(toolName) {
|
|
1387
|
+
return this.webMcpAdapter.isRegistered(toolName);
|
|
1388
|
+
}
|
|
1389
|
+
/**
|
|
1390
|
+
* Get names of tools registered via the WebMCP API.
|
|
1391
|
+
*/
|
|
1392
|
+
getWebMcpRegisteredTools() {
|
|
1393
|
+
return this.webMcpAdapter.getRegisteredTools();
|
|
1394
|
+
}
|
|
1099
1395
|
/**
|
|
1100
1396
|
* Handle tool call from worker - execute handler in main thread and return result
|
|
1101
1397
|
* @private
|
|
@@ -1235,6 +1531,7 @@ async function queryEvents(filters) {
|
|
|
1235
1531
|
});
|
|
1236
1532
|
}
|
|
1237
1533
|
export {
|
|
1534
|
+
WebMcpAdapter,
|
|
1238
1535
|
WorkerClient,
|
|
1239
1536
|
logger,
|
|
1240
1537
|
queryEvents,
|
package/mcp-service-worker.js
CHANGED
|
@@ -21357,14 +21357,24 @@ var MCPController = class _MCPController {
|
|
|
21357
21357
|
this.socket = null;
|
|
21358
21358
|
}
|
|
21359
21359
|
return new Promise((resolve) => {
|
|
21360
|
-
|
|
21361
|
-
this.socket = new WebSocket(url2);
|
|
21360
|
+
this.socket = new WebSocket(this.backendUrl);
|
|
21362
21361
|
this.socket.onopen = async () => {
|
|
21363
21362
|
logger.log("[MCPController] Connected to backend MCP server");
|
|
21364
21363
|
this.reconnectAttempts = 0;
|
|
21365
21364
|
this.isReconnectingForToken = false;
|
|
21366
21365
|
try {
|
|
21367
21366
|
if (this.socket) {
|
|
21367
|
+
if (this.authToken) {
|
|
21368
|
+
try {
|
|
21369
|
+
await this.performAuthHandshake(this.socket, this.authToken);
|
|
21370
|
+
} catch (error2) {
|
|
21371
|
+
logger.error("[MCPController] Auth handshake failed:", error2);
|
|
21372
|
+
this.broadcastFn({ type: "CONNECTION_STATUS", connected: false });
|
|
21373
|
+
this.socket.close(4001, "Authentication failed");
|
|
21374
|
+
resolve();
|
|
21375
|
+
return;
|
|
21376
|
+
}
|
|
21377
|
+
}
|
|
21368
21378
|
this.transport = new WebSocketTransport(this.socket);
|
|
21369
21379
|
if (typeof this.transport.start === "function") {
|
|
21370
21380
|
try {
|
|
@@ -21667,6 +21677,37 @@ var MCPController = class _MCPController {
|
|
|
21667
21677
|
}
|
|
21668
21678
|
return true;
|
|
21669
21679
|
}
|
|
21680
|
+
/**
|
|
21681
|
+
* Sends AUTH message and waits for AUTH_OK before MCP transport is started.
|
|
21682
|
+
* Token is never sent in the URL — only in this initial payload message.
|
|
21683
|
+
*/
|
|
21684
|
+
performAuthHandshake(socket, token) {
|
|
21685
|
+
return new Promise((resolve, reject) => {
|
|
21686
|
+
const timeout = setTimeout(() => {
|
|
21687
|
+
socket.removeEventListener("message", handleMessage);
|
|
21688
|
+
reject(new Error("Authentication timeout"));
|
|
21689
|
+
}, 1e4);
|
|
21690
|
+
const handleMessage = (event) => {
|
|
21691
|
+
let msg;
|
|
21692
|
+
try {
|
|
21693
|
+
msg = JSON.parse(event.data);
|
|
21694
|
+
} catch {
|
|
21695
|
+
return;
|
|
21696
|
+
}
|
|
21697
|
+
if (msg.type === "AUTH_OK") {
|
|
21698
|
+
clearTimeout(timeout);
|
|
21699
|
+
socket.removeEventListener("message", handleMessage);
|
|
21700
|
+
resolve();
|
|
21701
|
+
} else if (msg.type === "AUTH_ERROR") {
|
|
21702
|
+
clearTimeout(timeout);
|
|
21703
|
+
socket.removeEventListener("message", handleMessage);
|
|
21704
|
+
reject(new Error(msg.message || "Authentication rejected by server"));
|
|
21705
|
+
}
|
|
21706
|
+
};
|
|
21707
|
+
socket.addEventListener("message", handleMessage);
|
|
21708
|
+
socket.send(JSON.stringify({ type: "AUTH", token }));
|
|
21709
|
+
});
|
|
21710
|
+
}
|
|
21670
21711
|
getConnectionStatus() {
|
|
21671
21712
|
return this.socket?.readyState === WebSocket.OPEN;
|
|
21672
21713
|
}
|
package/mcp-shared-worker.js
CHANGED
|
@@ -21357,14 +21357,24 @@ var MCPController = class _MCPController {
|
|
|
21357
21357
|
this.socket = null;
|
|
21358
21358
|
}
|
|
21359
21359
|
return new Promise((resolve) => {
|
|
21360
|
-
|
|
21361
|
-
this.socket = new WebSocket(url2);
|
|
21360
|
+
this.socket = new WebSocket(this.backendUrl);
|
|
21362
21361
|
this.socket.onopen = async () => {
|
|
21363
21362
|
logger.log("[MCPController] Connected to backend MCP server");
|
|
21364
21363
|
this.reconnectAttempts = 0;
|
|
21365
21364
|
this.isReconnectingForToken = false;
|
|
21366
21365
|
try {
|
|
21367
21366
|
if (this.socket) {
|
|
21367
|
+
if (this.authToken) {
|
|
21368
|
+
try {
|
|
21369
|
+
await this.performAuthHandshake(this.socket, this.authToken);
|
|
21370
|
+
} catch (error2) {
|
|
21371
|
+
logger.error("[MCPController] Auth handshake failed:", error2);
|
|
21372
|
+
this.broadcastFn({ type: "CONNECTION_STATUS", connected: false });
|
|
21373
|
+
this.socket.close(4001, "Authentication failed");
|
|
21374
|
+
resolve();
|
|
21375
|
+
return;
|
|
21376
|
+
}
|
|
21377
|
+
}
|
|
21368
21378
|
this.transport = new WebSocketTransport(this.socket);
|
|
21369
21379
|
if (typeof this.transport.start === "function") {
|
|
21370
21380
|
try {
|
|
@@ -21667,6 +21677,37 @@ var MCPController = class _MCPController {
|
|
|
21667
21677
|
}
|
|
21668
21678
|
return true;
|
|
21669
21679
|
}
|
|
21680
|
+
/**
|
|
21681
|
+
* Sends AUTH message and waits for AUTH_OK before MCP transport is started.
|
|
21682
|
+
* Token is never sent in the URL — only in this initial payload message.
|
|
21683
|
+
*/
|
|
21684
|
+
performAuthHandshake(socket, token) {
|
|
21685
|
+
return new Promise((resolve, reject) => {
|
|
21686
|
+
const timeout = setTimeout(() => {
|
|
21687
|
+
socket.removeEventListener("message", handleMessage);
|
|
21688
|
+
reject(new Error("Authentication timeout"));
|
|
21689
|
+
}, 1e4);
|
|
21690
|
+
const handleMessage = (event) => {
|
|
21691
|
+
let msg;
|
|
21692
|
+
try {
|
|
21693
|
+
msg = JSON.parse(event.data);
|
|
21694
|
+
} catch {
|
|
21695
|
+
return;
|
|
21696
|
+
}
|
|
21697
|
+
if (msg.type === "AUTH_OK") {
|
|
21698
|
+
clearTimeout(timeout);
|
|
21699
|
+
socket.removeEventListener("message", handleMessage);
|
|
21700
|
+
resolve();
|
|
21701
|
+
} else if (msg.type === "AUTH_ERROR") {
|
|
21702
|
+
clearTimeout(timeout);
|
|
21703
|
+
socket.removeEventListener("message", handleMessage);
|
|
21704
|
+
reject(new Error(msg.message || "Authentication rejected by server"));
|
|
21705
|
+
}
|
|
21706
|
+
};
|
|
21707
|
+
socket.addEventListener("message", handleMessage);
|
|
21708
|
+
socket.send(JSON.stringify({ type: "AUTH", token }));
|
|
21709
|
+
});
|
|
21710
|
+
}
|
|
21670
21711
|
getConnectionStatus() {
|
|
21671
21712
|
return this.socket?.readyState === WebSocket.OPEN;
|
|
21672
21713
|
}
|
package/package.json
CHANGED
package/src/client/index.d.ts
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
* Use this in your application code
|
|
4
4
|
*/
|
|
5
5
|
export { WorkerClient, type WorkerClientInitOptions } from './worker-client';
|
|
6
|
+
export { WebMcpAdapter } from './web-mcp-adapter';
|
|
7
|
+
export type { ModelContext, ModelContextTool, ModelContextOptions, ModelContextClient, ToolExecuteCallback, UserInteractionCallback, WebMcpToolAnnotations, } from './web-mcp-types';
|
|
6
8
|
export type { ToolHandler, ToolDefinition, Icon, ToolAnnotations, ToolExecution, UserEvent, EventFilters, TabInfo, } from '../shared/types';
|
|
7
9
|
export { logger } from '../shared/logger';
|
|
8
10
|
import { WorkerClient } from './worker-client';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../libs/mcp-worker/src/client/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,YAAY,EAAE,KAAK,uBAAuB,EAAE,MAAM,iBAAiB,CAAC;AAG7E,YAAY,EACV,WAAW,EACX,cAAc,EACd,IAAI,EACJ,eAAe,EACf,aAAa,EACb,SAAS,EACT,YAAY,EACZ,OAAO,GACR,MAAM,iBAAiB,CAAC;AAGzB,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAG1C,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,eAAO,MAAM,YAAY,cAAqB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../libs/mcp-worker/src/client/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,YAAY,EAAE,KAAK,uBAAuB,EAAE,MAAM,iBAAiB,CAAC;AAG7E,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAClD,YAAY,EACV,YAAY,EACZ,gBAAgB,EAChB,mBAAmB,EACnB,kBAAkB,EAClB,mBAAmB,EACnB,uBAAuB,EACvB,qBAAqB,GACtB,MAAM,iBAAiB,CAAC;AAGzB,YAAY,EACV,WAAW,EACX,cAAc,EACd,IAAI,EACJ,eAAe,EACf,aAAa,EACb,SAAS,EACT,YAAY,EACZ,OAAO,GACR,MAAM,iBAAiB,CAAC;AAGzB,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAG1C,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,eAAO,MAAM,YAAY,cAAqB,CAAC"}
|