A universal plugin framework for development tools that enables seamless browser-server communication and MCP (Model Context Protocol) integration with AI/LLM systems.
- 🔌 Universal Plugin System - Create plugins once, use everywhere
- 🌐 Multi-Bundler Support - Works with Vite, Webpack, Rspack, Farm, and more via unplugin
- 🔄 Real-time Communication - WebSocket-based bidirectional RPC between browser and development server
- 🤖 MCP Integration - Built-in Model Context Protocol server for AI/LLM automation
- 🎯 DOM Inspector Plugin - Out-of-the-box DOM inspection and manipulation for web automation
- 🛠️ Development-Only - Zero production overhead, only runs in dev mode
npm install -D unplugin-devpilot
npm install -D devpilot-plugin-dom-inspectorVite
// vite.config.ts
import DomInspector from 'devpilot-plugin-dom-inspector';
import Devpilot from 'unplugin-devpilot/vite';
import { defineConfig } from 'vite';
export default defineConfig({
plugins: [
Devpilot({
plugins: [DomInspector],
}),
],
});Webpack
// webpack.config.js
import DomInspector from 'devpilot-plugin-dom-inspector';
import Devpilot from 'unplugin-devpilot/webpack';
export default {
plugins: [
Devpilot({
plugins: [DomInspector],
}),
],
};Rspack
// rspack.config.ts
import DomInspector from 'devpilot-plugin-dom-inspector';
import Devpilot from 'unplugin-devpilot/rspack';
export default {
plugins: [
Devpilot({
plugins: [DomInspector],
}),
],
};Add this import to your project entry point to enable the devpilot client:
// main.ts or main.js (entry point)
import 'virtual:devpilot-client';This import activates the WebSocket connection to the development server and initializes all registered plugins on the client side.
Core plugin framework providing:
- Multi-bundler support through unplugin
- WebSocket server for browser-server communication
- MCP server for AI/LLM integration
- Plugin system with namespace isolation
- Virtual module generation for client-side code
Built-in DOM inspection plugin offering:
- Compact DOM snapshots optimized for LLM tokens
- Element querying via devpilot-id or CSS selectors (supports :has() and advanced selectors)
- Element interaction (click, text input)
- Scroll elements into view
- Visual layout analysis
- Browser console log access
- Page and element screenshot capture
- 8 MCP tools for web automation
MCP Tools:
get_page_snapshot- Get LLM-friendly DOM structure (compact, token-efficient)get_visual_hierarchy- Analyze visual layout hierarchy and coverageget_element_details- Get comprehensive element info (HTML + accessibility + position)click_element- Click elementsinput_text- Fill form fieldsget_console_logs- Access browser logs (filtered by client)scroll_to_element- Scroll element into view (for scrollable containers)capture_screenshot- Capture page or element screenshot (cross-origin images without CORS headers may appear blank)
Element ID Format: All element identifiers use the e prefix format (e.g., e1, e2, e123). The get_page_snapshot tool returns devpilotId in this format, which can be directly used in other APIs.
Automate browser interactions and DOM manipulation for testing and scripting.
Enable AI systems to interact with web applications through standardized MCP tools.
Build custom development tools and extensions with real-time browser access.
Debug and inspect web applications with real-time server communication.
┌────────────────────────────────────────────┐
│ Web Application Browser │
│ ┌─────────────────────────────────────┐ │
│ │ Virtual Module: devpilot-client │ │
│ │ - WebSocket Connection │ │
│ │ - RPC Handlers │ │
│ │ - Plugin Client Modules │ │
│ └─────────────────────────────────────┘ │
│ ▲ ▲ │
│ │ WebSocket │ RPC │
└───────────┼────────────────────┼───────────┘
│ │
┌───────────┼────────────────────┼──────────┐
│ ▼ ▼ │
│ ┌─────────────────────────────────────┐ │
│ │ Development Server (Node.js) │ │
│ │ ┌──────────────────────────────┐ │ │
│ │ │ WebSocket Server (:3100) │ │ │
│ │ │ - Client Management │ │ │
│ │ │ - RPC Routing │ │ │
│ │ └──────────────────────────────┘ │ │
│ │ ┌──────────────────────────────┐ │ │
│ │ │ MCP Server (:3101) │ │ │
│ │ │ - Tool Registration │ │ │
│ │ │ - Tool Invocation │ │ │
│ │ └──────────────────────────────┘ │ │
│ │ ┌──────────────────────────────┐ │ │
│ │ │ Plugin System │ │ │
│ │ │ - DOM Inspector │ │ │
│ │ │ - Custom Plugins │ │ │
│ │ └──────────────────────────────┘ │ │
│ └─────────────────────────────────────┘ │
│ ▲ │
│ │ MCP Protocol │
└───────────┼───────────────────────────────┘
│
┌───────┴───────┐
│ │
┌───▼──┐ ┌─────▼────┐
│ LLM │ │ AI Tools │
└──────┘ └──────────┘
Create a custom plugin:
import type { DevpilotPlugin } from 'unplugin-devpilot';
import { defineMcpToolRegister, resolveClientModule } from 'unplugin-devpilot';
export default {
namespace: 'my-plugin',
clientModule: resolveClientModule(import.meta.url, './client/index.mjs'),
serverSetup(ctx) {
return {
// Server-side RPC methods
myServerMethod: (arg: string) => `Result: ${arg}`,
};
},
mcpSetup(ctx) {
return [
defineMcpToolRegister(
'my_tool',
{
title: 'My Tool',
description: 'A custom MCP tool',
inputSchema: z.object({
param: z.string(),
}),
},
async (params) => {
// Tool implementation
return {
content: [{
type: 'text' as const,
text: `Tool result: ${params.param}`,
}],
};
},
),
];
},
} satisfies DevpilotPlugin;Each plugin gets a namespaced storage instance (powered by unstorage) via ctx.storage, available in both serverSetup and mcpSetup. Storage is isolated per plugin namespace, so plugins won't interfere with each other.
export default {
// In serverSetup or mcpSetup
serverSetup(ctx) {
return {
async saveData(items: MyData[]) {
// Domain-specific logic runs on the server
const existing = await ctx.storage.getItem<MyData[]>('key') || [];
const merged = [...existing, ...items];
await ctx.storage.setItem('key', merged);
},
};
},
mcpSetup(ctx) {
// MCP tools read directly from storage - no browser RPC needed
const data = await ctx.storage.getItem<MyData[]>('key') || [];
},
};The client can use createClientStorage for simple key-value operations that bridge to server storage via WebSocket RPC:
import { createClientStorage, getDevpilotClient } from 'unplugin-devpilot/client';
const client = getDevpilotClient();
const storage = createClientStorage(client, 'my-plugin');
await storage.setItem('key', value);
const data = await storage.getItem<MyType>('key');For domain-specific operations (e.g., incremental append with deduplication), define methods in serverSetup and call them from the client via rpcCall:
// shared-types.ts - Shared type ensures client and server stay in sync
export interface MyPluginServerMethods extends Record<string, (...args: any[]) => any> {
appendData: (items: MyData[]) => Promise<void>
}
// server (index.ts)
export default <DevpilotPlugin>{
serverSetup(ctx): MyPluginServerMethods {
return {
async appendData(items) {
const existing = await ctx.storage.getItem<MyData[]>('data') || [];
await ctx.storage.setItem('data', [...existing, ...items].slice(-500));
},
};
},
};// client
import { getDevpilotClient } from 'unplugin-devpilot/client';
const client = getDevpilotClient<MyPluginServerMethods>();
client.rpcCall('appendData', batch);This pattern keeps domain logic on the server, minimizes RPC payload, and maintains type safety across both sides.
- Node.js 22+
- pnpm@~9
pnpm installpnpm buildpnpm devpnpm testpnpm typecheckThe plugin automatically manages port allocation to prevent conflicts:
Devpilot({
wsPort: 3100, // Optional: WebSocket server port (random if not specified)
mcpPort: 3101, // Optional: MCP server port (random if occupied)
plugins: [/* ... */],
});Port Allocation Strategy:
- wsPort: When provided, the specified port is used if available; otherwise, a random available port is allocated. When not provided, a random available port is automatically allocated. This ensures no port conflicts.
- mcpPort: When not provided, defaults to 3101. If the port is already in use, an error will be thrown.
This ensures your MCP server runs on a predictable port. If the default port is occupied, you'll need to specify a different port or free up the occupied port.
Each plugin can be configured based on its implementation. Refer to individual plugin documentation.
- Zero Production Cost - Only runs in development mode
- Minimal Overhead - Lazy-loads plugin client modules
- Efficient Communication - Binary WebSocket messages
- Token Optimized - Compact DOM snapshots for LLM usage
- Ensure development server is running
- Check if port 3100 is not blocked by firewall
- Verify
wsPortconfiguration matches
- Confirm plugins are registered in configuration
- Check server logs for plugin loading errors
- Verify MCP server is running on port 3101
- Refresh the browser page to reconnect
- Check browser console for connection errors
- Use
get_visual_hierarchyorlist_clientstools to discover available clients
MIT © 2025 zcf0508
Contributions welcome! Please feel free to submit a Pull Request.