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