docs(release): add protocol runbooks and rollout checklist
parent
bfd752ac39
commit
be9bbf4f83
@ -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…
Reference in New Issue