@terreno/mcp 56.0.0-beta.0 → 56.0.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/bootstrap.js +7 -7
- package/dist/bootstrap.js.map +1 -1
- package/dist/docs/templates/bootstrap/backend/types/models/userTypes.template.md +5 -4
- package/dist/docs/templates/tools/model.template.md +4 -2
- package/dist/docs/upgrades/0.31.0.md +60 -0
- package/dist/docs/upgrades/56.0.0-beta.2.md +59 -0
- package/dist/docs/versioned/next/README.md +1 -0
- package/dist/docs/versioned/next/explanation/README.md +7 -1
- package/dist/docs/versioned/next/explanation/deployment-architecture-gcp.md +65 -0
- package/dist/docs/versioned/next/explanation/deployment-baseline.md +78 -0
- package/dist/docs/versioned/next/explanation/positioning.md +164 -0
- package/dist/docs/versioned/next/explanation/repository-settings.md +44 -0
- package/dist/docs/versioned/next/explanation/roadmap-process.md +330 -0
- package/dist/docs/versioned/next/explanation/roadmap-seed-issues.md +887 -0
- package/dist/docs/versioned/next/how-to/README.md +8 -0
- package/dist/docs/versioned/next/how-to/build-for-web.md +69 -0
- package/dist/docs/versioned/next/how-to/deploy-backend-to-cloud-run.md +115 -0
- package/dist/docs/versioned/next/how-to/deploy-to-gcp.md +20 -228
- package/dist/docs/versioned/next/how-to/deploy-web-to-gcs-cdn.md +183 -0
- package/dist/docs/versioned/next/implementationPlans/PLAN_INDEX.md +31 -0
- package/dist/docs/versioned/next/implementationPlans/README.md +56 -30
- package/dist/docs/versioned/next/implementationPlans/agentic-sdlc-plugin.md +2 -1
- package/dist/docs/versioned/next/implementationPlans/ai-dev-loop-boost.md +1 -0
- package/dist/docs/versioned/next/implementationPlans/b2b-platform-program.md +115 -0
- package/dist/docs/versioned/next/implementationPlans/billing-stripe.md +158 -0
- package/dist/docs/versioned/next/implementationPlans/build-terreno-app-validation.md +1 -0
- package/dist/docs/versioned/next/implementationPlans/comms-abstraction.md +323 -0
- package/dist/docs/versioned/next/implementationPlans/comms-adapter-expo-push.md +107 -0
- package/dist/docs/versioned/next/implementationPlans/comms-adapter-sendgrid.md +157 -0
- package/dist/docs/versioned/next/implementationPlans/comms-adapter-twilio-sms.md +155 -0
- package/dist/docs/versioned/next/implementationPlans/comms-adapter-twilio-verify.md +99 -0
- package/dist/docs/versioned/next/implementationPlans/comms-admin-dashboard.md +180 -0
- package/dist/docs/versioned/next/implementationPlans/deploy-to-gcp.md +1 -0
- package/dist/docs/versioned/next/implementationPlans/deploy-to-vercel.md +1 -0
- package/dist/docs/versioned/next/implementationPlans/deployment-foundation.md +1 -0
- package/dist/docs/versioned/next/implementationPlans/docs-reference-coverage.md +1 -0
- package/dist/docs/versioned/next/implementationPlans/docs-tutorials-ai-first.md +1 -0
- package/dist/docs/versioned/next/implementationPlans/examples-demo-coverage.md +1 -0
- package/dist/docs/versioned/next/implementationPlans/infra-mcp-research.md +188 -0
- package/dist/docs/versioned/next/implementationPlans/infra-mcp.md +269 -0
- package/dist/docs/versioned/next/implementationPlans/migrate-cicd-to-circleci-research.md +126 -0
- package/dist/docs/versioned/next/implementationPlans/migrate-cicd-to-circleci.md +269 -0
- package/dist/docs/versioned/next/implementationPlans/native-module-baseline.md +105 -0
- package/dist/docs/versioned/next/implementationPlans/orgs-and-teams.md +158 -0
- package/dist/docs/versioned/next/implementationPlans/oss-governance-baseline.md +1 -0
- package/dist/docs/versioned/next/implementationPlans/password-reset-and-email-verification.md +147 -0
- package/dist/docs/versioned/next/implementationPlans/positioning-django-rails-universal.md +1 -0
- package/dist/docs/versioned/next/implementationPlans/public-roadmap-github.md +1 -0
- package/dist/docs/versioned/next/implementationPlans/upgrade-guides-and-skill.md +1 -0
- package/dist/docs/versioned/next/implementationPlans/web-ssr-and-admin-spa.md +1 -0
- package/dist/docs/versioned/next/reference/README.md +5 -0
- package/dist/docs/versioned/next/reference/admin-spa.md +130 -0
- package/dist/docs/versioned/next/reference/ai.md +519 -0
- package/dist/docs/versioned/next/reference/comms.md +132 -0
- package/dist/docs/versioned/next/reference/environment-variables.md +229 -382
- package/dist/docs/versioned/next/reference/test.md +108 -0
- package/dist/docs/versioned/next/roadmap.md +9 -0
- package/dist/docs/versioned/next/tasks/README.md +42 -0
- package/dist/docs/versioned/next/tasks/agentic-sdlc-plugin.md +5 -5
- package/dist/docs/versioned/next/tasks/billing-stripe.md +75 -0
- package/dist/docs/versioned/next/tasks/comms-abstraction.md +90 -0
- package/dist/docs/versioned/next/tasks/comms-adapter-expo-push.md +29 -0
- package/dist/docs/versioned/next/tasks/comms-adapter-sendgrid.md +39 -0
- package/dist/docs/versioned/next/tasks/comms-adapter-twilio-sms.md +39 -0
- package/dist/docs/versioned/next/tasks/comms-adapter-twilio-verify.md +19 -0
- package/dist/docs/versioned/next/tasks/comms-admin-dashboard.md +52 -0
- package/dist/docs/versioned/next/tasks/deploy-to-gcp.md +11 -11
- package/dist/docs/versioned/next/tasks/deployment-foundation.md +12 -12
- package/dist/docs/versioned/next/tasks/docs-reference-coverage.md +4 -4
- package/dist/docs/versioned/next/tasks/infra-mcp.md +43 -0
- package/dist/docs/versioned/next/tasks/migrate-cicd-to-circleci.md +215 -0
- package/dist/docs/versioned/next/tasks/native-module-baseline.md +42 -0
- package/dist/docs/versioned/next/tasks/orgs-and-teams.md +70 -0
- package/dist/docs/versioned/next/tasks/oss-governance-baseline.md +18 -18
- package/dist/docs/versioned/next/tasks/password-reset-and-email-verification.md +60 -0
- package/dist/docs/versioned/next/tasks/positioning-django-rails-universal.md +4 -4
- package/dist/docs/versioned/next/tasks/public-roadmap-github.md +15 -15
- package/package.json +4 -3
- package/src/docs/templates/bootstrap/backend/types/models/userTypes.template.md +5 -4
- package/src/docs/templates/tools/model.template.md +4 -2
- package/src/docs/upgrades/0.31.0.md +60 -0
- package/src/docs/upgrades/56.0.0-beta.2.md +59 -0
- package/src/docs/versioned/next/README.md +1 -0
- package/src/docs/versioned/next/explanation/README.md +7 -1
- package/src/docs/versioned/next/explanation/deployment-architecture-gcp.md +65 -0
- package/src/docs/versioned/next/explanation/deployment-baseline.md +78 -0
- package/src/docs/versioned/next/explanation/positioning.md +164 -0
- package/src/docs/versioned/next/explanation/repository-settings.md +44 -0
- package/src/docs/versioned/next/explanation/roadmap-process.md +330 -0
- package/src/docs/versioned/next/explanation/roadmap-seed-issues.md +887 -0
- package/src/docs/versioned/next/how-to/README.md +8 -0
- package/src/docs/versioned/next/how-to/build-for-web.md +69 -0
- package/src/docs/versioned/next/how-to/deploy-backend-to-cloud-run.md +115 -0
- package/src/docs/versioned/next/how-to/deploy-to-gcp.md +20 -228
- package/src/docs/versioned/next/how-to/deploy-web-to-gcs-cdn.md +183 -0
- package/src/docs/versioned/next/implementationPlans/PLAN_INDEX.md +31 -0
- package/src/docs/versioned/next/implementationPlans/README.md +56 -30
- package/src/docs/versioned/next/implementationPlans/agentic-sdlc-plugin.md +2 -1
- package/src/docs/versioned/next/implementationPlans/ai-dev-loop-boost.md +1 -0
- package/src/docs/versioned/next/implementationPlans/b2b-platform-program.md +115 -0
- package/src/docs/versioned/next/implementationPlans/billing-stripe.md +158 -0
- package/src/docs/versioned/next/implementationPlans/build-terreno-app-validation.md +1 -0
- package/src/docs/versioned/next/implementationPlans/comms-abstraction.md +323 -0
- package/src/docs/versioned/next/implementationPlans/comms-adapter-expo-push.md +107 -0
- package/src/docs/versioned/next/implementationPlans/comms-adapter-sendgrid.md +157 -0
- package/src/docs/versioned/next/implementationPlans/comms-adapter-twilio-sms.md +155 -0
- package/src/docs/versioned/next/implementationPlans/comms-adapter-twilio-verify.md +99 -0
- package/src/docs/versioned/next/implementationPlans/comms-admin-dashboard.md +180 -0
- package/src/docs/versioned/next/implementationPlans/deploy-to-gcp.md +1 -0
- package/src/docs/versioned/next/implementationPlans/deploy-to-vercel.md +1 -0
- package/src/docs/versioned/next/implementationPlans/deployment-foundation.md +1 -0
- package/src/docs/versioned/next/implementationPlans/docs-reference-coverage.md +1 -0
- package/src/docs/versioned/next/implementationPlans/docs-tutorials-ai-first.md +1 -0
- package/src/docs/versioned/next/implementationPlans/examples-demo-coverage.md +1 -0
- package/src/docs/versioned/next/implementationPlans/infra-mcp-research.md +188 -0
- package/src/docs/versioned/next/implementationPlans/infra-mcp.md +269 -0
- package/src/docs/versioned/next/implementationPlans/migrate-cicd-to-circleci-research.md +126 -0
- package/src/docs/versioned/next/implementationPlans/migrate-cicd-to-circleci.md +269 -0
- package/src/docs/versioned/next/implementationPlans/native-module-baseline.md +105 -0
- package/src/docs/versioned/next/implementationPlans/orgs-and-teams.md +158 -0
- package/src/docs/versioned/next/implementationPlans/oss-governance-baseline.md +1 -0
- package/src/docs/versioned/next/implementationPlans/password-reset-and-email-verification.md +147 -0
- package/src/docs/versioned/next/implementationPlans/positioning-django-rails-universal.md +1 -0
- package/src/docs/versioned/next/implementationPlans/public-roadmap-github.md +1 -0
- package/src/docs/versioned/next/implementationPlans/upgrade-guides-and-skill.md +1 -0
- package/src/docs/versioned/next/implementationPlans/web-ssr-and-admin-spa.md +1 -0
- package/src/docs/versioned/next/reference/README.md +5 -0
- package/src/docs/versioned/next/reference/admin-spa.md +130 -0
- package/src/docs/versioned/next/reference/ai.md +519 -0
- package/src/docs/versioned/next/reference/comms.md +132 -0
- package/src/docs/versioned/next/reference/environment-variables.md +229 -382
- package/src/docs/versioned/next/reference/test.md +108 -0
- package/src/docs/versioned/next/roadmap.md +9 -0
- package/src/docs/versioned/next/tasks/README.md +42 -0
- package/src/docs/versioned/next/tasks/agentic-sdlc-plugin.md +5 -5
- package/src/docs/versioned/next/tasks/billing-stripe.md +75 -0
- package/src/docs/versioned/next/tasks/comms-abstraction.md +90 -0
- package/src/docs/versioned/next/tasks/comms-adapter-expo-push.md +29 -0
- package/src/docs/versioned/next/tasks/comms-adapter-sendgrid.md +39 -0
- package/src/docs/versioned/next/tasks/comms-adapter-twilio-sms.md +39 -0
- package/src/docs/versioned/next/tasks/comms-adapter-twilio-verify.md +19 -0
- package/src/docs/versioned/next/tasks/comms-admin-dashboard.md +52 -0
- package/src/docs/versioned/next/tasks/deploy-to-gcp.md +11 -11
- package/src/docs/versioned/next/tasks/deployment-foundation.md +12 -12
- package/src/docs/versioned/next/tasks/docs-reference-coverage.md +4 -4
- package/src/docs/versioned/next/tasks/infra-mcp.md +43 -0
- package/src/docs/versioned/next/tasks/migrate-cicd-to-circleci.md +215 -0
- package/src/docs/versioned/next/tasks/native-module-baseline.md +42 -0
- package/src/docs/versioned/next/tasks/orgs-and-teams.md +70 -0
- package/src/docs/versioned/next/tasks/oss-governance-baseline.md +18 -18
- package/src/docs/versioned/next/tasks/password-reset-and-email-verification.md +60 -0
- package/src/docs/versioned/next/tasks/positioning-django-rails-universal.md +4 -4
- package/src/docs/versioned/next/tasks/public-roadmap-github.md +15 -15
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
# Research: Infrastructure MCP server (privileged tools gateway)
|
|
2
|
+
|
|
3
|
+
**Status:** Complete — decisions made, IP written: [infra-mcp.md](infra-mcp.md)
|
|
4
|
+
**Created:** 2026-08-09
|
|
5
|
+
|
|
6
|
+
## Scope statement
|
|
7
|
+
|
|
8
|
+
Evaluate approaches for a new deployable, self-contained MCP server ("infra MCP") that puts privileged infrastructure tooling — GCP, Expo/EAS, Vercel, MongoDB, Sentry, and similar — behind a single MCP endpoint that:
|
|
9
|
+
|
|
10
|
+
- Authenticates each user individually.
|
|
11
|
+
- Enforces per-user permissions (at minimum: read-only vs. write tiers; e.g. "log digging" vs. "change infra").
|
|
12
|
+
- Is deployable as its own service (Cloud Run, following the existing `terreno-mcp` deployment pattern).
|
|
13
|
+
|
|
14
|
+
Also: confirm whether targeting the new MCP spec revision ("MCP 2.0") is the right call.
|
|
15
|
+
|
|
16
|
+
## PRD summary
|
|
17
|
+
|
|
18
|
+
From the request:
|
|
19
|
+
|
|
20
|
+
- **Problem:** Privileged infra tools are currently accessed with broadly-scoped credentials (e.g. env-injected service accounts, personal API keys). There is no per-user gate, no read-only tier, and no central audit point for agent-driven infra access.
|
|
21
|
+
- **Goal:** One MCP server controlling authentication and authorization per user, supporting a read-only mode (log digging, metrics, build status) and a write mode (actual infra changes).
|
|
22
|
+
- **Integrations named:** GCP, Expo/EAS, Vercel, MongoDB, Sentry, "etc." (extensible).
|
|
23
|
+
- **Constraint:** Deployable and self-contained.
|
|
24
|
+
|
|
25
|
+
## Codebase findings
|
|
26
|
+
|
|
27
|
+
### Existing MCP package (`mcp-server/`)
|
|
28
|
+
|
|
29
|
+
- `@terreno/mcp` is a docs/codegen MCP server on `@modelcontextprotocol/sdk` **v1.27** (2025-era spec), served over Streamable HTTP via `createMcpExpressApp` + `StreamableHTTPServerTransport`, wrapped with Sentry (`Sentry.wrapMcpServerWithSentry`).
|
|
30
|
+
- It has **no authentication** — it serves public docs/tooling. It is not a suitable base for privileged tools but proves out the HTTP-MCP + Sentry + Cloud Run shape.
|
|
31
|
+
- A second bin (`terreno-mcp-local`) runs stdio-side local tools (logs, db query/schema, runtime) — relevant prior art for "log digging" tool design.
|
|
32
|
+
|
|
33
|
+
### Deployment prior art (`terraform/`)
|
|
34
|
+
|
|
35
|
+
- `module "mcp_service"` deploys `terreno-mcp` to **Cloud Run** with its own Artifact Registry repo, min/max instances, and a scoped deploy service account. A new infra MCP service can clone this module usage nearly verbatim.
|
|
36
|
+
|
|
37
|
+
### Auth/permissions prior art (`api/`)
|
|
38
|
+
|
|
39
|
+
- `@terreno/api` ships JWT/Passport auth, Better Auth (OAuth 2.x social login), a permission system (`IsAdmin`, `IsAuthenticated`, per-route permission arrays), and the `TerrenoPlugin` pattern.
|
|
40
|
+
- The [RBAC Permissions IP](rbac-permissions.md) (Draft) is designing richer role-based permissions for `@terreno/api` — potential overlap if the infra MCP models roles in Mongo.
|
|
41
|
+
|
|
42
|
+
### Related IPs
|
|
43
|
+
|
|
44
|
+
- [modelRouter MCP tools](model-router-mcp.md) (backlog): generates MCP tools from modelRouter with per-tool auth — same "authenticated MCP tools" problem, app-data-facing rather than infra-facing.
|
|
45
|
+
- [MCP Boost parity](mcp-boost-parity.md) (in progress, PR #802): expands `@terreno/mcp`.
|
|
46
|
+
- [Deploy to GCP](deploy-to-gcp.md), [Deployment foundation](deployment-foundation.md): deployment story this service should align with.
|
|
47
|
+
|
|
48
|
+
### Current credential handling (the problem being solved)
|
|
49
|
+
|
|
50
|
+
- Cloud agent VMs receive raw env-injected service accounts (`GCP_SA_TERRENO`, `GCP_SA_PRD`, `GCP_SA_STG`, `GCP_SA_ZAPLING`) and `SENTRY_CLIENT_SECRET`. Every agent gets the full power of each credential; scoping is done only at the GCP IAM level per SA, with no read-only/write split per user and no central audit trail.
|
|
51
|
+
|
|
52
|
+
## External findings
|
|
53
|
+
|
|
54
|
+
### "MCP 2.0" — the 2026-07-28 spec revision
|
|
55
|
+
|
|
56
|
+
There is no product officially named "MCP 2.0", but the **2026-07-28 MCP specification** is the largest revision since launch and is what the TypeScript **SDK v2** implements. Its changes are unusually well-suited to this project:
|
|
57
|
+
|
|
58
|
+
- **Stateless core** — the `initialize` handshake and `Mcp-Session-Id` are gone; every request is self-contained. Scales on plain HTTP load-balanced infra (ideal for Cloud Run).
|
|
59
|
+
- **Header-based routing** — `Mcp-Method` and `Mcp-Name` HTTP headers are mandatory and mirrored to the body (mismatch → `-32020`). A gateway or middleware can authorize per-tool **from headers alone**, without parsing JSON-RPC bodies. This is effectively purpose-built for the per-user, per-tool permission layer this project needs.
|
|
60
|
+
- **Authorization hardening** — OAuth 2.1 + PKCE, RFC 9728 Protected Resource Metadata (401 + `WWW-Authenticate` discovery), RFC 8707 resource indicators (anti-confused-deputy), RFC 9207 issuer validation, and Client ID Metadata Documents (CIMD) replacing Dynamic Client Registration.
|
|
61
|
+
- **Tasks extension** — long-running work (e.g. an EAS build or terraform apply) has a first-class pattern.
|
|
62
|
+
|
|
63
|
+
SDK situation:
|
|
64
|
+
|
|
65
|
+
- TS SDK v2 = new packages `@modelcontextprotocol/server` (+ `@modelcontextprotocol/client`, `@modelcontextprotocol/node`), released stable 2026-07-28. The v1 `@modelcontextprotocol/sdk` line (which `@terreno/mcp` uses) remains maintained; both can coexist in one repo.
|
|
66
|
+
- `createMcpHandler(factory, {legacy: "stateless"})` serves **both** 2026-era and 2025-era clients from one endpoint, so older MCP clients (Claude Code/Cursor versions that predate the spec) still work.
|
|
67
|
+
- Gotcha: SDK v2 requires a Standard Schema validator with a `jsonSchema` property — **zod 4** (repo catalog currently needs checking; may need `zod@^4` under an alias if anything pins zod 3).
|
|
68
|
+
|
|
69
|
+
**Conclusion: yes — build new on the 2026-07-28 spec via SDK v2 with the legacy stateless fallback.** Starting a new privileged server on the 2025-era SDK would mean migrating within months and would forfeit the header-based authz and stateless scaling that this project specifically benefits from.
|
|
70
|
+
|
|
71
|
+
### Vendor MCP server landscape (all targets have official servers)
|
|
72
|
+
|
|
73
|
+
| Vendor | Official MCP server | Auth model | Notes |
|
|
74
|
+
|---|---|---|---|
|
|
75
|
+
| GCP | Managed remote endpoints (e.g. `https://cloudcli.googleapis.com/mcp` for gcloud, plus BigQuery/Storage/etc.); open-source `gcloud-mcp`, `observability-mcp`, `storage-mcp` (npm, runnable self-hosted) | OAuth 2.0 + IAM | Remote server blocks `gcloud auth`, `gcloud iam service-accounts`, etc. Observability MCP covers logs/metrics/traces/errors — the "log digging" use case |
|
|
76
|
+
| Sentry | Hosted `https://mcp.sentry.dev/mcp` and self-hostable | OAuth | Issues, events, traces, Seer |
|
|
77
|
+
| MongoDB | `mongodb-mcp-server` (official, npm) | Connection string / Atlas API creds | Has a `--readOnly` flag — native read-only mode |
|
|
78
|
+
| Vercel | Hosted `https://mcp.vercel.com` | OAuth | Deployments, logs, projects |
|
|
79
|
+
| Expo/EAS | Hosted `https://mcp.expo.dev/mcp` (Streamable HTTP) | OAuth (Expo account); paid EAS plan | Docs + project tools; EAS build/update tools evolving |
|
|
80
|
+
|
|
81
|
+
### Proxying feasibility per vendor (follow-up research)
|
|
82
|
+
|
|
83
|
+
Mechanics of chaining our MCP to upstream MCP servers are simple with SDK v2 (`@modelcontextprotocol/client` speaks both protocol eras; stateless spec = no per-upstream session to maintain; stdio servers run as child processes). Long-term authentication is the real differentiator:
|
|
84
|
+
|
|
85
|
+
| Vendor | Headless credential for MCP? | Long-term auth | Verdict for proxying |
|
|
86
|
+
|---|---|---|---|
|
|
87
|
+
| Sentry | Yes — hosted `mcp.sentry.dev` accepts `Authorization: Sentry-Bearer <token>` header; stdio `@sentry/mcp-server` takes `SENTRY_ACCESS_TOKEN` (non-expiring user auth token / internal integration token) | Static token, rotate manually | **Green** |
|
|
88
|
+
| MongoDB | Yes — `mongodb-mcp-server` takes a connection string; native `--readOnly` flag | Static | **Green** |
|
|
89
|
+
| GCP | Yes — remote endpoints and self-hosted `gcloud-mcp`/`observability-mcp` accept ADC/service-account identity; on Cloud Run the *service's own SA* is the credential, google-auth auto-refreshes | Automatic (workload identity) | **Green** |
|
|
90
|
+
| Vercel | **No** — `mcp.vercel.com` is OAuth-only **with a client allowlist** (Claude, Cursor, Codex, etc.); our server would not be an approved client, and there is no API-token fallback | n/a | **Red** — go native (Vercel REST API + static token) |
|
|
91
|
+
| Expo/EAS | Hosted `mcp.expo.dev` is browser OAuth (user-scoped, paid plan); robot tokens (`EXPO_TOKEN`) are for CI/CLI, MCP acceptance unconfirmed | Uncertain | **Amber/Red** — go native (`EXPO_TOKEN` + EAS CLI/GraphQL) |
|
|
92
|
+
|
|
93
|
+
Implication: a **pure** gateway (Option A) is not achievable for Vercel and likely not Expo — their hosted MCPs are designed for interactive human OAuth from allowlisted AI clients, not server-to-server delegation. The proxied path works cleanly exactly where static/service credentials exist, which is the same place the native path is also easy. This strengthens Option C (hybrid) and weakens A.
|
|
94
|
+
|
|
95
|
+
If we ever must proxy an interactive-OAuth-only upstream: one-time browser consent by an admin, persist the refresh token in an encrypted vault (Mongo + KMS or Secret Manager), background refresh, and an admin "re-link integration" flow for `invalid_grant`/revocation. Real ops burden — avoid unless the vendor MCP offers something the REST API doesn't.
|
|
96
|
+
|
|
97
|
+
### Gateway prior art
|
|
98
|
+
|
|
99
|
+
Multiple open-source projects already implement "aggregate MCP backends + per-user tool RBAC":
|
|
100
|
+
|
|
101
|
+
- `mcp-zero-trust-proxy` — OAuth 2.1 front door (any OIDC provider), roles with `allowed_tools`/`deny_tools`, claim-based role mapping, audit logging, rate limiting. Config-file driven.
|
|
102
|
+
- `joshrotenberg/mcp-proxy` (Rust) — multi-backend aggregation, capability filtering per backend, JWT/JWKS RBAC, token passthrough, OTel.
|
|
103
|
+
- Microsoft `mcp-gateway` — Kubernetes-native, session-aware routing, Entra ID.
|
|
104
|
+
- Common pattern: filter `tools/list` by role, deny `tools/call` for unauthorized tools, route by prefix to upstream servers.
|
|
105
|
+
|
|
106
|
+
These validate the architecture but none is a drop-in: they are config-file-driven (not Mongo-backed per-user), most predate the 2026-07-28 stateless spec, and none handles the "server holds privileged vendor credentials per role tier" model out of the box.
|
|
107
|
+
|
|
108
|
+
## Candidate options
|
|
109
|
+
|
|
110
|
+
Two orthogonal decisions: **(1) where tools come from** and **(2) whose credentials execute them**. Options A–C cover decision 1; the credential question is listed separately because it applies to all three.
|
|
111
|
+
|
|
112
|
+
### Option A: Pure gateway/aggregator over vendor MCP servers
|
|
113
|
+
|
|
114
|
+
New service speaks MCP to clients, authenticates users itself (OAuth 2.1 per the new spec), and proxies tool calls to upstream vendor MCP servers (GCP remote MCP, Sentry, Vercel, Expo hosted; MongoDB MCP as an in-process/sidecar child). Per-user roles filter which upstream tools appear in `tools/list` and which pass `tools/call`.
|
|
115
|
+
|
|
116
|
+
- **Pros:** Least tool-maintenance — vendors maintain their own tools; broad coverage immediately; new-spec `Mcp-Name` headers make the filter layer thin; read-only tiers are mostly "allow-list of known-read tools" plus MongoDB's native `--readOnly`.
|
|
117
|
+
- **Cons:** Upstream hosted servers (Expo, Vercel, Sentry, GCP remote) authenticate via *their own* OAuth — the gateway must hold/refresh upstream tokens (per user, or one service identity), which is real complexity (token vault); read/write classification of third-party tools is inference, not contract — a mislabeled vendor tool could let a "read-only" user mutate infra; tool churn upstream can silently change the permission surface; latency of double-hop.
|
|
118
|
+
|
|
119
|
+
### Option B: Self-contained curated tool server (no upstreams)
|
|
120
|
+
|
|
121
|
+
Hand-write a curated set of tools that call vendor APIs/SDKs directly (Cloud Logging API, Sentry REST API, Vercel API, EAS GraphQL/CLI, MongoDB driver). The server holds vendor credentials (e.g. two GCP SAs: viewer + editor; a read-only Mongo user + a write user). Every tool is explicitly classified read or write at authorship time.
|
|
122
|
+
|
|
123
|
+
- **Pros:** Read-only is a *hard guarantee* — enforced twice (tool tier + underlying credential scope, e.g. viewer-only SA for the read tier); smallest attack surface; no upstream token management; full control over tool descriptions/outputs (can redact); simplest to audit.
|
|
124
|
+
- **Cons:** Highest build-and-maintain cost — every vendor surface is our code; coverage starts narrow (whatever tools we write); vendors ship new capabilities that we lag behind.
|
|
125
|
+
|
|
126
|
+
### Option C: Hybrid — curated core + optional proxied upstreams (adapter model)
|
|
127
|
+
|
|
128
|
+
Same authenticated core as B, with an integration-adapter interface. Each integration is either **native** (hand-written tools calling vendor APIs, like B) or **proxied** (an MCP client to an upstream server with an explicit per-tool allow-list and read/write annotation required in config, like A). Start native for the high-risk/high-value surfaces (GCP logs + deploys, Mongo, Sentry), add proxied adapters where a vendor MCP is high-quality and low-risk.
|
|
129
|
+
|
|
130
|
+
- **Pros:** Ships useful read-only value fast on the native path; keeps the hard read-only guarantee where it matters; leaves a sanctioned escape hatch to vendor servers without weakening the permission model (proxied tools are deny-by-default until annotated); the 2026-07-28 header routing makes both paths share one authz middleware.
|
|
131
|
+
- **Cons:** Two code paths to test; the proxied path inherits A's token-management complexity *when used*; discipline required so the allow-list doesn't rot into "allow everything".
|
|
132
|
+
|
|
133
|
+
### Cross-cutting: credential model (applies to A/B/C)
|
|
134
|
+
|
|
135
|
+
1. **Server-held, role-tiered service credentials** — users authenticate to the infra MCP; tool calls execute with shared per-tier credentials (e.g. `infra-mcp-readonly@` SA vs `infra-mcp-editor@` SA). Simple, strong read-only floor, but vendor-side audit logs show the service identity, not the human (the MCP server's own audit log must carry user attribution).
|
|
136
|
+
2. **Per-user upstream identity (on-behalf-of)** — each user links their own GCP/Sentry/Vercel/Expo account via OAuth; the server stores/refreshes their tokens and acts as them. Vendor audit logs show the real human; but requires a token vault, per-vendor OAuth apps, and users must all have vendor accounts with correct IAM — significantly more work.
|
|
137
|
+
3. **Hybrid** — server-held credentials for read tier (low risk, frictionless), per-user identity required for write tier.
|
|
138
|
+
|
|
139
|
+
### Cross-cutting: where the code lives
|
|
140
|
+
|
|
141
|
+
- **New workspace package in terreno** (e.g. `infra-mcp/`), reusing `@terreno/api` (Mongo user model, logger, Sentry patterns) and cloning the `mcp_service` terraform module — fits monorepo conventions, and `@terreno/api`'s auth/permission machinery is available.
|
|
142
|
+
- **Standalone generic product package** (e.g. `@terreno/infra-mcp` designed for any org to deploy, config-driven integrations) — bigger ambition, aligns with the OSS launch program, more design constraints up front.
|
|
143
|
+
|
|
144
|
+
## Audience fit (one-man shop → startup → growing startup)
|
|
145
|
+
|
|
146
|
+
The target audience for the packaged product is solo founders through growing startups (not identity-team enterprises). That constraint selects the architecture:
|
|
147
|
+
|
|
148
|
+
- **Credential model 1 (server-held, role-tiered)** is the only model that works at the low end: paste tokens once, nothing expires (static tokens + workload identity), no per-vendor OAuth app registration. Vendor-side audit logs show the service identity, so the product's own Mongo audit log (with user attribution) is the compliance answer at this scale.
|
|
149
|
+
- **Better Auth logins (GitHub/Google/email)** instead of requiring an external IdP — the audience has GitHub accounts, not Okta. Domain-restricted Google login covers the "growing startup" tier.
|
|
150
|
+
- **CTO security story is structural, not procedural:** read tier executes with physically read-only credentials (viewer SA, read-only Mongo user) so misclassified tools cannot mutate; proxied tools deny-by-default until annotated; per-integration grants; full audit log. `admin-backend`/`admin-frontend` provide the user/role admin UI.
|
|
151
|
+
- **Enterprise upgrade path preserved:** credential resolution takes a user context, so a future "write tier requires linking your own vendor identity" mode (model 3) can be added without rearchitecting.
|
|
152
|
+
- **Packaging:** reusable `@terreno/infra-mcp` package with config-driven integration registry, dogfooded via our own Cloud Run deployment — aligns with the OSS launch program.
|
|
153
|
+
|
|
154
|
+
This resolves blocking questions 1 (Option C), 2 (model 1 with model-3 upgrade path), 3 (Mongo users via Better Auth), 4 (readonly/write tiers + per-integration grants), and 8 (reusable package).
|
|
155
|
+
|
|
156
|
+
## Recommendation (pending answers)
|
|
157
|
+
|
|
158
|
+
Option **C** (hybrid, starting native-first per B) with credential model **1** (server-held role-tiered credentials) as the initial phase, built as a new terreno workspace package on the 2026-07-28 spec via TS SDK v2, deployed to Cloud Run alongside `terreno-mcp`. But this is presented as a recommendation, not a decision — see blocking questions.
|
|
159
|
+
|
|
160
|
+
## Blocking questions
|
|
161
|
+
|
|
162
|
+
1. **Tool sourcing:** Option A (proxy vendor MCPs), B (all curated/native), or C (hybrid, native-first)?
|
|
163
|
+
2. **Credential model:** server-held tiered credentials, per-user vendor identity, or hybrid (read=shared, write=per-user)?
|
|
164
|
+
3. **User store & login:** Mongo-backed users in this service (reusing `@terreno/api` auth / Better Auth as the OAuth 2.1 authorization server), or an external IdP (Google Workspace / GitHub org) with the MCP server as a pure resource server? Who administers users/roles, and via what surface (admin UI? config? CLI)?
|
|
165
|
+
4. **Permission granularity for v1:** just two tiers (readonly/write) per user, or per-integration grants (e.g. "Sentry read + Mongo read, no GCP")?
|
|
166
|
+
5. **Write-action safety:** should write tools require an extra confirmation step (MCP elicitation), a second approval, or is role membership sufficient?
|
|
167
|
+
6. **Integration priority for phase 1:** which 2–3 integrations first? (Research suggests GCP logs/observability + Sentry + Mongo read-only covers the "log digging" case.)
|
|
168
|
+
7. **Audit requirements:** is an in-Mongo audit log of every tool call (user, tool, args, result status) a v1 requirement?
|
|
169
|
+
8. **Packaging ambition:** terreno-internal service first, or design from day one as a reusable OSS-able `@terreno/*` package (affects config surface and scope)?
|
|
170
|
+
9. **Consumers:** which MCP clients must work day one (Cursor cloud agents, Claude Code, humans' IDEs)? Cloud agents matter because they currently get raw SA env vars — is replacing those env-injected SAs an explicit goal?
|
|
171
|
+
|
|
172
|
+
## Non-blocking questions
|
|
173
|
+
|
|
174
|
+
- Rate limiting per user/tier in v1 or deferred?
|
|
175
|
+
- Should the existing `@terreno/mcp` docs server eventually merge into or share infra with this service, or stay separate? (Default: separate.)
|
|
176
|
+
- Naming: `infra-mcp`, `@terreno/infra-mcp`, `terreno-ops-mcp`?
|
|
177
|
+
|
|
178
|
+
## References
|
|
179
|
+
|
|
180
|
+
- MCP 2026-07-28 spec: https://modelcontextprotocol.io/specification/2026-07-28
|
|
181
|
+
- Spec release notes: https://blog.modelcontextprotocol.io/posts/2026-07-28/
|
|
182
|
+
- TS SDK v2 (`@modelcontextprotocol/server`): https://ts.sdk.modelcontextprotocol.io/v2/
|
|
183
|
+
- SDK v2 legacy support guide: https://ts.sdk.modelcontextprotocol.io/v2/migration/support-2026-07-28
|
|
184
|
+
- MCP authorization tutorial (OAuth 2.1): https://modelcontextprotocol.io/docs/2026-07-28/tutorials/security/authorization
|
|
185
|
+
- GCP MCP servers: https://github.com/googleapis/gcloud-mcp, https://docs.cloud.google.com/sdk/use-gcloud-mcp, https://github.com/Google/mcp
|
|
186
|
+
- Expo MCP: https://docs.expo.dev/eas/ai/mcp/
|
|
187
|
+
- Gateway prior art: https://github.com/keith-aykira/mcp-zero-trust-proxy, https://github.com/joshrotenberg/mcp-proxy, https://microsoft.github.io/mcp-gateway/
|
|
188
|
+
- In-repo: `mcp-server/src/index.ts`, `terraform/main.tf` (`mcp_service`), `docs/implementationPlans/model-router-mcp.md`, `docs/implementationPlans/rbac-permissions.md`
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
# Implementation Plan: Infrastructure MCP server (`@terreno/infra-mcp`)
|
|
2
|
+
|
|
3
|
+
**Status:** Draft — blocked on the RBAC module ([rbac-permissions.md](rbac-permissions.md)) landing in `@terreno/api`
|
|
4
|
+
**Branch:** TBD
|
|
5
|
+
**Owner:** TBD
|
|
6
|
+
**Created:** 2026-08-09
|
|
7
|
+
**Research:** [infra-mcp-research.md](infra-mcp-research.md)
|
|
8
|
+
|
|
9
|
+
## Goal
|
|
10
|
+
|
|
11
|
+
A deployable, self-contained MCP server that puts privileged infrastructure tooling — GCP, Sentry, MongoDB, and later Expo/EAS and Vercel — behind a single MCP endpoint with per-user authentication, RBAC-driven read/write permission tiers, per-call write confirmation, and a full audit trail. Packaged as a reusable `@terreno/infra-mcp` workspace package targeting one-man shops through growing startups, dogfooded by our own Cloud Run deployment. Cloud agents stop receiving raw `GCP_SA_*` service-account env vars and instead call this server with scoped service tokens.
|
|
12
|
+
|
|
13
|
+
## Non-Goals
|
|
14
|
+
|
|
15
|
+
- Per-user vendor identity (on-behalf-of OAuth to GCP/Sentry/etc.) — the credential interface accepts a user context so this can be added later, but v1 executes with server-held credentials.
|
|
16
|
+
- Proxying vendor MCP servers that require interactive browser OAuth (Vercel `mcp.vercel.com`, Expo `mcp.expo.dev`) — research showed these are allowlisted/interactive-only; those vendors get native tools instead.
|
|
17
|
+
- A bespoke permission engine — permissioning is the `@terreno/api` RBAC module (`createAccess`), not custom code.
|
|
18
|
+
- Rate limiting, cost tracking, tool versioning (future work).
|
|
19
|
+
- Changes to the existing `@terreno/mcp` docs server (stays on SDK v1, unchanged).
|
|
20
|
+
|
|
21
|
+
## Decisions
|
|
22
|
+
|
|
23
|
+
| Question | Decision |
|
|
24
|
+
|----------|----------|
|
|
25
|
+
| Approach | **Option C (hybrid):** native curated tools first; proxied vendor-MCP adapters behind an explicit per-tool allow-list with mandatory read/write annotation |
|
|
26
|
+
| Protocol | **MCP 2026-07-28 spec** via TS SDK v2 (`@modelcontextprotocol/server`), `createMcpHandler` with `legacy: "stateless"` so 2025-era clients keep working |
|
|
27
|
+
| Permissioning | **`@terreno/api` RBAC module** — this IP is blocked behind that PR. Each integration is an RBAC resource with `read`/`write` actions; meta-permissions live on an `infra` resource |
|
|
28
|
+
| Write safety | **Write tools require per-call confirmation (MCP elicitation).** A config option can disable confirmation per integration or globally, but it only takes effect for users holding `infra:writeUnconfirmed` — everyone else is still prompted |
|
|
29
|
+
| Phase 1 scope | **Read-only:** GCP observability (logs/metrics/errors), Sentry, MongoDB |
|
|
30
|
+
| GCP credentials | **Cloud Run default runtime service account only (ADC).** No injected SA key env vars anywhere; the `GCP_SA_*` pattern for cloud agents is retired. IAM on the runtime SA is the outer bound; the MCP's RBAC is the per-user gate |
|
|
31
|
+
| Other credentials | Static tokens (Sentry auth token, Mongo connection strings, later `EXPO_TOKEN`/Vercel token) from Secret Manager/env |
|
|
32
|
+
| User store & login | Mongo users via `@terreno/api` (`baseUserPlugin` + `rbacUserPlugin`); MCP clients authenticate via OAuth 2.1 using **`@better-auth/oauth-provider` + `@better-auth/mcp`** (already aligned with the 2026-07-28 authorization spec: CIMD, RFC 9728, `requireMcpAuth`) |
|
|
33
|
+
| Non-human callers | Service tokens (Better Auth `apiKey` plugin if compatible, else a `ServiceToken` model) mapped to machine users with RBAC roles — this is how cloud agents call the server |
|
|
34
|
+
| Packaging | New workspace package `infra-mcp/` publishing `@terreno/infra-mcp`; config-driven integration registry; deployed via a clone of the existing `mcp_service` terraform module |
|
|
35
|
+
|
|
36
|
+
## Architecture
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
MCP client (Cursor, Claude Code, cloud agent)
|
|
40
|
+
| OAuth 2.1 bearer token (human) or service token (machine)
|
|
41
|
+
v
|
|
42
|
+
POST /mcp — SDK v2 createMcpHandler (2026-07-28 + legacy stateless)
|
|
43
|
+
|
|
|
44
|
+
1. Auth: Better Auth verifies token -> User (+ roles via rbacUserPlugin)
|
|
45
|
+
2. Authz: access.can({[integration]: [tier]}) using Mcp-Name header ->
|
|
46
|
+
integration/tool lookup; tools/list is filtered to the caller's grants
|
|
47
|
+
3. Write tools: elicitation confirmation (unless override + infra:writeUnconfirmed)
|
|
48
|
+
4. Dispatch: IntegrationAdapter (native handler | proxied MCP client)
|
|
49
|
+
5. Audit: InfraAuditLog record for every tools/call
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Integration adapter interface
|
|
53
|
+
|
|
54
|
+
```typescript
|
|
55
|
+
// infra-mcp/src/integrations/types.ts
|
|
56
|
+
export interface InfraToolContext {
|
|
57
|
+
user: UserDocument; // resolved caller (human or machine)
|
|
58
|
+
access: TerrenoAccess<InfraStatements>;
|
|
59
|
+
confirmWrite: (summary: string) => Promise<boolean>; // elicitation wrapper
|
|
60
|
+
logger: Logger;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface InfraTool {
|
|
64
|
+
name: string; // exposed as `${integration}_${name}`
|
|
65
|
+
tier: "read" | "write"; // maps to the RBAC action checked
|
|
66
|
+
description: string;
|
|
67
|
+
inputSchema: ZodType; // zod 4 (SDK v2 Standard Schema requirement)
|
|
68
|
+
handler: (args: unknown, ctx: InfraToolContext) => Promise<ToolResult>;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export type IntegrationAdapter =
|
|
72
|
+
| {kind: "native"; name: string; tools: InfraTool[]}
|
|
73
|
+
| {
|
|
74
|
+
kind: "proxied";
|
|
75
|
+
name: string;
|
|
76
|
+
transport: {type: "stdio"; command: string[]} | {type: "http"; url: string; headers?: Record<string, string>};
|
|
77
|
+
// Deny-by-default: only listed upstream tools are exposed, and each
|
|
78
|
+
// MUST carry an explicit tier annotation.
|
|
79
|
+
allowedTools: Record<string, {tier: "read" | "write"; rename?: string}>;
|
|
80
|
+
};
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Proxied adapters use `@modelcontextprotocol/client` (speaks both protocol eras; stateless — no per-upstream session). Stdio upstreams run as child processes inside the container.
|
|
84
|
+
|
|
85
|
+
### RBAC statements
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
// infra-mcp/src/access.ts — merged with terrenoStatements via createAccess
|
|
89
|
+
export const infraStatements = {
|
|
90
|
+
...terrenoStatements,
|
|
91
|
+
gcp: ["read", "write"],
|
|
92
|
+
sentry: ["read", "write"],
|
|
93
|
+
mongo: ["read", "write"],
|
|
94
|
+
expo: ["read", "write"], // phase 3
|
|
95
|
+
vercel: ["read", "write"], // phase 3
|
|
96
|
+
infra: ["viewAudit", "manageIntegrations", "writeUnconfirmed"],
|
|
97
|
+
} as const;
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
- `read`/`write` are in the RBAC module's `READ_ACTIONS` semantics, so the shipped `auditor` role (read-only everywhere) works unmodified — that is the "log digging" role for free.
|
|
101
|
+
- Default roles seeded on top of the RBAC defaults: `infra-readonly` (every integration `read`), `infra-operator` (`read` + `write`, still confirmed per call), `superadmin` from RBAC covers everything including `infra:writeUnconfirmed`.
|
|
102
|
+
- Per-integration grants ("Sentry read only, no GCP") are just roles with narrower permission sets — no extra machinery.
|
|
103
|
+
|
|
104
|
+
### Write confirmation flow
|
|
105
|
+
|
|
106
|
+
1. Tool handler for a `write`-tier tool calls `ctx.confirmWrite(summary)` before mutating.
|
|
107
|
+
2. `confirmWrite` sends an MCP **elicitation** request to the client ("Confirm: roll back Cloud Run service X to revision Y? yes/no") and resolves with the user's answer. Decline → tool returns a cancelled result; audit records `rejected`.
|
|
108
|
+
3. Override: `config.integrations[name].confirmWrites: false` (or global `confirmWrites`) skips the elicitation **only when** `access.can({infra: ["writeUnconfirmed"]})` passes for the caller. Without the permission, the config flag is ignored and the prompt still fires.
|
|
109
|
+
4. Clients that do not support elicitation (some non-interactive agents): write tools **fail closed** with an instructive error unless the caller holds `infra:writeUnconfirmed` and the override is on.
|
|
110
|
+
|
|
111
|
+
### Configuration
|
|
112
|
+
|
|
113
|
+
Static config (code/env, validated at boot with zod):
|
|
114
|
+
|
|
115
|
+
```typescript
|
|
116
|
+
// consuming app / our deployment: infra.config.ts
|
|
117
|
+
export default defineInfraMcp({
|
|
118
|
+
integrations: {
|
|
119
|
+
gcp: {enabled: true, projectId: env("GCP_PROJECT")}, // ADC — no key
|
|
120
|
+
sentry: {enabled: true, authToken: secret("SENTRY_MCP_TOKEN"), org: "flourish"},
|
|
121
|
+
mongo: {enabled: true, readUri: secret("MONGO_READ_URI"), writeUri: secret("MONGO_WRITE_URI")},
|
|
122
|
+
},
|
|
123
|
+
confirmWrites: true, // global default; per-integration override allowed
|
|
124
|
+
});
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## Models
|
|
128
|
+
|
|
129
|
+
All models live in `infra-mcp/src/types/models/` per convention; every field carries a `description`.
|
|
130
|
+
|
|
131
|
+
### User
|
|
132
|
+
|
|
133
|
+
`@terreno/api` user schema with `baseUserPlugin` + `rbacUserPlugin` (adds `roles: string[]`). Machine users (cloud agents) are ordinary users flagged `isServiceAccount: true`.
|
|
134
|
+
|
|
135
|
+
### InfraAuditLog
|
|
136
|
+
|
|
137
|
+
| Field | Type | Notes |
|
|
138
|
+
|---|---|---|
|
|
139
|
+
| `userId` | ObjectId ref User | caller (human or machine) |
|
|
140
|
+
| `tool` | string | full exposed tool name |
|
|
141
|
+
| `integration` | string | adapter name |
|
|
142
|
+
| `tier` | "read" \| "write" | |
|
|
143
|
+
| `args` | Mixed | redacted via per-tool redaction list before persist |
|
|
144
|
+
| `status` | enum | `allowed`, `denied`, `confirmed`, `rejected`, `error` |
|
|
145
|
+
| `error` | string? | |
|
|
146
|
+
| `durationMs` | number | |
|
|
147
|
+
| `created` | Date | plugin-managed |
|
|
148
|
+
|
|
149
|
+
Indexed on `{userId, created}` and `{integration, created}`. Read surface gated by `infra:viewAudit`.
|
|
150
|
+
|
|
151
|
+
### ServiceToken (only if Better Auth `apiKey` plugin is unsuitable)
|
|
152
|
+
|
|
153
|
+
`name`, `hashedToken`, `userId`, `expiresAt?`, `lastUsedAt`, `created`. Verified in the same auth middleware path as bearer tokens.
|
|
154
|
+
|
|
155
|
+
## APIs
|
|
156
|
+
|
|
157
|
+
| Method | Path | Auth | Purpose |
|
|
158
|
+
|---|---|---|---|
|
|
159
|
+
| POST | `/mcp` | OAuth bearer / service token | MCP endpoint (2026-07-28 + legacy stateless) |
|
|
160
|
+
| GET | `/.well-known/oauth-protected-resource` | — | RFC 9728 metadata (via `@better-auth/mcp`) |
|
|
161
|
+
| * | `/oauth2/*`, `/api/auth/*` | — | Better Auth authorization server + login pages |
|
|
162
|
+
| GET | `/health` | — | health check |
|
|
163
|
+
| * | `/rbac/*` | RBAC perms | role CRUD/assignment (RBAC module's `rbacRouter`) |
|
|
164
|
+
| * | `/admin/*` | `admin:access` | admin panel (`@terreno/admin-backend`): Users, InfraAuditLog (read-only), ServiceTokens |
|
|
165
|
+
|
|
166
|
+
### Phase 1 tools (read tier)
|
|
167
|
+
|
|
168
|
+
| Integration | Tools |
|
|
169
|
+
|---|---|
|
|
170
|
+
| gcp | `gcp_query_logs`, `gcp_list_log_names`, `gcp_query_metrics`, `gcp_list_error_groups`, `gcp_list_cloud_run_services`, `gcp_get_cloud_run_service` |
|
|
171
|
+
| sentry | `sentry_search_issues`, `sentry_get_issue`, `sentry_list_issue_events`, `sentry_get_event` |
|
|
172
|
+
| mongo | `mongo_list_collections`, `mongo_collection_schema`, `mongo_find`, `mongo_aggregate` (read-only stages; `$out`/`$merge` rejected; executed on the read-only connection) |
|
|
173
|
+
|
|
174
|
+
GCP tools call the Cloud Logging/Monitoring/Error Reporting/Cloud Run Admin APIs via ADC. Sentry tools call the Sentry REST API. Mongo tools reuse the read-query/schema logic behind `mcp-server/src/local/tools/databaseQuery.ts`/`databaseSchema.ts` — extracted into a shared module both packages import rather than forked into infra-mcp — run against the configured read URI.
|
|
175
|
+
|
|
176
|
+
### Phase 2 tools (write tier, all elicitation-confirmed)
|
|
177
|
+
|
|
178
|
+
Initial minimal set: `sentry_resolve_issue`, `sentry_assign_issue`, `gcp_rollback_cloud_run_revision`, `gcp_update_cloud_run_traffic`, `mongo_update_one`, `mongo_delete_one` (write URI). Expansion is deliberate and per-PR.
|
|
179
|
+
|
|
180
|
+
## Notifications
|
|
181
|
+
|
|
182
|
+
None in v1. Future: optional Slack notifier (via `@terreno/api` notifiers) on write-tier tool execution.
|
|
183
|
+
|
|
184
|
+
## UI
|
|
185
|
+
|
|
186
|
+
No new frontend package. Admin surfaces reuse `@terreno/admin-backend` + `@terreno/admin-frontend`/`admin-spa`:
|
|
187
|
+
|
|
188
|
+
- Users + role assignment (RBAC module's admin screens per its IP).
|
|
189
|
+
- InfraAuditLog read-only table (`listFields`: created, user, tool, tier, status).
|
|
190
|
+
- ServiceToken management (create shows the token once).
|
|
191
|
+
|
|
192
|
+
## Phases
|
|
193
|
+
|
|
194
|
+
### Phase 1 — Read-only core (the "log digging" MVP)
|
|
195
|
+
|
|
196
|
+
- Package scaffold `infra-mcp/` (workspace, biome, tsconfig, bun test, catalog deps; zod 4 catalog entry).
|
|
197
|
+
- SDK v2 `/mcp` endpoint (`createMcpHandler`, `legacy: "stateless"`, `toNodeHandler`) mounted on a TerrenoApp/Express server.
|
|
198
|
+
- Better Auth OAuth 2.1 (`@better-auth/oauth-provider` + `@better-auth/mcp`): discovery, login, token verify middleware.
|
|
199
|
+
- RBAC wiring: `infraStatements`, default roles, `access.can()` authz middleware keyed on `Mcp-Name`, filtered `tools/list`. **Gated on the RBAC PR.**
|
|
200
|
+
- Native adapters: gcp (ADC), sentry, mongo — read tools above.
|
|
201
|
+
- `InfraAuditLog` + audit middleware.
|
|
202
|
+
- Terraform: clone `mcp_service` module (new service name, Artifact Registry, runtime SA with viewer/log IAM roles only for now).
|
|
203
|
+
- Tests: authz matrix (role × tool), tool handlers against mocks/emulators, audit records.
|
|
204
|
+
- **Deliverable:** a teammate signs in from Cursor/Claude Code, gets exactly the tools their role allows, digs logs across GCP/Sentry/Mongo.
|
|
205
|
+
|
|
206
|
+
### Phase 2 — Write tier + machine callers
|
|
207
|
+
|
|
208
|
+
- Elicitation `confirmWrite` wrapper; config override gated by `infra:writeUnconfirmed`; fail-closed for non-elicitation clients.
|
|
209
|
+
- Initial write tools (list above); runtime SA IAM widened only for the specific write APIs.
|
|
210
|
+
- Service tokens (Better Auth `apiKey` spike → fallback `ServiceToken` model); machine users with `infra-readonly` role.
|
|
211
|
+
- Cloud agent migration: agents get `INFRA_MCP_URL` + service token instead of `GCP_SA_*`; update AGENTS.md/environment docs; retire the env-injected SA keys.
|
|
212
|
+
- **Deliverable:** write actions work with per-call confirmation; cloud agents dig logs through the MCP with no raw GCP credentials.
|
|
213
|
+
|
|
214
|
+
### Phase 3 — Proxied adapters + remaining vendors
|
|
215
|
+
|
|
216
|
+
- `ProxiedAdapter` (MCP client, stdio + HTTP transports, allow-list with mandatory tier annotations, name prefixing).
|
|
217
|
+
- First proxied integration: `@sentry/mcp-server` (stdio, static token) or `observability-mcp` — pick one as the reference implementation and keep the native tools as fallback.
|
|
218
|
+
- Native Expo/EAS tools (`EXPO_TOKEN`: builds, updates, submissions status) and Vercel tools (API token: deployments, logs).
|
|
219
|
+
- **Deliverable:** adding a vendor MCP is config + annotations, not code.
|
|
220
|
+
|
|
221
|
+
### Phase 4 — OSS packaging
|
|
222
|
+
|
|
223
|
+
- `defineInfraMcp` config reference docs, quickstart (one-man-shop path: tokens + deploy anywhere a container runs), website docs page.
|
|
224
|
+
- Publish `@terreno/infra-mcp`; bootstrap/MCP-server docs updates; changelog + upgrade notes.
|
|
225
|
+
- **Deliverable:** an external startup can deploy their own instance from docs alone.
|
|
226
|
+
|
|
227
|
+
## Feature Flags & Migrations
|
|
228
|
+
|
|
229
|
+
- **No data migrations** — all collections are new.
|
|
230
|
+
- Rollout is additive: new package, new Cloud Run service. Nothing existing changes until Phase 2's cloud-agent credential switch, which is coordinated: keep `GCP_SA_*` injection until the MCP path is verified, then remove.
|
|
231
|
+
- Blocked dependency: the RBAC module PR must merge first (Phase 1 RBAC wiring consumes `createAccess`, `rbacUserPlugin`, `rbacRouter`, default-role seeding).
|
|
232
|
+
|
|
233
|
+
## Activity Log & User Updates
|
|
234
|
+
|
|
235
|
+
`InfraAuditLog` records every `tools/call` (allowed, denied, confirmed, rejected, error) with user attribution — this is the compliance answer for server-held credentials, since vendor-side logs only show the service identity. RBAC role changes are covered by the RBAC module's `RbacAudit`.
|
|
236
|
+
|
|
237
|
+
## Not Included / Future Work
|
|
238
|
+
|
|
239
|
+
- Per-user vendor identity for the write tier (credential interface takes user context; add later).
|
|
240
|
+
- Rate limiting / quotas per user or tier.
|
|
241
|
+
- Slack notifications on write actions.
|
|
242
|
+
- Proxying interactive-OAuth-only vendor MCPs (Vercel/Expo hosted) — revisit if they ship headless credentials.
|
|
243
|
+
- Terraform-apply / IaC tools (high blast radius; needs its own design).
|
|
244
|
+
- MCP Tasks extension for long-running operations (EAS builds) — Phase 3+ candidate.
|
|
245
|
+
|
|
246
|
+
## Files to Create / Modify
|
|
247
|
+
|
|
248
|
+
- `infra-mcp/` — new package: `src/index.ts` (server entry), `src/server.ts`, `src/access.ts`, `src/config.ts`, `src/auth.ts`, `src/audit.ts` + `src/models/infraAuditLog.ts`, `src/integrations/{types,registry,gcp/*,sentry/*,mongo/*,proxied}.ts`, `src/types/models/*`, tests.
|
|
249
|
+
- `package.json` (root) — workspace entry, catalog: zod 4, `@modelcontextprotocol/server`, `@better-auth/oauth-provider`, `@better-auth/mcp`, Google API clients.
|
|
250
|
+
- `terraform/main.tf`, `variables.tf`, `outputs.tf` — `infra_mcp_service` module instance + runtime SA IAM.
|
|
251
|
+
- `.cursor/rules/infra-mcp/00-infra-mcp.mdc`, `AGENTS.md`/`CLAUDE.md` package lists, docs site page.
|
|
252
|
+
- `docs/implementationPlans/PLAN_INDEX.md` — register this IP.
|
|
253
|
+
- `ROADMAP.md` + `docs/explanation/roadmap-seed-issues.md` — roadmap entry and ready-to-paste tracking issue (done); `scripts/issueAreaLabels.ts` + bug-report package dropdown when the package lands.
|
|
254
|
+
|
|
255
|
+
## Task List
|
|
256
|
+
|
|
257
|
+
See [docs/tasks/infra-mcp.md](../tasks/infra-mcp.md).
|
|
258
|
+
|
|
259
|
+
## Acceptance Criteria
|
|
260
|
+
|
|
261
|
+
- [ ] An MCP client completes OAuth against the deployed server and lists only the tools the signed-in user's roles allow.
|
|
262
|
+
- [ ] A user with only `auditor`/`infra-readonly` can query GCP logs, Sentry issues, and Mongo collections, and cannot see or call any write tool.
|
|
263
|
+
- [ ] A write-tier tool call triggers an elicitation prompt; declining aborts with an audited `rejected` status.
|
|
264
|
+
- [ ] With `confirmWrites: false` configured, confirmation is skipped **only** for callers holding `infra:writeUnconfirmed`; all others are still prompted.
|
|
265
|
+
- [ ] Every tool call (including denials) produces an `InfraAuditLog` record with user attribution and redacted args.
|
|
266
|
+
- [ ] The deployed service runs with only the Cloud Run default runtime SA — no SA key files or `GCP_SA_*` env vars anywhere in the stack.
|
|
267
|
+
- [ ] A cloud agent authenticates with a service token and reads logs; the `GCP_SA_*` env vars are removed from agent environments.
|
|
268
|
+
- [ ] A 2025-era MCP client (pre-2026 spec) connects successfully via the legacy stateless fallback.
|
|
269
|
+
- [ ] `bun run lint`, package tests, and CI pass.
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# Research: Migrate CI/CD to CircleCI
|
|
2
|
+
|
|
3
|
+
**Status:** Research complete — decisions recorded in IP `migrate-cicd-to-circleci.md`
|
|
4
|
+
**Date:** 2026-08-17
|
|
5
|
+
**Repo:** FlourishHealth/terreno (`master` @ `1e3e79a6`)
|
|
6
|
+
**Branch:** `cursor/migrate-cicd-circleci-2b6d`
|
|
7
|
+
|
|
8
|
+
## Scope statement
|
|
9
|
+
|
|
10
|
+
Investigate what “migrate all CI/CD to CircleCI” means for Terreno: inventory every GitHub Actions workflow, classify what is portable vs GitHub-native, map secrets/OIDC/deploy targets, and surface **candidate** CircleCI architectures with tradeoffs. No implementation decisions are committed here.
|
|
11
|
+
|
|
12
|
+
## Current state (findings)
|
|
13
|
+
|
|
14
|
+
### Scale
|
|
15
|
+
|
|
16
|
+
- **~36** hand-maintained `.github/workflows/*.yml` workflows
|
|
17
|
+
- **4** agentic `*.lock.yml` pipelines (gh-aw / Copilot), compiled from `.md` sources
|
|
18
|
+
- **No** `.circleci/` today
|
|
19
|
+
- **No** `.github/actions/` composites — Bun setup + cache is copy-pasted per workflow
|
|
20
|
+
- Helper scripts live under `.github/workflows/scripts/` (EAS/fingerprint) and `.github/scripts/`
|
|
21
|
+
|
|
22
|
+
### Category inventory
|
|
23
|
+
|
|
24
|
+
| Category | Examples | CircleCI portability |
|
|
25
|
+
|----------|----------|----------------------|
|
|
26
|
+
| Package CI | `api-ci`, `ui-ci`, `rtk-ci`, `ai-ci`, `comms-ci`, `mcp-server-ci`, example-* | High |
|
|
27
|
+
| Web E2E | `e2e-ci`, `admin-spa-ci`, `admin-spa-integration` (Playwright + Mongo) | High–medium |
|
|
28
|
+
| Mobile E2E | `demo-appium-ci` (Android + macos-15 iOS), `maestro-e2e` | Medium–low (macOS/Android emulator parity) |
|
|
29
|
+
| Expo/EAS | `eas-pr`, `eas-dev-build`, `fingerprint-gate` | Medium (CLI portable; PR labels/comments GitHub-centric) |
|
|
30
|
+
| Deploy GCP | `cd.yml`, `preview-cleanup` | Medium–hard (OIDC is GitHub-issuer only today) |
|
|
31
|
+
| Deploy Netlify | `demo-deploy`, `frontend-example-deploy`, `docs-deploy` | High–medium |
|
|
32
|
+
| Publish | `publish-on-tag`, `publish-feature-flags-manual` | Medium–hard (token push to master, Zoom, cross-workflow dispatch) |
|
|
33
|
+
| Policy | `repo-policies`, `rulesync-check`, `dco` | High (DCO is GitHub-PR metadata) |
|
|
34
|
+
| Security | `codeql-analysis` | Low (GitHub Code Scanning / Security tab) |
|
|
35
|
+
| GitHub-native | `dependabot-auto-merge`, `triage`, `roadmap-generate` | Low–none |
|
|
36
|
+
| Agentic | `architectural-pr-review`, `agentics-maintenance`, `*.lock.yml` | None (gh-aw, `pull_request_target`, Copilot) |
|
|
37
|
+
|
|
38
|
+
### Shared CI patterns
|
|
39
|
+
|
|
40
|
+
1. `oven-sh/setup-bun@v2` (`bun-version: latest`, sometimes pinned `1.3.10` for EAS)
|
|
41
|
+
2. `actions/cache` on `~/.bun/install/cache` keyed by OS + ref + lockfile
|
|
42
|
+
3. `bun install --frozen-lockfile` then package `lint` / `compile` / `test(:coverage|:ci)`
|
|
43
|
+
4. Mongo via `supercharge/mongodb-github-action` (image `mirror.gcr.io/library/mongo`) for api/ai/example-backend/e2e
|
|
44
|
+
5. Most package CI is **`on: push` with path filters**, not `pull_request` — checks appear when the PR head branch is pushed
|
|
45
|
+
6. Path filtering also uses `dorny/paths-filter` inside larger workflows (`cd.yml`, deploys)
|
|
46
|
+
|
|
47
|
+
### Deploy / release targets
|
|
48
|
+
|
|
49
|
+
| Target | Mechanism |
|
|
50
|
+
|--------|-----------|
|
|
51
|
+
| GCP `flourish-terreno` | Cloud Run (backend, tasks, mcp), Artifact Registry, Infra Manager terraform |
|
|
52
|
+
| Auth to GCP | Workload Identity Federation — issuer `https://token.actions.githubusercontent.com` (`terraform/modules/github_oidc`) |
|
|
53
|
+
| Preview cleanup | Still uses `GCP_SA_KEY` JSON (not WIF) |
|
|
54
|
+
| Netlify | demo, example-frontend, docs sites via `nwtgck/actions-netlify` |
|
|
55
|
+
| npm | Tag-driven publish of ~12 `@terreno/*` packages |
|
|
56
|
+
| EAS Cloud | PR updates/builds + manual workflow dispatch |
|
|
57
|
+
| GitHub Deployments API | CD + Netlify preview environments |
|
|
58
|
+
|
|
59
|
+
### Secrets / vars (union, migration-relevant)
|
|
60
|
+
|
|
61
|
+
**Secrets:** `NPM_TOKEN`, `EXPO_TOKEN`, `NETLIFY_*`, `GCP_SA_KEY`, `MCP_SENTRY_DSN`, `E2E_*`, `CURSOR_API_KEY`, `REPO_ADMIN_TOKEN`, `ROADMAP_PROJECT_TOKEN`, `ZOOM_WEBHOOK_*`, plus gh-aw Copilot tokens on lockfiles.
|
|
62
|
+
|
|
63
|
+
**Vars:** `GCP_WIF_PROVIDER_PROD`, `GCP_TF_ADMIN_SA_PROD`, `GCP_CD_DEPLOYER_SA_PROD`, `GCP_TF_PROJECT_ID_PROD`, `GCP_INFRA_MANAGER_LOCATION`, `ARCHITECTURAL_REVIEW_MODEL`, `TERRENO_PROJECT_NUMBER`.
|
|
64
|
+
|
|
65
|
+
### Branch protection (documented)
|
|
66
|
+
|
|
67
|
+
`docs/explanation/repository-settings.md`: require status checks including **Repository policies**, **package CI**, and **Rulesync Check** relevant to the change. Path-filtered checks are not always present — any CircleCI cutover must remap required checks carefully.
|
|
68
|
+
|
|
69
|
+
### External / CircleCI platform notes
|
|
70
|
+
|
|
71
|
+
- Official migration guide: map Actions → CircleCI jobs/workflows; Actions Marketplace ≈ Orb Registry ([CircleCI migrate from GitHub](https://circleci.com/docs/guides/migrate/migrating-from-github/)).
|
|
72
|
+
- Monorepo best practice: **dynamic config** (`setup: true`) + **path-filtering** orb so only affected packages run ([2026 monorepo guidance](https://www.devopsness.com/blog/circleci-best-practices-2026-07-19)).
|
|
73
|
+
- `circleci/node` orb **≥7.2.0** has first-class Bun (`install-bun`, frozen lockfile, cache) ([node-orb Bun support](https://github.com/oven-sh/bun/discussions/22830)).
|
|
74
|
+
- GCP OIDC from CircleCI is supported but requires a **new** Workload Identity provider (not a drop-in for the current GitHub-only module).
|
|
75
|
+
|
|
76
|
+
## Candidate options (not chosen)
|
|
77
|
+
|
|
78
|
+
### A. Full cutover (literal “all”)
|
|
79
|
+
|
|
80
|
+
Move every runnable pipeline to CircleCI; disable GHA workflows.
|
|
81
|
+
|
|
82
|
+
- **Pros:** Single CI vendor; one secrets home; one config language.
|
|
83
|
+
- **Cons:** Breaks CodeQL Security tab, Dependabot auto-merge, gh-aw agentic bots, issue triage, roadmap generate, `pull_request_target` review, GitHub Deployments UX unless reimplemented via API. Highest risk and cost.
|
|
84
|
+
|
|
85
|
+
### B. Hybrid “CI/CD on CircleCI, GitHub stays for GitHub-native”
|
|
86
|
+
|
|
87
|
+
Move package CI, E2E, Netlify, GCP CD, EAS triggers, publish to CircleCI. Keep CodeQL, Dependabot, DCO/triage/roadmap, and all gh-aw agentic workflows on GitHub Actions.
|
|
88
|
+
|
|
89
|
+
- **Pros:** Matches “migrate CI/CD” intent without destroying OSS/agentic GitHub integrations; clear ownership split.
|
|
90
|
+
- **Cons:** Two systems forever; dual required-check configuration; doc/contrib complexity.
|
|
91
|
+
|
|
92
|
+
### C. Phased hybrid with long dual-run
|
|
93
|
+
|
|
94
|
+
Same end-state as B, but phases: (1) mirror package CI, (2) Netlify, (3) EAS/fingerprint, (4) GCP OIDC + CD, (5) publish last. GHA remains required until each phase proves green.
|
|
95
|
+
|
|
96
|
+
- **Pros:** Lowest blast radius; easy rollback; validates CircleCI org/contexts/credits before cutting release path.
|
|
97
|
+
- **Cons:** Temporary double spend; longer calendar; discipline needed to delete GHA when done.
|
|
98
|
+
|
|
99
|
+
### D. CircleCI for compute-heavy only; keep CD/publish on GHA
|
|
100
|
+
|
|
101
|
+
Only migrate lint/test/Playwright/Appium; leave `cd.yml` + `publish-on-tag.yml` on Actions.
|
|
102
|
+
|
|
103
|
+
- **Pros:** Avoids OIDC rewrite and release double-publish risk; still reduces GHA minute pressure if that is the pain.
|
|
104
|
+
- **Cons:** Does not fulfill “all CI/CD”; two systems for the most critical paths.
|
|
105
|
+
|
|
106
|
+
## Recommended research framing (for questions, not a plan decision)
|
|
107
|
+
|
|
108
|
+
Option **C** is the lowest-risk path that still delivers a real CircleCI CD home; Option **A** is only viable if product/security accepts losing or re-homing GitHub-native surfaces. Option **D** is a scope cut, not a full migration.
|
|
109
|
+
|
|
110
|
+
## Open questions (fed into Step 3)
|
|
111
|
+
|
|
112
|
+
1. Literal “all” vs hybrid (what stays on GitHub)?
|
|
113
|
+
2. Dual-run duration and when GHA workflows are deleted?
|
|
114
|
+
3. CircleCI org/project already exist? Free vs Performance plan? Credit budget?
|
|
115
|
+
4. In-scope: GCP CD + npm publish in v1, or defer?
|
|
116
|
+
5. macOS Appium iOS — CircleCI macOS resource class vs keep on GHA?
|
|
117
|
+
6. Who owns secrets migration and branch-protection remapping?
|
|
118
|
+
7. Roadmap tracking issue after Approved?
|
|
119
|
+
|
|
120
|
+
## References
|
|
121
|
+
|
|
122
|
+
- Inventory of workflows: `.github/workflows/`
|
|
123
|
+
- GCP OIDC: `terraform/modules/github_oidc/`
|
|
124
|
+
- Deploy docs: `docs/explanation/deployment-architecture-gcp.md`, `terraform/README.md`
|
|
125
|
+
- Branch protection: `docs/explanation/repository-settings.md`
|
|
126
|
+
- CircleCI: [Migrating from GitHub](https://circleci.com/docs/guides/migrate/migrating-from-github/), node orb Bun support
|