@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.
Files changed (48) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +531 -0
  3. package/bin/webmcp-toolkit.mjs +81 -0
  4. package/dist/debug.d.ts +5 -0
  5. package/dist/debug.d.ts.map +1 -0
  6. package/dist/debug.js +26 -0
  7. package/dist/debug.js.map +1 -0
  8. package/dist/dev-panel.d.ts +22 -0
  9. package/dist/dev-panel.d.ts.map +1 -0
  10. package/dist/dev-panel.js +1046 -0
  11. package/dist/dev-panel.js.map +1 -0
  12. package/dist/index.d.ts +6 -0
  13. package/dist/index.d.ts.map +1 -0
  14. package/dist/index.js +36 -0
  15. package/dist/index.js.map +1 -0
  16. package/dist/model-context.d.ts +13 -0
  17. package/dist/model-context.d.ts.map +1 -0
  18. package/dist/model-context.js +28 -0
  19. package/dist/model-context.js.map +1 -0
  20. package/dist/resources.d.ts +15 -0
  21. package/dist/resources.d.ts.map +1 -0
  22. package/dist/resources.js +179 -0
  23. package/dist/resources.js.map +1 -0
  24. package/dist/tiers.d.ts +31 -0
  25. package/dist/tiers.d.ts.map +1 -0
  26. package/dist/tiers.js +107 -0
  27. package/dist/tiers.js.map +1 -0
  28. package/dist/types.d.ts +145 -0
  29. package/dist/types.d.ts.map +1 -0
  30. package/dist/types.js +9 -0
  31. package/dist/types.js.map +1 -0
  32. package/hooks/post-commit +17 -0
  33. package/package.json +86 -0
  34. package/skills/add-edge-mcp-dev-panel/SKILL.md +206 -0
  35. package/skills/plan-capabilities-and-state/SKILL.md +168 -0
  36. package/skills/setup-edge-mcp/SKILL.md +546 -0
  37. package/skills/sync-webmcp-tools/SKILL.md +26 -0
  38. package/src/debug.ts +26 -0
  39. package/src/dev-panel.ts +1318 -0
  40. package/src/index.ts +66 -0
  41. package/src/model-context.ts +31 -0
  42. package/src/resources.ts +207 -0
  43. package/src/tiers.ts +132 -0
  44. package/src/types.ts +177 -0
  45. package/tools/generate-capabilities.mjs +266 -0
  46. package/tools/install-hook.mjs +81 -0
  47. package/tools/runners/anthropic.mjs +75 -0
  48. 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: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE)
6
+ [![standard: WebMCP](https://img.shields.io/badge/standard-WebMCP-purple.svg)](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();
@@ -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"}