Skip to content

desktop: mcp: OAuth-required servers cannot onboard because callback uses public_base_url #119

Description

@sanjitkverma

What this prevents

  • I can install an MCP server that requires OAuth and complete its connect flow.
  • I can use a cloud-hosted MCP server (Notion, Linear, Gmail, Google Drive) from the desktop client.

MCP servers that require OAuth use the orchestrator's public callback URL, which is set from public_base_url. On desktop the sidecar binds to 127.0.0.1, so the value resolves to a loopback or internal URL the third-party provider rejects as a redirect URI mismatch. Users see the provider's consent screen, click Allow, and land on a "redirect URI mismatch" error page. Stdio-only MCP servers (where no OAuth is required) keep working.


Surface

  • The MCP OAuth router builds the redirect URI from public_base_url.
  • The MCP OAuth flow service consumes the redirect URI for the provider's authorization request.
  • The config layer resolves public_base_url. On desktop it has no sensible value because there is no public-facing service.
  • The desktop deep-link handler already handles tesslate://auth/callback for the cloud-pairing flow. There is no equivalent handler for MCP OAuth callbacks.

Expected vs Actual

Expected: clicking Connect on a cloud MCP server brings up the provider's OAuth consent, the user clicks Allow, and the desktop client receives the token and stores it (encrypted) on the user's MCP config.

Actual: the provider rejects the redirect URI because it points at a loopback or non-public address that was not registered with them. The flow dies on the provider's side.

Suggested fix

Two-tier strategy depending on what each provider accepts:

  1. For providers that accept loopback redirect URIs (Google does, with 127.0.0.1-based callbacks): build the redirect URI from http://127.0.0.1:{sidecar_port}/api/mcp/oauth/callback. The sidecar already serves on a known port; the pairing token can be passed through state.
  2. For providers that require an https URL or a custom scheme: route the provider callback through a Tesslate-hosted relay (or a custom URL scheme handled by the desktop deep-link handler), then bounce back to the sidecar.

Either path lands the token on the local sidecar and stores it on the user's MCP config row.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingopensourceSelf-contained issue suitable for external contributors

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions