A simple HTTP-to-stdio bridge for MCP (Model Context Protocol) servers.
This crate provides an HTTP server that acts as a bridge between HTTP clients and stdio-based MCP servers. It spawns an MCP server as a subprocess and proxies JSON-RPC messages between HTTP requests and the server's stdin/stdout.
Status: Complete and Production-Ready OK
- Simple Integration: Works with any stdio-based MCP server
- Automatic Process Management: Spawns and manages the MCP server subprocess
- Auto-Restart: Automatically restarts the server if it crashes
- CORS Support: Allows cross-origin requests for web clients
- Health Check: Provides a
/healthendpoint for monitoring
cargo build --relese{
"mcpServers": {
"ahma-stdio-debug-vscode": {
"cwd": "${workspaceFolder}",
"command": "/path/to/ahma/target/release/ahma",
"args": [
"--tools-dir",
"/path/to/ahma/.ahma",
"--log-to-stderr"
],
"env": {
"RUST_LOG": "debug"
}
}
}
}{
"mcpServers": {
"ahma": {
"cwd": "/path/to/working/directory",
"command": "/path/to/ahma/target/release/ahma",
"args": [
"--sync",
"--tools-dir",
"/path/to/ahma/.ahma",
"--log-to-stderr"
],
"env": {
"RUST_LOG": "debug"
}
}
}
}{
"servers": {
"ahma": {
"cwd": "/path/to/working/directory",
"command": "/path/to/ahma/target/release/ahma",
"args": [
"--sync",
"--tools-dir",
"/path/to/ahma/.ahma",
"--log-to-stderr"
],
}
}
}#[tokio::main] async fn main() -> anyhow::Result<()> { let config = BridgeConfig { bind_addr: "127.0.0.1:3000".parse().unwrap(), server_command: "ahma".to_string(), server_args: vec!["--tools-dir".to_string(), "./tools".to_string()],
The bridge is integrated into the ahma_shell binary:
# Start HTTP bridge on default port (3000)
# Clients must provide roots/list unless explicit fallback scope is configured
ahma serve http
# Explicit fallback scope for clients without roots/list support
ahma serve http --sandbox-scope /path/to/project
# Start on custom port
ahma serve http --port 8080
# Start with specific tools directory
ahma serve http --tools-dir ./my-tools| Environment | Port | Notes |
|---|---|---|
| Production | 3000 (default) | Configurable via --port |
| Integration Tests | 5721 (reserved) | Hardcoded constant, do not change |
Integration tests use a dedicated port (5721) to ensure:
- Isolation: Tests never accidentally connect to a production server on port 3000
- Reproducibility: All tests use the same port for consistent behavior
- Debugging: Port collisions immediately reveal concurrent test issues
If port 5721 is already in use when running tests, they will fail loudly. This indicates either:
- Another test process is running concurrently
- A previous test crashed and left an orphaned process
To fix: Kill any existing ahma processes using port 5721.
Send JSON-RPC messages to the MCP server.
Request:
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/list",
"params": {}
}Response:
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"tools": [...]
}
}Health check endpoint.
Response:
OK
Server-Sent Events stream for real-time notifications (per MCP Streamable HTTP transport spec).
- Streams JSON-RPC notifications emitted by the MCP server
- Includes keep-alive comments to maintain the connection
- Same endpoint as POST /mcp, differentiated by HTTP method
- HTTP bridge starts and binds to the specified address
- On first request, it spawns the MCP server as a subprocess
- HTTP requests are converted to JSON-RPC and sent to server's stdin
- Server responses from stdout are returned as HTTP responses
- If the server crashes, it's automatically restarted on the next request
┌─────────────┐
│ HTTP Client │
└──────┬──────┘
│ HTTP POST /mcp
│ JSON-RPC Request
▼
┌──────────────────┐
│ HTTP Bridge │
│ (This Crate) │
└──────┬───────────┘
│ stdin (JSON-RPC)
│
▼
┌──────────────────┐
│ MCP Server │
│ (stdio mode) │
└──────┬───────────┘
│ stdout (JSON-RPC)
│
▼
┌──────────────────┐
│ HTTP Bridge │
└──────┬───────────┘
│ HTTP Response
│ JSON-RPC Result
▼
┌─────────────┐
│ HTTP Client │
└─────────────┘
A test script is provided:
./test_http_bridge.shThis script:
- Starts the HTTP bridge
- Tests the health endpoint
- Sends MCP initialize request
- Sends tools/list request
- Verifies responses
- Cleans up
- Easy HTTP Access: No need to understand stdio transport
- Web Integration: Can be called from web browsers and HTTP clients
- Testing: Easy to test with curl or Postman
- Debugging: Can inspect requests/responses with HTTP tools
ahma_http_bridge is licensed under MIT OR Apache-2.0.
This crate stays permissive because it is a transport and integration layer: applications can expose MCP over HTTP without changing the license choice for the bridge itself. If you combine it with AGPL-licensed sibling crates elsewhere in the workspace, those crates keep their own terms.
The authoritative license declaration for this crate is in
ahma_http_bridge/Cargo.toml.
- Clean Separation: Bridge is a separate crate
- Reusable: Can be used as a library in other projects
- Maintainable: Simple, focused implementation (~250 lines)
- Extensible: Easy to add features like authentication, rate limiting, etc.
- No Server-Initiated Messages: HTTP is request-response, so server-initiated notifications are not supported
- Single Request at a Time: Requests are serialized through the subprocess
- No Streaming: Large responses are buffered completely
For production use with server-initiated messages, consider using WebSocket.
The HTTP bridge implements a sandboxing security model to restrict AI-generated commands to the client's workspace.
- File Write Access: Write operations are restricted to the client's workspace (sandbox scope)
- Per-Session Isolation: Each client session gets its own sandbox scope derived from workspace roots
- Immutable Sandbox: Once locked, the sandbox scope cannot be changed (prevents escalation attacks)
- Path Traversal Protection: Attempts to escape via
../or symlinks are blocked
- macOS: Uses
sandbox-execwith Seatbelt profiles per command - Linux: Uses Landlock (kernel 5.13+) for process-level restrictions
For environments requiring stricter security, the --disable-temp-files flag blocks writes to temp directories:
ahma-http-bridge --disable-temp-filesThis prevents data exfiltration via /tmp or /var/folders but breaks tools that require temp file access.
These are accepted trade-offs for practical operation:
| Limitation | Risk | Mitigation |
|---|---|---|
| Read access unrestricted | AI can read any file (including ~/.ssh/id_rsa) |
Required for shells to function; outer sandbox recommended |
| Network unrestricted | Data exfiltration via network | Future: optional --restrict-network |
| Temp dirs writable | Persistence outside sandbox | Use --disable-temp-files for high-security |
| No authentication | Localhost access is trusted | Only bind to 127.0.0.1 |
- Local Development Only: The HTTP bridge is designed for local use on developer machines
- Trusted Client: Clients on localhost are trusted (no authentication)
- Outer Sandbox: When running inside Cursor/VS Code, the IDE's sandbox provides additional protection
Security tests verify that sandbox escape attempts fail:
- Path traversal (
../../../etc/passwd) - Symlink escapes (link inside workspace pointing outside)
- Absolute path escapes (
/etc/passwd) - Command injection via path
See tests/sandbox_security_test.rs and ahma/tests/sandbox_security_red_team_test.rs.