foldkit-agent-webmcp 0.2.0 → 0.3.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.
Files changed (2) hide show
  1. package/README.md +175 -59
  2. package/package.json +3 -3
package/README.md CHANGED
@@ -1,69 +1,125 @@
1
1
  # `foldkit-agent-webmcp`
2
2
 
3
- The browser-native adapter for [`foldkit-agent`](../agent). It projects exposed
4
- Foldkit Messages into Chrome's experimental WebMCP Imperative API.
3
+ Publishes a [`foldkit-agent`](../agent) runtime as browser-native WebMCP tools.
5
4
 
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.
5
+ Use it when an agent runs **inside the page** and should operate the same live
6
+ Foldkit application the person is using. This package does not define new agent
7
+ capabilities and does not automate the DOM. It translates the capabilities your
8
+ agent contract already exposes into `document.modelContext` tools.
9
9
 
10
10
  ```text
11
- browser agent -> document.modelContext -> foldkit-agent-webmcp
12
- -> Foldkit AgentRuntime -> Message -> update -> Model + Commands
11
+ Foldkit application
12
+ |
13
+ v
14
+ AssistantAgent protocol-neutral contract
15
+ |
16
+ | AgentBuilder.bind(...)
17
+ v
18
+ AgentRuntime live Model + dispatch + policy
19
+ |
20
+ v
21
+ foldkit-agent-webmcp adapter only
22
+ |
23
+ v
24
+ document.modelContext browser tools
25
+ |
26
+ v
27
+ Message -> update -> Model + Commands
13
28
  ```
14
29
 
30
+ The authority boundary stays in `foldkit-agent`: the contract decides what the
31
+ agent may observe and which Messages it may cause. WebMCP only makes those
32
+ capabilities available to a browser-resident agent.
33
+
34
+ [WebMCP](https://github.com/webmachinelearning/webmcp) is an experimental web
35
+ platform proposal. No stable browser ships it today, so feature detection is
36
+ part of the normal integration path.
37
+
15
38
  ## Install
16
39
 
17
40
  ```bash
18
41
  pnpm add foldkit-agent foldkit-agent-webmcp
19
42
  ```
20
43
 
21
- ## Usage
44
+ `foldkit`, `effect`, and `foldkit-agent` are peer dependencies.
45
+
46
+ ## Sixty seconds: publish a bound runtime
47
+
48
+ Assume the application already declared a protocol-neutral contract:
49
+
50
+ ```ts
51
+ const AgentBuilder = Agent.forApplication(App).withPrincipal<Principal>()
52
+ const AssistantAgent = AgentBuilder.make({ ... })
53
+ ```
54
+
55
+ Bind that contract to the running application, then register its currently
56
+ available capabilities with WebMCP:
22
57
 
23
58
  ```ts
24
59
  import { AgentWebMcp } from 'foldkit-agent-webmcp'
25
60
 
26
- const registration = AgentWebMcp.register({ agent: agentRuntime })
61
+ const agentRuntime = AgentBuilder.bind({
62
+ definition: AssistantAgent,
63
+ host: {
64
+ model: mounted.model,
65
+ dispatch: message => mounted.dispatch(message),
66
+ subscribe: mounted.subscribe,
67
+ observe: mounted.observe, // required only by capabilities with completion contracts
68
+ },
69
+ })
70
+
71
+ const modelContext = AgentWebMcp.documentModelContext()
72
+
73
+ if (modelContext !== undefined) {
74
+ const registration = AgentWebMcp.register({
75
+ agent: agentRuntime,
76
+ modelContext,
77
+ })
78
+
79
+ window.addEventListener('beforeunload', () => registration.unregister())
80
+ }
27
81
  ```
28
82
 
29
- [examples/todo](../../examples/todo) registers a real contract and executes the
30
- resulting tools.
83
+ That is the whole architecture: the contract remains protocol-neutral, the
84
+ runtime remains the one authority for availability/authorization/dispatch, and
85
+ this package translates it to the browser API.
31
86
 
32
- `agent` is the `AgentRuntime` returned by `Agent.bind(...)`.
87
+ `documentModelContext()` is safe during SSR and returns `undefined` when no
88
+ WebMCP producer surface exists. Calling `register()` without a model context
89
+ throws rather than silently pretending registration succeeded.
33
90
 
34
- Each currently available capability becomes one tool:
91
+ Registration starts immediately, but browser registration is asynchronous.
92
+ `register()` returns after reconciliation is scheduled; use
93
+ `await registration.refresh()` when the caller needs to know that the browser
94
+ has accepted the current tool set, and `onError` for background reconcile
95
+ failures.
35
96
 
36
- ```text
37
- capability name -> tool name
38
- variant description -> description
39
- derived JSON Schema -> inputSchema
40
- Runtime dispatch -> execute
41
- ```
97
+ ## What a capability becomes
42
98
 
43
- No schema or handler is repeated in this package.
99
+ Each **currently available** capability becomes one WebMCP tool:
44
100
 
45
- ### Options
101
+ ```text
102
+ foldkit-agent WebMCP
46
103
 
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. |
104
+ capability name -> tool name
105
+ variant description -> description
106
+ encoded input Schema -> inputSchema
107
+ AgentRuntime dispatch -> execute
108
+ completion contract -> resolved tool outcome
109
+ ```
55
110
 
56
- ### Returned registration
111
+ No input schema or handler is written again. `execute` delegates to the bound
112
+ runtime, which performs the same decode, availability check, authorization,
113
+ dispatch, cancellation, and completion handling as every other adapter.
57
114
 
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
- ```
115
+ A capability with a completion contract resolves when its correlated success or
116
+ failure Message is observed. Without one, validated dispatch is the completion
117
+ boundary.
63
118
 
64
- ## Dynamic availability
119
+ ## Tools follow the Model
65
120
 
66
- Tool availability is a projection of Model state, which falls out of TEA:
121
+ Availability is Model-dependent, so the advertised WebMCP tool set may change as
122
+ the application changes:
67
123
 
68
124
  ```ts
69
125
  RequestedDeleteTodo: {
@@ -74,32 +130,52 @@ RequestedDeleteTodo: {
74
130
  ```
75
131
 
76
132
  ```text
77
- Todo list tools: create_todo
78
- user selects todo -> Model changes
79
- -> tools: create_todo, delete_todo
133
+ no selection tools: create_todo
134
+ |
135
+ user selects todo
136
+ v
137
+ selected todo tools: create_todo, delete_todo
80
138
  ```
81
139
 
82
- The adapter holds one `AbortController` per registered tool and passes its
83
- signal in the options bag, where `registerTool` takes it:
140
+ With `followModel: true` (the default) and a host with `subscribe`, a Model
141
+ change reconciles registrations. A capability that disappears is unregistered;
142
+ a newly available capability is registered.
143
+
144
+ Availability is stronger than discoverability. A capability absent from the
145
+ tool list is also refused if a caller somehow invokes its known name.
146
+
147
+ Reconciliation is serialized, so overlapping Model changes cannot register the
148
+ same capability twice. A failed browser registration is not recorded as live and
149
+ is retried on a later reconcile.
150
+
151
+ ## Registration lifecycle
152
+
153
+ `AgentWebMcp.register` returns:
84
154
 
85
155
  ```ts
86
- await document.modelContext.registerTool(tool, { signal: controller.signal })
156
+ registration.registered() // capability names currently registered
157
+ await registration.refresh() // reconcile against the current Model
158
+ registration.unregister() // stop following and abort every registration
87
159
  ```
88
160
 
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`.
161
+ The main options are:
162
+
163
+ | Option | Default | Purpose |
164
+ | --- | --- | --- |
165
+ | `agent` | — | The bound `AgentRuntime`. |
166
+ | `modelContext` | `document.modelContext` | Producer surface to register against; pass a stand-in in tests. |
167
+ | `followModel` | `true` | Reconcile tools as capability availability changes. |
168
+ | `signal` | — | Unregister everything when aborted. |
169
+ | `invocationId` | `crypto.randomUUID()` | Supplies protocol invocation ids. |
170
+ | `onError` | — | Receives failures from background reconciliation. |
92
171
 
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.
172
+ Each registered tool has its own registration `AbortController`. That signal is
173
+ for the WebMCP registration itself. It is distinct from a tool invocation's
174
+ execution signal, which is forwarded to `AgentRuntime` as `Invocation.signal`.
98
175
 
99
176
  ## Errors
100
177
 
101
- A dispatch failure is returned as a tool error rather than thrown, so the
102
- calling agent can act on it:
178
+ Application refusals become tool errors the calling agent can reason about:
103
179
 
104
180
  ```text
105
181
  No such capability: delete_todo
@@ -109,12 +185,52 @@ Invalid input for "create_todo"
109
185
  Capability "create_todo" failed unexpectedly
110
186
  ```
111
187
 
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.
188
+ `execute` does not expose underlying application errors to the caller. A host or
189
+ adapter defect is flattened to a generic failure; the detailed error stays in
190
+ the application.
191
+
192
+ ## Testing without a browser
193
+
194
+ `modelContext` is the only browser-specific seam this package needs. Pass an
195
+ object with `registerTool` to capture descriptors and invoke their `execute`
196
+ functions directly:
197
+
198
+ ```ts
199
+ const registration = AgentWebMcp.register({
200
+ agent: agentRuntime,
201
+ modelContext: fakeModelContext,
202
+ })
203
+
204
+ await registration.refresh()
205
+ ```
206
+
207
+ That is how the package test suite and [`examples/todo`](../../examples/todo)
208
+ exercise the real adapter path without requiring browser support.
209
+
210
+ [`examples/todo-app`](../../examples/todo-app) shows the same adapter attached to
211
+ a real local-first Foldkit application.
212
+
213
+ ## Choosing an adapter
214
+
215
+ All four adapters serve the same `AssistantAgent` contract through a bound
216
+ runtime. Serving more than one does not duplicate capability declarations.
217
+
218
+ | Where the agent runs | Adapter |
219
+ | --- | --- |
220
+ | In the page, beside the user | `foldkit-agent-webmcp` |
221
+ | An external MCP client, over stdio or HTTP | [`foldkit-agent-mcp`](../agent-mcp) |
222
+ | Another agent, over A2A | [`foldkit-agent-a2a`](../agent-a2a) |
223
+ | An Agent Native host | [`foldkit-agent-native`](../agent-native) |
115
224
 
116
225
  ## Why this stays an adapter
117
226
 
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.
227
+ WebMCP is experimental and may change independently of Foldkit Plus. Its browser
228
+ shape is intentionally isolated here. If the proposal changes, this adapter can
229
+ change without changing the protocol-neutral `foldkit-agent` contract or the
230
+ application's Messages.
231
+
232
+ ## See also
233
+
234
+ - [Agents guide](../../docs/agents.md) — the full builder → contract → runtime mental model.
235
+ - [`foldkit-agent`](../agent) — declares and binds the contract this package serves.
236
+ - [WebMCP](https://github.com/webmachinelearning/webmcp) — the browser proposal.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "foldkit-agent-webmcp",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "WebMCP adapter for foldkit-agent. Projects exposed Foldkit Messages into document.modelContext tools.",
5
5
  "keywords": [
6
6
  "foldkit",
@@ -39,7 +39,7 @@
39
39
  "peerDependencies": {
40
40
  "effect": "^4.0.0-rc.112",
41
41
  "foldkit": "^0.158.2",
42
- "foldkit-agent": "^0.2.0"
42
+ "foldkit-agent": "^0.3.0"
43
43
  },
44
44
  "dependencies": {},
45
45
  "devDependencies": {
@@ -47,7 +47,7 @@
47
47
  "foldkit": "0.158.2",
48
48
  "tsdown": "^0.23.0",
49
49
  "typescript": "^5.7.2",
50
- "foldkit-agent": "0.2.0"
50
+ "foldkit-agent": "0.3.0"
51
51
  },
52
52
  "scripts": {
53
53
  "build": "tsdown",