Skip to content

Deploy MCP Proxies

MCP servers communicate via stdio (stdin/stdout), but browsers need HTTP endpoints. This tutorial shows you how to deploy the proxies that bridge the gap, locally with Docker or in production with systemd and nginx.

Deploy MCP proxies to make public MCP servers (NASA, HackerNews, Wikipedia, etc.) accessible from the browser.

  • Docker and Docker Compose installed (for the quick method)
  • Or: an Ubuntu/Debian VM with python3, node, npm, nginx (for production)
  • Understanding of what an MCP server is (see Connect an MCP server)

MCP servers accessible via HTTP, with CORS configured, recipes injected, and systemd monitoring.

graph LR
Browser[Browser] -->|HTTPS| Nginx[nginx reverse proxy]
Nginx -->|HTTP localhost:900x| Bridge["mcp-stdio-bridge.py"]
Bridge -->|stdin/stdout| MCP["MCP Server (npx/python3)"]

Each bridge is a Python process that:

  • Spawns the MCP server as a child process communicating via stdio
  • Accepts POST /mcp with a JSON-RPC 2.0 body
  • Forwards to the subprocess via stdin, reads the response from stdout
  • Manages the subprocess lifecycle (lazy start, restart on crash)
  • Optionally injects recipe tools from a JSON file
graph TB
subgraph "Per MCP server"
HTTP["POST /mcp (JSON-RPC 2.0)"] --> Bridge["mcp-stdio-bridge.py"]
Bridge --> Stdin["stdin"]
Stdin --> Process["MCP server subprocess"]
Process --> Stdout["stdout"]
Stdout --> Bridge
Bridge --> Response["HTTP Response"]
end
subgraph nginx
CORS["CORS Headers"] --> Proxy["proxy_pass localhost:900x"]
end
Client[McpClient] --> nginx
nginx --> HTTP

For local development, Docker Compose starts all servers in one command:

Terminal window
cd mcp-proxies
docker compose up -d

All servers are then accessible on localhost:9001 through localhost:9008.

To start a single server:

Terminal window
docker compose up -d hackernews

Checkpoint: test with curl:

Terminal window
curl -s -X POST http://localhost:9006/mcp \
-H 'Content-Type: application/json' \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2024-11-05",
"capabilities": {},
"clientInfo": { "name": "test", "version": "1.0" }
}
}'

A valid response contains "result" with the server capabilities.


For production on an Ubuntu/Debian VM:

Terminal window
cd mcp-proxies
sudo ./setup.sh

The script is idempotent (safe to re-run). It performs:

  1. Prerequisite checks (python3, node, npm, nginx)
  2. Creates a dedicated mcpbridge system user
  3. Copies bridge scripts to /opt/mcp-bridge/
  4. Generates systemd unit files for each server
  5. Generates nginx location blocks
  6. Starts or restarts all services
  7. Tests each endpoint with curl
sequenceDiagram
participant Admin
participant Setup as setup.sh
participant System as systemd
participant Nginx as nginx
Admin->>Setup: sudo ./setup.sh
Setup->>Setup: Check prerequisites
Setup->>Setup: Create mcpbridge user
Setup->>Setup: Copy scripts to /opt/mcp-bridge/
Setup->>System: Generate + enable systemd units
Setup->>Nginx: Generate locations + reload
Setup->>Setup: curl test each endpoint
Setup-->>Admin: Startup report

The NASA proxy requires a NASA_API_KEY environment variable:

Terminal window
export NASA_API_KEY="your-key-here"
sudo -E ./setup.sh

Terminal window
mkdir mcp-proxies/servers/my-server
mcp-proxies/servers/my-server/
recipes.json # Recipe tools injected into the server (optional)
service.conf # systemd configuration (command, port, env)
README.md # Server documentation
examples.md # Prompt examples
Section titled “3. Create recipes (optional but recommended)”
[
{
"name": "search-guide",
"description": "How to search in my-server",
"content": "## Search\\n\\nUse search({query: 'keyword'})..."
}
]
Terminal window
sudo ./setup.sh

Checkpoint: the new service appears in systemctl status mcp-bridge-my-server.


Send an initialize request:

Terminal window
curl -s -X POST http://localhost:9001/mcp \
-H 'Content-Type: application/json' \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2024-11-05",
"capabilities": {},
"clientInfo": { "name": "test", "version": "1.0" }
}
}'

Then list tools:

Terminal window
curl -s -X POST http://localhost:9001/mcp \
-H 'Content-Type: application/json' \
-d '{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/list",
"params": {}
}'

import { McpClient } from '@webmcp-auto-ui/core';
// Local development
const client = new McpClient('http://localhost:9001/mcp');
// Production
const client = new McpClient('https://demos.hyperskills.net/mcp-metmuseum/mcp');
await client.initialize();
const tools = await client.listTools();

For multiple servers:

import { McpMultiClient } from '@webmcp-auto-ui/core';
const multi = new McpMultiClient();
await multi.addServer('https://demos.hyperskills.net/mcp-metmuseum/mcp');
await multi.addServer('https://demos.hyperskills.net/mcp-wikipedia/mcp');

Each server can optionally include a recipes.json file that injects recipe tools (list_recipes, get_recipe, search_recipes) into the MCP server’s tool list. The bridge loads these at startup via the --recipes flag.

Recipes let the LLM discover how to use the server autonomously, even if the original MCP server has no built-in discovery mechanism.


ServerMain toolsPortProduction URLExample prompt
hackernewsget-front-page, search-posts9006/mcp-hackernews/mcp”Top 10 stories on HN today”
metmuseumsearch-museum-objects, get-museum-object9001/mcp-metmuseum/mcp”Show impressionist paintings”
openmeteoweather_forecast, geocoding9002/mcp-openmeteo/mcp”Weather in Paris this week”
wikipediasearch, readArticle9005/mcp-wikipedia/mcp”Article about quantum computing”
inaturalistsearch_observations9007/mcp-inaturalist/mcp”Birds spotted near Lyon”
nasanasa_apod, nasa_mars_rover, nasa_neo9008/mcp-nasa/mcp”Latest Mars rover photos”
datagouvremote proxy (no bridge)/mcp-datagouv/mcp”Transport datasets in France”

Production URLs are prefixed with https://demos.hyperskills.net.


mcp-proxies/
bridge/
mcp-stdio-bridge.py # Generic stdio-to-HTTP bridge
inaturalist-mcp.py # Custom Python MCP server for iNaturalist
nginx/
mcp-locations.conf # nginx location blocks (CORS + proxy_pass)
servers/
hackernews/ # Per-server config and recipes
metmuseum/
openmeteo/
wikipedia/
inaturalist/
nasa/
datagouv/
setup.sh # VM provisioning script
docker-compose.yml # Docker alternative for local dev

ProblemLikely causeSolution
”Connection refused”Bridge not startedsystemctl status mcp-bridge-<name>
”CORS error”Missing CORS headers in nginxCheck mcp-locations.conf
Timeout on first callMCP server takes time to startBridge does lazy start, first call is slow
”Invalid JSON-RPC”Wrong request formatCheck JSON-RPC 2.0 body format
Server crash loopMissing npm dependencyjournalctl -u mcp-bridge-<name> for logs

  • Create a custom MCP server: write your own MCP server in Node.js or Python
  • Monitoring: add systemd healthchecks and alerts
  • Security: add token authentication to endpoints