Build MCP control plane linking
## Original requirement (verbatim, epic 13348)
Deep research all third party integrations in Lovable (Connect) and implement all of them.
Ensure the highest level of code reuse, abstract out any generic code across all integrations. Users need an easy way to connect. Documentation must be as detailed as in Lovable or better.
If something needs to be done on the partner website (e.g. register an app) and it can only be done by human create a follow up task per third party with exact step by step of what user needs to do). Mark it as user only task to set up but the runner will validate it was done correctly.
You can refactor anything to make working with creds more robust.
## Settled inputs
Full PRD and architecture: the epic's `prd` and `architecture` artifacts (Task 13348 BA, Task 13349 Architect). Section numbers below (§3.2 item 6, AC2.3, …) refer to them. They are settled: realize them, do not reopen them. Operator decisions O1–O6 and D1–D8 bind.
## This child's scope
C4a, architecture §5 (control-plane half) and the `mcp_server` type from §4.
- Table `project_mcp_links` already exists from C1's revision (no migration in this child). Creating a link also grants that project's tasks the credential if not already visible. `PUT` / `DELETE` / `GET /api/projects/{id}/mcp-links/{credential_id}` with the same authorization as `routes/credentials/project_pool.py`; refuse linking a `system_owned` row.
- Credential type `mcp_server` (`url`, optional `bearer_token` secret, optional OAuth fields).
- OAuth for MCP in `oauth_providers.py` / `oauth2_wire.py`: RFC 9728 protected-resource discovery, then RFC 8414 metadata; RFC 7591 Dynamic Client Registration with the client stored as an operator-plane `oauth_app` row per (vendor, environment); PKCE and the RFC 8707 `resource` parameter. Remote MCP URLs dialled through `vetted_addresses`.
- Launch contract: `DispatchDecision` (`apps/api/app/routes/fleet.py`) gains `mcp_connections: list[McpConnectionOut] = []` ({name unique per task, credential_path roster path, url https, transport}; no secret), filled by `apps/api/app/dispatch_compose/compose.py` from `project_mcp_links` for the task's project.
- MCP Test (owned here, reused by W1-MCP and custom MCP): a tester that performs the MCP `initialize` handshake then `tools/list` over streamable_http and sse, dialled through `vetted_addresses`, mapping 401/403 to "<Vendor> rejected the token (401)"; registered as a bespoke `tester` hook so a spec's `[mcp]` block gets it with no code.
- `supports_remote_mcp` served via the CP re-export `apps/api/app/agent_providers.py` and `GET /api/accounts/providers`.
Credentials & reach:
- MCP on dev (linked project lists tools; transcript grep for the token) — deferred (MCP half): no MCP vendor or test-bearer credential exists, operator chose unit tests only (O6); proven by C4b unit tests. Console half verified via `org/console/dev-login`
- Unit tests, `./dc check quick`, `tools/check_context.py` — verified (no credential)
Out of scope (PRD §6, verbatim, plus what NOT to touch):
- No app-user connectors (per-end-user OAuth, connector gateway for published apps).
- No "Managed by Jaah" vendor keys or credit billing; no change to `apps/api/app/billing/`.
- No per-vendor API client libraries or MCP tool wrappers built by Jaah (except where the vendor's own hosted MCP server is the connector).
- No Lovable action-approval UI (allow once/always).
- No change to runner auth to model providers (`account_auth.py`, `Account` table) or to login/sign-in OAuth (`routes/login_oauth.py`).
- No change to NodeVault crypto semantics (`credential_crypto.py` encryption tiers) beyond what a refactor needs to keep behaviour identical.
- Do not break existing rows: every existing credential type and every existing row keeps working; migrations additive (two-release overlap rule).
- Supabase/project-backend and git-sync are not new work; Figma plugin/.fig upload out.
- Never write a credential value into a task, doc, log or PR.
- Do NOT file Tasks (no licence): the human-only OAuth registration tasks are filed outside this child.
Done means:
- AC4.1 (CP) MCP vendors and "Custom MCP server" are connectable (OAuth or key per vendor, server URL editable where the vendor allows). — unit test drives DCR discovery → registration → authorize (with PKCE + `resource`) → token store for a mocked MCP authorization server; a bearer `mcp_server` row saves and tests.
- AC4.2 (CP half) An MCP connection is attached only when the session's project has linked it. — unit test: dispatch for a task in the linked project carries the connection in `mcp_connections`; a task in another project of the same org gets `[]`; the decision contains no secret.
- Link routes enforce project authorization and refuse a `system_owned` credential — authz unit tests (other org → 404, member without project write → 403).
- `GET /api/accounts/providers` returns `supports_remote_mcp` per agent — unit test.
- MCP tester: unit tests against a mocked streamable_http server and a mocked sse server (pass lists tool count; 401 gives the vendor sentence; a 169.254.169.254 URL is refused).
- AC1.5 `./dc check quick` green and `tools/check_context.py` green on this PR — the gate output cited in the PR body.
Activity
- Draft
- Ready
- Runner started