Skip to content

feat(jetbrains): add MCP OAuth sign-in - #14667

Merged
kirillk merged 24 commits into
mainfrom
lively-zephyr
Oct 7, 2026
Merged

kirillk merged 24 commits into
mainfrom
lively-zephyr

Conversation

@kirillk

@kirillk kirillk commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

What Problem This Solves

JetBrains users could install remote MCP servers that require OAuth, but Marketplace installation stopped at needs_auth without offering a login flow. The existing MCP Settings action also discarded the CLI's returned status, provided no cancellation or browser fallback, and could not configure servers that require a pre-registered OAuth client.

Two further problems were found while finishing this work:

  • The plugin decided "this failure really means sign-in required" by pattern-matching the CLI's human-readable MCP error text. Core has since consolidated MCP OAuth and reports that state structurally, so the plugin was duplicating classification rules it does not own and would silently drift from them.
  • Every localized bundle key added by this PR was copied verbatim from English, so non-English users saw raw English strings.

Why This Change Was Made

This uses the CLI's existing generic MCP OAuth endpoints and status model rather than adding provider-specific behavior. Authentication is coordinated in one JetBrains service and reused by Settings, Marketplace, and a reusable session-issues action menu in the prompt. OAuth client settings are carried through the split-mode RPC DTOs so editing a remote server does not silently remove its existing client configuration.

The follow-up work adopts the consolidated core contract that landed in feat: consolidate MCP OAuth in core, which is the same contract the VS Code extension migrated to. The plugin now reads structured fields instead of parsing prose, which keeps a single owner for the classification rules.

No issue is linked; this follows investigation of the Anaconda Marketplace MCP integration.

Changes:

  • Prompt users to sign in after installing an OAuth-protected MCP and expose authentication as an action in a generic session-issues menu that can host future recovery actions.
  • Preserve real authentication statuses and errors, support cancellation and credential reset, and show a copyable authorization URL when a browser cannot be opened.
  • Support automatic, disabled, and custom OAuth client configuration for any remote MCP server supported by the CLI authorization-code flow.
  • Take needs_auth straight from the CLI status and delete the local marker/regex classifier, so recoverable auth failures are identified in exactly one place.
  • Request external handling from auth/authenticate and open the authorization URL from the mcp.auth.url event, so the plugin owns the browser hand-off instead of the CLI opening a browser next to itself.
  • Cancel through auth/cancel, so abandoning or timing out an attempt no longer deletes credentials the user already had. DELETE /auth stays behind Reset sign-in, which is the only destructive path.
  • Report a missing server distinctly instead of as a generic failure.
  • Translate every new bundle key into all 18 locales.

User Impact

JetBrains users can complete OAuth sign-in without hand-editing configuration or manually discovering why an MCP server has no tools. Session-scoped problems now have a reusable prompt action menu that is hidden when there are no issues. Servers requiring a pre-registered client ID can be configured in the MCP editor. Local stdio servers and remote servers with oauth: false retain their existing behavior.

Cancelling a sign-in, or letting one time out, now leaves existing credentials intact; previously both deleted them, so a server that was already working could be knocked back into needs_auth. The authorization URL now opens through the IDE on the machine the user is sitting at rather than beside the CLI process.

Non-English users now see translated strings for the whole MCP sign-in flow.

The CLI's OAuth callback is still a loopback redirect served next to the CLI process, so a fully remote host/client split can still require forwarding that port; this PR does not change the redirect URI.

Screenshots / Video

Anaconda on marketplace

Screen Shot 2026-09-30 at 8 24 34 PM

Install details

Screen Shot 2026-09-30 at 8 24 54 PM

After install

Screen Shot 2026-09-30 at 8 25 21 PM

Sing in warning in chat

Screen Shot 2026-09-30 at 8 26 34 PM

Signed in notfication

Screen Shot 2026-09-30 at 8 27 08 PM

Anaconda MCP installed

Screen Shot 2026-09-30 at 8 27 40 PM

Removing MCP would remove skills as well as part of the marketplace unit

Screen Shot 2026-09-30 at 8 28 23 PM

Evidence

  • ./gradlew typecheck from packages/kilo-jetbrains/: passed.
  • ./gradlew test from packages/kilo-jetbrains/: passed, 4416 frontend tests plus the shared and backend suites.
  • Targeted suites after the refactor, all passed: KiloMcpAuthServiceTest, KiloAgentBehaviorServiceTest, McpSettingsUiTest, MarketplaceSettingsUiTest, KiloBundleLocaleTest, KiloCliDataParserTest, KiloAgentBehaviorRpcApiImplTest.
  • New coverage asserts the behavior that replaced the string parsing: the status is passed through verbatim, auth-shaped text on a failed status is never upgraded, authenticate sends external, auth/cancel is used for cancel and timeout, credentials survive both, and an external auth URL opens in the client browser with the dialog only as a fallback.
  • Verified the pinned CLI (v7.8.7) contains the consolidated endpoints before adopting them, so the plugin is not calling routes its bundled CLI lacks.
  • Localization verified programmatically: every key added by this PR is present in all 18 locale bundles with no unintended English copies and no unescaped MessageFormat apostrophes.
  • bun run script/check-opencode-annotations.ts --worktree and bun run script/check-md-table-padding.ts: passed.
  • git diff --check: passed.
  • git push: pre-push hooks passed the repository-wide TypeScript typechecks and the JetBrains typecheck. The first attempt failed on WorktreeManager.ts, a file this branch does not touch: the worktree had a stale simple-git 3.36.0 installed against the pinned 4.0.2. bun install resolved it, and the resulting one-line lockfile sync for the kilo-jetbrains workspace version is committed separately.
  • Changeset: .changeset/jetbrains-mcp-oauth-sign-in.md adds a minor JetBrains release note.

CI Flake Fixed Here

unit (linux, 2/2) failed on packages/opencode/test/kilocode/snapshot-freeze-repro.test.ts, which is unrelated to the MCP work: this branch changes no file under packages/opencode/src. It turned out to be a genuinely bad assertion rather than infrastructure noise, so it is fixed here instead of retried.

The test required a 25 ms setInterval to fire at least once during the diff, as a proxy for "the event loop stayed responsive". That workload now finishes in roughly 150 ms, so only a handful of 25 ms windows exist at all, and one ordinary synchronous chunk of diff work on a loaded runner can starve every one of them. CI recorded ticks=0 on a 289 ms run while the abort endpoint still answered in under 2 ms, so nothing was actually frozen. The assertion also became more fragile the faster diffFull got, which is backwards for a performance regression guard.

It now records when the timer actually runs and asserts that the longest stretch without a callback stays under two seconds, including the head and tail windows. That is a strictly sharper bound than the existing five-second total, because it catches a long contiguous stall even when the overall run finishes inside the budget.

Evidence it still guards the original bug: injecting a 2.5 s synchronous block makes it report a 2566 ms stall and fail. After the change it passed 10/10 consecutive runs on an idle machine.

Known Flakiness Not Addressed Here

KiloBackendWorkspaceTest and KiloSessionRpcApiImplTest fail a different subset on repeated local --rerun-tasks runs. This is a local environment artifact rather than a defect in this PR, and the JetBrains CI job passes:

  • The failure is always a startup assertion where the SSE connection reports an unexpected HTTP status, for example SSE: failure (HTTP 404) or Received HTTP_PROXY_AUTH (407) code while not using proxy.
  • MockCliServer binds an ephemeral port via ServerSocket(0). On a developer machine with a dense localhost port space, the backend's SSE reconnect can reach a port owned by an unrelated local process. One failing run named port 52532; lsof shows that port held by VS Code's Code Helper, and curl http://127.0.0.1:52532/global/event returns exactly the HTTP 404 seen in the log.
  • They fail the same way on the merge base with this PR's changes stashed.

Fixing it means reworking the mock server's port and lifecycle handling across the whole backend suite. That is unrelated to MCP auth and would not change any CI result, so it is deliberately left out of this PR rather than masked.

Dependency

None. The MCP OAuth endpoints and the mcp.auth.url event are already provided by the pinned CLI release the plugin bundles.

Expose remote MCP authentication across Settings, Marketplace, and the prompt toolbar so servers that require OAuth can be connected without hand-editing configuration. Preserve custom OAuth client settings and surface browser, timeout, and server errors to make sign-in recoverable.
@kilo-code-bot

kilo-code-bot Bot commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: No Issues Found | Recommendation: Merge

No new issues in the incremental diff a9bd0d42b5..dd7d971d76 (1 file, +12/-27). The only commit, dd7d971d76, cleanly reverts 216437d229 — an unrelated snapshot-freeze test change — and the test file now matches the branch-point version exactly (git diff 113caf1916..HEAD on this file is empty). main has since independently deflaked the same test (9cdc84a5b8, 524cde07d1), and those commits are not ancestors of HEAD, so a normal merge keeps main's stall-watchdog version rather than the reverted tick-count assertion. Retyping the PR back to its branch-point state does not regress main and correctly re-scopes this PR to the MCP OAuth work.

No memory-leak or resource findings in the changed code: the restored setInterval is still cleared in the finally block.

Files Reviewed (1 file)
  • packages/opencode/test/kilocode/snapshot-freeze-repro.test.ts

Fix these issues in Kilo Cloud

Previous Review Summaries (12 snapshots, latest commit a9bd0d4)

Current summary above is authoritative. Previous snapshots are kept for context only.

Previous review (commit a9bd0d4)

Status: No Issues Found | Recommendation: Merge

Incremental review of 216437d229..a9bd0d42b5 (23 files). This commit addresses prior MCP OAuth sign-in feedback and the changes hold up:

  • Non-web URL hardening: webUrl (new util/Url.kt) accepts only absolute http/https URIs with a host, and is applied in KiloMcpAuthService.onAuthUrl, McpAuthUrlDialog (Open withheld, Copy still allowed), and McpEditDialog.doValidate for redirect URIs. Rejecting file://, smb://, javascript: and hostless URLs is correct, and the authorization URL is no longer logged.
  • Secret handling: McpOAuthDto.toString() now redacts clientSecret; saveMcpOverride drops the secret from the display overlay and withSavedMcp re-reads it from authoritative config, which is the right trade-off. The edit dialog still prefills from the authoritative value, so explicit-null oauth serialization does not wipe a stored secret on a normal save.
  • Explicit-null oauth patch: correct given the config schema deep-merges on PATCH; only the MCP save path emits these keys, and values the dialog reports are authoritative.
  • Behavioral changes are scoped: refreshOnFocus defaulting to false restores the load-once behavior these pages had on main (the focus-refresh feature is new in this PR); MCP/Skills/Marketplace opt in explicitly. keepActions reverts the list default to the main active && isEnabled check and only MCP/Skills opt in.

No memory-leak regressions identified (the overlay now retains less, not more, and no new listeners/resources are held).

Files Reviewed (23 files)
  • packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/cli/KiloCliDataParser.kt
  • packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/rpc/KiloAgentBehaviorRpcApiImpl.kt
  • packages/kilo-jetbrains/backend/src/test/kotlin/ai/kilocode/backend/cli/KiloCliDataParserTest.kt
  • packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/app/KiloMcpAuthService.kt
  • packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/settings/agents/McpAuthUrlDialog.kt
  • packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/settings/agents/McpBundles.kt
  • packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/settings/agents/McpConfigurable.kt
  • packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/settings/agents/McpEditDialog.kt
  • packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/settings/agents/SkillsConfigurable.kt
  • packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/settings/base/SettingsListPanel.kt
  • packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/settings/marketplace/MarketplaceSettingsUi.kt
  • packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/ui/list/ActiveListModel.kt
  • packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/ui/list/ActiveListRenderer.kt
  • packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/util/Url.kt
  • packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/app/KiloMcpAuthServiceTest.kt
  • packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/settings/agents/McpAuthUrlDialogTest.kt
  • packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/settings/agents/McpEditDialogTest.kt
  • packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/settings/agents/McpSettingsUiTest.kt
  • packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/settings/agents/SkillsSettingsUiTest.kt
  • packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/settings/base/SettingsListViewTest.kt
  • packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/testing/FakeMarketplaceRpcApi.kt
  • packages/kilo-jetbrains/shared/src/main/kotlin/ai/kilocode/rpc/dto/KiloAppStateDto.kt
  • packages/kilo-jetbrains/shared/src/test/kotlin/ai/kilocode/rpc/dto/McpOAuthDtoTest.kt

Previous review (commit 216437d)

Status: No Issues Found | Recommendation: Merge

Incremental review of 86df0dc011..216437d229. The only branch-only change since the previous review is a test refactor that replaces the flaky tick-count assertion with a direct max-stall measurement. The stall computation correctly derives the longest gap across [start, ...marks, end], the interval is cleared in finally, and the new bound (< 2000ms) still catches the multi-second freeze the test guards against. No new issues.

Files Reviewed (1 file)
  • packages/opencode/test/kilocode/snapshot-freeze-repro.test.ts

Previous review (commit 86df0dc)

Status: No Issues Found | Recommendation: Merge

The branch merged origin/main after the previous review, so the raw 7643cb9f..HEAD range is dominated by unrelated upstream commits. This review covers the branch-only commits since 7643cb9f: the MCP-auth contract refactor, its locale translations, and the lockfile sync.

Files Reviewed (32 files)
  • packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/cli/KiloCliDataParser.kt - drops the client-side needs_auth classifier and reads the CLI status verbatim.
  • packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/rpc/KiloAgentBehaviorRpcApiImpl.kt - posts external: true to auth/authenticate, adds auth/cancel, subscribes to mcp.auth.url.
  • packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/app/KiloMcpAuthService.kt - cancel/timeout keep credentials, opens the URL client-side with a dialog fallback, adds not_found.
  • packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/app/KiloAgentBehaviorService.kt
  • packages/kilo-jetbrains/shared/src/main/kotlin/ai/kilocode/rpc/KiloAgentBehaviorRpcApi.kt
  • packages/kilo-jetbrains/shared/src/main/kotlin/ai/kilocode/rpc/dto/AgentBehaviorDto.kt
  • backend tests: KiloCliDataParserTest.kt, KiloAgentBehaviorRpcApiImplTest.kt, MockCliServer.kt
  • frontend tests: KiloMcpAuthServiceTest.kt, McpSettingsUiTest.kt, FakeAgentBehaviorRpcApi.kt
  • 18 KiloBundle_*.properties locale bundles
  • .changeset/jetbrains-mcp-oauth-sign-in.md, bun.lock

The classifier removal is safe: the CLI classifies HTTP 401/403 and OAuth failures centrally in packages/opencode/src/kilocode/mcp/auth-failure.ts at connect time and reports needs_auth structurally, and the new auth/cancel endpoint plus the external authenticate option exist on the CLI side, matching what the plugin now calls. Translating the bundles preserved every MessageFormat placeholder and dropped no keys.

Previous review (commit 7643cb9)

Status: No Issues Found | Recommendation: Merge

Files Reviewed (5 files, incremental since f01d37c)
  • packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/cli/KiloCliDataParser.kt - no issues; the HTTP-status auth marker now covers 401/403 but stays http/status-anchored with word boundaries, so unrelated digits (ports, IDs, 403 ms) are still not reclassified. Positive and negative cases both added.
  • packages/kilo-jetbrains/backend/src/test/kotlin/ai/kilocode/backend/cli/KiloCliDataParserTest.kt - no issues; covers SSE 403/401 as auth and 404/500 as unrelated.
  • packages/kilo-jetbrains/backend/src/test/kotlin/ai/kilocode/backend/rpc/KiloAgentBehaviorRpcApiImplTest.kt - no issues; asserts the SSE Non-200 status code (403) wire path normalizes to needs_auth while keeping the original error text.
  • packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/settings/base/SettingsListPanel.kt - no issues; the new SHOWING_CHANGED hierarchy listener consumes the first show (initial load is already owned by onReadyComponentCreated/DirectoryReadyConfigurable) and reloads only on re-show, gated by disposed and refreshOnFocus(). The listener is registered on the panel itself and captures no external scope, so it introduces no leak.
  • packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/settings/agents/McpSettingsUiTest.kt - no issues; drives a real hide/show transition and asserts a reload only on re-show.

Carried forward: none. The prior unresolved findings are on files outside this incremental diff.

Previous review (commit f01d37c)

Status: No Issues Found | Recommendation: Merge

Files Reviewed (2 files, incremental since a73754f)
  • packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/ui/list/ActiveListView.kt - previous warning resolved: the enabled = false hit test now keys blocking off whether the hit id is a real visible cell/region (activeListVisibleCells(...).map { it.id } + activeListRegions(item).keys) instead of a non-null per-cell action, so onCell-routed action cells (Sign In, Connect, Delete, rename) and the __menu__ glyph once again block row activation, while tooltip-only status pills still fall through.
  • packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/settings/base/SettingsListViewTest.kt - no issues; the new test exercises the fixed path with a real onCell-routed cell and asserts the row's open handler is not invoked.

Carried forward: none. The prior ActiveListView warning (onCell cells / menu glyph not swallowing activation) is fixed on the changed lines and the tooltip-only badge behavior is preserved, as covered by the existing badge test plus the new cell test.

Previous review (commit a73754f)

Status: 1 Issue Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 1
SUGGESTION 0
Issue Details (click to expand)

WARNING

File Line Issue
packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/ui/list/ActiveListView.kt 888 onCell-routed action cells and the row menu glyph have no per-cell action, so hit(e, enabled = false) now returns null for them and mouseClicked activates the row: double-clicking a non-primary cell fires the primary Edit cell, and a single click calls onOpen in lists that set it.
Files Reviewed (4 files, incremental since ad1568f)
  • packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/cli/KiloCliDataParser.kt - previous suggestion resolved (markers are now word/status anchored)
  • packages/kilo-jetbrains/backend/src/test/kotlin/ai/kilocode/backend/cli/KiloCliDataParserTest.kt - no issues
  • packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/ui/list/ActiveListView.kt - 1 issue
  • packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/settings/base/SettingsListViewTest.kt - no issues

Carried forward: none. The KiloCliDataParser bare-401/oauth suggestion is resolved by the anchored MCP_AUTH_HTTP_401/MCP_AUTH_FAILURE_WORD_MARKERS matchers and the new negative test. The McpConfigurable status-pill hit-test suggestion is resolved for tooltip-only badges, but the same ActiveListView branch over-narrows and drops onCell-routed action cells and the menu glyph, which is the new warning above.

Fix these issues in Kilo Cloud

Previous review (commit ad1568f)

Status: 2 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 0
SUGGESTION 2
Issue Details (click to expand)

SUGGESTION

File Line Issue
packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/cli/KiloCliDataParser.kt 143 The bare 401 marker is matched as a substring of the lowered error text, so a non-auth failed status that merely contains those digits is reclassified as needs_auth, offering a sign-in that cannot succeed.
packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/settings/agents/McpConfigurable.kt 171 Giving the status pill an id opts it into hit-testing. hit(e, enabled = false) reads activeListCellBounds, so double-clicking the pill returns status and the row's edit activation is skipped, while the id-less type pill still activates.
Files Reviewed (fix commit ad1568f, 35 files)
  • packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/marketplace/KiloBackendMarketplaceManager.kt - no new issues
  • packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/rpc/KiloMarketplaceRpcApiImpl.kt - no new issues
  • packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/app/KiloMarketplaceService.kt - no new issues
  • packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/settings/agents/McpConfigurable.kt - 1 carried-forward suggestion
  • packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/settings/agents/SkillsConfigurable.kt - no new issues
  • packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/ui/list/ActiveListModel.kt - no new issues
  • packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/ui/list/ActiveListRenderer.kt - no new issues
  • packages/kilo-jetbrains/shared/src/main/kotlin/ai/kilocode/rpc/KiloMarketplaceRpcApi.kt, .../rpc/dto/MarketplaceDto.kt - no new issues
  • KiloBundle*.properties locales and McpSettingsUiTest, SkillsSettingsUiTest, SettingsListViewTest, KiloMarketplaceRpcApiImplTest, KiloBundleLocaleTest, FakeMarketplaceRpcApi - no new issues

The fix commit was reviewed in full. The disposeReadyComponent super-call fix (line 68) is retained. The new bundle-uninstall path (shared .kilo-marketplace.json owner marker read in KiloBackendMarketplaceManager.bundles, scope-matched removal from both MCP and Skills Settings) and the selected && list.isEnabled action-cell visibility change introduce no new changed-code issues: bundle scope mapping (workspace→project) matches the Marketplace scope values, marker ids are validated against Paths.isSafeId-style rules plus UUID, and reads are size-bounded. Both suggestions above are carried forward and still reproduce on the current HEAD.

Fix these issues in Kilo Cloud

Previous review (commit 3e81774)

Status: 2 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 0
SUGGESTION 2
Issue Details (click to expand)

SUGGESTION

File Line Issue
packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/cli/KiloCliDataParser.kt 143 The new bare 401 marker (and the loose oauth marker) is matched as a substring, so a non-auth failed error that merely contains those digits is reclassified as needs_auth, offering a sign-in that cannot succeed.
packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/settings/agents/McpConfigurable.kt 164 The status badge id makes the pill an active mouse hit region, so clicks over the pill no longer fall through to the row's primary edit cell while the id-less type pill still does.
Files Reviewed (4 files, incremental since bbc1281)
  • packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/cli/KiloCliDataParser.kt - 1 suggestion (over-broad auth marker list)
  • packages/kilo-jetbrains/backend/src/test/kotlin/ai/kilocode/backend/cli/KiloCliDataParserTest.kt - no issues
  • packages/kilo-jetbrains/backend/src/test/kotlin/ai/kilocode/backend/rpc/KiloAgentBehaviorRpcApiImplTest.kt - no issues
  • packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/settings/agents/McpSettingsUiTest.kt - no issues

Carried forward after re-verification: the McpConfigurable status-pill hit-test suggestion (line 164) still applies; the disposeReadyComponent disposal warning is fixed (super is now called at line 66).

Fix these issues in Kilo Cloud

Previous review (commit bbc1281)

Status: 1 Issue Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 0
SUGGESTION 1
Issue Details (click to expand)

SUGGESTION

File Line Issue
packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/settings/agents/McpConfigurable.kt 164 The status badge id makes the status pill an active mouse hit region, so single/double clicks over the pill no longer fall through to the row's primary edit cell while the id-less type pill still does.
Files Reviewed (2 files, incremental since de1bcd1)
  • packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/settings/agents/McpConfigurable.kt - 1 suggestion (status badge hit id swallows activate-over-pill)
  • packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/settings/agents/McpSettingsUiTest.kt - no issues (new tooltip and hit-testing coverage)

Resolved since the previous review: the McpConfigurable.disposeReadyComponent override now calls super.disposeReadyComponent(component), so the retained panel is cleared and McpSettingsUi (a Disposable SettingsListPanel) is disposed instead of leaking.

Fix these issues in Kilo Cloud

Previous review (commit de1bcd1)

Status: No Issues Found | Recommendation: Merge

Files Reviewed (3 files)
  • packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/settings/agents/McpConfigurable.kt - previous disposal warning fixed (now delegates to DraftReadyConfigurableBase)
  • packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/settings/agents/McpSettingsUiTest.kt - new regression test, no issues
  • packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/testing/FakeAgentBehaviorRpcApi.kt - test gate, no issues

Resolved since the previous review: the McpConfigurable.disposeReadyComponent override now calls super.disposeReadyComponent(component), so the retained panel is cleared and McpSettingsUi (a Disposable SettingsListPanel) is disposed instead of leaking.

Previous review (commit d75cc82)

Status: 1 Issue Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 1
SUGGESTION 0
Issue Details (click to expand)

WARNING

File Line Issue
packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/settings/agents/McpConfigurable.kt 62 disposeReadyComponent overrides the base without calling super, so DraftReadyConfigurableBase never nulls its retained panel field and never disposes the MCP settings panel (McpSettingsUi is a SettingsListPanel, i.e. Disposable). The page stays strongly referenced until it is reopened, and SettingsListPanel.dispose() (job cancel, setBusy(false)) is skipped. Unchanged by the latest commit.
Files Reviewed (6 files)
  • packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/cli/KiloCliDataParser.kt — no new issues (failed + auth error is reclassified as needs_auth; new buildMcpOverlayPatch)
  • packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/rpc/KiloAgentBehaviorRpcApiImpl.kt — no new issues (saveMcp now writes both scopes through /config/overlay)
  • packages/kilo-jetbrains/backend/src/test/kotlin/ai/kilocode/backend/cli/KiloCliDataParserTest.kt — no new issues
  • packages/kilo-jetbrains/backend/src/test/kotlin/ai/kilocode/backend/rpc/KiloAgentBehaviorRpcApiImplTest.kt — no new issues
  • packages/kilo-jetbrains/backend/src/test/kotlin/ai/kilocode/backend/testing/MockCliServer.kt — no new issues
  • packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/settings/agents/McpConfigurable.kt — 1 carried-forward warning (line 62, unchanged)

Fix these issues in Kilo Cloud

Previous review (commit cf1b9d6)

Status: 1 Issue Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 1
SUGGESTION 0
Issue Details (click to expand)

WARNING

File Line Issue
packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/settings/agents/McpConfigurable.kt 62 The new disposeReadyComponent override does not call super, so DraftReadyConfigurableBase never disposes the MCP settings panel and keeps a strong reference to it after the settings dialog closes.
Files Reviewed (27 files, incremental since 776f81c)
  • packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/settings/agents/McpConfigurable.kt — 1 issue (missing super.disposeReadyComponent, retention of the settings panel)
  • packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/SessionUi.kt — no new issues (issue-tracking job still cancelled in dispose(); new openMcpSettings action)
  • packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/prompt/PromptPanel.kt — no new issues (issues slot aligned to top; provider-grouped actions)
  • packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/prompt/SessionIssue.kt — no new issues (SessionIssueAction model)
  • packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/settings/base/SettingsListPanel.kt — no new issues (filter(query) pre-populates the search)
  • packages/kilo-jetbrains/frontend/src/main/resources/messages/KiloBundle*.properties (19 bundles) — no new issues (prompt.mcp.provider, reworded prompt.mcp.openSettings; no translations lost)
  • packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/ui/PromptPanelTest.kt — no new issues (now drives the panel-created action)
  • `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode

[Snapshot truncated.]

Additional previous summary content was truncated to keep this comment within platform limits.


Reviewed by deepseek-v4.1-flash · Input: 44.2K · Output: 6.7K · Cached: 375.9K

Review guidance: REVIEW.md from base branch main

Keep Marketplace authentication progress visible, treat user cancellation as a quiet outcome, and bound cached MCP authentication state across Agent Manager worktrees.
@kirillk

kirillk commented Sep 29, 2026

Copy link
Copy Markdown
Contributor Author

Addressed all three review findings in commit 0c0a9e7f7d: Marketplace authentication progress is deferred until the install overlay finishes, active cancellation is treated as a quiet cancelled outcome, and directory authentication state is bounded to 64 entries. Added focused regressions and verified with ./gradlew typecheck plus the full ./gradlew test suite.

Reconnect remote MCP servers after clearing credentials so the runtime immediately reports needs_auth and the chat toolbar can offer sign-in before another tool call fails.
Move MCP sign-in into a reusable session issue action menu in the prompt input, keeping the control hidden when no session-scoped recovery actions are available.
Scope OAuth cancellation to individual attempts, keep reset state consistent through reconnect failures, cancel session issue tracking on disposal, and cover the prompt action wiring directly.
@kirillk

kirillk commented Sep 29, 2026

Copy link
Copy Markdown
Contributor Author

Addressed all five unresolved review findings in commit 776f81ca27: cancellation is scoped per sign-in attempt, reset quietly cancels active authorization and preserves needs_auth through reconnect failures, session issue tracking is cancelled on UI disposal, and the prompt test invokes the actual panel-created action. Verified with ./gradlew typecheck, focused regressions, and the full ./gradlew test suite.

Anchor the session issue trigger at the prompt top-right, group recovery actions by MCP provider without item icons or a popup header, and open MCP Settings with the affected provider pre-filtered.
Normalize legacy unauthorized MCP failures into sign-in-required state and use config overlay set/unset operations so installed MCP servers can be removed from Settings.
Delegate disposeReadyComponent to DraftReadyConfigurableBase so closing Settings releases the retained McpSettingsUi and cancels its in-flight reload.
Carry the runtime failure reason on the MCP status badge tooltip, since the row hides its description line, and log it for every unhealthy server so the cause is diagnosable from the IDE log.
The CLI only reports needs_auth for UnauthorizedError or an OAuth message, so a rejected browser flow, a failed token exchange, or a bare 401 arrived as failed and silently removed sign-in from MCP Settings, the Marketplace install prompt, and the session prompt. Reclassify those OAuth-specific failures as needing authentication so all three surfaces offer sign-in again.
# Conflicts:
#	packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/settings/marketplace/MarketplaceSettingsUiTest.kt
…tall

- MCP settings: the needs-auth status pill now carries the warning
  glyph used in the session issue indicator, and the row's action set
  matches the actual connection state. needs_auth shows only Sign In
  (no Connect, no Reset sign-in, since there is nothing to reset until
  a session exists). Reset sign-in only appears once a remote server
  is connected.
- Marketplace MCP + companion skill bundles can now be uninstalled
  consistently from either MCP Settings or Skills Settings: deleting
  either half confirms and removes the whole bundle (server, owned
  skills, and the ownership marker), instead of leaving an orphaned
  half installed. Ownership is read from a marker written next to the
  installed skill so removal still works after the catalog changes.
- Active list renderer: selected-row action cells (and disabled-cell
  hit tests) now stay visible/interactive while the list is selected
  but unfocused, instead of only when the list is the focus owner.
  This is a shared behavior change across every settings list built
  on ActiveList.

Adds/updates focused tests in McpSettingsUiTest, SkillsSettingsUiTest,
SettingsListViewTest, KiloMarketplaceRpcApiImplTest, and
KiloBundleLocaleTest, plus localized delete-confirmation strings.
…esting

- Anchor the 401/oauth auth-failure markers so an unrelated failure
  that merely embeds a port number, ID, or hostname containing those
  substrings is not misclassified as needs_auth. 401 now requires an
  http/status-prefixed match; oauth requires a word boundary.
- A tooltip-only badge (an id with no action, like the MCP status
  pill) no longer blocks single/double-click fall-through to the
  row's own click/open handler. Only regions that carry an actual
  action still swallow the click.

Addresses PR #14667 review threads on KiloCliDataParser.kt and
McpConfigurable.kt/ActiveListView.kt.
kirillk and others added 6 commits September 30, 2026 16:31
… tooltip badges

The prior fix (a73754f) keyed click fall-through on ActiveListHit.action,
but that field is only the per-cell handler and is null for every cell
routed through the panel-level onCell callback (Sign In, Connect, Delete,
rename, ...) and for the row menu glyph. That regressed double-clicking
any real onCell-routed action into also firing row activation.

hit(e, enabled = false) now blocks fall-through when the hit id belongs
to a real visible cell (activeListVisibleCells, including the menu glyph
when available) or a registered badge region (activeListRegions) —
independent of whether that cell/region carries its own action lambda.
A tooltip-only badge (id with neither an action nor an onCell binding)
still correctly falls through to row activation.

Added a regression test that double-clicks a non-primary onCell action
cell and asserts it fires the cell action without also invoking onOpen/
activate, per PR review feedback.

Addresses PR #14667 review thread on ActiveListView.kt.
Some remote MCP servers (e.g. anaconda) reject an unauthenticated SSE
connection with 403 rather than 401, reaching the plugin as a plain
"SSE error: Non-200 status code (403)" failure. The CLI only reports
needs_auth for UnauthorizedError or an OAuth-mentioning message, so this
fell through to failed and broke sign-in end to end: no post-install
sign-in prompt, and MCP Settings showed "failed" with no Sign In action
instead of the needs-auth badge.

Extend the anchored http/status marker regex from 401-only to 401 or
403 (\b(?:http|status)\D{0,10}40[13]\b), so both transports'
unauthenticated rejections normalize to needs_auth while still
rejecting unrelated failures that merely embed those digits (ports,
durations, server names, 404/500 statuses).

Adds parser-level coverage for the exact SSE 403 message plus the
existing 401/oauth false-positive guards, and a full-HTTP-path test
through KiloAgentBehaviorRpcApiImplTest/MockCliServer proving the CLI
response reaches the frontend DTO as needs_auth.
Add a refreshOnFocus toggle (default true) to SettingsListPanel so every
Agent Behavior list page (MCP, Skills, Commands, Agents) and Marketplace/
Rules settings automatically reload when the panel becomes visible again,
e.g. navigating away in Settings and back, or reopening the dialog.

Wired via a HierarchyListener on SHOWING_CHANGED: the initial mount is
ignored (directory-ready/initial load already handles that), and every
subsequent re-show triggers reload() when refreshOnFocus() is true. No
per-page changes needed since no subclass overrides the default.

Adds a regression test that attaches a panel to a host, hides and shows
it via removeNotify()/addNotify(), and asserts a reload happens only on
the re-show.
The MCP OAuth, marketplace sign-in, and bundled uninstall keys added in
this branch were copied verbatim from the English bundle into all 18
localized bundles, so non-English users saw raw English strings.

Translate every one of those keys per locale, preserving MessageFormat
placeholders and leaving product/technical tokens (MCP, OAuth,
Marketplace) untranslated.
…arsing

The plugin re-derived "sign-in required" from the CLI's human-readable MCP
error text, duplicating rules the CLI now owns. Core has since consolidated
the MCP OAuth flow and reports that state structurally, so the plugin can
read it instead of guessing.

Drop the local marker/regex classifier and take `needs_auth` straight from the
CLI status. Ask `auth/authenticate` for `external` handling and open the
authorization URL from the `mcp.auth.url` event, so the browser opens on the
user's machine rather than on the host in remote development. Cancel through
the new `auth/cancel` endpoint so abandoning or timing out an attempt no
longer deletes credentials the user already had; `DELETE /auth` stays behind
Reset sign-in. Report a missing server distinctly instead of as a generic
failure.
The CLI pin bump moved packages/kilo-jetbrains to 7.8.7 without updating the
lockfile's workspace entry, so bun install rewrites it on every fresh setup.

@brunoagatao brunoagatao left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

High (security): No http/https check before the browser opens the authorization URL.
KiloMcpAuthService.kt:223,230 and McpAuthUrlDialog.kt:43 call BrowserUtil.browse(url) on any mcp.auth.url. The URL comes from the remote server's authorization_endpoint, so the server controls it.
Repro: a server whose metadata gives file:///... or smb://... as the endpoint.
Fix: accept only http and https in onAuthUrl and in the dialog. Also apply it to the redirect URI check (McpEditDialog.kt:221 accepts javascript:x).

Medium:

  • A Marketplace failure stops the MCP and Skills pages from loading (McpConfigurable.kt:97, SkillsConfigurable.kt:122).
  • A cleared OAuth field stays in kilo.json (McpEditDialog.kt:130).
  • The client secret goes to the project kilo.json, stays in the backend cache, and McpOAuthDto.toString prints it.
  • The full authorization URL, with state, goes to the log (KiloMcpAuthService.kt:235).
  • Shared list behaviour changed for all lists (ActiveListRenderer.kt:361, SettingsListPanel.kt:84), and a test was edited to fit.

`pathological diffFull workload finishes quickly and does not block abort`
required a 25ms `setInterval` to fire at least once during the diff. The whole
workload now finishes in roughly 150ms, so only a handful of 25ms windows ever
exist, and a single ordinary synchronous chunk of diff work on a loaded CI box
can starve every one of them. CI saw `ticks=0` with the run taking 289ms while
the abort endpoint still answered in under 2ms, so the assertion failed without
anything being frozen. The assertion also got more fragile the faster diffFull
became, which is backwards for a performance regression guard.

Record when the timer actually runs and assert the longest stretch without a
callback stays under two seconds, including the head and tail windows. That is
a sharper bound than the existing five-second total, since it catches a long
contiguous stall even when the overall run lands inside the budget, and it no
longer depends on the work outliving one timer period.

Verified the bound still fails on a real freeze: injecting a 2.5s synchronous
block reports a 2566ms stall and fails the test.
Security: the authorization URL and the OAuth redirect URI both come from the
remote MCP server, and both reached a browser or the config file unchecked, so a
server could hand over `file:///…`, `smb://…`, or `javascript:…`. Add a parsed
http(s)-with-host check and apply it where the URL leaves our control: the auth
event is dropped before it can open or reach the fallback dialog, the dialog's
Open button is disabled and re-checks on click, and redirect URI validation no
longer accepts any absolute URI.

Also from review:

- Stop logging the authorization URL; it carries the OAuth `state` and other
  single-use parameters, and the server name identifies the flow on its own.
- Write cleared OAuth fields as explicit nulls. The config schema deep-merges on
  PATCH, so an omitted key kept the client id/secret/scope the user had just
  cleared, and it came back after a restart.
- Redact `clientSecret` in `McpOAuthDto.toString`, which otherwise printed it
  through RPC logging and exception messages, and keep it out of the backend's
  save overlay, reading it back from the authoritative config instead.
- Keep the MCP and Skills pages working when the Marketplace is unreachable.
  Bundle ownership only decides whether a delete offers to remove a whole
  install, so it now degrades to the plain delete confirmation instead of
  failing the page.
- Scope the two shared list changes behind opt-in flags so unrelated lists keep
  their previous behavior: `ActiveListConfig.keepActions` for showing a selected
  row's actions while focus is elsewhere, and `refreshOnFocus` now defaults to
  off. MCP, Skills, and Marketplace opt in. This restores the inverted
  `SettingsListViewTest` assertion rather than leaving the test bent to fit.
@kirillk

kirillk commented Oct 7, 2026

Copy link
Copy Markdown
Contributor Author

Addressed all six findings in a9bd0d42b5.

High (security): non-web authorization URLs. Added webUrl (frontend/.../client/util/Url.kt), which parses the value and requires http/https with a host, and applied it at all three places the URL leaves our control:

  • KiloMcpAuthService.onAuthUrl now drops a non-web URL before it can be opened or offered in the fallback dialog.
  • McpAuthUrlDialog disables Open for a non-web URL and re-checks on click; Copy stays enabled since copying is harmless.
  • McpEditDialog.doValidate no longer accepts any absolute URI. URI.isAbsolute was true for javascript:x and file:///x; the redirect target is a loopback HTTP listener, so only http(s)-with-host validates.

Verified the guard is load-bearing: with the onAuthUrl check removed, test non-web auth urls are dropped instead of opened fails.

Medium, all five:

  • Authorization URL in the log — removed. It carries the OAuth state and other single-use parameters; the server name already identifies which flow failed to open.
  • Cleared OAuth field surviving in kilo.json — the patch now emits explicit nulls for clientId/clientSecret/scope/callbackPort/redirectUri. The schema deep-merges on PATCH, so an omitted key kept the value the user had just cleared and it returned after a restart. The existing buildConfigPatch expectation was updated to the null-sentinel shape (same pattern already used for removed provider models).
  • Client secret exposure — McpOAuthDto.toString now redacts it (it reached the IDE log through RPC logging and exception messages), and the backend's save overlay no longer stores it: withSavedMcp reads it back from the authoritative config, so the edit dialog still prefills. Persistence in kilo.json itself is the CLI's config model and is already called out in the OAuth help text, so that part is unchanged.
  • Marketplace failure blocking MCP/Skills — both pages now load bundles through ownedBundles, which logs and degrades to an empty list. Bundle ownership only decides whether a delete offers to remove a whole Marketplace install, so an outage now falls back to the plain delete confirmation instead of failing the page.
  • Shared list behaviour + the bent test — both changes are now opt-in, so unrelated lists keep their previous behaviour. Added ActiveListConfig.keepActions (default off) for keeping a selected row's actions visible while focus is elsewhere, and refreshOnFocus now defaults to off; MCP, Skills, and Marketplace opt in. The inverted test in-place action cells are hidden on unfocused selected row assertion is restored to its original form, with a separate test covering the opt-in flag.

New coverage: non-web URL rejection in the service, Open enabled/disabled in the dialog, redirect-URI validation, explicit-null OAuth patch, toString redaction (new shared test), and both pages surviving a Marketplace outage.

Checks: ./gradlew typecheck and ./gradlew test (shared + backend + frontend, 4426 frontend tests) pass, plus check-opencode-annotations, check-md-table-padding, and check-kilocode-change.

… tick count"

This reverts commit 216437d.

That flaky-test fix is unrelated to this PR's MCP auth work and is being
handled in another PR instead.
@kirillk
kirillk requested a review from brunoagatao October 7, 2026 16:43
@kirillk
kirillk merged commit 8f271b1 into main Oct 7, 2026
34 checks passed
@kirillk
kirillk deleted the lively-zephyr branch October 7, 2026 18:43
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.

3 participants