Skip to main content
Glama
README.md
# MCP Hub

Internal MCP (Model Context Protocol) server and AI tools platform for the homelab. Acts as a **single gateway** that aggregates dozens of upstream MCP servers — your AIs connect once and get access to everything.

## How It Works

```
Claude Code / Claude Desktop / Any MCP Client
            │
            ▼
    ┌───────────────┐
    │   MCP Hub     │  ← single SSE connection
    │  (FastAPI)    │
    └──────┬────────┘
           │
    ┌──────┴────────────────────────────────┐
    │                                       │
    ▼              ▼              ▼         ▼
 Local Tools    GitHub MCP    Brave MCP   Wikipedia MCP
 (GitLab,K8s,   (stdio)       (stdio)     (stdio)
  Homelab)        ...           ...         ...
```

Your AI connects to **one endpoint** and gets access to:
- 15 built-in local tools (GitLab, Kubernetes, Homelab)
- 30+ upstream MCP servers (search, databases, code, AI, monitoring, communication)

## Quick Start

```bash
cd ~/projects/homelab/mcp-hub
export MH_GITLAB_TOKEN=<your-gitlab-pat>
export MH_GITHUB_TOKEN=<your-github-token>
docker compose up -d

# Dashboard: http://localhost:8500
# MCP SSE:   http://localhost:8500/mcp/sse
```

## Connect Your AI

Add to Claude Code `settings.json`:

```json
{
  "mcpServers": {
    "mcp-hub": {
      "type": "sse",
      "url": "http://mcp-hub.steelcanvas.dev/mcp/sse"
    }
  }
}
```

That's it. One connection, all tools.

## Available Upstream Servers

Configured in `upstreams.yaml`. Enabled by default (no API key needed):

| Server | Prefix | Description |
|--------|--------|-------------|
| fetch | `web__` | Fetch web pages, convert to markdown |
| github | `github__` | Repos, issues, PRs, actions, code search |
| docker | `docker__` | Container, image, volume management |
| arxiv | `arxiv__` | Search and read academic papers |
| wikipedia | `wiki__` | Search and read encyclopedia articles |
| filesystem | `fs__` | Read, write, search files |
| memory | `memory__` | Persistent knowledge graph |
| time | `time__` | Time, timezone conversions |
| sequential-thinking | `think__` | Structured reasoning |

Set an API key to enable:

| Server | Prefix | Env Var |
|--------|--------|---------|
| Brave Search | `brave__` | `MH_BRAVE_API_KEY` |
| Tavily | `tavily__` | `MH_TAVILY_API_KEY` |
| Exa | `exa__` | `MH_EXA_API_KEY` |
| Puppeteer | `browser__` | (just enable) |
| PostgreSQL | `pg__` | `MH_PROXY_POSTGRES_URL` |
| SQLite | `sqlite__` | `MH_SQLITE_PATH` |
| Redis | `redis__` | `MH_REDIS_URL` |
| Qdrant | `qdrant__` | `MH_QDRANT_URL` |
| Wolfram Alpha | `wolfram__` | `MH_WOLFRAM_APP_ID` |
| Slack | `slack__` | `MH_SLACK_BOT_TOKEN` |
| Discord | `discord__` | `MH_DISCORD_TOKEN` |
| Linear | `linear__` | `MH_LINEAR_API_KEY` |
| Notion | `notion__` | `MH_NOTION_API_KEY` |
| HuggingFace | `hf__` | `MH_HF_TOKEN` |
| OpenAI | `openai__` | `MH_OPENAI_API_KEY` |
| Prometheus | `prom__` | `MH_PROMETHEUS_URL` |
| Grafana | `grafana__` | `MH_GRAFANA_URL` + `MH_GRAFANA_API_KEY` |
| Sentry | `sentry__` | `MH_SENTRY_TOKEN` |
| S3/MinIO | `s3__` | `MH_S3_ACCESS_KEY` + `MH_S3_SECRET_KEY` |
| Cloudflare | `cf__` | `MH_CLOUDFLARE_TOKEN` |
| Terraform | `tf__` | (just enable) |
| Google Drive | `gdrive__` | (OAuth setup) |

## Adding Custom Upstream Servers

Edit `upstreams.yaml`:

```yaml
upstreams:
  # stdio-based (spawns a local process)
  my-server:
    transport: stdio
    enabled: true
    description: "My custom MCP server"
    command: npx
    args: ["-y", "my-mcp-package"]
    env:
      API_KEY: "${MH_MY_API_KEY}"
    prefix: my

  # SSE-based (connects to a remote HTTP endpoint)
  remote-server:
    transport: sse
    enabled: true
    description: "Remote MCP server"
    url: "http://192.168.1.50:9000/mcp/sse"
    headers:
      Authorization: "Bearer ${MH_REMOTE_TOKEN}"
    prefix: remote
```

## API Endpoints

| Endpoint | Description |
|----------|-------------|
| `GET /` | Web dashboard |
| `GET /health` | Health check (DB + proxy status) |
| `GET /api/tools` | All tools (local + proxied) with source |
| `GET /api/logs` | Tool invocation logs |
| `GET /api/proxy/status` | Upstream connection status |
| `GET /api/proxy/tools` | Proxied tool name -> source mapping |
| `POST /api/proxy/reconnect/{name}` | Reconnect a specific upstream |
| `GET /mcp/sse` | MCP SSE endpoint |
| `GET /docs` | OpenAPI docs |

## Architecture

```
mcp-hub/
├── mcp_hub/
│   ├── main.py            # FastAPI app + dashboard + proxy lifecycle
│   ├── mcp_server.py      # MCP server (FastMCP) with local tools
│   ├── config.py          # Pydantic settings (MH_* env vars)
│   ├── database.py        # SQLAlchemy async engine
│   ├── models/            # ORM models (ToolLog)
│   ├── tools/             # Built-in local tools
│   │   ├── gitlab_tools.py
│   │   ├── k8s_tools.py
│   │   └── homelab_tools.py
│   └── proxy/             # Upstream MCP proxy engine
│       ├── manager.py     # Orchestrates connections, registers proxied tools
│       ├── connector.py   # MCP client for a single upstream (stdio/SSE)
│       ├── registry.py    # Server config dataclass + YAML loader
│       ├── defaults.py    # Built-in default upstream configs
│       └── env_resolver.py # ${VAR} placeholder resolution
├── upstreams.yaml         # User-editable upstream config
├── templates/             # Jinja2 dashboard
├── static/css/            # Dark-themed dashboard styles
├── Dockerfile             # Multi-stage (Python + Node.js for npx)
├── docker-compose.yml     # Local dev stack
└── .gitlab-ci.yml         # CI/CD pipeline
```

## Development

```bash
python -m venv .venv
source .venv/bin/activate
pip install -e .[dev]

# Run locally
export MH_DATABASE_URL=postgresql+asyncpg://mcphub:mcphub@localhost:5432/mcphub
uvicorn mcp_hub.main:app --reload --port 8500

# Lint & test
ruff check .
pytest -v
```