docs: rewrite README with structured overview and quick start guide
Replaces the minimal project description with a comprehensive README including a component overview table, quick start instructions, common Ansible operations, and links to detailed documentation. Aligns with Red Panda Approval™ standards.
This commit is contained in:
303
docs/mcpo.md
Normal file
303
docs/mcpo.md
Normal file
@@ -0,0 +1,303 @@
|
||||
# MCPO - Model Context Protocol OpenAI-Compatible Proxy
|
||||
|
||||
## Overview
|
||||
|
||||
MCPO is an OpenAI-compatible proxy that aggregates multiple Model Context Protocol (MCP) servers behind a single HTTP endpoint. It acts as the central MCP gateway for the Agathos sandbox, exposing tools from 13 MCP servers through a unified REST API with interactive Swagger documentation.
|
||||
|
||||
**Host:** miranda.incus
|
||||
**Role:** MCP Docker Host
|
||||
**Service Port:** 25530
|
||||
**API Docs:** http://miranda.incus:25530/docs
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
┌───────────────┐ ┌──────────────────────────────────────────────────────────┐
|
||||
│ LLM Client │ │ Miranda (miranda.incus) │
|
||||
│ (LobeChat, │────▶│ ┌────────────────────────────────────────────────────┐ │
|
||||
│ Open WebUI, │ │ │ MCPO :25530 │ │
|
||||
│ VS Code) │ │ │ OpenAI-compatible proxy │ │
|
||||
└───────────────┘ │ └─────┬────────────┬────────────┬───────────────────┘ │
|
||||
│ │ │ │ │
|
||||
│ ┌─────▼─────┐ ┌────▼────┐ ┌────▼─────┐ │
|
||||
│ │ stdio │ │ Local │ │ Remote │ │
|
||||
│ │ servers │ │ Docker │ │ servers │ │
|
||||
│ │ │ │ MCP │ │ │ │
|
||||
│ │ • time │ │ │ │ • athena │ │
|
||||
│ │ • ctx7 │ │ • neo4j │ │ • github │ │
|
||||
│ │ │ │ • graf │ │ • hface │ │
|
||||
│ │ │ │ • gitea │ │ • argos │ │
|
||||
│ │ │ │ │ │ • rommie │ │
|
||||
│ │ │ │ │ │ • caliban│ │
|
||||
│ │ │ │ │ │ • korax │ │
|
||||
│ └───────────┘ └─────────┘ └──────────┘ │
|
||||
└──────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
MCPO manages two categories of MCP servers:
|
||||
- **stdio servers**: MCPO spawns and manages the process (time, context7)
|
||||
- **streamable-http servers**: MCPO proxies to Docker containers on localhost or remote services across the Incus network
|
||||
|
||||
## Terraform Resources
|
||||
|
||||
### Host Definition
|
||||
|
||||
MCPO runs on Miranda, defined in `terraform/containers.tf`:
|
||||
|
||||
| Attribute | Value |
|
||||
|-----------|-------|
|
||||
| Image | noble |
|
||||
| Role | mcp_docker_host |
|
||||
| Security Nesting | true |
|
||||
| AppArmor | unconfined |
|
||||
| Proxy: mcp_containers | `0.0.0.0:25530-25539` → `127.0.0.1:25530-25539` |
|
||||
| Proxy: mcpo_ports | `0.0.0.0:25560-25569` → `127.0.0.1:25560-25569` |
|
||||
|
||||
### Dependencies
|
||||
|
||||
| Resource | Relationship |
|
||||
|----------|--------------|
|
||||
| prospero | Monitoring (Alloy → Loki, Prometheus) |
|
||||
| ariel | Neo4j database for neo4j-cypher and neo4j-memory MCP servers |
|
||||
| puck | Athena MCP server |
|
||||
| caliban | Caliban and Rommie MCP servers |
|
||||
|
||||
## Ansible Deployment
|
||||
|
||||
### Playbook
|
||||
|
||||
```bash
|
||||
cd ansible
|
||||
ansible-playbook mcpo/deploy.yml
|
||||
```
|
||||
|
||||
### Files
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `mcpo/deploy.yml` | Main deployment playbook |
|
||||
| `mcpo/config.json.j2` | MCP server configuration template |
|
||||
| `mcpo/mcpo.service.j2` | Systemd service unit template |
|
||||
| `mcpo/restart.yml` | Restart playbook with health check |
|
||||
| `mcpo/requirements.txt` | Python package requirements |
|
||||
|
||||
### Deployment Steps
|
||||
|
||||
1. **Create System User**: `mcpo:mcpo` system account
|
||||
2. **Create Directory**: `/srv/mcpo` with restricted permissions
|
||||
3. **Backup Config**: Saves existing `config.json` before overwriting
|
||||
4. **Template Config**: Renders `config.json.j2` with MCP server definitions
|
||||
5. **Install Node.js 22.x**: NodeSource repository for npx-based MCP servers
|
||||
6. **Install Python 3.12**: System packages for virtual environment
|
||||
7. **Create Virtual Environment**: Python 3.12 venv at `/srv/mcpo/.venv`
|
||||
8. **Install pip Packages**: `wheel`, `mcpo`, `mcp-server-time`
|
||||
9. **Pre-install Context7**: Downloads `@upstash/context7-mcp` via npx
|
||||
10. **Deploy Systemd Service**: Enables and starts `mcpo.service`
|
||||
11. **Health Check**: Verifies `http://localhost:25530/docs` returns HTTP 200
|
||||
|
||||
## MCP Servers
|
||||
|
||||
MCPO aggregates the following MCP servers in `config.json`:
|
||||
|
||||
### stdio Servers (managed by MCPO)
|
||||
|
||||
| Server | Command | Purpose |
|
||||
|--------|---------|---------|
|
||||
| `time` | `mcp-server-time` (Python venv) | Current time with timezone support |
|
||||
| `upstash-context7` | `npx @upstash/context7-mcp` | Library documentation lookup |
|
||||
|
||||
### streamable-http Servers (local Docker containers)
|
||||
|
||||
| Server | URL | Purpose |
|
||||
|--------|-----|---------|
|
||||
| `neo4j-cypher` | `localhost:25531/mcp` | Neo4j Cypher query execution |
|
||||
| `neo4j-memory` | `localhost:25532/mcp` | Neo4j knowledge graph memory |
|
||||
| `grafana` | `localhost:25533/mcp` | Grafana dashboard and API integration |
|
||||
| `gitea` | `localhost:25535/mcp` | Gitea repository management |
|
||||
|
||||
### streamable-http Servers (remote services)
|
||||
|
||||
| Server | URL | Purpose |
|
||||
|--------|-----|---------|
|
||||
| `argos-searxng` | `miranda.incus:25534/mcp` | SearXNG search integration |
|
||||
| `athena` | `puck.incus:22461/mcp` | Athena knowledge service (auth required) |
|
||||
| `github` | `api.githubcopilot.com/mcp/` | GitHub API integration |
|
||||
| `rommie` | `caliban.incus:8080/mcp` | Rommie agent interface |
|
||||
| `caliban` | `caliban.incus:22021/mcp` | Caliban computer use agent |
|
||||
| `korax` | `korax.helu.ca:22021/mcp` | Korax external agent |
|
||||
| `huggingface` | `huggingface.co/mcp` | Hugging Face model hub |
|
||||
|
||||
## Configuration
|
||||
|
||||
### Systemd Service
|
||||
|
||||
MCPO runs as a systemd service:
|
||||
|
||||
```
|
||||
ExecStart=/srv/mcpo/.venv/bin/mcpo --port 25530 --config /srv/mcpo/config.json
|
||||
```
|
||||
|
||||
- **User:** mcpo
|
||||
- **Restart:** always (3s delay)
|
||||
- **WorkingDirectory:** /srv/mcpo
|
||||
|
||||
### Storage Locations
|
||||
|
||||
| Path | Purpose | Owner |
|
||||
|------|---------|-------|
|
||||
| `/srv/mcpo` | Service directory | mcpo:mcpo |
|
||||
| `/srv/mcpo/.venv` | Python virtual environment | mcpo:mcpo |
|
||||
| `/srv/mcpo/config.json` | MCP server configuration | mcpo:mcpo |
|
||||
| `/srv/mcpo/config.json.bak` | Config backup (pre-deploy) | mcpo:mcpo |
|
||||
|
||||
## Required Vault Secrets
|
||||
|
||||
Add to `ansible/inventory/group_vars/all/vault.yml`:
|
||||
|
||||
| Variable | Purpose |
|
||||
|----------|---------|
|
||||
| `vault_athena_mcp_auth` | Bearer token for Athena MCP server |
|
||||
| `vault_github_personal_access_token` | GitHub personal access token |
|
||||
| `vault_huggingface_mcp_token` | Hugging Face API token |
|
||||
| `vault_gitea_mcp_access_token` | Gitea personal access token for MCP |
|
||||
|
||||
```bash
|
||||
ansible-vault edit inventory/group_vars/all/vault.yml
|
||||
```
|
||||
|
||||
## Host Variables
|
||||
|
||||
**File:** `ansible/inventory/host_vars/miranda.incus.yml`
|
||||
|
||||
```yaml
|
||||
# MCPO Config
|
||||
mcpo_user: mcpo
|
||||
mcpo_group: mcpo
|
||||
mcpo_directory: /srv/mcpo
|
||||
mcpo_port: 25530
|
||||
argos_mcp_url: http://miranda.incus:25534/mcp
|
||||
athena_mcp_auth: "{{ vault_athena_mcp_auth }}"
|
||||
athena_mcp_url: http://puck.incus:22461/mcp
|
||||
github_personal_access_token: "{{ vault_github_personal_access_token }}"
|
||||
neo4j_cypher_mcp_port: 25531
|
||||
neo4j_memory_mcp_port: 25532
|
||||
caliban_mcp_url: http://caliban.incus:22021/mcp
|
||||
korax_mcp_url: http://korax.helu.ca:22021/mcp
|
||||
huggingface_mcp_token: "{{ vault_huggingface_mcp_token }}"
|
||||
gitea_mcp_port: 25535
|
||||
```
|
||||
|
||||
## Monitoring
|
||||
|
||||
### Loki Logs
|
||||
|
||||
MCPO logs are collected via systemd journal by Alloy on Miranda. A relabel rule in Alloy's config tags `mcpo.service` journal entries with `job="mcpo"` so they appear as a dedicated app in Grafana dashboards.
|
||||
|
||||
| Log Source | Labels |
|
||||
|------------|--------|
|
||||
| Systemd journal | `{job="mcpo", hostname="miranda.incus"}` |
|
||||
|
||||
The Docker-based MCP servers (neo4j, grafana, gitea) each have dedicated syslog ports forwarded to Loki:
|
||||
|
||||
| Server | Syslog Port | Loki Job |
|
||||
|--------|-------------|----------|
|
||||
| neo4j-cypher | 51431 | `neo4j-cypher` |
|
||||
| neo4j-memory | 51432 | `neo4j-memory` |
|
||||
| grafana-mcp | 51433 | `grafana_mcp` |
|
||||
| argos | 51434 | `argos` |
|
||||
| gitea-mcp | 51435 | `gitea-mcp` |
|
||||
|
||||
### Grafana
|
||||
|
||||
Query MCPO-related logs in Grafana Explore:
|
||||
|
||||
```
|
||||
{hostname="miranda.incus", job="mcpo"}
|
||||
{hostname="miranda.incus", job="gitea-mcp"}
|
||||
{hostname="miranda.incus", job="grafana_mcp"}
|
||||
```
|
||||
|
||||
## Operations
|
||||
|
||||
### Start/Stop
|
||||
|
||||
```bash
|
||||
ssh miranda.incus
|
||||
|
||||
# MCPO service
|
||||
sudo systemctl start mcpo
|
||||
sudo systemctl stop mcpo
|
||||
sudo systemctl restart mcpo
|
||||
|
||||
# Or use the restart playbook with health check
|
||||
cd ansible
|
||||
ansible-playbook mcpo/restart.yml
|
||||
```
|
||||
|
||||
### Health Check
|
||||
|
||||
```bash
|
||||
# API docs endpoint
|
||||
curl http://miranda.incus:25530/docs
|
||||
|
||||
# From Miranda itself
|
||||
curl http://localhost:25530/docs
|
||||
```
|
||||
|
||||
### Logs
|
||||
|
||||
```bash
|
||||
# MCPO systemd journal
|
||||
ssh miranda.incus "sudo journalctl -u mcpo -f"
|
||||
|
||||
# Docker MCP server logs
|
||||
ssh miranda.incus "docker logs -f gitea-mcp"
|
||||
ssh miranda.incus "docker logs -f grafana-mcp"
|
||||
```
|
||||
|
||||
### Adding a New MCP Server
|
||||
|
||||
1. Add the server definition to `ansible/mcpo/config.json.j2`
|
||||
2. Add any required variables to `ansible/inventory/host_vars/miranda.incus.yml`
|
||||
3. Add vault secrets (if needed) to `inventory/group_vars/all/vault.yml`
|
||||
4. If Docker-based: create a new `ansible/{service}/deploy.yml` and `docker-compose.yml.j2`
|
||||
5. If Docker-based: add a syslog port to Miranda's host vars and Alloy config
|
||||
6. Redeploy: `ansible-playbook mcpo/deploy.yml`
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
| Symptom | Cause | Resolution |
|
||||
|---------|-------|------------|
|
||||
| MCPO won't start | Config JSON syntax error | Check `config.json` with `python -m json.tool` |
|
||||
| Server shows "unavailable" | Backend MCP server not running | Check Docker containers or remote service status |
|
||||
| Context7 timeout on first use | npx downloading package | Wait for download to complete, or re-run pre-install |
|
||||
| Health check fails | Port not ready | Increase retry delay, check `journalctl -u mcpo` |
|
||||
| stdio server crash loops | Missing runtime dependency | Verify Python venv and Node.js installation |
|
||||
|
||||
### Debug Commands
|
||||
|
||||
```bash
|
||||
# Check MCPO service status
|
||||
ssh miranda.incus "sudo systemctl status mcpo"
|
||||
|
||||
# Validate config.json syntax
|
||||
ssh miranda.incus "python3 -m json.tool /srv/mcpo/config.json"
|
||||
|
||||
# List Docker MCP containers
|
||||
ssh miranda.incus "docker ps --filter name=mcp"
|
||||
|
||||
# Test a specific MCP server endpoint
|
||||
ssh miranda.incus "curl -s http://localhost:25531/mcp | head"
|
||||
|
||||
# Check MCPO port is listening
|
||||
ssh miranda.incus "ss -tlnp | grep 25530"
|
||||
```
|
||||
|
||||
## References
|
||||
|
||||
- **MCPO Repository**: https://github.com/nicobailey/mcpo
|
||||
- **MCP Specification**: https://modelcontextprotocol.io/
|
||||
- [Ansible Practices](ansible.md)
|
||||
- [Agathos Overview](agathos.md)
|
||||
Reference in New Issue
Block a user