# sh-mcp-ops Operations-tier Sea Haven MCP server. Exposes the **ops** tool registry (internal-data, knowledge-base, google-maps, gmail, calendar, tasks, reminders — see `docs/design.md §3`) over **two universal interfaces backed by one shared dispatch path**: - **MCP** (Streamable HTTP) at `POST /mcp` — via `@modelcontextprotocol/sdk`. - **OpenAPI 3.1** — full document at `GET /openapi.json`, plus one `POST /tools/{tool-name}` endpoint per tool. All scope enforcement, audience binding, input validation, rate limiting, and audit logging live in `@sh-mcp/shared` (`executeTool`) and are identical across both interfaces (`docs/design.md §2.5`). ## Run locally (no AWS, no Cognito) ```bash # from the repo root npm install npm run build # or: npx tsc -b SH_MCP_ENV=local PORT=8081 npm run start -w @sh-mcp/server-ops # or live-reload: SH_MCP_ENV=local npm run dev -w @sh-mcp/server-ops ``` `SH_MCP_ENV=local` wires **in-memory dev clients** (seeded fake data, no network) and the `LocalAuthProvider`, which maps dev bearer tokens to identities. The local provider refuses to construct unless `SH_MCP_ENV=local`. ### Endpoints | Method | Path | Auth | Notes | | ------ | -------------------- | ---- | -------------------------------------- | | GET | `/healthz` | no | `{ "status": "ok" }` | | GET | `/openapi.json` | no | full OpenAPI 3.1 document | | POST | `/mcp` (+GET/DELETE) | yes | MCP Streamable HTTP | | POST | `/tools/:name` | yes | one-shot tool call, JSON in / JSON out | ### Dev bearer tokens (local only) | Token | Identity | Scopes | | --------------- | ------------------------ | -------------------------------------------- | | `dev-ops-only` | ops-only@seahavenind.com | `ops:read`, `ops:tasks` | | `dev-assistant` | lauren@seahavenind.com | + `gmail:self`, `calendar:self` | | `dev-ops-admin` | adam@seahavenind.com | `ops:read`, `ops:tasks`, gmail/calendar self | Tokens minted for a different tier (e.g. `dev-finance`) are **rejected** by this server (audience binding). ### Sample curl ```bash TOKEN=dev-ops-only curl -s localhost:8081/healthz curl -s localhost:8081/openapi.json | jq '.openapi, (.paths | keys | length)' curl -s -X POST localhost:8081/tools/lookup_work_order \ -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \ -d '{"workOrderId":"WO-1001"}' | jq # tool-hiding is convenience; the boundary is server-side — a forced call to a # tool you lack scope for is still 403: curl -s -o /dev/null -w '%{http_code}\n' -X POST localhost:8081/tools/search_inbox \ -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \ -d '{"query":"invoice"}' # → 403 (needs gmail:self) ``` ### MCP Inspector Point the [MCP Inspector](https://github.com/modelcontextprotocol/inspector) at `http://localhost:8081/mcp` (transport: **Streamable HTTP**) and set an `Authorization: Bearer dev-assistant` header. `tools/list` reflects only the tools your token's scopes permit. ## Environment | Var | Mode | Purpose | | ---------------------------- | ---- | --------------------------------------------- | | `SH_MCP_ENV` | both | `local` (dev clients) or `aws` (real/Cognito) | | `PORT` | both | listen port (default 8081) | | `AWS_REGION` | aws | Cognito region | | `COGNITO_USER_POOL_ID` | aws | issuer / JWKS source | | `COGNITO_ALLOWED_CLIENT_IDS` | aws | comma-list; the audience boundary | | `GOOGLE_MAPS_API_KEY` | aws | maps client | | `REMINDER_TARGET_ARN` etc. | aws | scheduler wiring | `aws` mode is not runtime-exercised in Phase 1 (real external clients are deferred stubs; Cognito infra is out of scope — see `docs/build-plan-phase-1.md`). ## CDK `cdk/app.ts` is a **synth-only** placeholder (`npm run synth -w @sh-mcp/server-ops`) so the CI `cdk synth` gate has a valid app. It provisions **no** real IAM, Cognito, API Gateway, or WAF — those land in a later phase behind the mandatory IAM cross-review.