Skip to content

fix: resolve memory leaks causing ~4GB memory growth - #30

Merged
Minitour merged 3 commits into
mainfrom
fix/memory-leaks
Mar 20, 2026
Merged

Minitour merged 3 commits into
mainfrom
fix/memory-leaks

Conversation

@Minitour

@Minitour Minitour commented Mar 20, 2026 •

Copy link
Copy Markdown
Member

Summary

Analysis of a 3.7 GB memory dump revealed several critical memory leaks in the MCP server infrastructure that caused unbounded heap growth:

  • Double process spawn: StdioClientTransport already spawns its own child process internally, but MCPProxy.createStdioClient() was also calling SubprocessManager.getOrCreateSubprocess() — resulting in two processes per stdio MCP server. The first was orphaned and never cleaned up on shutdown.

  • Leaked temporary CapaMCPServer instances: handleProjectConfigure (plugin enrichment + tool validation) and handleGetServerTools created temporary CapaMCPServer instances with their own MCPProxy and MCP client connections that were never closed. Each project reconfigure leaked more connections.

  • close() not closing the proxy: CapaMCPServer.close() only closed the MCP Server but never called mcpProxy.closeAll(), leaking all client connections and their transport buffers.

Changes

  • Remove SubprocessManager dependency from MCPProxy — let StdioClientTransport manage its own subprocess lifecycle
  • Add getOrCreateMCPServer() helper to CapaServer to centralize CapaMCPServer instance management and ensure reuse via the mcpServers Map
  • Fix CapaMCPServer.close() to call mcpProxy.closeAll() before closing the server
  • Add onclose handlers on MCP clients to auto-remove dead entries from the cache

Test plan

  • Start capa server, configure a project with stdio MCP servers, verify tools still work
  • Monitor process memory over time — should remain stable instead of growing
  • Reconfigure a project multiple times and verify no new leaked processes appear
  • Stop the server and verify all child processes are cleaned up

Made with Cursor

- Remove double process spawn for stdio MCP servers: StdioClientTransport
  already spawns its own child process, so the redundant SubprocessManager
  call was creating a second orphaned process per server.

- Fix CapaMCPServer.close() to call mcpProxy.closeAll() before closing
  the MCP server, ensuring all client connections are properly released.

- Eliminate leaked temporary CapaMCPServer instances in
  handleProjectConfigure and handleGetServerTools by reusing a shared
  getOrCreateMCPServer() helper that stores instances in the mcpServers Map.

- Add onclose handlers to MCP clients so dead/disconnected entries are
  automatically removed from the clients cache.
Arguments passed via `capa sh --arg value` were always strings,
causing MCP tool calls to fail when the schema expected number,
integer, array, or object types.

The new coerceValue() function handles: number, integer, boolean,
array (comma-separated or JSON), and object (JSON). For arrays,
item types are respected (e.g. array of numbers). When no schema
type is declared, values are inferred from their content.
@Minitour
Minitour merged commit c0b1f82 into main Mar 20, 2026
4 checks passed
@Minitour
Minitour deleted the fix/memory-leaks branch March 20, 2026 18:48
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