Skip to main content

MCP and OAuth

The mcp Edge Function is a stateless Streamable HTTP Model Context Protocol resource server. It lets OAuth-authorized assistants work with a user's Navaid companies without sharing the user's browser session or a service-role key.

Components

MCP client
├─ discovers resource/auth metadata
├─ dynamically registers when hosted policy permits
└─ Supabase OAuth 2.1 authorization flow
└─ /oauth/consent in Navaid frontend

Bearer token
└─ /functions/v1/mcp
├─ auth.getUser(token)
├─ require OAuth client_id claim
├─ load active memberships/roles
├─ enforce mcp_enabled (admin bypass)
└─ execute bounded tool with service client

The function does not mint tokens. Supabase Auth is the authorization server.

Deployment Requirements

  • mcp deployed with --no-verify-jwt
  • hosted Supabase OAuth server enabled
  • consent URL configured as /oauth/consent
  • dynamic client registration enabled/configured if the target client needs it
  • application redirect URLs correct
  • mcp_enabled set as intended

The audited developer-local, Git-ignored supabase/config.toml disables OAuth server and dynamic registration. That is not a main-branch declaration; hosted state must be checked separately.

Authentication

Tool requests require:

  • syntactically valid Bearer header,
  • token validated by auth.getUser,
  • OAuth client_id claim,
  • current Navaid user profile,
  • feature access.

A normal web-session token without OAuth client identity is not accepted as an MCP connector credential.

Discovery/challenge endpoints are intentionally reachable without bearer auth. They must expose metadata, not tenant data.

/oauth/consent?authorization_id=...:

  • looks up the pending OAuth authorization request,
  • identifies the requesting client/scopes,
  • redirects through login when necessary,
  • validates safe return state,
  • lets the user approve or deny,
  • is localized and announces progress to assistive technology.

The authorization identifier is not an arbitrary redirect URL.

Feature and Tenant Scope

mcp_enabled defaults off. System administrators bypass the flag.

The function uses a service client, so it constructs an authorization context from active company_memberships and system-admin state. Every company argument is resolved against that context.

Admin-equivalent profile writes allow:

  • system admin
  • company_admin
  • company_consultant

Managers/users cannot update company facts through MCP.

Tools

Current tools:

ToolAccess
list_companiesActive memberships
get_maturity_summaryMember of company
get_dimension_detailMember; bounded tasks/detail
list_tasksMember; optional status/dimension
list_sessionsRole/session visibility rules
get_session_summaryAuthorized session visibility
get_company_knowledgeCompany member
update_company_factsAdmin/consultant
complete_taskAuthorized task/company
dismiss_taskAuthorized task/company

Results are bounded and structured. Internal error details are converted to MCP tool errors rather than leaking database/provider internals.

Profile Write Concurrency

update_company_facts:

  1. resolves authorized company,
  2. loads profile/version,
  3. applies allowlisted facts update,
  4. conditionally writes the expected version,
  5. appends company_profile_history with change_source='mcp',
  6. reports a conflict when concurrent state changed.

It must not overwrite maturity or other pipeline-owned fields.

Scopes

Keep OAuth scopes least privilege. If scope granularity is expanded:

  • separate read from write,
  • surface human-readable scope descriptions in consent,
  • enforce scopes in addition to membership/role,
  • preserve compatibility for existing clients,
  • add revoke/re-consent guidance.

Do not treat user approval as tenant authorization; both are required.

Security Failure Modes

FailureResponse
No bearer on tool callOAuth resource challenge/401
Invalid/expired token401
Normal web tokenreject missing OAuth client identity
Feature off403 for non-admin
Unknown/cross-tenant companytool error/forbidden
Insufficient write roletool error/forbidden
Optimistic write conflictretryable conflict, no lost update
Discovery requestmetadata only

Never log bearer tokens or full authorization callback URLs.

Adding a Tool

  1. Define a Zod input schema.
  2. Bound lists and text.
  3. Resolve company/session/task through authorization context.
  4. Decide membership and role.
  5. Enforce OAuth scopes if used.
  6. Avoid returning secrets, raw tokens, hidden prompts, or unrelated columns.
  7. Use concurrency/audit patterns for writes.
  8. Add protocol, auth, tenant and malformed-input tests.
  9. Update consent copy/scopes and this table.

Troubleshooting

Client cannot discover server

Check public metadata/challenge routes, deployment flag, canonical function URL, and provider expectations.

Verify safe redirect preservation and Auth site/redirect URLs.

DCR fails

Hosted dynamic registration may be disabled even though the function is deployed.

Web session works but MCP token fails

Expected if the token lacks an OAuth client_id claim.

Tool sees no companies

Inspect active membership status and role; selected browser-company state is irrelevant to MCP.