Skip to content

fix(mcp): shut down scoped executor on session eviction (#1917) - #1971

Open
Adityakk9031 wants to merge 2 commits into
UsefulSoftwareCo:mainfrom
Adityakk9031:fix/issue-1917-mcp-session-executor-leak
Open

fix(mcp): shut down scoped executor on session eviction (#1917)#1971
Adityakk9031 wants to merge 2 commits into
UsefulSoftwareCo:mainfrom
Adityakk9031:fix/issue-1917-mcp-session-executor-leak

Conversation

@Adityakk9031

Copy link
Copy Markdown
Contributor

Summary

Retain the scoped Executor created for each MCP session and shut it down when an in-memory MCP session is evicted, closed, or fails eager initialization. This ensures child tool subprocesses and connection pools are properly released.

Linked issue

Fixes #1917

Root Cause

Executor creates a scoped Executor for each MCP session in makeMcpBuildServer via makeExecutionStack(principal.accountId, principal.organizationId, ...). This scoped executor owns plugin instances, including @executor-js/plugin-mcp, which manages a connection pool holding spawned child tool subprocesses (e.g. stdio MCP tools).

However, makeMcpBuildServer previously returned only { mcpServer, engine }, discarding the executor reference. Furthermore, BuiltMcpServer and makeInMemoryMcpSessionStore only tracked and closed transport, mcpServer, and engine. When an idle session was evicted or closed:

  1. transport.close(), server.close(), and engine.shutdown were executed.
  2. executor.close() was never invoked.
  3. Consequently, runtimes.values() never had plugin.close() called, connectionPool.close() was never invoked, and tool subprocesses remained running indefinitely in the host system.

Changes

  • @executor-js/host-mcp:
    • Extended BuiltMcpServer to optionally accept executor?: Executor and close?: () => Promise<void>.
    • Updated makeInMemoryMcpSessionStore to track executors and closers per session ID.
    • Added shutdownExecutor helper and wired it into dispose(), eager initialization failure cleanup (onClose), and store shutdown (close()).
    • Added unit tests in in-memory-session-store.test.ts verifying executor shutdown upon idle session eviction and store teardown.
  • @executor-js/api:
    • Preserved and returned executor from makeMcpBuildServer alongside mcpServer and engine.
  • Added changeset file .changeset/mcp-session-executor-leak.md.

Verification

  • bun run format:check — all matched files formatted
  • bun run lint — 0 warnings, 0 errors
  • bun run --cwd packages/hosts/mcp typecheck — 0 errors
  • bun run --cwd packages/core/api typecheck — 0 errors
  • bun run --cwd packages/hosts/mcp test — 223 passed
  • bun run --cwd packages/core/api test — 119 passed
  • bun x vitest run src/mcp/mcp.test.ts (in apps/host-selfhost) — 5 passed

Checklist

  • Added a changeset (bun run changeset), or this change needs none.
  • Added or updated tests for the new behaviour.
  • No secrets, credentials, or private data in the diff.

…reCo#1917)

Retain the scoped Executor created for each MCP session and shut it down when an in-memory MCP session is evicted, closed, or fails eager initialization. This ensures child tool subprocesses and connection pools owned by the session are properly cleaned up.
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.

[bug] Tool subprocesses stay alive with no sessions

1 participant