@frontmcp/plugin-webmcp 1.9.4 → 1.9.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -38,13 +38,14 @@ An agent's call runs the server's `tools:call-tool` flow, so hooks, authorities,
38
38
 
39
39
  ## Options
40
40
 
41
- | Option | Type | Default | Description |
42
- | -------------- | ---------------------------------------------- | ------------------------- | ------------------------------------------------------------------ |
43
- | `prefix` | `string` | `''` | Prepended to every exposed tool name, e.g. `'shop.'`. |
44
- | `include` | `(tool) => boolean` | all | Decides which listed tools are exposed. |
45
- | `exposedTo` | `string[]` | — | Other origins (e.g. an iframe's parent) the tools are offered to. |
46
- | `authContext` | `DirectAuthContext \| () => DirectAuthContext` | anonymous `webmcp` caller | Who the server sees calling. A function is resolved on every call. |
47
- | `modelContext` | `ModelContext` | `document.modelContext` | The context to register with: a polyfill, or a test double. |
41
+ | Option | Type | Default | Description |
42
+ | -------------- | ---------------------------------------------- | ------------------------- | ------------------------------------------------------------------- |
43
+ | `prefix` | `string` | `''` | Prepended to every exposed tool name, e.g. `'shop.'`. |
44
+ | `include` | `(tool) => boolean` | all | Decides which listed tools are exposed. |
45
+ | `exposedTo` | `string[]` | — | Other origins (e.g. an iframe's parent) the tools are offered to. |
46
+ | `authContext` | `DirectAuthContext \| () => DirectAuthContext` | anonymous `webmcp` caller | Who the server sees calling. A function is resolved on every call. |
47
+ | `modelContext` | `ModelContext` | `document.modelContext` | The context to register with: a polyfill, or a test double. |
48
+ | `result` | `'structured' \| 'content' \| 'both'` | `'structured'` | What a call resolves to (see [Results](#how-tools-are-translated)). |
48
49
 
49
50
  ## Choosing which tools agents see
50
51
 
@@ -61,9 +62,63 @@ Tools are listed through the `'webmcp'` call surface, so `availableWhen` decides
61
62
  `app:tool` becomes `app_tool`), names are cut to 128 characters, and collisions get `_2`, `_3`, …
62
63
  - **Annotations.** MCP's `readOnlyHint` maps to `readOnlyHint`. An explicit `destructiveHint: true`
63
64
  maps to `consequentialHint`, and an explicit `openWorldHint: true` to `untrustedContentHint`.
64
- - **Results.** A call resolves to `{ content, structuredContent? }`, without `_meta`.
65
+ - **Results.** An agent reads the whole result as text, so by default it gets each result once. With
66
+ `result: 'structured'` (the default) a call resolves to the tool's `structuredContent` alone when its content only
67
+ repeats it: one text block with the same JSON, or the primitive a `{ content }` wrapper holds, as the server writes
68
+ it for clients that don't read structured output. Otherwise it resolves to `{ content }`, plus `structuredContent`
69
+ when there is one, so text that says more and images are kept. `'content'` always resolves to `{ content }`, and
70
+ `'both'` to `{ content, structuredContent }` as an MCP client gets them (the shape up to 1.9.4). `_meta` is never
71
+ included.
65
72
  - **Errors.** An error result (`isError`), or an error from the server, rejects with its message.
66
73
 
74
+ ## Registering tools before the server loads
75
+
76
+ A server in the page is a large bundle, and starting it is one long task. To keep it off page load, compute the
77
+ tool list at build time and register it in the page; the server loads on an agent's first call:
78
+
79
+ `server.ts`, one factory for the build script and the page:
80
+
81
+ ```typescript
82
+ export function createShopServer({ modelContext }: { modelContext?: ModelContext } = {}) {
83
+ return create({
84
+ info: { name: 'shop', version: '1.0.0' },
85
+ tools: [SearchProducts, AddToCart],
86
+ plugins: [WebMcpPlugin.init({ prefix: 'shop.', modelContext })],
87
+ });
88
+ }
89
+ ```
90
+
91
+ The build script (Node):
92
+
93
+ ```typescript
94
+ import { listWebMcpTools } from '@frontmcp/plugin-webmcp';
95
+
96
+ const tools = await listWebMcpTools((modelContext) => createShopServer({ modelContext }));
97
+ await writeFile('src/webmcp-tools.json', JSON.stringify(tools));
98
+ ```
99
+
100
+ The page entry, a chunk without the SDK:
101
+
102
+ ```typescript
103
+ import { registerWebMcpTools, resolveDocumentModelContext } from '@frontmcp/plugin-webmcp/register';
104
+
105
+ import tools from './webmcp-tools.json';
106
+
107
+ void registerWebMcpTools(resolveDocumentModelContext(), tools, (modelContext) =>
108
+ import('./server').then(({ createShopServer }) => createShopServer({ modelContext })),
109
+ );
110
+ ```
111
+
112
+ - `listWebMcpTools(factory)` builds the server with a `modelContext` that records what the plugin registers,
113
+ returns those descriptors (name, title, description, input schema, hints) and disposes the server. The factory
114
+ must pass the `modelContext` it is given to `WebMcpPlugin.init()`.
115
+ - `registerWebMcpTools(modelContext, tools, loadServer, { exposedTo? })` registers the list at once. The first call
116
+ loads the server with `loadServer` (once, however many calls arrive together) and runs through the plugin; a failed
117
+ load rejects that call (a server it built is disposed, and the listed tools stay), and the next call tries again. From then on the plugin registers and unregisters tools as
118
+ the server's tools change, and `server.dispose()` unregisters them. A listed tool the loaded server lacks is
119
+ unregistered. Without a `modelContext` (no WebMCP) it does nothing.
120
+ - `@frontmcp/plugin-webmcp/register` does not import `@frontmcp/sdk`; it is a few kilobytes.
121
+
67
122
  ## Browser support
68
123
 
69
124
  WebMCP is in origin trial in Chrome and Edge (Chrome 149+). For local development, enable
package/esm/index.mjs CHANGED
@@ -18,9 +18,17 @@ import {
18
18
  InternalMcpError
19
19
  } from "@frontmcp/sdk";
20
20
  import { randomUUID, runRequestExclusive } from "@frontmcp/utils";
21
- var MAX_WEBMCP_NAME_LENGTH = 128;
22
- var INVALID_NAME_CHARACTERS = /[^A-Za-z0-9_.-]/g;
23
- var ANONYMOUS_SUBJECT = "anon:webmcp";
21
+
22
+ // plugins/plugin-webmcp/src/webmcp.handoff.ts
23
+ var SYNC_LISTENER = /* @__PURE__ */ Symbol.for("@frontmcp/plugin-webmcp/sync-listener");
24
+ function withSyncListener(modelContext, listener) {
25
+ return Object.assign(modelContext, { [SYNC_LISTENER]: listener });
26
+ }
27
+ function syncListenerOf(modelContext) {
28
+ return modelContext?.[SYNC_LISTENER];
29
+ }
30
+
31
+ // plugins/plugin-webmcp/src/webmcp.model-context.ts
24
32
  function resolveDocumentModelContext() {
25
33
  const modelContext = globalThis.document?.modelContext;
26
34
  return typeof modelContext === "object" && modelContext !== null && typeof modelContext.registerTool === "function" ? modelContext : void 0;
@@ -28,6 +36,11 @@ function resolveDocumentModelContext() {
28
36
  function isWebMcpSupported() {
29
37
  return resolveDocumentModelContext() !== void 0;
30
38
  }
39
+
40
+ // plugins/plugin-webmcp/src/webmcp.bridge.ts
41
+ var MAX_WEBMCP_NAME_LENGTH = 128;
42
+ var INVALID_NAME_CHARACTERS = /[^A-Za-z0-9_.-]/g;
43
+ var ANONYMOUS_SUBJECT = "anon:webmcp";
31
44
  function toWebMcpToolName(name) {
32
45
  const cleaned = name.replace(INVALID_NAME_CHARACTERS, "_").slice(0, MAX_WEBMCP_NAME_LENGTH);
33
46
  return cleaned.length > 0 ? cleaned : "_";
@@ -47,6 +60,27 @@ function toWebMcpAnnotations(annotations) {
47
60
  if (annotations?.openWorldHint === true) hints.untrustedContentHint = true;
48
61
  return Object.keys(hints).length > 0 ? hints : void 0;
49
62
  }
63
+ function isSameJson(text, value) {
64
+ try {
65
+ return JSON.stringify(JSON.parse(text)) === JSON.stringify(value);
66
+ } catch {
67
+ return false;
68
+ }
69
+ }
70
+ function isTextCopyOf(content, structuredContent) {
71
+ const [block, ...others] = content;
72
+ if (block?.type !== "text" || others.length > 0) return false;
73
+ if (isSameJson(block.text, structuredContent)) return true;
74
+ const keys = Object.keys(structuredContent);
75
+ return keys.length === 1 && keys[0] === "content" && block.text === String(structuredContent["content"] ?? "");
76
+ }
77
+ function toAgentResult(result, mode) {
78
+ const content = result.content ?? [];
79
+ const structuredContent = result.structuredContent;
80
+ if (mode === "content" || structuredContent === void 0) return { content };
81
+ if (mode === "structured" && isTextCopyOf(content, structuredContent)) return structuredContent;
82
+ return { content, structuredContent };
83
+ }
50
84
  function textOf(content) {
51
85
  return (content ?? []).map((block) => block.type === "text" ? block.text : "").filter((text) => text.length > 0).join("\n");
52
86
  }
@@ -103,6 +137,7 @@ var WebMcpBridge = class {
103
137
  return;
104
138
  }
105
139
  this.modelContext = modelContext;
140
+ syncListenerOf(modelContext)?.started();
106
141
  this.unsubscribe = this.scope.tools.subscribe({ immediate: true }, () => this.refresh());
107
142
  }
108
143
  /**
@@ -131,15 +166,19 @@ var WebMcpBridge = class {
131
166
  /** Sync until no change is pending. Starts a microtask later, so a burst of changes syncs once. */
132
167
  async drain() {
133
168
  await Promise.resolve();
169
+ let failure;
134
170
  try {
135
171
  while (this.pending && !this.stopped) {
136
172
  this.pending = false;
137
173
  try {
138
174
  await this.sync();
175
+ failure = void 0;
139
176
  } catch (error) {
177
+ failure = error;
140
178
  this.logger.warn(`WebMCP sync failed: ${toAgentError(error).message}`);
141
179
  }
142
180
  }
181
+ if (!this.stopped) syncListenerOf(this.modelContext)?.synced(failure);
143
182
  } finally {
144
183
  this.run = void 0;
145
184
  }
@@ -230,10 +269,7 @@ var WebMcpBridge = class {
230
269
  throw toAgentError(error);
231
270
  }
232
271
  if (result.isError) throw new Error(textOf(result.content) || `Tool "${mcpName}" failed`);
233
- return {
234
- content: result.content ?? [],
235
- ...result.structuredContent !== void 0 && { structuredContent: result.structuredContent }
236
- };
272
+ return toAgentResult(result, this.options.result);
237
273
  }
238
274
  /**
239
275
  * Run a flow as its own request. A browser build without AsyncContext runs requests one at a
@@ -284,7 +320,15 @@ var webMcpPluginOptionsSchema = z.object({
284
320
  /** Auth context the tools are listed and called with. Defaults to an anonymous `webmcp` caller. */
285
321
  authContext: z.custom((value) => typeof value === "function" || typeof value === "object" && value !== null).optional(),
286
322
  /** The `ModelContext` to register with. Defaults to `document.modelContext`; pass a polyfill or test double. */
287
- modelContext: z.custom(isModelContext, { message: "modelContext must have a registerTool function" }).optional()
323
+ modelContext: z.custom(isModelContext, { message: "modelContext must have a registerTool function" }).optional(),
324
+ /**
325
+ * What a call resolves to. The agent reads the whole value as text, so by default it gets each result once:
326
+ * - `'structured'`: the tool's `structuredContent` alone when its content only repeats it (one text block with
327
+ * the same JSON, as the server writes it); otherwise `{ content }`, plus `structuredContent` when there is one.
328
+ * - `'content'`: `{ content }`.
329
+ * - `'both'`: `{ content, structuredContent }`, as an MCP client gets them.
330
+ */
331
+ result: z.enum(["structured", "content", "both"]).default("structured")
288
332
  });
289
333
 
290
334
  // plugins/plugin-webmcp/src/webmcp.plugin.ts
@@ -321,11 +365,168 @@ WebMcpPlugin = __decorateClass([
321
365
  description: "Exposes the server's tools to in-browser agents through WebMCP (document.modelContext)"
322
366
  })
323
367
  ], WebMcpPlugin);
368
+
369
+ // plugins/plugin-webmcp/src/webmcp.lazy.ts
370
+ var NO_PLUGIN_MESSAGE = "The server has no WebMcpPlugin on the modelContext it was given: pass it to WebMcpPlugin.init({ modelContext })";
371
+ function createPluginSync() {
372
+ let started = false;
373
+ let resolveSynced;
374
+ let rejectSynced;
375
+ const synced = new Promise((resolve, reject) => {
376
+ resolveSynced = resolve;
377
+ rejectSynced = reject;
378
+ });
379
+ synced.catch(() => void 0);
380
+ return {
381
+ listener: {
382
+ started: () => {
383
+ started = true;
384
+ },
385
+ synced: (error) => error === void 0 ? resolveSynced?.() : rejectSynced?.(error)
386
+ },
387
+ async whenSynced() {
388
+ await new Promise((resolve) => setTimeout(resolve, 0));
389
+ if (!started) throw new Error(NO_PLUGIN_MESSAGE);
390
+ await synced;
391
+ }
392
+ };
393
+ }
394
+ function fingerprintOf(descriptor, exposedTo) {
395
+ return JSON.stringify([descriptor, exposedTo ?? []]);
396
+ }
397
+ async function listWebMcpTools(createServer) {
398
+ const descriptors = /* @__PURE__ */ new Map();
399
+ const pluginSync = createPluginSync();
400
+ const modelContext = withSyncListener(
401
+ {
402
+ async registerTool({ execute: _execute, ...descriptor }, options) {
403
+ descriptors.set(descriptor.name, descriptor);
404
+ options?.signal?.addEventListener("abort", () => descriptors.delete(descriptor.name), { once: true });
405
+ }
406
+ },
407
+ pluginSync.listener
408
+ );
409
+ const server = await createServer(modelContext);
410
+ try {
411
+ await pluginSync.whenSynced();
412
+ return [...descriptors.values()];
413
+ } finally {
414
+ await server.dispose();
415
+ }
416
+ }
417
+ var LazyWebMcpTools = class {
418
+ constructor(modelContext, loadServer) {
419
+ this.modelContext = modelContext;
420
+ this.loadServer = loadServer;
421
+ }
422
+ modelContext;
423
+ loadServer;
424
+ placeholders = /* @__PURE__ */ new Map();
425
+ /** The plugin's `execute` of every tool it registered, by name. */
426
+ executors = /* @__PURE__ */ new Map();
427
+ loading;
428
+ async register(tools, exposedTo) {
429
+ await Promise.all(tools.map((tool) => this.registerPlaceholder(tool, exposedTo)));
430
+ }
431
+ async registerPlaceholder(tool, exposedTo) {
432
+ const placeholder = {
433
+ fingerprint: fingerprintOf(tool, exposedTo),
434
+ controller: new AbortController(),
435
+ adopted: false
436
+ };
437
+ this.placeholders.set(tool.name, placeholder);
438
+ try {
439
+ await this.modelContext.registerTool(
440
+ { ...tool, execute: (input, options) => this.call(tool.name, input, options) },
441
+ { signal: placeholder.controller.signal, ...exposedTo && { exposedTo } }
442
+ );
443
+ } catch {
444
+ this.release(tool.name, placeholder);
445
+ }
446
+ }
447
+ async call(name, input, options) {
448
+ await this.load();
449
+ const execute = this.executors.get(name);
450
+ if (!execute) throw new Error(`Tool "${name}" not found`);
451
+ return execute(input, options);
452
+ }
453
+ /** Load the server once; a failed load is tried again on the next call. */
454
+ load() {
455
+ this.loading ??= this.loadThroughPlugin().catch((error) => {
456
+ this.loading = void 0;
457
+ throw error;
458
+ });
459
+ return this.loading;
460
+ }
461
+ async loadThroughPlugin() {
462
+ const pluginSync = createPluginSync();
463
+ const attempt = { failed: false };
464
+ const handoff = withSyncListener(
465
+ { registerTool: (tool, options) => this.adopt(tool, options, attempt) },
466
+ pluginSync.listener
467
+ );
468
+ const server = await this.loadServer(handoff);
469
+ try {
470
+ await pluginSync.whenSynced();
471
+ } catch (error) {
472
+ await this.abandon(attempt, server);
473
+ throw error;
474
+ }
475
+ for (const [name, placeholder] of this.placeholders) {
476
+ if (!placeholder.adopted) this.release(name, placeholder);
477
+ }
478
+ }
479
+ /** Forget what a failed load's plugin registered, keep the listed tools for the next try, and dispose its server. */
480
+ async abandon(attempt, server) {
481
+ attempt.failed = true;
482
+ this.executors.clear();
483
+ for (const placeholder of this.placeholders.values()) placeholder.adopted = false;
484
+ const disposable = server;
485
+ if (typeof disposable?.dispose === "function") await Promise.resolve(disposable.dispose()).catch(() => void 0);
486
+ }
487
+ /**
488
+ * A registration from the plugin: an unchanged listed tool keeps its registration and runs the plugin's
489
+ * `execute`; anything else replaces the listed tool of that name, or is new, and goes to the page's context.
490
+ */
491
+ async adopt(tool, options, attempt) {
492
+ if (attempt.failed || options?.signal?.aborted) return;
493
+ const { execute, ...descriptor } = tool;
494
+ const placeholder = this.placeholders.get(tool.name);
495
+ const kept = placeholder && !placeholder.adopted && placeholder.fingerprint === fingerprintOf(descriptor, options?.exposedTo) ? placeholder : void 0;
496
+ this.executors.set(tool.name, execute);
497
+ options?.signal?.addEventListener(
498
+ "abort",
499
+ () => {
500
+ if (attempt.failed) return;
501
+ if (this.executors.get(tool.name) === execute) this.executors.delete(tool.name);
502
+ if (kept) this.release(tool.name, kept);
503
+ },
504
+ { once: true }
505
+ );
506
+ if (kept) {
507
+ kept.adopted = true;
508
+ return;
509
+ }
510
+ if (placeholder) this.release(tool.name, placeholder);
511
+ await this.modelContext.registerTool(tool, options);
512
+ }
513
+ release(name, placeholder) {
514
+ placeholder.controller.abort();
515
+ if (this.placeholders.get(name) === placeholder) this.placeholders.delete(name);
516
+ }
517
+ };
518
+ async function registerWebMcpTools(modelContext, tools, loadServer, options = {}) {
519
+ if (!modelContext) return;
520
+ await new LazyWebMcpTools(modelContext, loadServer).register(tools, options.exposedTo);
521
+ }
324
522
  export {
325
523
  WebMcpBridge,
326
524
  WebMcpPlugin,
327
525
  WebMcpPlugin as default,
328
526
  isWebMcpSupported,
527
+ listWebMcpTools,
528
+ registerWebMcpTools,
529
+ resolveDocumentModelContext,
329
530
  toWebMcpToolName,
330
531
  webMcpPluginOptionsSchema
331
532
  };
package/esm/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@frontmcp/plugin-webmcp",
3
- "version": "1.9.4",
3
+ "version": "1.9.5",
4
4
  "description": "WebMCP plugin for FrontMCP - expose an in-browser FrontMCP server's tools to browser agents through document.modelContext",
5
5
  "author": "AgentFront <info@agentfront.dev>",
6
6
  "license": "Apache-2.0",
@@ -25,7 +25,7 @@
25
25
  "bugs": {
26
26
  "url": "https://github.com/agentfront/frontmcp/issues"
27
27
  },
28
- "homepage": "https://github.com/agentfront/frontmcp/blob/main/plugins/plugin-webmcp/README.md",
28
+ "homepage": "https://frontmcp.dev/reference/plugins/webmcp",
29
29
  "publishConfig": {
30
30
  "access": "public",
31
31
  "registry": "https://registry.npmjs.org/"
@@ -46,14 +46,24 @@
46
46
  "types": "../index.d.ts",
47
47
  "default": "./index.mjs"
48
48
  }
49
+ },
50
+ "./register": {
51
+ "require": {
52
+ "types": "../register.d.ts",
53
+ "default": "../register.js"
54
+ },
55
+ "import": {
56
+ "types": "../register.d.ts",
57
+ "default": "./register.mjs"
58
+ }
49
59
  }
50
60
  },
51
61
  "dependencies": {
52
- "@frontmcp/sdk": "1.9.4",
53
- "@frontmcp/utils": "1.9.4"
62
+ "@frontmcp/sdk": "1.9.5",
63
+ "@frontmcp/utils": "1.9.5"
54
64
  },
55
65
  "peerDependencies": {
56
- "@frontmcp/lazy-zod": "1.9.4"
66
+ "@frontmcp/lazy-zod": "1.9.5"
57
67
  },
58
68
  "devDependencies": {
59
69
  "reflect-metadata": "^0.2.2"
@@ -0,0 +1,153 @@
1
+ // plugins/plugin-webmcp/src/webmcp.model-context.ts
2
+ function resolveDocumentModelContext() {
3
+ const modelContext = globalThis.document?.modelContext;
4
+ return typeof modelContext === "object" && modelContext !== null && typeof modelContext.registerTool === "function" ? modelContext : void 0;
5
+ }
6
+ function isWebMcpSupported() {
7
+ return resolveDocumentModelContext() !== void 0;
8
+ }
9
+
10
+ // plugins/plugin-webmcp/src/webmcp.handoff.ts
11
+ var SYNC_LISTENER = /* @__PURE__ */ Symbol.for("@frontmcp/plugin-webmcp/sync-listener");
12
+ function withSyncListener(modelContext, listener) {
13
+ return Object.assign(modelContext, { [SYNC_LISTENER]: listener });
14
+ }
15
+
16
+ // plugins/plugin-webmcp/src/webmcp.lazy.ts
17
+ var NO_PLUGIN_MESSAGE = "The server has no WebMcpPlugin on the modelContext it was given: pass it to WebMcpPlugin.init({ modelContext })";
18
+ function createPluginSync() {
19
+ let started = false;
20
+ let resolveSynced;
21
+ let rejectSynced;
22
+ const synced = new Promise((resolve, reject) => {
23
+ resolveSynced = resolve;
24
+ rejectSynced = reject;
25
+ });
26
+ synced.catch(() => void 0);
27
+ return {
28
+ listener: {
29
+ started: () => {
30
+ started = true;
31
+ },
32
+ synced: (error) => error === void 0 ? resolveSynced?.() : rejectSynced?.(error)
33
+ },
34
+ async whenSynced() {
35
+ await new Promise((resolve) => setTimeout(resolve, 0));
36
+ if (!started) throw new Error(NO_PLUGIN_MESSAGE);
37
+ await synced;
38
+ }
39
+ };
40
+ }
41
+ function fingerprintOf(descriptor, exposedTo) {
42
+ return JSON.stringify([descriptor, exposedTo ?? []]);
43
+ }
44
+ var LazyWebMcpTools = class {
45
+ constructor(modelContext, loadServer) {
46
+ this.modelContext = modelContext;
47
+ this.loadServer = loadServer;
48
+ }
49
+ modelContext;
50
+ loadServer;
51
+ placeholders = /* @__PURE__ */ new Map();
52
+ /** The plugin's `execute` of every tool it registered, by name. */
53
+ executors = /* @__PURE__ */ new Map();
54
+ loading;
55
+ async register(tools, exposedTo) {
56
+ await Promise.all(tools.map((tool) => this.registerPlaceholder(tool, exposedTo)));
57
+ }
58
+ async registerPlaceholder(tool, exposedTo) {
59
+ const placeholder = {
60
+ fingerprint: fingerprintOf(tool, exposedTo),
61
+ controller: new AbortController(),
62
+ adopted: false
63
+ };
64
+ this.placeholders.set(tool.name, placeholder);
65
+ try {
66
+ await this.modelContext.registerTool(
67
+ { ...tool, execute: (input, options) => this.call(tool.name, input, options) },
68
+ { signal: placeholder.controller.signal, ...exposedTo && { exposedTo } }
69
+ );
70
+ } catch {
71
+ this.release(tool.name, placeholder);
72
+ }
73
+ }
74
+ async call(name, input, options) {
75
+ await this.load();
76
+ const execute = this.executors.get(name);
77
+ if (!execute) throw new Error(`Tool "${name}" not found`);
78
+ return execute(input, options);
79
+ }
80
+ /** Load the server once; a failed load is tried again on the next call. */
81
+ load() {
82
+ this.loading ??= this.loadThroughPlugin().catch((error) => {
83
+ this.loading = void 0;
84
+ throw error;
85
+ });
86
+ return this.loading;
87
+ }
88
+ async loadThroughPlugin() {
89
+ const pluginSync = createPluginSync();
90
+ const attempt = { failed: false };
91
+ const handoff = withSyncListener(
92
+ { registerTool: (tool, options) => this.adopt(tool, options, attempt) },
93
+ pluginSync.listener
94
+ );
95
+ const server = await this.loadServer(handoff);
96
+ try {
97
+ await pluginSync.whenSynced();
98
+ } catch (error) {
99
+ await this.abandon(attempt, server);
100
+ throw error;
101
+ }
102
+ for (const [name, placeholder] of this.placeholders) {
103
+ if (!placeholder.adopted) this.release(name, placeholder);
104
+ }
105
+ }
106
+ /** Forget what a failed load's plugin registered, keep the listed tools for the next try, and dispose its server. */
107
+ async abandon(attempt, server) {
108
+ attempt.failed = true;
109
+ this.executors.clear();
110
+ for (const placeholder of this.placeholders.values()) placeholder.adopted = false;
111
+ const disposable = server;
112
+ if (typeof disposable?.dispose === "function") await Promise.resolve(disposable.dispose()).catch(() => void 0);
113
+ }
114
+ /**
115
+ * A registration from the plugin: an unchanged listed tool keeps its registration and runs the plugin's
116
+ * `execute`; anything else replaces the listed tool of that name, or is new, and goes to the page's context.
117
+ */
118
+ async adopt(tool, options, attempt) {
119
+ if (attempt.failed || options?.signal?.aborted) return;
120
+ const { execute, ...descriptor } = tool;
121
+ const placeholder = this.placeholders.get(tool.name);
122
+ const kept = placeholder && !placeholder.adopted && placeholder.fingerprint === fingerprintOf(descriptor, options?.exposedTo) ? placeholder : void 0;
123
+ this.executors.set(tool.name, execute);
124
+ options?.signal?.addEventListener(
125
+ "abort",
126
+ () => {
127
+ if (attempt.failed) return;
128
+ if (this.executors.get(tool.name) === execute) this.executors.delete(tool.name);
129
+ if (kept) this.release(tool.name, kept);
130
+ },
131
+ { once: true }
132
+ );
133
+ if (kept) {
134
+ kept.adopted = true;
135
+ return;
136
+ }
137
+ if (placeholder) this.release(tool.name, placeholder);
138
+ await this.modelContext.registerTool(tool, options);
139
+ }
140
+ release(name, placeholder) {
141
+ placeholder.controller.abort();
142
+ if (this.placeholders.get(name) === placeholder) this.placeholders.delete(name);
143
+ }
144
+ };
145
+ async function registerWebMcpTools(modelContext, tools, loadServer, options = {}) {
146
+ if (!modelContext) return;
147
+ await new LazyWebMcpTools(modelContext, loadServer).register(tools, options.exposedTo);
148
+ }
149
+ export {
150
+ isWebMcpSupported,
151
+ registerWebMcpTools,
152
+ resolveDocumentModelContext
153
+ };
package/index.d.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  export { default, default as WebMcpPlugin } from './webmcp.plugin';
2
- export { WebMcpBridge, isWebMcpSupported, toWebMcpToolName, type WebMcpToolResult } from './webmcp.bridge';
3
- export { webMcpPluginOptionsSchema, type WebMcpAuthContext, type WebMcpListedTool, type WebMcpPluginOptions, type WebMcpPluginOptionsInput, } from './webmcp.options';
2
+ export { WebMcpBridge, toWebMcpToolName, type WebMcpContentResult, type WebMcpToolResult } from './webmcp.bridge';
3
+ export { isWebMcpSupported, resolveDocumentModelContext } from './webmcp.model-context';
4
+ export { listWebMcpTools, registerWebMcpTools, type RegisterWebMcpToolsOptions, type WebMcpServerFactory, type WebMcpToolDescriptor, } from './webmcp.lazy';
5
+ export { webMcpPluginOptionsSchema, type WebMcpAuthContext, type WebMcpListedTool, type WebMcpPluginOptions, type WebMcpPluginOptionsInput, type WebMcpResultMode, } from './webmcp.options';
4
6
  export type { ModelContext, ModelContextRegisterToolOptions, ModelContextRegisteredTool, ModelContextTool, ModelContextToolAnnotations, ModelContextToolExecuteOptions, } from './webmcp.types';
5
7
  //# sourceMappingURL=index.d.ts.map
package/index.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,iBAAiB,CAAC;AACnE,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,KAAK,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAC3G,OAAO,EACL,yBAAyB,EACzB,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,EACrB,KAAK,mBAAmB,EACxB,KAAK,wBAAwB,GAC9B,MAAM,kBAAkB,CAAC;AAC1B,YAAY,EACV,YAAY,EACZ,+BAA+B,EAC/B,0BAA0B,EAC1B,gBAAgB,EAChB,2BAA2B,EAC3B,8BAA8B,GAC/B,MAAM,gBAAgB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,iBAAiB,CAAC;AACnE,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,KAAK,mBAAmB,EAAE,KAAK,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAClH,OAAO,EAAE,iBAAiB,EAAE,2BAA2B,EAAE,MAAM,wBAAwB,CAAC;AACxF,OAAO,EACL,eAAe,EACf,mBAAmB,EACnB,KAAK,0BAA0B,EAC/B,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,GAC1B,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,yBAAyB,EACzB,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,EACrB,KAAK,mBAAmB,EACxB,KAAK,wBAAwB,EAC7B,KAAK,gBAAgB,GACtB,MAAM,kBAAkB,CAAC;AAC1B,YAAY,EACV,YAAY,EACZ,+BAA+B,EAC/B,0BAA0B,EAC1B,gBAAgB,EAChB,2BAA2B,EAC3B,8BAA8B,GAC/B,MAAM,gBAAgB,CAAC"}
package/index.js CHANGED
@@ -32,6 +32,9 @@ __export(index_exports, {
32
32
  WebMcpPlugin: () => WebMcpPlugin,
33
33
  default: () => WebMcpPlugin,
34
34
  isWebMcpSupported: () => isWebMcpSupported,
35
+ listWebMcpTools: () => listWebMcpTools,
36
+ registerWebMcpTools: () => registerWebMcpTools,
37
+ resolveDocumentModelContext: () => resolveDocumentModelContext,
35
38
  toWebMcpToolName: () => toWebMcpToolName,
36
39
  webMcpPluginOptionsSchema: () => webMcpPluginOptionsSchema
37
40
  });
@@ -43,9 +46,17 @@ var import_sdk2 = require("@frontmcp/sdk");
43
46
  // plugins/plugin-webmcp/src/webmcp.bridge.ts
44
47
  var import_sdk = require("@frontmcp/sdk");
45
48
  var import_utils = require("@frontmcp/utils");
46
- var MAX_WEBMCP_NAME_LENGTH = 128;
47
- var INVALID_NAME_CHARACTERS = /[^A-Za-z0-9_.-]/g;
48
- var ANONYMOUS_SUBJECT = "anon:webmcp";
49
+
50
+ // plugins/plugin-webmcp/src/webmcp.handoff.ts
51
+ var SYNC_LISTENER = /* @__PURE__ */ Symbol.for("@frontmcp/plugin-webmcp/sync-listener");
52
+ function withSyncListener(modelContext, listener) {
53
+ return Object.assign(modelContext, { [SYNC_LISTENER]: listener });
54
+ }
55
+ function syncListenerOf(modelContext) {
56
+ return modelContext?.[SYNC_LISTENER];
57
+ }
58
+
59
+ // plugins/plugin-webmcp/src/webmcp.model-context.ts
49
60
  function resolveDocumentModelContext() {
50
61
  const modelContext = globalThis.document?.modelContext;
51
62
  return typeof modelContext === "object" && modelContext !== null && typeof modelContext.registerTool === "function" ? modelContext : void 0;
@@ -53,6 +64,11 @@ function resolveDocumentModelContext() {
53
64
  function isWebMcpSupported() {
54
65
  return resolveDocumentModelContext() !== void 0;
55
66
  }
67
+
68
+ // plugins/plugin-webmcp/src/webmcp.bridge.ts
69
+ var MAX_WEBMCP_NAME_LENGTH = 128;
70
+ var INVALID_NAME_CHARACTERS = /[^A-Za-z0-9_.-]/g;
71
+ var ANONYMOUS_SUBJECT = "anon:webmcp";
56
72
  function toWebMcpToolName(name) {
57
73
  const cleaned = name.replace(INVALID_NAME_CHARACTERS, "_").slice(0, MAX_WEBMCP_NAME_LENGTH);
58
74
  return cleaned.length > 0 ? cleaned : "_";
@@ -72,6 +88,27 @@ function toWebMcpAnnotations(annotations) {
72
88
  if (annotations?.openWorldHint === true) hints.untrustedContentHint = true;
73
89
  return Object.keys(hints).length > 0 ? hints : void 0;
74
90
  }
91
+ function isSameJson(text, value) {
92
+ try {
93
+ return JSON.stringify(JSON.parse(text)) === JSON.stringify(value);
94
+ } catch {
95
+ return false;
96
+ }
97
+ }
98
+ function isTextCopyOf(content, structuredContent) {
99
+ const [block, ...others] = content;
100
+ if (block?.type !== "text" || others.length > 0) return false;
101
+ if (isSameJson(block.text, structuredContent)) return true;
102
+ const keys = Object.keys(structuredContent);
103
+ return keys.length === 1 && keys[0] === "content" && block.text === String(structuredContent["content"] ?? "");
104
+ }
105
+ function toAgentResult(result, mode) {
106
+ const content = result.content ?? [];
107
+ const structuredContent = result.structuredContent;
108
+ if (mode === "content" || structuredContent === void 0) return { content };
109
+ if (mode === "structured" && isTextCopyOf(content, structuredContent)) return structuredContent;
110
+ return { content, structuredContent };
111
+ }
75
112
  function textOf(content) {
76
113
  return (content ?? []).map((block) => block.type === "text" ? block.text : "").filter((text) => text.length > 0).join("\n");
77
114
  }
@@ -128,6 +165,7 @@ var WebMcpBridge = class {
128
165
  return;
129
166
  }
130
167
  this.modelContext = modelContext;
168
+ syncListenerOf(modelContext)?.started();
131
169
  this.unsubscribe = this.scope.tools.subscribe({ immediate: true }, () => this.refresh());
132
170
  }
133
171
  /**
@@ -156,15 +194,19 @@ var WebMcpBridge = class {
156
194
  /** Sync until no change is pending. Starts a microtask later, so a burst of changes syncs once. */
157
195
  async drain() {
158
196
  await Promise.resolve();
197
+ let failure;
159
198
  try {
160
199
  while (this.pending && !this.stopped) {
161
200
  this.pending = false;
162
201
  try {
163
202
  await this.sync();
203
+ failure = void 0;
164
204
  } catch (error) {
205
+ failure = error;
165
206
  this.logger.warn(`WebMCP sync failed: ${toAgentError(error).message}`);
166
207
  }
167
208
  }
209
+ if (!this.stopped) syncListenerOf(this.modelContext)?.synced(failure);
168
210
  } finally {
169
211
  this.run = void 0;
170
212
  }
@@ -255,10 +297,7 @@ var WebMcpBridge = class {
255
297
  throw toAgentError(error);
256
298
  }
257
299
  if (result.isError) throw new Error(textOf(result.content) || `Tool "${mcpName}" failed`);
258
- return {
259
- content: result.content ?? [],
260
- ...result.structuredContent !== void 0 && { structuredContent: result.structuredContent }
261
- };
300
+ return toAgentResult(result, this.options.result);
262
301
  }
263
302
  /**
264
303
  * Run a flow as its own request. A browser build without AsyncContext runs requests one at a
@@ -309,7 +348,15 @@ var webMcpPluginOptionsSchema = import_lazy_zod.z.object({
309
348
  /** Auth context the tools are listed and called with. Defaults to an anonymous `webmcp` caller. */
310
349
  authContext: import_lazy_zod.z.custom((value) => typeof value === "function" || typeof value === "object" && value !== null).optional(),
311
350
  /** The `ModelContext` to register with. Defaults to `document.modelContext`; pass a polyfill or test double. */
312
- modelContext: import_lazy_zod.z.custom(isModelContext, { message: "modelContext must have a registerTool function" }).optional()
351
+ modelContext: import_lazy_zod.z.custom(isModelContext, { message: "modelContext must have a registerTool function" }).optional(),
352
+ /**
353
+ * What a call resolves to. The agent reads the whole value as text, so by default it gets each result once:
354
+ * - `'structured'`: the tool's `structuredContent` alone when its content only repeats it (one text block with
355
+ * the same JSON, as the server writes it); otherwise `{ content }`, plus `structuredContent` when there is one.
356
+ * - `'content'`: `{ content }`.
357
+ * - `'both'`: `{ content, structuredContent }`, as an MCP client gets them.
358
+ */
359
+ result: import_lazy_zod.z.enum(["structured", "content", "both"]).default("structured")
313
360
  });
314
361
 
315
362
  // plugins/plugin-webmcp/src/webmcp.plugin.ts
@@ -346,3 +393,157 @@ WebMcpPlugin = __decorateClass([
346
393
  description: "Exposes the server's tools to in-browser agents through WebMCP (document.modelContext)"
347
394
  })
348
395
  ], WebMcpPlugin);
396
+
397
+ // plugins/plugin-webmcp/src/webmcp.lazy.ts
398
+ var NO_PLUGIN_MESSAGE = "The server has no WebMcpPlugin on the modelContext it was given: pass it to WebMcpPlugin.init({ modelContext })";
399
+ function createPluginSync() {
400
+ let started = false;
401
+ let resolveSynced;
402
+ let rejectSynced;
403
+ const synced = new Promise((resolve, reject) => {
404
+ resolveSynced = resolve;
405
+ rejectSynced = reject;
406
+ });
407
+ synced.catch(() => void 0);
408
+ return {
409
+ listener: {
410
+ started: () => {
411
+ started = true;
412
+ },
413
+ synced: (error) => error === void 0 ? resolveSynced?.() : rejectSynced?.(error)
414
+ },
415
+ async whenSynced() {
416
+ await new Promise((resolve) => setTimeout(resolve, 0));
417
+ if (!started) throw new Error(NO_PLUGIN_MESSAGE);
418
+ await synced;
419
+ }
420
+ };
421
+ }
422
+ function fingerprintOf(descriptor, exposedTo) {
423
+ return JSON.stringify([descriptor, exposedTo ?? []]);
424
+ }
425
+ async function listWebMcpTools(createServer) {
426
+ const descriptors = /* @__PURE__ */ new Map();
427
+ const pluginSync = createPluginSync();
428
+ const modelContext = withSyncListener(
429
+ {
430
+ async registerTool({ execute: _execute, ...descriptor }, options) {
431
+ descriptors.set(descriptor.name, descriptor);
432
+ options?.signal?.addEventListener("abort", () => descriptors.delete(descriptor.name), { once: true });
433
+ }
434
+ },
435
+ pluginSync.listener
436
+ );
437
+ const server = await createServer(modelContext);
438
+ try {
439
+ await pluginSync.whenSynced();
440
+ return [...descriptors.values()];
441
+ } finally {
442
+ await server.dispose();
443
+ }
444
+ }
445
+ var LazyWebMcpTools = class {
446
+ constructor(modelContext, loadServer) {
447
+ this.modelContext = modelContext;
448
+ this.loadServer = loadServer;
449
+ }
450
+ modelContext;
451
+ loadServer;
452
+ placeholders = /* @__PURE__ */ new Map();
453
+ /** The plugin's `execute` of every tool it registered, by name. */
454
+ executors = /* @__PURE__ */ new Map();
455
+ loading;
456
+ async register(tools, exposedTo) {
457
+ await Promise.all(tools.map((tool) => this.registerPlaceholder(tool, exposedTo)));
458
+ }
459
+ async registerPlaceholder(tool, exposedTo) {
460
+ const placeholder = {
461
+ fingerprint: fingerprintOf(tool, exposedTo),
462
+ controller: new AbortController(),
463
+ adopted: false
464
+ };
465
+ this.placeholders.set(tool.name, placeholder);
466
+ try {
467
+ await this.modelContext.registerTool(
468
+ { ...tool, execute: (input, options) => this.call(tool.name, input, options) },
469
+ { signal: placeholder.controller.signal, ...exposedTo && { exposedTo } }
470
+ );
471
+ } catch {
472
+ this.release(tool.name, placeholder);
473
+ }
474
+ }
475
+ async call(name, input, options) {
476
+ await this.load();
477
+ const execute = this.executors.get(name);
478
+ if (!execute) throw new Error(`Tool "${name}" not found`);
479
+ return execute(input, options);
480
+ }
481
+ /** Load the server once; a failed load is tried again on the next call. */
482
+ load() {
483
+ this.loading ??= this.loadThroughPlugin().catch((error) => {
484
+ this.loading = void 0;
485
+ throw error;
486
+ });
487
+ return this.loading;
488
+ }
489
+ async loadThroughPlugin() {
490
+ const pluginSync = createPluginSync();
491
+ const attempt = { failed: false };
492
+ const handoff = withSyncListener(
493
+ { registerTool: (tool, options) => this.adopt(tool, options, attempt) },
494
+ pluginSync.listener
495
+ );
496
+ const server = await this.loadServer(handoff);
497
+ try {
498
+ await pluginSync.whenSynced();
499
+ } catch (error) {
500
+ await this.abandon(attempt, server);
501
+ throw error;
502
+ }
503
+ for (const [name, placeholder] of this.placeholders) {
504
+ if (!placeholder.adopted) this.release(name, placeholder);
505
+ }
506
+ }
507
+ /** Forget what a failed load's plugin registered, keep the listed tools for the next try, and dispose its server. */
508
+ async abandon(attempt, server) {
509
+ attempt.failed = true;
510
+ this.executors.clear();
511
+ for (const placeholder of this.placeholders.values()) placeholder.adopted = false;
512
+ const disposable = server;
513
+ if (typeof disposable?.dispose === "function") await Promise.resolve(disposable.dispose()).catch(() => void 0);
514
+ }
515
+ /**
516
+ * A registration from the plugin: an unchanged listed tool keeps its registration and runs the plugin's
517
+ * `execute`; anything else replaces the listed tool of that name, or is new, and goes to the page's context.
518
+ */
519
+ async adopt(tool, options, attempt) {
520
+ if (attempt.failed || options?.signal?.aborted) return;
521
+ const { execute, ...descriptor } = tool;
522
+ const placeholder = this.placeholders.get(tool.name);
523
+ const kept = placeholder && !placeholder.adopted && placeholder.fingerprint === fingerprintOf(descriptor, options?.exposedTo) ? placeholder : void 0;
524
+ this.executors.set(tool.name, execute);
525
+ options?.signal?.addEventListener(
526
+ "abort",
527
+ () => {
528
+ if (attempt.failed) return;
529
+ if (this.executors.get(tool.name) === execute) this.executors.delete(tool.name);
530
+ if (kept) this.release(tool.name, kept);
531
+ },
532
+ { once: true }
533
+ );
534
+ if (kept) {
535
+ kept.adopted = true;
536
+ return;
537
+ }
538
+ if (placeholder) this.release(tool.name, placeholder);
539
+ await this.modelContext.registerTool(tool, options);
540
+ }
541
+ release(name, placeholder) {
542
+ placeholder.controller.abort();
543
+ if (this.placeholders.get(name) === placeholder) this.placeholders.delete(name);
544
+ }
545
+ };
546
+ async function registerWebMcpTools(modelContext, tools, loadServer, options = {}) {
547
+ if (!modelContext) return;
548
+ await new LazyWebMcpTools(modelContext, loadServer).register(tools, options.exposedTo);
549
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@frontmcp/plugin-webmcp",
3
- "version": "1.9.4",
3
+ "version": "1.9.5",
4
4
  "description": "WebMCP plugin for FrontMCP - expose an in-browser FrontMCP server's tools to browser agents through document.modelContext",
5
5
  "author": "AgentFront <info@agentfront.dev>",
6
6
  "license": "Apache-2.0",
@@ -25,7 +25,7 @@
25
25
  "bugs": {
26
26
  "url": "https://github.com/agentfront/frontmcp/issues"
27
27
  },
28
- "homepage": "https://github.com/agentfront/frontmcp/blob/main/plugins/plugin-webmcp/README.md",
28
+ "homepage": "https://frontmcp.dev/reference/plugins/webmcp",
29
29
  "publishConfig": {
30
30
  "access": "public",
31
31
  "registry": "https://registry.npmjs.org/"
@@ -46,14 +46,24 @@
46
46
  "types": "./index.d.ts",
47
47
  "default": "./esm/index.mjs"
48
48
  }
49
+ },
50
+ "./register": {
51
+ "require": {
52
+ "types": "./register.d.ts",
53
+ "default": "./register.js"
54
+ },
55
+ "import": {
56
+ "types": "./register.d.ts",
57
+ "default": "./esm/register.mjs"
58
+ }
49
59
  }
50
60
  },
51
61
  "dependencies": {
52
- "@frontmcp/sdk": "1.9.4",
53
- "@frontmcp/utils": "1.9.4"
62
+ "@frontmcp/sdk": "1.9.5",
63
+ "@frontmcp/utils": "1.9.5"
54
64
  },
55
65
  "peerDependencies": {
56
- "@frontmcp/lazy-zod": "1.9.4"
66
+ "@frontmcp/lazy-zod": "1.9.5"
57
67
  },
58
68
  "devDependencies": {
59
69
  "reflect-metadata": "^0.2.2"
package/register.d.ts ADDED
@@ -0,0 +1,4 @@
1
+ export { isWebMcpSupported, resolveDocumentModelContext } from './webmcp.model-context';
2
+ export { registerWebMcpTools, type RegisterWebMcpToolsOptions, type WebMcpServerFactory, type WebMcpToolDescriptor, } from './webmcp.lazy';
3
+ export type { ModelContext, ModelContextRegisterToolOptions, ModelContextTool, ModelContextToolAnnotations, ModelContextToolExecuteOptions, } from './webmcp.types';
4
+ //# sourceMappingURL=register.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"register.d.ts","sourceRoot":"","sources":["../src/register.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,2BAA2B,EAAE,MAAM,wBAAwB,CAAC;AACxF,OAAO,EACL,mBAAmB,EACnB,KAAK,0BAA0B,EAC/B,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,GAC1B,MAAM,eAAe,CAAC;AACvB,YAAY,EACV,YAAY,EACZ,+BAA+B,EAC/B,gBAAgB,EAChB,2BAA2B,EAC3B,8BAA8B,GAC/B,MAAM,gBAAgB,CAAC"}
package/register.js ADDED
@@ -0,0 +1,176 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // plugins/plugin-webmcp/src/register.ts
21
+ var register_exports = {};
22
+ __export(register_exports, {
23
+ isWebMcpSupported: () => isWebMcpSupported,
24
+ registerWebMcpTools: () => registerWebMcpTools,
25
+ resolveDocumentModelContext: () => resolveDocumentModelContext
26
+ });
27
+ module.exports = __toCommonJS(register_exports);
28
+
29
+ // plugins/plugin-webmcp/src/webmcp.model-context.ts
30
+ function resolveDocumentModelContext() {
31
+ const modelContext = globalThis.document?.modelContext;
32
+ return typeof modelContext === "object" && modelContext !== null && typeof modelContext.registerTool === "function" ? modelContext : void 0;
33
+ }
34
+ function isWebMcpSupported() {
35
+ return resolveDocumentModelContext() !== void 0;
36
+ }
37
+
38
+ // plugins/plugin-webmcp/src/webmcp.handoff.ts
39
+ var SYNC_LISTENER = /* @__PURE__ */ Symbol.for("@frontmcp/plugin-webmcp/sync-listener");
40
+ function withSyncListener(modelContext, listener) {
41
+ return Object.assign(modelContext, { [SYNC_LISTENER]: listener });
42
+ }
43
+
44
+ // plugins/plugin-webmcp/src/webmcp.lazy.ts
45
+ var NO_PLUGIN_MESSAGE = "The server has no WebMcpPlugin on the modelContext it was given: pass it to WebMcpPlugin.init({ modelContext })";
46
+ function createPluginSync() {
47
+ let started = false;
48
+ let resolveSynced;
49
+ let rejectSynced;
50
+ const synced = new Promise((resolve, reject) => {
51
+ resolveSynced = resolve;
52
+ rejectSynced = reject;
53
+ });
54
+ synced.catch(() => void 0);
55
+ return {
56
+ listener: {
57
+ started: () => {
58
+ started = true;
59
+ },
60
+ synced: (error) => error === void 0 ? resolveSynced?.() : rejectSynced?.(error)
61
+ },
62
+ async whenSynced() {
63
+ await new Promise((resolve) => setTimeout(resolve, 0));
64
+ if (!started) throw new Error(NO_PLUGIN_MESSAGE);
65
+ await synced;
66
+ }
67
+ };
68
+ }
69
+ function fingerprintOf(descriptor, exposedTo) {
70
+ return JSON.stringify([descriptor, exposedTo ?? []]);
71
+ }
72
+ var LazyWebMcpTools = class {
73
+ constructor(modelContext, loadServer) {
74
+ this.modelContext = modelContext;
75
+ this.loadServer = loadServer;
76
+ }
77
+ modelContext;
78
+ loadServer;
79
+ placeholders = /* @__PURE__ */ new Map();
80
+ /** The plugin's `execute` of every tool it registered, by name. */
81
+ executors = /* @__PURE__ */ new Map();
82
+ loading;
83
+ async register(tools, exposedTo) {
84
+ await Promise.all(tools.map((tool) => this.registerPlaceholder(tool, exposedTo)));
85
+ }
86
+ async registerPlaceholder(tool, exposedTo) {
87
+ const placeholder = {
88
+ fingerprint: fingerprintOf(tool, exposedTo),
89
+ controller: new AbortController(),
90
+ adopted: false
91
+ };
92
+ this.placeholders.set(tool.name, placeholder);
93
+ try {
94
+ await this.modelContext.registerTool(
95
+ { ...tool, execute: (input, options) => this.call(tool.name, input, options) },
96
+ { signal: placeholder.controller.signal, ...exposedTo && { exposedTo } }
97
+ );
98
+ } catch {
99
+ this.release(tool.name, placeholder);
100
+ }
101
+ }
102
+ async call(name, input, options) {
103
+ await this.load();
104
+ const execute = this.executors.get(name);
105
+ if (!execute) throw new Error(`Tool "${name}" not found`);
106
+ return execute(input, options);
107
+ }
108
+ /** Load the server once; a failed load is tried again on the next call. */
109
+ load() {
110
+ this.loading ??= this.loadThroughPlugin().catch((error) => {
111
+ this.loading = void 0;
112
+ throw error;
113
+ });
114
+ return this.loading;
115
+ }
116
+ async loadThroughPlugin() {
117
+ const pluginSync = createPluginSync();
118
+ const attempt = { failed: false };
119
+ const handoff = withSyncListener(
120
+ { registerTool: (tool, options) => this.adopt(tool, options, attempt) },
121
+ pluginSync.listener
122
+ );
123
+ const server = await this.loadServer(handoff);
124
+ try {
125
+ await pluginSync.whenSynced();
126
+ } catch (error) {
127
+ await this.abandon(attempt, server);
128
+ throw error;
129
+ }
130
+ for (const [name, placeholder] of this.placeholders) {
131
+ if (!placeholder.adopted) this.release(name, placeholder);
132
+ }
133
+ }
134
+ /** Forget what a failed load's plugin registered, keep the listed tools for the next try, and dispose its server. */
135
+ async abandon(attempt, server) {
136
+ attempt.failed = true;
137
+ this.executors.clear();
138
+ for (const placeholder of this.placeholders.values()) placeholder.adopted = false;
139
+ const disposable = server;
140
+ if (typeof disposable?.dispose === "function") await Promise.resolve(disposable.dispose()).catch(() => void 0);
141
+ }
142
+ /**
143
+ * A registration from the plugin: an unchanged listed tool keeps its registration and runs the plugin's
144
+ * `execute`; anything else replaces the listed tool of that name, or is new, and goes to the page's context.
145
+ */
146
+ async adopt(tool, options, attempt) {
147
+ if (attempt.failed || options?.signal?.aborted) return;
148
+ const { execute, ...descriptor } = tool;
149
+ const placeholder = this.placeholders.get(tool.name);
150
+ const kept = placeholder && !placeholder.adopted && placeholder.fingerprint === fingerprintOf(descriptor, options?.exposedTo) ? placeholder : void 0;
151
+ this.executors.set(tool.name, execute);
152
+ options?.signal?.addEventListener(
153
+ "abort",
154
+ () => {
155
+ if (attempt.failed) return;
156
+ if (this.executors.get(tool.name) === execute) this.executors.delete(tool.name);
157
+ if (kept) this.release(tool.name, kept);
158
+ },
159
+ { once: true }
160
+ );
161
+ if (kept) {
162
+ kept.adopted = true;
163
+ return;
164
+ }
165
+ if (placeholder) this.release(tool.name, placeholder);
166
+ await this.modelContext.registerTool(tool, options);
167
+ }
168
+ release(name, placeholder) {
169
+ placeholder.controller.abort();
170
+ if (this.placeholders.get(name) === placeholder) this.placeholders.delete(name);
171
+ }
172
+ };
173
+ async function registerWebMcpTools(modelContext, tools, loadServer, options = {}) {
174
+ if (!modelContext) return;
175
+ await new LazyWebMcpTools(modelContext, loadServer).register(tools, options.exposedTo);
176
+ }
@@ -7,16 +7,13 @@
7
7
  */
8
8
  import { type CallToolResult, type ScopeEntry } from '@frontmcp/sdk';
9
9
  import type { WebMcpPluginOptions } from './webmcp.options';
10
- import type { ModelContext } from './webmcp.types';
11
- /** What a WebMCP call resolves to: the MCP result without its `_meta` (and never `isError`). */
12
- export interface WebMcpToolResult {
10
+ /** The MCP result without its `_meta` (and never `isError`). */
11
+ export interface WebMcpContentResult {
13
12
  content: CallToolResult['content'];
14
13
  structuredContent?: CallToolResult['structuredContent'];
15
14
  }
16
- /** `document.modelContext` in a browser that implements WebMCP (or has a polyfill installed). */
17
- export declare function resolveDocumentModelContext(): ModelContext | undefined;
18
- /** Whether this page can register WebMCP tools (`document.modelContext.registerTool` exists). */
19
- export declare function isWebMcpSupported(): boolean;
15
+ /** What a WebMCP call resolves to: the tool's structured content alone, or its content (see the `result` option). */
16
+ export type WebMcpToolResult = WebMcpContentResult | NonNullable<CallToolResult['structuredContent']>;
20
17
  /** Make `name` a valid WebMCP tool name: invalid characters become `_`, cut to 128 characters. */
21
18
  export declare function toWebMcpToolName(name: string): string;
22
19
  export declare class WebMcpBridge {
@@ -1 +1 @@
1
- {"version":3,"file":"webmcp.bridge.d.ts","sourceRoot":"","sources":["../src/webmcp.bridge.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAGL,KAAK,cAAc,EAEnB,KAAK,UAAU,EAChB,MAAM,eAAe,CAAC;AAGvB,OAAO,KAAK,EAAoB,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAC9E,OAAO,KAAK,EACV,YAAY,EAIb,MAAM,gBAAgB,CAAC;AAMxB,gGAAgG;AAChG,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,cAAc,CAAC,SAAS,CAAC,CAAC;IACnC,iBAAiB,CAAC,EAAE,cAAc,CAAC,mBAAmB,CAAC,CAAC;CACzD;AA4BD,iGAAiG;AACjG,wBAAgB,2BAA2B,IAAI,YAAY,GAAG,SAAS,CAOtE;AAED,iGAAiG;AACjG,wBAAgB,iBAAiB,IAAI,OAAO,CAE3C;AAED,kGAAkG;AAClG,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAGrD;AAsDD,qBAAa,YAAY;IAgBrB,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAhB1B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAiB;IACxC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA4B;IACtD,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAmC;IACjE,6FAA6F;IAC7F,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA6B;IACrD,OAAO,CAAC,YAAY,CAAC,CAAe;IACpC,OAAO,CAAC,WAAW,CAAC,CAAa;IACjC,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,OAAO,CAAS;IACxB,gEAAgE;IAChE,OAAO,CAAC,OAAO,CAAS;IACxB,0CAA0C;IAC1C,OAAO,CAAC,GAAG,CAAC,CAAgB;gBAGT,KAAK,EAAE,UAAU,EACjB,OAAO,EAAE,mBAAmB;IAK/C,4EAA4E;IAC5E,IAAI,mBAAmB,IAAI,MAAM,EAAE,CAElC;IAED;;;OAGG;IACH,KAAK,IAAI,IAAI;IAab;;;;OAIG;IACH,OAAO,IAAI,IAAI;IAMf,qDAAqD;IAC/C,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAI/B,4FAA4F;IAC5F,IAAI,IAAI,IAAI;IASZ,mGAAmG;YACrF,KAAK;YAgBL,IAAI;IAgBlB,kFAAkF;YACpE,SAAS;IAevB,OAAO,CAAC,YAAY;YAmBN,QAAQ;IAwBtB,kGAAkG;YACpF,OAAO;IA2BrB;;;OAGG;YACW,SAAS;IASvB,2FAA2F;YAC7E,QAAQ;CAiBvB"}
1
+ {"version":3,"file":"webmcp.bridge.d.ts","sourceRoot":"","sources":["../src/webmcp.bridge.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAGL,KAAK,cAAc,EAEnB,KAAK,UAAU,EAChB,MAAM,eAAe,CAAC;AAKvB,OAAO,KAAK,EAAoB,mBAAmB,EAAoB,MAAM,kBAAkB,CAAC;AAYhG,gEAAgE;AAChE,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,cAAc,CAAC,SAAS,CAAC,CAAC;IACnC,iBAAiB,CAAC,EAAE,cAAc,CAAC,mBAAmB,CAAC,CAAC;CACzD;AAED,qHAAqH;AACrH,MAAM,MAAM,gBAAgB,GAAG,mBAAmB,GAAG,WAAW,CAAC,cAAc,CAAC,mBAAmB,CAAC,CAAC,CAAC;AA4BtG,kGAAkG;AAClG,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAGrD;AAmFD,qBAAa,YAAY;IAgBrB,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAhB1B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAiB;IACxC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA4B;IACtD,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAmC;IACjE,6FAA6F;IAC7F,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA6B;IACrD,OAAO,CAAC,YAAY,CAAC,CAAe;IACpC,OAAO,CAAC,WAAW,CAAC,CAAa;IACjC,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,OAAO,CAAS;IACxB,gEAAgE;IAChE,OAAO,CAAC,OAAO,CAAS;IACxB,0CAA0C;IAC1C,OAAO,CAAC,GAAG,CAAC,CAAgB;gBAGT,KAAK,EAAE,UAAU,EACjB,OAAO,EAAE,mBAAmB;IAK/C,4EAA4E;IAC5E,IAAI,mBAAmB,IAAI,MAAM,EAAE,CAElC;IAED;;;OAGG;IACH,KAAK,IAAI,IAAI;IAcb;;;;OAIG;IACH,OAAO,IAAI,IAAI;IAMf,qDAAqD;IAC/C,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAI/B,4FAA4F;IAC5F,IAAI,IAAI,IAAI;IASZ,mGAAmG;YACrF,KAAK;YAoBL,IAAI;IAgBlB,kFAAkF;YACpE,SAAS;IAevB,OAAO,CAAC,YAAY;YAmBN,QAAQ;IAwBtB,kGAAkG;YACpF,OAAO;IAwBrB;;;OAGG;YACW,SAAS;IASvB,2FAA2F;YAC7E,QAAQ;CAiBvB"}
@@ -0,0 +1,10 @@
1
+ import type { ModelContext } from './webmcp.types';
2
+ export interface WebMcpSyncListener {
3
+ /** The plugin found this `ModelContext` and follows the server's tools on it. */
4
+ started(): void;
5
+ /** The plugin registered the server's current tools; `error` when listing them failed. */
6
+ synced(error?: unknown): void;
7
+ }
8
+ export declare function withSyncListener(modelContext: ModelContext, listener: WebMcpSyncListener): ModelContext;
9
+ export declare function syncListenerOf(modelContext: ModelContext | undefined): WebMcpSyncListener | undefined;
10
+ //# sourceMappingURL=webmcp.handoff.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webmcp.handoff.d.ts","sourceRoot":"","sources":["../src/webmcp.handoff.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAQnD,MAAM,WAAW,kBAAkB;IACjC,iFAAiF;IACjF,OAAO,IAAI,IAAI,CAAC;IAChB,0FAA0F;IAC1F,MAAM,CAAC,KAAK,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;CAC/B;AAID,wBAAgB,gBAAgB,CAAC,YAAY,EAAE,YAAY,EAAE,QAAQ,EAAE,kBAAkB,GAAG,YAAY,CAEvG;AAED,wBAAgB,cAAc,CAAC,YAAY,EAAE,YAAY,GAAG,SAAS,GAAG,kBAAkB,GAAG,SAAS,CAErG"}
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Offering a page's tools to agents before its server is loaded: `listWebMcpTools()` computes, at build time,
3
+ * what WebMcpPlugin registers, and `registerWebMcpTools()` registers that list in the page and loads the server
4
+ * on an agent's first call. The calls then run through the plugin. Neither imports the SDK.
5
+ */
6
+ import type { ModelContext, ModelContextTool } from './webmcp.types';
7
+ /** A tool as WebMcpPlugin passes it to `registerTool()`, without its `execute`. */
8
+ export type WebMcpToolDescriptor = Omit<ModelContextTool, 'execute'>;
9
+ /**
10
+ * Builds the server, passing `modelContext` to `WebMcpPlugin.init({ modelContext })` with the plugin's other
11
+ * options as the page uses them.
12
+ */
13
+ export type WebMcpServerFactory<Server = unknown> = (modelContext: ModelContext) => Promise<Server>;
14
+ /** Options of `registerWebMcpTools()`. */
15
+ export interface RegisterWebMcpToolsOptions {
16
+ /** Other origins the tools are offered to, as the plugin's `exposedTo` option. */
17
+ exposedTo?: string[];
18
+ }
19
+ /**
20
+ * What WebMcpPlugin registers for the server `createServer` builds: names, titles, descriptions, input schemas
21
+ * and hints, as JSON-safe values. Run it at build time and ship the result to `registerWebMcpTools()`. The
22
+ * server is disposed before this resolves.
23
+ *
24
+ * @example
25
+ * ```typescript
26
+ * const tools = await listWebMcpTools((modelContext) => createShopServer({ modelContext }));
27
+ * await writeFile('webmcp-tools.json', JSON.stringify(tools));
28
+ * ```
29
+ */
30
+ export declare function listWebMcpTools(createServer: WebMcpServerFactory<{
31
+ dispose(): Promise<void>;
32
+ }>): Promise<WebMcpToolDescriptor[]>;
33
+ /**
34
+ * Register `tools` (from `listWebMcpTools()`) with `modelContext` at once, without loading the server. An
35
+ * agent's first call loads it with `loadServer` (once, however many calls arrive together) and runs through
36
+ * WebMcpPlugin from then on, which also registers and unregisters tools as the server's tools change. Does
37
+ * nothing when `modelContext` is undefined (no WebMCP in this browser).
38
+ *
39
+ * @example
40
+ * ```typescript
41
+ * import { registerWebMcpTools, resolveDocumentModelContext } from '@frontmcp/plugin-webmcp/register';
42
+ * import tools from './webmcp-tools.json';
43
+ *
44
+ * void registerWebMcpTools(resolveDocumentModelContext(), tools, (modelContext) =>
45
+ * import('./server').then(({ createShopServer }) => createShopServer({ modelContext })),
46
+ * );
47
+ * ```
48
+ */
49
+ export declare function registerWebMcpTools(modelContext: ModelContext | undefined, tools: readonly WebMcpToolDescriptor[], loadServer: WebMcpServerFactory, options?: RegisterWebMcpToolsOptions): Promise<void>;
50
+ //# sourceMappingURL=webmcp.lazy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webmcp.lazy.d.ts","sourceRoot":"","sources":["../src/webmcp.lazy.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,KAAK,EACV,YAAY,EAEZ,gBAAgB,EAEjB,MAAM,gBAAgB,CAAC;AAExB,mFAAmF;AACnF,MAAM,MAAM,oBAAoB,GAAG,IAAI,CAAC,gBAAgB,EAAE,SAAS,CAAC,CAAC;AAErE;;;GAGG;AACH,MAAM,MAAM,mBAAmB,CAAC,MAAM,GAAG,OAAO,IAAI,CAAC,YAAY,EAAE,YAAY,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;AAEpG,0CAA0C;AAC1C,MAAM,WAAW,0BAA0B;IACzC,kFAAkF;IAClF,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;CACtB;AAyCD;;;;;;;;;;GAUG;AACH,wBAAsB,eAAe,CACnC,YAAY,EAAE,mBAAmB,CAAC;IAAE,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;CAAE,CAAC,GAC9D,OAAO,CAAC,oBAAoB,EAAE,CAAC,CAmBjC;AAmID;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,mBAAmB,CACvC,YAAY,EAAE,YAAY,GAAG,SAAS,EACtC,KAAK,EAAE,SAAS,oBAAoB,EAAE,EACtC,UAAU,EAAE,mBAAmB,EAC/B,OAAO,GAAE,0BAA+B,GACvC,OAAO,CAAC,IAAI,CAAC,CAGf"}
@@ -0,0 +1,6 @@
1
+ import type { ModelContext } from './webmcp.types';
2
+ /** `document.modelContext` in a browser that implements WebMCP (or has a polyfill installed). */
3
+ export declare function resolveDocumentModelContext(): ModelContext | undefined;
4
+ /** Whether this page can register WebMCP tools (`document.modelContext.registerTool` exists). */
5
+ export declare function isWebMcpSupported(): boolean;
6
+ //# sourceMappingURL=webmcp.model-context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webmcp.model-context.d.ts","sourceRoot":"","sources":["../src/webmcp.model-context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAEnD,iGAAiG;AACjG,wBAAgB,2BAA2B,IAAI,YAAY,GAAG,SAAS,CAOtE;AAED,iGAAiG;AACjG,wBAAgB,iBAAiB,IAAI,OAAO,CAE3C"}
@@ -11,7 +11,14 @@ export declare const webMcpPluginOptionsSchema: import("@frontmcp/lazy-zod").Zod
11
11
  exposedTo: import("@frontmcp/lazy-zod").ZodOptional<import("@frontmcp/lazy-zod").ZodArray<import("@frontmcp/lazy-zod").ZodString>>;
12
12
  authContext: import("@frontmcp/lazy-zod").ZodOptional<import("zod").ZodCustom<WebMcpAuthContext, WebMcpAuthContext>>;
13
13
  modelContext: import("@frontmcp/lazy-zod").ZodOptional<import("zod").ZodCustom<ModelContext, ModelContext>>;
14
+ result: import("@frontmcp/lazy-zod").ZodDefault<import("@frontmcp/lazy-zod").ZodEnum<{
15
+ structured: "structured";
16
+ content: "content";
17
+ both: "both";
18
+ }>>;
14
19
  }, import("zod/v4/core").$strip>;
20
+ /** What an agent's call resolves to (see the `result` option). */
21
+ export type WebMcpResultMode = z.output<typeof webMcpPluginOptionsSchema>['result'];
15
22
  /** Options of `WebMcpPlugin.init()`, as the plugin holds them (defaults applied). */
16
23
  export type WebMcpPluginOptions = z.output<typeof webMcpPluginOptionsSchema>;
17
24
  /** Options of `WebMcpPlugin.init()`, as passed. */
@@ -1 +1 @@
1
- {"version":3,"file":"webmcp.options.d.ts","sourceRoot":"","sources":["../src/webmcp.options.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,oBAAoB,CAAC;AACvC,OAAO,KAAK,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAExE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAEnD,yFAAyF;AACzF,MAAM,MAAM,gBAAgB,GAAG,eAAe,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,CAAC;AAEhE,gGAAgG;AAChG,MAAM,MAAM,iBAAiB,GAAG,iBAAiB,GAAG,CAAC,MAAM,iBAAiB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAAC,CAAC;AAM3G,eAAO,MAAM,yBAAyB;;qFAOX,gBAAgB,KAAK,OAAO,SAA5B,gBAAgB,KAAK,OAAO;;;;gCAWrD,CAAC;AAEH,qFAAqF;AACrF,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,yBAAyB,CAAC,CAAC;AAE7E,mDAAmD;AACnD,MAAM,MAAM,wBAAwB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAC"}
1
+ {"version":3,"file":"webmcp.options.d.ts","sourceRoot":"","sources":["../src/webmcp.options.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,oBAAoB,CAAC;AACvC,OAAO,KAAK,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAExE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAEnD,yFAAyF;AACzF,MAAM,MAAM,gBAAgB,GAAG,eAAe,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,CAAC;AAEhE,gGAAgG;AAChG,MAAM,MAAM,iBAAiB,GAAG,iBAAiB,GAAG,CAAC,MAAM,iBAAiB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAAC,CAAC;AAM3G,eAAO,MAAM,yBAAyB;;qFAOX,gBAAgB,KAAK,OAAO,SAA5B,gBAAgB,KAAK,OAAO;;;;;;;;;gCAmBrD,CAAC;AAEH,kEAAkE;AAClE,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,yBAAyB,CAAC,CAAC,QAAQ,CAAC,CAAC;AAEpF,qFAAqF;AACrF,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,yBAAyB,CAAC,CAAC;AAE7E,mDAAmD;AACnD,MAAM,MAAM,wBAAwB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAC"}