MCP Server Architecture
This page describes how the PinchTab MCP server is structured internally and how it integrates with the rest of the stack.
Overview
The MCP server is a thin stdio-based JSON-RPC 2.0 layer. It runs as a separate process (pinchtab mcp) and delegates every browser action to an already-running PinchTab instance via its REST API.
flowchart LR
A["AI Agent\n(Claude, Copilot, Cursorβ¦)"] -- "stdio / JSON-RPC 2.0" --> M["pinchtab mcp"]
M -- "HTTP / REST" --> P["PinchTab Server\nor Bridge"]
P -- "Chrome DevTools Protocol" --> C["Chrome"]
Key design decisions:
- No direct Chrome dependency β the MCP process has no CDP connection. All browser work is delegated to the PinchTab instance.
- Any deployment works β use
--serverflag to point at a local server, Docker container, or remote host. - Stateless protocol layer β the MCP server holds no browser state itself; it is purely a translation adapter.
Transport
The MCP server uses the stdio transport defined in the MCP specification 2025-11-25. The AI client writes JSON-RPC requests to stdin and reads responses from stdout. Logs and diagnostics go to stderr.
This transport is universally supported by MCP clients (Claude Desktop, VS Code, Cursor, and any SDK-based client).
Process Model
pinchtab mcp
β
βββ reads config port (default http://127.0.0.1:9867)
βββ --server flag (override for remote servers)
βββ reads PINCHTAB_TOKEN (env or config)
β
βββ creates internal/mcp.Client (HTTP client with 120 s timeout)
βββ registers 21 MCP tools via mcp-go SDK
βββ calls server.ServeStdio() (blocking read loop)
The process exits when stdin is closed by the client.
Code Layout
internal/mcp/
βββ server.go # NewServer() wires tools β handlers; Serve() starts stdio
βββ tools.go # allTools() β JSON-schema tool definitions for all 21 tools
βββ handlers.go # handlerMap() β one handler closure per tool
βββ client.go # Client β thin HTTP wrapper for PinchTab REST API
cmd/pinchtab/
βββ cmd_mcp.go # runMCP() β reads config, calls mcp.Serve()
server.go
NewServer creates an MCPServer via the mcp-go SDK, iterates allTools(), looks up the matching handler in handlerMap, and calls s.AddTool. A panic fires at startup if a tool has no handler, preventing silent gaps.
Serve wraps server.ServeStdio for the normal execution path.
tools.go
allTools returns a []mcp.Tool slice. Each tool is declared with:
- a name (
pinchtab_*) - a human-readable description used by the LLM to select the right tool
- typed parameter schemas with
Required()/Description()annotations
The declarations are grouped by category: Navigation, Interaction, Content, Tab Management, Utility.
handlers.go
Each handler is a factory function returning a func(context.Context, mcp.CallToolRequest) (*mcp.CallToolResult, error) closure. Handlers:
- Extract and validate arguments from
r.GetArguments() - Build the corresponding PinchTab REST payload
- Call
c.Getorc.Postwith the request context - Return
mcp.NewToolResultTexton success ormcp.NewToolResultErroron HTTP 4xx/5xx
The context passed from the MCP SDK carries the client's deadline, so long-running navigations will be cancelled if the client disconnects.
client.go
Client wraps net/http with:
- a 120-second timeout (covers page loads and PDF exports)
- optional
Authorization: Bearer <token>header injection - a 10 MB response body limit
- URL validation in
handleNavigate(must start withhttp://orhttps://)
Tool Categories
| Category | Count | REST Endpoints Used |
|---|---|---|
| Navigation | 4 | /navigate, /snapshot, /screenshot, /text |
| Interaction | 8 | /action (with action field) |
| Content | 3 | /evaluate, /pdf, /find |
| Tab Management | 4 | /tabs, /health, /cookies |
| Utility | 2 | /evaluate (wait-for-selector), local sleep |
Security Considerations
pinchtab_evalcalls/evaluate, which requiressecurity.allowEvaluate: truein the PinchTab config. It returns HTTP 403 by default. This is intentional β arbitrary JS execution is a separate opt-in from browser control.- URL validation β
pinchtab_navigaterejects non-HTTP/HTTPS URLs to prevent SSRF viafile://,javascript:, or custom schemes. - Token forwarding β the MCP client forwards the configured bearer token to PinchTab, so access control at the PinchTab layer applies to all tool calls.
- Wait caps β
pinchtab_waitandpinchtab_wait_for_selectorenforce a 30-second maximum to prevent agent runaway.