- Rust 97.4%
- Dockerfile 2.6%
| .forgejo/workflows | ||
| src | ||
| .dockerignore | ||
| .gitignore | ||
| Cargo.lock | ||
| Cargo.toml | ||
| config.example.toml | ||
| Dockerfile | ||
| kubernetes.example.yaml | ||
| LICENSE | ||
| README.md | ||
ACP-MCP Proxy Server
A configurable ACP (Agent Client Protocol) server written in Rust that proxies requests to multiple MCP (Model Context Protocol) servers. This server acts as a unified gateway, allowing clients to interact with multiple MCP servers through a single HTTP endpoint.
Features
- Multi-Server Support: Connect to multiple MCP servers simultaneously
- Flexible Transport: Support for both STDIO (local processes) and HTTP/SSE (remote servers)
- Resilient & Self-Healing: Auto-retry failed connections, never crashes on MCP server failures
- HTTP API: Expose all functionality via a simple HTTP REST API
- Container-Ready: Designed to run in Docker/Kubernetes environments
- Official SDK: Uses the official rmcp (Rust MCP SDK) for MCP protocol compliance
- Simple Configuration: TOML-based configuration for easy setup
- Always Available: Health endpoint always returns OK, proxy stays running even if all MCP servers are down
<old_text line=18>
┌─────────────┐
│ Client │
└──────┬──────┘
│ HTTP
▼
┌─────────────┐
│ ACP Server │
│ (Rust) │
└──────┬──────┘
│
├─────────┐
│ │
▼ ▼
┌────────┐ ┌────────┐
│ MCP #1 │ │ MCP #2 │
│ STDIO │ │ STDIO │
└────────┘ └────────┘
Architecture
┌─────────────┐
│ Client │
└──────┬──────┘
│ HTTP
▼
┌─────────────┐
│ ACP Server │
│ (Rust) │
└──────┬──────┘
│
├─────────┐
│ │
▼ ▼
┌────────┐ ┌────────┐
│ MCP #1 │ │ MCP #2 │
│ STDIO │ │ HTTP │
└────────┘ └────────┘
Prerequisites
- Rust 1.75 or later
- Docker (optional, for containerized deployment)
Installation
From Source
# Clone the repository
git clone <repository-url>
cd acp
# Build the project
cargo build --release
# The binary will be at target/release/acp-mcp-proxy
Configuration
Create a config.toml file to configure the server and its MCP backends:
# Server settings
host = "0.0.0.0"
port = 8080
# MCP Servers
[[mcp_servers]]
name = "filesystem"
type = "stdio"
command = "node"
args = ["filesystem-server.js"]
[[mcp_servers]]
name = "remote-api"
type = "http"
url = "http://localhost:8081/mcp"
headers = { "Authorization" = "Bearer token" }
See config.example.toml for more examples.
<old_text line=91> STDIO Transport (for local processes):
[[mcp_servers]]
name = "my-server"
type = "stdio"
command = "node" # Command to execute
args = ["server.js"] # Command arguments
env = { KEY = "value" } # Environment variables (optional)
HTTP Transport (for remote servers):
[[mcp_servers]]
name = "remote-server"
type = "http"
url = "http://localhost:8081/mcp"
headers = { "Authorization" = "Bearer token" } # Optional headers
Configuration Options
Server Settings
host: IP address to bind to (default:0.0.0.0)port: Port to listen on (default:8080)
MCP Server Types
STDIO Transport (for local processes):
[[mcp_servers]]
name = "my-server"
type = "stdio"
command = "node" # Command to execute
args = ["server.js"] # Command arguments
env = { KEY = "value" } # Environment variables (optional)
HTTP Transport (for remote servers):
[[mcp_servers]]
name = "remote-server"
type = "http"
url = "http://localhost:8081/mcp"
headers = { "Authorization" = "Bearer token" } # Optional headers
Usage
Running the Server
# Using default config.toml in current directory
./target/release/acp-mcp-proxy
# Using custom config path
CONFIG_PATH=/path/to/config.toml ./target/release/acp-mcp-proxy
# With debug logging
RUST_LOG=debug ./target/release/acp-mcp-proxy
Running with Docker
# Dockerfile
FROM rust:1.75 as builder
WORKDIR /app
COPY . .
RUN cargo build --release
FROM debian:bookworm-slim
RUN apt-get update && apt-get install -y ca-certificates && rm -rf /var/lib/apt/lists/*
COPY --from=builder /app/target/release/acp-mcp-proxy /usr/local/bin/
COPY config.toml /etc/acp/config.toml
ENV CONFIG_PATH=/etc/acp/config.toml
EXPOSE 8080
CMD ["acp-mcp-proxy"]
Build and run:
docker build -t acp-mcp-proxy .
docker run -p 8080:8080 -v $(pwd)/config.toml:/etc/acp/config.toml acp-mcp-proxy
Kubernetes
Deploy to Kubernetes using the provided manifest:
# Review and customize kubernetes.yaml first
kubectl apply -f kubernetes.yaml
# Check deployment status
kubectl get pods -n acp-mcp-proxy
# View logs
kubectl logs -n acp-mcp-proxy -l app=acp-mcp-proxy -f
# Check health
kubectl exec -n acp-mcp-proxy deployment/acp-mcp-proxy -- wget -qO- http://localhost:8080/healthz/ready
The deployment includes:
- Health Probes: Startup, liveness, and readiness probes for Kubernetes
- Auto-scaling: HorizontalPodAutoscaler configuration
- High Availability: 2 replicas with PodDisruptionBudget
- Security: SecurityContext, read-only filesystem, non-root user
- Resource Management: CPU and memory requests/limits
Health Endpoints for Kubernetes:
/healthz/live- Liveness probe (always returns 200 if process is alive)/healthz/ready- Readiness probe (returns 200 if at least one MCP server connected)/healthz/startup- Startup probe (returns 200 once initialized)
API Endpoints
Health Check
GET /health
Returns server health status. Always returns OK even if MCP servers are disconnected (they will auto-retry in background).
Response:
{
"status": "ok",
"service": "acp-mcp-proxy"
}
Kubernetes Health Probes
Liveness Probe - Checks if the process is alive:
GET /healthz/live
Readiness Probe - Checks if ready to serve traffic:
GET /healthz/ready
Returns 200 if at least one MCP server is connected, 503 otherwise.
Startup Probe - Checks if application has started:
GET /healthz/startup
Returns 200 once initialized with configured servers.
Detailed Status
GET /status
Returns detailed connection status for all MCP servers.
Response:
{
"service": "acp-mcp-proxy",
"servers": {
"total": 2,
"connected": 1,
"disconnected": 1
},
"connections": {
"filesystem": {
"state": "connected",
"healthy": true
},
"remote-api": {
"state": "failed",
"healthy": false,
"error": "Connection refused"
}
}
}
Get Capabilities
GET /capabilities
Returns server capabilities and connected MCP servers.
Response:
{
"capabilities": {
"tools": true,
"prompts": false,
"resources": false
},
"mcp_servers": ["filesystem", "remote-api"],
"server_count": 2,
"connected_count": 1
}
Manually Reconnect Server
POST /reconnect/:server_name
Manually trigger reconnection to a specific MCP server.
Example:
curl -X POST http://localhost:8080/reconnect/filesystem
Response:
{
"status": "success",
"message": "Reconnecting to server: filesystem"
}
List Tools
GET /tools
Lists all tools from all connected MCP servers.
Response:
{
"tools": [
{
"name": "read_file",
"description": "Read a file from the filesystem",
"mcp_server": "filesystem",
"inputSchema": { ... }
}
]
}
Call Tool
POST /tools/call
Content-Type: application/json
{
"server": "filesystem",
"tool": "read_file",
"arguments": {
"path": "/etc/hosts"
}
}
Calls a specific tool on a specific MCP server.
Response:
{
"content": [...],
"isError": false
}
<old_text line=261>
Call a tool
curl -X POST http://localhost:8080/tools/call
-H "Content-Type: application/json"
-d '{
"server": "filesystem",
"tool": "read_file",
"arguments": {"path": "/etc/hosts"}
}'
## Examples
### Using curl
```bash
# Check server health (always returns OK)
curl http://localhost:8080/health
# Check detailed connection status
curl http://localhost:8080/status
# List all capabilities
curl http://localhost:8080/capabilities
# List all tools
curl http://localhost:8080/tools
# Manually reconnect to a server
curl -X POST http://localhost:8080/reconnect/filesystem
# Call a tool
curl -X POST http://localhost:8080/tools/call \
-H "Content-Type: application/json" \
-d '{
"server": "filesystem",
"tool": "read_file",
"arguments": {"path": "/etc/hosts"}
}'
# Send raw MCP request
curl -X POST http://localhost:8080/mcp/request \
-H "Content-Type: application/json" \
-d '{
"server": "filesystem",
"method": "tools/list",
"params": {}
}'
Using Python
import requests
# List all tools
response = requests.get("http://localhost:8080/tools")
tools = response.json()["tools"]
# Call a tool
response = requests.post(
"http://localhost:8080/tools/call",
json={
"server": "filesystem",
"tool": "read_file",
"arguments": {"path": "/etc/hosts"}
}
)
result = response.json()
Development
Building
cargo build
Running Tests
cargo test
Running with Hot Reload
cargo install cargo-watch
cargo watch -x run
Logging
Set the RUST_LOG environment variable to control log levels:
# Info level (default)
RUST_LOG=info cargo run
# Debug level
RUST_LOG=debug cargo run
# Trace level (very verbose)
RUST_LOG=trace cargo run
# Module-specific logging
RUST_LOG=acp_mcp_proxy=debug,mcp_client=trace cargo run
Troubleshooting
MCP Server Connection Issues
The proxy is designed to be resilient and will never crash due to MCP server failures:
- Auto-Retry: Failed connections automatically retry every 30 seconds
- Graceful Degradation: If an MCP server is down, others continue working
- Always Available: The
/healthendpoint always returns OK - Monitor Status: Use
/statusendpoint to see connection states - Manual Reconnect: Use
POST /reconnect/:server_nameto force reconnection
Debugging:
- STDIO servers: Check that the command path is correct and executable
- HTTP servers: Verify the URL is reachable and the server is running
- Ensure the MCP server supports the transport type you're using
- Check logs with
RUST_LOG=debugfor detailed error messages - Verify environment variables are set correctly if using the
envfield for STDIO - For HTTP servers, check that custom headers are properly formatted
- Watch logs for automatic retry attempts
<old_text line=353>
Ensure at least one MCP server is configured correctly in config.toml. The server will fail to start if no MCP servers can be connected.
Port Already in Use
Change the port in config.toml or set it to a different value.
No MCP Servers Connected
The proxy will start even if no MCP servers can initially connect. They will automatically retry in the background. Check:
- Configuration syntax in
config.toml - At least one MCP server is configured
- Use
GET /statusto monitor connection attempts - Check logs for detailed error messages
Contributing
Contributions are welcome! Please ensure code is readable and not over-engineered.
License
[Your License Here]
Features & Limitations
✅ Implemented
- Resilient Architecture: Auto-retry, graceful degradation, never crashes
- Multi-Server Support: Connect to multiple MCP servers
- Dual Transport: STDIO and HTTP/SSE support
- Health Monitoring: Status endpoint with connection details
- Manual Control: Trigger reconnections via API
🔄 Current Limitations
- Prompts & Resources: Currently only tools are proxied. Prompts and resources support may be added in future releases.
- Streaming: Tool responses are not streamed. Full responses are returned at once.
- Retry Interval: Fixed at 30 seconds (not configurable yet)
Kubernetes Best Practices
When deploying to Kubernetes:
- Configure Health Probes: The manifest includes all three probe types
- Set Resource Limits: Adjust CPU/memory based on your MCP server count
- Use ConfigMaps: Store configuration in ConfigMaps for easy updates
- Enable Auto-scaling: HPA scales based on CPU/memory usage
- Network Policies: Restrict ingress/egress as needed for your environment
- Secrets: Use Kubernetes Secrets for sensitive data (API tokens, etc.)
- Monitor Logs: Use
kubectl logsor log aggregation tools - Graceful Shutdown: 30-second termination grace period allows cleanup
Example ConfigMap update:
kubectl edit configmap acp-mcp-proxy-config -n acp-mcp-proxy
kubectl rollout restart deployment/acp-mcp-proxy -n acp-mcp-proxy