-
Notifications
You must be signed in to change notification settings - Fork 1
feat: add agent skills for webmcp-react setup and tool scaffolding #13
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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: | ||
|
|
||
| ```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`. | | ||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
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?