MCP Task Queue — EC2 + SSE
README.md
# MCP Task Queue — EC2 + SSE
Pure Python MCP setup so Cursor on your laptop works on tasks stored in a Postgres DB on EC2. Cursor talks MCP over SSE/HTTPS to the EC2 MCP server; it never touches Postgres directly.
## Architecture
```
Jira Issue
↓
n8n Workflow (triggered by Jira)
↓ HTTP POST /ingest
EC2: Ingest API (FastAPI) — validates, normalizes, writes to Postgres
↓
EC2: Postgres — tasks table (pending, in_progress, completed, failed)
↑
EC2: MCP Server (SSE) — tools operate on tasks, exposes /mcp/sse
↑ SSE/HTTPS
Laptop: Cursor — configured to connect to EC2 MCP server
```
## Project layout
```
app/
__init__.py
config.py # DATABASE_URL, INGEST_TOKEN
database.py # async Postgres task CRUD
ingest_api.py # FastAPI routes: /health, POST /ingest
mcp_server.py # FastMCP with tools (list_tasks, get_task, ...)
mcp_sse.py # SSE transport wiring
ingest_app.py # Entry: uvicorn ingest_app:app
mcp_sse_app.py # Entry: uvicorn mcp_sse_app:app
init_db.py # Create tasks table
env.example # Template for .env
```
## Install
```bash
python -m venv .venv
source .venv/bin/activate # Windows: .\.venv\Scripts\Activate.ps1
pip install -r requirements.txt
cp env.example .env
# Edit .env: DATABASE_URL, INGEST_TOKEN
```
## Database migrations
Create the `tasks` table once:
```bash
python init_db.py
```
Schema: `id`, `source`, `title`, `instructions`, `acceptance_criteria` (JSONB), `file_hints` (JSONB), `meta` (JSONB), `status`, timestamps (`created_at`, `started_at`, `completed_at`, `failed_at`, `updated_at`), `completion_note`, `failure_reason`, `previous_status`.
## Run the services
### Ingest API (n8n webhook)
```bash
uvicorn ingest_app:app --host 0.0.0.0 --port 8787
```
- `GET /health` — health check
- `POST /ingest` — requires header `X-Ingest-Token: <INGEST_TOKEN>`
### MCP SSE server (Cursor endpoint)
```bash
uvicorn mcp_sse_app:app --host 0.0.0.0 --port 8080
```
- `GET /health` — health check
- `GET /mcp/sse` — MCP SSE endpoint (Cursor connects here)
- `POST /mcp/messages/` — MCP message endpoint (used by client after SSE)
## Configure Cursor (client)
Configure Cursor on your laptop to talk to the EC2 MCP server over SSE. Copy `mcp.json.example` to `.cursor/mcp.json` or add to global MCP settings:
```json
{
"mcpServers": {
"ec2-tasks": {
"url": "https://<YOUR_DOMAIN_OR_IP>/mcp/sse",
"env": {}
}
}
}
```
Replace `<YOUR_DOMAIN_OR_IP>` with your EC2 hostname or IP. If using a non-standard port, include it: `https://example.com:8080/mcp/sse`. For local dev: `http://localhost:8080/mcp/sse`.
**Important:** Cursor connects to the EC2 MCP server over SSE/HTTPS. EC2 talks to Postgres; Cursor never touches the database.
## Rules & work prompt
Edit `rules.md` in the project root to add rules and instructions the MCP server will include in work prompts. This content is combined with each task and passed to the agent.
- **MCP prompt** `task_work_prompt(task_id)` — Returns the full prompt (rules + task) to pass to the agent.
- **Tool** `get_work_prompt(task_id)` — Same content as JSON for programmatic use.
## MCP tools
| Tool | Description |
|------------------|--------------------------------------------------|
| `list_tasks` | List tasks (inbox by default; filter by status) |
| `get_task` | Get full task by id |
| `get_work_prompt`| Build full prompt (rules + task) for a task id |
| `enqueue_task` | Manually create a task (for testing) |
| `start_task` | Set status to `in_progress` |
| `complete_task` | Set status to `completed` |
| `fail_task` | Set status to `failed` |
## n8n HTTP Request (Jira → ingest)
- **Method:** POST
- **URL:** `https://<YOUR_EC2>/ingest` (or via ngrok / load balancer)
- **Headers:** `Content-Type: application/json`, `X-Ingest-Token: <INGEST_TOKEN>`
- **Body:** JSON with at least `id`, `instructions`; optional `source`, `title`, `acceptance_criteria`, `file_hints`, `meta`
## EC2 deployment notes
1. **Environment:** Set `DATABASE_URL` and `INGEST_TOKEN` (e.g. via `.env` or systemd env).
2. **Processes:**
- Ingest: `uvicorn ingest_app:app --host 0.0.0.0 --port 8787`
- MCP SSE: `uvicorn mcp_sse_app:app --host 0.0.0.0 --port 8080`
3. **Reverse proxy (Nginx):**
- Proxy `/ingest` and `/health` to ingest on 8787.
- Proxy `/mcp/sse` and `/mcp/messages/` to MCP on 8080.
- Use HTTPS and keep MCP paths under the same origin.
4. **ngrok:**
- Expose both ports or put a reverse proxy in front and expose one.
- Example: `ngrok http 8080` for MCP, `ngrok http 8787` for ingest (separate tunnels).
## Ingest behavior
- **New task** → insert as `pending`.
- **Existing, status `pending` or `in_progress`** → return duplicate error (no overwrite).
- **Existing, status `completed` or `failed`** → upsert as new `pending` version, preserve `previous_status`.
This server cannot be deployed
Maintenance
ActivityInactive
ResponsivenessNo issues