docs : add HTTP transport + API token auth to MCP spec
Both STDIO (local) and HTTP (LAN) transports are now in scope. HTTP secured by API token on User entity with custom authenticator. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -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://<server-ip>: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 <api-token>"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
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 <token>` 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 <username>` 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 <token>" 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
|
||||
|
||||
Reference in New Issue
Block a user