Skip to content

Conversation

@hendem
Copy link

@hendem hendem commented Jan 9, 2026

Relates to #3013, #7261

Summary

  • Track local MCP server transports to enable forceful process termination
  • Add forceKillProcess() helper for cross-platform process termination
  • Force kill remaining MCP processes on dispose after graceful close timeout
  • Check if process is alive before attempting force kill

Problem

The MCP SDK's client.close() uses AbortController.abort() which may not work reliably for:

  • Docker containers without --init flag
  • Hung or unresponsive processes
  • Processes that don't handle signals properly

Solution

Defensive fallback that only triggers if graceful close doesn't work:

  1. Try graceful client.close() with 2 second timeout
  2. Check if process is still alive
  3. Force kill only if still running

Testing

Added tests in test/mcp/process-cleanup.test.ts.

Copilot AI review requested due to automatic review settings January 9, 2026 04:54
@github-actions
Copy link
Contributor

github-actions bot commented Jan 9, 2026

The following comment was made by an LLM, it may be inaccurate:

No duplicate PRs found

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This pull request introduces force-kill process cleanup for MCP (Model Context Protocol) server processes to prevent orphan processes from accumulating when graceful shutdown fails. The change tracks local MCP server transports and forcefully terminates their processes after attempting graceful closure, addressing a memory leak issue where ~300MB+ processes could persist after OpenCode exits.

Key changes:

  • Added forceKillProcess() helper with platform-specific process termination (taskkill on Windows, SIGKILL on Unix)
  • Introduced LocalTransportInfo type to track StdioClientTransport instances alongside MCP clients
  • Modified dispose and disconnect logic to force-kill processes after 2-second graceful close timeout

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 6 comments.

File Description
packages/opencode/src/mcp/index.ts Core implementation: adds LocalTransportInfo tracking, forceKillProcess helper, and force-kill logic in dispose/disconnect flows
packages/opencode/test/mcp/process-cleanup.test.ts Unit tests verifying PID tracking for local transports and client.close() invocation during disconnect
Comments suppressed due to low confidence (1)

packages/opencode/src/mcp/index.ts:529

  • When listTools fails during client creation, the code closes the MCP client but doesn't force kill the local process. If the graceful close fails (as described in the PR description), this could leave orphan processes. Consider adding force kill logic here as well, similar to the disconnect function, by checking if localTransport is defined and calling forceKillProcess after the failed close attempt.
    if (!result) {
      await mcpClient.close().catch((error) => {
        log.error("Failed to close MCP client", {
          error,
        })
      })
      status = {
        status: "failed",
        error: "Failed to get tools",
      }
      return {
        mcpClient: undefined,
        localTransport: undefined,
        status: {
          status: "failed" as const,
          error: "Failed to get tools",
        },
      }

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@hendem hendem force-pushed the fix/mcp-orphan-process-cleanup branch from 1be530c to a43aead Compare January 9, 2026 05:11
@hendem hendem requested a review from Copilot January 9, 2026 05:14
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@github-actions
Copy link
Contributor

github-actions bot commented Jan 9, 2026

Thanks for your contribution!

This PR doesn't have a linked issue. All PRs must reference an existing issue.

Please:

  1. Open an issue describing the bug/feature (if one doesn't exist)
  2. Add Fixes #<number> or Closes #<number> to this PR description

See CONTRIBUTING.md for details.

@hendem hendem force-pushed the fix/mcp-orphan-process-cleanup branch from a43aead to 275599f Compare January 9, 2026 05:54
@fwang fwang requested a review from adamdotdevin as a code owner January 9, 2026 06:32
fwang and others added 2 commits January 9, 2026 09:45
Revert "wip: zen"

This reverts commit 9a607cb201fd154ef7101a4b20220065a934d3b4.
…cesses

Track StdioClientTransport instances for local MCP servers to enable
forceful process termination on dispose. The MCP SDK's client.close()
relies on AbortController signals which don't work for all processes
(e.g., docker containers without --init, hung processes).

Changes:
- Track LocalTransportInfo (transport + name) alongside MCP clients
- Add forceKillProcess() helper for cross-platform process termination
- Use taskkill on Windows, SIGKILL on Unix for forceful termination
- Add timeout to client.close() calls to prevent hanging on dispose
- Update connect() and disconnect() to track/cleanup local transports
- Add tests for transport tracking and client lifecycle

This prevents orphan MCP server processes from accumulating across
sessions, which was causing significant memory usage (~300MB+ per
orphan process).

Related: anomalyco#7261
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants