Skip to main content
Glama
mohamedomar193

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`.