npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@web-ai-sdk/webmcp

v0.4.0

Published

Building block for the Web's native WebMCP API (navigator.modelContext)

Readme

@web-ai-sdk/webmcp

Building block for the W3C WebMCP API exposed at navigator.modelContext.

An ergonomic, framework-agnostic adapter over the native browser API, with safe register/unregister cleanup and a feature-detected no-op fallback for non-supporting browsers.

Docs: https://web-ai-sdk.dev/docs/guides/webmcp/ · React: useWebMCP

Status

WebMCP shipped as an early preview in Chrome 146+ behind chrome://flags/#enable-webmcp-testing; a public origin trial opens in Chrome 149. Edge added support in 147+ behind the matching edge://flags/ toggle. On any browser that doesn't expose navigator.modelContext, this library is a no-op. Your app stays callable, and no tools get registered.

Install

pnpm add @web-ai-sdk/webmcp
# or: npm i @web-ai-sdk/webmcp / bun add @web-ai-sdk/webmcp

React adapter is shipped as a subpath export, with no extra install. react is a peer dependency only when you import the /react entry.

Vanilla TypeScript / DOM

import { registerTool } from "@web-ai-sdk/webmcp";

const tools = [
  {
    name: "list_blog_posts",
    description: "List published blog posts.",
    readOnly: true,
    execute: async () => {
      const res = await fetch("/api/posts.json");
      return { results: await res.json() };
    },
  },
  {
    name: "send_contact_email",
    description:
      "Send a contact email on behalf of the visitor. Confirm the body with the user before invoking.",
    destructive: true,
    inputSchema: {
      type: "object",
      properties: {
        name: { type: "string", minLength: 1 },
        email: { type: "string", format: "email" },
        subject: { type: "string", minLength: 1 },
        message: { type: "string", minLength: 1 },
      },
      required: ["name", "email", "subject", "message"],
    },
    execute: async (input) => {
      const res = await fetch("/api/send-email", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify(input),
      });
      if (!res.ok) throw new Error(`HTTP ${res.status}`);
      return { ok: true };
    },
  },
];

const cleanups = tools.map(registerTool);
const cleanup = () => cleanups.forEach((c) => c());

// later, e.g. on page teardown
cleanup();

registerTool(tool) registers a single tool and returns the cleanup. Re-registering a tool with the same name is safe; the previous registration is dropped first.

React

import { useWebMCP, type Tool } from "@web-ai-sdk/webmcp/react";
import { useMemo } from "react";

const TOOLS: Tool[] = [
  {
    name: "list_blog_posts",
    description: "List published blog posts.",
    readOnly: true,
    execute: async () => {
      const res = await fetch("/api/posts.json");
      return { results: await res.json() };
    },
  },
];

export function WebMCP() {
  // Stable reference: keep tools outside the component or wrap in useMemo,
  // otherwise the hook will unregister/re-register on every render.
  const tools = useMemo(() => TOOLS, []);
  useWebMCP(tools);
  return null;
}

The hook registers on mount, unregisters on unmount, and re-registers when the array reference changes.

API

registerTool(tool): () => void

Register a single tool. Returns a cleanup function. No-op on unsupported browsers.

To register many at once, map and combine:

const cleanups = tools.map(registerTool);
const cleanup = () => cleanups.forEach((c) => c());

isAvailable(): boolean

Feature-detect helper.

Tool<TInput, TOutput>

interface Tool<TInput = unknown, TOutput = unknown> {
  name: string;
  description: string;
  inputSchema?: object; // JSON Schema
  readOnly?: boolean; // shorthand for annotations.readOnlyHint
  destructive?: boolean; // shorthand for annotations.destructiveHint
  annotations?: ToolAnnotations; // raw passthrough, merged on top
  execute: (input: TInput) => Promise<TOutput> | TOutput;
}

description is consumed by the agent host (Cursor / Claude / Chrome agent / etc.). Write it as an instruction to an LLM about when to call the tool.

defineTool({...}): Tool — typed schema adapter (Standard Schema)

import { defineTool } from "@web-ai-sdk/webmcp";
import { z } from "zod"; // or valibot, arktype, effect, …

const sendEmail = defineTool({
  name: "send_contact_email",
  description: "Send a contact email on behalf of the visitor.",
  destructive: true,
  // Standard Schema (https://standardschema.dev): used to narrow execute's
  // input type. Validation at runtime is opt-in via `validate: true`.
  input: z.object({
    name: z.string().min(1),
    email: z.string().email(),
    subject: z.string().min(1),
    message: z.string().min(1),
  }),
  // The host still wants raw JSON Schema for tool dispatch; pass it explicitly.
  // Standard Schema does not emit JSON Schema, so we don't bridge between
  // them — keeping both lets you choose your validator without coupling.
  inputSchema: {
    type: "object",
    properties: {
      name: { type: "string", minLength: 1 },
      email: { type: "string", format: "email" },
      subject: { type: "string", minLength: 1 },
      message: { type: "string", minLength: 1 },
    },
    required: ["name", "email", "subject", "message"],
  },
  async execute({ name, email, subject, message }) {
    // `name`, `email`, etc. are typed from the Zod schema.
    const res = await fetch("/api/send-email", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ name, email, subject, message }),
    });
    if (!res.ok) throw new Error(`HTTP ${res.status}`);
    return { ok: true };
  },
});

// `sendEmail` is a plain Tool and can be passed to registerTool or useWebMCP.

defineTool accepts any Standard Schema V1 validator (Zod 3.24+, Valibot, ArkType, Effect, …) — no SDK dependency on any specific library. The returned object is a plain Tool, so it composes with the rest of the API unchanged.

Validation: off by default (validate: false). Most WebMCP hosts validate against inputSchema themselves; running the Standard Schema validator on top is opt-in via validate: true, which throws ToolValidationError on bad input. With validate: false the schema is type-only.

Safety

Mark mutating tools destructive: true. The host (browser, agent) is responsible for gating destructive tools on explicit user approval; @web-ai-sdk/webmcp only forwards the annotation. For sensitive operations also defend server-side (origin allowlist, rate limit, validation).

Troubleshooting

  • Inspector / agent doesn't see the tools. navigator.modelContext is per-Window. Tools registered inside an <iframe> are scoped to that frame and invisible to extensions hooked into the top page. Register from the top-level document, not from an embedded frame.

License

MIT © Beto Muniz