@nekuda/webmcp-sdk 0.7.0-dev.21.1 → 0.7.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/CHANGELOG.md +7 -0
- package/README.md +273 -30
- package/dist/index.js +2 -2
- package/package.json +4 -9
package/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,13 @@ the second of them puts a request shape on the wire that no earlier backend pars
|
|
|
10
10
|
ships with it. The events an install emits, and the fields on them, are byte for
|
|
11
11
|
byte 0.6.0's.
|
|
12
12
|
|
|
13
|
+
### Documentation
|
|
14
|
+
|
|
15
|
+
- The README is rewritten for people using the package: what it does, install, defining
|
|
16
|
+
and registering tools, page-scoped tools, browser support, the optional tool-call
|
|
17
|
+
analytics, and exactly what the default-on usage telemetry sends, never sends, and how
|
|
18
|
+
to turn it off. The npm description now says what the package does.
|
|
19
|
+
|
|
13
20
|
### Telemetry beacons without a preflight
|
|
14
21
|
|
|
15
22
|
- The telemetry beacon is sent as `text/plain` instead of `application/json`. The
|
package/README.md
CHANGED
|
@@ -1,32 +1,275 @@
|
|
|
1
1
|
# @nekuda/webmcp-sdk
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
3
|
+
Let AI agents use your website. This SDK turns the things your site already does,
|
|
4
|
+
such as searching products, adding to cart or booking a slot, into **tools** that a
|
|
5
|
+
browser agent can call through [WebMCP](https://webmachinelearning.github.io/webmcp/),
|
|
6
|
+
the draft web standard for exposing page actions to agents.
|
|
7
|
+
|
|
8
|
+
You describe each tool once. The SDK registers it with the browser, adds and removes it
|
|
9
|
+
as the user moves between pages, keeps working when the WebMCP draft changes, and does
|
|
10
|
+
nothing at all in browsers that don't support it yet.
|
|
11
|
+
|
|
12
|
+
```ts
|
|
13
|
+
import { defineTool, registerTools } from "@nekuda/webmcp-sdk";
|
|
14
|
+
|
|
15
|
+
const addToCart = defineTool({
|
|
16
|
+
stableKey: "cart.add",
|
|
17
|
+
name: "add_to_cart",
|
|
18
|
+
description: "Add a product to the shopping cart by SKU.",
|
|
19
|
+
inputSchema: {
|
|
20
|
+
type: "object",
|
|
21
|
+
properties: {
|
|
22
|
+
sku: { type: "string", description: "Product SKU" },
|
|
23
|
+
quantity: { type: "integer", minimum: 1, default: 1 },
|
|
24
|
+
},
|
|
25
|
+
required: ["sku"],
|
|
26
|
+
},
|
|
27
|
+
async execute({ sku, quantity = 1 }: { sku: string; quantity?: number }) {
|
|
28
|
+
const res = await fetch("/cart/add", {
|
|
29
|
+
method: "POST",
|
|
30
|
+
headers: { "content-type": "application/json" },
|
|
31
|
+
body: JSON.stringify({ sku, quantity }),
|
|
32
|
+
});
|
|
33
|
+
if (!res.ok) throw new Error(`Could not add ${sku} to the cart`);
|
|
34
|
+
return await res.json();
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
registerTools([addToCart]);
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
An agent visiting the page now sees an `add_to_cart` tool, with your description and
|
|
42
|
+
input schema, and can call it.
|
|
43
|
+
|
|
44
|
+
## Install
|
|
45
|
+
|
|
46
|
+
```sh
|
|
47
|
+
npm install @nekuda/webmcp-sdk
|
|
48
|
+
# or: pnpm add @nekuda/webmcp-sdk · yarn add @nekuda/webmcp-sdk · bun add @nekuda/webmcp-sdk
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
The package is a browser ES module with TypeScript types included. It has no runtime
|
|
52
|
+
dependencies. `@opentelemetry/api-logs` is an optional peer, needed only if you turn on
|
|
53
|
+
[OpenTelemetry output](#tool-call-analytics-optional).
|
|
54
|
+
|
|
55
|
+
**No bundler?** Serve `node_modules/@nekuda/webmcp-sdk/dist/index.js` from your site and
|
|
56
|
+
map the package name to it:
|
|
57
|
+
|
|
58
|
+
```html
|
|
59
|
+
<script type="importmap">
|
|
60
|
+
{ "imports": { "@nekuda/webmcp-sdk": "/vendor/webmcp-sdk/index.js" } }
|
|
61
|
+
</script>
|
|
62
|
+
<script type="module" src="/webmcp/entry.js"></script>
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Defining a tool
|
|
66
|
+
|
|
67
|
+
`defineTool` checks the definition straight away, freezes it and returns it. It does not
|
|
68
|
+
touch the browser, so a module that only defines tools is safe to import anywhere,
|
|
69
|
+
including tests and server-side code. An invalid definition throws a `TypeError` when the
|
|
70
|
+
module loads, not later when an agent calls the tool.
|
|
71
|
+
|
|
72
|
+
| Field | Required | What it is |
|
|
73
|
+
|---|---|---|
|
|
74
|
+
| `stableKey` | yes | Your permanent ID for the tool, in `domain.action` form (`cart.add`, `catalog.search`): lowercase letters, digits and `_`, with at least one dot. Keep it the same when you rename the tool; analytics follow the tool through renames by this key. It is never sent to the browser API. |
|
|
75
|
+
| `name` | no | The name agents see: 1–128 characters of `A–Z a–z 0–9 _ - .`. Defaults to `stableKey`. |
|
|
76
|
+
| `description` | yes | What the tool does, written for the agent. Be specific about when to use it. |
|
|
77
|
+
| `inputSchema` | no | A JSON Schema object describing `execute`'s input. |
|
|
78
|
+
| `title` | no | A human-readable display name. |
|
|
79
|
+
| `annotations` | no | WebMCP hints, for example `{ readOnlyHint: true }` for a tool that only reads. |
|
|
80
|
+
| `pages` | no | Paths the tool is available on. Leave it out to make the tool available everywhere. See [Tools for specific pages](#tools-for-specific-pages). |
|
|
81
|
+
| `version` | no | Your version string for the tool (semver, a build hash, anything). |
|
|
82
|
+
| `intent` | no | `"answer"`, `"act"` or `"transact"`: whether the tool reads, changes something, or moves money. |
|
|
83
|
+
| `source` | no | `"scanner_generated"` or `"merchant_authored"`. |
|
|
84
|
+
| `execute` | yes | Your code. It receives the agent's input and may be `async`. |
|
|
85
|
+
|
|
86
|
+
**Return values.** Return any JSON value or a plain string and the SDK wraps it in the
|
|
87
|
+
result format agents expect. If you already build a WebMCP `{ content: [...] }` result,
|
|
88
|
+
it is passed through unchanged.
|
|
89
|
+
|
|
90
|
+
**Errors.** Throw when the action fails. The error goes back to the agent as it is, so
|
|
91
|
+
write the message for the agent ("No product with SKU 123") and not for your logs.
|
|
92
|
+
|
|
93
|
+
**TypeScript.** Declare the input as a `type`, not an `interface`:
|
|
94
|
+
`defineTool<{ sku: string }>(…)` works, but an `interface` fails the
|
|
95
|
+
`Record<string, unknown>` constraint.
|
|
96
|
+
|
|
97
|
+
## Registering tools
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
const registration = registerTools([addToCart, searchProducts], options);
|
|
101
|
+
|
|
102
|
+
const results = await registration.ready;
|
|
103
|
+
// [{ stableKey: "cart.add", name: "add_to_cart", state: "registered" }, …]
|
|
104
|
+
|
|
105
|
+
registration.current(); // names registered right now, e.g. ["add_to_cart"]
|
|
106
|
+
registration.unregister(); // remove every tool in this call
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Each tool in `ready` ends in one of four states:
|
|
110
|
+
|
|
111
|
+
| State | Meaning |
|
|
112
|
+
|---|---|
|
|
113
|
+
| `registered` | The browser accepted the tool. |
|
|
114
|
+
| `unsupported` | This browser has no WebMCP API. Nothing happened, and nothing needs to be done. |
|
|
115
|
+
| `aborted` | The registration was cancelled before the tool registered. |
|
|
116
|
+
| `failed` | The browser refused the tool. The reason is on `error`. |
|
|
117
|
+
|
|
118
|
+
`ready` never rejects, so you don't need a `try`/`catch` around it. `registerTools` itself
|
|
119
|
+
throws only for a programming error: two tools in one call with the same `name` or the
|
|
120
|
+
same `stableKey`.
|
|
121
|
+
|
|
122
|
+
| Option | What it does |
|
|
123
|
+
|---|---|
|
|
124
|
+
| `signal` | An `AbortSignal`. Aborting it is the same as calling `unregister()`. |
|
|
125
|
+
| `onChange(names)` | Called with the current tool names whenever they change, for example after navigation. |
|
|
126
|
+
| `tracking` | Connects tool calls to your AgentLane account. See [Tool-call analytics](#tool-call-analytics-optional). |
|
|
127
|
+
| `telemetry` | Set to `false` to turn off anonymous usage telemetry for this call. See [Usage telemetry](#usage-telemetry). |
|
|
128
|
+
|
|
129
|
+
### Where to call it
|
|
130
|
+
|
|
131
|
+
Keep tool definitions in their own modules and call `registerTools` in one place that
|
|
132
|
+
owns the tools' lifetime.
|
|
133
|
+
|
|
134
|
+
**React (and Vite, Remix or any React SPA)**: register in an effect and unregister on
|
|
135
|
+
cleanup.
|
|
136
|
+
|
|
137
|
+
```tsx
|
|
138
|
+
import { useEffect } from "react";
|
|
139
|
+
import { registerTools } from "@nekuda/webmcp-sdk";
|
|
140
|
+
import { addToCart } from "./tools/cart";
|
|
141
|
+
|
|
142
|
+
export function WebMCPTools() {
|
|
143
|
+
useEffect(() => {
|
|
144
|
+
const registration = registerTools([addToCart]);
|
|
145
|
+
return () => registration.unregister();
|
|
146
|
+
}, []);
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
**Next.js App Router**: put the same component in a file marked `"use client"` and render
|
|
152
|
+
it from `app/layout.tsx`. Never call `registerTools` from a server component.
|
|
153
|
+
|
|
154
|
+
**Plain or server-rendered pages**: load one module with `<script type="module">` from
|
|
155
|
+
your shared layout.
|
|
156
|
+
|
|
157
|
+
```ts
|
|
158
|
+
import { registerTools } from "@nekuda/webmcp-sdk";
|
|
159
|
+
import { addToCart } from "./tools/cart.js";
|
|
160
|
+
|
|
161
|
+
const registration = registerTools([addToCart]);
|
|
162
|
+
addEventListener("pagehide", () => registration.unregister(), { once: true });
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
**Tools that need state**: register tools such as "update cart item" or "start checkout"
|
|
166
|
+
only while that state exists (for example, a non-empty cart or a signed-in user). Tie the
|
|
167
|
+
`registerTools` call to that state and unregister when it goes away.
|
|
168
|
+
|
|
169
|
+
## Tools for specific pages
|
|
170
|
+
|
|
171
|
+
Give a tool a `pages` list and it is only offered on matching pages:
|
|
172
|
+
|
|
173
|
+
```ts
|
|
174
|
+
defineTool({
|
|
175
|
+
stableKey: "product.add_review",
|
|
176
|
+
description: "Post a review of the product on this page.",
|
|
177
|
+
pages: ["/products/*"],
|
|
178
|
+
execute: postReview,
|
|
179
|
+
});
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
- `*` matches within one path segment (`/products/*`, `/product.html*`), and `**` matches
|
|
183
|
+
any number of segments (`/docs/**`).
|
|
184
|
+
- A tool with several patterns is available when any of them matches.
|
|
185
|
+
- Query strings and trailing slashes are ignored, and repeated slashes count as one.
|
|
186
|
+
- Hash routers work too: `/#/product/*`.
|
|
187
|
+
- During server-side rendering there is no location to check, so every tool counts as
|
|
188
|
+
eligible.
|
|
189
|
+
|
|
190
|
+
In a single-page app the SDK adds and removes page-scoped tools on every navigation
|
|
191
|
+
(`pushState`, `replaceState`, back/forward and hash changes), so you don't need to call
|
|
192
|
+
anything yourself. `ready` reports only the tools eligible on the first page; use
|
|
193
|
+
`onChange` or `current()` to follow later changes. If your app locks down the History
|
|
194
|
+
API, navigation is still detected through back/forward and hash changes.
|
|
195
|
+
|
|
196
|
+
## Browser support
|
|
197
|
+
|
|
198
|
+
The SDK works wherever the WebMCP API is present, as `document.modelContext` or
|
|
199
|
+
`navigator.modelContext`, whether a browser provides it natively or an extension does.
|
|
200
|
+
WebMCP is a draft that still changes month to month. The SDK tracks it so your code
|
|
201
|
+
doesn't have to, which is why you should call the SDK and not `modelContext` directly.
|
|
202
|
+
|
|
203
|
+
Where the API is missing, every tool reports `unsupported` and nothing else happens: no
|
|
204
|
+
errors and no changes to your page. You can ship the same code to every browser.
|
|
205
|
+
|
|
206
|
+
## Tool-call analytics (optional)
|
|
207
|
+
|
|
208
|
+
Connect your site in the [AgentLane dashboard](https://app.agentlane.com) to see which tools agents call, how
|
|
209
|
+
often they succeed and where they fail. Connecting means adding the site's publishable
|
|
210
|
+
key. Your tool definitions don't change.
|
|
211
|
+
|
|
212
|
+
```ts
|
|
213
|
+
registerTools([addToCart], {
|
|
214
|
+
tracking: { apiKey: "wmk_…" }, // publishable key from the AgentLane dashboard
|
|
215
|
+
});
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
With a key, the SDK sends an event for each tool call to AgentLane. It is off unless you
|
|
219
|
+
set it. Two other settings are available:
|
|
220
|
+
|
|
221
|
+
- `otel: true` also, or instead, emits each tool-call event as an OpenTelemetry log record
|
|
222
|
+
through your app's global `LoggerProvider`, so you can send it to your own
|
|
223
|
+
observability stack. Your app owns the exporter, and `apiKey` isn't needed for this.
|
|
224
|
+
- `disabled: true` switches tool-call analytics off completely, for example until the
|
|
225
|
+
visitor accepts analytics cookies. While it is set, no identifier is created, nothing
|
|
226
|
+
is stored and nothing is sent.
|
|
227
|
+
|
|
228
|
+
Tool-call analytics keeps an anonymous visitor and session identifier in the browser's
|
|
229
|
+
storage, so ask for consent wherever your analytics consent rules require it.
|
|
230
|
+
|
|
231
|
+
## Usage telemetry
|
|
232
|
+
|
|
233
|
+
The SDK sends anonymous usage telemetry by default, so we can see which versions are in
|
|
234
|
+
use and catch breakage. It needs no key and **stores nothing in the browser** (no
|
|
235
|
+
cookies, no localStorage, no identifiers).
|
|
236
|
+
|
|
237
|
+
**Sent:** SDK version, whether WebMCP is available, browser family, device type, page
|
|
238
|
+
language and route pattern (`/products/:id`, never the URL); each tool's name, whether it
|
|
239
|
+
registered, and the shape of its schema; and per tool call, the outcome, duration, result
|
|
240
|
+
size and a scrubbed error template.
|
|
241
|
+
|
|
242
|
+
**Never sent:** tool inputs or results, raw error messages, URLs, query strings, referrer
|
|
243
|
+
URLs, page titles or the full user-agent.
|
|
244
|
+
|
|
245
|
+
To count daily visitors, our server stores a one-way hash of site, IP and user-agent under
|
|
246
|
+
a key that rotates every day. It can't be reversed or linked across sites or days. If the
|
|
247
|
+
page sets `tracking.apiKey`, telemetry is attributed to your site.
|
|
248
|
+
|
|
249
|
+
**Turn it off:**
|
|
250
|
+
|
|
251
|
+
```ts
|
|
252
|
+
globalThis.__WEBMCP_TELEMETRY__ = false; // whole page; set before the SDK loads
|
|
253
|
+
registerTools(tools, { telemetry: false }); // one call
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
It is also off automatically when the browser sends
|
|
257
|
+
[Global Privacy Control](https://globalprivacycontrol.org/) and during server-side
|
|
258
|
+
rendering. `tracking.disabled` does not affect it.
|
|
259
|
+
|
|
260
|
+
## Advanced
|
|
261
|
+
|
|
262
|
+
These exports are for code that hosts the SDK on a page it controls, such as a tag
|
|
263
|
+
loader. Most sites don't need them.
|
|
264
|
+
|
|
265
|
+
- `matchPage(patterns, location)` and `currentPageKey()`: the page matcher `pages` uses.
|
|
266
|
+
- `resolveModelContext()`: returns the WebMCP object the SDK would use, or `undefined`.
|
|
267
|
+
- `registerTools(tools, { modelContext })`: register against a specific WebMCP object.
|
|
268
|
+
- `tracking.sessionId` and `createCallTracker(tool, tracking)`: report tool calls under
|
|
269
|
+
your own session ID, including for tools you registered with the browser yourself.
|
|
270
|
+
- The telemetry event types (`TelemetryEvent`, `SdkInitEvent`, `ToolRegistrationEvent`,
|
|
271
|
+
`ToolCallEvent`, …) are exported for anyone consuming the beacons.
|
|
272
|
+
|
|
273
|
+
## Changelog
|
|
274
|
+
|
|
275
|
+
See `CHANGELOG.md`, which ships in the package.
|
package/dist/index.js
CHANGED
|
@@ -136,7 +136,7 @@ function resolveModelContext(scope = globalThis) {
|
|
|
136
136
|
}
|
|
137
137
|
|
|
138
138
|
// src/transport.ts
|
|
139
|
-
var INGEST_BASE = "https://ingest.agentlane.
|
|
139
|
+
var INGEST_BASE = "https://ingest.agentlane.com";
|
|
140
140
|
var DEFAULT_COLLECT_ENDPOINT = `${INGEST_BASE}/v1/collect`;
|
|
141
141
|
var DEFAULT_TELEMETRY_ENDPOINT = `${INGEST_BASE}/v1/telemetry`;
|
|
142
142
|
function tryFetch(scope, url, headers, json, onResponse) {
|
|
@@ -1114,7 +1114,7 @@ function shapeMetrics(inputSchema) {
|
|
|
1114
1114
|
|
|
1115
1115
|
// src/telemetry.ts
|
|
1116
1116
|
var SDK_NAME = "@nekuda/webmcp-sdk";
|
|
1117
|
-
var SDK_VERSION = "0.7.0
|
|
1117
|
+
var SDK_VERSION = "0.7.0";
|
|
1118
1118
|
var INSTALL_MODES = ["npm", "cdn_snippet"];
|
|
1119
1119
|
var SDK_INSTALL_MODE = INSTALL_MODES.find((mode) => mode === (typeof __WEBMCP_INSTALL_MODE__ === "string" ? __WEBMCP_INSTALL_MODE__ : "")) ?? "npm";
|
|
1120
1120
|
function parseSampleRate(raw) {
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nekuda/webmcp-sdk",
|
|
3
|
-
"version": "0.7.0
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"description": "
|
|
5
|
+
"description": "Let AI agents use your website: define tools once and register them through WebMCP, with page-scoped tools, SPA navigation and unsupported browsers handled for you.",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
7
|
"types": "./dist/index.d.ts",
|
|
8
8
|
"exports": {
|
|
@@ -11,10 +11,7 @@
|
|
|
11
11
|
"default": "./dist/index.js"
|
|
12
12
|
}
|
|
13
13
|
},
|
|
14
|
-
"files": [
|
|
15
|
-
"dist",
|
|
16
|
-
"CHANGELOG.md"
|
|
17
|
-
],
|
|
14
|
+
"files": ["dist", "CHANGELOG.md"],
|
|
18
15
|
"publishConfig": {
|
|
19
16
|
"access": "public"
|
|
20
17
|
},
|
|
@@ -39,7 +36,5 @@
|
|
|
39
36
|
"optional": true
|
|
40
37
|
}
|
|
41
38
|
},
|
|
42
|
-
"trustedDependencies": [
|
|
43
|
-
"@biomejs/biome"
|
|
44
|
-
]
|
|
39
|
+
"trustedDependencies": ["@biomejs/biome"]
|
|
45
40
|
}
|