Skip to content

🔒feat: Add On-Behalf-Of (OBO) token exchange support for MCP server connections#13429

Merged
danny-avila merged 17 commits into
danny-avila:devfrom
jcbartle:new/feature/obo_authentication_clean
Jun 2, 2026
Merged

🔒feat: Add On-Behalf-Of (OBO) token exchange support for MCP server connections#13429
danny-avila merged 17 commits into
danny-avila:devfrom
jcbartle:new/feature/obo_authentication_clean

Conversation

@jcbartle

Copy link
Copy Markdown
Contributor

Summary

Overview

Add support for the OAuth 2.0 On-Behalf-Of (OBO) flow when connecting to MCP servers, gated behind a dedicated MCP_SERVERS.CONFIGURE_OBO permission. Users authenticated via OpenID Connect can transparently authenticate to downstream MCP servers using their existing identity — no per-server OAuth redirect flow.

Operators control which roles are allowed to configure or modify OBO servers, and any role downgrade fails closed at runtime.

More Details

General

  • New OboTokenService extracts the generic OBO exchange logic (jwt-bearer grant via openid-client) with caching per user/scope. GraphTokenService is refactored to delegate to it.
  • resolveOboToken in packages/api validates the user's federated token, calls the resolver, and returns MCPOAuthTokens.
  • OBO tokens are injected via request headers (not OAuth transport) and refreshed on each tool call.
  • Configuration via librechat.yaml: add obo.scopes to any MCP server entry.
  • Configuration via UI: new "On-Behalf-Of (OBO)" auth type radio option with a scopes input field.
  • Schema validation enforces non-empty scopes. When OBO is configured but token exchange fails, an explicit error is thrown (no fallthrough to standard OAuth).
  • OBO MCP traffic is isolated to user-scoped MCP connections — never an app-level shared connection.
  • OBO configuration is restricted to header-capable transports (SSE and streamable-HTTP).
  • Concurrent OBO token exchanges for the same user+scope are coalesced via an in-flight Map so a fan-out of parallel tool calls produces a single upstream jwt-bearer request.

MCP_SERVERS.CONFIGURE_OBO permission

OBO silently mints per-user delegated tokens and forwards them to whatever URL the server config points at, so anyone who can configure an OBO-enabled MCP server controls a token-exfiltration channel.

A dedicated MCP_SERVERS.CONFIGURE_OBO permission now gates OBO configuration. Default ADMIN: true, USER: false; existing role documents are backfilled with the new sub-key on startup via updateInterfacePermissions, preserving any operator-set values. The permission is exposed in the MCP role-permissions admin dialog alongside USE / CREATE / SHARE / SHARE_PUBLIC.

Enforcement runs at three layers:

  1. Create/update. POST /api/mcp/servers and PATCH /api/mcp/servers/:serverName reject any payload that adds, modifies, or removes the obo block without the permission. Adds and modifies are blocked because they configure OBO; removals are blocked because they silently downgrade an OBO server to no auth (a self-OBO-disable vector).

  2. Runtime fail-closed. Before performing an OBO token exchange, the MCP runtime re-checks the original author of the DB-stored config against MCP_SERVERS.CONFIGURE_OBO. If the author has been downgraded since the server was created, the exchange is skipped (factory) or refused (callTool) — retained configs lose their privileges automatically without any operator action. YAML/Config-source servers bypass the check, since admin-authored deployment-level config is already trusted.

  3. UI. The "On-Behalf-Of (OBO)" auth option is hidden from users without the permission in the create flow; when in edit mode without the permission, the OBO row remains visible (so the server's auth state is legible) but every OBO-target field is locked. When editing an OBO server without CONFIGURE_OBO, only an allowlist of cosmetic fields (title, description, iconPath) may change (controlled via the manually-populated OBO_USER_EDITABLE_FIELDS allowlist). Any modification to url, type, proxy, headers, oauth, apiKey, obo, etc. is rejected with 403. The frontend disables the corresponding form sections (URL / transport / auth / trust) so the form's interactive surface matches the backend policy. The comparison surface is derived from MCPServerUserInputSchema, so new schema fields are locked by default and adding to the allowlist is the only way to unlock them.

Tests

Tests cover create/update gating, runtime fail-closed, OBO/OAuth coexistence, schema enforcement, and UI lockdown.

Migration notes for operators

  • New MCP_SERVERS.CONFIGURE_OBO permission appears in the MCP role admin dialog. Defaults to true for ADMIN, false for USER. Existing role documents are backfilled on app startup; no manual DB migration required.
  • Existing DB-stored MCP servers with requiresOAuth: true and obo set continue to work — the inspector previously auto-set requiresOAuth based on a probe response that ignored OBO. Connection routing now treats OBO as authoritative regardless of the requiresOAuth flag.
  • Users without CONFIGURE_OBO who can edit an OBO server they own cannot change the URL, transport, or auth fields. Title / description / icon remain editable. To take an OBO server out of service, contact an admin.

Change Type

Please delete any irrelevant options.

  • New feature (non-breaking change which adds functionality)
  • This change requires a documentation update

Setup and Testing

We have this fully-implemented and running in dev and prod. This requires two Entra ID app registrations, the first for LibreChat itself (the client) and the second for the downstream MCP (the server).

Server App Registration Part I (used by MCP Server)

  • Create a confidential client app registration
  • Under Expose an API, set the Application ID URI to api://<server-client-id> and add a scope supported by that server (such as access_as_user)
  • Under API permissions, add whatever permissions are required (if any) and grant admin consent
  • Under Certificates & secrets, create a client secret

Client App Registration (used by LibreChat)

Server App Registration Part II

  • On the server app registration, under Expose an API → Authorized client applications, add the client app's client ID and authorize it for the scope created above (this pre-authorizes the client so users aren't prompted to consent to the server API)

Add the following to your librechat.yaml file:

mcpServers:
  MyMcpServer:
    type: streamable-http
    url: https://mcp.example.com
    obo:
      scopes: "api://<client-id>/<my-permission>"

(alternatively, add the MCP server via the UI and select the On-Behalf-Of tab)

Your user can now log into LibreChat and authenticate to the MCP server without having a separate visible login step.

CONTRIBUTE.md

Tests documented in the CONTRIBUTE.md are passing. The tests which are failing also failing in the upstream. I addressed the linter errors related to files I changed (except for one which was not in an area of the file I modified).

Checklist

Please delete any irrelevant options.

  • My code adheres to this project's style guidelines
  • I have performed a self-review of my own code
  • I have commented in any complex areas of my code
  • I have made pertinent documentation changes
  • My changes do not introduce new warnings
  • I have written tests demonstrating that my changes are effective or that my feature works
  • Local unit tests pass with my changes
  • A pull request for updating the documentation has been submitted.

Documentation will still need to be done.

jcbartle added 14 commits May 11, 2026 15:40
Enables transparent authentication to Entra ID-backed MCP servers using the logged-in user's federated token via the OAuth 2.0 jwt-bearer grant. Configured via obo.scopes in librechat.yaml server config.

- Extract generic OboTokenService from GraphTokenService (jwt-bearer grant + cache)
- Refactor GraphTokenService to thin wrapper delegating to OboTokenService
- Add obo schema field to BaseOptionsSchema in data-provider
- Add resolveOboToken in packages/api/src/mcp/oauth/obo.ts (validates federated token, calls resolver, returns MCPOAuthTokens)
- Wire oboTokenResolver through MCPConnectionFactory, MCPManager, UserConnectionManager
- OBO tokens injected via request headers (not OAuth transport), refreshed on each tool call
- Explicit error on OBO failure (no fallthrough to standard OAuth redirect)
- Add unit tests for both resolveOboToken (9 tests) and exchangeOboToken (14 tests)
  Enable users to configure On-Behalf-Of (OBO) token exchange for MCP servers created via the UI (MongoDB-stored), in addition to the existing YAML-based configuration.

  - Add "On-Behalf-Of (OBO)" radio option to MCP server auth section with scopes input field
  - Remove obo from omitServerManagedFields so the field passes UI schema validation
  - Add OBO to AuthTypeEnum, obo_scopes to AuthConfig, and OBO handling in form defaults and submission
  - Add .min(1) validation on obo.scopes to reject empty strings
  - Add English localization keys: com_ui_obo, com_ui_obo_scopes, com_ui_obo_scopes_description
  - Add 5 schema validation tests for OBO field acceptance, transport compatibility, and edge cases
…BO configuration not showing up in the MCP UI after app restart
…change failures

- stop tool calls from falling back to stale Authorization headers when per-call OBO refresh fails
- add one-time retry for transient Entra OBO exchange failures (network/429/5xx)
- preserve structured OBO failure reasons and retryability in resolveOboToken
- improve OBO auth error messaging for connection setup and tool execution
- add tests for transient vs permanent OBO failure paths
- block OBO-enabled servers from app-level shared MCP connections
- bypass shared connection lookup for OBO servers in MCPManager.getConnection
- add regressions covering OBO connection scoping and preserve non-OBO app connection reuse
- add shared requiresUserScopedConnection helper for OAuth, OBO, and customUserVars
- use the shared predicate in MCPManager and ConnectionsRepository
- add utils coverage for user-scoped connection policy
- Move OBO configuration out of the shared MCP base options schema and allow it
only on SSE and streamable-http transports, where request headers are applied.
- Explicitly reject OBO on stdio and websocket configs to avoid accepted-but-
nonfunctional server definitions. Add schema coverage for admin/config parsing
and user-input websocket validation.
Concurrent tool calls that arrive on a cache miss were each issuing
their own jwt-bearer request to the IdP. Under that fan-out, Entra
intermittently returned errors that the retry classifier saw as
non-retryable, surfacing as:

  "The identity provider rejected the OBO token exchange.
   Cannot execute tool <name>. Re-authenticate the user or
   verify the configured OBO scopes and retry."

A user retry then hit the populated cache and succeeded, which matches
the observed flakiness — the cache was empty at the moment of fan-out
but populated by the time the user clicked retry.

- Coalesce concurrent exchanges in `OboTokenService.exchangeOboToken`
keyed by `${openidId}:${scopes}`. Callers that arrive while an exchange
is in flight share the same upstream request and receive the same
result. `fromCache=false` continues to force a fresh, independent
exchange (and is not joined by `fromCache=true` callers). The IdP
call, single-retry path, and cache write are unchanged — they were
moved into a `performOboExchange` helper so the coalescing wrapper
stays small.
- Tests cover: coalescing on the same key, isolation between different
keys, cleanup on success, cleanup on failure, and the
`fromCache=false` bypass.
OBO silently mints per-user delegated tokens from the caller's federated
access token and forwards them to whatever URL the server config points at.
Previously, anyone with MCP_SERVERS.CREATE could configure obo.scopes — so
if server creation is ever delegated beyond admins, a user could stand up
an attacker-controlled server, attach it to a shared agent, and exfiltrate
other users' downstream tokens on tool invocation.

Add a dedicated MCP_SERVERS.CONFIGURE_OBO permission (ADMIN: true, USER:
false by default) and enforce it at three layers so the safety property
no longer depends on CREATE staying admin-only:

- Create/update: POST/PATCH /api/mcp/servers returns 403 when the body
  carries `obo` and the caller's role lacks the permission.
- Runtime fail-closed: for DB-sourced configs, MCPConnectionFactory and
  MCPManager.callTool re-check the original author's role before each
  OBO exchange. If the author has been downgraded, the exchange is
  skipped (factory) or refused (callTool) — retained configs lose their
  privileges automatically.
- UI: the OBO option is hidden in the MCP server dialog for users
  without the permission; a CONFIGURE_OBO toggle is exposed in the MCP
  admin role editor.

Existing role docs receive the new sub-key via the permission backfill
in updateInterfacePermissions on next startup, preserving any
operator-set values. YAML/Config-sourced server configs are unaffected
since they're admin-controlled at the deployment level.
Resolved conflicts in:

- packages/data-provider/src/mcp.ts (kept OboOptionsSchema alongside main's
  OAuth audience validators; both `obo` and `proxy` on SSE/HTTP schemas)
- packages/data-provider/specs/mcp.spec.ts
- packages/api/src/mcp/utils.ts (kept both requiresUserScopedConnection
  and getMissingCustomUserVars)
- packages/api/src/mcp/__tests__/utils.test.ts
- packages/api/src/mcp/MCPManager.ts (merged util imports)
- api/server/controllers/mcp.js (kept OBO permission gate plus main's
  reservedServerNames lookup)
- api/server/routes/__tests__/mcp.spec.js

Integration fix: added oboTokenResolver and oboTrustChecker to the new
UserMCPConnectionOptions interface introduced by main.
The discovery and user-connection paths gated OAuth wiring (flow
manager, token methods, oboTokenResolver, oboTrustChecker) behind
isOAuthServer(), which only considers requiresOAuth/oauth fields.
A DB-stored OBO server with requiresOAuth: false therefore landed in
the non-OAuth branch, never received an oboTokenResolver, and the
factory's usesObo getter evaluated to false — sending a bare request
that the upstream rejected with invalid_token.

Add requiresOAuthMachinery() (OAuth OR OBO) and use it at those two
gates. isOAuthServer remains for the OAuth-handshake-only check
(shouldInitiateOAuthBeforeConnect), where OBO must not initiate a
handshake. Plumb the OBO resolver/trust-checker through
ToolDiscoveryOptions so reinitMCPServer can pass them on the
discovery path.
… CONFIGURE_OBO

The CONFIGURE_OBO permission was meant to gate control of the endpoint
that receives OBO-minted per-user delegated tokens and the scopes that
are requested. The previous frontend lock + backend gate only covered
obo.scopes and the auth section, leaving url/proxy/headers/etc. editable
by anyone with UPDATE — meaning a non-permission user could still
redirect an existing OBO server's token flow to an attacker endpoint.

Switch to an allowlist policy: when editing an OBO server without
CONFIGURE_OBO, only title/description/iconPath are mutable. Backend
rejects any other field change with 403; frontend disables the
non-allowlist sections (URL, transport, auth, trust) via fieldset.
The comparison surface (MCP_USER_INPUT_FIELDS) is derived from
MCPServerUserInputSchema's union members so it stays in sync with the
schema. New schema fields land in the locked set by default — adding to
the allowlist is the only way to unlock them, which preserves the
security-review boundary.
@jcbartle

Copy link
Copy Markdown
Contributor Author

This PR replaces / supersedes this PR: #12986

@danny-avila

Copy link
Copy Markdown
Owner

@codex review

@danny-avila

Copy link
Copy Markdown
Owner

Thanks! FYI there is a merge conflict

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7278ed7047

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/data-provider/src/mcp.ts
jcbartle added 2 commits May 31, 2026 18:54
MCPServerInspector.inspectServer() ran an unauthenticated temp connection
unless the config had requiresOAuth or customUserVars set. For OBO-only
servers without standard MCP OAuth advertisement, this caused
MCPConnectionFactory.create to attempt the connection without a user or
oboTokenResolver — failing on servers that reject the MCP initialize
handshake without a valid bearer token, which surfaced as
MCP_INSPECTION_FAILED on create/update.

Add `obo` to the skip list alongside requiresOAuth and customUserVars,
matching the existing pattern for user-scoped auth modes.
@jcbartle

Copy link
Copy Markdown
Contributor Author

@danny-avila - fixed merge conflict and addressed Codex's finding.

@danny-avila

Copy link
Copy Markdown
Owner

@codex review

@danny-avila danny-avila changed the title 🔒feat: Add On-Behalf-Of (OBO) token exchange support for MCP server connections (Cleaned) 🔒feat: Add On-Behalf-Of (OBO) token exchange support for MCP server connections Jun 1, 2026
@danny-avila

Copy link
Copy Markdown
Owner

@jcbartle ESLint failing

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Breezy!

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

…d (the auto-fill logic at line 156 uses getValues('title') instead). Deleted constant.
@jcbartle

jcbartle commented Jun 1, 2026

Copy link
Copy Markdown
Contributor Author

@jcbartle ESLint failing

@danny-avila - fixed pre-existing linting error.

@danny-avila danny-avila changed the base branch from main to dev June 2, 2026 02:35
@danny-avila danny-avila merged commit 268f095 into danny-avila:dev Jun 2, 2026
18 checks passed
fuuuzzy pushed a commit to fuuuzzy/LibreChat that referenced this pull request Jun 4, 2026
…danny-avila#13429)

* Add OBO (On-Behalf-Of) token exchange support for MCP server connections

Enables transparent authentication to Entra ID-backed MCP servers using the logged-in user's federated token via the OAuth 2.0 jwt-bearer grant. Configured via obo.scopes in librechat.yaml server config.

- Extract generic OboTokenService from GraphTokenService (jwt-bearer grant + cache)
- Refactor GraphTokenService to thin wrapper delegating to OboTokenService
- Add obo schema field to BaseOptionsSchema in data-provider
- Add resolveOboToken in packages/api/src/mcp/oauth/obo.ts (validates federated token, calls resolver, returns MCPOAuthTokens)
- Wire oboTokenResolver through MCPConnectionFactory, MCPManager, UserConnectionManager
- OBO tokens injected via request headers (not OAuth transport), refreshed on each tool call
- Explicit error on OBO failure (no fallthrough to standard OAuth redirect)
- Add unit tests for both resolveOboToken (9 tests) and exchangeOboToken (14 tests)

* Add OBO authentication option to MCP server UI configuration

  Enable users to configure On-Behalf-Of (OBO) token exchange for MCP servers created via the UI (MongoDB-stored), in addition to the existing YAML-based configuration.

  - Add "On-Behalf-Of (OBO)" radio option to MCP server auth section with scopes input field
  - Remove obo from omitServerManagedFields so the field passes UI schema validation
  - Add OBO to AuthTypeEnum, obo_scopes to AuthConfig, and OBO handling in form defaults and submission
  - Add .min(1) validation on obo.scopes to reject empty strings
  - Add English localization keys: com_ui_obo, com_ui_obo_scopes, com_ui_obo_scopes_description
  - Add 5 schema validation tests for OBO field acceptance, transport compatibility, and edge cases

* 🧊 fix: Add obo to safe properties in redactServerSecrets. Fixes the OBO configuration not showing up in the MCP UI after app restart

* Address linter errors

* 🧊 fix: fail closed on OBO refresh errors and retry transient token exchange failures

- stop tool calls from falling back to stale Authorization headers when per-call OBO refresh fails
- add one-time retry for transient Entra OBO exchange failures (network/429/5xx)
- preserve structured OBO failure reasons and retryability in resolveOboToken
- improve OBO auth error messaging for connection setup and tool execution
- add tests for transient vs permanent OBO failure paths

* Addressing linting errors / warnings

* 🧊 fix: isolate OBO MCP auth to user-scoped connections

- block OBO-enabled servers from app-level shared MCP connections
- bypass shared connection lookup for OBO servers in MCPManager.getConnection
- add regressions covering OBO connection scoping and preserve non-OBO app connection reuse

* 🛠️ refactor: centralize MCP user-scoped connection policy

- add shared requiresUserScopedConnection helper for OAuth, OBO, and customUserVars
- use the shared predicate in MCPManager and ConnectionsRepository
- add utils coverage for user-scoped connection policy

* 🧊 fix: restrict MCP OBO config to header-capable transports

- Move OBO configuration out of the shared MCP base options schema and allow it
only on SSE and streamable-http transports, where request headers are applied.
- Explicitly reject OBO on stdio and websocket configs to avoid accepted-but-
nonfunctional server definitions. Add schema coverage for admin/config parsing
and user-input websocket validation.

* 🧊 fix: single-flight concurrent OBO token exchanges

Concurrent tool calls that arrive on a cache miss were each issuing
their own jwt-bearer request to the IdP. Under that fan-out, Entra
intermittently returned errors that the retry classifier saw as
non-retryable, surfacing as:

  "The identity provider rejected the OBO token exchange.
   Cannot execute tool <name>. Re-authenticate the user or
   verify the configured OBO scopes and retry."

A user retry then hit the populated cache and succeeded, which matches
the observed flakiness — the cache was empty at the moment of fan-out
but populated by the time the user clicked retry.

- Coalesce concurrent exchanges in `OboTokenService.exchangeOboToken`
keyed by `${openidId}:${scopes}`. Callers that arrive while an exchange
is in flight share the same upstream request and receive the same
result. `fromCache=false` continues to force a fresh, independent
exchange (and is not joined by `fromCache=true` callers). The IdP
call, single-retry path, and cache write are unchanged — they were
moved into a `performOboExchange` helper so the coalescing wrapper
stays small.
- Tests cover: coalescing on the same key, isolation between different
keys, cleanup on success, cleanup on failure, and the
`fromCache=false` bypass.

* 🔒 feat: gate MCP OBO config behind MCP_SERVERS.CONFIGURE_OBO permission

OBO silently mints per-user delegated tokens from the caller's federated
access token and forwards them to whatever URL the server config points at.
Previously, anyone with MCP_SERVERS.CREATE could configure obo.scopes — so
if server creation is ever delegated beyond admins, a user could stand up
an attacker-controlled server, attach it to a shared agent, and exfiltrate
other users' downstream tokens on tool invocation.

Add a dedicated MCP_SERVERS.CONFIGURE_OBO permission (ADMIN: true, USER:
false by default) and enforce it at three layers so the safety property
no longer depends on CREATE staying admin-only:

- Create/update: POST/PATCH /api/mcp/servers returns 403 when the body
  carries `obo` and the caller's role lacks the permission.
- Runtime fail-closed: for DB-sourced configs, MCPConnectionFactory and
  MCPManager.callTool re-check the original author's role before each
  OBO exchange. If the author has been downgraded, the exchange is
  skipped (factory) or refused (callTool) — retained configs lose their
  privileges automatically.
- UI: the OBO option is hidden in the MCP server dialog for users
  without the permission; a CONFIGURE_OBO toggle is exposed in the MCP
  admin role editor.

Existing role docs receive the new sub-key via the permission backfill
in updateInterfacePermissions on next startup, preserving any
operator-set values. YAML/Config-sourced server configs are unaffected
since they're admin-controlled at the deployment level.

* 🧊 fix: wire OBO machinery for servers with requiresOAuth: false

The discovery and user-connection paths gated OAuth wiring (flow
manager, token methods, oboTokenResolver, oboTrustChecker) behind
isOAuthServer(), which only considers requiresOAuth/oauth fields.
A DB-stored OBO server with requiresOAuth: false therefore landed in
the non-OAuth branch, never received an oboTokenResolver, and the
factory's usesObo getter evaluated to false — sending a bare request
that the upstream rejected with invalid_token.

Add requiresOAuthMachinery() (OAuth OR OBO) and use it at those two
gates. isOAuthServer remains for the OAuth-handshake-only check
(shouldInitiateOAuthBeforeConnect), where OBO must not initiate a
handshake. Plumb the OBO resolver/trust-checker through
ToolDiscoveryOptions so reinitMCPServer can pass them on the
discovery path.

* 🧊 fix: lock all OBO-target fields (URL, proxy, headers, auth) without CONFIGURE_OBO

The CONFIGURE_OBO permission was meant to gate control of the endpoint
that receives OBO-minted per-user delegated tokens and the scopes that
are requested. The previous frontend lock + backend gate only covered
obo.scopes and the auth section, leaving url/proxy/headers/etc. editable
by anyone with UPDATE — meaning a non-permission user could still
redirect an existing OBO server's token flow to an attacker endpoint.

Switch to an allowlist policy: when editing an OBO server without
CONFIGURE_OBO, only title/description/iconPath are mutable. Backend
rejects any other field change with 403; frontend disables the
non-allowlist sections (URL, transport, auth, trust) via fieldset.
The comparison surface (MCP_USER_INPUT_FIELDS) is derived from
MCPServerUserInputSchema's union members so it stays in sync with the
schema. New schema fields land in the locked set by default — adding to
the allowlist is the only way to unlock them, which preserves the
security-review boundary.

* 🧊 fix: skip unauthenticated MCP inspection for OBO-only servers

MCPServerInspector.inspectServer() ran an unauthenticated temp connection
unless the config had requiresOAuth or customUserVars set. For OBO-only
servers without standard MCP OAuth advertisement, this caused
MCPConnectionFactory.create to attempt the connection without a user or
oboTokenResolver — failing on servers that reject the MCP initialize
handshake without a valid bearer token, which surfaced as
MCP_INSPECTION_FAILED on create/update.

Add `obo` to the skip list alongside requiresOAuth and customUserVars,
matching the existing pattern for user-scoped auth modes.

* Addressed linting error: watchedTitle is declared but never referenced (the auto-fill logic at line 156 uses getValues('title') instead). Deleted constant.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants