simple-webmcp 0.1.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/.agents/skills/webmcp-simple/SKILL.md +136 -0
- package/.agents/skills/webmcp-simple/references/api.md +77 -0
- package/.agents/skills/webmcp-simple/references/recipes.md +113 -0
- package/LICENSE +21 -0
- package/README.md +136 -0
- package/dist/index.cjs +849 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +153 -0
- package/dist/index.d.ts +153 -0
- package/dist/index.js +826 -0
- package/dist/index.js.map +1 -0
- package/dist/polyfill.cjs +68 -0
- package/dist/polyfill.cjs.map +1 -0
- package/dist/polyfill.d.cts +18 -0
- package/dist/polyfill.d.ts +18 -0
- package/dist/polyfill.js +62 -0
- package/dist/polyfill.js.map +1 -0
- package/dist/react.cjs +110 -0
- package/dist/react.cjs.map +1 -0
- package/dist/react.d.cts +30 -0
- package/dist/react.d.ts +30 -0
- package/dist/react.js +106 -0
- package/dist/react.js.map +1 -0
- package/dist/types-D-cwSfEU.d.cts +126 -0
- package/dist/types-D-cwSfEU.d.ts +126 -0
- package/dist/zod.cjs +144 -0
- package/dist/zod.cjs.map +1 -0
- package/dist/zod.d.cts +20 -0
- package/dist/zod.d.ts +20 -0
- package/dist/zod.js +137 -0
- package/dist/zod.js.map +1 -0
- package/package.json +100 -0
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: simple-webmcp
|
|
3
|
+
description: Expose existing JS/TS functions as WebMCP agent tools with minimal boilerplate — function-first, works with vanilla JS, TS, React, Vite, Next.js. Use when the user wants to make functions callable by LLM agents via WebMCP (document.modelContext).
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# simple-webmcp Skill
|
|
7
|
+
|
|
8
|
+
## When to use
|
|
9
|
+
|
|
10
|
+
Trigger this skill when the user wants to:
|
|
11
|
+
- Expose a JS/TS function to LLM / agentic tool calling via WebMCP
|
|
12
|
+
- Make `addToCart`, `searchCustomers`, `updateCustomer`, etc. available as `document.modelContext` tools
|
|
13
|
+
- Support plain JS, TypeScript, React (scoped lifecycle), or Next.js (server actions) without heavy boilerplate
|
|
14
|
+
|
|
15
|
+
Do **not** use for consuming external APIs — this skill is for *exposing* your own functions as tools.
|
|
16
|
+
|
|
17
|
+
## Quick Start
|
|
18
|
+
|
|
19
|
+
### 1. Vanilla / Vite — 1 line
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
import { webmcp } from 'simple-webmcp';
|
|
23
|
+
|
|
24
|
+
async function searchCustomers({ query, limit = 20 }: { query: string; limit?: number }) {
|
|
25
|
+
return customers.filter(c => c.name.includes(query)).slice(0, limit);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// still callable as before, but also a WebMCP tool descriptor
|
|
29
|
+
export const searchTool = webmcp(searchCustomers, {
|
|
30
|
+
description: 'Search customers in current account',
|
|
31
|
+
fields: {
|
|
32
|
+
query: { description: 'Name, email, or ID' },
|
|
33
|
+
limit: { type: 'integer', minimum: 1, maximum: 50, description: 'Max results' },
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// vanilla global register (outside React)
|
|
38
|
+
await searchTool.register(); // => document.modelContext.registerTool(...)
|
|
39
|
+
searchTool.unregister();
|
|
40
|
+
|
|
41
|
+
// still callable like original
|
|
42
|
+
await searchTool({ query: 'alice' });
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### 2. React — component lifecycle
|
|
46
|
+
|
|
47
|
+
```tsx
|
|
48
|
+
'use client';
|
|
49
|
+
import { webmcp } from 'simple-webmcp';
|
|
50
|
+
import { useWebMCP, Scope } from 'simple-webmcp/react';
|
|
51
|
+
|
|
52
|
+
const addToCartTool = webmcp(addToCart, { description: 'Add product to cart' });
|
|
53
|
+
|
|
54
|
+
export function ProductPage() {
|
|
55
|
+
// exposed only while component mounted — maps to AbortSignal
|
|
56
|
+
useWebMCP(addToCartTool);
|
|
57
|
+
return <div>...</div>;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Or route/component subtree (also works for Next.js layouts)
|
|
61
|
+
export function CustomersLayout({ children }: { children: React.ReactNode }) {
|
|
62
|
+
return <Scope tools={[searchTool]}>{children}</Scope>;
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### 3. Zod / StandardSchema — per-field or whole
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
import { z } from 'zod';
|
|
70
|
+
import { webmcp } from 'simple-webmcp';
|
|
71
|
+
|
|
72
|
+
// whole schema
|
|
73
|
+
webmcp(fn, { description: '…', schema: z.object({ query: z.string().min(1), limit: z.number().max(50).optional() }) });
|
|
74
|
+
|
|
75
|
+
// per-field mix — Lean + Zod
|
|
76
|
+
webmcp(searchCustomers, {
|
|
77
|
+
description: 'Search',
|
|
78
|
+
fields: {
|
|
79
|
+
query: z.string().describe('Name or email'), // StandardSchema
|
|
80
|
+
limit: { type: 'integer', minimum: 1, maximum: 50 }, // FieldDef (Partial<JsonSchema>)
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
In all cases `schema` (whole) establishes contract; `fields` patches it (adds descriptions, min/max) but does not silently change core type.
|
|
86
|
+
|
|
87
|
+
### 4. Polyfill (non-Chrome)
|
|
88
|
+
|
|
89
|
+
```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();
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### 5. Global vs Scoped
|
|
96
|
+
|
|
97
|
+
```ts
|
|
98
|
+
import { webmcp } from 'simple-webmcp';
|
|
99
|
+
// scoped (default) — inert until useWebMCP or .register()
|
|
100
|
+
webmcp(fn, { description: '…' });
|
|
101
|
+
// global — registers on import (client only)
|
|
102
|
+
webmcp(fn, { description: '…', global: true });
|
|
103
|
+
webmcp.global(fn, { description: '…' }); // alias
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Progression Ladder (teach stepwise)
|
|
107
|
+
|
|
108
|
+
1. **Beginner:** `webmcp(fn)` done.
|
|
109
|
+
2. **Better desc:** `webmcp(fn, { description: '…' })`
|
|
110
|
+
3. **Field docs:** `webmcp(fn, { fields: { query: { description: '…' } } })`
|
|
111
|
+
4. **Full control:** `webmcp(fn, { schema: z.object({…}) })`
|
|
112
|
+
5. **Lifecycle:** `useWebMCP(tool)` / `<Scope tools>`
|
|
113
|
+
|
|
114
|
+
## Rules
|
|
115
|
+
|
|
116
|
+
- Always provide `description` (or JSDoc); warning if missing.
|
|
117
|
+
- Prefer single object param `fn({query, limit})` for tools — best inference. Multi-arg legacy deferred to `webmcp.bind` (0.4).
|
|
118
|
+
- Use `readOnlyHint`/`destructiveHint` in `annotations` for agent hints; annotations are extensible `Record<string,unknown>`.
|
|
119
|
+
- Registration is async: `await tool.register()` → `() => void` unregister. React hook handles AbortSignal.
|
|
120
|
+
- `strict:true` makes ambiguous runtime inference a build error instead of warn.
|
|
121
|
+
|
|
122
|
+
## Inference
|
|
123
|
+
|
|
124
|
+
- **Runtime** (0.1): param names + defaults → low-confidence `{type:'object'}` placeholder.
|
|
125
|
+
- **Build** (0.2 `simple-webmcp/unplugin` for Vite/Webpack): TS types + JSDoc before erasure. Same API.
|
|
126
|
+
- Never pretend `function search(query)` alone is fully known — warn if no type/schema/fields.
|
|
127
|
+
|
|
128
|
+
## Next.js Server Actions — experimental (0.3, requires spike)
|
|
129
|
+
|
|
130
|
+
Do not assume transparent bridge works yet. The spike must verify `webmcp.server(action)` reference survives server/client boundary. Until then, keep server tools as described in docs but behind experimental flag.
|
|
131
|
+
|
|
132
|
+
## References
|
|
133
|
+
|
|
134
|
+
- `references/api.md` — full API surface
|
|
135
|
+
- `references/recipes.md` — vanilla, React, Vite, Next patterns
|
|
136
|
+
- `references/build.md` — unplugin inference
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# API Reference — simple-webmcp 0.1
|
|
2
|
+
|
|
3
|
+
## Core
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
import { webmcp } from 'simple-webmcp';
|
|
7
|
+
|
|
8
|
+
function webmcp<F extends (...args:any)=>any>(fn: F, options?: WebMCPOptions<F>): WebMCPTool<F>
|
|
9
|
+
namespace webmcp { function global<F>(fn:F, opts?: Omit<WebMCPOptions<F>,'global'>): WebMCPTool<F> }
|
|
10
|
+
|
|
11
|
+
type WebMCPTool<F> = F & {
|
|
12
|
+
__webmcpBrand: true;
|
|
13
|
+
tool: ToolContract; definition: ToolContract;
|
|
14
|
+
register(opts?: {signal?:AbortSignal}): Promise<()=>void>;
|
|
15
|
+
unregister(): void;
|
|
16
|
+
status: 'unregistered'|'registering'|'registered'|'unregistering'|'error';
|
|
17
|
+
registration: Promise<void>|null;
|
|
18
|
+
isRegistered(): boolean;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
type WebMCPOptions<F> = {
|
|
22
|
+
name?: string; description?: string;
|
|
23
|
+
schema?: JsonSchema | StandardSchemaV1; // whole contract (Zod, Valibot etc or JSON)
|
|
24
|
+
outputSchema?: JsonSchema | StandardSchemaV1;
|
|
25
|
+
fields?: Record<string, FieldDef|StandardSchemaV1>; // FieldDef = Partial<JsonSchema>
|
|
26
|
+
annotations?: Record<string,unknown> & {readOnlyHint?, destructiveHint?, openWorldHint?, title?}
|
|
27
|
+
scope?: 'global'|'scoped'|'manual'; global?: boolean; enabled?: boolean; strict?: boolean;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
type ToolContract = { name:string; description:string; inputSchema:JsonSchema; outputSchema?:JsonSchema; annotations?:Record<string,unknown> }
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Hierarchy: `schema` (whole) → inferred (runtime 0.1 / build 0.2) → `fields` patch → metadata.
|
|
34
|
+
|
|
35
|
+
`fields` example:
|
|
36
|
+
|
|
37
|
+
```ts
|
|
38
|
+
fields: {
|
|
39
|
+
query: { description:'Name or email' },
|
|
40
|
+
limit: { type:'integer', minimum:1, maximum:50 }
|
|
41
|
+
// or per-field Zod: query: z.string().describe('...')
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Errors: `SimpleWebMCPError` → `NotSupportedError`, `NotAllowedError` (Permissions Policy), `RegistrationError`, `ValidationError`, `ConfigurationError` — each `code` + `cause`.
|
|
46
|
+
|
|
47
|
+
Utils:
|
|
48
|
+
|
|
49
|
+
```ts
|
|
50
|
+
import { isWebMCPSupported, getModelContext, toSnakeCase, registry } from 'simple-webmcp';
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## React
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
import { useWebMCP, Scope } from 'simple-webmcp/react'; // 'use client'
|
|
57
|
+
|
|
58
|
+
function useWebMCP<F>(tool: WebMCPTool<F>, opts?:{enabled?:boolean}): {supported:boolean; registered:boolean; error:Error|null; isPolyfilled:boolean; status:string}
|
|
59
|
+
function Scope({tools, enabled, children}: {tools:WebMCPTool<any>[]; enabled?:boolean; children?:React.ReactNode}): JSX.Element
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Polyfill
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
import 'simple-webmcp/polyfill'; // auto
|
|
66
|
+
import { installPolyfill, isPolyfilled } from 'simple-webmcp/polyfill';
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Next (experimental, 0.3)
|
|
70
|
+
|
|
71
|
+
```ts
|
|
72
|
+
// after spike passes
|
|
73
|
+
import { webmcp } from 'simple-webmcp/next';
|
|
74
|
+
const tool = webmcp.server(action, opts); // action is 'use server' fn
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Not in 0.1 — do not depend yet.
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# Recipes
|
|
2
|
+
|
|
3
|
+
## Vanilla JS (Vite, plain TS)
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
import { webmcp } from 'simple-webmcp';
|
|
7
|
+
import 'simple-webmcp/polyfill'; // dev only
|
|
8
|
+
|
|
9
|
+
export async function addToCart({ productId, quantity }: { productId: string; quantity: number }) {
|
|
10
|
+
cart.push({ productId, quantity });
|
|
11
|
+
return { ok: true };
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const addToCartTool = webmcp(addToCart, {
|
|
15
|
+
description: 'Add product to shopping cart',
|
|
16
|
+
fields: {
|
|
17
|
+
productId: { description: 'Product ID' },
|
|
18
|
+
quantity: { type: 'integer', minimum: 1 },
|
|
19
|
+
},
|
|
20
|
+
annotations: { readOnlyHint: false },
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
// outside React — register programmatically
|
|
24
|
+
await addToCartTool.register();
|
|
25
|
+
// still call directly
|
|
26
|
+
await addToCartTool({ productId: 'p_123', quantity: 2 });
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## TypeScript + Zod (per-field)
|
|
30
|
+
|
|
31
|
+
```ts
|
|
32
|
+
import { z } from 'zod';
|
|
33
|
+
import { webmcp } from 'simple-webmcp';
|
|
34
|
+
|
|
35
|
+
async function searchCustomers({ query, limit }: { query: string; limit?: number }) {
|
|
36
|
+
// ...
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export const searchTool = webmcp(searchCustomers, {
|
|
40
|
+
description: 'Search customers',
|
|
41
|
+
fields: {
|
|
42
|
+
query: z.string().min(1).describe('Name, email, or ID'),
|
|
43
|
+
limit: z.number().int().min(1).max(50).optional().describe('Max results'),
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Zod whole schema
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
webmcp(fn, { description: '…', schema: z.object({ query: z.string(), limit: z.number().optional() }) });
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## React — page level
|
|
55
|
+
|
|
56
|
+
```tsx
|
|
57
|
+
'use client';
|
|
58
|
+
import { webmcp } from 'simple-webmcp';
|
|
59
|
+
import { useWebMCP } from 'simple-webmcp/react';
|
|
60
|
+
|
|
61
|
+
const searchTool = webmcp(searchCustomers, { description: 'Search' });
|
|
62
|
+
|
|
63
|
+
export function SearchPage() {
|
|
64
|
+
useWebMCP(searchTool);
|
|
65
|
+
return <SearchUI />;
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## React — layout/route scope
|
|
70
|
+
|
|
71
|
+
```tsx
|
|
72
|
+
'use client';
|
|
73
|
+
import { Scope } from 'simple-webmcp/react';
|
|
74
|
+
import { searchTool, updateTool } from '@/lib/tools';
|
|
75
|
+
|
|
76
|
+
export function CustomersLayout({ children }: { children: React.ReactNode }) {
|
|
77
|
+
// Tools exposed while layout mounted → natural route scope in Next.js layouts
|
|
78
|
+
return <Scope tools={[searchTool, updateTool]}>{children}</Scope>;
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Global (entire app)
|
|
83
|
+
|
|
84
|
+
```ts
|
|
85
|
+
import { webmcp } from 'simple-webmcp';
|
|
86
|
+
webmcp.global(searchCustomers, { description: 'Global search' });
|
|
87
|
+
// or
|
|
88
|
+
webmcp(searchCustomers, { description: 'Search', global: true });
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Strict mode (warn vs error)
|
|
92
|
+
|
|
93
|
+
```ts
|
|
94
|
+
// default: dev warn if no type/schema/fields
|
|
95
|
+
webmcp(fnWithoutTypes, { description: '…' }); // warn
|
|
96
|
+
|
|
97
|
+
// strict: throw
|
|
98
|
+
webmcp(fnWithoutTypes, { description: '…', strict: true }); // throws ConfigurationError
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Inference tips
|
|
102
|
+
|
|
103
|
+
- Best: `async function fn({query, limit}:{query:string, limit?:number})` — runtime sees `query` required, `limit` optional default
|
|
104
|
+
- Better with build (0.2 `simple-webmcp/unplugin`): TS type + JSDoc `@param query ...` → full schema, no manual `fields`
|
|
105
|
+
- Fallback for plain JS: add `fields` or `schema` explicitly
|
|
106
|
+
|
|
107
|
+
## Global registry for tests
|
|
108
|
+
|
|
109
|
+
```ts
|
|
110
|
+
import { registry } from 'simple-webmcp';
|
|
111
|
+
registry.list(); // [{name, status}]
|
|
112
|
+
registry.clear();
|
|
113
|
+
```
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Muhammed Emin Gure (https://github.com/emingure)
|
|
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,136 @@
|
|
|
1
|
+
# simple-webmcp
|
|
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.
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
import { webmcp } from 'simple-webmcp';
|
|
7
|
+
|
|
8
|
+
async function searchCustomers({ query, limit = 20 }: { query: string; limit?: number }) {
|
|
9
|
+
return customers.filter(c => c.name.includes(query)).slice(0, limit);
|
|
10
|
+
}
|
|
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' } },
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
await searchTool({ query: 'alice' }); // still just a function
|
|
19
|
+
await searchTool.register(); // => document.modelContext.registerTool(...)
|
|
20
|
+
// or React: useWebMCP(searchTool) → mounted = exposed
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Works with plain JS, TS, React, Vite… (Next.js Server Actions experimental 0.3). Open source, lean (`sideEffects:false`, core no React dep).
|
|
24
|
+
|
|
25
|
+
**Docs:** https://emingure.github.io/simple-webmcp/ (via GitHub Pages)
|
|
26
|
+
|
|
27
|
+
## Install
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
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
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Quick Start
|
|
36
|
+
|
|
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.
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
// prefer single object param fn({query, limit}) for best inference
|
|
52
|
+
webmcp(fn, { description:'…', fields:{ query:{description:'…'}, limit:{type:'integer', maximum:50} } });
|
|
53
|
+
|
|
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) }) });
|
|
58
|
+
|
|
59
|
+
// per-field mix — same import enables it
|
|
60
|
+
webmcp(fn, { fields:{ query: z.string().describe('…'), limit:{type:'integer'} } });
|
|
61
|
+
|
|
62
|
+
// JSON Schema directly
|
|
63
|
+
webmcp(fn, { schema:{type:'object', properties:{query:{type:'string'}}, required:['query']} });
|
|
64
|
+
```
|
|
65
|
+
|
|
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
|
|
69
|
+
|
|
70
|
+
```tsx
|
|
71
|
+
'use client';
|
|
72
|
+
import { webmcp } from 'simple-webmcp';
|
|
73
|
+
import { useWebMCP, Scope } from 'simple-webmcp/react';
|
|
74
|
+
|
|
75
|
+
const tool = webmcp(fn, { description:'…' });
|
|
76
|
+
|
|
77
|
+
export function Page() {
|
|
78
|
+
const { supported, registered, error } = useWebMCP(tool);
|
|
79
|
+
return null; // exposed while mounted — maps to AbortSignal
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// layout-level route scope (Next.js app/layout.tsx naturally gives route scope)
|
|
83
|
+
<Scope tools={[toolA, toolB]}>{children}</Scope>
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Polyfill
|
|
87
|
+
|
|
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.
|
|
89
|
+
|
|
90
|
+
```ts
|
|
91
|
+
import 'simple-webmcp/polyfill';
|
|
92
|
+
import { installPolyfill } from 'simple-webmcp/polyfill'; // programmatic
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## API
|
|
96
|
+
|
|
97
|
+
See `.agents/skills/webmcp-simple/references/api.md` and examples `examples/`.
|
|
98
|
+
|
|
99
|
+
### Core
|
|
100
|
+
|
|
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`
|
|
105
|
+
|
|
106
|
+
### React
|
|
107
|
+
|
|
108
|
+
- `useWebMCP(tool, {enabled})`
|
|
109
|
+
- `<Scope tools>`
|
|
110
|
+
|
|
111
|
+
### Errors & Lifecycle
|
|
112
|
+
|
|
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`.
|
|
114
|
+
|
|
115
|
+
## Development
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
npm run build # tsup ESM+CJS+DTS
|
|
119
|
+
npm test # vitest jsdom
|
|
120
|
+
npm run typecheck
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Versioning
|
|
124
|
+
|
|
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
|
|
129
|
+
|
|
130
|
+
## Skills (Agent)
|
|
131
|
+
|
|
132
|
+
This package ships `.agents/skills/webmcp-simple/SKILL.md` for auto-discovery by OpenCode/Claude. No install needed — agents discover via `.agents/skills/`.
|
|
133
|
+
|
|
134
|
+
## License
|
|
135
|
+
|
|
136
|
+
MIT
|