@napster-corp/webmcp-toolkit 1.0.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 +21 -0
- package/README.md +531 -0
- package/bin/webmcp-toolkit.mjs +81 -0
- package/dist/debug.d.ts +5 -0
- package/dist/debug.d.ts.map +1 -0
- package/dist/debug.js +26 -0
- package/dist/debug.js.map +1 -0
- package/dist/dev-panel.d.ts +22 -0
- package/dist/dev-panel.d.ts.map +1 -0
- package/dist/dev-panel.js +1046 -0
- package/dist/dev-panel.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +36 -0
- package/dist/index.js.map +1 -0
- package/dist/model-context.d.ts +13 -0
- package/dist/model-context.d.ts.map +1 -0
- package/dist/model-context.js +28 -0
- package/dist/model-context.js.map +1 -0
- package/dist/resources.d.ts +15 -0
- package/dist/resources.d.ts.map +1 -0
- package/dist/resources.js +179 -0
- package/dist/resources.js.map +1 -0
- package/dist/tiers.d.ts +31 -0
- package/dist/tiers.d.ts.map +1 -0
- package/dist/tiers.js +107 -0
- package/dist/tiers.js.map +1 -0
- package/dist/types.d.ts +145 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +9 -0
- package/dist/types.js.map +1 -0
- package/hooks/post-commit +17 -0
- package/package.json +86 -0
- package/skills/add-edge-mcp-dev-panel/SKILL.md +206 -0
- package/skills/plan-capabilities-and-state/SKILL.md +168 -0
- package/skills/setup-edge-mcp/SKILL.md +546 -0
- package/skills/sync-webmcp-tools/SKILL.md +26 -0
- package/src/debug.ts +26 -0
- package/src/dev-panel.ts +1318 -0
- package/src/index.ts +66 -0
- package/src/model-context.ts +31 -0
- package/src/resources.ts +207 -0
- package/src/tiers.ts +132 -0
- package/src/types.ts +177 -0
- package/tools/generate-capabilities.mjs +266 -0
- package/tools/install-hook.mjs +81 -0
- package/tools/runners/anthropic.mjs +75 -0
- package/tools/runners/copilot.mjs +63 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Napster
|
|
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,531 @@
|
|
|
1
|
+
# @napster-corp/webmcp-toolkit
|
|
2
|
+
|
|
3
|
+
> Let an AI agent actually operate your web app — by exposing the app's real operations as tools the agent can call, on top of the WebMCP standard.
|
|
4
|
+
|
|
5
|
+
[](./LICENSE)
|
|
6
|
+
[](https://github.com/webmachinelearning/webmcp)
|
|
7
|
+
|
|
8
|
+
Most AI agents on websites today are observers. A voice-and-video assistant on
|
|
9
|
+
your homepage, or a chatbot in the corner, can answer questions about your
|
|
10
|
+
product, read off pricing, maybe file a support ticket. But when the user says
|
|
11
|
+
"add the 65-inch OLED to my cart and check out," the agent's answer is
|
|
12
|
+
something like "sure, click the Add to Cart button." It can't actually do it.
|
|
13
|
+
The user still drives the UI by hand.
|
|
14
|
+
|
|
15
|
+
WebMCP closes that gap. Your app declares which of its real operations an
|
|
16
|
+
agent is allowed to run — `products.search`, `cart.add`,
|
|
17
|
+
`checkout.placeOrder`, whatever you choose to expose — and any compatible
|
|
18
|
+
agent running in the same page can call them. The agent doesn't simulate your
|
|
19
|
+
app; it operates the app, using the same functions your buttons and forms
|
|
20
|
+
already call. The cart drawer slides open. The product page navigates. The
|
|
21
|
+
order gets placed.
|
|
22
|
+
|
|
23
|
+
This is **the Model Context Protocol, but for the browser.** The original MCP
|
|
24
|
+
exposes server-side tools to an AI client over a wire protocol; WebMCP exposes
|
|
25
|
+
the app's in-browser tools to whichever agent is running on the same page,
|
|
26
|
+
through `document.modelContext`. A compatible agent SDK finds the app's tools
|
|
27
|
+
at runtime and wires itself up — no glue code, nothing to dispatch by hand.
|
|
28
|
+
|
|
29
|
+
The **Napster WebMCP Toolkit** is a thin layer on top of that standard. It
|
|
30
|
+
**polyfills** WebMCP so `document.modelContext` exists in every browser, and it
|
|
31
|
+
**adds value** on top — safety tiers, live-state resources, and a dev panel —
|
|
32
|
+
without changing how you register tools. Your `registerTool` code is standard
|
|
33
|
+
WebMCP: remove the toolkit and it still works against the native browser API.
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
npm install @napster-corp/webmcp-toolkit
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## Why this exists
|
|
42
|
+
|
|
43
|
+
Even when teams try to make a website agent that *does* things instead of
|
|
44
|
+
just talking, the usual path is to build a second, parallel system next to
|
|
45
|
+
the app: copy your data into a vector store, hand-wrap your APIs as agent
|
|
46
|
+
tools, write a workflow mapping layer, and keep all of it in sync with the
|
|
47
|
+
real application every time it ships. The parallel system drifts. The agent
|
|
48
|
+
makes confident, wrong claims based on stale knowledge — and when it does
|
|
49
|
+
try to act, it acts on a copy of the app, not the app itself.
|
|
50
|
+
|
|
51
|
+
WebMCP inverts this. Instead of duplicating your app for the agent, you
|
|
52
|
+
point the agent **at** the live app. You expose an approved set of operations
|
|
53
|
+
the agent is allowed to invoke and an approved set of state slices the agent
|
|
54
|
+
is allowed to observe. The agent calls the same functions the UI calls; the
|
|
55
|
+
app stays the source of truth.
|
|
56
|
+
|
|
57
|
+
Two consequences worth stating up front:
|
|
58
|
+
|
|
59
|
+
- **The UI stays in sync automatically.** An agent's `cart.add` updates the
|
|
60
|
+
one cart store, and every component bound to it re-renders. You don't write
|
|
61
|
+
sync code.
|
|
62
|
+
- **The agent runs as the signed-in user.** The toolkit doesn't grant new
|
|
63
|
+
permissions; it picks a subset of the user's existing rights and makes them
|
|
64
|
+
callable through the agent. Auth, validation, and authorization stay
|
|
65
|
+
exactly where they already are.
|
|
66
|
+
|
|
67
|
+
And one that's specific to building on a standard:
|
|
68
|
+
|
|
69
|
+
- **Zero lock-in.** Tools are registered through the native
|
|
70
|
+
`document.modelContext.registerTool` API. If the customer removes this
|
|
71
|
+
toolkit, their tool code keeps working against the browser's own WebMCP
|
|
72
|
+
implementation — and any standards-compliant WebMCP agent can already drive
|
|
73
|
+
them. The toolkit's extras (tiers, resources, dev panel) layer on top
|
|
74
|
+
without owning the call site.
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## What importing the package does
|
|
79
|
+
|
|
80
|
+
Importing `@napster-corp/webmcp-toolkit` is a **browser-only side effect**. On
|
|
81
|
+
import it does two things:
|
|
82
|
+
|
|
83
|
+
1. **Polyfills WebMCP.** It initializes
|
|
84
|
+
[`@mcp-b/webmcp-polyfill`](https://www.npmjs.com/package/@mcp-b/webmcp-polyfill)
|
|
85
|
+
so `document.modelContext` exists cross-browser. When the browser already
|
|
86
|
+
supports WebMCP natively, this is a no-op.
|
|
87
|
+
2. **Installs the resource extension.** It adds an MCP-shaped live-state
|
|
88
|
+
"resource extension" onto `document.modelContext` (see
|
|
89
|
+
[Live state](#live-state--the-eyes)).
|
|
90
|
+
|
|
91
|
+
Outside a browser — SSR (Next.js / Nuxt / Remix / SvelteKit), web workers, edge
|
|
92
|
+
runtimes — importing the package **does nothing and touches no globals**. The
|
|
93
|
+
real surface comes up in the browser when the bundle hydrates. You don't need
|
|
94
|
+
to add any guards in your own code; the package handles it.
|
|
95
|
+
|
|
96
|
+
```ts
|
|
97
|
+
// src/webmcp/index.ts
|
|
98
|
+
import '@napster-corp/webmcp-toolkit'; // polyfill + resource extension (side effect)
|
|
99
|
+
import './tools'; // your registerTool / registerStatefulTool calls
|
|
100
|
+
import './resources'; // your registerResource calls
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## Quick example
|
|
106
|
+
|
|
107
|
+
The website developer writes **standard WebMCP tools** — there's no
|
|
108
|
+
Napster-specific call required to register a tool:
|
|
109
|
+
|
|
110
|
+
```ts
|
|
111
|
+
// src/webmcp/tools.ts
|
|
112
|
+
document.modelContext.registerTool({
|
|
113
|
+
name: 'cart.add',
|
|
114
|
+
description: 'Add a product to the cart.',
|
|
115
|
+
inputSchema: {
|
|
116
|
+
type: 'object',
|
|
117
|
+
properties: { productId: { type: 'string' } },
|
|
118
|
+
required: ['productId'],
|
|
119
|
+
},
|
|
120
|
+
annotations: { readOnlyHint: false }, // WebMCP defines only readOnlyHint + untrustedContentHint
|
|
121
|
+
async execute({ productId }) {
|
|
122
|
+
cartStore.add(productId);
|
|
123
|
+
return { content: [{ type: 'text', text: 'Added to cart' }] }; // standard MCP result shape
|
|
124
|
+
},
|
|
125
|
+
});
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
That's the entire integration for a plain tool. A WebMCP-aware agent SDK
|
|
129
|
+
detects `document.modelContext`, reads the tool list, and wires itself up — no
|
|
130
|
+
glue code, nothing to dispatch by hand.
|
|
131
|
+
|
|
132
|
+
> `cartStore` is a stand-in for your app's own code — the same functions your
|
|
133
|
+
> UI's buttons and forms already call. The toolkit doesn't replace any of
|
|
134
|
+
> them; it exposes the ones you choose to a connected agent.
|
|
135
|
+
|
|
136
|
+
See [`examples/app-side.ts`](./examples/app-side.ts) for the full app-side
|
|
137
|
+
pattern.
|
|
138
|
+
|
|
139
|
+
### The three value-adds, briefly
|
|
140
|
+
|
|
141
|
+
Everything Napster adds lives **off** the standard call site, so your base
|
|
142
|
+
tools stay portable:
|
|
143
|
+
|
|
144
|
+
| Add-on | Call | Why it's not in the standard |
|
|
145
|
+
| --- | --- | --- |
|
|
146
|
+
| Safety tiers | `registerStatefulTool({ ..., napsterTier })` | The polyfill drops custom annotation keys from `getTools()`, so the tier is recorded in a side registry |
|
|
147
|
+
| Live state | `registerResource({ uri, get, subscribe })` | WebMCP hasn't formalized resources yet; consumed over Napster's own path |
|
|
148
|
+
| Dev panel | `installDevPanel()` | Dev-only inspector, not part of the runtime contract |
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## Have a coding agent set it up for you
|
|
153
|
+
|
|
154
|
+
If you'd rather not work through tool and state design by hand, this repo ships
|
|
155
|
+
three composing skills that any Agent Skills-compatible tool (Claude Code,
|
|
156
|
+
Cursor, Codex, OpenCode, etc.) can load.
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
npx skills add napster-corp/webmcp-toolkit
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
The skills:
|
|
163
|
+
|
|
164
|
+
- **`setup-edge-mcp`** — the main entry point. Installs the package, registers
|
|
165
|
+
the agreed tools and resources against the app's real code, walks the
|
|
166
|
+
developer through sign-off.
|
|
167
|
+
- **`plan-capabilities-and-state`** — invoked by `setup-edge-mcp` as its first
|
|
168
|
+
step. Analyzes the codebase, proposes a curated starter plan (tools, live-state
|
|
169
|
+
resources, and deliberate withholds), walks the developer through it until
|
|
170
|
+
approved. No file output — the plan lives in the conversation, the code is the
|
|
171
|
+
record.
|
|
172
|
+
- **`add-edge-mcp-dev-panel`** *(opt-in)* — installs a small dev-only floating
|
|
173
|
+
panel for testing by hand: a form-based runner for every tool (fields rendered
|
|
174
|
+
from each tool's `inputSchema`), a live view of every resource, and an event
|
|
175
|
+
log. `setup-edge-mcp` offers it at the end; run it directly any time after
|
|
176
|
+
setup.
|
|
177
|
+
|
|
178
|
+
In your project, just say what you want in plain language — "set up WebMCP",
|
|
179
|
+
"agentify this app", "what should I expose to the agent?", "add a panel for
|
|
180
|
+
testing" — and the matching skill fires.
|
|
181
|
+
|
|
182
|
+
These skills stop once your WebMCP surface is built. Connecting an actual agent
|
|
183
|
+
(Napster's Omniagent, or any other WebMCP-compatible vendor) is a separate step
|
|
184
|
+
handled by that vendor's own SDK or skills. For the Napster Omniagent, install
|
|
185
|
+
`napster/omniagent-api-skills` alongside this one; its Web SDK auto-attaches to
|
|
186
|
+
`document.modelContext` at runtime.
|
|
187
|
+
|
|
188
|
+
---
|
|
189
|
+
|
|
190
|
+
## Core concepts
|
|
191
|
+
|
|
192
|
+
### Tools — the hands
|
|
193
|
+
|
|
194
|
+
What the agent can do. One tool per real operation you choose to expose. Name
|
|
195
|
+
them in your app's own domain terms (`products.search`, `cart.add`,
|
|
196
|
+
`orders.cancel`), not in agent-product terms.
|
|
197
|
+
|
|
198
|
+
`execute` calls your app's **real** code — the same function the UI's own
|
|
199
|
+
button or form submits to. Composing several real operations into one tool is
|
|
200
|
+
fine (and often necessary). What's forbidden is re-deriving business logic the
|
|
201
|
+
app already owns; if you find yourself recomputing a price the app already
|
|
202
|
+
calculates, stop.
|
|
203
|
+
|
|
204
|
+
Register with the standard `document.modelContext.registerTool(...)`. Reach for
|
|
205
|
+
`registerStatefulTool(...)` (below) when you want a safety tier recorded.
|
|
206
|
+
|
|
207
|
+
### Live state — the eyes
|
|
208
|
+
|
|
209
|
+
What the agent can perceive that it didn't get back from a tool. Resources are
|
|
210
|
+
the **exception, not the rule**. Most things don't need one — if a tool returns
|
|
211
|
+
its result, the agent already knows.
|
|
212
|
+
|
|
213
|
+
Add a resource only for state that changes **out of band**:
|
|
214
|
+
|
|
215
|
+
- The user edits something by hand (cart quantity, filter, navigation)
|
|
216
|
+
- The server changes state over time (an order moves from `processing` to
|
|
217
|
+
`shipped` mid-conversation)
|
|
218
|
+
|
|
219
|
+
Pure pull state — something you could just read on demand — is better modeled
|
|
220
|
+
as a read-only tool than as a resource. And if a tool already returns the
|
|
221
|
+
answer, do **not** add a resource that mirrors it. A search that returns its
|
|
222
|
+
results inline needs no `searchResults` resource; the agent has the data
|
|
223
|
+
already.
|
|
224
|
+
|
|
225
|
+
### Safety tiers
|
|
226
|
+
|
|
227
|
+
Tools you register with `registerStatefulTool` carry a `napsterTier`. Consumers
|
|
228
|
+
use it to gate confirmation flows.
|
|
229
|
+
|
|
230
|
+
| Tier | Example | Confirmation |
|
|
231
|
+
| --- | --- | --- |
|
|
232
|
+
| `read` | search, look up, compare | None — call freely |
|
|
233
|
+
| `reversible` | add to cart, save draft, apply filter | Brief announce |
|
|
234
|
+
| `irreversible` | place order, cancel, send | Explicit user confirmation, wait for consent |
|
|
235
|
+
|
|
236
|
+
Set the safer tier when in doubt. **The consumer (the Web SDK / agent) enforces
|
|
237
|
+
confirmation — never the model.** A plain `registerTool` tool with no recorded
|
|
238
|
+
tier is treated as `reversible`.
|
|
239
|
+
|
|
240
|
+
For irreversible tools, also set `idempotent: true` if your underlying
|
|
241
|
+
operation tolerates safe retries (e.g. via an idempotency key on the server).
|
|
242
|
+
|
|
243
|
+
---
|
|
244
|
+
|
|
245
|
+
## API
|
|
246
|
+
|
|
247
|
+
### `registerStatefulTool(tool)`
|
|
248
|
+
|
|
249
|
+
A thin wrapper over `document.modelContext.registerTool`. It registers a
|
|
250
|
+
**standard** WebMCP tool **and** records a safety tier in a name-keyed registry.
|
|
251
|
+
The side registry is necessary because the polyfill strips custom annotation
|
|
252
|
+
keys out of `getTools()`, so the tier wouldn't survive on the tool itself.
|
|
253
|
+
|
|
254
|
+
```ts
|
|
255
|
+
import { registerStatefulTool } from '@napster-corp/webmcp-toolkit';
|
|
256
|
+
|
|
257
|
+
const unregister = registerStatefulTool({
|
|
258
|
+
// ...all the same fields as registerTool (name, description, inputSchema, execute)...
|
|
259
|
+
name: 'checkout.placeOrder',
|
|
260
|
+
description: 'Submit the cart for purchase.',
|
|
261
|
+
inputSchema: {
|
|
262
|
+
type: 'object',
|
|
263
|
+
properties: {
|
|
264
|
+
paymentMethodId: { type: 'string' },
|
|
265
|
+
addressId: { type: 'string' },
|
|
266
|
+
},
|
|
267
|
+
required: ['paymentMethodId', 'addressId'],
|
|
268
|
+
},
|
|
269
|
+
napsterTier: 'irreversible', // 'read' | 'reversible' | 'irreversible'
|
|
270
|
+
idempotent: true, // optional
|
|
271
|
+
untrustedContent: false, // optional
|
|
272
|
+
async execute({ paymentMethodId, addressId }) {
|
|
273
|
+
const order = await placeOrder(paymentMethodId, addressId);
|
|
274
|
+
return { content: [{ type: 'text', text: `Order ${order.id} placed` }] };
|
|
275
|
+
},
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
// later: unregister();
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
**Extra fields (beyond `registerTool`):**
|
|
282
|
+
|
|
283
|
+
| Field | Purpose |
|
|
284
|
+
| --- | --- |
|
|
285
|
+
| `napsterTier` | `'read'` \| `'reversible'` \| `'irreversible'` — the confirmation tier |
|
|
286
|
+
| `idempotent?` | mark an irreversible op as safe to retry |
|
|
287
|
+
| `untrustedContent?` | the tool returns content that may carry injected instructions |
|
|
288
|
+
|
|
289
|
+
The tier maps to the standard WebMCP annotations the agent reads off the tool:
|
|
290
|
+
`read` → `readOnlyHint: true`, and `untrustedContent: true` →
|
|
291
|
+
`untrustedContentHint: true`.
|
|
292
|
+
|
|
293
|
+
**Returns** an `unregister()` function.
|
|
294
|
+
|
|
295
|
+
### `getTier(name)` / `getTierMap()`
|
|
296
|
+
|
|
297
|
+
Read back the recorded tiers (consumer-facing).
|
|
298
|
+
|
|
299
|
+
```ts
|
|
300
|
+
import { getTier, getTierMap } from '@napster-corp/webmcp-toolkit';
|
|
301
|
+
|
|
302
|
+
getTier('checkout.placeOrder'); // 'irreversible'
|
|
303
|
+
getTierMap(); // { 'cart.add': 'reversible', 'checkout.placeOrder': 'irreversible', ... }
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
A tool with no recorded tier is treated as `reversible`.
|
|
307
|
+
|
|
308
|
+
### `registerResource(resource)`
|
|
309
|
+
|
|
310
|
+
Register a live-state resource, modeled on MCP resources. Use it only for
|
|
311
|
+
[out-of-band state](#live-state--the-eyes).
|
|
312
|
+
|
|
313
|
+
```ts
|
|
314
|
+
import { registerResource } from '@napster-corp/webmcp-toolkit';
|
|
315
|
+
|
|
316
|
+
registerResource({
|
|
317
|
+
uri: 'state://cart',
|
|
318
|
+
name: 'cart',
|
|
319
|
+
description: 'The current shopping cart', // optional
|
|
320
|
+
mimeType: 'application/json', // optional
|
|
321
|
+
get: () => cartStore.getCurrent(),
|
|
322
|
+
subscribe: (onChange) => cartStore.subscribe(onChange), // optional; return an unsubscribe fn
|
|
323
|
+
});
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
The consumer-side surface lives on `document.modelContext` (installed by the
|
|
327
|
+
import side effect):
|
|
328
|
+
|
|
329
|
+
| Member | Purpose |
|
|
330
|
+
| --- | --- |
|
|
331
|
+
| `getResources()` | List registered resources |
|
|
332
|
+
| `readResource(uri)` | Read the current value of one resource |
|
|
333
|
+
| `subscribeResource(uri, handler)` | Subscribe to changes for one resource |
|
|
334
|
+
| `resourceupdated` event | `CustomEvent` with `detail = { uri, value }` |
|
|
335
|
+
| `resourcelistchanged` event | Fired when the resource list changes |
|
|
336
|
+
|
|
337
|
+
> This resource channel is consumed by the Napster agent over its own path. It
|
|
338
|
+
> is **not interoperable** with third-party WebMCP agents until the standard
|
|
339
|
+
> formalizes resources. The standard tool surface, by contrast, is fully
|
|
340
|
+
> interoperable today.
|
|
341
|
+
|
|
342
|
+
### `installDevPanel(options?)`
|
|
343
|
+
|
|
344
|
+
A dev-only in-page inspector. Imported from a subpath so it never ships in your
|
|
345
|
+
production bundle if you don't reference it.
|
|
346
|
+
|
|
347
|
+
```ts
|
|
348
|
+
import { installDevPanel } from '@napster-corp/webmcp-toolkit/dev-panel';
|
|
349
|
+
|
|
350
|
+
const uninstall = installDevPanel({ shortcut: 'cmd+shift+e', startOpen: false });
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
It reads `document.modelContext` directly — **it takes no instance argument**.
|
|
354
|
+
Toggle with **Cmd+Shift+E** (**Ctrl+Shift+E** on non-Mac).
|
|
355
|
+
|
|
356
|
+
### Adapter exports
|
|
357
|
+
|
|
358
|
+
The single swap-point over the polyfill, for code that wants the model-context
|
|
359
|
+
object directly instead of reaching for the global:
|
|
360
|
+
|
|
361
|
+
| Export | Returns |
|
|
362
|
+
| --- | --- |
|
|
363
|
+
| `getModelContext()` | `document.modelContext` (with a deprecated `navigator.modelContext` fallback) |
|
|
364
|
+
| `getModelContextWithResources()` | the same object, with the resource extension surface |
|
|
365
|
+
| `isBrowserEnvironment()` | `true` only in a real browser |
|
|
366
|
+
|
|
367
|
+
---
|
|
368
|
+
|
|
369
|
+
## How an agent discovers your app
|
|
370
|
+
|
|
371
|
+
A WebMCP agent — or the Napster Companion Web SDK — attaches with no
|
|
372
|
+
coordination from you:
|
|
373
|
+
|
|
374
|
+
1. **Detect** by the presence of `document.modelContext`.
|
|
375
|
+
2. **Read tools** via `getTools()`.
|
|
376
|
+
3. **Invoke** via `executeTool(toolInfo, JSON.stringify(args))`.
|
|
377
|
+
4. **Observe tool changes** via the `toolchange` event — a bare `Event` with no
|
|
378
|
+
`detail`; re-read `getTools()` when it fires.
|
|
379
|
+
5. **Relay live state** via `subscribeResource` / the `resourceupdated` event.
|
|
380
|
+
|
|
381
|
+
Because every one of those steps is standard, **the agent attaches to any
|
|
382
|
+
WebMCP-enabled site** — even one that never installed this toolkit. The
|
|
383
|
+
Napster extras (safety tiers, live-state resources) simply light up when the
|
|
384
|
+
toolkit is present and are absent otherwise; nothing breaks either way.
|
|
385
|
+
|
|
386
|
+
---
|
|
387
|
+
|
|
388
|
+
## Hosting
|
|
389
|
+
|
|
390
|
+
The toolkit is browser-only and runs in the same module graph as your UI. A few
|
|
391
|
+
notes worth knowing up front:
|
|
392
|
+
|
|
393
|
+
- **Recommended file layout.** Keep your WebMCP wiring in a `src/webmcp/`
|
|
394
|
+
folder: `index.ts` imports the toolkit and wires everything up, `tools.ts`
|
|
395
|
+
holds your `registerTool` / `registerStatefulTool` calls, and `resources.ts`
|
|
396
|
+
holds your `registerResource` calls.
|
|
397
|
+
- **Imperative actions from outside the component tree.** A tool's `execute`
|
|
398
|
+
lives in a plain module. Things like navigation often only exist inside the
|
|
399
|
+
framework (e.g. React Router's `useNavigate` hook). Register a module-level
|
|
400
|
+
handle from an in-tree component at mount and have `execute` call through
|
|
401
|
+
that; otherwise it has no way to drive navigation.
|
|
402
|
+
- **Server-side rendering is a no-op.** Outside the browser (SSR, workers, edge
|
|
403
|
+
runtimes), importing the package does nothing and touches no globals. This is
|
|
404
|
+
deliberate: the toolkit connects an in-browser agent to in-browser state;
|
|
405
|
+
running it on the server would bleed per-user state through the Node
|
|
406
|
+
process's shared globals. The real surface comes up in the browser when the
|
|
407
|
+
bundle hydrates.
|
|
408
|
+
|
|
409
|
+
---
|
|
410
|
+
|
|
411
|
+
## Automation — keep your tools file in sync
|
|
412
|
+
|
|
413
|
+
Your tool list is hand-curated, but it drifts as the app changes — a route gets
|
|
414
|
+
renamed, an operation is removed, a new one becomes worth exposing. The package
|
|
415
|
+
ships an **opt-in agent** that re-analyzes the app and rewrites your tools file
|
|
416
|
+
(e.g. `src/webmcp/tools.ts`) to match the current code — runnable from a
|
|
417
|
+
post-commit hook locally, or from CI on a pull request. It produces
|
|
418
|
+
**uncommitted** changes you review.
|
|
419
|
+
|
|
420
|
+
It uses this package's own `plan-capabilities-and-state` / `setup-edge-mcp`
|
|
421
|
+
skills as the methodology, and by default runs on the **Claude Agent SDK**
|
|
422
|
+
(`@anthropic-ai/claude-agent-sdk`, Claude Opus 4.8).
|
|
423
|
+
|
|
424
|
+
### Setup (in the host app)
|
|
425
|
+
|
|
426
|
+
```bash
|
|
427
|
+
npm install @napster-corp/webmcp-toolkit
|
|
428
|
+
npm install -D @anthropic-ai/claude-agent-sdk # the default engine's runtime
|
|
429
|
+
npx webmcp-toolkit install-hook # installs the opt-in post-commit hook
|
|
430
|
+
export ANTHROPIC_API_KEY=... # local: your key; CI: a secret
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
Put your engine's key where the CLI can read it — exported, in CI as a secret,
|
|
434
|
+
or in the app's gitignored `.env.local`.
|
|
435
|
+
|
|
436
|
+
### How it triggers — opt-in marker
|
|
437
|
+
|
|
438
|
+
The hook runs **only** when a commit message contains the marker `[webmcp-toolkit]`:
|
|
439
|
+
|
|
440
|
+
```bash
|
|
441
|
+
git commit -m "feat(cart): add bulk remove [webmcp-toolkit]"
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
Every other commit is untouched — no agent run, no token cost. When the marker
|
|
445
|
+
is present, the agent analyzes the app and leaves **uncommitted** changes to
|
|
446
|
+
your tools file — a post-commit hook can't amend the commit, so you review the
|
|
447
|
+
diff and commit it separately:
|
|
448
|
+
|
|
449
|
+
```
|
|
450
|
+
✎ src/webmcp/tools.ts (unstaged)
|
|
451
|
+
+ cart.bulkRemove (reversible) — src/store/cart.ts:bulkRemove
|
|
452
|
+
~ checkout.placeOrder (signature changed) — src/api/checkout.ts:placeOrder
|
|
453
|
+
→ review & commit when ready
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
### Run it manually / in CI
|
|
457
|
+
|
|
458
|
+
`webmcp-toolkit generate` is the same command the hook calls — run it anywhere:
|
|
459
|
+
|
|
460
|
+
```bash
|
|
461
|
+
npx webmcp-toolkit generate # regenerate now, against the current working tree
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
On CI (e.g. a GitHub Action on `pull_request`), set the engine's key as a
|
|
465
|
+
secret and run `npx webmcp-toolkit generate`, then open/update a PR with the result.
|
|
466
|
+
It auto-detects the tools file (`src/webmcp/`, `lib/webmcp/`, …); override with
|
|
467
|
+
`--file path` if needed.
|
|
468
|
+
|
|
469
|
+
### What the agent may touch
|
|
470
|
+
|
|
471
|
+
Locked down by design: the agent is restricted to `Read` / `Grep` / `Glob` /
|
|
472
|
+
`Edit` / `Write`, with no shell — it reads the app to find real operations and
|
|
473
|
+
edits **only** your tools file. It cannot run commands, install packages, or
|
|
474
|
+
touch git.
|
|
475
|
+
|
|
476
|
+
### Engines
|
|
477
|
+
|
|
478
|
+
Pluggable — pick with `--engine` or `WEBMCP_TOOLKIT_ENGINE`:
|
|
479
|
+
|
|
480
|
+
| Engine | Default | Needs | Notes |
|
|
481
|
+
| --- | --- | --- | --- |
|
|
482
|
+
| `anthropic` | ✅ | `ANTHROPIC_API_KEY` + `@anthropic-ai/claude-agent-sdk` | Claude Agent SDK (Opus 4.8); restricted to read/edit tools (no shell) |
|
|
483
|
+
| `copilot` | | the GitHub **Copilot CLI** + a Copilot subscription | Shells out to the CLI; no Anthropic key |
|
|
484
|
+
|
|
485
|
+
```bash
|
|
486
|
+
WEBMCP_TOOLKIT_ENGINE=copilot npx webmcp-toolkit generate # or: npx webmcp-toolkit generate --engine copilot
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
The skills, prompt, path detection, opt-in marker, and uncommitted-output
|
|
490
|
+
policy are identical across engines — only the agent runtime changes.
|
|
491
|
+
|
|
492
|
+
> The Copilot CLI's non-interactive flags evolve, so the exact invocation is
|
|
493
|
+
> env-overridable rather than hardcoded — set `WEBMCP_TOOLKIT_COPILOT_BIN` and
|
|
494
|
+
> `WEBMCP_TOOLKIT_COPILOT_ARGS` to match your installed `copilot --help`.
|
|
495
|
+
|
|
496
|
+
### Commands
|
|
497
|
+
|
|
498
|
+
| Command | What it does |
|
|
499
|
+
| --- | --- |
|
|
500
|
+
| `webmcp-toolkit install-hook` | Install/refresh the opt-in `[webmcp-toolkit]` post-commit hook (idempotent; composes with an existing hook) |
|
|
501
|
+
| `webmcp-toolkit generate [--engine anthropic\|copilot]` | Analyze the app and rewrite your tools file (local or CI) |
|
|
502
|
+
|
|
503
|
+
---
|
|
504
|
+
|
|
505
|
+
## Migrating from `@napster-corp/edge-mcp`
|
|
506
|
+
|
|
507
|
+
This package was re-based from the proprietary "Edge MCP" contract onto the
|
|
508
|
+
**WebMCP standard**. If you're coming from the old package:
|
|
509
|
+
|
|
510
|
+
- There is no more `createEdgeMcp()` instance, and no well-known global slot.
|
|
511
|
+
Register tools directly on `document.modelContext` instead.
|
|
512
|
+
- `registerCapability({ ..., sideEffect, run })` →
|
|
513
|
+
`document.modelContext.registerTool({ ..., execute })`, or
|
|
514
|
+
`registerStatefulTool({ ..., napsterTier, execute })` when you want a tier.
|
|
515
|
+
The `sideEffect` field becomes `napsterTier`; `run` becomes `execute` and
|
|
516
|
+
returns the standard `{ content: [...] }` shape.
|
|
517
|
+
- `registerStateProvider(...)` → `registerResource({ uri, get, subscribe })`.
|
|
518
|
+
Read it consumer-side with `getResources()` / `readResource()` /
|
|
519
|
+
`subscribeResource()` and the `resourceupdated` event.
|
|
520
|
+
- The consumer API (`listCapabilities()`, `invoke()`, `snapshot()`,
|
|
521
|
+
`getState()`, `onStateChange()`), the discovery helpers (`publishEdgeMcp`,
|
|
522
|
+
`whenEdgeMcpReady`, …), and the `EDGE_MCP_KEY` symbol are gone — discovery is
|
|
523
|
+
now standard WebMCP (`getTools()` / `executeTool()` / the `toolchange` event).
|
|
524
|
+
- `installDevPanel` no longer takes an instance argument — it reads
|
|
525
|
+
`document.modelContext`.
|
|
526
|
+
|
|
527
|
+
---
|
|
528
|
+
|
|
529
|
+
## License
|
|
530
|
+
|
|
531
|
+
[MIT](./LICENSE).
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// webmcp-toolkit CLI — tooling for keeping a host app's WebMCP tools in sync
|
|
3
|
+
// with its code via an autonomous Claude agent.
|
|
4
|
+
//
|
|
5
|
+
// webmcp-toolkit install-hook Install the opt-in post-commit hook into this repo.
|
|
6
|
+
// webmcp-toolkit generate Analyze the app and (re)write the WebMCP tools file (src/webmcp/tools.ts).
|
|
7
|
+
//
|
|
8
|
+
// `generate` is also what CI calls on a PR — it's the same command, just run on
|
|
9
|
+
// a server with ANTHROPIC_API_KEY provided as a secret.
|
|
10
|
+
|
|
11
|
+
import { generateCapabilities } from "../tools/generate-capabilities.mjs";
|
|
12
|
+
import { installHook } from "../tools/install-hook.mjs";
|
|
13
|
+
|
|
14
|
+
const HELP = `webmcp-toolkit — keep WebMCP tools in sync with your code
|
|
15
|
+
|
|
16
|
+
Usage:
|
|
17
|
+
webmcp-toolkit install-hook [dir] Install the opt-in [webmcp-toolkit] post-commit hook
|
|
18
|
+
webmcp-toolkit generate [dir] [--engine x] [--file p] Regenerate the tools file now
|
|
19
|
+
webmcp-toolkit --help
|
|
20
|
+
|
|
21
|
+
[dir] target app directory (default: current directory)
|
|
22
|
+
--engine NAME agent engine: anthropic (default) | copilot
|
|
23
|
+
(or set WEBMCP_TOOLKIT_ENGINE)
|
|
24
|
+
--file PATH tools file to write (default: auto-detected)
|
|
25
|
+
|
|
26
|
+
Notes:
|
|
27
|
+
- anthropic engine needs ANTHROPIC_API_KEY + @anthropic-ai/claude-agent-sdk.
|
|
28
|
+
- copilot engine needs the GitHub Copilot CLI + a Copilot subscription.
|
|
29
|
+
- generate writes UNCOMMITTED changes for you to review.
|
|
30
|
+
- the hook only runs generate when a commit message contains [webmcp-toolkit].
|
|
31
|
+
`;
|
|
32
|
+
|
|
33
|
+
function parseArgs(argv) {
|
|
34
|
+
const out = { _: [] };
|
|
35
|
+
for (let i = 0; i < argv.length; i++) {
|
|
36
|
+
const a = argv[i];
|
|
37
|
+
if (a === "--file") out.file = argv[++i];
|
|
38
|
+
else if (a.startsWith("--file=")) out.file = a.slice("--file=".length);
|
|
39
|
+
else if (a === "--engine") out.engine = argv[++i];
|
|
40
|
+
else if (a.startsWith("--engine=")) out.engine = a.slice("--engine=".length);
|
|
41
|
+
else out._.push(a);
|
|
42
|
+
}
|
|
43
|
+
return out;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async function main() {
|
|
47
|
+
const cmd = process.argv[2];
|
|
48
|
+
const args = parseArgs(process.argv.slice(3));
|
|
49
|
+
const targetDir = args._[0]; // optional positional dir; undefined → cwd
|
|
50
|
+
try {
|
|
51
|
+
switch (cmd) {
|
|
52
|
+
case "install-hook": {
|
|
53
|
+
const hookPath = await installHook({ targetDir });
|
|
54
|
+
console.log(`[webmcp-toolkit] installed post-commit hook → ${hookPath}`);
|
|
55
|
+
console.log(
|
|
56
|
+
"[webmcp-toolkit] add `[webmcp-toolkit]` to a commit message to trigger regeneration.",
|
|
57
|
+
);
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
case "generate": {
|
|
61
|
+
console.log("[webmcp-toolkit] analyzing app and regenerating tools…\n");
|
|
62
|
+
await generateCapabilities({ targetDir, file: args.file, engine: args.engine });
|
|
63
|
+
break;
|
|
64
|
+
}
|
|
65
|
+
case "--help":
|
|
66
|
+
case "-h":
|
|
67
|
+
case undefined:
|
|
68
|
+
console.log(HELP);
|
|
69
|
+
break;
|
|
70
|
+
default:
|
|
71
|
+
console.error(`[webmcp-toolkit] unknown command: ${cmd}\n`);
|
|
72
|
+
console.log(HELP);
|
|
73
|
+
process.exit(1);
|
|
74
|
+
}
|
|
75
|
+
} catch (err) {
|
|
76
|
+
console.error(`[webmcp-toolkit] ${err instanceof Error ? err.message : String(err)}`);
|
|
77
|
+
process.exit(1);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
main();
|
package/dist/debug.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** Enable or disable the toolkit's `[webmcp-toolkit]` flow logs. */
|
|
2
|
+
export declare function setDebug(on: boolean): void;
|
|
3
|
+
/** Log a flow event when debug is on. No-op otherwise. */
|
|
4
|
+
export declare function debugLog(...args: unknown[]): void;
|
|
5
|
+
//# sourceMappingURL=debug.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"debug.d.ts","sourceRoot":"","sources":["../src/debug.ts"],"names":[],"mappings":"AAMA,oEAAoE;AACpE,wBAAgB,QAAQ,CAAC,EAAE,EAAE,OAAO,GAAG,IAAI,CAE1C;AAWD,0DAA0D;AAC1D,wBAAgB,QAAQ,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAIjD"}
|
package/dist/debug.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// Opt-in flow logging for the toolkit. OFF by default so the published package
|
|
2
|
+
// is quiet in production. Turn it on either from code — `setDebug(true)` — or at
|
|
3
|
+
// runtime from the browser console: `globalThis.__WEBMCP_DEBUG__ = true`.
|
|
4
|
+
let enabled = false;
|
|
5
|
+
/** Enable or disable the toolkit's `[webmcp-toolkit]` flow logs. */
|
|
6
|
+
export function setDebug(on) {
|
|
7
|
+
enabled = on;
|
|
8
|
+
}
|
|
9
|
+
function isOn() {
|
|
10
|
+
if (enabled)
|
|
11
|
+
return true;
|
|
12
|
+
try {
|
|
13
|
+
return Boolean(globalThis.__WEBMCP_DEBUG__);
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
/** Log a flow event when debug is on. No-op otherwise. */
|
|
20
|
+
export function debugLog(...args) {
|
|
21
|
+
if (!isOn())
|
|
22
|
+
return;
|
|
23
|
+
// eslint-disable-next-line no-console
|
|
24
|
+
console.info('[webmcp-toolkit]', ...args);
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=debug.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"debug.js","sourceRoot":"","sources":["../src/debug.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,iFAAiF;AACjF,0EAA0E;AAE1E,IAAI,OAAO,GAAG,KAAK,CAAC;AAEpB,oEAAoE;AACpE,MAAM,UAAU,QAAQ,CAAC,EAAW;IAClC,OAAO,GAAG,EAAE,CAAC;AACf,CAAC;AAED,SAAS,IAAI;IACX,IAAI,OAAO;QAAE,OAAO,IAAI,CAAC;IACzB,IAAI,CAAC;QACH,OAAO,OAAO,CAAE,UAA6C,CAAC,gBAAgB,CAAC,CAAC;IAClF,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,0DAA0D;AAC1D,MAAM,UAAU,QAAQ,CAAC,GAAG,IAAe;IACzC,IAAI,CAAC,IAAI,EAAE;QAAE,OAAO;IACpB,sCAAsC;IACtC,OAAO,CAAC,IAAI,CAAC,kBAAkB,EAAE,GAAG,IAAI,CAAC,CAAC;AAC5C,CAAC"}
|