Skip to content
WebMCP Kit
Esc
↑↓navigate↵open⌘Jpreview
On this page

WebMCP SDK for TypeScript and JavaScript

Install @nekuda/webmcp-sdk. Expose existing app actions as WebMCP tools in React, Next.js, or JavaScript.

@nekuda/webmcp-sdk is a TypeScript and JavaScript SDK for browser WebMCP tools. Use it to expose actions that your app already supports. Install it directly or let WebMCP Kit add it for you.

The SDK defines tools, registers them with the browser, and provides cleanup when their scope ends. It has TypeScript types and no runtime dependencies.

When to use this SDK

Use it when your site already has the actions an agent needs, and you want to expose them as browser tools:

  • Call the same functions as your UI. Keep validation, state updates, and server access checks in those functions.
  • Limit tools to pages with pages. The SDK updates registration when a single-page app changes routes.
  • Tie tools to a component or session. Call unregister() or abort a signal when that scope ends.
  • Keep the site working in browsers without WebMCP. Registration returns unsupported when the browser has no WebMCP surface.

You write the input type and JSON Schema explicitly. The SDK does not infer TypeScript types from the schema or validate incoming arguments against it. Keep your app’s runtime validation.

For a few tools with no routing or cleanup needs, you can also use the browser API directly. The SDK does not provide a browser polyfill or a server MCP transport. It sends limited telemetry by default; see Telemetry for the data and opt-out.

Install

npm install @nekuda/webmcp-sdk

WebMCP is a draft browser standard. The SDK uses document.modelContext or the older navigator.modelContext surface. If neither is available, registration returns unsupported. It does not add a browser polyfill. See Browser setup.

Define a tool

Keep the tool definition in its own module. Register it from the page or component that owns it.

This example assumes your app exports an addToCart function from cart.ts. It accepts { sku, quantity }, validates the input, saves the cart, updates the UI, and returns the saved cart as JSON. Call that same function from the tool. Keep server-side validation and authorization in place.

For the full source of a working app that follows this pattern, run the board example. Its form and tool share one renameBoard function, including input validation and the visible update.

// webmcp/cart.ts
import { defineTool } from "@nekuda/webmcp-sdk";
import { addToCart as addProductToCart } from "../cart";

type AddToCartInput = { sku: string; quantity?: number };

export const addToCart = defineTool<AddToCartInput>({
  stableKey: "cart.add",
  name: "add_to_cart",
  title: "Add to cart",
  description: "Add a product to the shopping cart by SKU.",
  inputSchema: {
    type: "object",
    properties: {
      sku: { type: "string", description: "Product SKU" },
      quantity: { type: "integer", minimum: 1, default: 1 },
    },
    required: ["sku"],
    additionalProperties: false,
  },
  annotations: { readOnlyHint: false },
  execute: addProductToCart,
});

inputSchema describes the input to the agent. It does not replace validation, and default: 1 does not apply the default for you. Your shared action must reject invalid input and supply the default quantity.

For example, the board app checks input before changing any state:

function renameBoard(input) {
  if (!signedIn) throw new Error("Start the demo session first.");
  if (!input || typeof input !== "object" || Array.isArray(input) ||
      Object.keys(input).length !== 1 || !Object.hasOwn(input, "title") || typeof input.title !== "string" ||
      !input.title.trim() || input.title.length > 80) {
    throw new Error("Use a title with 1 to 80 characters and no extra fields.");
  }
  title = input.title.trim();
  render(state());
  return { title };
}

The example source includes the surrounding state, form, tool definition, and registration. Its demo session is local state; a real app must also check access on the server.

Register in React

Mount this component where the action is available. Unmount it when that scope ends, such as when the user signs out.

// webmcp/WebMCPTools.tsx
import { useEffect } from "react";
import { registerTools } from "@nekuda/webmcp-sdk";
import { addToCart } from "./cart";

export function WebMCPTools() {
  useEffect(() => {
    const registration = registerTools([addToCart]);
    return () => registration.unregister();
  }, []);
  return null;
}

Register in Next.js

For the App Router, add "use client"; as the first line of WebMCPTools.tsx. Render the component from the layout or page that owns the action. Do not call registerTools from a server component.

Register in JavaScript

In an app with a bundler, import the SDK from your browser entry module. Write the tool module in JavaScript by removing the type alias and <AddToCartInput>.

// webmcp/register.js
import { registerTools } from "@nekuda/webmcp-sdk";
import { addToCart } from "./cart.js";

const registration = registerTools([addToCart]);
console.table(await registration.ready);

For a single-page app, also call unregister() when the route or user state no longer permits the action. An AbortSignal can own this cleanup instead.

For a plain HTML page without a bundler, use the verification example.

Verify the result

Use WebMCP Kit’s verify skill against your running app:

/webmcp-kit:verify http://localhost:3000

Check that the tool appears once, a valid call returns the saved result and updates the UI, invalid input leaves the app unchanged, and ending the tool’s scope removes it. For the cart example, check the visible cart quantity as well as the returned result.

The verification example includes a downloadable app, a local check, and the browser steps for its rename_board tool. Use it to try the full flow, including ending and restarting the demo session.

defineTool

Validates, freezes, and returns the tool. It does not register it. An invalid definition throws a TypeError when the module loads.

Field Required What it is
stableKey yes Durable identity, dot-namespaced domain.action (e.g. cart.add). Written once, survives renames and re-runs. Never sent to the browser.
description yes What the tool does, when to use it, and what it returns. Agents read this text.
name no Wire name agents call, 1–128 chars of [A-Za-z0-9_.-]. Defaults to stableKey. Safe to change anytime.
title no Human-readable display name.
inputSchema no Plain JSON Schema object. Set additionalProperties: false.
annotations no Hints like { readOnlyHint: true }.
pages no URL patterns that limit where the tool is available. The SDK updates registration when the route changes.
execute yes The page-owned logic. Throw on failure. Return a JSON value; the SDK normalizes it.

registerTools

const reg = registerTools(tools, {
  signal,             // optional AbortSignal — abort means unregister
  telemetry: false,   // optional — see Telemetry below
  // tracking: {...}  // optional, opt-in per-call analytics
});

Returns { ready, current, unregister, signal }. Use current() to list registered tool names. Check ready for each tool’s initial registration result:

State Meaning
registered Live on the page’s WebMCP surface.
unsupported Browser has no WebMCP surface — graceful no-op, site unaffected.
aborted The signal aborted before registration.
failed Registration failed — e.g. a duplicate name on the page.

Two rules to know:

  • One batch per scope. A duplicate name or stableKey within a batch throws and fails the whole call.
  • Call unregister() or abort the signal to end the scope. Tools with pages are also removed when navigation leaves a matching route.

Telemetry

The SDK emits limited usage telemetry by default — SDK adoption, browser support mix, and tool-call reliability. It does not use a persistent visitor identifier or include raw tool inputs and results. Opting out is one flag: registerTools(tools, { telemetry: false }); for a guaranteed page-wide opt-out, set globalThis.__WEBMCP_TELEMETRY__ = false before the SDK loads. It also stays silent automatically when the browser sends Global Privacy Control, and during SSR. The separate tracking option can include tool inputs and results but is entirely opt-in and off unless configured. See the Privacy Policy for the collected data and controls.

Was this page helpful?