init
This commit is contained in:
577
README.md
Normal file
577
README.md
Normal file
@@ -0,0 +1,577 @@
|
||||
# local-mcp
|
||||
|
||||
`local-mcp` is a localhost-first MCP server whose primary responsibility is to deliver the latest user instruction to an agent through the `get_user_request` tool, while also providing a responsive web UI for managing the instruction queue and monitoring server/agent activity.
|
||||
|
||||
This document is the implementation plan for the project.
|
||||
|
||||
## 1. Goals
|
||||
|
||||
- Provide a single MCP tool, `get_user_request`, that returns at most one instruction per call.
|
||||
- Give the user a polished local web UI to add, edit, remove, review, and monitor instructions.
|
||||
- Preserve queue integrity so consumed instructions are clearly visible but no longer editable/deletable.
|
||||
- Support configurable waiting/default-response behavior when no instruction is available.
|
||||
- Show live server status and inferred agent connectivity in the UI.
|
||||
- Keep the stack lightweight, maintainable, debuggable, and friendly to local development.
|
||||
|
||||
## 2. Recommended Tech Stack
|
||||
|
||||
### Backend
|
||||
|
||||
- **Language/runtime:** Python 3.11+
|
||||
- **MCP integration:** official Python MCP SDK
|
||||
- **HTTP server/API layer:** FastAPI
|
||||
- **ASGI server:** Uvicorn
|
||||
- **Persistence:** SQLite via Python standard library `sqlite3`
|
||||
- **Concurrency/state coordination:** `asyncio` + standard library synchronization primitives where needed
|
||||
- **Logging/error handling:** Python `logging`, structured request logs, centralized exception handling
|
||||
- **Configuration:** environment variables + small local config file (`.json` or `.toml`)
|
||||
|
||||
### Why this backend stack
|
||||
|
||||
- The MCP SDK is the correct dependency for exposing the MCP tool cleanly.
|
||||
- FastAPI + Uvicorn is a small, pragmatic backend stack that simplifies routing, validation, health endpoints, and server-sent updates without introducing a heavy framework.
|
||||
- SQLite keeps the system local-first, dependency-light, and durable enough for instruction history and settings.
|
||||
- Most supporting concerns remain in the Python standard library, which keeps third-party dependencies minimal.
|
||||
|
||||
### Frontend
|
||||
|
||||
- **UI technology:** plain HTML, CSS, and JavaScript only
|
||||
- **Realtime updates:** Server-Sent Events (preferred) with polling fallback if necessary
|
||||
- **Styling:** local CSS files with design tokens and component-specific stylesheets
|
||||
- **Client architecture:** modular vanilla JS organized by feature (`api.js`, `state.js`, `events.js`, `instructions.js`, etc.)
|
||||
- **Assets:** all fonts/icons/scripts/styles stored locally in the repository; no CDN usage
|
||||
|
||||
### Mandatory frontend implementation instruction
|
||||
|
||||
Any future frontend implementation work **must first read and follow**:
|
||||
|
||||
- `.github/instructions/frontend-design.instructions.md`
|
||||
|
||||
This instruction file is mandatory for the UI because it requires a distinctive, production-grade, non-generic frontend. The implementation should not default to generic dashboard aesthetics.
|
||||
|
||||
## 3. Product/Architecture Plan
|
||||
|
||||
### Core backend responsibilities
|
||||
|
||||
1. Expose the MCP tool `get_user_request`.
|
||||
2. Maintain an instruction queue with durable storage.
|
||||
3. Mark instructions as consumed atomically when delivered to an agent.
|
||||
4. Expose local HTTP endpoints for the web UI.
|
||||
5. Stream status/instruction updates to the browser in real time.
|
||||
6. Infer agent connectivity from recent MCP tool activity.
|
||||
7. Persist and serve server configuration such as wait timeout and default empty response.
|
||||
|
||||
### Core frontend responsibilities
|
||||
|
||||
1. Show queued and consumed instructions in separate, clearly labeled sections.
|
||||
2. Allow add/edit/delete only for instructions that are still pending.
|
||||
3. Cross out and grey out consumed instructions.
|
||||
4. Show server status, inferred agent status, last fetch time, and configuration values.
|
||||
5. Update live as instruction state changes.
|
||||
6. Remain usable and visually polished on desktop and smaller screens.
|
||||
|
||||
### Suggested repository layout
|
||||
|
||||
```text
|
||||
local-mcp/
|
||||
├─ main.py
|
||||
├─ README.md
|
||||
├─ requirements.txt
|
||||
├─ app/
|
||||
│ ├─ __init__.py
|
||||
│ ├─ config.py
|
||||
│ ├─ database.py
|
||||
│ ├─ logging_setup.py
|
||||
│ ├─ models.py
|
||||
│ ├─ services/
|
||||
│ │ ├─ instruction_service.py
|
||||
│ │ ├─ status_service.py
|
||||
│ │ └─ event_service.py
|
||||
│ ├─ api/
|
||||
│ │ ├─ routes_instructions.py
|
||||
│ │ ├─ routes_status.py
|
||||
│ │ └─ routes_config.py
|
||||
│ └─ mcp_server.py
|
||||
├─ static/
|
||||
│ ├─ index.html
|
||||
│ ├─ css/
|
||||
│ │ ├─ base.css
|
||||
│ │ ├─ layout.css
|
||||
│ │ └─ components.css
|
||||
│ ├─ js/
|
||||
│ │ ├─ api.js
|
||||
│ │ ├─ app.js
|
||||
│ │ ├─ events.js
|
||||
│ │ ├─ instructions.js
|
||||
│ │ └─ status.js
|
||||
│ └─ assets/
|
||||
└─ data/
|
||||
└─ local_mcp.sqlite3
|
||||
```
|
||||
|
||||
## 4. Data Model Plan
|
||||
|
||||
### `instructions`
|
||||
|
||||
- `id` - string/UUID primary key
|
||||
- `content` - text, required
|
||||
- `status` - enum: `pending`, `consumed`
|
||||
- `created_at` - datetime
|
||||
- `updated_at` - datetime
|
||||
- `consumed_at` - nullable datetime
|
||||
- `consumed_by_agent_id` - nullable string
|
||||
- `position` - integer for stable queue order
|
||||
|
||||
### `settings`
|
||||
|
||||
- `default_wait_seconds` - integer — **minimum** seconds the tool waits before returning empty/default response; agents may request longer but not shorter
|
||||
- `default_empty_response` - text, nullable
|
||||
- `agent_stale_after_seconds` - integer
|
||||
|
||||
### `agent_activity`
|
||||
|
||||
- `agent_id` - string primary key
|
||||
- `last_seen_at` - datetime
|
||||
- `last_fetch_at` - datetime
|
||||
- `last_result_type` - enum: `instruction`, `empty`, `default_response`
|
||||
|
||||
## 5. Detailed API Design
|
||||
|
||||
All routes are local-only and intended for `localhost` usage.
|
||||
|
||||
### 5.1 MCP tool contract
|
||||
|
||||
#### Tool: `get_user_request`
|
||||
|
||||
**Purpose**
|
||||
|
||||
- Return the next pending instruction, if one exists.
|
||||
- If none exists, wait for a configurable duration, then return either an empty response or a configured default response.
|
||||
- Record agent activity so the UI can infer whether an agent is currently connected/recently active.
|
||||
|
||||
**Suggested input schema**
|
||||
|
||||
```json
|
||||
{
|
||||
"agent_id": "optional-string",
|
||||
"wait_seconds": "optional-integer",
|
||||
"default_response_override": "optional-string"
|
||||
}
|
||||
```
|
||||
|
||||
**Suggested output schema when an instruction is delivered**
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "ok",
|
||||
"result_type": "instruction",
|
||||
"instruction": {
|
||||
"id": "uuid",
|
||||
"content": "user instruction text",
|
||||
"consumed_at": "ISO-8601 timestamp"
|
||||
},
|
||||
"remaining_pending": 3,
|
||||
"waited_seconds": 0
|
||||
}
|
||||
```
|
||||
|
||||
**Suggested output schema when queue is empty**
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "ok",
|
||||
"result_type": "empty",
|
||||
"instruction": null,
|
||||
"response": "",
|
||||
"remaining_pending": 0,
|
||||
"waited_seconds": 10
|
||||
}
|
||||
```
|
||||
|
||||
**Suggested output schema when a default response is returned**
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "ok",
|
||||
"result_type": "default_response",
|
||||
"instruction": null,
|
||||
"response": "No new instructions available.",
|
||||
"remaining_pending": 0,
|
||||
"waited_seconds": 10
|
||||
}
|
||||
```
|
||||
|
||||
**Behavior rules**
|
||||
|
||||
- Deliver the oldest pending instruction first.
|
||||
- Mark the delivered instruction as consumed in the same transaction used to claim it.
|
||||
- Never return more than one instruction per call.
|
||||
- `default_wait_seconds` is a **minimum** enforced by the server. The actual wait is `max(wait_seconds or 0, server_minimum)`. Agents may request longer waits but cannot go below the floor — this prevents busy-polling.
|
||||
- Clamp `actual_wait` to an absolute server maximum (300 s).
|
||||
- Update the agent activity record on every call, even when no instruction is returned.
|
||||
- The UI should infer "agent connected" if the latest activity is within `agent_stale_after_seconds`.
|
||||
- Agent implementations should continue calling this tool instead of ending their work session on their own, so they can pick up newly added instructions without missing critical follow-up requests.
|
||||
|
||||
### 5.2 HTTP API for the web UI
|
||||
|
||||
#### `GET /healthz`
|
||||
|
||||
Returns service health.
|
||||
|
||||
**Response**
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "ok",
|
||||
"server_time": "ISO-8601 timestamp"
|
||||
}
|
||||
```
|
||||
|
||||
#### `GET /api/status`
|
||||
|
||||
Returns current server and agent summary.
|
||||
|
||||
**Response**
|
||||
|
||||
```json
|
||||
{
|
||||
"server": {
|
||||
"status": "up",
|
||||
"started_at": "ISO-8601 timestamp"
|
||||
},
|
||||
"agent": {
|
||||
"connected": true,
|
||||
"last_seen_at": "ISO-8601 timestamp",
|
||||
"last_fetch_at": "ISO-8601 timestamp",
|
||||
"agent_id": "copilot-agent"
|
||||
},
|
||||
"queue": {
|
||||
"pending_count": 2,
|
||||
"consumed_count": 8
|
||||
},
|
||||
"settings": {
|
||||
"default_wait_seconds": 10,
|
||||
"default_empty_response": "No new instructions available.",
|
||||
"agent_stale_after_seconds": 30
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### `GET /api/instructions`
|
||||
|
||||
Returns all instructions in queue order.
|
||||
|
||||
**Query params**
|
||||
|
||||
- `status=pending|consumed|all` (default `all`)
|
||||
|
||||
**Response**
|
||||
|
||||
```json
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
"id": "uuid",
|
||||
"content": "Implement logging",
|
||||
"status": "pending",
|
||||
"created_at": "ISO-8601 timestamp",
|
||||
"updated_at": "ISO-8601 timestamp",
|
||||
"consumed_at": null,
|
||||
"consumed_by_agent_id": null,
|
||||
"position": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### `POST /api/instructions`
|
||||
|
||||
Creates a new pending instruction.
|
||||
|
||||
**Request**
|
||||
|
||||
```json
|
||||
{
|
||||
"content": "Add a new status indicator"
|
||||
}
|
||||
```
|
||||
|
||||
**Response**: `201 Created`
|
||||
|
||||
```json
|
||||
{
|
||||
"item": {
|
||||
"id": "uuid",
|
||||
"content": "Add a new status indicator",
|
||||
"status": "pending",
|
||||
"created_at": "ISO-8601 timestamp",
|
||||
"updated_at": "ISO-8601 timestamp",
|
||||
"consumed_at": null,
|
||||
"consumed_by_agent_id": null,
|
||||
"position": 3
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### `PATCH /api/instructions/{instruction_id}`
|
||||
|
||||
Edits a pending instruction only.
|
||||
|
||||
**Request**
|
||||
|
||||
```json
|
||||
{
|
||||
"content": "Reword an existing pending instruction"
|
||||
}
|
||||
```
|
||||
|
||||
**Rules**
|
||||
|
||||
- Return `409 Conflict` if the instruction has already been consumed.
|
||||
- Return `404 Not Found` if the instruction does not exist.
|
||||
|
||||
#### `DELETE /api/instructions/{instruction_id}`
|
||||
|
||||
Deletes a pending instruction only.
|
||||
|
||||
**Rules**
|
||||
|
||||
- Return `409 Conflict` if the instruction has already been consumed.
|
||||
- Return `204 No Content` on success.
|
||||
|
||||
#### `GET /api/config`
|
||||
|
||||
Returns editable runtime settings.
|
||||
|
||||
**Response**
|
||||
|
||||
```json
|
||||
{
|
||||
"default_wait_seconds": 10,
|
||||
"default_empty_response": "No new instructions available.",
|
||||
"agent_stale_after_seconds": 30
|
||||
}
|
||||
```
|
||||
|
||||
#### `PATCH /api/config`
|
||||
|
||||
Updates runtime settings.
|
||||
|
||||
**Request**
|
||||
|
||||
```json
|
||||
{
|
||||
"default_wait_seconds": 15,
|
||||
"default_empty_response": "",
|
||||
"agent_stale_after_seconds": 45
|
||||
}
|
||||
```
|
||||
|
||||
#### `GET /api/events`
|
||||
|
||||
Server-Sent Events endpoint for live UI updates.
|
||||
|
||||
**Event types**
|
||||
|
||||
- `instruction.created`
|
||||
- `instruction.updated`
|
||||
- `instruction.deleted`
|
||||
- `instruction.consumed`
|
||||
- `status.changed`
|
||||
- `config.updated`
|
||||
|
||||
**SSE payload example**
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "instruction.consumed",
|
||||
"timestamp": "ISO-8601 timestamp",
|
||||
"data": {
|
||||
"id": "uuid",
|
||||
"consumed_by_agent_id": "copilot-agent"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 6. UI/UX Plan
|
||||
|
||||
### Layout priorities
|
||||
|
||||
- A strong local-control dashboard feel rather than a generic admin template
|
||||
- Clear separation between pending work and already-consumed history
|
||||
- High-visibility connection/status strip for server and agent state
|
||||
- Fast creation flow for new instructions
|
||||
- Mobile-friendly stacking without losing queue readability
|
||||
|
||||
### Required screens/sections
|
||||
|
||||
- Header with project identity and server status
|
||||
- Agent activity panel with last seen/fetch information
|
||||
- Composer form for new instructions
|
||||
- Pending instructions list with edit/delete actions
|
||||
- Consumed instructions list with crossed-out styling and metadata
|
||||
- Settings panel for wait timeout/default response behavior
|
||||
|
||||
### Frontend quality bar
|
||||
|
||||
- Follow `.github/instructions/frontend-design.instructions.md` before implementing any UI.
|
||||
- Use only local assets.
|
||||
- Build a visually distinctive interface with careful typography, color, spacing, motion, and responsive behavior.
|
||||
- Keep accessibility in scope: semantic HTML, keyboard support, visible focus states, sufficient contrast.
|
||||
|
||||
## 7. Logging, Reliability, and Error Handling Plan
|
||||
|
||||
- Log startup, shutdown, configuration load, database initialization, and MCP registration.
|
||||
- Log each instruction lifecycle event: created, updated, deleted, consumed.
|
||||
- Log each `get_user_request` call with agent id, wait time, and result type.
|
||||
- Return structured JSON errors for API failures.
|
||||
- Protect queue consumption with transactions/locking so two simultaneous fetches cannot consume the same instruction.
|
||||
- Validate payloads and reject empty or whitespace-only instructions.
|
||||
- Handle browser reconnects for SSE cleanly.
|
||||
|
||||
## 8. Todo List
|
||||
|
||||
- [x] **Project setup**
|
||||
- [x] Create the backend package structure under `app/`.
|
||||
- [x] Add `requirements.txt` with only the required dependencies.
|
||||
- [x] Replace the placeholder contents of `main.py` with the application entrypoint.
|
||||
- [x] Add a local configuration strategy for defaults and runtime overrides.
|
||||
|
||||
- [x] **Data layer**
|
||||
- [x] Create SQLite schema for `instructions`, `settings`, and `agent_activity`.
|
||||
- [x] Add startup migration/initialization logic.
|
||||
- [x] Implement queue ordering and atomic consumption behavior.
|
||||
- [x] Seed default settings on first run.
|
||||
|
||||
- [x] **MCP server**
|
||||
- [x] Register the `get_user_request` tool using the official MCP Python SDK.
|
||||
- [x] Implement one-at-a-time delivery semantics.
|
||||
- [x] Implement wait-until-timeout behavior when the queue is empty.
|
||||
- [x] Return empty/default responses based on configuration.
|
||||
- [x] Record agent activity on every tool call.
|
||||
|
||||
- [x] **HTTP API**
|
||||
- [x] Implement `GET /healthz`.
|
||||
- [x] Implement `GET /api/status`.
|
||||
- [x] Implement `GET /api/instructions`.
|
||||
- [x] Implement `POST /api/instructions`.
|
||||
- [x] Implement `PATCH /api/instructions/{instruction_id}`.
|
||||
- [x] Implement `DELETE /api/instructions/{instruction_id}`.
|
||||
- [x] Implement `GET /api/config`.
|
||||
- [x] Implement `PATCH /api/config`.
|
||||
- [x] Implement `GET /api/events` for SSE.
|
||||
|
||||
- [x] **Frontend**
|
||||
- [x] Read and follow `.github/instructions/frontend-design.instructions.md` before starting UI work.
|
||||
- [x] Create `static/index.html` and split CSS/JS into separate folders/files.
|
||||
- [x] Build the instruction composer.
|
||||
- [x] Build the pending instruction list with edit/delete controls.
|
||||
- [x] Build the consumed instruction list with crossed-out/greyed-out styling.
|
||||
- [x] Build the live server/agent status panel.
|
||||
- [x] Build the settings editor for timeout/default-response behavior.
|
||||
- [x] Wire SSE updates into the UI so changes appear in real time.
|
||||
- [x] Make the interface responsive and keyboard accessible.
|
||||
|
||||
- [x] **Observability and robustness**
|
||||
- [x] Add centralized logging configuration.
|
||||
- [x] Add structured error responses and exception handling.
|
||||
- [x] Add queue-consumption concurrency protection.
|
||||
- [x] Add validation for invalid edits/deletes of consumed instructions.
|
||||
- [ ] Add tests for empty-queue, timeout, and consume-once behavior.
|
||||
|
||||
- [x] **Improvements (post-launch)**
|
||||
- [x] Replace 1-second polling wait loop with `asyncio.Event`-based immediate wakeup.
|
||||
- [x] Min-wait is a floor only when the queue is empty — a new instruction immediately wakes any waiting tool call (verified with timing test in `tests/test_wakeup.py`).
|
||||
- [x] Enrich SSE events with full item payloads (no extra re-fetch round-trips).
|
||||
- [x] Auto-refresh relative timestamps in the UI every 20 s.
|
||||
- [x] Document title badge showing pending instruction count.
|
||||
- [x] SSE reconnecting indicator in the header.
|
||||
- [x] Dark / light theme toggle defaulting to OS colour-scheme preference.
|
||||
- [x] `default_wait_seconds` changed to a server-enforced minimum (agents may request longer).
|
||||
- [x] Non-blocking `server.ps1` management script (start / stop / restart / status / logs).
|
||||
- [x] MCP stateless/stateful mode configurable via `MCP_STATELESS` env var (default `true`).
|
||||
- [x] Per-agent generation counter prevents abandoned (timed-out) coroutines from silently consuming instructions meant for newer calls.
|
||||
- [x] `tests/test_wakeup.py` covers both immediate-wakeup timing and concurrent-call generation safety.
|
||||
|
||||
- [ ] **Documentation and developer experience**
|
||||
- [x] Document local run instructions.
|
||||
- [x] Document the MCP tool contract clearly.
|
||||
- [x] Document the HTTP API with request/response examples.
|
||||
- [x] Document how agent connectivity is inferred.
|
||||
- [x] Document how the frontend design instruction must be used during UI implementation.
|
||||
|
||||
## 9. Running the Server
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Python 3.11+
|
||||
- pip
|
||||
|
||||
### Install dependencies
|
||||
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
### Start the server
|
||||
|
||||
```bash
|
||||
python main.py
|
||||
```
|
||||
|
||||
Or use the included management script (recommended — non-blocking):
|
||||
|
||||
```powershell
|
||||
.\server.ps1 start # start in background, logs to logs/
|
||||
.\server.ps1 stop # graceful stop
|
||||
.\server.ps1 restart # stop + start
|
||||
.\server.ps1 status # PID, memory, tail logs
|
||||
.\server.ps1 logs # show last 40 stdout lines
|
||||
.\server.ps1 logs -f # follow logs live
|
||||
.\server.ps1 logs 100 # show last 100 lines
|
||||
```
|
||||
|
||||
The server starts on `http://localhost:8000` by default.
|
||||
|
||||
| URL | Description |
|
||||
|-----|-------------|
|
||||
| `http://localhost:8000/` | Web UI |
|
||||
| `http://localhost:8000/mcp` | MCP streamable-HTTP endpoint |
|
||||
| `http://localhost:8000/docs` | FastAPI interactive API docs |
|
||||
|
||||
### Environment variable overrides
|
||||
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `HOST` | `0.0.0.0` | Bind address |
|
||||
| `HTTP_PORT` | `8000` | HTTP port |
|
||||
| `DB_PATH` | `data/local_mcp.sqlite3` | SQLite database path |
|
||||
| `LOG_LEVEL` | `INFO` | Logging level |
|
||||
| `DEFAULT_WAIT_SECONDS` | `10` | Default tool wait timeout |
|
||||
| `DEFAULT_EMPTY_RESPONSE` | _(empty)_ | Default response when queue is empty |
|
||||
| `AGENT_STALE_AFTER_SECONDS` | `30` | Seconds of inactivity before agent shown as idle |
|
||||
| `MCP_STATELESS` | `true` | `true` for stateless sessions (survives restarts, recommended); `false` for stateful |
|
||||
|
||||
### Configuring an MCP client (agent)
|
||||
|
||||
Point the agent's MCP client to the streamable-HTTP transport:
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"local-mcp": {
|
||||
"url": "http://localhost:8000/mcp",
|
||||
"transport": "streamable-http"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The agent should call `get_user_request` aggressively and continuously — **do not end the working session**. Every call returns the next pending instruction (if any). When the queue is empty the tool waits up to `wait_seconds` before returning an empty/default response, so the agent should loop and call again.
|
||||
|
||||
## 10. Implementation Notes for Future Work
|
||||
|
||||
- Prefer small, explicit modules over monolithic files.
|
||||
- Keep the project localhost-first and avoid remote asset dependencies.
|
||||
- Treat the MCP tool and the web UI as two views over the same instruction queue.
|
||||
- Optimize for correctness of queue semantics first, then refine the visual and realtime experience.
|
||||
Reference in New Issue
Block a user