Skill
Community

webmcp

Install
1
Install the plugin
$
npx claudepluginhub webmcp-org/npm-packages

Want just this skill?

Then install: npx claudepluginhub u/[userId]/[slug]

Description

Create MCP tools for any website or web app. Use when the user wants to: - Add AI tools to their React/Vue/Next.js app - Create userscripts for sites they don't control (Notion, GitHub, etc.) - Test MCP tools on their running app (Rails, Django, Laravel) - Add MCP to vanilla JS/HTML apps - Create a distributable site package with tools + documentation Enables autonomous iteration: write code -> inject -> test -> fix -> repeat. Self-verifies via chrome-devtools-mcp. TypeScript files are auto-bundled via esbuild.

Tool Access

This skill is limited to using the following tools:

ReadWriteEditBashGlobGrepmcp__chrome-devtools__*mcp__docs__SearchWebMcpDocumentation
Supporting Assets
View in Repository
references/HELPERS_API.md
references/PRODUCTION_TESTING.md
references/REACT_INTEGRATION.md
references/SELF_TESTING.md
references/TOOL_DESIGN.md
references/TROUBLESHOOTING.md
references/USERSCRIPT_GUIDE.md
references/VANILLA_JS.md
Skill Content

WebMCP Development

Create MCP tools for any website or web app. Inject, test, iterate.

Quick Start

The universal development loop:

  1. Navigate: navigate_page to target URL
  2. Reload: navigate_page with type="reload" (clears stale state from prior sessions)
  3. Explore: take_snapshot to understand page structure
  4. Write: Tool code (just JavaScript with execute: function)
  5. Inject: inject_webmcp_script - polyfill auto-injected if needed
  6. Verify: diff_webmcp_tools to see registered tools
  7. Test: Call tools directly: mcp__chrome-devtools__webmcp_{domain}_page{idx}_{name}
  8. Debug: list_console_messages if failures
  9. Iterate: Fix code, reinject, retest

IMPORTANT: Always reload the page before injecting to avoid stale polyfill issues.

// Minimal tool - no imports needed!
navigator.modelContext.registerTool({
  name: 'get_page_title',
  description: 'Get the current page title',
  inputSchema: { type: 'object', properties: {} },
  execute: async () => ({
    content: [{ type: 'text', text: document.title }],
  }),
});

Context Detection

React/Vue/Next.js app? -> See REACT_INTEGRATION.md

External site (Notion, GitHub)? -> See USERSCRIPT_GUIDE.md

Running Rails/Django/Laravel app? -> See PRODUCTION_TESTING.md

Vanilla JS/HTML? -> See VANILLA_JS.md

Tool Naming

After injection, tools become first-class MCP tools:

Your Tool NameBecomes
search_pageswebmcp_notion_so_page0_search_pages
list_orderswebmcp_localhost_3000_page0_list_orders

inject_webmcp_script Tool

The key tool for development iteration:

// Option 1: Inline code (quick prototyping)
inject_webmcp_script({
  code: `
    navigator.modelContext.registerTool({
      name: 'my_tool',
      description: 'Description here',
      inputSchema: { type: 'object', properties: {} },
      execute: async () => ({
        content: [{ type: 'text', text: 'Result' }]
      })
    });
  `,
  wait_for_tools: true, // Wait for registration (default: true)
  timeout: 5000, // Max wait time ms (default: 5000)
});

// Option 2: TypeScript file (production development)
inject_webmcp_script({
  file_path: './tools/src/mysite.ts',
  // TypeScript auto-bundled via esbuild (~10ms)
  // Imports from node_modules resolved automatically
});

Features:

  • Auto-detects if polyfill needed (checks navigator.modelContext)
  • Prepends @mcp-b/global polyfill if missing
  • Auto-bundles TypeScript (.ts/.tsx) via esbuild
  • Resolves imports from node_modules (e.g., @webmcp/helpers)
  • Waits for tools to register
  • Returns registered tool names
  • Handles CSP errors gracefully

@webmcp/helpers Package

NOTE: Not published yet. The site-package template includes inline helpers until published.

Lightweight tree-shakable helpers for userscripts:

// Helper functions (inlined in template until package published)
function textResponse(text: string): ToolResponse;
function jsonResponse(data: unknown, indent = 2): ToolResponse;
function errorResponse(message: string): ToolResponse;
function getAllElements(selector: string): Element[];
function getText(selectorOrElement: string | Element | null): string | null;
function waitForElement(selector: string, timeout = 5000): Promise<Element>;

See HELPERS_API.md for full API.

Calling Injected Tools

After injection, tools are automatically registered with the Chrome DevTools MCP server and become immediately callable. The server sends tools/list_changed notifications to inform clients about new tools.

Tool Naming Convention

Tools follow the pattern: webmcp_{sanitized_domain}_page{N}_{tool_name}

Examples:

  • webmcp_news_ycombinator_com_page0_get_stories
  • webmcp_old_reddit_com_page0_get_posts
  • webmcp_github_com_page1_search_repos

In Claude Code Sessions

When calling tools in Claude Code, use the MCP server prefix:

mcp__chrome -
  devtools__webmcp_news_ycombinator_com_page0_get_stories({
    limit: 10,
  });

The mcp__chrome-devtools__ prefix routes the call to the correct MCP server.

In MCP SDK / Tests

When using the MCP SDK directly (e.g., in test scripts), call by the tool name only:

await client.callTool({
  name: 'webmcp_news_ycombinator_com_page0_get_stories',
  arguments: { limit: 10 },
});

No prefix needed - the SDK already knows which server to call.

Self-Testing Protocol

CRITICAL: Verify every tool before considering done.

  1. diff_webmcp_tools -> Tools appear as webmcp_{domain}_page{idx}_{name}
  2. Call each tool using the correct calling convention (see above)
  3. If fails: list_console_messages + take_snapshot
  4. Fix -> reinject -> retest
  5. Only done when ALL tools pass

See SELF_TESTING.md for detailed protocol.

Tool Design Principles

Categories by safety:

  • Read-only (readOnlyHint: true): No side effects
  • Read-write: Modify UI, reversible
  • Destructive (destructiveHint: true): Permanent actions

Two-tool pattern for forms:

  • fill_*_form (read-write) - populate fields
  • submit_*_form (destructive) - commit changes

Handler return format:

// Success
return {
  content: [{ type: 'text', text: 'Result data' }],
};

// Error
return {
  content: [{ type: 'text', text: 'Error message' }],
  isError: true,
};

See TOOL_DESIGN.md for patterns.

Common Patterns

Scraping Data

navigator.modelContext.registerTool({
  name: 'get_items',
  description: 'Get items from the page',
  inputSchema: {
    type: 'object',
    properties: {
      limit: { type: 'number', description: 'Max items (default: 10)' },
    },
  },
  execute: async ({ limit = 10 }) => {
    const items = [];
    document.querySelectorAll('.item').forEach((el, i) => {
      if (i >= limit) return;
      items.push({
        title: el.querySelector('.title')?.textContent?.trim() || '',
        url: el.querySelector('a')?.href || '',
      });
    });
    return {
      content: [{ type: 'text', text: JSON.stringify(items, null, 2) }],
    };
  },
});

Form Filling

// Step 1: Fill form (read-write, reversible)
navigator.modelContext.registerTool({
  name: 'fill_contact_form',
  description: 'Fill contact form fields. Call submit_contact_form to send.',
  inputSchema: {
    type: 'object',
    properties: {
      name: { type: 'string' },
      email: { type: 'string' },
      message: { type: 'string' },
    },
  },
  execute: async ({ name, email, message }) => {
    if (name) document.querySelector('#name').value = name;
    if (email) document.querySelector('#email').value = email;
    if (message) document.querySelector('#message').value = message;
    return { content: [{ type: 'text', text: 'Form filled' }] };
  },
});

// Step 2: Submit form (destructive)
navigator.modelContext.registerTool({
  name: 'submit_contact_form',
  description: 'Submit the contact form. Sends the message.',
  inputSchema: { type: 'object', properties: {} },
  execute: async () => {
    document.querySelector('form#contact').submit();
    return { content: [{ type: 'text', text: 'Form submitted' }] };
  },
});

Navigation

navigator.modelContext.registerTool({
  name: 'navigate_to',
  description: 'Navigate to a section',
  inputSchema: {
    type: 'object',
    properties: {
      section: { type: 'string', enum: ['home', 'about', 'contact'] },
    },
    required: ['section'],
  },
  execute: async ({ section }) => {
    const urls = { home: '/', about: '/about', contact: '/contact' };
    window.location.href = urls[section];
    return { content: [{ type: 'text', text: `Navigating to ${section}...` }] };
  },
});

When to Use This Skill

Use when:

  • Creating MCP tools for any website
  • Testing tools on your running app
  • Adding MCP to vanilla JS/HTML/CSS apps
  • Prototyping before committing to an approach

Don't use when:

  • Site already has WebMCP (just use existing tools)
  • Just exploring without building tools

API Reference

Search docs for specifics:

mcp__docs__SearchWebMcpDocumentation("registerTool inputSchema")
mcp__docs__SearchWebMcpDocumentation("tool annotations")

Example Scripts

Reference implementations available in examples/:

  • hackernews.js - Hacker News
  • github.js - GitHub repositories
  • wikipedia.js - Wikipedia articles
  • reddit.js - Reddit posts/comments
  • youtube.js - YouTube videos
  • twitter.js - Twitter/X
  • amazon.js - Amazon products
  • linkedin.js - LinkedIn profiles/jobs
  • stackoverflow.js - Stack Overflow Q&A
  • notion.js - Notion pages/databases
  • google-docs.js - Google Docs

Creating a Site Package

For distributable tools + documentation, use the site package template:

# Copy template
cp -r templates/site-package ./mysite-mcp

# Update placeholders: {{site}}, {{Site}}, {{site_url}}
# Edit: SKILL.md, tools/src/{{site}}.ts, etc.

# Install dependencies
cd mysite-mcp/tools && npm install

# Develop tools
inject_webmcp_script({ file_path: "./mysite-mcp/tools/src/mysite.ts" })

Site package structure:

mysite-mcp/
├── SKILL.md              # Main skill (Setup + Workflows)
├── tools/
│   ├── package.json      # Dependencies (@webmcp/helpers)
│   └── src/mysite.ts     # Tool implementations
├── reference/
│   ├── api.md            # Detailed API docs
│   └── workflows.md      # Usage examples
└── README.md

The SKILL.md includes a Setup section that tells future agents where to find and inject the tools.

Troubleshooting

See TROUBLESHOOTING.md

Common issues:

IssueSolution
Connection timeout / stale polyfillnavigate_page with type="reload", then reinject
CSP blocking injectionUse browser extension approach
Tools not registeringCheck console for errors
Stale tools after navigationReinject script
Selector not finding elementUse take_snapshot to verify page state
TypeScript build failsCheck for syntax errors, run pnpm install
Imports not resolvedEnsure dependencies in package.json, run install
handler not recognizedUse execute: instead of handler: in tool definition
Stats
Stars41
Forks6
Last CommitMar 15, 2026

Similar Skills