AI代理任务管理工具

WebMCP演示项目,展示如何通过oRPC后端将Next.js应用的API路由暴露为WebMCP工具,使AI代理能直接从浏览器调用。

  • by Mark-Life
  • TypeScript
  • 本地部署
  • 3 星标
  • 更新于
在 GitHub 查看 Mark-Life/webMCP-example

使用说明

WebMCP Demo — oRPC Task Manager

Demo showing how a Next.js app with oRPC backend can expose its API routes as WebMCP tools — enabling AI agents to call them directly from the browser.

Includes a generic oRPC -> WebMCP bridge package (packages/orpc-webmcp) that auto-registers oRPC procedures as WebMCP tools.

What is WebMCP?

A browser-native JS API (document.modelContext) that lets web pages expose "tools" to AI agents.

Think: MCP but client-side, in the browser, with human-in-the-loop.

The specification is a Draft Community Group Report of the W3C Web Machine Learning Community Group, latest publication 28 July 2026. A Community Group Draft Report is not a W3C Standard and is not on the W3C Standards Track — it can and does change. This demo has been broken twice by it already.

API surface used here

// Registration is async and returns nothing. There is no handle.
await document.modelContext.registerTool(tool, { signal })

// Unregistration is aborting the signal you registered with.
controller.abort()

// Producer-side discovery and execution.
const tools = await document.modelContext.getTools()
await document.modelContext.executeTool(tools[0], '{"id":"1"}')

// Tool-list changes.
document.modelContext.addEventListener('toolchange', handler)

navigator.modelContext still resolves to the same object, but it is a deprecated back-compat alias — the getter moved from Navigator to Document in the 27 May 2026 draft (webmachinelearning/webmcp#173, PR #184) and Chrome deprecated it in Chrome 150. Reading it logs a deprecation warning. Nothing in this repo touches it.

unregisterTool(), callTool(), listTools() and registerToolsChangedCallback() are either removed from the spec or non-standard MCP-B extensions. This demo uses none of them.

Browser support

Browser Status
Chrome 150+ Native document.modelContext, behind a flag (see below)
Edge 147 Native
Chrome 149 Origin trial, on the old navigator.modelContext location
Firefox, Safari No shipping commitment

Everywhere else, the app loads @mcp-b/webmcp-polyfill, which installs document.modelContext itself. On Chrome 150+ the polyfill detects native support and stands down; on Chrome 149 it mirrors the native navigator.modelContext onto document.modelContext so page code only ever needs the one location.

Architecture

┌──────────────────────────────────────────────┐
│  Browser                                     │
│                                              │
│  ┌──────────────┐    ┌───────────────────┐   │
│  │ AI Agent     │◄──►│ document          │   │
│  │ (or the Tool │    │ .modelContext     │   │
│  │  Inspector)  │    └────────┬──────────┘   │
│  └──────────────┘             │              │
│                     execute() calls          │
│                               │              │
│                     ┌─────────▼──────────┐   │
│                     │ oRPC client        │   │
│                     │ (type-safe calls)  │   │
│                     └─────────┬──────────┘   │
│                               │              │
└───────────────────────────────┼──────────────┘
                                │ HTTP
┌───────────────────────────────┼──────────────┐
│  Next.js Server               │              │
│                     ┌─────────▼──────────┐   │
│                     │ oRPC router        │   │
│                     │ (procedures)       │   │
│                     └─────────┬──────────┘   │
│                               │              │
│                     ┌─────────▼──────────┐   │
│                     │ In-memory store    │   │
│                     └────────────────────┘   │
└──────────────────────────────────────────────┘

Stack

Layer Tech
Monorepo Turborepo + Bun
Frontend Next.js 16, React 19, Tailwind 4, shadcn/ui
Backend API oRPC 1.14
Schema Zod 4
WebMCP runtime @mcp-b/webmcp-polyfill 4 + @mcp-b/webmcp-types 4
WebMCP bridge packages/orpc-webmcp (included)

Why the polyfill and not @mcp-b/global

Upstream ships three packages. @mcp-b/webmcp-types is types only, @mcp-b/webmcp-polyfill is the runtime polyfill only, and @mcp-b/global adds the MCPB extras — an MCP bridge transport, prompts and resources, testing helpers — on top of the polyfill.

This demo registers tools and nothing else, and the MCP-B browser extension the bridge transport talks to is no longer available. So it takes the polyfill plus the types and skips @mcp-b/global, which would otherwise pull in @mcp-b/transports and @mcp-b/webmcp-ts-sdk and open a transport with no counterpart.

Getting Started

Prerequisites

  • Bun 1.3+
  • A Chromium-based browser

Install & Run

bun install
bun dev

App runs at http://localhost:3000. Tools register on load — the browser console prints [WebMCP] Registered 6/6 tools from router.

Using WebMCP

Any browser (polyfill)

The polyfill installs document.modelContext and, alongside it, navigator.modelContextTesting. From DevTools:

// List what the page exposes
await document.modelContext.getTools()

// Execute one. Arguments are a JSON *string*, not an object.
const tools = await document.modelContext.getTools()
await document.modelContext.executeTool(
  tools.find((t) => t.name === 'tasks.create'),
  JSON.stringify({ title: 'Buy groceries', description: 'Milk, eggs, bread' }),
)

Mutating tools call router.refresh() through the bridge's onMutate option, so the task list on the page updates as the tool runs.

Chrome 150+ (native)

  1. Open chrome://flags/#enable-webmcp-testing, set WebMCP for testing to Enabled, relaunch.
  2. Run the app and open http://localhost:3000.
  3. The native API takes over and the polyfill stands down. The same document.modelContext.getTools() / executeTool() calls work from DevTools.

Chrome also exposes navigator.modelContextTesting.listTools() and .executeTool(name, argsJson) under this flag. Note the different signature: the testing API executes by tool name, the producer API by the descriptor object from getTools().

Model Context Tool Inspector (visual)

A Chrome extension that lists the tools on a page and lets you fill in parameters and execute them without writing any console code.

Exposed Tools

The app registers these oRPC procedures as WebMCP tools:

Tool Type Description
tasks.list query List all tasks, optional status filter
tasks.get query Get single task by ID
tasks.create mutation Create a new task
tasks.update mutation Update task title/description/status
tasks.delete mutation Delete a task
tasks.search query Search tasks by keyword

exposeRouter() registers every procedure it finds, mutations included, and the readOnlyHint annotation is a hint to the agent, not a gate on execution. That is fine here — the store is in-memory and resets when the server restarts — but it is not a pattern to copy onto a real backend. Opt-in exposure and a human-confirmation path are being designed as part of turning the bridge into a published package.

Project Structure

apps/web/                        # Next.js app
  app/
    page.tsx                     # Task list UI
    actions/tasks.ts             # Server actions wrapping oRPC
    rpc/[[...rest]]/route.ts     # oRPC API route
  lib/
    orpc/                        # oRPC router, schemas, client, store
  components/
    providers.tsx                # Loads the polyfill, calls useWebMCP()
    ...                          # Task UI components

packages/orpc-webmcp/            # Generic oRPC-to-WebMCP bridge
  src/
    expose.ts                    # exposeRouter() — core bridge logic
    react.ts                     # React hook (useWebMCP)
    types.ts                     # Type definitions

How the bridge unregisters

registerTool() returns Promise<void> — no handle. One exposeRouter() call creates one AbortController and passes its signal to every registration; unregister() aborts it, which takes every tool in the batch back out at once.

exposeRouter() returns synchronously so useWebMCP's cleanup always has a controller to abort, even if the component unmounts before the registrations resolve. Aborting mid-flight removes tools that already landed and short-circuits the ones that have not. The returned handle also carries a ready promise that settles with the per-tool outcome; it never rejects, so ignoring it cannot produce an unhandled rejection.

References