Skip to main content
Glama
README.md
# MCP Hub

Aggregate and proxy multiple [Model Context Protocol](https://modelcontextprotocol.io) servers into unified Streamable HTTP endpoints. Connect to any combination of stdio, SSE, and Streamable HTTP MCP servers and expose them through a single HTTP interface.

## Features

- **Aggregate endpoint** (`/mcp`) — merges tools, resources, and prompts from all configured servers
- **Individual server endpoints** (`/mcp/server/:id`) — proxy a single downstream server
- **Group endpoints** (`/mcp/group/:id`) — proxy a logical group of servers
- **Namespacing** — tools are prefixed as `serverId__toolName` to avoid collisions
- **Per-endpoint auth** — optional bearer token authentication on any endpoint
- **Auto-reconnect** — exponential backoff with jitter for dropped connections
- **Health monitoring** — `GET /health` shows connection status for all servers
- **Transport support** — stdio, SSE, and Streamable HTTP downstream transports

## Requirements

- Node.js >= 18

## Install

```bash
npm install
npm run build
```

## Usage

```bash
# Start with a config file
node dist/index.js --config config.json

# Override the port
node dist/index.js --config config.json --port 8080

# Development mode (no build step)
npm run dev -- --config config.json
```

## Configuration

Create a `config.json` file (see `config.example.json` for a working example):

```json
{
  "servers": {
    "filesystem": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"],
      "transport": "stdio"
    },
    "remote-api": {
      "url": "http://localhost:3001/mcp",
      "transport": "streamable-http",
      "headers": { "Authorization": "Bearer token" }
    },
    "legacy": {
      "url": "http://localhost:3002/sse",
      "transport": "sse"
    }
  },
  "groups": {
    "dev-tools": ["filesystem", "remote-api"]
  },
  "hub": {
    "port": 7070,
    "auth": {
      "default": { "enabled": false },
      "endpoints": {
        "/mcp": { "enabled": true, "token": "my-secret-token" }
      }
    },
    "reconnect": {
      "maxAttempts": 3,
      "initialDelayMs": 1000,
      "maxDelayMs": 30000
    }
  }
}
```

### Server transports

| Transport | Required fields | Optional fields |
|-----------|----------------|-----------------|
| `stdio` | `command` | `args`, `env` |
| `streamable-http` | `url` | `headers` |
| `sse` | `url` | `headers` |

### Groups

Groups define named subsets of servers. Each group lists server IDs that must exist in the `servers` section.

### Auth

Auth can be configured per-endpoint or as a default. When `enabled` is `true`, requests must include an `Authorization: Bearer <token>` header.

Endpoint-specific config takes priority over the default. Prefix matching is used for parameterized routes (e.g., a rule for `/mcp` also covers `/mcp/server/filesystem`).

### Reconnect

Controls automatic reconnection to downstream servers when connections drop:

- `maxAttempts` — maximum retry attempts before giving up (default: 3)
- `initialDelayMs` — base delay for exponential backoff (default: 1000)
- `maxDelayMs` — maximum delay cap (default: 30000)

## Endpoints

| Method | Path | Description |
|--------|------|-------------|
| POST | `/mcp` | Send MCP requests to all servers (aggregated) |
| GET | `/mcp` | SSE stream for server-to-client notifications |
| DELETE | `/mcp` | Close the session |
| POST | `/mcp/server/:id` | Send MCP requests to a single server |
| GET | `/mcp/server/:id` | SSE stream for a single server |
| DELETE | `/mcp/server/:id` | Close the session |
| POST | `/mcp/group/:id` | Send MCP requests to a server group |
| GET | `/mcp/group/:id` | SSE stream for a server group |
| DELETE | `/mcp/group/:id` | Close the session |
| GET | `/health` | Health check with per-server connection status |

All MCP endpoints use the [Streamable HTTP transport](https://modelcontextprotocol.io/specification/2025-03-26/basic/transports#streamable-http). Sessions are tracked via the `mcp-session-id` header.

## Examples

### Health check

```bash
curl http://localhost:7070/health
```

```json
{
  "status": "healthy",
  "servers": [
    {
      "id": "filesystem",
      "connected": true,
      "tools": 14,
      "resources": 0,
      "prompts": 0
    }
  ]
}
```

### MCP initialize

```bash
curl -X POST http://localhost:7070/mcp \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "initialize",
    "params": {
      "protocolVersion": "2025-03-26",
      "capabilities": {},
      "clientInfo": { "name": "my-client", "version": "1.0.0" }
    }
  }'
```

### List tools (with session)

```bash
curl -X POST http://localhost:7070/mcp \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -H "mcp-session-id: <session-id-from-initialize>" \
  -d '{"jsonrpc": "2.0", "id": 2, "method": "tools/list", "params": {}}'
```

Tools are namespaced with the server ID:

```
filesystem__read_file
filesystem__write_file
filesystem__list_directory
...
```

### Call a tool

```bash
curl -X POST http://localhost:7070/mcp \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -H "mcp-session-id: <session-id>" \
  -d '{
    "jsonrpc": "2.0",
    "id": 3,
    "method": "tools/call",
    "params": {
      "name": "filesystem__list_directory",
      "arguments": { "path": "/tmp" }
    }
  }'
```

### Authenticated request

```bash
curl -X POST http://localhost:7070/mcp \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -H "Authorization: Bearer my-secret-token" \
  -d '{ ... }'
```

## Project Structure

```
src/
├── index.ts                 # CLI entry point (Commander)
├── types.ts                 # Shared TypeScript types
├── config/
│   ├── schema.ts            # Zod schemas + inferred types
│   ├── loader.ts            # Load and parse JSON config
│   └── validator.ts         # Semantic validation
├── client/
│   ├── manager.ts           # ServerManager — lifecycle of all connections
│   ├── connection.ts        # ClientConnection — single MCP client wrapper
│   ├── transports.ts        # Transport factory (stdio/SSE/HTTP)
│   └── reconnect.ts         # Exponential backoff with jitter
├── server/
│   ├── proxy.ts             # Creates MCP Server delegating to Aggregator
│   ├── aggregator.ts        # Merges tools/resources/prompts, routes calls
│   └── namespacing.ts       # serverId__toolName prefix utilities
├── http/
│   ├── app.ts               # Express app setup
│   ├── router.ts            # MCP endpoint routes + session management
│   ├── auth.ts              # Per-endpoint bearer token middleware
│   └── health.ts            # GET /health endpoint
└── utils/
    ├── logger.ts            # Winston logger
    ├── errors.ts            # Custom error classes
    └── shutdown.ts          # Graceful SIGINT/SIGTERM handling
```

## License

MIT