docs(release): add protocol runbooks and rollout checklist

master
oabrivard 6 days ago
parent bfd752ac39
commit be9bbf4f83

@ -223,6 +223,12 @@ curl -X POST "http://127.0.0.1:8000/unsubscribe/auto-run?max_results=500" \
- Selected lists can be remembered (`remember_selection=true`) for scheduled auto-unsubscribe.
- State is saved in `UNSUBSCRIBE_HIL_STATE_FILE`.
## Runbooks
- A2A protocol runbook: `docs/a2a.md`
- MCP protocol runbook: `docs/mcp.md`
- Security, rotation, and rollout checklist: `docs/security.md`
## Notes
- Gmail "folders" are labels. This agent creates:

@ -0,0 +1,107 @@
# A2A Runbook
## Scope
This runbook covers the Agent-to-Agent (A2A) adapter exposed by the REST service in `app.main`.
## Endpoints
- Agent Card: `GET /.well-known/agent-card.json`
- RPC endpoint: `POST /a2a/rpc`
When using Docker Compose:
- Base URL: `http://127.0.0.1:8000`
## Protocol contract
- `protocolVersion` advertised in Agent Card: `1.0`
- Response header on A2A routes: `A2A-Version: 1.0`
- JSON-RPC version: `2.0`
Implemented methods:
- `ping` / `health.ping` / `health/ping`
- `SendMessage` (availability only)
## Authentication
The A2A adapter uses the same auth backend as REST:
- `AUTH_MODE=api_key`: `X-API-Key` or `Authorization: Bearer <AGENT_API_KEY>`
- `AUTH_MODE=jwt`: `Authorization: Bearer <JWT>`
- `AUTH_MODE=hybrid`: API key first, then JWT
Required scope for `SendMessage`:
- `availability:read`
## Request shape for SendMessage
`SendMessage` accepts several input shapes; the request must contain at least:
- `start` ISO datetime with timezone offset
- `end` ISO datetime with timezone offset
Accepted locations:
- `params.start` / `params.end`
- `params.input.start` / `params.input.end`
- `params.arguments.start` / `params.arguments.end`
- JSON embedded in message content
Optional:
- `calendar_ids`: array of calendar ids (defaults to `["primary"]`)
## Smoke tests
Agent Card:
```bash
curl http://127.0.0.1:8000/.well-known/agent-card.json
```
Availability:
```bash
curl -X POST "http://127.0.0.1:8000/a2a/rpc" \
-H "Content-Type: application/json" \
-H "X-API-Key: $AGENT_API_KEY" \
-d '{
"jsonrpc":"2.0",
"id":"req-1",
"method":"SendMessage",
"params":{
"start":"2026-03-10T09:00:00+01:00",
"end":"2026-03-10T10:00:00+01:00",
"calendar_ids":["primary"]
}
}'
```
## Error mapping
- `-32600`: invalid JSON-RPC request
- `-32601`: unknown method
- `-32602`: invalid params (including bad time window)
- `-32001`: unauthorized
- `-32000`: backend/runtime error
## Troubleshooting
If you get `-32001`:
- Verify `AUTH_MODE`
- Verify API key/JWT and scope `availability:read`
If you get `-32602`:
- Ensure `start` and `end` include timezone offsets
- Ensure `end > start`
If you get `-32000` with OAuth file errors:
- Check `GOOGLE_CLIENT_SECRETS_FILE` path
- Check `GOOGLE_TOKEN_FILE` path

@ -0,0 +1,102 @@
# MCP Runbook
## Scope
This runbook covers the MCP adapter exposed by `app.mcp_main`.
## Endpoint
- Streamable HTTP endpoint: `POST /mcp` (mounted under `app.mcp_main`)
When using Docker Compose:
- Base URL: `http://127.0.0.1:8001/mcp`
## Runtime modes
Local:
```bash
uv run uvicorn app.mcp_main:app --host 0.0.0.0 --port 8001
```
Docker Compose service:
- `personal-agent-mcp`
## Tool surface
Always enabled:
- `check_availability`
Optional mutation tools (disabled by default):
- `scan_mailbox`
- `list_unsubscribe_candidates`
- `execute_unsubscribe`
Enable optional tools with:
```bash
MCP_ENABLE_MUTATION_TOOLS=true
```
## Authorization and scope gates
MCP tools call the shared auth backend and read auth headers from request context.
Supported auth headers:
- `X-API-Key`
- `Authorization: Bearer ...`
Required scopes:
- `check_availability`: `availability:read`
- `scan_mailbox`: `mail:scan`
- `list_unsubscribe_candidates`: `unsubscribe:read`
- `execute_unsubscribe`: `unsubscribe:execute`
## Tool verification
Verify tool list from Python:
```bash
uv run python - <<'PY'
import asyncio
from app.mcp.server import mcp
async def main():
tools = await mcp.list_tools()
print([t.name for t in tools])
asyncio.run(main())
PY
```
Expected output by mode:
- default: `['check_availability']`
- with `MCP_ENABLE_MUTATION_TOOLS=true`: all four tools
## Protocol notes
- The server uses FastMCP Streamable HTTP.
- Basic GET to `/mcp` is not a health endpoint; MCP expects protocol-compliant requests.
- In local development, FastMCP may enforce host/origin checks. If you see `421 Misdirected Request`, verify host/port and reverse-proxy headers.
## Troubleshooting
If tools fail with auth errors:
- Check `AUTH_MODE` and credentials
- Confirm JWT contains required scopes
- For API key mode, verify `AGENT_API_KEY`
If tool calls fail with Google errors:
- Verify OAuth file mounts in Docker:
- `GOOGLE_CLIENT_SECRETS_FILE`
- `GOOGLE_TOKEN_FILE`

@ -0,0 +1,110 @@
# Security Runbook
## Security model
The service supports three auth modes via `AUTH_MODE`:
- `api_key`: static shared secret (`AGENT_API_KEY`)
- `jwt`: bearer JWT with scope checks
- `hybrid`: API key accepted first, then JWT fallback
The same backend is used across:
- REST API
- A2A adapter
- MCP tools
## Recommended deployment posture
External traffic:
- Use `AUTH_MODE=jwt`
- Require HTTPS at reverse proxy/gateway
- Restrict exposed routes to required protocol endpoints
Internal traffic:
- `AUTH_MODE=hybrid` is acceptable during migration
- Prefer mTLS/private network for service-to-service traffic
## Scope matrix
- `availability:read`: availability access
- `mail:scan`: inbox scan and triage
- `unsubscribe:read`: candidate discovery
- `unsubscribe:execute`: unsubscribe execution
- `unsubscribe:digest`: digest scan and send
## Secret and token handling
Never commit secrets:
- `.env`
- `token.json`
- Google OAuth client secret files
Always persist and back up:
- `token.json`
- `data/sent_unsubscribe_links.json`
- `data/unsubscribed_methods.json`
## Key and token rotation
### API key rotation (api_key/hybrid)
1. Generate new strong key.
2. Update environment (`AGENT_API_KEY`) in deployment.
3. Restart services.
4. Update all clients.
5. Remove old key from all stores.
### JWT secret rotation (jwt/hybrid)
1. Generate new signing secret.
2. Roll out issuer/signing config first.
3. Update server `AUTH_JWT_SECRET`.
4. Restart services.
5. Force token refresh for clients.
## Incident response checklist
If credential leak is suspected:
1. Revoke compromised key/secret immediately.
2. Rotate API key and JWT secret.
3. Invalidate active tokens (issuer-side).
4. Review logs for unusual scans/unsubscribe operations.
5. Disable mutation MCP tools (`MCP_ENABLE_MUTATION_TOOLS=false`) until investigation completes.
6. Re-enable features after containment and verification.
## Release rollout checklist
Preflight:
1. `uv run pytest -q`
2. `uv run python -c "import app.main, app.mcp_main; print('import_ok')"`
3. `docker compose config --services`
Canary:
1. Deploy to one node/environment.
2. Validate:
- `GET /health`
- `GET /.well-known/agent-card.json`
- A2A `SendMessage`
- MCP tool listing
3. Monitor errors for 30-60 minutes.
Full rollout:
1. Deploy all nodes.
2. Re-run smoke checks.
3. Confirm scheduler jobs continue as expected.
Rollback:
1. Redeploy previous image/tag.
2. Verify health and protocol smoke checks.
3. Keep state files (`data/*.json`) unchanged during rollback.
Loading…
Cancel
Save