Skip to content

Latest commit

 

History

History
151 lines (112 loc) · 12.4 KB

File metadata and controls

151 lines (112 loc) · 12.4 KB

CLAUDE.md

Guidance for Claude Code / AI agents working in this repo. This file is loaded into every session — keep it lean. It is orientation + behavior only; detailed reference lives in docs/.

Project Overview

MCPProxy is a Go desktop application that acts as a smart proxy for AI agents using the Model Context Protocol (MCP): intelligent tool discovery, massive token savings, and built-in security quarantine against malicious MCP servers.

Stack: Go 1.26 (backend) · TypeScript 5.9 / Vue 3.5 (frontend) · Swift 5.9 (macOS tray). Storage: BBolt (config.db) + Bleve (search index). Avoid new dependencies without clear need.

Editions (Personal & Server)

Built in two editions from one codebase via Go build tags:

Edition Build Binary Distribution
Personal (default) go build ./cmd/mcpproxy mcpproxy macOS DMG, Windows installer, Linux tar.gz
Server go build -tags server -o mcpproxy-server ./cmd/mcpproxy mcpproxy-server Docker image (ghcr.io) only — no .deb / tar.gz

All server code is behind //go:build server in internal/serveredition/; the personal edition is unaffected. The binary self-identifies (mcpproxy version, /api/v1/status → "edition"). Server multi-user OAuth (Spec 024): see docs/development/server-edition-multiuser-auth.md.

Every feature decision should ask: "Does this make the personal edition so good that developers tell their teammates about it?"

Architecture

Core + Tray split: mcpproxy (headless HTTP API + MCP proxy) and mcpproxy-tray (GUI that manages the core). The tray is a UI controller — it holds no state; it reads/writes core config via REST + SSE. Tray↔core over a Unix socket (~/.mcpproxy/mcpproxy.sock) / named pipe on Windows; socket connections bypass the API key (OS-level auth), TCP requires it.

Directory Purpose
cmd/mcpproxy/ CLI entry point (Cobra)
cmd/mcpproxy-tray/ System tray app (state machine)
internal/runtime/ Lifecycle, event bus, background services
internal/server/ HTTP server, MCP proxy
internal/httpapi/ REST API (/api/v1)
internal/upstream/ 3-layer client: core/managed/cli
internal/config/ Configuration management
internal/index/ Bleve BM25 search index
internal/storage/ BBolt database
internal/oauth/ OAuth 2.1 + PKCE
internal/security/ Sensitive-data detection + quarantine
internal/serveredition/ Server-only code (//go:build server)
native/macos/MCPProxy/ Swift macOS tray app

See docs/architecture.md and docs/socket-communication.md.

Development Commands

# Build
go build -o mcpproxy ./cmd/mcpproxy                       # core (personal)
go build -tags server -o mcpproxy-server ./cmd/mcpproxy   # core (server edition)
make build                                                # frontend + backend
make build-docker                                         # server Docker image

# Test — ALWAYS run before committing
./scripts/test-api-e2e.sh                                 # quick API E2E (required)
go test -race ./internal/... -v                           # unit + race
go test -tags server ./internal/serveredition/... -race   # server edition
./scripts/run-all-tests.sh                                # full suite

# Lint — CI uses golangci-lint v2 with .github/.golangci.yml, which is STRICTER
# than the local scripts/run-linter.sh (v1.x) and catches things it misses.
# CI runs it TWICE: bare, and with --build-tags server (server-edition code is
# invisible to the bare run). Run both before pushing:
/opt/homebrew/bin/golangci-lint run --config .github/.golangci.yml ./...
/opt/homebrew/bin/golangci-lint run --config .github/.golangci.yml --build-tags server ./...
# CI also race-tests internal/server, httpapi and storage under -tags server
# with the unit-tests.yml -skip regex (bare `go test ./internal/server/...`
# hangs to the timeout on the binary-spawning tests):
go test -race -tags server -timeout 20m -skip "E2E|Binary|MCPProtocol|TestInfoEndpoint|TestGracefulShutdownNoPanic|TestSocketInfoEndpoint" ./internal/serveredition/... ./internal/config/... ./internal/oauth/... ./internal/server/... ./internal/httpapi/... ./internal/storage/...

# Run
./mcpproxy serve [--listen :8080] [--log-level=debug]     # core (localhost:8080)
./mcpproxy-tray                                           # tray (auto-starts core)

CLI management — mcpproxy upstream|tools|activity|token|telemetry|feedback|doctor|update … (update is channel-aware: guidance for package-manager installs, verified self-update only on tarball). Output: -o json|yaml, MCPPROXY_OUTPUT=json, --help-json (machine-readable for agents). References: docs/cli-management-commands.md · docs/cli/activity-commands.md · docs/features/agent-tokens.md · docs/cli-output-formatting.md.

Verifying Web-UI changes (Playwright sweep + HTML report) — required when touching frontend/src/: docs/development/web-ui-verification.md.

Configuration

Default locations: Config ~/.mcpproxy/mcp_config.json · Data ~/.mcpproxy/config.db (BBolt) · Index ~/.mcpproxy/index.bleve/ · Logs ~/.mcpproxy/logs/.

{
  "listen": "127.0.0.1:8080",
  "api_key": "auto-generated-if-empty",
  "require_mcp_auth": false,
  "enable_socket": true,
  "enable_web_ui": true,
  "mcpServers": [
    { "name": "github", "url": "https://api.github.com/mcp", "protocol": "http", "enabled": true },
    { "name": "ast-grep", "command": "npx", "args": ["ast-grep-mcp"], "working_dir": "/path", "protocol": "stdio", "enabled": true }
  ]
}

Env vars: MCPPROXY_LISTEN, MCPPROXY_API_KEY, MCPPROXY_DEBUG, MCPPROXY_TELEMETRY=false, HEADLESS. Full reference: docs/configuration.md.

MCP Protocol

Built-in tools: retrieve_tools (BM25 search across upstream tools; Spec 049 opt-in include_disabled; Spec 085 detail override + compact signatures under tool_response_mode: compact) · describe_tool (Spec 085: batch ≤5 ids → full schemas; Spec 102: also on the direct surface, server:tool or server__tool ids) · call_tool_read|write|destructive (Spec 018 intent variants; operation type inferred from the variant; Spec 085 pre-dispatch arg validation with self-healing invalid_params errors) · code_execution (sandboxed JS, on by default since v0.66.0) · upstream_servers (CRUD, Spec 049) · quarantine_security (Spec 032). Tool format: <serverName>:<toolName> (e.g. github:create_issue); the direct surface lists as <serverName>__<toolName> and accepts both.

REST API base /api/v1, auth via X-API-Key header or ?apikey=. MCP endpoints (/mcp) stay unprotected for client compatibility; the REST API always requires a key (auto-generated if absent). All responses carry X-Request-Id (correlate with mcpproxy activity list --request-id <id>). Live updates via SSE at /events. Full endpoint list: oas/swagger.yaml + docs/api/rest-api.md.

All server responses include a unified health field: level (healthy|degraded|unhealthy), admin_state (enabled|disabled|quarantined), plus summary/detail/action.

Connect payload (Spec 075): GET /api/v1/connect is content-read-free (stat-only; no macOS App-Data prompt) — each ClientStatus carries access_state="unknown". GET /api/v1/connect/{client} resolves it on-demand to accessible|absent|malformed|denied (+ remediation when denied); a denied connect/disconnect returns 403 with remediation. See docs/api/rest-api.md.

Security Model

  • Localhost-only by default (127.0.0.1:8080); API key always required (auto-generated and persisted if not provided).
  • Agent tokens: scoped credentials for AI agents (mcp_agt_ prefix, HMAC-SHA256 hashed). See docs/features/agent-tokens.md.
  • Quarantine: new servers quarantined until approved; Tool Poisoning Attack (TPA) detection on descriptions. Tool-level quarantine (Spec 032): SHA-256 hashes detect new ("pending") and changed ("changed", rug-pull) tools. Trusted (non-quarantined) servers auto-approve their current toolset as a baseline; post-baseline changes/additions are reviewed unless per-server auto_approve_tool_changes:true (MCP-2931, deprecates skip_quarantine). Config: quarantine_enabled (global), auto_approve_tool_changes (per-server). See docs/features/security-quarantine.md.
  • require_mcp_auth: when enabled, /mcp rejects unauthenticated requests (default off, for back-compat).
  • Sensitive-data detection (internal/security/): scans tool args/responses for secrets (cloud creds, private keys, API tokens, DB strings, Luhn-validated cards, sensitive file paths, high-entropy strings). On by default; integrates with the activity log. Config under sensitive_data_detection. See docs/features/sensitive-data-detection.md.

Key Implementation Details

  • Docker isolation: runtime detection (uvx→Python, npx→Node), image selection, container lifecycle. docs/docker-isolation.md
  • OAuth: dynamic port allocation, RFC 8252 + PKCE, internal/oauth/coordinator.go, automatic token refresh. docs/oauth-resource-autodetect.md
  • Code execution: sandboxed JavaScript (ES2020+) orchestrating multiple upstream tools in one request. docs/code_execution/overview.md
  • Connection management: exponential backoff; state machine Disconnected → Connecting → Authenticating → Ready.
  • Tool indexing: full rebuild on server changes, hash-based change detection, background indexing.
  • Tool-level quarantine (Spec 032) key files: internal/storage/models.go & bbolt.go, internal/runtime/tool_quarantine.go, internal/runtime/lifecycle.go (applyDifferentialToolUpdate), internal/server/mcp.go, internal/config/config.go, frontend/src/views/ServerDetail.vue.
  • Signal handling: graceful shutdown, context cancellation, Docker cleanup, double-shutdown protection. Before running the core, kill existing instances — it locks the DB.

Debugging

mcpproxy doctor                           # quick diagnostics
mcpproxy upstream list                    # server status
mcpproxy upstream logs <name> --follow    # per-server logs
tail -f ~/Library/Logs/mcpproxy/main.log  # main log (macOS; Linux: ~/.mcpproxy/logs/main.log)

Exit codes: 0 success · 1 general · 2 port conflict · 3 DB locked · 4 config · 5 permission · 6 shutdown timeout (graceful shutdown exceeded its 60s hard deadline after SIGINT/SIGTERM).

Development Guidelines

  • File organization: internal/ subdirectories, Go conventions. Tests: *_test.go; E2E in internal/server/e2e_test.go. E2E prereqs: Node.js, npm, jq, a built mcpproxy binary.
  • Error handling: structured logging (zap), context wrapping, graceful degradation.
  • Config changes: update both storage and file system; the file watcher hot-reloads.
  • macOS tray dev (build / replace / verify with mcpproxy-ui-test): docs/development/macos-tray.md.
  • Windows installer: docs/github-actions-windows-wix-research.md. Prerelease (next branch + v*-rc.* tags, opt-in, off stable channels): docs/prerelease-builds.md.

Recent Changes

  • 105-agent-scope-hardening: Go 1.26, backend-only, existing deps only — mcp-go v1.0.0 (WithToolFilter re-runs at tools/call), bleve (server_name facet), bbolt, zap/lumberjack. No new dependencies.
  • 058-mcp-2026-upgrade: Added Go 1.25.5 (go.mod toolchain) + mark3labs/mcp-go v0.57.0 → v1.0.0 (the only dependency change); existing santhosh-tekuri/jsonschema/v6, zap, Cobra, BBolt, Bleve. No new dependencies.
  • 103-token-bench: Go 1.25 (bench/) + existing only — tiktoken-go v0.1.8 (cl100k_base), the mcp-go transport already used by bench/mcpcaller.go. MCPMark is an external SHA-pinned tool invoked out of process, not a module dependency. No new dependencies.