Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,16 @@ export default function App() {

That's it. The tool is registered on `navigator.modelContext` and can be called by WebMCP-compatible agents.

### Using an AI agent?

This repo ships with [agent skills](./skills) that can set up webmcp-react and scaffold tools for you. Install them with the [skills CLI](https://skills.sh):

```bash
npx skills add mcpcat/webmcp-react
```

Works with Cursor, Claude Code, GitHub Copilot, Cline, and [18+ other agents](https://vercel.com/docs/agent-resources/skills).

## How it works

[WebMCP](https://www.w3.org/community/webmcp/) is an emerging web standard that adds `navigator.modelContext` to the browser, an API that lets any page expose typed, callable tools to AI agents. Native browser support is still experimental and may evolve quickly. Chrome recently [released it in Early Preview](https://developer.chrome.com/blog/webmcp-epp).
Expand Down
213 changes: 213 additions & 0 deletions skills/webmcp-add-tool/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
---
name: webmcp-add-tool
description: Scaffolds a new WebMCP tool component using useMcpTool with Zod schema, handler, annotations, and wires it into the WebMCPProvider tree. Use when the user wants to expose functionality as an MCP tool, make something callable by AI, add a new tool, or create an AI-accessible action.
---

# Add a WebMCP Tool

## Overview

Each tool is a React component that calls `useMcpTool`. It registers on mount, unregisters on unmount. Tools can be headless (return `null`) or render UI showing execution state.

## Workflow

1. **Determine** the tool's name, description, input schema, and what the handler does
2. **Choose** headless vs UI tool
3. **Scaffold** using the appropriate template below
4. **Set annotations** based on tool behavior
5. **Wire** the component into the `<WebMCPProvider>` tree

## Template: Headless tool

For tools that do work without rendering anything visible:

```tsx
import { useMcpTool } from "webmcp-react";
import { z } from "zod";

export function MyTool() {
useMcpTool({
name: "my_tool",
description: "One-line description of what this tool does",
input: z.object({
// Define each input field with .describe() for AI context
query: z.string().describe("The search query"),
}),
handler: async ({ query }) => {
// Implement tool logic here
const result = await doSomething(query);
return {
content: [{ type: "text", text: JSON.stringify(result) }],
};
},
});
return null;
}
```

## Template: Tool with execution state UI

For tools that show loading, results, or errors:

```tsx
import { useMcpTool } from "webmcp-react";
import { z } from "zod";

export function MyTool() {
const { state, execute, reset } = useMcpTool({
name: "my_tool",
description: "One-line description of what this tool does",
input: z.object({
text: z.string().describe("Input text to process"),
}),
handler: async ({ text }) => {
const result = await process(text);
return { content: [{ type: "text", text: result }] };
},
onSuccess: (result) => {
// Optional: analytics, toast, etc.
},
onError: (error) => {
// Optional: error reporting
},
});

return (
<div>
<button onClick={() => execute({ text: "hello" })} disabled={state.isExecuting}>
{state.isExecuting ? "Processing..." : "Run"}
</button>
{state.lastResult && <p>{state.lastResult.content[0].text}</p>}
{state.error && <p className="error">{state.error.message}</p>}
<span>Executions: {state.executionCount}</span>
</div>
);
}
```

## Annotations

Set annotations to hint AI agents about tool behavior. Only include annotations that apply:

```tsx
useMcpTool({
// ...
annotations: {
title: "Human-friendly title", // Display name for the tool
readOnlyHint: true, // Tool only reads data, no side effects
destructiveHint: true, // Tool deletes or irreversibly modifies data
idempotentHint: true, // Safe to call multiple times with same input
openWorldHint: true, // Tool interacts with external systems
},
});
```

**Guideline for choosing annotations:**

| Tool behavior | Annotations to set |
|---|---|
| Fetches/queries data | `readOnlyHint: true` |
| Creates a record | `idempotentHint: false` (or omit, false is default) |
| Deletes or overwrites data | `destructiveHint: true` |
| Calls an external API | `openWorldHint: true` |
| Can be retried safely | `idempotentHint: true` |

## Return format

Handlers must return `CallToolResult`:

```tsx
// Text content (most common)
return {
content: [{ type: "text", text: "result string" }],
};

// Structured content (for machine-readable results alongside text)
return {
content: [{ type: "text", text: "Human summary" }],
structuredContent: { key: "value", count: 42 },
};

// Error result (caught by the framework, but you can also return errors explicitly)
return {
content: [{ type: "text", text: "Something went wrong" }],
isError: true,
};

// Image content
return {
content: [{ type: "image", data: base64String, mimeType: "image/png" }],
};
```

## Dynamic tools

Tools register on mount and unregister on unmount. Use conditional rendering to dynamically control which tools are available:

```tsx
function App({ user }) {
return (
<WebMCPProvider name="app" version="1.0">
<PublicTools />
{user.isAdmin && <AdminTools />}
{featureFlags.betaSearch && <BetaSearchTool />}
</WebMCPProvider>
);
}
```

## Wiring into the provider

Render the tool component anywhere inside `<WebMCPProvider>`. Common patterns:

**Dedicated tools file** (recommended for apps with many tools):

```tsx
// components/mcp-tools.tsx
export function McpTools() {
return (
<>
<SearchTool />
<TranslateTool />
<CheckoutTool />
</>
);
}

// In app root:
<WebMCPProvider name="app" version="1.0">
<McpTools />
<App />
</WebMCPProvider>
```

**Colocated with feature** (when the tool is tightly coupled to a specific component):

```tsx
function ProductPage({ product }) {
return (
<div>
<ProductDetails product={product} />
<AddToCartTool productId={product.id} />
</div>
);
}
```

## Naming conventions

- Tool names: `snake_case` (e.g., `search_catalog`, `delete_user`, `get_order_status`)
- Component names: PascalCase ending in `Tool` (e.g., `SearchCatalogTool`, `DeleteUserTool`)
- Descriptions: start with a verb, be specific (e.g., "Search the product catalog by keyword" not "Search stuff")

## Checklist

Before finishing, verify:

- [ ] Tool name is unique across the app
- [ ] Description clearly explains what the tool does (AI agents read this)
- [ ] All input fields have `.describe()` for AI context
- [ ] Handler returns `CallToolResult` with `content` array
- [ ] Appropriate annotations are set
- [ ] Component is rendered inside `<WebMCPProvider>`
- [ ] For Next.js: file has `"use client"` directive
167 changes: 167 additions & 0 deletions skills/webmcp-setup/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
---
name: webmcp-setup
description: Bootstraps webmcp-react into an existing React or Next.js app. Installs dependencies, adds WebMCPProvider, creates a first tool, and configures the MCP client bridge. Use when the user wants to set up WebMCP, add MCP tools to their app, integrate webmcp-react, or make their React app accessible to AI agents.
---

# Set Up webmcp-react

## Overview

`webmcp-react` exposes React app functionality as typed tools on `navigator.modelContext` (the W3C WebMCP API). AI agents discover and call these tools. This skill bootstraps the full setup.

## Step 1: Install dependencies

```bash
npm install webmcp-react zod
```

`zod` is a peer dependency used for typed tool input schemas.

## Step 2: Add WebMCPProvider

All `useMcpTool` calls must be descendants of `<WebMCPProvider>`. It installs a polyfill when native browser support is absent.

### React / Vite

Wrap the app root:

```tsx
import { WebMCPProvider } from "webmcp-react";

function App() {
return (
<WebMCPProvider name="my-app" version="1.0">
{/* existing app content */}
</WebMCPProvider>
);
}
```

### Next.js (App Router)

Create a client component wrapper since webmcp-react uses browser APIs:

```tsx
// app/providers.tsx
"use client";

import { WebMCPProvider } from "webmcp-react";

export function Providers({ children }: { children: React.ReactNode }) {
return (
<WebMCPProvider name="my-app" version="1.0">
{children}
</WebMCPProvider>
);
}
```

Then wrap `children` in the root layout:

```tsx
// app/layout.tsx
import { Providers } from "./providers";

export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
<Providers>{children}</Providers>
</body>
</html>
);
}
```

The library ships with a `"use client"` banner in its build output, so no `transpilePackages` config is needed when installing from npm.

## Step 3: Create a first tool

Create a simple tool component to verify the wiring works:

```tsx
// components/mcp-tools.tsx (add "use client" at top if Next.js)
import { useMcpTool } from "webmcp-react";
import { z } from "zod";

export function GreetTool() {
useMcpTool({
name: "greet",
description: "Greet someone by name",
input: z.object({
name: z.string().describe("The name to greet"),
}),
handler: async ({ name }) => ({
content: [{ type: "text", text: `Hello, ${name}!` }],
}),
});
return null;
}
```

Render it inside the provider:

```tsx
<WebMCPProvider name="my-app" version="1.0">
<GreetTool />
{/* rest of app */}
</WebMCPProvider>
```

## Step 4: Connect to AI clients

Desktop MCP clients (Cursor, Claude Code) cannot access `navigator.modelContext` directly. A Chrome extension + local MCP server bridges the gap.

### 4a. Install the Chrome extension

Install the "WebMCP Bridge" extension from the Chrome Web Store, or build from source:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still not available on Chrome Web Store yet. Maybe make this a "If available on the Chrome Web Store" so the agent doesn't error on this step and stop?


```bash
git clone https://github.com/MCPCat/webmcp-react.git
cd webmcp-react/extension && pnpm install && pnpm build
```

Then load unpacked from `extension/dist/` at `chrome://extensions/`.

### 4b. Configure the MCP client

**Cursor** -- add to `.cursor/mcp.json`:

```json
{
"mcpServers": {
"webmcp-server": {
"command": "npx",
"args": ["webmcp-server"]
}
}
}
```

**Claude Code:**

```bash
claude mcp add --transport stdio webmcp-server -- npx webmcp-server
```

### 4c. Activate the extension

1. Open your app in Chrome
2. Click the WebMCP Bridge extension icon
3. Choose "Always on" for persistent activation, or "Until reload" for one-shot testing
4. Green dot = connected to MCP client and tools are available

## Step 5: Verify

1. Run the app in the browser
2. Open the extension popup -- registered tools should appear
3. From Cursor or Claude Code, the tools should be discoverable and callable

## Troubleshooting

| Symptom | Fix |
|---------|-----|
| Tools not appearing in AI client | Check extension is activated (green/yellow dot). Verify MCP server is running. |
| Yellow dot (no MCP connection) | Ensure `webmcp-server` is configured in your MCP client. Check port 12315 is free. |
| `useMcpTool` warning about missing provider | Ensure the component is rendered inside `<WebMCPProvider>`. |
| Next.js hydration errors | Add `"use client"` to any file using `useMcpTool` or `WebMCPProvider`. |