Add stdio MCP bridge for Claude Code integration

Add a Python stdio-based MCP server that bridges to ProxySQL's HTTPS
MCP endpoint, enabling Claude Code to use ProxySQL MCP tools directly.

The bridge:
- Implements stdio MCP server protocol (for Claude Code)
- Acts as MCP client to ProxySQL's HTTPS endpoint
- Supports initialize, tools/list, tools/call methods
- Handles authentication via Bearer tokens
- Configurable via environment variables

Usage:
- Configure in Claude Code MCP settings
- Set PROXYSQL_MCP_ENDPOINT environment variable
- Optional: PROXYSQL_MCP_TOKEN for auth
pull/5310/head
Rene Cannao 1 month ago
parent 9d6a2173bf
commit 01c182ccac

@ -0,0 +1,134 @@
# ProxySQL MCP stdio Bridge
A bridge that converts between **stdio-based MCP** (for Claude Code) and **ProxySQL's HTTPS MCP endpoint**.
## What It Does
```
┌─────────────┐ stdio ┌──────────────────┐ HTTPS ┌──────────┐
│ Claude Code│ ──────────> │ stdio Bridge │ ──────────> │ ProxySQL │
│ (MCP Client)│ │ (this script) │ │ MCP │
└─────────────┘ └──────────────────┘ └──────────┘
```
- **To Claude Code**: Acts as an MCP Server (stdio transport)
- **To ProxySQL**: Acts as an MCP Client (HTTPS transport)
## Installation
1. Install dependencies:
```bash
pip install httpx
```
2. Make the script executable:
```bash
chmod +x proxysql_mcp_stdio_bridge.py
```
## Configuration
### Environment Variables
| Variable | Required | Default | Description |
|----------|----------|---------|-------------|
| `PROXYSQL_MCP_ENDPOINT` | Yes | - | ProxySQL MCP endpoint URL (e.g., `https://127.0.0.1:6071/mcp/query`) |
| `PROXYSQL_MCP_TOKEN` | No | - | Bearer token for authentication (if configured) |
| `PROXYSQL_MCP_INSECURE_SSL` | No | 0 | Set to 1 to disable SSL verification (for self-signed certs) |
### Configure in Claude Code
Add to your Claude Code MCP settings (usually `~/.config/claude-code/mcp_config.json` or similar):
```json
{
"mcpServers": {
"proxysql": {
"command": "python3",
"args": ["/home/rene/proxysql-vec/scripts/mcp/proxysql_mcp_stdio_bridge.py"],
"env": {
"PROXYSQL_MCP_ENDPOINT": "https://127.0.0.1:6071/mcp/query",
"PROXYSQL_MCP_TOKEN": "your_token_here",
"PROXYSQL_MCP_INSECURE_SSL": "1"
}
}
}
}
```
### Quick Test from Terminal
```bash
export PROXYSQL_MCP_ENDPOINT="https://127.0.0.1:6071/mcp/query"
export PROXYSQL_MCP_TOKEN="your_token" # optional
export PROXYSQL_MCP_INSECURE_SSL="1" # for self-signed certs
python3 proxysql_mcp_stdio_bridge.py
```
Then send a JSON-RPC request via stdin:
```json
{"jsonrpc": "2.0", "id": 1, "method": "tools/list"}
```
## Supported MCP Methods
| Method | Description |
|--------|-------------|
| `initialize` | Handshake protocol |
| `tools/list` | List available ProxySQL MCP tools |
| `tools/call` | Call a ProxySQL MCP tool |
| `ping` | Health check |
## Available Tools (from ProxySQL)
Once connected, the following tools will be available in Claude Code:
- `list_schemas` - List databases
- `list_tables` - List tables in a schema
- `describe_table` - Get table structure
- `get_constraints` - Get foreign keys and constraints
- `sample_rows` - Sample data from a table
- `run_sql_readonly` - Execute read-only SQL queries
- `explain_sql` - Get query execution plan
- `table_profile` - Get table statistics
- `column_profile` - Get column statistics
- `catalog_upsert` - Store data in the catalog
- `catalog_get` - Retrieve from the catalog
- `catalog_search` - Search the catalog
- And more...
## Example Usage in Claude Code
Once configured, you can ask Claude:
> "List all tables in the testdb schema"
> "Describe the customers table"
> "Show me 5 rows from the orders table"
> "Run SELECT COUNT(*) FROM customers"
## Troubleshooting
### Connection Refused
Make sure ProxySQL MCP server is running:
```bash
curl -k https://127.0.0.1:6071/mcp/query \
-X POST \
-H "Content-Type: application/json" \
-d '{"jsonrpc": "2.0", "method": "ping", "id": 1}'
```
### SSL Certificate Errors
Set `PROXYSQL_MCP_INSECURE_SSL=1` to bypass certificate verification.
### Authentication Errors
Check that `PROXYSQL_MCP_TOKEN` matches the token configured in ProxySQL:
```sql
SHOW VARIABLES LIKE 'mcp-query_endpoint_auth';
```
## Requirements
- Python 3.7+
- httpx (`pip install httpx`)
- ProxySQL with MCP enabled

@ -0,0 +1,330 @@
#!/usr/bin/env python3
"""
ProxySQL MCP stdio Bridge
Translates between stdio-based MCP (for Claude Code) and ProxySQL's HTTPS MCP endpoint.
Usage:
export PROXYSQL_MCP_ENDPOINT="https://127.0.0.1:6071/mcp/query"
export PROXYSQL_MCP_TOKEN="your_token" # optional
python proxysql_mcp_stdio_bridge.py
Or configure in Claude Code's MCP settings:
{
"mcpServers": {
"proxysql": {
"command": "python3",
"args": ["/path/to/proxysql_mcp_stdio_bridge.py"],
"env": {
"PROXYSQL_MCP_ENDPOINT": "https://127.0.0.1:6071/mcp/query",
"PROXYSQL_MCP_TOKEN": "your_token"
}
}
}
}
"""
import asyncio
import json
import os
import sys
from typing import Any, Dict, Optional
import httpx
class ProxySQLMCPEndpoint:
"""Client for ProxySQL's HTTPS MCP endpoint."""
def __init__(self, endpoint: str, auth_token: Optional[str] = None, verify_ssl: bool = True):
self.endpoint = endpoint
self.auth_token = auth_token
self.verify_ssl = verify_ssl
self._client: Optional[httpx.AsyncClient] = None
self._initialized = False
async def __aenter__(self):
self._client = httpx.AsyncClient(
timeout=120.0,
verify=self.verify_ssl,
)
# Initialize connection
await self._initialize()
return self
async def __aexit__(self, *args):
if self._client:
await self._client.aclose()
async def _initialize(self):
"""Initialize the MCP connection."""
request = {
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2024-11-05",
"capabilities": {},
"clientInfo": {
"name": "proxysql-mcp-stdio-bridge",
"version": "1.0.0"
}
}
}
response = await self._call(request)
self._initialized = True
return response
async def _call(self, request: Dict[str, Any]) -> Dict[str, Any]:
"""Make a JSON-RPC call to ProxySQL MCP endpoint."""
if not self._client:
raise RuntimeError("Client not initialized")
headers = {"Content-Type": "application/json"}
if self.auth_token:
headers["Authorization"] = f"Bearer {self.auth_token}"
try:
r = await self._client.post(self.endpoint, json=request, headers=headers)
r.raise_for_status()
return r.json()
except httpx.HTTPStatusError as e:
return {
"jsonrpc": "2.0",
"error": {
"code": -32000,
"message": f"HTTP error: {e.response.status_code}",
"data": str(e)
},
"id": request.get("id", "")
}
except Exception as e:
return {
"jsonrpc": "2.0",
"error": {
"code": -32603,
"message": f"Internal error: {str(e)}"
},
"id": request.get("id", "")
}
async def tools_list(self) -> Dict[str, Any]:
"""List available tools."""
request = {
"jsonrpc": "2.0",
"id": 2,
"method": "tools/list",
"params": {}
}
return await self._call(request)
async def tools_call(self, name: str, arguments: Dict[str, Any], req_id: str) -> Dict[str, Any]:
"""Call a tool."""
request = {
"jsonrpc": "2.0",
"id": req_id,
"method": "tools/call",
"params": {
"name": name,
"arguments": arguments
}
}
return await self._call(request)
class StdioMCPServer:
"""stdio-based MCP server that bridges to ProxySQL's HTTPS MCP."""
def __init__(self, proxysql_endpoint: str, auth_token: Optional[str] = None, verify_ssl: bool = True):
self.proxysql_endpoint = proxysql_endpoint
self.auth_token = auth_token
self.verify_ssl = verify_ssl
self._proxysql: Optional[ProxySQLMCPEndpoint] = None
self._request_id = 1
async def run(self):
"""Main server loop."""
async with ProxySQLMCPEndpoint(self.proxysql_endpoint, self.auth_token, self.verify_ssl) as client:
self._proxysql = client
# Send initialized notification
await self._write_notification("notifications/initialized")
# Main message loop
while True:
try:
line = await self._readline()
if not line:
break
message = json.loads(line)
response = await self._handle_message(message)
if response:
await self._writeline(response)
except json.JSONDecodeError as e:
await self._write_error(-32700, f"Parse error: {e}", "")
except Exception as e:
await self._write_error(-32603, f"Internal error: {e}", "")
async def _readline(self) -> Optional[str]:
"""Read a line from stdin."""
loop = asyncio.get_event_loop()
line = await loop.run_in_executor(None, sys.stdin.readline)
if not line:
return None
return line.strip()
async def _writeline(self, data: Any):
"""Write JSON data to stdout."""
loop = asyncio.get_event_loop()
output = json.dumps(data, ensure_ascii=False) + "\n"
await loop.run_in_executor(None, sys.stdout.write, output)
await loop.run_in_executor(None, sys.stdout.flush)
async def _write_notification(self, method: str, params: Optional[Dict[str, Any]] = None):
"""Write a notification (no id)."""
notification = {
"jsonrpc": "2.0",
"method": method
}
if params:
notification["params"] = params
await self._writeline(notification)
async def _write_response(self, result: Any, req_id: str):
"""Write a response."""
response = {
"jsonrpc": "2.0",
"result": result,
"id": req_id
}
await self._writeline(response)
async def _write_error(self, code: int, message: str, req_id: str):
"""Write an error response."""
response = {
"jsonrpc": "2.0",
"error": {
"code": code,
"message": message
},
"id": req_id
}
await self._writeline(response)
async def _handle_message(self, message: Dict[str, Any]) -> Optional[Dict[str, Any]]:
"""Handle an incoming message."""
method = message.get("method")
req_id = message.get("id", "")
params = message.get("params", {})
if method == "initialize":
return await self._handle_initialize(req_id, params)
elif method == "tools/list":
return await self._handle_tools_list(req_id)
elif method == "tools/call":
return await self._handle_tools_call(req_id, params)
elif method == "ping":
return {"jsonrpc": "2.0", "result": {"status": "ok"}, "id": req_id}
else:
await self._write_error(-32601, f"Method not found: {method}", req_id)
return None
async def _handle_initialize(self, req_id: str, params: Dict[str, Any]) -> Dict[str, Any]:
"""Handle initialize request."""
return {
"jsonrpc": "2.0",
"result": {
"protocolVersion": "2024-11-05",
"capabilities": {
"tools": {}
},
"serverInfo": {
"name": "proxysql-mcp-stdio-bridge",
"version": "1.0.0"
}
},
"id": req_id
}
async def _handle_tools_list(self, req_id: str) -> Dict[str, Any]:
"""Handle tools/list request - forward to ProxySQL."""
if not self._proxysql:
return {
"jsonrpc": "2.0",
"error": {"code": -32000, "message": "ProxySQL client not initialized"},
"id": req_id
}
response = await self._proxysql.tools_list()
# The response from ProxySQL is the full JSON-RPC response
# We need to extract the result and return it in our format
if "error" in response:
return {
"jsonrpc": "2.0",
"error": response["error"],
"id": req_id
}
return {
"jsonrpc": "2.0",
"result": response.get("result", {}),
"id": req_id
}
async def _handle_tools_call(self, req_id: str, params: Dict[str, Any]) -> Dict[str, Any]:
"""Handle tools/call request - forward to ProxySQL."""
if not self._proxysql:
return {
"jsonrpc": "2.0",
"error": {"code": -32000, "message": "ProxySQL client not initialized"},
"id": req_id
}
name = params.get("name", "")
arguments = params.get("arguments", {})
response = await self._proxysql.tools_call(name, arguments, req_id)
if "error" in response:
return {
"jsonrpc": "2.0",
"error": response["error"],
"id": req_id
}
return {
"jsonrpc": "2.0",
"result": response.get("result", {}),
"id": req_id
}
async def main():
# Get configuration from environment
endpoint = os.getenv("PROXYSQL_MCP_ENDPOINT", "https://127.0.0.1:6071/mcp/query")
token = os.getenv("PROXYSQL_MCP_TOKEN", "")
insecure_ssl = os.getenv("PROXYSQL_MCP_INSECURE_SSL", "0").lower() in ("1", "true", "yes")
# Validate endpoint
if not endpoint:
sys.stderr.write("Error: PROXYSQL_MCP_ENDPOINT environment variable is required\n")
sys.exit(1)
# Run the server
server = StdioMCPServer(endpoint, token or None, verify_ssl=not insecure_ssl)
try:
await server.run()
except KeyboardInterrupt:
pass
except Exception as e:
sys.stderr.write(f"Error: {e}\n")
sys.exit(1)
if __name__ == "__main__":
asyncio.run(main())
Loading…
Cancel
Save