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/.
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.
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?"
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.
# 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.
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.
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.
- 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, deprecatesskip_quarantine). Config:quarantine_enabled(global),auto_approve_tool_changes(per-server). See docs/features/security-quarantine.md. require_mcp_auth: when enabled,/mcprejects 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 undersensitive_data_detection. See docs/features/sensitive-data-detection.md.
- 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.
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).
- File organization:
internal/subdirectories, Go conventions. Tests:*_test.go; E2E ininternal/server/e2e_test.go. E2E prereqs: Node.js, npm, jq, a builtmcpproxybinary. - 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 (
nextbranch +v*-rc.*tags, opt-in, off stable channels): docs/prerelease-builds.md.
- 105-agent-scope-hardening: Go 1.26, backend-only, existing deps only — mcp-go v1.0.0 (
WithToolFilterre-runs attools/call), bleve (server_namefacet), bbolt, zap/lumberjack. No new dependencies. - 058-mcp-2026-upgrade: Added Go 1.25.5 (
go.modtoolchain) +mark3labs/mcp-gov0.57.0 → v1.0.0 (the only dependency change); existingsanthosh-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 bybench/mcpcaller.go. MCPMark is an external SHA-pinned tool invoked out of process, not a module dependency. No new dependencies.