CedarCopilot wants to merge 12 commits into staging from feat/mcp-oauth-foundation
Live on prod, no production signal yet
Strict CloudWatch fallback saw 131 success-shaped log lines matching auth, login, session, token, but no tied operation was present, so Libra is not calling this working.
OTEL fallback saw 4,691 prod spans for the repo/env since deploy, but 144 were errors and the intent has no concrete surface to attribute them to.
Strict CloudWatch fallback saw 247 success-shaped log lines matching crm, hubspot, salesforce, deal, deals, but no tied operation was present, so Libra is not calling this working.
Strict CloudWatch fallback saw 3,196 success-shaped log lines matching email, emails, gmail, mail, inbox, but no tied operation was present, so Libra is not calling this working.
Strict CloudWatch fallback found 12 recent prod failure log lines for [SCHEDULED] Failed to enrich unenriched calendar event (Failed query: insert into \), but the same failure had 9 log lines in the comparable pre-deploy baseline. Libra is not blaming this PR intent without a.
Strict CloudWatch fallback found 54 prod failure log lines for [processIncomingSlackChannelData] Error processing channel (No valid connection tokens found for user <id>), but this domain-wide failure family is not tied to this intent.
internal_only
internal_only
internal_only
internal_only
internal_only
internal_only
internal_only
internal_only
internal_only
Libra has verdicts on 0 of 5 tracked behaviors on prod; 5 are still being checked. Libra checks hourly for 3 days after each deploy.
Connecting an MCP server now needs a name and a URL instead of a code change, and a server a user pastes works with no provider entry at all. Adds a credential vault for systems that have no MCP, and moves three meeting recorders off hand-extracted CLI tokens.
The OAuth endpoints cannot be derived from an MCP URL. Caretta's MCP is on gateway.caretta.app while its authorization server is a Supabase project on supabase.co; Gong's MCP is on mcp.gong.io while registration is on app.gong.io. Every server checked puts at least one endpoint on a host you would not predict, so they have to be discovered: RFC 9728 protected-resource metadata, then RFC 8414 authorization-server metadata, then RFC 7591 dynamic client registration.
Verified against twelve live servers, including three deviations the chain has to absorb:
WWW-Authenticate carries no resource_metadata, so the well-known path must be constructed.providers/dcr-pkce-driver.ts is deleted. Notion, Pylon and Mintlify move onto discovery, keeping pinned narrow scopes: Mintlify's resource advertises write, deploy and billing scopes that a docs integration must not request.
| Provider | Before | After |
|---|---|---|
| Caretta | npx mcp-remote, a python3 glob over ~/.mcp-auth/**, paste 3 values | one button |
| Circleback | npm i -g @circleback/cli, cb login, cat tokens.json, paste 4 values | one button |
| Krisp | npx mcp-remote, two cats, paste 4 values incl. a client secret | one button |
Existing pasted credentials keep working. One connection row holds both shapes and the MCP token set wins when present, so nobody has to reconnect on deploy.
createConnection would have inserted a second row when a provider with an existing api_key connection connected over MCP: the conflict target is (userId, providerId, externalId) and the callback keyed on the server URL. Two rows break credential lookup and double-process the backfill cron.mcp-token-refresh's expiryUnchanged guard compiled to NULL IS NOT DISTINCT FROM NULL whenever a provider omits expires_in, so concurrent writers both landed and could persist an already-rotated refresh token. That is also the connection shape with the worst concurrency, since nothing refreshes proactively and every process 401s at the same instant.updateMcpConnection rebuilt metadata from three inputs and full-replaced the column. Harmless while providerId: 'mcp' meant static-header only; this change puts OAuth rows under that id, so it would have wiped their refresh tokens.Four of the five review commits are security. Each is pinned by a test confirmed to fail without its fix.
userId sat outside the AES-GCM sealed blob while the callback route, which has no auth middleware, trusted it to decide whose account to install a connection on. An attacker could run a real connect in their own account, obtain a genuine code and sealed blob, rewrite userId, and replay the callback. Encryption is not integrity, and the previous shape conflated them: it looked authenticated without being so, which is worse than the plainly-unauthenticated base64 it replaced.resource matched the one requested, so a hostile server could name Notion's real AS, and the resulting Notion bearer would be shipped to the attacker's host on every call.tokenUrl was validated by `z.string().url()Libra has not measured any production surfaces for this change yet.