AUXteam's picture
Upload folder using huggingface_hub
6a7089a verified

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 --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. 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