Summary
Remote MCP servers that use OAuth 2.0 (the MCP "Authorization" spec with WWW-Authenticate: Bearer realm="OAuth" + resource_metadata) cannot be connected from LangBot. The SSE/HTTP MCP connector only supports static headers, so any OAuth-protected hosted server returns 401/403 on connect, and the install UI gives the user no way to complete an authorization flow.
This affects several popular hosted MCP servers now listed on LangBot Space (Linear, Vercel, Cloudflare, and a growing number of first-party hosted servers that are moving to OAuth).
Steps to reproduce
- Add an SSE MCP server pointing at
https://mcp.linear.app/sse (or install "Linear" from the Space marketplace).
- Enable it / connect.
- Connection fails with
401 Unauthorized.
Root cause
The MCP loader builds the transport from static config only:
src/langbot/pkg/provider/tools/loaders/mcp.py
- SSE:
sse_client(url, headers=server_config.get('headers', {}), ...)
- HTTP:
streamable_http_client(url, http_client=httpx.AsyncClient(headers=server_config.get('headers', {}), ...))
The install form (web/.../mcp-server/mcp-form/MCPFormDialog.tsx) only renders parameters for keys that already exist in extra_args.headers (sse/http) or extra_args.env (stdio). There is:
- no OAuth client (no Authorization Server discovery, no Dynamic Client Registration, no PKCE authorize/redirect/token exchange), and
- no UI affordance to launch a browser authorization flow or store the resulting tokens.
So even if the user pastes a personal API token into an Authorization header, OAuth-only servers reject it — they require a real OAuth access token obtained via the interactive flow.
Evidence (live probes)
initialize POST to each endpoint, with and without a static Bearer token:
linear https://mcp.linear.app/sse
401 WWW-Authenticate: Bearer realm="OAuth",
resource_metadata="https://mcp.linear.app/.well-known/oauth-protected-resource/sse"
(identical with a static Bearer token — PAT is not accepted)
vercel https://mcp.vercel.com
401 WWW-Authenticate: Bearer error="invalid_token",
resource_metadata="https://mcp.vercel.com/.well-known/oauth-protected-resource"
cloudflare https://observability.mcp.cloudflare.com/sse
403 Error 1010 (blocks non-browser clients) — browser OAuth required
These are standard MCP Authorization (OAuth 2.1) servers: they advertise .well-known/oauth-protected-resource, expect Authorization Server metadata discovery, Dynamic Client Registration, and a PKCE authorize → token flow.
Proposed solution
Implement the MCP Authorization spec for remote (SSE/HTTP) servers:
- Detect the OAuth challenge on
401 (WWW-Authenticate: Bearer ... resource_metadata=...).
- Discover the protected-resource metadata → authorization server → its
.well-known/oauth-authorization-server metadata.
- Dynamic Client Registration (RFC 7591) if no client is pre-registered.
- PKCE authorize: open the system browser to the authorize URL with a local loopback redirect (e.g.
http://127.0.0.1:<port>/callback), capture the code.
- Token exchange + refresh token persistence (encrypted, per MCP server config).
- Attach
Authorization: Bearer <access_token> to the SSE/HTTP transport and auto-refresh on expiry.
- UI: an "Authorize" button in the MCP install/detail dialog for OAuth-type servers; show connection/authorization status.
The Python SDK (mcp) has reference OAuth client helpers that can be leveraged for the discovery/PKCE/token machinery.
Interim mitigation (already done on Space)
On the marketplace side, OAuth-only hosted servers (Linear, Vercel, Cloudflare) now carry a README banner stating they require OAuth and are not yet connectable in LangBot, to avoid users hitting silent 401s. Servers that use static auth work fine and import their parameters correctly:
- stdio: required keys live in
extra_args.env (e.g. Postgres DATABASE_URI) → rendered as fields. ✅
- sse/http with static token: required keys live in
extra_args.headers (e.g. Context7 CONTEXT7_API_KEY). ✅
- key-in-URL: e.g. Tavily
?tavilyApiKey=.... ✅
This issue tracks adding the OAuth flow so the OAuth-only servers become first-class too.
Acceptance criteria
Summary
Remote MCP servers that use OAuth 2.0 (the MCP "Authorization" spec with
WWW-Authenticate: Bearer realm="OAuth"+resource_metadata) cannot be connected from LangBot. The SSE/HTTP MCP connector only supports static headers, so any OAuth-protected hosted server returns401/403on connect, and the install UI gives the user no way to complete an authorization flow.This affects several popular hosted MCP servers now listed on LangBot Space (Linear, Vercel, Cloudflare, and a growing number of first-party hosted servers that are moving to OAuth).
Steps to reproduce
https://mcp.linear.app/sse(or install "Linear" from the Space marketplace).401 Unauthorized.Root cause
The MCP loader builds the transport from static config only:
src/langbot/pkg/provider/tools/loaders/mcp.pysse_client(url, headers=server_config.get('headers', {}), ...)streamable_http_client(url, http_client=httpx.AsyncClient(headers=server_config.get('headers', {}), ...))The install form (
web/.../mcp-server/mcp-form/MCPFormDialog.tsx) only renders parameters for keys that already exist inextra_args.headers(sse/http) orextra_args.env(stdio). There is:So even if the user pastes a personal API token into an
Authorizationheader, OAuth-only servers reject it — they require a real OAuth access token obtained via the interactive flow.Evidence (live probes)
initializePOST to each endpoint, with and without a static Bearer token:These are standard MCP Authorization (OAuth 2.1) servers: they advertise
.well-known/oauth-protected-resource, expect Authorization Server metadata discovery, Dynamic Client Registration, and a PKCE authorize → token flow.Proposed solution
Implement the MCP Authorization spec for remote (SSE/HTTP) servers:
401(WWW-Authenticate: Bearer ... resource_metadata=...)..well-known/oauth-authorization-servermetadata.http://127.0.0.1:<port>/callback), capture the code.Authorization: Bearer <access_token>to the SSE/HTTP transport and auto-refresh on expiry.The Python SDK (
mcp) has reference OAuth client helpers that can be leveraged for the discovery/PKCE/token machinery.Interim mitigation (already done on Space)
On the marketplace side, OAuth-only hosted servers (Linear, Vercel, Cloudflare) now carry a README banner stating they require OAuth and are not yet connectable in LangBot, to avoid users hitting silent 401s. Servers that use static auth work fine and import their parameters correctly:
extra_args.env(e.g. PostgresDATABASE_URI) → rendered as fields. ✅extra_args.headers(e.g. Context7CONTEXT7_API_KEY). ✅?tavilyApiKey=.... ✅This issue tracks adding the OAuth flow so the OAuth-only servers become first-class too.
Acceptance criteria