Skip to content

fix(plugin): stop MCP servers on quit - #1793

Merged
zerob13 merged 2 commits into
devfrom
codex/plugin-shutdown-lifecycle
Jun 20, 2026
Merged

fix(plugin): stop MCP servers on quit#1793
zerob13 merged 2 commits into
devfrom
codex/plugin-shutdown-lifecycle

Conversation

@zerob13

@zerob13 zerob13 commented Jun 20, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • add a plugin shutdown lifecycle before presenter teardown
  • stop plugin-owned MCP servers on app quit without disabling plugins or deleting config
  • make MCP stdio cleanup await transport close and terminate spawned process trees for helpers such as CUA driver

Tests

  • pnpm run format
  • pnpm run i18n
  • pnpm run lint
  • pnpm run typecheck
  • pnpm exec vitest run test/main/presenter/mcpPresenter.test.ts test/main/presenter/mcpClient.test.ts
  • pnpm exec vitest run test/main/presenter/pluginPresenter.test.ts -t "shuts down running plugin-owned MCP servers|continues plugin shutdown"

Summary by CodeRabbit

Release Notes

  • Bug Fixes

    • Improved application shutdown to reliably stop plugin-owned helper connections and terminate related spawned processes.
    • Preserved saved MCP server configuration and plugin/install state across restarts.
  • Improvements

    • Made disconnect/shutdown cleanup more robust by waiting for transport cleanup and closing process trees when available.
  • Tests

    • Added unit tests covering plugin and MCP shutdown behavior, including failure handling and cleanup order.

@coderabbitai

coderabbitai Bot commented Jun 20, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 5be5c327-615d-4c85-b313-e8592650a184

📥 Commits

Reviewing files that changed from the base of the PR and between b303af6 and aecec80.

📒 Files selected for processing (4)
  • src/main/presenter/index.ts
  • src/main/presenter/mcpPresenter/mcpClient.ts
  • test/main/presenter/mcpClient.test.ts
  • test/main/presenter/pluginPresenter.test.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/main/presenter/index.ts
  • test/main/presenter/pluginPresenter.test.ts
  • test/main/presenter/mcpClient.test.ts
  • src/main/presenter/mcpPresenter/mcpClient.ts

📝 Walkthrough

Walkthrough

Adds a plugin shutdown lifecycle to DeepChat's main process: PluginPresenter.shutdown() stops running plugin-owned MCP servers, unregisters plugin tool policies, and closes settings windows; McpPresenter.shutdown() stops any remaining running clients; MCP client cleanupResources becomes async with process-tree termination for stdio transports; Presenter.destroy() runs both new shutdown steps first; spec, plan, and tasks documents define the design and acceptance criteria.

Changes

Plugin Shutdown Lifecycle

Layer / File(s) Summary
IMCPPresenter interface and design docs
src/shared/types/presenters/core.presenter.d.ts, docs/issues/plugin-shutdown-lifecycle/spec.md, docs/issues/plugin-shutdown-lifecycle/plan.md, docs/issues/plugin-shutdown-lifecycle/tasks.md
IMCPPresenter gains shutdown(): Promise<void>. Spec, plan, and tasks documents define the acceptance criteria, ownership model, stdio cleanup semantics, presenter ordering, and compatibility requirements.
Async MCP client transport cleanup with process-tree termination
src/main/presenter/mcpPresenter/mcpClient.ts, test/main/presenter/mcpClient.test.ts
Imports ChildProcess and terminateProcessTree. Adds StdioClientTransportProcessAccess type and closeTransport helper that terminates the stdio child-process tree before calling transport.close(). Converts cleanupResources to async and updates call sites in connect(), session-error, and disconnect paths to await it. Tests verify ordered execution (process-tree before transport close) and failure resilience.
McpPresenter.shutdown with running client enumeration
src/main/presenter/mcpPresenter/index.ts, test/main/presenter/mcpPresenter.test.ts
Implements McpPresenter.shutdown() to fetch running clients via serverManager.getRunningClients and attempt to stop each via stopServer, isolating individual failures with try/catch logging. Test verifies all clients are targeted despite partial failure.
PluginPresenter.shutdown and ownership model
src/main/presenter/pluginPresenter/index.ts, test/main/presenter/pluginPresenter.test.ts
Implements PluginPresenter.shutdown() to collect installed plugins, identify and stop running plugin-owned MCP servers, unregister plugin tool policies, and close settings windows. Adds private helpers isPluginOwnedServerConfig, isServerOwnedByPlugin, getServerOwnerPluginId, and closeAllPluginSettingsWindows; refactors disableByOwner() to use isServerOwnedByPlugin. Tests verify server targeting is limited to running instances, saved MCP config is never removed, and failures do not abort remaining stops.
Presenter.destroy() shutdown sequencing
src/main/presenter/index.ts
Inserts await pluginPresenter.shutdown() and await mcpPresenter.shutdown() at the head of Presenter.destroy() before destroyRemoteControl(), each with separate try/catch and error logging.

Sequence Diagram(s)

sequenceDiagram
  participant Presenter
  participant PluginPresenter
  participant McpPresenter
  participant McpClient
  participant terminateProcessTree

  rect rgba(99, 179, 237, 0.5)
    note over Presenter,PluginPresenter: Phase 1 – Plugin-scoped cleanup
    Presenter->>PluginPresenter: await shutdown()
    PluginPresenter->>McpPresenter: stopServer(plugin-owned running server)
    McpPresenter->>McpClient: disconnect()
    McpClient->>terminateProcessTree: await terminateProcessTree(_process, {graceMs:2000})
    terminateProcessTree-->>McpClient: done
    McpClient->>McpClient: await transport.close()
    McpClient-->>McpPresenter: stopped
    McpPresenter-->>PluginPresenter: done
    PluginPresenter->>PluginPresenter: unregisterPluginToolPolicies()
    PluginPresenter->>PluginPresenter: closeAllPluginSettingsWindows()
    PluginPresenter-->>Presenter: done
  end

  rect rgba(154, 205, 50, 0.5)
    note over Presenter,McpPresenter: Phase 2 – Global MCP cleanup
    Presenter->>McpPresenter: await shutdown()
    McpPresenter->>McpClient: stopServer(remaining running clients)
    McpClient-->>McpPresenter: stopped (errors logged, not thrown)
    McpPresenter-->>Presenter: done
  end

  Presenter->>Presenter: destroyRemoteControl() + rest of teardown
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐇 Hoppity-hop, time to say good night,
Plugin servers stop in order, clean and right.
Process trees trimmed, transports closed with care,
No orphaned helpers left out in the air.
Shutdown flows swift—no configs erased,
Next launch finds everything right back in place! 🌙

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: implementing plugin shutdown to stop MCP servers when the application quits.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/plugin-shutdown-lifecycle

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/main/presenter/index.ts`:
- Around line 896-897: The destroy() method's calls to
this.pluginPresenter.shutdown() and this.mcpPresenter.shutdown() will abort the
entire destroy process if either throws an error, preventing subsequent cleanup
steps from executing. Wrap each of these two shutdown calls in separate
try/catch blocks to isolate failures, logging any caught errors appropriately
while allowing the method to continue and complete all remaining cleanup.
Maintain the existing order with pluginPresenter shutdown first, followed by
mcpPresenter shutdown.

In `@src/main/presenter/mcpPresenter/mcpClient.ts`:
- Around line 551-560: In the closeTransport method, if terminateProcessTree
throws an error, the subsequent transport.close() call will not execute, leaving
transport resources open. Wrap the process termination logic (the
StdioClientTransport check and terminateProcessTree call) in a try-catch block
or use a try-finally structure to ensure that transport.close() is always
invoked regardless of whether the process tree termination succeeds or fails.

In `@test/main/presenter/pluginPresenter.test.ts`:
- Around line 930-951: The test sets up a spy on console.warn at the beginning
but never asserts that it was called, which means any regression in the failure
logging path would not be caught. Since the test mocks mcpPresenter.stopServer
to reject with an error on the first call (simulating a failure), add an expect
assertion after the presenter.shutdown() call to verify that consoleWarnSpy was
called, ensuring the warning is actually logged when the shutdown process
encounters the failure.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9dcaa214-6d67-40e3-aedf-9b2d6cc09658

📥 Commits

Reviewing files that changed from the base of the PR and between f171c4f and b303af6.

📒 Files selected for processing (11)
  • docs/issues/plugin-shutdown-lifecycle/plan.md
  • docs/issues/plugin-shutdown-lifecycle/spec.md
  • docs/issues/plugin-shutdown-lifecycle/tasks.md
  • src/main/presenter/index.ts
  • src/main/presenter/mcpPresenter/index.ts
  • src/main/presenter/mcpPresenter/mcpClient.ts
  • src/main/presenter/pluginPresenter/index.ts
  • src/shared/types/presenters/core.presenter.d.ts
  • test/main/presenter/mcpClient.test.ts
  • test/main/presenter/mcpPresenter.test.ts
  • test/main/presenter/pluginPresenter.test.ts

Comment thread src/main/presenter/index.ts Outdated
Comment thread src/main/presenter/mcpPresenter/mcpClient.ts
Comment thread test/main/presenter/pluginPresenter.test.ts
@zerob13
zerob13 merged commit b5a26d8 into dev Jun 20, 2026
3 checks passed
@zhangmo8
zhangmo8 deleted the codex/plugin-shutdown-lifecycle branch June 22, 2026 06:16
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.

1 participant