- Go 68.9%
- Rust 26.3%
- TypeScript 1.6%
- JavaScript 1.2%
- Shell 0.8%
- Other 1.1%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
|
|
||
| .agents/skills/forge | ||
| .cursor/hooks/state | ||
| .forgejo/workflows | ||
| cmd/private-proxy-mcp | ||
| docs | ||
| internal | ||
| proxy-rs | ||
| scripts | ||
| sidecar/browser-playwright | ||
| .dockerignore | ||
| .gitignore | ||
| AGENTS.md | ||
| config.example.yaml | ||
| Dockerfile | ||
| go.mod | ||
| go.sum | ||
| Makefile | ||
| README.md | ||
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-serverserves MCP Streamable HTTP athttp://127.0.0.1:9334/mcpby default.private-proxymaintains an outbound HTTP/1.1 upgrade connection to the public connector endpoint on port9335by 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.