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:
- 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.
- 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.
What this prevents
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
public_base_url.public_base_url. On desktop it has no sensible value because there is no public-facing service.tesslate://auth/callbackfor 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:
127.0.0.1-based callbacks): build the redirect URI fromhttp://127.0.0.1:{sidecar_port}/api/mcp/oauth/callback. The sidecar already serves on a known port; the pairing token can be passed throughstate.Either path lands the token on the local sidecar and stores it on the user's MCP config row.