Automattic / Automattic/php-mcp-client

StdioTransport disconnect leaves child server processes running

Open
#4 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
PHP
Stars
0
Forks
1
PR merge metrics
No merged PRs in 30d

Description

## Problem

`StdioTransport::connect()` passes a command string to `proc_open()`. On Linux this starts a shell wrapper. `disconnect()` calls `proc_terminate()` on that shell PID, and the actual MCP server process remains alive after the caller exits. Repeated request-scoped transports therefore leak MCP server processes.

Calling `McpClient::disconnect()` does not alter this behavior: it delegates directly to `StdioTransport::disconnect()`.

## Reproduction

Automattic/intelligence#976 contains a standalone regression that:

1. Starts three concurrent callers.
2. Starts two real stdio PHP server processes per caller and records their PIDs.
3. Calls the normal disconnect path and allows each caller to exit.
4. Checks each server PID with `posix_kill($pid, 0)` and, on Linux, `/proc/$pid/stat` to exclude zombies.

Run from that branch with:

```sh
php tests/mcp/broad-search-stdio-liveness-smoke.php
```

The Linux GitHub Actions job has reproduced the same result twice at the same commit: all three callers exit, but all six MCP server PIDs remain alive (`10 passed, 6 failed`). The test passes on macOS, so the observed defect is Linux-specific.

- First run: https://github.com/Automattic/intelligence/actions/runs/32666393397/job/97260311193
- Identical rerun: https://github.com/Automattic/intelligence/actions/runs/32666393397/job/97263513202

## Expected behavior

After `StdioTransport::disconnect()` returns, the MCP server process started by that transport is no longer running and has been reaped. Graceful termination remains bounded and escalates to forced termination when needed.

## Proposed direction

Pass the executable and arguments to `proc_open()` as an array so PHP starts the MCP server directly rather than through a shell, preserving argument boundaries. Add a Linux-capable regression that records the real server PID and verifies it is dead after disconnect.

## Scope

The observed fix should be small: replacing string command construction at the `proc_open()` boundary and adding a focused lifecycle test. Arbitrary descendant-tree/process-group management is not required to close this specific reproduction.

## AI assistance disclosure

OpenAI GPT-5.6 Sol via OpenCode helped reproduce the process-lifecycle defect, inspect the client lifecycle, and draft this report. Chris Huber reviewed and remains responsible for the report.

Contributor guide

No contributing guide indexed for this repository

Research direction

Start at StdioTransport::connect() and disconnect(), then inspect the McpClient::disconnect() delegation and the proc_open() boundary. Run tests/mcp/broad-search-stdio-liveness-smoke.php or its regression equivalent; done means the real MCP server PID is no longer running and has been reaped after disconnect, including on Linux.

Written by the indexing model from the issue text.

Assessment

Tech stack
php
Domain
backend
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
75/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.