mcp-proxy
Exposes metrics in Prometheus format for monitoring the MCP server and its proxied services.
Provides distributed rate limiting across multiple MCP server instances using Redis.
Allows importing OpenAPI/Swagger specifications to automatically generate MCP tool definitions for REST APIs.
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@mcp-proxyfetch posts from jsonplaceholder"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
MCP Proxy Service
An enterprise-grade MCP (Model Context Protocol) proxy that bridges LLM clients with existing RESTful microservices. Configure any REST API as an MCP tool — no changes to your microservices required.
Table of Contents
Related MCP server: OpenAPI REST MCP Server
Features
Protocol Translation — Automatically converts MCP tool calls into REST API requests
Dynamic Tool Registration — Define REST endpoints as MCP tools via YAML configuration
OpenAPI Import — Generate tool definitions from OpenAPI/Swagger specifications
Transport Agnostic — Supports stdio (for Claude Desktop, CLI) and HTTP (Streamable HTTP with SSE)
Circuit Breaker — Fault tolerance with automatic service recovery (opossum)
Rate Limiting — Fixed window, sliding window, and token bucket strategies
Access Control — Role-based permissions at service and tool level
Authentication — API key, bearer token, basic auth, and OAuth2 client credentials
Request Validation — Type checking, pattern matching, format validation (email, URL, UUID)
Request Cancellation — Abort in-flight requests with signal propagation to upstream services
Response Transformation — JSON, text, and XML response format conversion
Monitoring — Prometheus metrics, health checks, readiness probes
Retry with Jittered Backoff — Prevents thundering herd on failure recovery
Graceful Shutdown — Clean connection draining on SIGINT/SIGTERM
Prerequisites
Node.js >= 18.0.0
npm >= 8.0.0
Docker (optional, for containerized deployment)
Redis (optional, for distributed rate limiting across multiple instances)
Quick Start
# Install dependencies
npm install
# Copy environment file
cp .env.example .env
# Build
npm run build
# Start with stdio transport (default, for Claude Desktop)
npm run start:stdio
# Start with HTTP transport
npm run start:httpEnvironment Setup
Development
# .env
NODE_ENV=development
LOG_LEVEL=debug
# Server
MCP_SERVER_PORT=3000
HEALTH_CHECK_PORT=8080
# Redis (optional — in-memory rate limiting used if not configured)
REDIS_HOST=localhost
REDIS_PORT=6379
# Monitoring
METRICS_ENABLED=trueRun in development mode with hot reload:
npm run devProduction
# .env
NODE_ENV=production
LOG_LEVEL=info
# Server
MCP_SERVER_PORT=3000
HEALTH_CHECK_PORT=8080
# Redis (required for distributed rate limiting)
REDIS_HOST=redis.internal
REDIS_PORT=6379
REDIS_PASSWORD=your-secure-password
# MCP server authentication (protect the /mcp endpoint)
MCP_SERVER_AUTH_ENABLED=true
MCP_SERVER_AUTH_TYPE=bearer
MCP_SERVER_AUTH_TOKEN_ENV=MCP_AUTH_TOKEN
MCP_AUTH_TOKEN=your-secret-token-here
# Service authentication tokens
USER_SERVICE_TOKEN=your_user_service_token
PAYMENT_API_KEY=your_payment_api_key
ANALYTICS_CLIENT_SECRET=your_oauth_client_secret
# OAuth2 (if using oauth2 auth strategy)
OAUTH2_CLIENT_ID=your_client_id
OAUTH2_CLIENT_SECRET=your_client_secret
OAUTH2_TOKEN_URL=https://auth.example.com/oauth/token
# Monitoring
METRICS_ENABLED=true
PROMETHEUS_PORT=9090Deploy with Docker:
docker build -t mcp-proxy .
docker run -p 3000:3000 -p 8080:8080 \
-v ./config:/app/config:ro \
--env-file .env \
mcp-proxyOr with Docker Compose (includes Redis):
docker-compose upConfiguration
Service configuration is defined in YAML. The proxy reads config/services.yaml by default.
Specifying a Custom Config File
Use the --config (or -c) flag to load a different configuration file:
# Stdio with custom config
node dist/server/unified-server.js --transport stdio --config ./config/my-services.yaml
# HTTP with custom config
node dist/server/unified-server.js --transport http --port 3000 --config /absolute/path/to/services.yamlImportant: The config path is resolved relative to the working directory of the process, not relative to the project root. When the proxy is spawned by an external tool (Claude Desktop, LangChain, etc.), the working directory may differ from the project root. Use an absolute path to avoid
CONFIGURATION_ERRORfailures.
Minimal Example (No Auth)
version: "1.0"
settings:
log_level: "info"
metrics_enabled: true
health_check_port: 8080
services:
- name: "jsonplaceholder"
description: "JSONPlaceholder - Free fake REST API"
base_url: "https://jsonplaceholder.typicode.com"
tools:
- name: "get_post"
description: "Get a blog post by ID"
method: "GET"
path: "/posts/{id}"
parameters:
- name: "id"
type: "number"
required: true
description: "Post ID (1-100)"
in: "path"
- name: "list_posts"
description: "List all blog posts"
method: "GET"
path: "/posts"
- name: "get_user"
description: "Get user by ID"
method: "GET"
path: "/users/{id}"
parameters:
- name: "id"
type: "number"
required: true
description: "User ID (1-10)"
in: "path"Full Enterprise Example
version: "1.0"
settings:
log_level: "info"
metrics_enabled: true
health_check_port: 8080
mcp_server_port: 3000
services:
- name: "user_service"
description: "User management service"
base_url: "https://api.example.com/v1/users"
auth:
type: "bearer"
config:
token_env: "USER_SERVICE_TOKEN"
circuit_breaker:
enabled: true
timeout: 3000
error_threshold: 50
reset_timeout: 30000
volume_threshold: 10
rate_limit:
enabled: true
max_requests: 100
window: 60000
strategy: "sliding_window"
access_control:
enabled: true
allowed_roles: ["admin", "user"]
required_permissions: ["read:users"]
retry:
enabled: true
max_attempts: 3
backoff_ms: 1000
timeout: 5000
tools:
- name: "create_user"
description: "Create a new user account"
method: "POST"
path: "/"
access_control:
enabled: true
required_permissions: ["write:users"]
parameters:
- name: "username"
type: "string"
required: true
description: "Username for the new account"
in: "body"
validation:
min_length: 3
max_length: 50
pattern: "^[a-zA-Z0-9_]+$"
- name: "email"
type: "string"
required: true
description: "Email address"
in: "body"
validation:
format: "email"
request_body:
content_type: "application/json"
template: |
{
"username": "{{username}}",
"email": "{{email}}",
"created_at": "{{$timestamp}}"
}
response:
success_codes: [201]
error_codes: [400, 409]
transform: "json"
- name: "payment_service"
description: "Payment processing service"
base_url: "https://api.payments.example.com/v2"
auth:
type: "api_key"
config:
key_env: "PAYMENT_API_KEY"
header_name: "X-Payment-API-Key"
circuit_breaker:
enabled: true
timeout: 5000
error_threshold: 30
reset_timeout: 60000
volume_threshold: 5
rate_limit:
enabled: true
max_requests: 50
window: 60000
strategy: "token_bucket"
tools:
- name: "create_payment"
description: "Process a payment transaction"
method: "POST"
path: "/payments"
parameters:
- name: "amount"
type: "number"
required: true
description: "Payment amount in cents"
in: "body"
validation:
minimum: 1
- name: "currency"
type: "string"
required: true
description: "Currency code"
in: "body"
validation:
enum: ["USD", "EUR", "GBP"]
- name: "analytics_service"
description: "Analytics and reporting service"
base_url: "https://analytics.example.com/api"
auth:
type: "oauth2"
config:
client_id: "analytics_client"
client_secret_env: "ANALYTICS_CLIENT_SECRET"
token_url: "https://auth.example.com/oauth/token"
scope: "analytics:read analytics:write"
tools:
- name: "get_report"
description: "Generate analytics report"
method: "GET"
path: "/reports/{report_id}"
parameters:
- name: "report_id"
type: "string"
required: true
description: "Report identifier"
in: "path"
- name: "format"
type: "string"
required: false
description: "Report format"
in: "query"
validation:
enum: ["json", "csv", "pdf"]Authentication Types
Type | Config | Description |
|
| Bearer token from environment variable |
|
| API key injected as a header |
|
| HTTP Basic authentication |
|
| OAuth2 client credentials flow |
MCP Server Inbound Authentication
Protect the /mcp endpoint for HTTP transport with environment variables:
# Bearer token
MCP_SERVER_AUTH_ENABLED=true
MCP_SERVER_AUTH_TYPE=bearer
MCP_AUTH_TOKEN=your-secret-token-here
# API key
MCP_SERVER_AUTH_ENABLED=true
MCP_SERVER_AUTH_TYPE=api_key
MCP_API_KEY=your-api-key-here
# JWT (HMAC-SHA256)
MCP_SERVER_AUTH_ENABLED=true
MCP_SERVER_AUTH_TYPE=jwt
JWT_SECRET=your-jwt-secret-hereRequest Body Templates
Templates support variable substitution with JSON injection prevention:
request_body:
content_type: "application/json"
template: |
{
"name": "{{name}}",
"id": "{{$uuid}}",
"timestamp": "{{$timestamp}}",
"date": "{{$date}}"
}Special variables: {{$timestamp}} (ISO 8601), {{$date}} (YYYY-MM-DD), {{$uuid}} (v4 UUID).
Parameter Validation
parameters:
- name: "email"
type: "string"
required: true
in: "body"
validation:
format: "email" # email, url, uuid, date
pattern: "^[a-z]+$" # regex pattern
min_length: 5
max_length: 100
enum: ["a", "b", "c"] # allowed values
- name: "count"
type: "number"
required: false
in: "query"
validation:
minimum: 1
maximum: 1000Response Transformation
Control how upstream REST responses are converted to MCP tool results:
response:
success_codes: [200, 201]
error_codes: [400, 404, 500]
transform: "json" # json | text | xmlTransform | Behavior |
| Parses response as JSON object. If already an object, passes through. If string, attempts |
| Wraps response in |
| Wraps raw XML in |
Request Cancellation
The proxy supports MCP request cancellation. When a client cancels a pending tool call:
The MCP engine receives the cancellation notification
The associated
AbortSignalis triggeredThe in-flight HTTP request to the upstream service is aborted
Any pending retry sleep is interrupted immediately
The client receives a cancellation acknowledgment
This prevents wasted upstream resources on abandoned requests.
CLI Usage
The CLI manages configuration and server lifecycle.
Generate Config from OpenAPI
# Generate from an OpenAPI spec
npm run cli -- generate \
-i ./specs/petstore.yaml \
-n pet_service \
-u https://api.petstore.example.com \
-o ./config/pet-service.yaml
# With auth and filtering
npm run cli -- generate \
-i ./specs/api.yaml \
-n my_service \
-u https://api.example.com \
--auth-type bearer \
--auth-token-env MY_SERVICE_TOKEN \
--include-tags "users,orders" \
--exclude-operations "deleteAll,purge" \
-o ./config/my-service.yamlOptions:
Flag | Description |
| Path to OpenAPI/Swagger spec (required) |
| Service name (required) |
| Base URL (required) |
| Output path (default: |
| Auth type: |
| Env var name for auth token |
| Comma-separated operation IDs to include |
| Comma-separated operation IDs to exclude |
| Comma-separated tags to include |
| Comma-separated tags to exclude |
| Enable circuit breaker (default: true) |
| Enable rate limiting (default: true) |
Validate Configuration
# Validate a config file
npm run cli -- validate -c ./config/services.yaml
# Strict mode
npm run cli -- validate -c ./config/services.yaml --strictStart Server via CLI
npm run cli -- serve -c ./config/services.yaml -p 3000Transport Modes
Stdio (Default)
Used with Claude Desktop, LangChain, and other MCP clients that communicate over stdin/stdout.
# Using default config (config/services.yaml)
npm run start:stdio
# Using a custom config file
node dist/server/unified-server.js --transport stdio --config ./config/my-services.yamlAdd to Claude Desktop config (claude_desktop_config.json):
{
"mcpServers": {
"my-proxy": {
"command": "node",
"args": [
"/path/to/mcp-proxy/dist/server/unified-server.js",
"--transport", "stdio",
"--config", "/path/to/mcp-proxy/config/services.yaml"
],
"env": {
"NODE_ENV": "production"
}
}
}
}Use with LangChain (langchain-mcp-adapters):
from langchain_mcp_adapters.client import MultiServerMCPClient
server_configs = {
"my_proxy": {
"command": "node",
"args": [
"/path/to/mcp-proxy/dist/server/unified-server.js",
"--transport", "stdio",
"--config", "/path/to/mcp-proxy/config/services.yaml"
],
"transport": "stdio",
}
}
async with MultiServerMCPClient(server_configs) as client:
tools = await client.get_tools()Note: Always use absolute paths for both the server script and the
--configfile when the proxy is launched by an external tool. These tools may set a different working directory, causing relative paths to fail withCONFIGURATION_ERROR.
HTTP (Streamable HTTP + SSE)
For web-based clients and multi-client deployments.
# Using default config
npm run start:http
# With custom port, host, and config
node dist/server/unified-server.js --transport http --host 127.0.0.1 --port 8000 --config ./config/my-services.yamlThe HTTP transport supports:
Session management with cryptographic session IDs
Optional inbound authentication (bearer, API key, JWT, basic)
CORS configuration
TLS support
Graceful Shutdown
The server handles SIGINT and SIGTERM for clean shutdown:
Stops accepting new connections
Waits for in-flight requests to complete
Closes transport connections
Cleans up the MCP engine (flushes metrics, closes circuit breakers)
Exits with code 0
Uncaught exceptions and unhandled rejections also trigger graceful shutdown with exit code 1.
Monitoring
The monitoring server runs on a separate port (default: 8080) and exposes:
Endpoint | Description |
| Detailed health status of all services (ping + circuit breaker state) |
| Readiness probe — returns 200 when all services are reachable |
| Liveness probe — always returns 200 if the process is running |
| Prometheus-format metrics |
| Metrics in JSON format |
| Human-readable status summary |
Metrics Collected
mcp_requests_total— Total MCP requests by tool and statusmcp_request_duration_seconds— Request duration histogrammcp_request_errors_total— Error count by tool and error typemcp_tool_executions_total— Tool execution countmcp_tool_execution_duration_seconds— Tool execution duration
Health Check Response
{
"status": "healthy",
"services": {
"user_service": {
"status": "up",
"responseTime": 45,
"circuitBreaker": "closed"
}
},
"timestamp": "2025-01-15T10:30:00.000Z"
}Kubernetes Integration
# Deployment probe configuration
livenessProbe:
httpGet:
path: /live
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 10
periodSeconds: 5Security
Credential Management
All secrets are managed through environment variables — never stored in configuration files. The .env file is gitignored. Use a secrets manager (Vault, AWS Secrets Manager, etc.) in production.
JSON Template Injection Prevention
Request body templates use JSON.stringify().slice(1,-1) to escape user-supplied values before substitution. This prevents injection of additional JSON keys or malformed payloads:
Input: hello", "admin": true, "x": "y
Result: {"message": "hello\", \"admin\": true, \"x\": \"y"}
^--- injection neutralized, treated as literal stringTiming-Safe Token Comparison
Authentication token comparison uses crypto.timingSafeEqual with buffer padding to prevent timing attacks. Both buffers are padded to equal length before comparison, eliminating length-based timing leaks.
Cryptographic Session IDs
HTTP transport sessions use crypto.randomUUID() (CSPRNG-backed) instead of predictable Math.random(). This prevents session prediction attacks.
Input Validation
Configuration: Zod schemas validate all config at load time (types, ranges, required fields)
Runtime: Per-tool parameter validation checks types, patterns, formats, and ranges before any upstream call is made
Rate Limiting
Protects upstream services from abuse. Supports per-service and per-user keying with Redis for distributed enforcement across multiple proxy instances.
Testing
# Unit tests
npm test
# Unit tests with coverage
npm run test:coverage
# Watch mode
npm run test:watch
# E2E tests (stdio transport + cancellation)
npm run test:e2e
# E2E: HTTP transport
npm run test:http
# E2E: HTTP with authentication
npm run test:http:auth
# All tests (unit + e2e)
npm run test:all
# Type checking only
npm run typecheckTest Coverage
Area | Tests |
Config schema validation | Zod schema correctness |
Template injection | JSON escape and injection neutralization |
Health checker | Ping, circuit breaker state, multi-service |
REST client | Ping, timeout, error handling |
Access control | Role/permission checks, deny/allow |
Request validator | Type, pattern, format, range validation |
Timing-safe comparison | Correctness across lengths, no early-return |
E2E stdio | Full tool call roundtrip over stdin/stdout |
E2E HTTP | HTTP transport with session management |
E2E cancellation | Request abort propagation |
Project Structure
mcp-proxy/
├── src/
│ ├── core/
│ │ └── mcp-engine.ts # Transport-agnostic MCP protocol engine
│ ├── transport/
│ │ ├── index.ts # Transport exports
│ │ ├── types.ts # ITransport interface, configs, states
│ │ ├── factory.ts # Transport factory (createTransport)
│ │ ├── stdio-transport.ts # Stdio transport implementation
│ │ └── http-transport.ts # HTTP transport (Streamable HTTP + SSE)
│ ├── server/
│ │ ├── unified-server.ts # Main entry: CLI arg parsing + bootstrap
│ │ └── tool-registry.ts # Dynamic tool registration
│ ├── config/
│ │ ├── loader.ts # YAML/JSON configuration loader
│ │ ├── schema.ts # Zod validation schemas
│ │ └── openapi-parser.ts # OpenAPI/Swagger → tool config parser
│ ├── rest/
│ │ ├── client.ts # Axios HTTP client with circuit breaker + retry
│ │ └── transformer.ts # Response transformation (JSON, text, XML)
│ ├── auth/
│ │ ├── manager.ts # Auth strategy manager
│ │ ├── types.ts # Auth types
│ │ └── strategies/
│ │ ├── api-key.ts
│ │ ├── bearer.ts
│ │ ├── basic.ts
│ │ └── oauth2.ts
│ ├── middleware/
│ │ ├── rate-limiter.ts # Rate limiting (fixed/sliding/token bucket)
│ │ ├── access-control.ts # RBAC implementation
│ │ └── validator.ts # Request parameter validation
│ ├── monitoring/
│ │ ├── metrics.ts # Prometheus-style metrics collector
│ │ ├── health.ts # Health checker (ping + circuit breaker)
│ │ └── http-server.ts # Monitoring HTTP server (/health, /metrics)
│ ├── types/
│ │ └── config.ts # ProxyConfig, ServiceConfig, ToolConfig types
│ ├── utils/
│ │ ├── errors.ts # Custom error classes
│ │ └── logger.ts # Winston structured logging
│ └── cli/
│ ├── index.ts # CLI entry point (commander)
│ └── commands/
│ ├── generate.ts # Generate config from OpenAPI
│ ├── validate.ts # Validate configuration
│ └── serve.ts # Start server
├── config/
│ ├── services.yaml # Active service configuration
│ ├── example-services.yaml # Full-featured example
│ └── openapi/
│ └── example-user-api.yaml # Example OpenAPI spec
├── tests/
│ ├── unit/ # Unit tests (Jest)
│ └── e2e/ # End-to-end tests
├── Dockerfile # Multi-stage production build
├── docker-compose.yaml # Docker Compose (proxy + Redis)
├── package.json
├── tsconfig.json
└── jest.config.jsTroubleshooting
Circuit breaker stays open
Symptom: All requests to a service fail immediately with "service temporarily unavailable".
Cause: The error rate exceeded error_threshold percentage within volume_threshold requests.
Fix:
Check the upstream service is healthy:
curl <service_base_url>/healthReview logs for the root cause:
LOG_LEVEL=debug npm run start:httpThe circuit will auto-close after
reset_timeoutms if a test request succeedsTo force reset, restart the proxy
Rate limit exceeded
Symptom: Requests return rate limit errors before reaching the upstream service.
Cause: More than max_requests were made within window ms.
Fix:
Increase
max_requestsorwindowin configIf using
per_userkeying, check if a single user is dominatingFor distributed deployments, ensure Redis is connected — without Redis, each instance tracks limits independently
Redis connection failures
Symptom: Startup warning about Redis connection, or rate limiting behaves inconsistently.
Cause: Redis is unreachable or credentials are wrong.
Fix:
Verify Redis is running:
redis-cli pingCheck
REDIS_HOST,REDIS_PORT,REDIS_PASSWORDin.envThe proxy falls back to in-memory rate limiting if Redis is unavailable — this is safe for single-instance deployments
Config validation errors
Symptom: Server fails to start with a Zod validation error.
Fix:
Run
npm run cli -- validate -c ./config/services.yamlfor detailed error outputCommon issues: missing
base_url, invalidmethod(must be uppercase GET/POST/PUT/DELETE/PATCH),typemust be one of string/number/boolean/object/array
Authentication failures to upstream services
Symptom: Upstream returns 401/403 even though auth is configured.
Fix:
Verify the env var specified in
token_env/key_envis set:echo $USER_SERVICE_TOKENFor OAuth2, check that
token_urlis reachable and client credentials are validEnable debug logging to see the outgoing auth headers (tokens are redacted):
LOG_LEVEL=debug
Stdio transport not connecting
Symptom: Claude Desktop shows the MCP server as disconnected.
Fix:
Ensure the project is built:
npm run buildVerify the path in
claude_desktop_config.jsonpoints todist/server/unified-server.jsTest manually:
echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"1.0.0"}}}' | node dist/server/unified-server.jsCheck that no other output (console.log, debug prints) pollutes stdout — the stdio transport requires clean JSON-RPC over stdin/stdout
Request timeout
Symptom: Tool calls fail with "Request timeout" after the configured timeout period.
Fix:
Increase
timeoutat the service level or per-tool levelIf circuit breaker is enabled, ensure
circuit_breaker.timeoutis >= servicetimeoutCheck network connectivity to the upstream service
Scripts Reference
Script | Description |
| Compile TypeScript to |
| Development mode with hot reload |
| Start server (legacy entry point) |
| Start unified server (auto-detect transport) |
| Start with stdio transport |
| Start with HTTP transport on port 3000 |
| Run unit tests |
| Run unit + e2e tests |
| Run E2E tests (stdio + cancellation) |
| Run HTTP transport E2E |
| Run HTTP auth E2E |
| Unit tests with coverage report |
| Run CLI commands (generate, validate, serve) |
| TypeScript type checking |
| ESLint |
| ESLint with auto-fix |
| Prettier formatting |
Technology Stack
Component | Technology |
Runtime | Node.js >= 18 |
Language | TypeScript 5.x |
MCP SDK |
|
HTTP Client | Axios |
Circuit Breaker | Opossum |
Rate Limiting | rate-limiter-flexible + optional Redis (ioredis) |
Validation | Zod |
Logging | Winston (structured JSON) |
Metrics | prom-client (Prometheus) |
HTTP Server | Express |
OpenAPI Parsing | @apidevtools/swagger-parser |
CLI | Commander |
Testing | Jest + ts-jest (ESM) |
Containerization | Docker (multi-stage, node:20-alpine) |
Related Documentation
Architecture Overview — System design, request flow diagrams, component details
Testing Guide — End-to-end testing walkthrough
Contributing
Fork the repository
Create a feature branch:
git checkout -b feature/my-featureMake your changes and add tests
Run the full test suite:
npm run test:allRun type checking:
npm run typecheckRun linting:
npm run lintCommit with a descriptive message
Open a Pull Request
Please ensure all tests pass and type checking succeeds before submitting.
License
MIT
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Latest Blog Posts
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/dyy-fq/mcp-proxy'
If you have feedback or need assistance with the MCP directory API, please join our Discord server