| # 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. |
|
|
| ```mermaid |
| 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 `--server` flag 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](https://spec.modelcontextprotocol.io/). 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: |
|
|
| 1. Extract and validate arguments from `r.GetArguments()` |
| 2. Build the corresponding PinchTab REST payload |
| 3. Call `c.Get` or `c.Post` with the request context |
| 4. Return `mcp.NewToolResultText` on success or `mcp.NewToolResultError` on 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 with `http://` or `https://`) |
|
|
| ## 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_eval`** calls `/evaluate`, which requires `security.allowEvaluate: true` in 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_navigate` rejects non-HTTP/HTTPS URLs to prevent SSRF via `file://`, `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_wait` and `pinchtab_wait_for_selector` enforce a 30-second maximum to prevent agent runaway. |
|
|
| ## Related Pages |
|
|
| - [MCP User Guide](../mcp.md) |
| - [Architecture Overview](./index.md) |
| - [MCP Tool Reference](../reference/mcp-tools.md) |
| - [Security Guide](../guides/security.md) |
|
|