foldkit-agent-webmcp 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Patrick Glenn
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,120 @@
1
+ # `foldkit-agent-webmcp`
2
+
3
+ The browser-native adapter for [`foldkit-agent`](../agent). It projects exposed
4
+ Foldkit Messages into Chrome's experimental WebMCP Imperative API.
5
+
6
+ WebMCP runs in the page itself, so a tool's `execute` dispatches directly into
7
+ the same live Foldkit Runtime the human is already using — no DOM automation,
8
+ and no external browser-session bridge.
9
+
10
+ ```text
11
+ browser agent -> document.modelContext -> foldkit-agent-webmcp
12
+ -> Foldkit AgentRuntime -> Message -> update -> Model + Commands
13
+ ```
14
+
15
+ ## Install
16
+
17
+ ```bash
18
+ pnpm add foldkit-agent foldkit-agent-webmcp
19
+ ```
20
+
21
+ ## Usage
22
+
23
+ ```ts
24
+ import { AgentWebMcp } from 'foldkit-agent-webmcp'
25
+
26
+ const registration = AgentWebMcp.register({ agent: agentRuntime })
27
+ ```
28
+
29
+ [examples/todo](../../examples/todo) registers a real contract and executes the
30
+ resulting tools.
31
+
32
+ `agent` is the `AgentRuntime` returned by `Agent.bind(...)`.
33
+
34
+ Each currently available capability becomes one tool:
35
+
36
+ ```text
37
+ capability name -> tool name
38
+ variant description -> description
39
+ derived JSON Schema -> inputSchema
40
+ Runtime dispatch -> execute
41
+ ```
42
+
43
+ No schema or handler is repeated in this package.
44
+
45
+ ### Options
46
+
47
+ | Option | Default | Purpose |
48
+ | --- | --- | --- |
49
+ | `agent` | — | The bound `AgentRuntime`. |
50
+ | `modelContext` | `document.modelContext` | The WebMCP producer surface. |
51
+ | `signal` | — | Unregisters everything when aborted. |
52
+ | `followModel` | `true` | Reconcile registrations as the Model changes. |
53
+ | `invocationId` | `crypto.randomUUID()` | Supplies the invocation id. |
54
+ | `onError` | — | Reports a failure from a reconcile no caller is awaiting. |
55
+
56
+ ### Returned registration
57
+
58
+ ```ts
59
+ registration.refresh() // reconcile against the current Model
60
+ registration.registered() // capability names currently registered
61
+ registration.unregister() // abort every registration and stop following
62
+ ```
63
+
64
+ ## Dynamic availability
65
+
66
+ Tool availability is a projection of Model state, which falls out of TEA:
67
+
68
+ ```ts
69
+ RequestedDeleteTodo: {
70
+ name: 'delete_todo',
71
+ description: 'Delete the selected todo',
72
+ available: model => Option.isSome(model.selectedTodoId),
73
+ }
74
+ ```
75
+
76
+ ```text
77
+ Todo list tools: create_todo
78
+ user selects todo -> Model changes
79
+ -> tools: create_todo, delete_todo
80
+ ```
81
+
82
+ The adapter holds one `AbortController` per registered tool and passes its
83
+ signal in the options bag, where `registerTool` takes it:
84
+
85
+ ```ts
86
+ await document.modelContext.registerTool(tool, { signal: controller.signal })
87
+ ```
88
+
89
+ Aborting that signal is what unregisters the tool, so it must not go on the
90
+ descriptor. This is the registration signal, distinct from the execution signal
91
+ passed to `execute`, which is forwarded as `Invocation.signal`.
92
+
93
+ Reconciles are serialized, and a capability is recorded as registered only once
94
+ `registerTool` resolves. A registration the browser refuses is not recorded and
95
+ is retried on the next reconcile; `refresh()` rejects with the first failure
96
+ after attempting the rest. Nothing is registered once `unregister()` has run,
97
+ and a registration that was in flight when it ran is aborted.
98
+
99
+ ## Errors
100
+
101
+ A dispatch failure is returned as a tool error rather than thrown, so the
102
+ calling agent can act on it:
103
+
104
+ ```text
105
+ No such capability: delete_todo
106
+ Capability "delete_todo" is not available right now
107
+ Not authorized to invoke "delete_todo"
108
+ Invalid input for "create_todo"
109
+ Capability "create_todo" failed unexpectedly
110
+ ```
111
+
112
+ The last covers an unexpected defect, such as the host's `dispatch` throwing.
113
+ `execute` never rejects and never echoes the underlying error back to the
114
+ caller.
115
+
116
+ ## Why this stays an adapter
117
+
118
+ WebMCP is experimental and evolving. The types it needs are declared locally in
119
+ `src/webmcp.ts` rather than imported, so the browser API can change without the
120
+ Foldkit agent contract changing with it.
@@ -0,0 +1,101 @@
1
+ import { Agent } from "foldkit-agent";
2
+ //#region src/webmcp.d.ts
3
+ /**
4
+ * The slice of Chrome's experimental WebMCP Imperative API this adapter uses.
5
+ *
6
+ * These types are declared locally rather than imported: the API is still
7
+ * experimental, and Foldkit should not be coupled to a browser proposal that is
8
+ * still changing.
9
+ */
10
+ /** Metadata passed to a tool's `execute` function. */
11
+ interface ToolExecutionContext {
12
+ /** Cancellation for this invocation: is this call still wanted? */
13
+ readonly signal?: AbortSignal | undefined;
14
+ }
15
+ /** The value a WebMCP tool returns. */
16
+ interface ToolResult {
17
+ readonly content: ReadonlyArray<{
18
+ readonly type: 'text';
19
+ readonly text: string;
20
+ }>;
21
+ readonly isError?: boolean;
22
+ }
23
+ /** A tool registration as accepted by `document.modelContext.registerTool`. */
24
+ interface ToolDescriptor {
25
+ readonly name: string;
26
+ readonly description: string;
27
+ readonly inputSchema: Record<string, unknown>;
28
+ readonly execute: (input: any, context: ToolExecutionContext) => Promise<ToolResult>;
29
+ }
30
+ /**
31
+ * Options accepted alongside a tool registration.
32
+ *
33
+ * The registration signal belongs here, not on the descriptor: aborting it is
34
+ * how the documented API unregisters a tool.
35
+ */
36
+ interface RegisterToolOptions {
37
+ readonly signal?: AbortSignal | undefined;
38
+ }
39
+ /** The producer surface exposed on `document.modelContext`. */
40
+ interface ModelContext {
41
+ readonly registerTool: (tool: ToolDescriptor, options?: RegisterToolOptions) => void | Promise<unknown>;
42
+ }
43
+ /** Reads `document.modelContext`, when the page provides it. */
44
+ declare const documentModelContext: () => ModelContext | undefined;
45
+ //#endregion
46
+ //#region src/register.d.ts
47
+ /** A live WebMCP registration. */
48
+ interface Registration {
49
+ /** Re-reads availability from the current Model and reconciles registrations. */
50
+ readonly refresh: () => Promise<void>;
51
+ /** Unregisters every tool and stops following the Model. */
52
+ readonly unregister: () => void;
53
+ /** The capability names currently registered with WebMCP. */
54
+ readonly registered: () => ReadonlyArray<string>;
55
+ }
56
+ interface RegisterOptions<Model, Context_, Principal, ByName, ByTag> {
57
+ /** The agent contract bound to a live Foldkit Runtime. */
58
+ readonly agent: Agent.AgentRuntime<Model, Context_, Principal, ByName, ByTag>;
59
+ /** Defaults to `document.modelContext`. */
60
+ readonly modelContext?: ModelContext | undefined;
61
+ /** Unregisters everything when aborted. */
62
+ readonly signal?: AbortSignal | undefined;
63
+ /**
64
+ * Follow the live Model and reconcile registrations as `available(model)`
65
+ * changes. Defaults to `true`, and is inert unless the host supports
66
+ * subscription.
67
+ */
68
+ readonly followModel?: boolean | undefined;
69
+ /** Supplies an invocation id. Defaults to `crypto.randomUUID()`. */
70
+ readonly invocationId?: (() => string) | undefined;
71
+ /**
72
+ * Reports a failure from a reconcile that no caller is awaiting -- the
73
+ * initial registration, or one triggered by a Model change.
74
+ */
75
+ readonly onError?: ((error: unknown) => void) | undefined;
76
+ }
77
+ /**
78
+ * Projects an agent contract into `document.modelContext.registerTool(...)`.
79
+ *
80
+ * Each currently available capability becomes one tool: the capability name
81
+ * becomes the tool name, the variant description becomes the description, the
82
+ * derived JSON Schema becomes `inputSchema`, and `execute` validates its input
83
+ * and dispatches into the same live Foldkit Runtime the human is using.
84
+ *
85
+ * Tool availability is itself a projection of Model state. When
86
+ * `available(model)` changes, registrations are reconciled: capabilities that
87
+ * are gone have their registration signal aborted, and capabilities that have
88
+ * appeared are registered.
89
+ *
90
+ * @example
91
+ * ```ts
92
+ * const registration = AgentWebMcp.register({ agent: agentRuntime })
93
+ * ```
94
+ */
95
+ declare const register: <Model, Context_, Principal, ByName, ByTag>(options: RegisterOptions<Model, Context_, Principal, ByName, ByTag>) => Registration;
96
+ declare namespace agentWebMcp_d_exports {
97
+ export { RegisterOptions, Registration, documentModelContext, register };
98
+ }
99
+ //#endregion
100
+ export { agentWebMcp_d_exports as AgentWebMcp, type ModelContext, type RegisterToolOptions, type ToolDescriptor, type ToolExecutionContext, type ToolResult };
101
+ //# sourceMappingURL=index.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/webmcp.ts","../src/register.ts","../src/agentWebMcp.ts"],"mappings":";;;;;;;;;;UASiB;;WAEN,SAAS;;;UAIH;WACN,SAAS;aAAyB;aAAuB;;WACzD;;;UAIM;WACN;WACA;WACA,aAAa;WACb,UAAU,YAAY,SAAS,yBAAyB,QAAQ;;;;;;;;UAS1D;WACN,SAAS;;;UAIH;WACN,eACP,MAAM,gBACN,UAAU,+BACA;;;cAID,4BAA2B;;;;UCrCvB;;WAEN,eAAe;;WAEf;;WAEA,kBAAkB;;UAGZ,gBAAgB,OAAO,UAAU,WAAW,QAAQ;;WAE1D,OAAO,MAAM,aAAa,OAAO,UAAU,WAAW,QAAQ;;WAE9D,eAAe;;WAEf,SAAS;;;;;;WAMT;;WAEA;;;;;WAKA,YAAY;;;;;;;;;;;;;;;;;;;;cA0BV,WAAY,OAAO,UAAU,WAAW,QAAQ,OAC3D,SAAS,gBAAgB,OAAO,UAAU,WAAW,QAAQ,WAC5D"}
package/dist/index.mjs ADDED
@@ -0,0 +1,147 @@
1
+ import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
2
+ import { Agent } from "foldkit-agent";
3
+ import { Effect } from "effect";
4
+ //#region src/webmcp.ts
5
+ /** Reads `document.modelContext`, when the page provides it. */
6
+ const documentModelContext = () => {
7
+ if (typeof document === "undefined") return void 0;
8
+ return document.modelContext;
9
+ };
10
+ //#endregion
11
+ //#region src/register.ts
12
+ const textResult = (text, isError = false) => ({
13
+ content: [{
14
+ type: "text",
15
+ text
16
+ }],
17
+ isError
18
+ });
19
+ /**
20
+ * Projects an agent contract into `document.modelContext.registerTool(...)`.
21
+ *
22
+ * Each currently available capability becomes one tool: the capability name
23
+ * becomes the tool name, the variant description becomes the description, the
24
+ * derived JSON Schema becomes `inputSchema`, and `execute` validates its input
25
+ * and dispatches into the same live Foldkit Runtime the human is using.
26
+ *
27
+ * Tool availability is itself a projection of Model state. When
28
+ * `available(model)` changes, registrations are reconciled: capabilities that
29
+ * are gone have their registration signal aborted, and capabilities that have
30
+ * appeared are registered.
31
+ *
32
+ * @example
33
+ * ```ts
34
+ * const registration = AgentWebMcp.register({ agent: agentRuntime })
35
+ * ```
36
+ */
37
+ const register = (options) => {
38
+ const modelContext = options.modelContext ?? documentModelContext();
39
+ if (modelContext === void 0) throw new Error("WebMCP is unavailable: no document.modelContext. Pass an explicit modelContext to register().");
40
+ const { agent } = options;
41
+ const nextInvocationId = options.invocationId ?? Agent.newInvocationId;
42
+ /** One AbortController per registered tool: aborting it unregisters that tool. */
43
+ const controllers = /* @__PURE__ */ new Map();
44
+ let disposed = false;
45
+ const toolFor = (name, description, inputSchema) => {
46
+ const controller = new AbortController();
47
+ const execute = async (input, context) => {
48
+ try {
49
+ const result = await Effect.runPromise(Effect.result(agent.messages.dispatchUnknown(name, input, {
50
+ id: nextInvocationId(),
51
+ transport: "webmcp",
52
+ signal: context.signal
53
+ })));
54
+ if (result._tag === "Failure") return textResult(result.failure.message, true);
55
+ const { completion } = result.success;
56
+ if (completion === void 0) return textResult(`Dispatched ${result.success.tag}`);
57
+ return textResult(`${completion.status === "completed" ? "Completed" : "Failed"}: ${completion.message._tag}`, completion.status === "failed");
58
+ } catch {
59
+ return textResult(`Capability "${name}" failed unexpectedly`, true);
60
+ }
61
+ };
62
+ return {
63
+ controller,
64
+ tool: {
65
+ name,
66
+ description,
67
+ inputSchema,
68
+ execute
69
+ }
70
+ };
71
+ };
72
+ const runReconcile = async () => {
73
+ if (disposed) return;
74
+ const available = await Effect.runPromise(agent.messages.available);
75
+ const wanted = new Map(available.map((descriptor) => [descriptor.name, descriptor]));
76
+ for (const [name, controller] of [...controllers]) if (!wanted.has(name)) {
77
+ controller.abort();
78
+ controllers.delete(name);
79
+ }
80
+ let failure;
81
+ for (const [name, descriptor] of wanted) {
82
+ if (disposed) return;
83
+ if (controllers.has(name)) continue;
84
+ const { controller, tool } = toolFor(name, descriptor.description, descriptor.inputSchema);
85
+ try {
86
+ await modelContext.registerTool(tool, { signal: controller.signal });
87
+ } catch (error) {
88
+ failure ??= error;
89
+ continue;
90
+ }
91
+ if (disposed) {
92
+ controller.abort();
93
+ return;
94
+ }
95
+ controllers.set(name, controller);
96
+ }
97
+ if (failure !== void 0) throw failure;
98
+ };
99
+ /**
100
+ * Reconciles are serialized.
101
+ *
102
+ * A capability is recorded only once the browser has accepted it, so two
103
+ * overlapping reconciles would both see it as missing and register it twice.
104
+ */
105
+ let queue = Promise.resolve();
106
+ const reconcile = () => {
107
+ const next = queue.then(runReconcile, runReconcile);
108
+ queue = next.then(() => {}, () => {});
109
+ return next;
110
+ };
111
+ let unsubscribe = () => {};
112
+ const unregister = () => {
113
+ disposed = true;
114
+ for (const controller of controllers.values()) controller.abort();
115
+ controllers.clear();
116
+ unsubscribe();
117
+ };
118
+ /**
119
+ * Reconciles without a caller to await the result. A rejection here would
120
+ * otherwise become an unhandled rejection, so it is reported through
121
+ * `onError` and the registration is left as it was.
122
+ */
123
+ const reconcileInBackground = () => {
124
+ reconcile().catch((error) => {
125
+ options.onError?.(error);
126
+ });
127
+ };
128
+ reconcileInBackground();
129
+ if (options.followModel !== false) unsubscribe = agent.subscribe(reconcileInBackground);
130
+ if (options.signal?.aborted === true) unregister();
131
+ else options.signal?.addEventListener("abort", unregister, { once: true });
132
+ return {
133
+ refresh: reconcile,
134
+ unregister,
135
+ registered: () => [...controllers.keys()]
136
+ };
137
+ };
138
+ //#endregion
139
+ //#region src/agentWebMcp.ts
140
+ var agentWebMcp_exports = /* @__PURE__ */ __exportAll({
141
+ documentModelContext: () => documentModelContext,
142
+ register: () => register
143
+ });
144
+ //#endregion
145
+ export { agentWebMcp_exports as AgentWebMcp };
146
+
147
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/webmcp.ts","../src/register.ts","../src/agentWebMcp.ts"],"sourcesContent":["/**\n * The slice of Chrome's experimental WebMCP Imperative API this adapter uses.\n *\n * These types are declared locally rather than imported: the API is still\n * experimental, and Foldkit should not be coupled to a browser proposal that is\n * still changing.\n */\n\n/** Metadata passed to a tool's `execute` function. */\nexport interface ToolExecutionContext {\n /** Cancellation for this invocation: is this call still wanted? */\n readonly signal?: AbortSignal | undefined\n}\n\n/** The value a WebMCP tool returns. */\nexport interface ToolResult {\n readonly content: ReadonlyArray<{ readonly type: 'text'; readonly text: string }>\n readonly isError?: boolean\n}\n\n/** A tool registration as accepted by `document.modelContext.registerTool`. */\nexport interface ToolDescriptor {\n readonly name: string\n readonly description: string\n readonly inputSchema: Record<string, unknown>\n readonly execute: (input: any, context: ToolExecutionContext) => Promise<ToolResult>\n}\n\n/**\n * Options accepted alongside a tool registration.\n *\n * The registration signal belongs here, not on the descriptor: aborting it is\n * how the documented API unregisters a tool.\n */\nexport interface RegisterToolOptions {\n readonly signal?: AbortSignal | undefined\n}\n\n/** The producer surface exposed on `document.modelContext`. */\nexport interface ModelContext {\n readonly registerTool: (\n tool: ToolDescriptor,\n options?: RegisterToolOptions,\n ) => void | Promise<unknown>\n}\n\n/** Reads `document.modelContext`, when the page provides it. */\nexport const documentModelContext = (): ModelContext | undefined => {\n if (typeof document === 'undefined') return undefined\n return (document as unknown as { modelContext?: ModelContext }).modelContext\n}\n","import { Agent } from 'foldkit-agent'\nimport { Effect } from 'effect'\nimport {\n type ModelContext,\n type ToolDescriptor,\n type ToolResult,\n documentModelContext,\n} from './webmcp.js'\n\n/** A live WebMCP registration. */\nexport interface Registration {\n /** Re-reads availability from the current Model and reconciles registrations. */\n readonly refresh: () => Promise<void>\n /** Unregisters every tool and stops following the Model. */\n readonly unregister: () => void\n /** The capability names currently registered with WebMCP. */\n readonly registered: () => ReadonlyArray<string>\n}\n\nexport interface RegisterOptions<Model, Context_, Principal, ByName, ByTag> {\n /** The agent contract bound to a live Foldkit Runtime. */\n readonly agent: Agent.AgentRuntime<Model, Context_, Principal, ByName, ByTag>\n /** Defaults to `document.modelContext`. */\n readonly modelContext?: ModelContext | undefined\n /** Unregisters everything when aborted. */\n readonly signal?: AbortSignal | undefined\n /**\n * Follow the live Model and reconcile registrations as `available(model)`\n * changes. Defaults to `true`, and is inert unless the host supports\n * subscription.\n */\n readonly followModel?: boolean | undefined\n /** Supplies an invocation id. Defaults to `crypto.randomUUID()`. */\n readonly invocationId?: (() => string) | undefined\n /**\n * Reports a failure from a reconcile that no caller is awaiting -- the\n * initial registration, or one triggered by a Model change.\n */\n readonly onError?: ((error: unknown) => void) | undefined\n}\n\nconst textResult = (text: string, isError = false): ToolResult => ({\n content: [{ type: 'text', text }],\n isError,\n})\n\n/**\n * Projects an agent contract into `document.modelContext.registerTool(...)`.\n *\n * Each currently available capability becomes one tool: the capability name\n * becomes the tool name, the variant description becomes the description, the\n * derived JSON Schema becomes `inputSchema`, and `execute` validates its input\n * and dispatches into the same live Foldkit Runtime the human is using.\n *\n * Tool availability is itself a projection of Model state. When\n * `available(model)` changes, registrations are reconciled: capabilities that\n * are gone have their registration signal aborted, and capabilities that have\n * appeared are registered.\n *\n * @example\n * ```ts\n * const registration = AgentWebMcp.register({ agent: agentRuntime })\n * ```\n */\nexport const register = <Model, Context_, Principal, ByName, ByTag>(\n options: RegisterOptions<Model, Context_, Principal, ByName, ByTag>,\n): Registration => {\n const modelContext = options.modelContext ?? documentModelContext()\n if (modelContext === undefined) {\n throw new Error(\n 'WebMCP is unavailable: no document.modelContext. Pass an explicit modelContext to register().',\n )\n }\n\n const { agent } = options\n const nextInvocationId = options.invocationId ?? Agent.newInvocationId\n\n /** One AbortController per registered tool: aborting it unregisters that tool. */\n const controllers = new Map<string, AbortController>()\n let disposed = false\n\n const toolFor = (\n name: string,\n description: string,\n inputSchema: Record<string, unknown>,\n ): { readonly controller: AbortController; readonly tool: ToolDescriptor } => {\n const controller = new AbortController()\n\n const execute = async (\n input: unknown,\n context: { readonly signal?: AbortSignal | undefined },\n ): Promise<ToolResult> => {\n try {\n // WebMCP hands the page a promise-returning callback, so this is the\n // edge where Effect meets the browser, not a run inside a service.\n const result = await Effect.runPromise(\n Effect.result(\n // The tool name and payload both come off the wire.\n agent.messages.dispatchUnknown(name, input, {\n id: nextInvocationId(),\n transport: 'webmcp',\n signal: context.signal,\n }),\n ),\n )\n\n // Every agent failure carries a message written for this audience.\n if (result._tag === 'Failure') return textResult(result.failure.message, true)\n\n // A declared completion contract has already resolved by the time\n // dispatch returns, so a failed completion is a failed tool call --\n // reported the same way the MCP adapter reports it.\n const { completion } = result.success\n if (completion === undefined) return textResult(`Dispatched ${result.success.tag}`)\n\n return textResult(\n `${completion.status === 'completed' ? 'Completed' : 'Failed'}: ${completion.message._tag}`,\n completion.status === 'failed',\n )\n } catch {\n // `Effect.result` captures expected failures but not defects, and a\n // rejected tool promise is not something a calling agent can act on.\n // Report it as a tool error without leaking the application's internals.\n return textResult(`Capability \"${name}\" failed unexpectedly`, true)\n }\n }\n\n return { controller, tool: { name, description, inputSchema, execute } }\n }\n\n const runReconcile = async (): Promise<void> => {\n if (disposed) return\n\n const available = await Effect.runPromise(agent.messages.available)\n const wanted = new Map(available.map(descriptor => [descriptor.name, descriptor]))\n\n // Unregister capabilities the Model no longer offers.\n for (const [name, controller] of [...controllers]) {\n if (!wanted.has(name)) {\n controller.abort()\n controllers.delete(name)\n }\n }\n\n // Register capabilities that have appeared.\n let failure: unknown\n for (const [name, descriptor] of wanted) {\n // unregister() can land while an await above is pending. Nothing more is\n // registered after that.\n if (disposed) return\n if (controllers.has(name)) continue\n\n const { controller, tool } = toolFor(name, descriptor.description, descriptor.inputSchema)\n\n try {\n // The signal goes in the options bag: aborting it is what unregisters\n // the tool.\n await modelContext.registerTool(tool, { signal: controller.signal })\n } catch (error) {\n // Nothing was registered, so nothing is recorded and nothing to abort.\n failure ??= error\n continue\n }\n\n // Disposal can also land while this registration is in flight, in which\n // case the tool is already registered and has to be taken back.\n if (disposed) {\n controller.abort()\n return\n }\n\n // Recorded only once the browser has accepted it.\n controllers.set(name, controller)\n }\n\n if (failure !== undefined) throw failure\n }\n\n /**\n * Reconciles are serialized.\n *\n * A capability is recorded only once the browser has accepted it, so two\n * overlapping reconciles would both see it as missing and register it twice.\n */\n let queue: Promise<void> = Promise.resolve()\n\n const reconcile = (): Promise<void> => {\n const next = queue.then(runReconcile, runReconcile)\n queue = next.then(\n () => {},\n () => {},\n )\n return next\n }\n\n let unsubscribe: () => void = () => {}\n\n const unregister = (): void => {\n disposed = true\n for (const controller of controllers.values()) controller.abort()\n controllers.clear()\n unsubscribe()\n }\n\n /**\n * Reconciles without a caller to await the result. A rejection here would\n * otherwise become an unhandled rejection, so it is reported through\n * `onError` and the registration is left as it was.\n */\n const reconcileInBackground = (): void => {\n reconcile().catch(error => {\n options.onError?.(error)\n })\n }\n\n reconcileInBackground()\n\n if (options.followModel !== false) {\n unsubscribe = agent.subscribe(reconcileInBackground)\n }\n\n if (options.signal?.aborted === true) {\n // An already-aborted signal never fires its listener.\n unregister()\n } else {\n options.signal?.addEventListener('abort', unregister, { once: true })\n }\n\n return {\n refresh: reconcile,\n unregister,\n registered: () => [...controllers.keys()],\n }\n}\n","export { register, type RegisterOptions, type Registration } from './register.js'\nexport { documentModelContext } from './webmcp.js'\n"],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ //#region \0rolldown/runtime.js
2
+ var __defProp = Object.defineProperty;
3
+ var __exportAll = (all, no_symbols) => {
4
+ let target = {};
5
+ for (var name in all) __defProp(target, name, {
6
+ get: all[name],
7
+ enumerable: true
8
+ });
9
+ if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
10
+ return target;
11
+ };
12
+ //#endregion
13
+ export { __exportAll as t };
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "foldkit-agent-webmcp",
3
+ "version": "0.1.0",
4
+ "description": "WebMCP adapter for foldkit-agent. Projects exposed Foldkit Messages into document.modelContext tools.",
5
+ "keywords": [
6
+ "foldkit",
7
+ "effect",
8
+ "agents",
9
+ "webmcp",
10
+ "browser"
11
+ ],
12
+ "type": "module",
13
+ "license": "MIT",
14
+ "author": "Patrick Glenn <doeixd@gmail.com>",
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "git+https://github.com/doeixd/foldkit-plus.git",
18
+ "directory": "packages/agent-webmcp"
19
+ },
20
+ "homepage": "https://github.com/doeixd/foldkit-plus/tree/main/packages/agent-webmcp#readme",
21
+ "bugs": {
22
+ "url": "https://github.com/doeixd/foldkit-plus/issues"
23
+ },
24
+ "sideEffects": false,
25
+ "publishConfig": {
26
+ "access": "public"
27
+ },
28
+ "exports": {
29
+ ".": {
30
+ "types": "./dist/index.d.mts",
31
+ "import": "./dist/index.mjs"
32
+ }
33
+ },
34
+ "files": [
35
+ "dist",
36
+ "README.md",
37
+ "LICENSE"
38
+ ],
39
+ "peerDependencies": {
40
+ "effect": "^4.0.0-rc.112",
41
+ "foldkit": "^0.158.2",
42
+ "foldkit-agent": "^0.1.0"
43
+ },
44
+ "dependencies": {},
45
+ "devDependencies": {
46
+ "effect": "4.0.0-rc.112",
47
+ "foldkit": "0.158.2",
48
+ "tsdown": "^0.23.0",
49
+ "typescript": "^5.7.2",
50
+ "foldkit-agent": "0.1.0"
51
+ },
52
+ "scripts": {
53
+ "build": "tsdown",
54
+ "typecheck": "tsc -b"
55
+ }
56
+ }