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

@zap-studio/webmcp

v1.0.1

Published

A framework-agnostic, SSR-safe wrapper around the native WebMCP document.modelContext API, with a batch tool registry for exposing JavaScript tools to AI agents.

Readme

@zap-studio/webmcp

A framework-agnostic, SSR-safe wrapper around the native WebMCP document.modelContext API, with a batch tool registry for exposing JavaScript tools to AI agents.

Full documentation: zapstudio.dev/webmcp

Motivation

WebMCP is a young API: a Web Machine Learning Community Group draft, not a W3C standard, shipping experimentally in Chrome/Edge as document.modelContext. It lets a page register JavaScript functions as "tools" — named, described, schema-typed — that an AI agent (browser-built-in, extension, or otherwise) can discover and call.

Calling document.modelContext directly has three rough edges for a real app. First, it crashes during server rendering — Next.js and TanStack Start both render on the server first, where there is no document at all. Second, unregistration is signal-based (abort a signal you passed at registration), which is easy to get wrong by hand for every tool a route exposes. Third, there is no batch primitive: a route with five tools means five separate registerTool calls and five separate cleanup paths to track.

@zap-studio/webmcp fixes all three: every function no-ops safely with no document, registerTool hands back a single idempotent unregister function per tool, and createToolRegistry groups a route's tools so they mount and unmount together. The public API stays small: defineTool, registerTool, createToolRegistry, and nothing else — no build step, no compiler plugin, no required dependency on the (still experimental) native API being present.

Installation

npm install @zap-studio/webmcp

Features

  • SSR-safe by default — every function checks for document first; nothing throws during Next.js or TanStack Start server rendering.
  • registerTool wraps document.modelContext.registerTool and returns a single idempotent unregister function, instead of making you manage an AbortController per tool.
  • defineTool validates a tool's name and description up front — the two fields an agent actually reads to decide whether, and how, to call the tool.
  • createToolRegistry batches a group of tools (e.g. everything a route exposes) behind one mount()/unmount() pair.
  • Typed errors — WebMCPNotSupportedError when the browser doesn't support WebMCP yet, plus hasWebMCPSupport() to check ahead of time.
  • No required runtime dependencies, and no assumption that the native API is stable — this package tracks the spec, it doesn't extend it.
  • No global type augmentation — modelContext is never merged into the ambient Document type (unsupported by JSR's public API checks, and it would leak into every consumer's own types); cast through the exported WebMCPDocument type for direct access.
  • React binding available separately as @zap-studio/webmcp-react.

Quick Start

import { defineTool, registerTool } from "@zap-studio/webmcp";

const likeTool = defineTool({
  name: "posts_like",
  description: "Like a post by ID",
  execute: async ({ id }: { id: string }) => {
    await likePost(id);
    return { liked: true };
  },
});

const unregister = await registerTool(likeTool);
// later, e.g. on route leave
unregister();

defineTool and registerTool

defineTool validates a tool's shape and returns it unchanged — name must be 1-128 characters (letters, digits, _, -, .), and description must be non-empty:

import { defineTool } from "@zap-studio/webmcp";

const shareTool = defineTool({
  name: "posts_share",
  description: "Share a post by ID",
  execute: async ({ id }: { id: string }) => ({ shared: true, id }),
});

registerTool does the actual work: it resolves to a no-op unregister function on the server, rejects with WebMCPNotSupportedError in a browser without WebMCP support, and otherwise registers the tool and returns an unregister function backed by an internal AbortSignal:

import { registerTool } from "@zap-studio/webmcp";

const unregister = await registerTool(shareTool);
unregister(); // idempotent — safe to call more than once

createToolRegistry

Groups tools that share a lifecycle, so they mount and unmount together instead of one registerTool call per tool:

import { createToolRegistry } from "@zap-studio/webmcp";

const registry = createToolRegistry();
registry.add(likeTool).add(shareTool);

await registry.mount(); // registers both
registry.unmount(); // unregisters both, e.g. on route leave

unmount() is idempotent, and safe to call even if mount() was never called.

Error Handling

registerTool rejects with WebMCPNotSupportedError when document.modelContext is unavailable. Check hasWebMCPSupport() first to skip registration entirely, or catch the error to degrade gracefully:

import { hasWebMCPSupport, registerTool, WebMCPNotSupportedError } from "@zap-studio/webmcp";

if (hasWebMCPSupport()) {
  await registerTool(likeTool);
}

// or:
try {
  await registerTool(likeTool);
} catch (error) {
  if (error instanceof WebMCPNotSupportedError) {
    // fall back to a regular button — no agent-callable tool here
  }
}

document.modelContext is never merged into the global Document type — cast through the exported WebMCPDocument type if you need to call it directly, beyond what registerTool/hasWebMCPSupport already cover:

import type { WebMCPDocument } from "@zap-studio/webmcp";

const modelContext = (document as WebMCPDocument).modelContext;
const tools = await modelContext?.getTools();

Runtime Support

| Runtime | Support | | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Node.js | SSR-safe no-op (>= 18.0.0) | | Bun | SSR-safe no-op (>= 1.0.0) | | Deno | SSR-safe no-op (>= 1.42) | | Cloudflare Workers | SSR-safe no-op | | Browsers | Chrome/Edge (experimental, behind a flag); other engines via a community polyfill such as @mcp-b/webmcp-polyfill, not a dependency of this package |

WebMCP itself is not yet a stable, cross-browser standard — this package tracks the current draft and will follow it as it evolves. Deno 1.42 is the first release that can install packages from JSR (deno add jsr:@zap-studio/webmcp).

License

MIT