No description
  • Rust 97.4%
  • Dockerfile 2.6%
Find a file
damfle d0b7ff8950
All checks were successful
CI / Lint (push) Successful in 46s
CI / Test (push) Successful in 58s
CI / Build (push) Successful in 1m35s
CI / Create Tag (push) Successful in 5s
doc: add LICENSE
2026-02-05 18:00:16 +01:00
.forgejo/workflows init: initial commit 2026-02-04 22:12:22 +01:00
src min: bump container and packages 2026-02-05 17:19:09 +01:00
.dockerignore init: initial commit 2026-02-04 22:12:22 +01:00
.gitignore init: initial commit 2026-02-04 22:12:22 +01:00
Cargo.lock min: bump container and packages 2026-02-05 17:19:09 +01:00
Cargo.toml min: bump container and packages 2026-02-05 17:19:09 +01:00
config.example.toml init: initial commit 2026-02-04 22:12:22 +01:00
Dockerfile min: bump container and packages 2026-02-05 17:19:09 +01:00
kubernetes.example.yaml init: initial commit 2026-02-04 22:12:22 +01:00
LICENSE doc: add LICENSE 2026-02-05 18:00:16 +01:00
README.md init: initial commit 2026-02-04 22:12:22 +01:00

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 /health endpoint always returns OK
  • Monitor Status: Use /status endpoint to see connection states
  • Manual Reconnect: Use POST /reconnect/:server_name to 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=debug for detailed error messages
  • Verify environment variables are set correctly if using the env field 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 /status to 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:

  1. Configure Health Probes: The manifest includes all three probe types
  2. Set Resource Limits: Adjust CPU/memory based on your MCP server count
  3. Use ConfigMaps: Store configuration in ConfigMaps for easy updates
  4. Enable Auto-scaling: HPA scales based on CPU/memory usage
  5. Network Policies: Restrict ingress/egress as needed for your environment
  6. Secrets: Use Kubernetes Secrets for sensitive data (API tokens, etc.)
  7. Monitor Logs: Use kubectl logs or log aggregation tools
  8. 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

References