loading…
Search for a command to run...
loading…
Don't let AI guess from screenshots. Give LLMs direct access to your React state, Context, and Data Grids. Features bidirectional communication via SharedWorker
Don't let AI guess from screenshots. Give LLMs direct access to your React state, Context, and Data Grids. Features bidirectional communication via SharedWorkers & WebSockets. Docker gateway included.
Give AI agents eyes and hands inside your live React app.
MCP-FE turns your browser into an active MCP node — letting agents like Claude or Cursor query live state, read user context, and trigger actions directly inside your frontend. No browser extension required.
// One hook. Claude can now see and control this component.
useMCPTool({
name: 'get_cart_items',
description: 'Returns current items in the shopping cart',
inputSchema: { type: 'object', properties: {} },
handler: async () => ({
content: [{ type: 'text', text: JSON.stringify(cartItems) }],
}),
});
// ✅ Available to remote agents via MCP proxy
// ✅ Available to browser's agent via navigator.modelContext (if supported)
🌐 Try the Live Demo — no setup required · 🎬 Watch demo · 🌍 mcp-fe.ai · 📦
pnpm add @mcp-fe/mcp-worker
AI agents are often runtime-blind: they can read your code, but they can't see the current DOM, the state of a Redux/Zustand store, or the exact interaction sequence that led to an error.
MCP-FE exposes the browser runtime as a first-class MCP Server so that context is retrievable on demand via tool calls.
In-app AI copilot — give users an AI assistant that truly understands your application. Because tools return structured component data rather than pixels or DOM nodes, the agent works reliably even as your UI evolves. "Book the cheapest Tuesday flight" or "fill the form with my usual details" — the agent reads live state and triggers actions directly, no browser extension required.
Support with full context — when a user opens a support chat, the agent immediately queries get_form_state or get_validation_errors instead of asking for screenshots. It sees exactly what the user sees — active errors, current field values, where they are in a flow — and resolves issues in seconds rather than back-and-forth exchanges.
Guided complex workflows — in tax forms, insurance claims, or ERP systems, users often get lost in deep menus and multi-step flows. The agent knows exactly which step they're on, what's already filled, and what's missing — and can navigate or configure the UI on their behalf instead of pointing them to documentation.
Ready for the browser AI era — as AI browser extensions and built-in browser agents become mainstream, apps with MCP-FE are already compatible. Via the WebMCP adapter, the browser's native agent gets structured semantic tools through navigator.modelContext instead of scraping the DOM. One registerTool() call covers both today's remote agents and tomorrow's browser-native ones.
This monorepo includes a small demo frontend app and the MCP proxy. Run the commands below to start a local live demo on your machine.
pnpm install
pnpm start
Navigate to http://localhost:4200 (or the port shown in your terminal). The browser worker will automatically register and connect.
Point your MCP-compatible agent to:
http://localhost:3001/mcpNote: the example app connects the worker to the proxy via WebSocket (e.g.,
ws://localhost:3001).
Runtime flow — how a tool call travels from agent to browser and back.
Traditional MCP integrations are backend-centric. Frontends usually push events continuously, whether anyone needs them or not.
MCP-FE inverts the flow:
SharedWorker (preferred) or ServiceWorker stores event history (IndexedDB) and coordinates tool calls.sequenceDiagram
participant A as 🤖 AI Agent (Claude/Cursor)
participant P as 🖥️ Node.js MCP Proxy
participant W as ⚙️ Shared/Service Worker
participant M as 🌐 Main Thread (App)
Note over A, M: The Pull Model: Context is retrieved only on demand
A ->> P: Call tool (e.g., 'get_react_state')
P ->> W: Forward call via WebSocket
W ->> M: Request data from registered handler
Note right of M: Handler accesses React State, <br/>DOM, or LocalStorage
M -->> W: Return serializable state/data
W -->> P: Send JSON-RPC response
P -->> A: Tool result (JSON)
Note over A: Agent now "sees" the UI runtime
SharedWorker (preferred):
ServiceWorker (fallback):
WorkerClient prefers SharedWorker and automatically falls back to ServiceWorker. It also supports passing an explicit ServiceWorkerRegistration to use a previously registered service worker.
The Shared/Service Worker acts as a lightweight edge node that enables you to:
The MCP worker never sends context proactively to the backend. Context is shared only when an AI agent explicitly requests it by calling a tool.
MCP-FE includes built-in support for the WebMCP specification (navigator.modelContext), an emerging W3C standard that allows web pages to register MCP tools directly with the browser. This means your tools are discoverable not only by remote AI agents (via the proxy), but also by browser-native agents, extensions, and assistive technologies.
Your App ──→ workerClient.registerTool('my-tool', ...)
│
├── ① Worker transport ──→ Proxy ──→ Remote AI agents (Claude, Cursor, ...)
│
└── ② WebMCP adapter ──→ navigator.modelContext.registerTool()
└──→ Browser's built-in agent / extensions
One registerTool() call → two delivery channels. Your tool handlers are written once and automatically served to both remote agents (via WebSocket + MCP proxy) and the browser's native agent system (via navigator.modelContext).
WebMCP is auto-detected — if the browser supports navigator.modelContext, tools are registered there automatically. No configuration needed:
// This single call registers the tool in BOTH systems:
await workerClient.registerTool(
'get_cart_items',
'Returns the current shopping cart contents',
{ type: 'object', properties: {} },
async () => ({
content: [{ type: 'text', text: JSON.stringify(getCart()) }],
}),
);
// ✅ Available to remote agents via MCP proxy
// ✅ Available to browser's agent via navigator.modelContext (if supported)
To explicitly disable WebMCP:
await workerClient.init({
backendWsUrl: 'ws://localhost:3001',
enableWebMcp: false, // opt-out
});
| Channel | Agent type | Transport | Requires proxy? |
|---|---|---|---|
| Worker + Proxy | Remote AI agents (Claude, Cursor, etc.) | WebSocket → HTTP/SSE | Yes |
| WebMCP | Browser's built-in agent, extensions, assistive tech | navigator.modelContext (in-process) |
No |
With WebMCP support, your frontend tools work even without a running proxy — the browser agent can invoke them directly. And when the proxy is running, remote agents get access too. Both channels coexist seamlessly.
📖 For implementation details, see libs/mcp-worker/docs/native-webmcp.md
Unlike traditional analytics or logging tools that stream data to third-party servers, MCP-FE is passive and restrictive:
registerTool or useMCPTool.Component overview — the three layers that make up MCP-FE.
The MCP-FE architecture is built on three core layers designed to keep the main application thread responsive while providing a persistent link to AI agents.
The Proxy acts as the gateway. It speaks the standard MCP Protocol towards the AI agent (via HTTP/SSE) and maintains a persistent WebSocket connection to the browser.
This is the "Brain" on the Frontend Edge. It runs in its own thread, meaning it doesn't slow down your UI.
This is where your React/Vue/JS code lives.
useMCPTool, your components register handlers that have direct access to the live DOM, State, and LocalStorage.graph TD
subgraph "AI Environment"
Agent["🤖 AI Agent (Claude/Cursor)"]
BrowserAgent["🌐 Browser Agent / Extensions"]
end
subgraph "Server"
Proxy["Node.js MCP Proxy"]
end
subgraph "Browser Runtime (FE Edge)"
subgraph "Main Thread (Frontend App)"
UI["React/Vue/JS App"]
Hooks["React Tools (useMCPTool)"]
State[("Live State / DOM")]
Tracker["Event Tracker"]
WebMCP["WebMCP Adapter"]
end
subgraph "Worker Context"
Worker["MCP Worker (Shared/Service)"]
DB[(IndexedDB)]
end
end
Agent <-->|MCP Protocol| Proxy
Proxy <-->|WebSockets| Worker
Worker <-->|Events/Tools| Hooks
Tracker -->|Log Events| Worker
Worker <-->|Persistence| DB
Hooks <-->|Direct Access| State
Hooks -->|Auto - register| WebMCP
WebMCP <-->|navigator . modelContext| BrowserAgent
style Agent fill: #f9f, stroke: #333, stroke-width: 2px
style BrowserAgent fill: #f9f, stroke: #333, stroke-width: 2px
style Worker fill: #bbf, stroke: #333, stroke-width: 2px
style Proxy fill: #dfd, stroke: #333, stroke-width: 2px
style WebMCP fill: #ffe0b2, stroke: #e65100, stroke-width: 2px
style State fill: #fff4dd, stroke: #d4a017
MCP-FE is delivered as a set of packages in this monorepo and can be consumed directly from your applications. For install instructions, APIs, and framework-specific examples, use the package READMEs:
| Package | What it's for | Docs |
|---|---|---|
@mcp-fe/mcp-worker |
Core: worker client + worker scripts + transport + dynamic tool registration | ./libs/mcp-worker/README.md |
@mcp-fe/event-tracker |
Core (optional): framework-agnostic event tracking (navigation/interactions/errors) | ./libs/event-tracker/README.md |
@mcp-fe/react-event-tracker |
React (optional): drop-in hooks for automatic navigation/click/input tracking | ./libs/react-event-tracker/README.md |
@mcp-fe/react-tools |
React (optional): hooks for registering tools with component lifecycle management | ./libs/react-tools/README.md |
mcp-server (Docker image) |
Proxy: Node.js MCP server that bridges remote agents ↔ browser worker | ./apps/mcp-server/README.md |
You can adopt MCP-FE incrementally. The smallest useful setup is:
mcp-server) somewhere reachable by your users' browsers.Minimal frontend setup:
pnpm add @mcp-fe/mcp-worker
import { workerClient } from '@mcp-fe/mcp-worker';
await workerClient.init({
backendWsUrl: 'ws://YOUR_PROXY_HOST:3001',
// Optional: custom paths for worker scripts (useful for cache-busting)
sharedWorkerUrl: '/mcp-shared-worker.js',
serviceWorkerUrl: '/mcp-service-worker.js',
});
@mcp-fe/mcp-worker + your own registerTool(...) handlers.@mcp-fe/event-tracker or @mcp-fe/react-event-tracker.@mcp-fe/mcp-worker + @mcp-fe/react-tools + @mcp-fe/react-event-tracker.import { workerClient } from '@mcp-fe/mcp-worker';
await workerClient.init({
backendWsUrl: 'ws://localhost:3001',
});
await workerClient.registerTool(
'get_user_data',
'Get current user information',
{ type: 'object', properties: {} },
async () => ({
content: [{ type: 'text', text: JSON.stringify(getCurrentUser()) }],
})
);
outputSchema)Tools can optionally declare an outputSchema — a JSON Schema describing the shape of the returned data. When provided, the agent receives strongly-typed, structured output instead of raw text, which makes it easier to consume tool results programmatically.
await workerClient.registerTool(
'get_cart_summary',
'Get current cart contents with totals',
{ type: 'object', properties: {} },
async () => ({
content: [{ type: 'text', text: JSON.stringify(getCartSummary()) }],
}),
{
outputSchema: {
type: 'object',
properties: {
items: { type: 'array' },
total: { type: 'number' },
currency: { type: 'string' },
},
required: ['items', 'total'],
},
}
);
With useMCPTool (React):
useMCPTool({
name: 'get_cart_summary',
description: 'Get current cart contents with totals',
inputSchema: { type: 'object', properties: {} },
outputSchema: {
type: 'object',
properties: {
items: { type: 'array' },
total: { type: 'number' },
currency: { type: 'string' },
},
required: ['items', 'total'],
},
handler: async () => ({
content: [{ type: 'text', text: JSON.stringify(getCartSummary()) }],
}),
});
We are actively working on hardening the proxy and worker. Contributions and PRs are highly welcome!
Resolved ✅
Strict JWT Verification: The Node proxy used a "mock" decoded JWT without verifying the signature. → Implemented jwtVerify() with HS256 (local mode) and JWKS-based RS256 validation (Keycloak mode).
Secure Token Transport: WebSockets initiated using → Migrated to an initial payload handshake: client sends ?token=... in the URL query string.{ type: "AUTH", token } as the first WebSocket message.
Open
Privacy-First Event Tracking: Default trackInput() hook captures raw input values. Roadmap: track length/hashes only, ignore type="password", introduce opt-in allowlist.
WebSocket Origin Validation: Stricter origin allowlist enforcement beyond the current CORS_ORIGIN configuration.
Data Retention Limits (client-side): SESSION_TTL_MINUTES is configurable on the server, but the local IndexedDB has no automatic TTL yet.
This project is currently a Proof of Concept. While the architecture is stable and demonstrates the power of MCP-FE, it is not yet intended for high-stakes production environments.
Current focus:
Contributions, issues, and architectural discussions are welcome!
Licensed under the Apache License, Version 2.0. See LICENSE for details.
Michal Kopecký — Frontend engineer & creator of MCP-FE
I built MCP-FE to solve the "runtime-blindness" of current AI agents. By treating the browser as an active edge-node, we can provide agents with deep, real-time context without sacrificing user privacy or network performance.
Добавь это в claude_desktop_config.json и перезапусти Claude Desktop.
{
"mcpServers": {
"mcp-fe-react-tools": {
"command": "npx",
"args": []
}
}
}