npx claudepluginhub webmcp-org/npm-packagesWant just this skill?
Then install: npx claudepluginhub u/[userId]/[slug]
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.
This skill is limited to using the following tools:
references/HELPERS_API.mdreferences/PRODUCTION_TESTING.mdreferences/REACT_INTEGRATION.mdreferences/SELF_TESTING.mdreferences/TOOL_DESIGN.mdreferences/TROUBLESHOOTING.mdreferences/USERSCRIPT_GUIDE.mdreferences/VANILLA_JS.mdWebMCP Development
Create MCP tools for any website or web app. Inject, test, iterate.
Quick Start
The universal development loop:
- Navigate:
navigate_pageto target URL - Reload:
navigate_pagewithtype="reload"(clears stale state from prior sessions) - Explore:
take_snapshotto understand page structure - Write: Tool code (just JavaScript with
execute:function) - Inject:
inject_webmcp_script- polyfill auto-injected if needed - Verify:
diff_webmcp_toolsto see registered tools - Test: Call tools directly:
mcp__chrome-devtools__webmcp_{domain}_page{idx}_{name} - Debug:
list_console_messagesif failures - 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 Name | Becomes |
|---|---|
search_pages | webmcp_notion_so_page0_search_pages |
list_orders | webmcp_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_storieswebmcp_old_reddit_com_page0_get_postswebmcp_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.
diff_webmcp_tools-> Tools appear aswebmcp_{domain}_page{idx}_{name}- Call each tool using the correct calling convention (see above)
- If fails:
list_console_messages+take_snapshot - Fix -> reinject -> retest
- 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 fieldssubmit_*_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 Newsgithub.js- GitHub repositorieswikipedia.js- Wikipedia articlesreddit.js- Reddit posts/commentsyoutube.js- YouTube videostwitter.js- Twitter/Xamazon.js- Amazon productslinkedin.js- LinkedIn profiles/jobsstackoverflow.js- Stack Overflow Q&Anotion.js- Notion pages/databasesgoogle-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
Common issues:
| Issue | Solution |
|---|---|
| Connection timeout / stale polyfill | navigate_page with type="reload", then reinject |
| CSP blocking injection | Use browser extension approach |
| Tools not registering | Check console for errors |
| Stale tools after navigation | Reinject script |
| Selector not finding element | Use take_snapshot to verify page state |
| TypeScript build fails | Check for syntax errors, run pnpm install |
| Imports not resolved | Ensure dependencies in package.json, run install |
handler not recognized | Use execute: instead of handler: in tool definition |
Similar Skills
Expert guidance for Next.js Cache Components and Partial Prerendering (PPR). **PROACTIVE ACTIVATION**: Use this skill automatically when working in Next.js projects that have `cacheComponents: true` in their next.config.ts/next.config.js. When this config is detected, proactively apply Cache Components patterns and best practices to all React Server Component implementations. **DETECTION**: At the start of a session in a Next.js project, check for `cacheComponents: true` in next.config. If enabled, this skill's patterns should guide all component authoring, data fetching, and caching decisions. **USE CASES**: Implementing 'use cache' directive, configuring cache lifetimes with cacheLife(), tagging cached data with cacheTag(), invalidating caches with updateTag()/revalidateTag(), optimizing static vs dynamic content boundaries, debugging cache issues, and reviewing Cache Component implementations.
Creating algorithmic art using p5.js with seeded randomness and interactive parameter exploration. Use this when users request creating art using code, generative art, algorithmic art, flow fields, or particle systems. Create original algorithmic art rather than copying existing artists' work to avoid copyright violations.