✨ feat(connector): custom OAuth MCP connectors — onboarding, runtime execution & connector-first (LOBE-9983)#15546
Merged
Merged
Conversation
…BE-9983) Connect the two OIDC schemes designed in LOBE-9736 (oidcConfig) end-to-end so users can add a custom OAuth MCP server from /settings/skill. Until now the DB schema, models, and tool-permission UI existed, but nothing ran the OAuth authorization flow — syncTools only worked when a token already existed. Flow (shared pipeline, branches only on where client_id comes from): - Add modal (client_id present → Pre-registration; absent → DCR/RFC 7591) - startOAuth: probe MCP URL → RFC 9728 protected-resource metadata → RFC 8414 AS metadata; DCR-register the client when no client_id; persist resolved oidcConfig; build PKCE authorize URL, stash verifier in Redis keyed by state - /oauth/connector/callback: consume state → exchange code → store encrypted tokens (KeyVaultsGateKeeper) + tokenExpiresAt + status=connected → postMessage - syncTools lazily refreshes the access token before connecting Built on @modelcontextprotocol/sdk OAuth helpers (discover/register/start/ exchange/refresh) — no hand-rolled protocol code. Security: - Wire KeyVaultsGateKeeper into ConnectorModel so OAuth tokens are encrypted at rest (previously the router passed no gatekeeper → plaintext) - Strip decrypted credentials and oidcConfig.clientSecret from the list response UI: - "+" button in /settings/skill Connectors tab opens the Add modal - SkillList surfaces custom connectors from the connector store - Modal wires the client secret field, infers the scheme, and shows the redirect URI to register Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The authorize request sent an empty scope list, so providers that require a scope (e.g. Linear MCP advertises scopes_supported ["read","write"]) issued a useless token or rejected the flow. Default to the authorization server's advertised scopes_supported when the user did not specify any, and use them for both DCR registration and the authorize request. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
/oauth/connector/callback is a backend route handler reached via a cross-site redirect from the OAuth provider, so the proxy middleware broke it two ways: 1. It was not in the backend passthrough list, so it got rewritten to the SPA / locale shell instead of running the route handler (307 → blank). 2. It was not in isPublicRoute, so BetterAuth treated it as protected; the cross-site top-level navigation doesn't reliably carry the SameSite session cookie, so it redirected to sign-in (307). Add /oauth/connector to backendApiEndpoints and /oauth/connector/callback to isPublicRoute (the handler validates its own single-use state, so it must not be session-gated). Scoped so /oauth/callback/success|error SPA pages are unaffected. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…e wiring Make custom OAuth MCP connectors actually callable, and sync their tools as soon as authorization completes. - callback: after token exchange, sync the tool list server-side via a shared syncConnectorToolsById — the connector is usable without a client round-trip - sync.ts: extract buildConnectorMcpParams (http+auth / stdio), shared by syncTools and the new callTool - connector router: add `callTool` (resolve connector, hard-block disabled tools, refresh token, call the remote MCP with decrypted credentials) - aiAgent runtime: pass a KeyVaultsGateKeeper when resolving connectors so OAuth tokens decrypt (otherwise tool calls 401); surface connectors in the agent-management availablePlugins as a new 'connector' type - AgentManagementContextInjector: render a <connector_plugins> section Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The front-end chat orchestrates tools client-side (via /webapi/chat proxy), separate from the server agent runtime. Connectors were invisible and unexecutable there. Wire them in, connector-first. - toolEngineering: build connector manifests from the store and inject them into createToolsEngine; drop plugins sharing a connector identifier (connector wins) - buildClientConnectorManifests: store rows → type 'mcp' manifests (no token; the client has none) with permission → humanIntervention mapping - mcpService.invokeMcpToolCall: route connector tool calls to connector.callTool before the plugin path (only connectors with a real MCP endpoint, so Lobehub/Klavis skills keep their executor) - DeferredStoreInitialization: fetch connectors post-login so chat sees them - AddConnectorModal: refresh after OAuth regardless of popup outcome - chat-input skills picker: surface custom connectors in the auto group Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ML (codex P1) - AddConnectorModal: open the OAuth popup synchronously inside the click handler (before any await), then navigate it to the authorize URL. Browsers block window.open once an async boundary is crossed, which left popup=null and the poll loop never resolving — the Add modal hung. Null popup now fails fast with a "allow popups" message. - callback route: escape the postMessage payload for `<script>` context (`<`, `>`, `&`, U+2028/U+2029 → \uXXXX). A malicious OAuth server could put `</script>...` in the error param and execute script on the app origin. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Contributor
There was a problem hiding this comment.
Sorry @ONLY-yours, you have reached your weekly rate limit of 500000 diff characters.
Please try again later or upgrade to continue using Sourcery
2 tasks
… + tests Address review: enforce the same constraints at the call site that the manifest layer enforces, and stop swallowing OAuth failures. - isEnabled on BOTH sides: invokeMcpToolCall only routes enabled connectors (a disabled connector no longer steals a same-name plugin's call), and the server rejects calls to a disabled connector. Matches buildClientConnectorManifests which only exposes enabled connectors. - callTool requires the toolName to exist in the synced user_connector_tools list — unsynced / hand-crafted tool names are rejected instead of being forwarded blindly to the remote MCP. - extract callConnectorToolById (typed ConnectorToolCallError → tRPC codes) so the gates are unit-testable. - AddConnectorModal: distinguish success / provider-error (show the reason) / user-dismissed instead of collapsing every failure into a silent close. - tests: exec gates (not-found / disabled connector / unknown tool / disabled tool / success / token-refresh) + buildClientConnectorManifests mapping. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## canary #15546 +/- ##
===========================================
+ Coverage 70.64% 89.15% +18.51%
===========================================
Files 3274 892 -2382
Lines 322959 108288 -214671
Branches 29419 10611 -18808
===========================================
- Hits 228155 96545 -131610
+ Misses 94621 11560 -83061
Partials 183 183
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
…ilure UX Second review round. - redirect URI: the modal showed a client-origin URI while the server sent an APP_URL one — register-vs-use mismatch broke the callback. Add a `connector.getRedirectUri` query (server source of truth) and show exactly that in the modal. - execAgent: derive the plugin-override set from the connectors that ACTUALLY produce a manifest (enabled + with tools), not the raw endpoint-having set — a disabled / not-yet-synced same-named connector no longer evicts the plugin and leaves the runtime with no tools. Matches the client-chat behaviour. - partial failure: when code exchange succeeds but the tool sync fails, the callback now reports `synced: false`; the modal shows "authorized but tools could not be synced" instead of a false "connected". Tests: execAgent overlap regression (disabled / 0-tool keeps the plugin; real tools replace it) + callback partial-failure (synced:false on sync error). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… 'connector' The agent-management availablePlugins types describe a tool's SOURCE (builtin / klavis / lobehub-skill); 'connector' named the storage system instead. Once plugins migrate to the connector table everything is a connector, so the source-based label is what matters. Rename to 'custom' to align with ConnectorSourceType.custom (single source of truth); section is <custom_plugins>. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…gins Community MCPs execute via the plugin path (not connector.callTool), so the per-tool permissions a user sets in the new Connectors UI weren't surfaced: needs_approval didn't trigger the approval prompt on either runtime. (disabled was already hard-blocked at execution by ToolExecutionService and the mcp router.) - extract patchManifestWithPermissions into a pure, client-safe module (patchManifestPermissions.ts); connectorPermissionCheck.ts re-exports it. - execAgent: also patch community-plugin manifests (pluginsWithoutConnectors) with their connector permissions, alongside lobehub/klavis. - client createToolsEngine: patch community-plugin manifests with connector permissions from the store so needs_approval surfaces as humanIntervention in the classic chat path too. - unit tests for the shared patch function. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
createToolsEngine now reads connectorSelectors.{customConnectors,connectorList};
toolEngineering/index.test.ts mocks getToolStoreState without `connectors`, so
the selectors hit `undefined.filter`. Guard with `?? []` (the real store always
seeds connectors:[] via initialState) and add connectors:[] to the test mock.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ized slice mcp.test.ts mocks the tool store without `connectors`, and invokeMcpToolCall calls connectorByIdentifier → `s.connectors.find` threw. The previous fix only guarded connectorList/customConnectors; harden all of them (find/filter) so any partial-store mock is safe. The real store always seeds connectors:[]. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ONLY-yours
added a commit
that referenced
this pull request
Jun 9, 2026
Reconcile the klavis→composio migration with the new Connectors system (custom OAuth MCP connectors) that landed on canary via #15546. Conflict resolutions (keep connector additions + apply composio rename): - AgentManagementContextInjector: plugin type union → 'builtin' | 'composio' | 'lobehub-skill' | 'custom' - contextEngineering: keep COMPOSIO_APP_TYPES + canary's isDesktop import - useControls / SkillList: keep composioStore + connectorSelectors; drop renamed klavisStore; community OAuth connectors live only in the Connectors view (canary dedup, #15510) - aiAgent: keep both ComposioService and deviceGateway imports Follow-on fixes for the now-merged tree: - execAgent.connectorOverlap.test: mock @/server/services/composio (klavis service removed) - finish incomplete rename in some consumers: servers→composioServers, serverName→label; composio.createConnection now returns authConfigId for the optimistic store entry
Closed
This was referenced Jun 10, 2026
Closed
Closed
Merged
arvinxx
added a commit
that referenced
this pull request
Jun 10, 2026
# 🚀 LobeHub Release (20260610) **Release Date:** June 10, 2026 **Since v2.2.2:** 131 merged PRs · 13 contributors > This weekly release strengthens agent collaboration across cloud, desktop, CLI, and workspace flows, with steadier runtime behavior and a broader foundation for workspace-scoped data. --- ## ✨ Highlights - **Agent execution across devices** — Unifies per-device working directories, project skill discovery, and sub-agent suspend/resume behavior across server, QStash, and device RPC flows. (#15543, #15566, #15481, #15620, #15591) - **Connector and sandbox platform** — Expands connector permissions, custom OAuth MCP connector onboarding, sandbox provider support, and user-uploaded file sync into cloud sandbox runs. (#15463, #15546, #15184, #15550) - **Desktop and CLI reliability** — Fixes desktop cold-start, auto-update, Windows build, CLI skill discovery, and `lh connect` agent dispatch paths. (#15547, #15525, #15527, #15562, #15632, #15634) - **Pages and sharing** — Refreshes topic sharing, improves Page Editor layout behavior, and routes Page Agent tool execution through the server-side editor path. (#15581, #15556, #15588, #15023, #15610) - **Model availability and provider updates** — Adds user-scoped LobeHub model availability, Claude Fable 5, Qwen thinking preservation, and MiniMax M3 updates. (#15590, #15639, #13494, #15376) --- ## 🏗️ Core Product & Architecture ### Agent Runtime & Heterogeneous Agents - Improves sub-agent lifecycle handling, including async suspend/resume, queue-mode QStash resume delivery, and blocking nested sub-agent calls. (#15481, #15620, #15575) - Stabilizes heterogeneous agent ingestion and streaming with raw stream dumps, per-turn usage, image forwarding on regenerate, and duplicate-text fixes. (#15602, #15577, #15592, #15585) - Adds execution-device and working-directory controls across device RPC, legacy defaults, and remote-spawned Claude Code sessions. (#15543, #15566, #15591, #15572) - Improves runtime diagnostics and compatibility, including Gemini multimodal output capture, abort stream semantics, and trace quality analysis. (#15535, #13677, #15508) --- ## 📱 Platforms, Integrations & UX ### Connectors, Sandbox & Tools - Ships API-level connector tool permissions, custom OAuth MCP connector onboarding, and connector-first runtime execution. (#15463, #15546) - Adds sandbox provider support, cloud sandbox file sync, and safer external URL file input handling with SSRF validation. (#15184, #15550, #12657) - Improves tool visibility and execution with pinned app-fixed tools, ANSI output rendering, gateway-tunneled MCP calls, and automatic headless tool runs. (#15509, #15516, #15469, #15492) ### Desktop, CLI & Web UX - Restores desktop startup and reload behavior, preserves IPC error causes, and keeps the tab bar new-tab action visible across routes. (#15547, #15597, #15638) - Fixes desktop update and build stability for browser quit guards, macOS update signing, and Windows Visual Studio detection. (#15525, #15527, #15562) - Shows the plan-limit upgrade UI on desktop builds. (#15628) - Adds the Agent Run delivery checker and fixes CLI device dispatch plus skill list/search output. (#15489, #15634, #15632) - Refreshes onboarding, auth source preservation, topic UI states, referral/Fable campaign copy, and chat-input control bar behavior. (#15629, #15544, #15573, #15614, #15616, #15617, #15622, #15643) --- ## 🔒 Security, Reliability & Rollout Notes - External URL file input now includes SSRF validation for safer Google file handling. (#12657) - Database workspace-scope migrations are part of this release; self-hosted operators should run the normal migration path before serving the updated app. (#15446, #15465, #15468, #15472) - The release branch was re-cut from `canary` and includes the latest `main` release-version commit so `v2.2.2` is the verified compare base. --- ## 👥 Contributors @ONLY-yours, @sxjeru, @hardy-one, @xujingli, @hezhijie0327, @Coooolfan, @arvinxx, @tjx666, @Innei, @rivertwilight, @rdmclin2, @cy948, @AmAzing129 **Full Changelog**: v2.2.2...release/weekly-20260610-recut-3
Open
4 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
💻 Change Type
🔗 Related Issue
LOBE-9983 (Connectors system). Builds on the
oidcConfigdesign from LOBE-9736 and the Connectors foundation in #15463.🔀 Description of Change
#15463 shipped the Connectors data layer + tool-permission UI, but custom OAuth MCP connectors were not usable end-to-end: there was no OAuth flow, and the connector tools were never injected/executed at runtime. This PR completes the feature.
1. OAuth onboarding (Pre-registration + DCR)
+button in/settings/skillopens the Add modal.connector.startOAuth: probe MCP URL → RFC 9728 protected-resource metadata → RFC 8414 AS metadata; DCR (RFC 7591) dynamic registration when noclient_id, otherwise pre-registration; persist resolvedoidcConfig; build a PKCE authorize URL with the state stashed single-use in Redis./oauth/connector/callback: consume state → exchange code → store encrypted tokens (KeyVaultsGateKeeper) → sync the tool list server-side → report back to the opener.scopes_supportedwhen the user gives no scope.@modelcontextprotocol/sdkOAuth helpers (discover / register / start / exchange / refresh).2. Runtime execution — connector-first
execAgent): resolve connectors with aKeyVaultsGateKeeperso OAuth tokens decrypt (otherwise tool calls 401); surface connectors in the agent-managementavailablePlugins(<connector_plugins>)./webapi/chatis a proxy; tools are assembled client-side): inject connector manifests increateToolsEngine, drop plugins sharing a connector identifier (connector wins), and route connector tool calls to a newconnector.callTooltRPC (resolve → hard-block disabled tools → refresh token → call the remote MCP with decrypted creds). LobeHub/Klavis skills keep their existing executor.3. Security / correctness
credentialsandoidcConfig.clientSecretfrom thelistresponse.<script>context (</script>/ U+2028-9 breakout) — codex P1./oauth/connector/callbackbypass the SPA-rewrite + auth gate (it's a cross-site backend callback).🧪 How to Test
/settings/skill→ Connectors →+→ Name + Remote MCP server URL. Empty Client ID → DCR; filled → pre-registration (register the shown redirect URI).connector.callToolwith the stored token; disabled tools are hard-blocked.📝 Additional Information
clientSecretis a new optional field inside the existingoidc_configjsonb column.clientSecretis stored plaintext inoidcConfigby design; access/refresh tokens are encrypted incredentials./oauth/connectorin the middlewarebackendApiEndpoints+isPublicRouteallowlists.