No description
  • Go 68.9%
  • Rust 26.3%
  • TypeScript 1.6%
  • JavaScript 1.2%
  • Shell 0.8%
  • Other 1.1%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Frank Hildebrandt f1b51fd54c
Some checks failed
Go CI / Formatting, vet, tests, and build (push) Failing after 1m12s
Go CI / Rust proxy workspace tests (push) Successful in 3m36s
feat: add web_search capability via DuckDuckGo HTML
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-20 19:31:11 +02:00
.agents/skills/forge chore: add agent skills, AGENTS.md, and media fixtures 2026-07-17 23:59:31 +02:00
.cursor/hooks/state feat: add web_search capability via DuckDuckGo HTML 2026-07-20 19:31:11 +02:00
.forgejo/workflows fix(ci): install Rust via rustup instead of unavailable Actions 2026-07-20 10:32:59 +02:00
cmd/private-proxy-mcp feat(cli): add cobra viper command foundation 2026-07-17 09:45:47 +02:00
docs feat: add web_search capability via DuckDuckGo HTML 2026-07-20 19:31:11 +02:00
internal feat: add web_search capability via DuckDuckGo HTML 2026-07-20 19:31:11 +02:00
proxy-rs feat: add web_search capability via DuckDuckGo HTML 2026-07-20 19:31:11 +02:00
scripts feat(release): add versioned multi-platform delivery 2026-07-17 17:24:47 +02:00
sidecar/browser-playwright feat(browser): add Playwright backend and shared identity/stealth 2026-07-17 23:57:54 +02:00
.dockerignore feat(release): add versioned multi-platform delivery 2026-07-17 17:24:47 +02:00
.gitignore feat(proxy-rs): add Rust connector and Tauri debug desktop 2026-07-18 01:49:51 +02:00
AGENTS.md docs: refresh AGENTS.md memory from continual learning 2026-07-20 19:30:58 +02:00
config.example.yaml feat: add web_search capability via DuckDuckGo HTML 2026-07-20 19:31:11 +02:00
Dockerfile feat(release): add versioned multi-platform delivery 2026-07-17 17:24:47 +02:00
go.mod merge: resolve main conflicts 2026-07-17 16:16:16 +02:00
go.sum merge: resolve main conflicts 2026-07-17 16:16:16 +02:00
Makefile feat: add web_search capability via DuckDuckGo HTML 2026-07-20 19:31:11 +02:00
README.md feat: add web_search capability via DuckDuckGo HTML 2026-07-20 19:31:11 +02:00

Private Proxy MCP Server

This repository contains the Go CLI and tunnel foundation for the private proxy MCP server. It provides two independently runnable commands:

  • mcp-server serves MCP Streamable HTTP at http://127.0.0.1:9334/mcp by default.
  • private-proxy maintains an outbound HTTP/1.1 upgrade connection to the public connector endpoint on port 9335 by default.

The MCP Streamable HTTP endpoint is POST /mcp. It exposes http_get for read-only GET/HEAD access and http_request for the methods configured in http.allowed_methods. Tool calls return both structured content and a compact JSON text block. HTTP 4xx and 5xx responses are normal tool results; validation, policy, timeout, execution, and unavailable-proxy failures are retryable or actionable tool execution errors as appropriate.

When a connected private proxy advertises web_search (default on), the dynamic tool list exposes structured DuckDuckGo HTML search. Search runs only on that private machine through httpexec / http.network_policy. Agents can still fetch search pages or result URLs with http_get / http_request. See Websuche.

When a connected private proxy advertises verified media binaries, the dynamic tool list also exposes media_download and youtube_transcript. Media jobs run only on that private machine. Large outputs remain there as expiring artifacts and are streamed through opaque links instead of being buffered by the MCP server. See Medien- und Transcript-Tools.

When the private proxy verifies a configured Chromium artifact, it additionally advertises isolated browser_* tools for navigation, bounded snapshots, interaction, waits and screenshot artifacts. The default backend is chromedp; playwright is available as a Node stdio sidecar. Both backends apply shared identity/stealth profiles so sessions look like a normal Chrome. Browser profiles and cookies are not reused, downloads are disabled, and every intercepted HTTP(S) navigation, redirect and subresource uses the existing network policy. See Browser-Tools, ADR 0001 and ADR 0002.

Both tools accept an optional client_profile. The default allowlist contains default, chrome-desktop, firefox-desktop, and mobile-chrome; the exact server-allowed names are published by tools/list. A profile applies a consistent, non-sensitive HTTP header identity, and the result reports only its effective name. Explicit request headers can override profile values only when the normal header policy permits them. Profiles never provide credentials or cookies, and do not change rate limits, destination policy, or SSRF checks.

Process liveness is available at GET /health/live. MCP readiness at GET /health/ready returns HTTP 503 until an authenticated private proxy tunnel is active and HTTP 200 while it is healthy. Health responses expose only live, ready, or not_ready.

private-proxy authenticates its HTTP/1.1 upgrade at GET /connector with private_proxy.shared_key. The mcp-server listens for these connections on private_proxy.listen_address. TLS certificates are verified by default; a custom CA and TLS server name can be configured. The proxy reconnects with bounded, jittered exponential backoff, resets the backoff after a successful upgrade, and uses tunnel ping/pong frames for liveness. A replaced connection receives goaway(replaced) before the new connection becomes the exclusive consumer. Set the same non-secret private_proxy.identity in both modes to correlate their request logs.

Run the CLI locally:

go run ./cmd/private-proxy-mcp --help
go run ./cmd/private-proxy-mcp mcp-server --private-proxy-shared-key "$CONNECTOR_KEY"
go run ./cmd/private-proxy-mcp private-proxy

Both commands shut down cleanly on SIGINT or SIGTERM. make build embeds version, commit, and commit time from Git; inspect them with version.

A second, protocol-compatible Rust connector lives under proxy-rs/ (private-proxy-rs headless CLI and a Tauri debug UI). See ADR 0003 and the Desktop-Proxy wiki page.

make proxy-rs-test
make desktop-dev

Lokale Prüfungen

CI verwendet die in go.mod festgelegte Go-Version 1.26.5 und führt für jeden Push und Pull Request dieselben Prüfungen aus:

make fmt-check
make vet
make test
make acceptance
make test-race
make vuln
make reproducible-build
make proxy-rs-test

make vuln führt die gepinnte Version golang.org/x/vuln/cmd/govulncheck@v1.1.4 aus. Der reproduzierbare Build kompiliert denselben Quellstand zweimal mit identischen, pfadunabhängigen Optionen, vergleicht beide Binärdateien byteweise und prüft, dass das eine Binary sowohl mcp-server als auch private-proxy enthält. Das Ergebnis liegt unter build/private-proxy-mcp.

Releases und Container

make release erzeugt unter dist/ versionierte Archive und checksums.txt für Linux (amd64/arm64), macOS (arm64) und Windows (amd64). Version und Commit werden standardmäßig aus Git ermittelt; die Build-Zeit ist der Zeitstempel des Commits und bleibt damit für denselben Stand stabil:

make release
make release-verify
build/private-proxy-mcp version

Ein Push eines Tags v* veröffentlicht diese Dateien als Forgejo-Release. Dieselbe Pipeline publiziert die OCI-Images repository.hildebrandt.io/ai-tools/private-proxy-mcp/mcp:<tag> und repository.hildebrandt.io/ai-tools/private-proxy-mcp/proxy:<tag> als Multi-Arch-Manifeste für linux/amd64 und linux/arm64.

Lokale Container-Builds:

make container-mcp
make container-proxy

Beide Images laufen ohne Root-Rechte. Das MCP-Image startet mcp-server, das Proxy-Image private-proxy; zusätzliche globale Optionen können den jeweiligen Standardbefehl ersetzen.

Die Fuzz-Targets können mit einem begrenzten Lauf lokal erweitert werden:

go test ./internal/tunnel -run '^$' -fuzz '^FuzzReadFrame$' -fuzztime=30s
go test ./internal/httpexec -run '^$' -fuzz '^FuzzURLNormalization$' -fuzztime=30s
go test ./internal/httpexec -run '^$' -fuzz '^FuzzHeaderNormalization$' -fuzztime=30s
go test ./internal/httpexec -run '^$' -fuzz '^FuzzPolicyInputs$' -fuzztime=30s

The MCP server uses the official Go SDK v1.6.1 and negotiates the current stable MCP protocol (2025-11-25) with clients. It exposes only the Streamable HTTP transport; the legacy HTTP+SSE transport is not enabled. The endpoint, header/idle/shutdown timeouts, and maximum header size are configurable under mcp. MCP request bodies and headers are not logged.

Logs use log/slog and default to JSON at info level. log.format, log.level, and log.redact_headers control output. Authorization, Cookie, Set-Cookie, Proxy-Authorization, and shared-key header values are always redacted. The internal metrics recorder tracks active proxy connections, in-flight requests, durations, stable error categories, reconnects, authentication errors, timeouts, truncations, and policy denials.

Configuration

Configuration is loaded with the following priority: command-line flags, PRIVATE_PROXY_MCP_ environment variables, configuration file, then defaults. Nested keys use screaming snake case in the environment; for example, PRIVATE_PROXY_MCP_MCP_LISTEN_ADDRESS.

Use --config to select an explicit configuration file. A selected file must exist. Without this flag, private-proxy-mcp.yaml in the current directory is loaded when present. See config.example.yaml.

Custom profiles and http.allowed_client_profiles are configured under http. Shipped browser-shaped profiles deliberately avoid fixed browser version claims. If an integration requires a particular current version, operators should replace the profile headers through controlled configuration and update them as part of their normal compatibility maintenance.

Client profiles affect HTTP headers only. Go's http.Client does not acquire a Chrome or Firefox TLS fingerprint from browser-like headers, so these profiles are neither browser emulation nor a guarantee of bypassing bot protection. A site requiring a consistent browser/header/TLS identity must use the separate browser backend when available. Authentication and user-specific browser cookies must remain isolated there rather than being copied into HTTP profiles.

private_proxy.shared_key is required to start private-proxy; it is never printed by help text, errors, or application logs. Cleartext HTTP and insecure_skip_verify require explicit configuration and emit development-only warnings. Shared keys and TLS files are read at process startup and can be rotated with a process restart.

On SIGINT or SIGTERM, the proxy sends goaway(shutdown), drains until private_proxy.drain_timeout, cancels remaining request contexts, closes the connection, and exits cleanly.

Dokumentation