simple-webmcp 0.1.0 → 0.2.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 (49) hide show
  1. package/.agents/skills/webmcp-simple/SKILL.md +27 -12
  2. package/README.md +194 -72
  3. package/dist/dev-polyfill.cjs +68 -0
  4. package/dist/dev-polyfill.cjs.map +1 -0
  5. package/dist/dev-polyfill.d.cts +1 -0
  6. package/dist/dev-polyfill.d.ts +1 -0
  7. package/dist/dev-polyfill.js +62 -0
  8. package/dist/dev-polyfill.js.map +1 -0
  9. package/dist/devtools.cjs +452 -0
  10. package/dist/devtools.cjs.map +1 -0
  11. package/dist/devtools.d.cts +14 -0
  12. package/dist/devtools.d.ts +14 -0
  13. package/dist/devtools.js +449 -0
  14. package/dist/devtools.js.map +1 -0
  15. package/dist/index.cjs +37 -37
  16. package/dist/index.cjs.map +1 -1
  17. package/dist/index.d.cts +4 -21
  18. package/dist/index.d.ts +4 -21
  19. package/dist/index.js +37 -37
  20. package/dist/index.js.map +1 -1
  21. package/dist/inspect.cjs +342 -0
  22. package/dist/inspect.cjs.map +1 -0
  23. package/dist/inspect.d.cts +37 -0
  24. package/dist/inspect.d.ts +37 -0
  25. package/dist/inspect.js +334 -0
  26. package/dist/inspect.js.map +1 -0
  27. package/dist/polyfill.cjs.map +1 -1
  28. package/dist/polyfill.d.cts +15 -7
  29. package/dist/polyfill.d.ts +15 -7
  30. package/dist/polyfill.js.map +1 -1
  31. package/dist/react.cjs +850 -12
  32. package/dist/react.cjs.map +1 -1
  33. package/dist/react.d.cts +7 -10
  34. package/dist/react.d.ts +7 -10
  35. package/dist/react.js +851 -14
  36. package/dist/react.js.map +1 -1
  37. package/dist/registry-BcPw1qCf.d.cts +38 -0
  38. package/dist/registry-C6Pz_Rja.d.ts +38 -0
  39. package/dist/testing.cjs +68 -0
  40. package/dist/testing.cjs.map +1 -0
  41. package/dist/testing.d.cts +1 -0
  42. package/dist/testing.d.ts +1 -0
  43. package/dist/testing.js +62 -0
  44. package/dist/testing.js.map +1 -0
  45. package/dist/{types-D-cwSfEU.d.cts → types-DH78HDAE.d.cts} +2 -2
  46. package/dist/{types-D-cwSfEU.d.ts → types-DH78HDAE.d.ts} +2 -2
  47. package/dist/zod.d.cts +1 -1
  48. package/dist/zod.d.ts +1 -1
  49. package/package.json +30 -1
@@ -42,18 +42,27 @@ searchTool.unregister();
42
42
  await searchTool({ query: 'alice' });
43
43
  ```
44
44
 
45
- ### 2. React — component lifecycle
45
+ ### 2. React — component lifecycle (1-line optional)
46
46
 
47
47
  ```tsx
48
48
  'use client';
49
- import { webmcp } from 'simple-webmcp';
50
49
  import { useWebMCP, Scope } from 'simple-webmcp/react';
50
+ import { useTool } from 'simple-webmcp/react'; // alias
51
51
 
52
- const addToCartTool = webmcp(addToCart, { description: 'Add product to cart' });
53
-
52
+ // 1-line: wrap + register while mounted recommended
54
53
  export function ProductPage() {
55
- // exposed only while component mounted maps to AbortSignal
56
- useWebMCP(addToCartTool);
54
+ const addToCartTool = useWebMCP(addToCart, { description: 'Add product to cart' });
55
+ // or: const tool = useTool(addToCart, { description: '...' });
56
+ // addToCartTool({productId:'p1', quantity:1}) still callable
57
+ // addToCartTool.registered, addToCartTool.status also available
58
+ return <div>...</div>;
59
+ }
60
+
61
+ // verbose 2-line still works:
62
+ import { webmcp } from 'simple-webmcp';
63
+ const addToCartTool2 = webmcp(addToCart, { description: 'Add product to cart' });
64
+ export function ProductPage2() {
65
+ useWebMCP(addToCartTool2);
57
66
  return <div>...</div>;
58
67
  }
59
68
 
@@ -86,10 +95,16 @@ In all cases `schema` (whole) establishes contract; `fields` patches it (adds de
86
95
 
87
96
  ### 4. Polyfill (non-Chrome)
88
97
 
98
+ For **production cross-browser** (Firefox/Safari), use the dedicated polyfill:
99
+
100
+ ```ts
101
+ import '@mcp-b/webmcp-polyfill'; // ~6k weekly, real transport
102
+ ```
103
+
104
+ For **dev/testing** (Storybook, vitest, local without Chrome), `simple-webmcp` provides a thin in-memory shim (not full MCP):
105
+
89
106
  ```ts
90
- // before importing tools — no-op in Chrome with native WebMCP
91
- import 'simple-webmcp/polyfill';
92
- // or programmatic: import { installPolyfill } from 'simple-webmcp/polyfill'; installPolyfill();
107
+ import 'simple-webmcp/dev-polyfill'; // or 'simple-webmcp/testing' or legacy 'simple-webmcp/polyfill'
93
108
  ```
94
109
 
95
110
  ### 5. Global vs Scoped
@@ -105,11 +120,11 @@ webmcp.global(fn, { description: '…' }); // alias
105
120
 
106
121
  ## Progression Ladder (teach stepwise)
107
122
 
108
- 1. **Beginner:** `webmcp(fn)` done.
109
- 2. **Better desc:** `webmcp(fn, { description: '…' })`
123
+ 1. **Beginner:** `webmcp(fn)` done (vanilla) or `useWebMCP(fn,{description})` / `useTool(fn)` in React — 1 line.
124
+ 2. **Better desc:** `webmcp(fn, { description: '…' })` or `useTool(fn,{description})`
110
125
  3. **Field docs:** `webmcp(fn, { fields: { query: { description: '…' } } })`
111
126
  4. **Full control:** `webmcp(fn, { schema: z.object({…}) })`
112
- 5. **Lifecycle:** `useWebMCP(tool)` / `<Scope tools>`
127
+ 5. **Lifecycle:** `useWebMCP(tool)` / `useTool(fn, opts)` / `<Scope tools>`
113
128
 
114
129
  ## Rules
115
130
 
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
- # simple-webmcp
1
+ # Make your existing functions agent-ready
2
2
 
3
- > Turn any JS/TS function into a WebMCP tool `webmcp(fn)` stays callable, auto-registers with React lifecycle or globally. Minimal, typed, framework-agnostic.
3
+ `simple-webmcp` turns ordinary JavaScript and TypeScript functions into WebMCP tools without creating a second tool layer.
4
4
 
5
5
  ```ts
6
6
  import { webmcp } from 'simple-webmcp';
@@ -9,128 +9,250 @@ async function searchCustomers({ query, limit = 20 }: { query: string; limit?: n
9
9
  return customers.filter(c => c.name.includes(query)).slice(0, limit);
10
10
  }
11
11
 
12
- // One line — same function, now also a tool
13
- export const searchTool = webmcp(searchCustomers, {
14
- description: 'Search customers in current account',
15
- fields: { query: { description: 'Name, email, or ID' } },
12
+ const search = webmcp(searchCustomers);
13
+ ```
14
+
15
+ That's it. Your function stays callable:
16
+
17
+ ```ts
18
+ await search({ query: 'alice' });
19
+ ```
20
+
21
+ And can be exposed to WebMCP:
22
+
23
+ ```ts
24
+ import { useWebMCP } from 'simple-webmcp/react';
25
+
26
+ function CustomersPage() {
27
+ const tool = useWebMCP(search, { description: 'Search customers' });
28
+ // visible while mounted — unregisters on unmount (AbortSignal)
29
+ return <CustomersUI />;
30
+ }
31
+ ```
32
+
33
+ **Framework-agnostic core. React adapter included. More adapters coming.** Works with vanilla JS, TypeScript, Vite — Next.js `server()` is experimental until the `fixtures/next-app` spike proves the Server Action bridge.
34
+
35
+ > **One function. Two interfaces.** Human code `search(input)` and agent `search(input)` — same capability.
36
+ >
37
+ > **Write the function once. Expose it to humans and agents.**
38
+
39
+ **Docs:** https://emingure.github.io/simple-webmcp/ · **Live Demo:** [Try the shopping cart demo →](https://emingure.github.io/simple-webmcp/demo/) *(see `examples/demo`)* · **npm:** `simple-webmcp`
40
+
41
+ ---
42
+
43
+ ## Why simple-webmcp?
44
+
45
+ Most WebMCP integrations force a second layer:
46
+
47
+ ```text
48
+ Existing app logic
49
+
50
+ Define tool metadata (name, description, inputSchema)
51
+
52
+ Define execute wrapper
53
+
54
+ Call registerTool()
55
+
56
+ Manage lifecycle (AbortSignal, StrictMode, SSR)
57
+ ```
58
+
59
+ `simple-webmcp` collapses it:
60
+
61
+ ```text
62
+ Existing function
63
+
64
+ webmcp(existingFunction)
65
+
66
+ Done — keep your API, types, and business logic
67
+ ```
68
+
69
+ Use **raw `document.modelContext.registerTool()`** when you want total control over `getTools()` / `executeTool()` / `toolchange` / `exposedTo` — the browser imperative API ([Chrome Docs](https://developer.chrome.com/docs/ai/webmcp/imperative-api)). Use **`simple-webmcp`** when you already have `addToCart`, `searchCustomers`, `updateCustomer` and want them agent-callable without duplicating schema and lifecycle.
70
+
71
+ **Focused on tool authoring and lifecycle** — not a full WebMCP SDK. The browser provides `getTools`, `executeTool`, `exposedTo` etc.; we provide the tiny application layer on top and stay stable while WebMCP evolves underneath.
72
+
73
+ ## Before / After
74
+
75
+ **Without — raw WebMCP:**
76
+
77
+ ```ts
78
+ document.modelContext.registerTool({
79
+ name: 'add_to_cart',
80
+ description: 'Add a product to the shopping cart',
81
+ inputSchema: {
82
+ type: 'object',
83
+ properties: {
84
+ productId: { type: 'string', description: 'Product ID' },
85
+ quantity: { type: 'number', minimum: 1 }
86
+ },
87
+ required: ['productId', 'quantity']
88
+ },
89
+ execute: ({ productId, quantity }) => addToCart(productId, quantity)
90
+ }, { signal });
91
+ ```
92
+
93
+ **With — function-first:**
94
+
95
+ ```ts
96
+ import { webmcp } from 'simple-webmcp';
97
+
98
+ const addToCartTool = webmcp(addToCart, {
99
+ description: 'Add a product to the shopping cart'
16
100
  });
17
101
 
18
- await searchTool({ query: 'alice' }); // still just a function
19
- await searchTool.register(); // => document.modelContext.registerTool(...)
20
- // or React: useWebMCP(searchTool) mounted = exposed
102
+ // same function, same app one line
103
+ await addToCartTool({ productId: 'p_123', quantity: 2 }); // human
104
+ // agent calls same tool via WebMCP when <CartPage> is mounted
21
105
  ```
22
106
 
23
- Works with plain JS, TS, React, Vite… (Next.js Server Actions experimental 0.3). Open source, lean (`sideEffects:false`, core no React dep).
107
+ **Same function. Same application. One line.** No duplicated business logic.
24
108
 
25
- **Docs:** https://emingure.github.io/simple-webmcp/ (via GitHub Pages)
109
+ For e-commerce, SaaS dashboards, booking, CRM, forms, internal tools — where `addToCart`, `searchCustomers`, `createInvoice` already exist and suddenly need to be agent-callable.
26
110
 
27
111
  ## Install
28
112
 
29
113
  ```bash
30
114
  npm i simple-webmcp
31
- # React is optional peer (≥18) only if you import simple-webmcp/react
32
- # Repo: https://github.com/emingure/simple-webmcp
115
+ # React is optional peer only if you use simple-webmcp/react
33
116
  ```
34
117
 
35
118
  ## Quick Start
36
119
 
37
- | Need | Code |
38
- |------|------|
39
- | Vanilla — global | `webmcp(fn,{description, global:true})` or `await tool.register()` |
40
- | Vanilla — manual | `tool.register()` / `tool.unregister()` |
41
- | React — page | `useWebMCP(tool)` while component mounted |
42
- | React — layout / route | `<Scope tools={[a,b]}>{children}</Scope>` |
43
- | Rate-limited / disabled | `webmcp(fn,{enabled:false})` |
44
- | Polyfill (Firefox/Safari) | `import 'simple-webmcp/polyfill'` |
45
-
46
- ## Schema
47
-
48
- Hierarchy **corrected per review**: `schema` (whole) → inferred (runtime 0.1 / build 0.2 TS/JSDoc) → `fields` patch → metadata override.
120
+ ### Vanilla manual or global
49
121
 
50
122
  ```ts
51
- // prefer single object param fn({query, limit}) for best inference
52
- webmcp(fn, { description:'…', fields:{ query:{description:'…'}, limit:{type:'integer', maximum:50} } });
123
+ import { webmcp } from 'simple-webmcp';
53
124
 
54
- // whole Zod / StandardSchema — requires `simple-webmcp/zod` side-effect (keeps core lean)
55
- import { z } from 'zod';
56
- import 'simple-webmcp/zod'; // enables Zod JSON Schema in core
57
- webmcp(fn, { description:'…', schema: z.object({ query: z.string().min(1) }) });
125
+ export const tool = webmcp(addToCart, {
126
+ description: 'Add product to shopping cart',
127
+ fields: { productId: { description: 'Product ID' }, quantity: { type: 'integer', minimum: 1 } }
128
+ });
58
129
 
59
- // per-field mix same import enables it
60
- webmcp(fn, { fields:{ query: z.string().describe('…'), limit:{type:'integer'} } });
130
+ await tool({ productId: 'p_1', quantity: 2 }); // human
131
+ await tool.register(); // expose uses document.modelContext (Chrome canary)
132
+ tool.unregister();
61
133
 
62
- // JSON Schema directly
63
- webmcp(fn, { schema:{type:'object', properties:{query:{type:'string'}}, required:['query']} });
134
+ // global (registers on import, client only) — prefer scoped for least privilege
135
+ webmcp.global(addToCart, { description: '...' });
64
136
  ```
65
137
 
66
- `fields` is a **patch** (`Partial<JsonSchema>` or per-field `StandardSchema`) — annotates, does not silently replace core type. Provide `strict:true` to make ambiguous runtime inference throw instead of warn.
67
-
68
- ## React
138
+ ### React 1-line (recommended)
69
139
 
70
140
  ```tsx
71
141
  'use client';
72
- import { webmcp } from 'simple-webmcp';
73
- import { useWebMCP, Scope } from 'simple-webmcp/react';
74
-
75
- const tool = webmcp(fn, { description:'…' });
142
+ import { useWebMCP } from 'simple-webmcp/react'; // alias: useTool
76
143
 
77
- export function Page() {
78
- const { supported, registered, error } = useWebMCP(tool);
79
- return null; // exposed while mounted maps to AbortSignal
144
+ export function ProductPage() {
145
+ const tool = useWebMCP(addToCart, { description: 'Add product to cart' });
146
+ // tool is callable + has tool.registered / tool.status
147
+ return <Product />;
80
148
  }
81
-
82
- // layout-level route scope (Next.js app/layout.tsx naturally gives route scope)
83
- <Scope tools={[toolA, toolB]}>{children}</Scope>
84
149
  ```
85
150
 
86
- ## Polyfill
151
+ Verbose 2-line still works: `const t = webmcp(fn); useWebMCP(t)`. Layout-level: `<Scope tools={[search, update]}>{children}</Scope>` — naturally gives route-level scope in Next.js `app/layout.tsx`.
152
+
153
+ `register()` is `async` (`Promise<() => void>`) per current `webmcp-types`; hook maps to `AbortSignal` and dedupes StrictMode. `status` is `'unregistered'|'registering'|'registered'|'unsupported'|'error'` — `supported` and `registered` are mutually exclusive (unsupported never claims registered).
87
154
 
88
- `simple-webmcp/polyfill` is an adapter entry (not hard-coded to one impl). It installs a thin in-memory shim if `document.modelContext` missing, shielding spec churn. For full MCP transport, install `@mcp-b/global` before.
155
+ ## Customize only what you need
89
156
 
90
157
  ```ts
91
- import 'simple-webmcp/polyfill';
92
- import { installPolyfill } from 'simple-webmcp/polyfill'; // programmatic
158
+ const search = webmcp(searchCustomers, {
159
+ description: 'Search customers by name or email',
160
+ fields: {
161
+ query: { description: 'Customer name, email, or ID' },
162
+ limit: { type: 'integer', minimum: 1, maximum: 50 }
163
+ }
164
+ });
93
165
  ```
94
166
 
95
- ## API
167
+ **Enhance inferred schemas without rewriting them.** `fields` is a patch over the base schema (`Partial<JsonSchema>` or per-field `StandardSchema`). Whole `schema` establishes the contract; `fields` decorates it.
96
168
 
97
- See `.agents/skills/webmcp-simple/references/api.md` and examples `examples/`.
169
+ ```ts
170
+ // Zod — requires side-effect (keeps core 6.26KB gz lean)
171
+ import { z } from 'zod';
172
+ import 'simple-webmcp/zod';
173
+ webmcp(fn, { schema: z.object({ query: z.string().min(1) }) });
174
+ webmcp(fn, { fields: { query: z.string().describe('Name') } });
175
+ ```
98
176
 
99
- ### Core
177
+ ## How inference works
100
178
 
101
- - `webmcp(fn, opts)` `WebMCPTool<F>` (callable + `tool/definition/register/unregister/status`)
102
- - `webmcp.global(fn, opts)` alias
103
- - `isWebMCPSupported()`, `toSnakeCase`, `registry.list()/clear()`
104
- - Errors: `NotSupportedError`, `NotAllowedError` (Permissions Policy), `RegistrationError`, `ValidationError`, `ConfigurationError`
179
+ **Infer what JavaScript can know at runtime. Get richer TypeScript/JSDoc inference with the optional build plugin.**
105
180
 
106
- ### React
181
+ *Runtime (0.1)* — best-effort, `confidence:'low'`: parameter names, defaults, destructured keys (`{query, limit=20}` → `query` required, `limit` optional `default:20`), some primitives from literal defaults. `function search(query: string)` alone becomes `{properties:{query:{}}}` — we warn and need `fields`/`schema` or `strict:true` throws.
107
182
 
108
- - `useWebMCP(tool, {enabled})`
109
- - `<Scope tools>`
183
+ *Build (0.2 `simple-webmcp/unplugin`)* — Vite/Webpack plugin reads TypeScript types + JSDoc before erasure (same approach `webmcp-nexus` validates with `ts-morph`). Same `webmcp(fn)` call, richer `inputSchema`, no code change.
110
184
 
111
- ### Errors & Lifecycle
185
+ Progressive:
112
186
 
113
- `register()` is async `Promise<()=>void>` per current WebMCP types. `status: 'unregistered'|'registering'|'registered'|'unregistering'|'error'`. Use `tool.status` / `tool.isRegistered()`; hook exposes `supported/registered/error`.
187
+ ```
188
+ webmcp(fn)
189
+ ↓ add description
190
+ ↓ add field metadata (fields)
191
+ ↓ provide a schema (Zod)
192
+ ↓ opt into build-time TS/JSDoc inference
193
+ ```
194
+
195
+ Start with one function.
196
+
197
+ ## Comparison
198
+
199
+ | | simple-webmcp | raw WebMCP (`document.modelContext`) | `usewebmcp` | `@mcp-b/react-webmcp` |
200
+ |---|---|---|---|---|
201
+ | Existing function stays callable | ✅ | ❌ | ❌ | ❌ |
202
+ | `webmcp(fn)` — function-first | ✅ | — | — | — |
203
+ | Metadata patching | ✅ | Manual `registerTool` | Manual | Manual |
204
+ | `fields` patch | ✅ | ❌ | ❌ | ❌ |
205
+ | React lifecycle (`AbortSignal`, StrictMode) | ✅ | Manual | ✅ | ✅ |
206
+ | Full MCP ecosystem / `getTools` etc | — (authoring) | ✅ Browser API | — | ✅ |
207
+ | Weekly downloads | new | n/a (browser) | — | ~6k |
208
+ | Focus | Function-first DX | Native API | React hooks | MCP ecosystem |
209
+
210
+ > **simple-webmcp + MCP-B are complementary.** `simple-webmcp` authors capabilities; MCP-B / native WebMCP is the runtime. For real cross-browser WebMCP (Firefox/Safari), use the dedicated `@mcp-b/webmcp-polyfill` (~6k weekly):
211
+ > ```bash
212
+ > npm i @mcp-b/webmcp-polyfill && import '@mcp-b/webmcp-polyfill'
213
+ > ```
214
+ > `simple-webmcp/polyfill` is a **dev/testing shim** (in-memory `registerTool`/`listTools`/`invokeTool`, not full transport) — prefer `simple-webmcp/dev-polyfill` or `simple-webmcp/testing` in tests/Storybook. Native `document.modelContext` (not `navigator.modelContext`) is detected first.
215
+
216
+ ## Live Demo
217
+
218
+ **Shopping Cart:** MacBook £1,299 / Keyboard £99 / [Checkout] — agent says *“Add a keyboard to my cart”* → `add_to_cart` via WebMCP, UI updates. Chrome's docs point to demos + inspector extension for this flow.
219
+
220
+ **Admin Dashboard (e-commerce/CRM):** `search_customers`, `get_customer`, `update_customer`, `create_invoice` — ordinary functions `const tools = [webmcp(search), webmcp(get), webmcp(update)]` exposed from the active component only. See `examples/demo/` and https://emingure.github.io/simple-webmcp/demo/.
221
+
222
+ ## API
223
+
224
+ See `.agents/skills/webmcp-simple/references/api.md`.
225
+
226
+ **Core:** `webmcp(fn, opts)` → `WebMCPTool` (callable + `tool`/`definition`/`register`/`status`), `webmcp.global`, `isWebMCPSupported()`, `registry.list()`. Errors: `NotSupportedError` (`unsupported` status, mutually exclusive with `registered`), `NotAllowedError`, `RegistrationError`, `ConfigurationError`.
227
+
228
+ **React:** `useWebMCP(fn, opts)` / `useWebMCP(tool)` → `WebMCPTool & status` / `status`, `useTool` alias, `<Scope tools>`.
229
+
230
+ **Zod:** `import 'simple-webmcp/zod'` then `schema`/`fields` accept Zod/StandardSchema.
231
+
232
+ WebMCP today is `document.modelContext` (Chrome canary, origin trial). This package tracks the spec — your app stays on the tiny `webmcp(fn)` API while we absorb browser changes.
114
233
 
115
234
  ## Development
116
235
 
117
236
  ```bash
118
- npm run build # tsup ESM+CJS+DTS
119
- npm test # vitest jsdom
237
+ npm run build # tsup ESM+CJS+DTS (core 6.26KB gz, zod 1.40KB separate)
238
+ npm test # vitest jsdom — 43 tests
120
239
  npm run typecheck
240
+ npm run docs:dev # VitePress
121
241
  ```
122
242
 
123
243
  ## Versioning
124
244
 
125
- - **0.1** — `webmcp()` + `useWebMCP` + `Scope` + runtime fallback + polyfill adapter + tests (this release)
126
- - **0.2** — `simple-webmcp/unplugin` TS/JSDoc build inference (Vite/Webpack)
127
- - **0.3** — `simple-webmcp/next` `webmcp.server()` — experimental, gated behind working `fixtures/next-app` spike (verifies Server Action reference survival)
128
- - **0.4** — `webmcp.bind()`, DevTools, CLI
245
+ * **0.1** — `webmcp()` + `useWebMCP`/`useTool` + `Scope` + runtime best-effort fallback + dev shim + 43 tests (this release)
246
+ * **0.2** — `simple-webmcp/unplugin` TS/JSDoc inference (Vite/Webpack)
247
+ * **0.3** — `simple-webmcp/next` `webmcp.server()` — experimental, gated behind `fixtures/next-app` spike. Next 16 defaults to Turbopack, so no Webpack plugin in 0.1.
248
+ * **0.4** — `webmcp.bind()`, DevTools, CLI — deferred
249
+
250
+ `outputSchema` is kept internally but not marketed in 0.1 — current browser API does not enforce it like `usewebmcp` notes.
129
251
 
130
252
  ## Skills (Agent)
131
253
 
132
- This package ships `.agents/skills/webmcp-simple/SKILL.md` for auto-discovery by OpenCode/Claude. No install needed agents discover via `.agents/skills/`.
254
+ This package ships `.agents/skills/webmcp-simple/SKILL.md` for auto-discovery by OpenCode/Claude the agent becomes distribution: *“Make this function WebMCP callable”* → `webmcp(fn)` instead of raw `registerTool`.
133
255
 
134
256
  ## License
135
257
 
136
- MIT
258
+ MIT — Copyright © 2026 Muhammed Emin Gure (https://github.com/emingure)
@@ -0,0 +1,68 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ // src/polyfill.ts
6
+ var installed = false;
7
+ function installPolyfill(opts) {
8
+ if (installed && !opts?.force) return true;
9
+ if (typeof document === "undefined") return false;
10
+ const docAny = document;
11
+ if (docAny.modelContext && !opts?.force) {
12
+ installed = true;
13
+ return true;
14
+ }
15
+ const tools = /* @__PURE__ */ new Map();
16
+ const polyfill = {
17
+ _isPolyfill: true,
18
+ _tools: tools,
19
+ async registerTool(def, opts2) {
20
+ const name = def?.name;
21
+ if (!name) throw new Error("registerTool: name required");
22
+ if (tools.has(name)) {
23
+ return;
24
+ }
25
+ tools.set(name, { def, signal: opts2?.signal });
26
+ if (opts2?.signal) {
27
+ const onAbort = () => {
28
+ if (tools.get(name)?.signal === opts2.signal) tools.delete(name);
29
+ };
30
+ if (opts2.signal.aborted) {
31
+ tools.delete(name);
32
+ } else {
33
+ opts2.signal.addEventListener("abort", onAbort, { once: true });
34
+ }
35
+ }
36
+ },
37
+ listTools() {
38
+ return Array.from(tools.values()).map((v) => v.def);
39
+ },
40
+ async invokeTool(name, args) {
41
+ const entry = tools.get(name);
42
+ if (!entry) throw new Error(`Tool ${name} not found`);
43
+ return entry.def.execute(args);
44
+ }
45
+ };
46
+ docAny.modelContext = polyfill;
47
+ installed = true;
48
+ return true;
49
+ }
50
+ function isPolyfilled() {
51
+ if (typeof document === "undefined") return false;
52
+ return !!document.modelContext?._isPolyfill;
53
+ }
54
+ if (typeof document !== "undefined") {
55
+ queueMicrotask(() => {
56
+ const docAny = document;
57
+ if (!docAny.modelContext) {
58
+ installPolyfill();
59
+ }
60
+ });
61
+ }
62
+ var polyfill_default = installPolyfill;
63
+
64
+ exports.default = polyfill_default;
65
+ exports.installPolyfill = installPolyfill;
66
+ exports.isPolyfilled = isPolyfilled;
67
+ //# sourceMappingURL=dev-polyfill.cjs.map
68
+ //# sourceMappingURL=dev-polyfill.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/polyfill.ts"],"names":["opts"],"mappings":";;;;;AAwBA,IAAI,SAAA,GAAY,KAAA;AAET,SAAS,gBAAgB,IAAA,EAAiC;AAC/D,EAAA,IAAI,SAAA,IAAa,CAAC,IAAA,EAAM,KAAA,EAAO,OAAO,IAAA;AACtC,EAAA,IAAI,OAAO,QAAA,KAAa,WAAA,EAAa,OAAO,KAAA;AAC5C,EAAA,MAAM,MAAA,GAAS,QAAA;AACf,EAAA,IAAI,MAAA,CAAO,YAAA,IAAgB,CAAC,IAAA,EAAM,KAAA,EAAO;AACvC,IAAA,SAAA,GAAY,IAAA;AACZ,IAAA,OAAO,IAAA;AAAA,EACT;AAIA,EAAA,MAAM,KAAA,uBAAY,GAAA,EAMhB;AAEF,EAAA,MAAM,QAAA,GAAW;AAAA,IACf,WAAA,EAAa,IAAA;AAAA,IACb,MAAA,EAAQ,KAAA;AAAA,IACR,MAAM,YAAA,CAAa,GAAA,EAAUA,KAAAA,EAAiC;AAC5D,MAAA,MAAM,OAAO,GAAA,EAAK,IAAA;AAClB,MAAA,IAAI,CAAC,IAAA,EAAM,MAAM,IAAI,MAAM,6BAA6B,CAAA;AACxD,MAAA,IAAI,KAAA,CAAM,GAAA,CAAI,IAAI,CAAA,EAAG;AAEnB,QAAA;AAAA,MACF;AACA,MAAA,KAAA,CAAM,IAAI,IAAA,EAAM,EAAE,KAAK,MAAA,EAAQA,KAAAA,EAAM,QAAQ,CAAA;AAC7C,MAAA,IAAIA,OAAM,MAAA,EAAQ;AAChB,QAAA,MAAM,UAAU,MAAM;AACpB,UAAA,IAAI,KAAA,CAAM,IAAI,IAAI,CAAA,EAAG,WAAWA,KAAAA,CAAK,MAAA,EAAQ,KAAA,CAAM,MAAA,CAAO,IAAI,CAAA;AAAA,QAChE,CAAA;AACA,QAAA,IAAIA,KAAAA,CAAK,OAAO,OAAA,EAAS;AACvB,UAAA,KAAA,CAAM,OAAO,IAAI,CAAA;AAAA,QACnB,CAAA,MAAO;AACL,UAAAA,KAAAA,CAAK,OAAO,gBAAA,CAAiB,OAAA,EAAS,SAAS,EAAE,IAAA,EAAM,MAAM,CAAA;AAAA,QAC/D;AAAA,MACF;AAAA,IACF,CAAA;AAAA,IACA,SAAA,GAAY;AACV,MAAA,OAAO,KAAA,CAAM,IAAA,CAAK,KAAA,CAAM,MAAA,EAAQ,EAAE,GAAA,CAAI,CAAC,CAAA,KAAM,CAAA,CAAE,GAAG,CAAA;AAAA,IACpD,CAAA;AAAA,IACA,MAAM,UAAA,CAAW,IAAA,EAAc,IAAA,EAAe;AAC5C,MAAA,MAAM,KAAA,GAAQ,KAAA,CAAM,GAAA,CAAI,IAAI,CAAA;AAC5B,MAAA,IAAI,CAAC,KAAA,EAAO,MAAM,IAAI,KAAA,CAAM,CAAA,KAAA,EAAQ,IAAI,CAAA,UAAA,CAAY,CAAA;AACpD,MAAA,OAAO,KAAA,CAAM,GAAA,CAAI,OAAA,CAAQ,IAAI,CAAA;AAAA,IAC/B;AAAA,GACF;AAEA,EAAA,MAAA,CAAO,YAAA,GAAe,QAAA;AACtB,EAAA,SAAA,GAAY,IAAA;AACZ,EAAA,OAAO,IAAA;AACT;AAEO,SAAS,YAAA,GAAwB;AACtC,EAAA,IAAI,OAAO,QAAA,KAAa,WAAA,EAAa,OAAO,KAAA;AAC5C,EAAA,OAAO,CAAC,CAAE,QAAA,CAAiB,YAAA,EAAc,WAAA;AAC3C;AAGA,IAAI,OAAO,aAAa,WAAA,EAAa;AAEnC,EAAA,cAAA,CAAe,MAAM;AACnB,IAAA,MAAM,MAAA,GAAS,QAAA;AACf,IAAA,IAAI,CAAC,OAAO,YAAA,EAAc;AACxB,MAAA,eAAA,EAAgB;AAAA,IAClB;AAAA,EACF,CAAC,CAAA;AACH;AAEA,IAAO,gBAAA,GAAQ","file":"dev-polyfill.cjs","sourcesContent":["/**\n * simple-webmcp/polyfill — dev/testing shim (NOT a WebMCP interoperability polyfill).\n *\n * This is an in-memory registry for tests, Storybook, and local dev without Chrome.\n * It implements `registerTool`/`listTools`/`invokeTool` but not full MCP transport.\n *\n * For real cross-browser WebMCP (Firefox/Safari), use the dedicated\n * `@mcp-b/webmcp-polyfill` (https://www.npmjs.com/package/@mcp-b/webmcp-polyfill)\n * — ~6k weekly downloads, broader MCP ecosystem. Example:\n * `npm i @mcp-b/webmcp-polyfill && import '@mcp-b/webmcp-polyfill'`\n *\n * In production Chrome with native `document.modelContext`, this is a no-op\n * (detected via native presence). Prefer the real polyfill for production\n * cross-browser, and this shim for testing.\n *\n * New aliases (preferred for clarity): `simple-webmcp/dev-polyfill` and\n * `simple-webmcp/testing` — same module, clearer intent. `simple-webmcp/polyfill`\n * is kept for backward compat but will be documented as dev shim.\n */\n\nexport type PolyfillOptions = {\n force?: boolean;\n};\n\nlet installed = false;\n\nexport function installPolyfill(opts?: PolyfillOptions): boolean {\n if (installed && !opts?.force) return true;\n if (typeof document === 'undefined') return false;\n const docAny = document as any;\n if (docAny.modelContext && !opts?.force) {\n installed = true;\n return true;\n }\n\n // Minimal polyfill — tracks tools in memory, invokes via same ABI.\n // Not a full MCP transport; sufficient for registry + tests + devtools.\n const tools = new Map<\n string,\n {\n def: any;\n signal?: AbortSignal;\n }\n >();\n\n const polyfill = {\n _isPolyfill: true,\n _tools: tools,\n async registerTool(def: any, opts?: { signal?: AbortSignal }) {\n const name = def?.name;\n if (!name) throw new Error('registerTool: name required');\n if (tools.has(name)) {\n // Dedup like native — ignore second registration\n return;\n }\n tools.set(name, { def, signal: opts?.signal });\n if (opts?.signal) {\n const onAbort = () => {\n if (tools.get(name)?.signal === opts.signal) tools.delete(name);\n };\n if (opts.signal.aborted) {\n tools.delete(name);\n } else {\n opts.signal.addEventListener('abort', onAbort, { once: true });\n }\n }\n },\n listTools() {\n return Array.from(tools.values()).map((v) => v.def);\n },\n async invokeTool(name: string, args: unknown) {\n const entry = tools.get(name);\n if (!entry) throw new Error(`Tool ${name} not found`);\n return entry.def.execute(args);\n },\n };\n\n docAny.modelContext = polyfill;\n installed = true;\n return true;\n}\n\nexport function isPolyfilled(): boolean {\n if (typeof document === 'undefined') return false;\n return !!(document as any).modelContext?._isPolyfill;\n}\n\n// Auto-install side-effect import: `import 'simple-webmcp/polyfill'`\nif (typeof document !== 'undefined') {\n // Defer to next tick to allow native detection first\n queueMicrotask(() => {\n const docAny = document as any;\n if (!docAny.modelContext) {\n installPolyfill();\n }\n });\n}\n\nexport default installPolyfill;\n"]}
@@ -0,0 +1 @@
1
+ export { PolyfillOptions, default, default as installPolyfill, isPolyfilled } from './polyfill.cjs';
@@ -0,0 +1 @@
1
+ export { PolyfillOptions, default, default as installPolyfill, isPolyfilled } from './polyfill.js';
@@ -0,0 +1,62 @@
1
+ // src/polyfill.ts
2
+ var installed = false;
3
+ function installPolyfill(opts) {
4
+ if (installed && !opts?.force) return true;
5
+ if (typeof document === "undefined") return false;
6
+ const docAny = document;
7
+ if (docAny.modelContext && !opts?.force) {
8
+ installed = true;
9
+ return true;
10
+ }
11
+ const tools = /* @__PURE__ */ new Map();
12
+ const polyfill = {
13
+ _isPolyfill: true,
14
+ _tools: tools,
15
+ async registerTool(def, opts2) {
16
+ const name = def?.name;
17
+ if (!name) throw new Error("registerTool: name required");
18
+ if (tools.has(name)) {
19
+ return;
20
+ }
21
+ tools.set(name, { def, signal: opts2?.signal });
22
+ if (opts2?.signal) {
23
+ const onAbort = () => {
24
+ if (tools.get(name)?.signal === opts2.signal) tools.delete(name);
25
+ };
26
+ if (opts2.signal.aborted) {
27
+ tools.delete(name);
28
+ } else {
29
+ opts2.signal.addEventListener("abort", onAbort, { once: true });
30
+ }
31
+ }
32
+ },
33
+ listTools() {
34
+ return Array.from(tools.values()).map((v) => v.def);
35
+ },
36
+ async invokeTool(name, args) {
37
+ const entry = tools.get(name);
38
+ if (!entry) throw new Error(`Tool ${name} not found`);
39
+ return entry.def.execute(args);
40
+ }
41
+ };
42
+ docAny.modelContext = polyfill;
43
+ installed = true;
44
+ return true;
45
+ }
46
+ function isPolyfilled() {
47
+ if (typeof document === "undefined") return false;
48
+ return !!document.modelContext?._isPolyfill;
49
+ }
50
+ if (typeof document !== "undefined") {
51
+ queueMicrotask(() => {
52
+ const docAny = document;
53
+ if (!docAny.modelContext) {
54
+ installPolyfill();
55
+ }
56
+ });
57
+ }
58
+ var polyfill_default = installPolyfill;
59
+
60
+ export { polyfill_default as default, installPolyfill, isPolyfilled };
61
+ //# sourceMappingURL=dev-polyfill.js.map
62
+ //# sourceMappingURL=dev-polyfill.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/polyfill.ts"],"names":["opts"],"mappings":";AAwBA,IAAI,SAAA,GAAY,KAAA;AAET,SAAS,gBAAgB,IAAA,EAAiC;AAC/D,EAAA,IAAI,SAAA,IAAa,CAAC,IAAA,EAAM,KAAA,EAAO,OAAO,IAAA;AACtC,EAAA,IAAI,OAAO,QAAA,KAAa,WAAA,EAAa,OAAO,KAAA;AAC5C,EAAA,MAAM,MAAA,GAAS,QAAA;AACf,EAAA,IAAI,MAAA,CAAO,YAAA,IAAgB,CAAC,IAAA,EAAM,KAAA,EAAO;AACvC,IAAA,SAAA,GAAY,IAAA;AACZ,IAAA,OAAO,IAAA;AAAA,EACT;AAIA,EAAA,MAAM,KAAA,uBAAY,GAAA,EAMhB;AAEF,EAAA,MAAM,QAAA,GAAW;AAAA,IACf,WAAA,EAAa,IAAA;AAAA,IACb,MAAA,EAAQ,KAAA;AAAA,IACR,MAAM,YAAA,CAAa,GAAA,EAAUA,KAAAA,EAAiC;AAC5D,MAAA,MAAM,OAAO,GAAA,EAAK,IAAA;AAClB,MAAA,IAAI,CAAC,IAAA,EAAM,MAAM,IAAI,MAAM,6BAA6B,CAAA;AACxD,MAAA,IAAI,KAAA,CAAM,GAAA,CAAI,IAAI,CAAA,EAAG;AAEnB,QAAA;AAAA,MACF;AACA,MAAA,KAAA,CAAM,IAAI,IAAA,EAAM,EAAE,KAAK,MAAA,EAAQA,KAAAA,EAAM,QAAQ,CAAA;AAC7C,MAAA,IAAIA,OAAM,MAAA,EAAQ;AAChB,QAAA,MAAM,UAAU,MAAM;AACpB,UAAA,IAAI,KAAA,CAAM,IAAI,IAAI,CAAA,EAAG,WAAWA,KAAAA,CAAK,MAAA,EAAQ,KAAA,CAAM,MAAA,CAAO,IAAI,CAAA;AAAA,QAChE,CAAA;AACA,QAAA,IAAIA,KAAAA,CAAK,OAAO,OAAA,EAAS;AACvB,UAAA,KAAA,CAAM,OAAO,IAAI,CAAA;AAAA,QACnB,CAAA,MAAO;AACL,UAAAA,KAAAA,CAAK,OAAO,gBAAA,CAAiB,OAAA,EAAS,SAAS,EAAE,IAAA,EAAM,MAAM,CAAA;AAAA,QAC/D;AAAA,MACF;AAAA,IACF,CAAA;AAAA,IACA,SAAA,GAAY;AACV,MAAA,OAAO,KAAA,CAAM,IAAA,CAAK,KAAA,CAAM,MAAA,EAAQ,EAAE,GAAA,CAAI,CAAC,CAAA,KAAM,CAAA,CAAE,GAAG,CAAA;AAAA,IACpD,CAAA;AAAA,IACA,MAAM,UAAA,CAAW,IAAA,EAAc,IAAA,EAAe;AAC5C,MAAA,MAAM,KAAA,GAAQ,KAAA,CAAM,GAAA,CAAI,IAAI,CAAA;AAC5B,MAAA,IAAI,CAAC,KAAA,EAAO,MAAM,IAAI,KAAA,CAAM,CAAA,KAAA,EAAQ,IAAI,CAAA,UAAA,CAAY,CAAA;AACpD,MAAA,OAAO,KAAA,CAAM,GAAA,CAAI,OAAA,CAAQ,IAAI,CAAA;AAAA,IAC/B;AAAA,GACF;AAEA,EAAA,MAAA,CAAO,YAAA,GAAe,QAAA;AACtB,EAAA,SAAA,GAAY,IAAA;AACZ,EAAA,OAAO,IAAA;AACT;AAEO,SAAS,YAAA,GAAwB;AACtC,EAAA,IAAI,OAAO,QAAA,KAAa,WAAA,EAAa,OAAO,KAAA;AAC5C,EAAA,OAAO,CAAC,CAAE,QAAA,CAAiB,YAAA,EAAc,WAAA;AAC3C;AAGA,IAAI,OAAO,aAAa,WAAA,EAAa;AAEnC,EAAA,cAAA,CAAe,MAAM;AACnB,IAAA,MAAM,MAAA,GAAS,QAAA;AACf,IAAA,IAAI,CAAC,OAAO,YAAA,EAAc;AACxB,MAAA,eAAA,EAAgB;AAAA,IAClB;AAAA,EACF,CAAC,CAAA;AACH;AAEA,IAAO,gBAAA,GAAQ","file":"dev-polyfill.js","sourcesContent":["/**\n * simple-webmcp/polyfill — dev/testing shim (NOT a WebMCP interoperability polyfill).\n *\n * This is an in-memory registry for tests, Storybook, and local dev without Chrome.\n * It implements `registerTool`/`listTools`/`invokeTool` but not full MCP transport.\n *\n * For real cross-browser WebMCP (Firefox/Safari), use the dedicated\n * `@mcp-b/webmcp-polyfill` (https://www.npmjs.com/package/@mcp-b/webmcp-polyfill)\n * — ~6k weekly downloads, broader MCP ecosystem. Example:\n * `npm i @mcp-b/webmcp-polyfill && import '@mcp-b/webmcp-polyfill'`\n *\n * In production Chrome with native `document.modelContext`, this is a no-op\n * (detected via native presence). Prefer the real polyfill for production\n * cross-browser, and this shim for testing.\n *\n * New aliases (preferred for clarity): `simple-webmcp/dev-polyfill` and\n * `simple-webmcp/testing` — same module, clearer intent. `simple-webmcp/polyfill`\n * is kept for backward compat but will be documented as dev shim.\n */\n\nexport type PolyfillOptions = {\n force?: boolean;\n};\n\nlet installed = false;\n\nexport function installPolyfill(opts?: PolyfillOptions): boolean {\n if (installed && !opts?.force) return true;\n if (typeof document === 'undefined') return false;\n const docAny = document as any;\n if (docAny.modelContext && !opts?.force) {\n installed = true;\n return true;\n }\n\n // Minimal polyfill — tracks tools in memory, invokes via same ABI.\n // Not a full MCP transport; sufficient for registry + tests + devtools.\n const tools = new Map<\n string,\n {\n def: any;\n signal?: AbortSignal;\n }\n >();\n\n const polyfill = {\n _isPolyfill: true,\n _tools: tools,\n async registerTool(def: any, opts?: { signal?: AbortSignal }) {\n const name = def?.name;\n if (!name) throw new Error('registerTool: name required');\n if (tools.has(name)) {\n // Dedup like native — ignore second registration\n return;\n }\n tools.set(name, { def, signal: opts?.signal });\n if (opts?.signal) {\n const onAbort = () => {\n if (tools.get(name)?.signal === opts.signal) tools.delete(name);\n };\n if (opts.signal.aborted) {\n tools.delete(name);\n } else {\n opts.signal.addEventListener('abort', onAbort, { once: true });\n }\n }\n },\n listTools() {\n return Array.from(tools.values()).map((v) => v.def);\n },\n async invokeTool(name: string, args: unknown) {\n const entry = tools.get(name);\n if (!entry) throw new Error(`Tool ${name} not found`);\n return entry.def.execute(args);\n },\n };\n\n docAny.modelContext = polyfill;\n installed = true;\n return true;\n}\n\nexport function isPolyfilled(): boolean {\n if (typeof document === 'undefined') return false;\n return !!(document as any).modelContext?._isPolyfill;\n}\n\n// Auto-install side-effect import: `import 'simple-webmcp/polyfill'`\nif (typeof document !== 'undefined') {\n // Defer to next tick to allow native detection first\n queueMicrotask(() => {\n const docAny = document as any;\n if (!docAny.modelContext) {\n installPolyfill();\n }\n });\n}\n\nexport default installPolyfill;\n"]}