diff --git a/docs/superpowers/specs/2026-03-15-mcp-server-design.md b/docs/superpowers/specs/2026-03-15-mcp-server-design.md index 5ebdc91..81ae6c1 100644 --- a/docs/superpowers/specs/2026-03-15-mcp-server-design.md +++ b/docs/superpowers/specs/2026-03-15-mcp-server-design.md @@ -8,8 +8,11 @@ Lesstime is a project management app (Symfony 8 + API Platform 4). We want AI assistants to interact with projects, tasks, and time entries via the Model Context Protocol (MCP). -**Phase 1 (this spec)**: STDIO transport for Claude Code local usage. -**Phase 2 (future)**: HTTP transport + API token auth + Cloudflare Tunnel for remote clients (Claude Web, ChatGPT, Codex). +Both transports are implemented together: +- **STDIO**: Claude Code on the same machine (local dev, `php bin/console mcp:server`) +- **HTTP**: Claude Code or any MCP client on the LAN (`http://:8082/_mcp`), secured by API token + +Future: Cloudflare Tunnel for internet-facing access (Claude Web, ChatGPT, Codex). ## Technology Choice @@ -69,13 +72,31 @@ mcp: Use list-users and list-clients to discover valid user and client IDs. client_transports: stdio: true - http: false # Phase 2 + http: true + + http: + path: /_mcp + session: + store: file + directory: '%kernel.cache_dir%/mcp-sessions' + ttl: 3600 +``` + +### Nginx Configuration + +Add a location block to pass `/_mcp` requests to Symfony (same pattern as `/api`): + +```nginx +location /_mcp { + try_files $uri /index.php$is_args$args; +} ``` ### Claude Code Configuration +**Option A — Local (STDIO, same machine):** + ```json -// .claude/settings.json or project settings { "mcpServers": { "lesstime": { @@ -87,13 +108,38 @@ mcp: } ``` -Note: The app runs in Docker (`php-lesstime-fpm` container), so the command uses `docker exec` to run inside the container. +**Option B — Network (HTTP, another machine on LAN):** -### Security Model (Phase 1) +```json +{ + "mcpServers": { + "lesstime": { + "type": "url", + "url": "http://192.168.x.x:8082/_mcp", + "headers": { + "Authorization": "Bearer " + } + } + } +} +``` -Phase 1 uses STDIO transport only (Claude Code local). The console command runs without a Symfony security context. All tools run with **full privileges** (equivalent to ROLE_ADMIN), since only the local developer has access. No authentication is needed. +### Security Model -Phase 2 will add API token authentication on the HTTP transport. +**STDIO transport**: No authentication. The console command runs locally with full privileges (equivalent to ROLE_ADMIN). Only the local developer has access. + +**HTTP transport**: Secured by API token. A new `apiToken` field on the `User` entity stores a unique token per user. A custom Symfony authenticator (`ApiTokenAuthenticator`) checks the `Authorization: Bearer ` header on `/_mcp` requests and authenticates as the corresponding user. + +#### API Token Implementation + +1. **Entity change**: Add `apiToken` (string, unique, nullable) to `User` + Doctrine migration +2. **Authenticator**: `src/Security/ApiTokenAuthenticator.php` — a Symfony custom authenticator that: + - Extracts the token from the `Authorization` header + - Looks up the user by `apiToken` + - Returns 401 if token missing/invalid +3. **Firewall**: New firewall entry in `config/packages/security.yaml` for `/_mcp` path, before the main `api` firewall +4. **Token generation**: A console command `app:generate-api-token ` to generate/regenerate tokens +5. **Fixtures**: Add an API token to the admin fixture user for dev/testing ## Tools Specification @@ -406,21 +452,21 @@ class ListTasksTool ## Installation Steps 1. `composer require symfony/mcp-bundle` (inside Docker container) -2. Create `config/packages/mcp.yaml` with STDIO transport -3. Create tool classes in `src/Mcp/Tool/` -4. Test with `php bin/console mcp:server` (STDIO) -5. Configure Claude Code settings to point to the MCP server +2. Create `config/packages/mcp.yaml` with STDIO + HTTP transports +3. Add MCP route: `config/routes/mcp.yaml` +4. Add Nginx location block for `/_mcp` +5. Add `apiToken` field to `User` entity + migration +6. Create `ApiTokenAuthenticator` + security firewall for `/_mcp` +7. Create `app:generate-api-token` console command +8. Update fixtures with API token for admin user +9. Create tool classes in `src/Mcp/Tool/` +10. Test STDIO: `php bin/console mcp:server` +11. Test HTTP: `curl -H "Authorization: Bearer " http://localhost:8082/_mcp` +12. Configure Claude Code settings (STDIO local or HTTP network) -Note: STDIO transport does not need HTTP routes. Routes are only needed for Phase 2 (HTTP transport). +## Future -## Phase 2 (Future) +When ready for internet-facing access: -When ready for remote clients: - -1. Enable HTTP transport on `/_mcp` -2. Add MCP route: `config/routes/mcp.yaml` -3. Add `apiToken` field on `User` entity + migration -4. Create `ApiTokenAuthenticator` (Symfony custom authenticator) -5. Add firewall rule for `/_mcp` path -6. Set up Cloudflare Tunnel for external access -7. Configure Claude Web / ChatGPT / Codex with the tunnel URL + token +1. Set up Cloudflare Tunnel for external access +2. Configure Claude Web / ChatGPT / Codex with the tunnel URL + token