webmcp-builder

WebMCP Tool Development Guide

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "webmcp-builder" with this command: npx skills add amedina/webmcp-agentic-template/amedina-webmcp-agentic-template-webmcp-builder

WebMCP Tool Development Guide

Overview

WebMCP is a browser API that lets web developers expose their application's functionality as "tools" — JavaScript functions with natural language descriptions and structured schemas — that AI agents, browser assistants, and assistive technologies can invoke. Think of it as turning a web page into an MCP server, but the tools run in client-side JavaScript instead of on a backend.

The key insight: WebMCP enables collaborative, human-in-the-loop workflows where users and agents work together within the same web interface. The user stays in control, the UI updates in real time, and the agent gets structured access to app functionality instead of having to scrape or automate the UI.

WebMCP also benefits accessibility — users with accessibility needs can complete tasks via conversational or agentic interfaces instead of relying solely on the accessibility tree, which many websites haven't fully implemented. See Accessibility-Focused Tool Design for concrete patterns.

WebMCP aligns closely with MCP tool schemas (name , description , inputSchema , execute ), so developers familiar with MCP can reuse their knowledge. The key difference: WebMCP tools run client-side in the browser, not on a backend server. The browser intermediates between the page and the agent, which allows it to enforce security policies and maintain backwards compatibility as MCP evolves. For always-on server-to-server tool access without a browser, use a traditional MCP server instead.

Quick Start

Here's a minimal, complete WebMCP tool — feature detect, register, execute, and return a result:

// Check if the browser supports WebMCP if ("modelContext" in navigator) { navigator.modelContext.registerTool({ name: "greet_user", description: "Returns a personalized greeting for the given name.", inputSchema: { type: "object", properties: { name: { type: "string", description: "The person's name" } }, required: ["name"] }, execute: ({ name }) => { document.getElementById("greeting").textContent = Hello, ${name}!; return Greeted ${name} successfully.; } }); }

This covers the four essentials: feature detection ("modelContext" in navigator ), tool registration (registerTool ), execution logic (update the DOM), and an informative return value. Everything below expands on this pattern.

Process

Phase 1: Understand the Requirements

Before writing any code, clarify what the web app needs to expose to agents:

  • What actions should agents be able to perform? List the core operations (e.g., "add item", "search", "filter results", "submit form").

  • What data should agents be able to read? Identify read-only queries (e.g., "get current state", "list items").

  • Does the tool set change based on UI state? Single-page apps may need to register/unregister tools as the user navigates between views.

  • What user interactions need confirmation? Destructive or irreversible actions (purchases, deletions) should use agent.requestUserInteraction() .

Guiding questions to ask before coding:

  • What existing JavaScript functions already do what you need? (Wrap them — don't rewrite.)

  • What's the app's framework? (Vanilla JS, React, Vue, etc. — this determines the UI sync pattern.)

  • Are there form validations or business rules that tools must respect?

  • What data is sensitive and should NOT be exposed as tool parameters?

  • Will the app be served over HTTPS in production? (Some browsers restrict modelContext to secure contexts.)

Phase 2: Design the Tools

Good WebMCP tools share these qualities:

  • Action-oriented names: Use verb-noun format like add_item , search_flights , set_filters . Kebab-case (add-item ) or snake_case (add_item ) are both acceptable — pick one and be consistent.

  • Clear descriptions: The description is what the agent reads to decide whether to use the tool. Be specific about what the tool does, what it returns, and any constraints.

  • Minimal required parameters: Only mark parameters as required if the tool truly cannot function without them. Use sensible defaults for optional parameters.

  • Structured input schemas: Use JSON Schema (type , properties , required , enum , description ) so agents know exactly what to pass.

  • Informative return values: Return text or structured content that tells the agent what happened. Include enough context for the agent to decide what to do next.

Bad vs. Good Examples

Tool naming:

  • ❌ data — vague, agent can't tell what it does

  • ✅ get_cart_contents — specific, action-oriented

Descriptions:

  • ❌ "Does stuff with the form" — agent has no idea what to expect

  • ✅ "Submits the contact form with the given name, email, and message. Returns a confirmation ID or validation errors." — agent knows inputs, outputs, and failure modes

Parameters:

  • ❌ Requiring user_email and user_location on a search tool that doesn't need them

  • ✅ Only requiring query , with optional max_results defaulting to 10

Tool Granularity

Balance between too many fine-grained tools and too few coarse ones:

  • Too fine: set_font_size , set_font_color , set_font_family → agent needs many calls for simple tasks

  • Too coarse: do_everything(instructions) → agent can't predict behavior, errors are vague

  • Right level: edit_design(instructions) for creative apps, or individual CRUD tools for data apps

When in doubt, start with one tool per user-facing action (each button, form submission, or filter corresponds to a tool).

Phase 3: Implement

Project Structure

WebMCP tools live in your web app's frontend code. A typical organization:

my-app/ ├── index.html ├── style.css ├── script.js # App logic + WebMCP tool registration

For larger apps, separate WebMCP code into its own module:

my-app/ ├── index.html ├── style.css ├── script.js # App logic ├── webmcp.ts # WebMCP tool definitions and registration

Complete Minimal Tool

Here's a complete tool you can copy-paste as a starting point — it includes feature detection, registration, execution with error handling, and an informative return value:

window.addEventListener('load', () => { if ("modelContext" in navigator) { navigator.modelContext.registerTool({ name: "add_todo", description: "Add a new todo item to the list. Returns confirmation with the current item count.", inputSchema: { type: "object", properties: { text: { type: "string", description: "The text of the todo item" } }, required: ["text"] }, annotations: { readOnlyHint: false, idempotentHint: false }, execute: ({ text }) => { if (!text.trim()) { return "Error: Todo text cannot be empty."; } addTodo(text); // Call your existing app function renderTodoList(); // Update the UI return Added todo: "${text}". You now have ${getTodoCount()} items.; } }); } });

The WebMCP API

The API lives on navigator.modelContext . Always feature-detect before using it, and always do so inside a window.addEventListener('load', ...) callback — never at the top level of a script. Browser extensions and runtimes that inject navigator.modelContext do so during or after page load; checking too early will always find it missing.

⚠️ HTTP required: navigator.modelContext is only available when the page is served over HTTP or HTTPS (e.g. http://localhost:8080 ). It will not be injected on file:// URLs. Always run a local dev server during development.

window.addEventListener('load', () => { if ("modelContext" in navigator) { // WebMCP is supported — register tools here } });

There are two registration approaches:

Approach 1: provideContext (batch registration)

Registers all tools at once. Calling it again replaces all previously registered tools. Good for simple apps or when the full tool set is known upfront.

navigator.modelContext.provideContext({ tools: [ { name: "add-todo", description: "Add a new todo item to the list", inputSchema: { type: "object", properties: { text: { type: "string", description: "The text of the todo item" } }, required: ["text"] }, execute: ({ text }, agent) => { addTodo(text); return { content: [ { type: "text", text: Added todo: "${text}" } ] }; } } ] });

Approach 2: registerTool / unregisterTool (incremental)

Add or remove individual tools. Better for SPAs where available tools change based on UI state.

navigator.modelContext.registerTool({ name: "search_flights", description: "Search for flights with the given parameters.", inputSchema: { type: "object", properties: { origin: { type: "string", description: "3-letter IATA airport code for origin", pattern: "^[A-Z]{3}$" }, destination: { type: "string", description: "3-letter IATA airport code for destination", pattern: "^[A-Z]{3}$" } }, required: ["origin", "destination"] }, execute: async ({ origin, destination }) => { const results = await searchFlights(origin, destination); return Found ${results.length} flights from ${origin} to ${destination}.; } });

// Later, when navigating away from search: navigator.modelContext.unregisterTool("search_flights");

Tool Definition Shape

Each tool object has these fields:

Field Required Description

name

Yes Unique identifier for the tool

description

Yes Natural language description of what the tool does

inputSchema

Yes JSON Schema object describing the parameters

execute

Yes Function (params, agent) => result that implements the tool

outputSchema

No JSON Schema describing the return value structure

annotations

No Hints like readOnlyHint , destructiveHint , idempotentHint , openWorldHint

The execute Function

The execute function receives two arguments:

  • params : An object with the parameters the agent passed, matching your inputSchema .

  • agent : An interface for interacting with the agent during execution.

It can be synchronous or async (return a Promise). The return value is sent back to the agent.

Return formats:

// Simple text response execute: ({ query }) => { return Found 5 results for "${query}"; }

// Structured content response (MCP-aligned) execute: ({ name }) => { return { content: [ { type: "text", text: Item "${name}" created successfully. } ] }; }

// Return data for the agent to process execute: () => { return JSON.stringify(getAppState()); }

Recommended Return Format

✅ Recommended: always include a success field and the new device state.

Returning a plain string (e.g. "Light turned on." ) is valid, but some agents treat an ambiguous response as a potential error. To give the agent unambiguous confirmation, return a JSON-stringified object with:

  • success: true/false — explicit boolean indicating whether the action succeeded.

  • message — human-readable description of what happened.

  • new_state — the updated state of the device(s) affected by the call, so the agent can verify the outcome without a follow-up get_* call.

  • On failure, include error instead of new_state .

// ✅ Success — clear confirmation + updated state execute: ({ light_id, action }) => { state.lights[light_id].on = (action === 'on'); renderLight(light_id); return JSON.stringify({ success: true, message: ${light_id} light turned ${action}., new_state: { light_id, on: state.lights[light_id].on }, }); }

// ✅ Failure — explicit flag so the agent knows to retry or report execute: ({ light_id, action }) => { if (!VALID_IDS.includes(light_id)) { return JSON.stringify({ success: false, error: Unknown light_id "${light_id}". Valid options: ${VALID_IDS.join(', ')}., }); } // ... }

For tools that affect multiple devices at once (e.g. a scene), include the full post-action snapshot in new_state so the agent doesn't need a separate status read.

User Interaction During Tool Execution

For actions that need user confirmation, use agent.requestUserInteraction() :

execute: async ({ product_id }, agent) => { const confirmed = await agent.requestUserInteraction(async () => { return new Promise((resolve) => { const ok = confirm(Purchase product ${product_id}?); resolve(ok); }); });

if (!confirmed) {
	throw new Error("Purchase cancelled by user.");
}

executePurchase(product_id);
return `Product ${product_id} purchased.`;

}

Annotations

Annotations help agents understand tool behavior without reading the implementation:

annotations: { readOnlyHint: true, // Tool only reads data, no side effects destructiveHint: false, // Tool doesn't delete or irreversibly modify data idempotentHint: true, // Calling multiple times with same args has same effect openWorldHint: false // Tool doesn't interact with external systems }

⚠️ Annotation values must be booleans (true /false ), not strings ("true" /"false" ). Passing strings will cause a runtime validation error (expected: "boolean" , code: "invalid_type" ).

Advanced Patterns

For advanced implementation details, please see Advanced Patterns. Topics include:

  • Choosing a UI Synchronization pattern (Direct DOM, Custom Events, Framework State) — with a decision guide

  • Dynamic Tool Registration for SPAs

  • Error Handling Patterns (DOM not found, network failure, invalid state, timeout)

  • Useful tips (Web Workers, toolactivated event, returning outputSchema )

Phase 4: Testing and Validation

Manual Testing Checklist

Verify these before moving to automated evals:

  • Feature detection: App works normally when navigator.modelContext is undefined (browsers without WebMCP support)

  • HTTP server: App is served over http:// or https:// , not file://

  • Load event: Tool registration is deferred to window.addEventListener('load', ...)

  • Annotation types: All annotation values are booleans (true /false ), not strings

  • Tool schema validation: inputSchema accurately describes what execute expects — mismatches cause agent errors

  • UI sync: After each tool call, the UI visually reflects the change

  • Return values: Every execute returns a JSON object with success: true/false , a message , and new_state (or error ) so the agent gets unambiguous confirmation — avoid returning plain strings that agents may misinterpret as errors

  • Error handling: Tools return { success: false, error: "..." } for invalid inputs, not unhandled exceptions or bare error strings

  • Edge cases: Test with missing optional parameters, empty strings, boundary values

  • Optional parameter defaults: Call tools with only required parameters — defaults should apply correctly

  • Destructive actions: Confirm requestUserInteraction() is used for purchases, deletions, etc.

  • Multiple calls: Calling the same tool twice in a row doesn't break state

Automated Evaluation

Use the WebMCP Evals CLI to test tool selection against AI agents. Write eval cases with natural language prompts and expected tool calls, then run them against your tool schema. For detailed setup and usage, see Testing WebMCP Tools.

Reference

Building

  • Advanced WebMCP Patterns — UI synchronization, SPA routing, error handling, patterns & tips

  • Simple vanilla JS examples — Pizza maker, restaurant booking, complete end-to-end example, accessibility patterns

  • React/TypeScript example — Flight search app with dynamic tool registration, complete component example

Quality

  • Testing guide — Manual checklist and automated evaluation with the Evals CLI

  • Security & privacy guide — Prompt injection, output injection walkthrough, over-parameterization, and a pre-ship checklist

Advanced

  • Service worker patterns — Background tool execution, session management, discovery, and routing

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Coding

Bitpanda Official

Query a Bitpanda account via the Bitpanda API using a bundled bash CLI. Covers all read-only endpoints: balances, trades, transactions, asset info, and live...

Registry SourceRecently Updated
Coding

OPC Landing Page Manager

Landing page strategy, copywriting, design, and code generation for solo entrepreneurs. From product idea to a complete, self-contained, conversion-optimized...

Registry SourceRecently Updated
Coding

kintone Ops

Build, query, and automate Cybozu kintone apps — Japan's leading no-code business platform with global deployments. Use this skill whenever the user mentions...

Registry SourceRecently Updated
Coding

Decode

Decode - command-line tool for everyday use

Registry SourceRecently Updated