foldkit-agent-webmcp 0.2.0 → 0.2.1
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 +121 -33
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1,37 +1,82 @@
|
|
|
1
1
|
# `foldkit-agent-webmcp`
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
Turns a [`foldkit-agent`](https://github.com/doeixd/foldkit-plus/tree/main/packages/agent)
|
|
4
|
+
contract into browser tools, so an agent running **inside the page** can use the
|
|
5
|
+
application the person in front of it is already using.
|
|
5
6
|
|
|
6
|
-
WebMCP
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
[WebMCP](https://github.com/webmachinelearning/webmcp) is an experimental web
|
|
8
|
+
platform proposal: a page publishes tools on `document.modelContext`, and a
|
|
9
|
+
browser-resident agent calls them. Because the tool runs in the page, its
|
|
10
|
+
`execute` dispatches a Message straight into the live Foldkit Runtime. There is
|
|
11
|
+
no DOM automation, no scraping, no second copy of the application's logic, and
|
|
12
|
+
no browser-session bridge.
|
|
9
13
|
|
|
10
14
|
```text
|
|
11
15
|
browser agent -> document.modelContext -> foldkit-agent-webmcp
|
|
12
16
|
-> Foldkit AgentRuntime -> Message -> update -> Model + Commands
|
|
13
17
|
```
|
|
14
18
|
|
|
19
|
+
The agent can only do what the contract exposes, and the contract only exposes
|
|
20
|
+
Messages `update` already handles. Everything an agent may see or do is decided
|
|
21
|
+
in `foldkit-agent`; this package translates and nothing else.
|
|
22
|
+
|
|
15
23
|
## Install
|
|
16
24
|
|
|
17
25
|
```bash
|
|
18
26
|
pnpm add foldkit-agent foldkit-agent-webmcp
|
|
19
27
|
```
|
|
20
28
|
|
|
29
|
+
`foldkit`, `effect`, and `foldkit-agent` are peer dependencies.
|
|
30
|
+
|
|
21
31
|
## Usage
|
|
22
32
|
|
|
33
|
+
The whole path, from a declared contract to live tools:
|
|
34
|
+
|
|
23
35
|
```ts
|
|
24
36
|
import { AgentWebMcp } from 'foldkit-agent-webmcp'
|
|
25
37
|
|
|
26
|
-
|
|
38
|
+
// 1. Bind the contract to the running application. `TodoAgent` is
|
|
39
|
+
// `Agent.forApplication(App)`, or `Agent.forModel<Model>()` without one.
|
|
40
|
+
const agent = TodoAgent.bind({
|
|
41
|
+
definition: AppAgent,
|
|
42
|
+
host: {
|
|
43
|
+
model: mounted.model,
|
|
44
|
+
dispatch: (message: Message) => {
|
|
45
|
+
mounted.dispatch(message)
|
|
46
|
+
},
|
|
47
|
+
subscribe: mounted.subscribe, // lets tools appear and disappear with the Model
|
|
48
|
+
observe: mounted.observe, // required by a capability with a completion contract
|
|
49
|
+
},
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
// 2. Publish it, if this browser speaks WebMCP.
|
|
53
|
+
const modelContext = AgentWebMcp.documentModelContext()
|
|
54
|
+
if (modelContext !== undefined) {
|
|
55
|
+
const registration = AgentWebMcp.register({ agent, modelContext })
|
|
56
|
+
window.addEventListener('beforeunload', () => registration.unregister())
|
|
57
|
+
}
|
|
27
58
|
```
|
|
28
59
|
|
|
29
|
-
|
|
30
|
-
|
|
60
|
+
**Check for support first.** WebMCP ships in no stable browser today, so
|
|
61
|
+
`document.modelContext` is usually absent. `documentModelContext()` returns it
|
|
62
|
+
or `undefined`, and is safe on a server, where there is no `document` at all.
|
|
63
|
+
Calling `register()` without a model context **throws**, on the grounds that
|
|
64
|
+
silently registering nothing would look like a working integration. Feature-detect,
|
|
65
|
+
or pass a `modelContext` of your own.
|
|
31
66
|
|
|
32
|
-
|
|
67
|
+
Registration begins immediately and in the background: `register` returns as
|
|
68
|
+
soon as the reconcile is scheduled, not when the browser has accepted the tools.
|
|
69
|
+
Pass `onError` to hear about a failure in that first pass, or `await
|
|
70
|
+
registration.refresh()` when you want to know the tools are live.
|
|
33
71
|
|
|
34
|
-
|
|
72
|
+
[`examples/todo`](https://github.com/doeixd/foldkit-plus/tree/main/examples/todo)
|
|
73
|
+
registers a real contract and executes the resulting tools;
|
|
74
|
+
[`examples/todo-app`](https://github.com/doeixd/foldkit-plus/tree/main/examples/todo-app)
|
|
75
|
+
does it in a real browser over a local-first replica.
|
|
76
|
+
|
|
77
|
+
## What a capability becomes
|
|
78
|
+
|
|
79
|
+
Each **currently available** capability becomes one tool:
|
|
35
80
|
|
|
36
81
|
```text
|
|
37
82
|
capability name -> tool name
|
|
@@ -40,30 +85,35 @@ derived JSON Schema -> inputSchema
|
|
|
40
85
|
Runtime dispatch -> execute
|
|
41
86
|
```
|
|
42
87
|
|
|
43
|
-
No schema
|
|
88
|
+
No schema and no handler is written twice. `execute` decodes its input through
|
|
89
|
+
the capability's own Effect Schema, runs `available` and `authorize`, and
|
|
90
|
+
dispatches; a capability with a `completion` contract resolves when the
|
|
91
|
+
correlated Message is applied, so the agent learns the outcome rather than that
|
|
92
|
+
the call was received.
|
|
44
93
|
|
|
45
94
|
### Options
|
|
46
95
|
|
|
47
96
|
| Option | Default | Purpose |
|
|
48
97
|
| --- | --- | --- |
|
|
49
98
|
| `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. |
|
|
99
|
+
| `modelContext` | `document.modelContext` | The WebMCP producer surface. Pass one to target a different surface, or to drive the adapter in a test. |
|
|
100
|
+
| `signal` | — | Unregisters everything when aborted. An already-aborted signal unregisters at once. |
|
|
101
|
+
| `followModel` | `true` | Reconcile registrations as the Model changes. Inert unless the host supports `subscribe`. |
|
|
53
102
|
| `invocationId` | `crypto.randomUUID()` | Supplies the invocation id. |
|
|
54
|
-
| `onError` | — | Reports a failure from a reconcile no caller is awaiting. |
|
|
103
|
+
| `onError` | — | Reports a failure from a reconcile no caller is awaiting: the initial registration, or one a Model change triggered. |
|
|
55
104
|
|
|
56
|
-
###
|
|
105
|
+
### The returned registration
|
|
57
106
|
|
|
58
107
|
```ts
|
|
59
|
-
registration.refresh()
|
|
60
|
-
registration.registered()
|
|
61
|
-
registration.unregister()
|
|
108
|
+
registration.refresh() // Promise: reconcile against the current Model
|
|
109
|
+
registration.registered() // the capability names currently registered
|
|
110
|
+
registration.unregister() // abort every registration and stop following
|
|
62
111
|
```
|
|
63
112
|
|
|
64
|
-
##
|
|
113
|
+
## Tools appear and disappear with the Model
|
|
65
114
|
|
|
66
|
-
|
|
115
|
+
Availability is a projection of Model state, which falls out of the
|
|
116
|
+
architecture rather than needing its own mechanism:
|
|
67
117
|
|
|
68
118
|
```ts
|
|
69
119
|
RequestedDeleteTodo: {
|
|
@@ -79,6 +129,12 @@ user selects todo -> Model changes
|
|
|
79
129
|
-> tools: create_todo, delete_todo
|
|
80
130
|
```
|
|
81
131
|
|
|
132
|
+
With `followModel` left on and a host that supports `subscribe`, every
|
|
133
|
+
transition reconciles the registrations: capabilities that are gone have their
|
|
134
|
+
registration signal aborted, and capabilities that have appeared are registered.
|
|
135
|
+
Availability is stronger than visibility. A capability the Model does not
|
|
136
|
+
currently offer is absent from the tool list **and** refused if called by name.
|
|
137
|
+
|
|
82
138
|
The adapter holds one `AbortController` per registered tool and passes its
|
|
83
139
|
signal in the options bag, where `registerTool` takes it:
|
|
84
140
|
|
|
@@ -88,18 +144,20 @@ await document.modelContext.registerTool(tool, { signal: controller.signal })
|
|
|
88
144
|
|
|
89
145
|
Aborting that signal is what unregisters the tool, so it must not go on the
|
|
90
146
|
descriptor. This is the registration signal, distinct from the execution signal
|
|
91
|
-
passed to `execute`, which is forwarded as `Invocation.signal
|
|
147
|
+
passed to `execute`, which is forwarded as `Invocation.signal` so an agent can
|
|
148
|
+
cancel a call in flight.
|
|
92
149
|
|
|
93
150
|
Reconciles are serialized, and a capability is recorded as registered only once
|
|
94
|
-
`registerTool` resolves
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
151
|
+
`registerTool` resolves, so two overlapping reconciles cannot register one tool
|
|
152
|
+
twice. A registration the browser refuses is not recorded and is retried on the
|
|
153
|
+
next reconcile; `refresh()` rejects with the first failure after attempting the
|
|
154
|
+
rest. Nothing is registered once `unregister()` has run, and a registration that
|
|
155
|
+
was in flight when it ran is aborted.
|
|
98
156
|
|
|
99
157
|
## Errors
|
|
100
158
|
|
|
101
|
-
A dispatch failure
|
|
102
|
-
calling agent can
|
|
159
|
+
A dispatch failure comes back as a tool error rather than a rejection, so the
|
|
160
|
+
calling agent can read it and decide what to do:
|
|
103
161
|
|
|
104
162
|
```text
|
|
105
163
|
No such capability: delete_todo
|
|
@@ -110,11 +168,41 @@ Capability "create_todo" failed unexpectedly
|
|
|
110
168
|
```
|
|
111
169
|
|
|
112
170
|
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
|
|
171
|
+
`execute` never rejects, and never echoes the underlying error back to the
|
|
172
|
+
caller: the detail stays in the application, and the agent gets a message
|
|
173
|
+
written for it.
|
|
174
|
+
|
|
175
|
+
## Testing without a browser
|
|
176
|
+
|
|
177
|
+
`modelContext` is the only thing this package needs from the platform, so pass
|
|
178
|
+
your own object with a `registerTool` method to capture the descriptors and call
|
|
179
|
+
their `execute` directly. That is how this package's own suite and
|
|
180
|
+
[`examples/todo`](https://github.com/doeixd/foldkit-plus/tree/main/examples/todo)
|
|
181
|
+
exercise the real registration path with no browser involved.
|
|
182
|
+
|
|
183
|
+
## Choosing an adapter
|
|
184
|
+
|
|
185
|
+
| Where the agent runs | Adapter |
|
|
186
|
+
| --- | --- |
|
|
187
|
+
| In the page, beside the user | `foldkit-agent-webmcp` |
|
|
188
|
+
| An external MCP client, over stdio or HTTP | [`foldkit-agent-mcp`](https://github.com/doeixd/foldkit-plus/tree/main/packages/agent-mcp) |
|
|
189
|
+
| Another agent, over A2A | [`foldkit-agent-a2a`](https://github.com/doeixd/foldkit-plus/tree/main/packages/agent-a2a) |
|
|
190
|
+
| An Agent Native host | [`foldkit-agent-native`](https://github.com/doeixd/foldkit-plus/tree/main/packages/agent-native) |
|
|
191
|
+
|
|
192
|
+
They share one contract, so serving two at once is two calls, not two
|
|
193
|
+
definitions.
|
|
115
194
|
|
|
116
195
|
## Why this stays an adapter
|
|
117
196
|
|
|
118
|
-
WebMCP is experimental and
|
|
119
|
-
`src/webmcp.ts` rather than imported, so the browser API can
|
|
120
|
-
Foldkit agent contract
|
|
197
|
+
WebMCP is experimental and still changing. The types it needs are declared
|
|
198
|
+
locally in `src/webmcp.ts` rather than imported, so the browser API can move
|
|
199
|
+
without the Foldkit agent contract moving with it. If the proposal changes
|
|
200
|
+
shape, this package changes and nothing else does.
|
|
201
|
+
|
|
202
|
+
## See also
|
|
203
|
+
|
|
204
|
+
- [The agents guide](https://github.com/doeixd/foldkit-plus/blob/main/docs/agents.md)
|
|
205
|
+
— what an agent may see and do, and why a capability is a Message.
|
|
206
|
+
- [`foldkit-agent`](https://github.com/doeixd/foldkit-plus/tree/main/packages/agent)
|
|
207
|
+
— the contract this adapter serves.
|
|
208
|
+
- [WebMCP](https://github.com/webmachinelearning/webmcp) — the proposal.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "foldkit-agent-webmcp",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
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.
|
|
42
|
+
"foldkit-agent": "^0.2.1"
|
|
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.
|
|
50
|
+
"foldkit-agent": "0.2.1"
|
|
51
51
|
},
|
|
52
52
|
"scripts": {
|
|
53
53
|
"build": "tsdown",
|