Libra CodeHub

CedarCopilot/cedar-mail

Branch: staging

feat(mcp): make MCP protected-resource identifier configurable

merged#2820CedarCopilot

CedarCopilot wants to merge 3 commits into staging from feat/cedar-mcp-public-resource-url

Live on prod, 1 of 1 surfaces working, less than a day leftTimeline and evidence
  1. Opened
    Sep 18, 2026, 8:43 AM
  2. Sep 18, 2026, 9:30 AM
  3. Merged
    Sep 18, 2026, 9:44 AM
  4. Live on prod
    Sep 18, 2026, 9:44 AM
  5. Observed 25 hours, 1 surface, 30 requests
    Sep 18, 2026, 9:44 AM
  6. Pipelines steady after this deploy
    Sep 18, 2026, 9:44 AM
  7. Watching

    Live on prod, 1 of 1 surfaces working, less than a day left

    Sep 19, 2026, 10:46 AM

Behaviors Libra is checking

MCP protected-resource metadata advertises the configured CEDAR_MCP_PUBLIC_URL when present, while authorization_servers remains on the existing authentication origin and unset deployments retain the prior default resource identifier.Workingmedium confidence

Since the deploy, 27 prod OTEL spans matching GET /.well-known/oauth-protected-resource ran with 0 error spans. Libra found no comparable traffic on this surface in the 25h before the deploy, so there is no before-and-after contrast. Confidence is medium because there was no.

prod, checked Sep 19, 2026, 10:28 AM
Browser-based MCP clients originating from the configured public MCP domain are accepted by Cedar's trusted-origin checks for MCP requests and CORS handling.Inconclusivelow confidence

Unable to complete verification from the available telemetry: the production API-service queries returned no post-deploy MCP, origin-validation, CORS, or protected-resource records, so there is no positive runtime evidence for the intended behavior.

prod, checked Sep 19, 2026, 10:30 AM
Staging and production deployments read CEDAR_AWS_STAGING_MCP_PUBLIC_URL or CEDAR_AWS_PROD_MCP_PUBLIC_URL and inject CEDAR_MCP_PUBLIC_URL into the ECS task environment only when the value is non-empty.Not checked
prod, checked Sep 19, 2026, 10:46 AM

Libra measured 1 of 1 production surface on prod. Libra checks hourly for 3 days after each deploy.

Summary

  • Adds CEDAR_MCP_PUBLIC_URL, threaded into better-auth's mcp() plugin as the resource override (apps/server/src/lib/auth.ts) and into the trusted-origins allowlist (apps/server/src/lib/trusted-origins.ts).
  • Lets Cedar's MCP server (/mcp, served by externalMcpRouter) publish /.well-known/oauth-protected-resource under a Manufact-proxied domain (mcp.cedarcopilot.com) for app store submission, while authorization_servers stays on the existing backend origin , auth can't move there without breaking session cookies.
  • This is the RFC 9728 resource/authorization-server split Pietro (Manufact) confirmed in Slack both their readiness checklist and the OpenAI/Anthropic store reviews accept.
  • Unset, behavior is unchanged: resource falls back to the app origin exactly as it did before this var existed (verified against better-auth's source: resource: options?.resource ?? origin).

Traced end-to-end: externalMcpRouter's .well-known/oauth-protected-resource route (apps/server/src/routes/external-mcp.ts) calls better-auth's oAuthProtectedResourceMetadata(auth), which derives authorization_servers from ctx.context.baseURL , statically configured as env.VITE_PUBLIC_BACKEND_URL (apps/server/src/lib/auth.ts:1008), not from the request's Host header. So the split holds regardless of which domain a request arrives on.

This is groundwork only , no DNS/proxy changes yet. The domain cutover (Manufact proxy config, DNS for mcp.cedarcopilot.com, setting this env var in an actual environment) is staged separately per Pietro's explicit ask not to touch production MCP traffic during the transition.

Test plan

  • pnpm --filter @zero/server run types , clean on the three touched files (pre-existing unrelated errors in conversationTool.ts on a different in-progress branch, not touched here)
  • Thermo review run on this diff , no blocking or should-fix findings
  • Manual: once CEDAR_MCP_PUBLIC_URL is set in an environment, fetch https://mcp.cedarcopilot.com/.well-known/oauth-protected-resource and confirm resource reads the new domain while authorization_servers still reads api.mail.cedarcopilot.com

🤖 Generated with Claude Code

RetriggerConfidence Score: 4/5

The PR is not safe to merge until the AWS task environment propagates the new MCP public URL to the API process.

Findings

  1. P1 Deployment Drops MCP URL ▶
Fix with agent prompt
### Issue 1
apps/server/src/env.ts:472
The new variable is read from `process.env`, but the AWS ECS task environment is built by `buildServiceEnvironment()`, which does not include `CEDAR_MCP_PUBLIC_URL`. As a result, the value will be undefined in the API process, so better-auth will keep advertising the backend origin and this configuration will have no effect in deployed AWS environments.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Summary

This PR introduces an optional public MCP resource identifier and threads it into better-auth metadata and the trusted-origin allowlist. The server-side wiring is internally consistent, but the repository’s AWS task environment contract does

Show production surfaces and changed-file mapping

Production surfaces

SurfaceRequestsErrorsp95UsersVerdict
/.well-known/oauth-protected-resource24 → 300 → 0 (0%)1 ms → 1 ms0Working
30 requests since the deploy with 0 errors (0 errors in the 24-request baseline before it). p95 1 ms, was 1 ms.

Changed files → surfaces

  • .github/workflows/prod-deploy.ymlno production surface mapped
  • .github/workflows/staging-deploy.ymlno production surface mapped
  • apps/server/src/env.tsno production surface mapped
  • apps/server/src/lib/auth.tsno production surface mapped
  • apps/server/src/lib/trusted-origins.tsno production surface mapped
  • aws/lib/config.tsno production surface mapped
  • aws/lib/runtime-contract.tsno production surface mapped