Fast, lightweight protocol translation hub.
Project description
protoproxy
Fast, lightweight protocol translation hub. Normalizes any supported protocol into a canonical format and re-serializes it out.
Overview
protoproxy sits between a reverse proxy and one or more backend services. Incoming requests — regardless of source protocol — are normalized into a shared canonical Python dict. From there, protoproxy routes and re-serializes into whatever protocol the target backend speaks.
New protocols are two files: an input module that normalizes into the canonical dict, and an output module that translates back out. The core stays untouched.
Currently supports HTTPS, MCP, and WebSocket.
+------------------------------------+
| protoproxy |
| |
| +------------------------------+ |
| | auth (bearer validation) | |
| +------------------------------+ |
| |
+----------+ | +----------+ +-------------+ |
Internet <--> | Nginx | <------> | | https | | canonical | |
(HTTPS) | :443 | | | input/ |<--->| dict | |
| TLS | | | output | | | |
|Rate limit| | +----------+ +-------------+ |
| | | ^ |
+----------+ | | |
| v |
| +-------------+ |
| | mcp | |
| | input/ | |
| | output | |
| +-------------+ |
| | |
+--------------------------+---------+
|
v
+---------------+
| MCP Server |
| :9090 |
+---------------+
protoproxy doesn't replace your reverse proxy — it complements it. Nginx handles TLS termination and rate limiting; protoproxy handles bearer token validation and protocol translation. Only clean, decrypted traffic reaches protoproxy.
How It Works
- Reverse proxy terminates TLS, enforces rate limits, and forwards decrypted traffic to protoproxy
- The matching input module (e.g.
https_input.py) receives the request on its configured port auth.pyvalidates the bearer token against the server-side secret; unauthorized requests are rejected before any further processing- The input module normalizes the authorized request into a canonical Python dict — a neutral intermediate format every module understands
router.pyconsultsconfig.jsonto pick the target output module and dispatches the canonical dict to it- The output module (e.g.
mcp_output.py) translates the canonical dict into the target protocol and forwards it to the backend service - The response travels back through the same chain in reverse — backend → output module → canonical dict → input module → reverse proxy → client
Configuration
protoproxy is configured via config.json. Secrets (tokens, keys) go in .env and are never committed.
{
"inputs": {
"https": {
"enabled": true,
"host": "0.0.0.0",
"port": 8080
},
"mcp": {
"enabled": true,
"host": "0.0.0.0",
"port": 8090
},
"websocket": {
"enabled": true,
"host": "0.0.0.0",
"port": 8081
}
},
"outputs": {
"https": {
"enabled": true,
"endpoint": "http://localhost:9080"
},
"mcp": {
"enabled": true,
"endpoint": "http://localhost:9090"
},
"websocket": {
"enabled": true,
"endpoint": "ws://localhost:9081"
}
},
"router": {
"default_output": "mcp"
}
}
inputs
Each key under inputs is a protocol name, mapped to a listening socket. The protocol name must match a module in src/protoproxy/modules/ (e.g. https → https_input.py).
| Key | Type | Description |
|---|---|---|
enabled |
bool | Whether to start this input on launch |
host |
str | Interface to bind to. 0.0.0.0 for all interfaces, 127.0.0.1 for localhost only |
port |
int | Port to listen on |
outputs
Each key under outputs is a protocol name, mapped to a backend destination protoproxy dials when routing. endpoint is a full URL because an output needs scheme + host + port + (optional) path — unlike inputs, which only need a bind target.
| Key | Type | Description |
|---|---|---|
enabled |
bool | Whether to make this output available to the router |
endpoint |
str | Full URL of the backend service (e.g. http://localhost:9090, ws://localhost:9081) |
router
| Key | Type | Description |
|---|---|---|
default_output |
str | Protocol name to route to when no explicit output is specified by the request |
Authentication
protoproxy authenticates incoming requests with a server-side bearer token. Every input module invokes modules/auth.py before normalizing the request; requests without a valid Authorization: Bearer <token> header are rejected with 401 Unauthorized.
The token lives in .env (never committed) and is loaded into the process environment at startup. Comparison is timing-safe (via hmac.compare_digest) to avoid leaking the secret through response-time side channels.
Setup:
- Generate a token:
python -c "import secrets; print(secrets.token_urlsafe(32))"
- Copy
.env.exampleto.envand paste the value:PROTOPROXY_BEARER_TOKEN=your-generated-token-here - Share the token out-of-band with any client that needs access.
Client usage:
curl -H "Authorization: Bearer <your-token>" https://your-host/path
Rotating the token is a matter of editing .env and restarting protoproxy. Clients using the old token get 401 immediately.
Installation
pip install protoproxy
Or from source:
git clone https://github.com/geomux/protoproxy.git
cd protoproxy
pip install -e .
Quick Start
python -m protoproxy
protoproxy loads config.json from the working directory and starts one listener per enabled input. With the default config, that's 0.0.0.0:8080 for HTTPS, 0.0.0.0:8090 for MCP, and 0.0.0.0:8081 for WebSocket.
Supported Protocols
- HTTPS
- MCP
- WebSocket
Contributing
Issues and PRs welcome. To add a new protocol, open an issue first to discuss the canonical dict contract before writing code.
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file protoproxy-0.0.4.tar.gz.
File metadata
- Download URL: protoproxy-0.0.4.tar.gz
- Upload date:
- Size: 4.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
46046af2dc5b2ef9135aaeb6314af54bbcc316c98433984ad3384a0d557e9217
|
|
| MD5 |
a2e363e5be8acdcd4068dd6887959d9f
|
|
| BLAKE2b-256 |
be50d19535c7a9a950a7d65c6bb5d56c26a9569f0d60a05bf217a0f19baee927
|
Provenance
The following attestation bundles were made for protoproxy-0.0.4.tar.gz:
Publisher:
publish.yml on geomux/protoproxy
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
protoproxy-0.0.4.tar.gz -
Subject digest:
46046af2dc5b2ef9135aaeb6314af54bbcc316c98433984ad3384a0d557e9217 - Sigstore transparency entry: 1361083425
- Sigstore integration time:
-
Permalink:
geomux/protoproxy@47d98dadd9a8914727f412418c3af43d0be72e65 -
Branch / Tag:
refs/tags/v0.0.4 - Owner: https://github.com/geomux
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@47d98dadd9a8914727f412418c3af43d0be72e65 -
Trigger Event:
release
-
Statement type:
File details
Details for the file protoproxy-0.0.4-py3-none-any.whl.
File metadata
- Download URL: protoproxy-0.0.4-py3-none-any.whl
- Upload date:
- Size: 6.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
903f91c17ebc2d4413253dcb4a9a9eae8f84bb8879a7267a11b9e4493959c986
|
|
| MD5 |
8502fabe938fcc8b11521d7e635577b4
|
|
| BLAKE2b-256 |
4c5bf0b5f03887df2b0666c304863e62b74cc0c5f642103ad43f346f989731cb
|
Provenance
The following attestation bundles were made for protoproxy-0.0.4-py3-none-any.whl:
Publisher:
publish.yml on geomux/protoproxy
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
protoproxy-0.0.4-py3-none-any.whl -
Subject digest:
903f91c17ebc2d4413253dcb4a9a9eae8f84bb8879a7267a11b9e4493959c986 - Sigstore transparency entry: 1361083485
- Sigstore integration time:
-
Permalink:
geomux/protoproxy@47d98dadd9a8914727f412418c3af43d0be72e65 -
Branch / Tag:
refs/tags/v0.0.4 - Owner: https://github.com/geomux
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@47d98dadd9a8914727f412418c3af43d0be72e65 -
Trigger Event:
release
-
Statement type: