PHPackages                             dpt/mcp-phpunit-warm - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Testing &amp; Quality](/categories/testing)
4. /
5. dpt/mcp-phpunit-warm

ActiveProject[Testing &amp; Quality](/categories/testing)

dpt/mcp-phpunit-warm
====================

Warm-process MCP server for PHPUnit. Keeps the test framework bootstrapped across calls — faster per-suite invocation vs cold CLI. Compatible with any MCP client (Claude Desktop, Cline, Continue, Zed, custom).

v0.2.0(2w ago)1783↑310%proprietaryPHPPHP &gt;=8.2CI passing

Since May 22Pushed 1w ago1 watchersCompare

[ Source](https://github.com/Digital-Process-Tools/mcp-phpunit-warm)[ Packagist](https://packagist.org/packages/dpt/mcp-phpunit-warm)[ Docs](https://github.com/Digital-Process-Tools/mcp-phpunit-warm)[ RSS](/packages/dpt-mcp-phpunit-warm/feed)WikiDiscussions main Synced 1w ago

READMEChangelog (1)Dependencies (3)Versions (4)Used By (0)

 [![mcp-phpunit-warm — every test warm. ~6× faster per call.](banner.png)](banner.png)

mcp-phpunit-warm
================

[](#mcp-phpunit-warm)

> **Stop paying PHPUnit's bootstrap tax on every test call.**A warm-process [MCP](https://modelcontextprotocol.io/) server that keeps [PHPUnit](https://phpunit.de/) bootstrapped across calls. **~6× faster per call** vs cold CLI. Works with every MCP client.
>
> **v0.4.0:** each test run executes in a short-lived **forked child**, so an edit made between calls is always picked up — no more stale results from a class the warm process loaded earlier. The framework stays warm; only your code is re-read.
>
> **v0.2.0:** results captured in-memory via `EventFacade` subscribers — no more JUnit XML round-trip.

[![Tests](https://github.com/Digital-Process-Tools/mcp-phpunit-warm/actions/workflows/tests.yml/badge.svg)](https://github.com/Digital-Process-Tools/mcp-phpunit-warm/actions/workflows/tests.yml)[![Packagist](https://camo.githubusercontent.com/54dd3cefe280099d3ab7283c061a7bd0bdf7ff2b51f813ad4f3576a6f9cd8700/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f6470742f6d63702d706870756e69742d7761726d2e737667)](https://packagist.org/packages/dpt/mcp-phpunit-warm)[![PHP](https://camo.githubusercontent.com/344e820b219cee3234648531306104364bd684892ad13c5dc79e66eb82a15b90/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f7068702d382e322532422d626c7565)](https://www.php.net/)[![License](https://camo.githubusercontent.com/b070db700d7549b4414d6c41907c169824e89a1e3e949816af5828685facd257/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d436f6d6d756e6974792d627269676874677265656e)](LICENSE)

[Why](#why) • [Install](#install) • [Use it](#use-it) • [Benchmark](#benchmark) • [Compatibility](#compatibility) • [Tools exposed](#tools-exposed) • [How it works](#how-it-works) • [FAQ](#faq) • [Credits](#credits)

---

Why
---

[](#why)

[PHPUnit](https://phpunit.de/) is the standard testing framework for PHP. It is also slow to **start**.

Every `phpunit` invocation pays the same toll: autoloader bootstrap, XML config parsing, test suite construction, extension bootstrapping. For agents and validators that run PHPUnit after every edit or after every MCP tool call, that cold-start cost adds up fast.

`mcp-phpunit-warm` keeps a long-lived PHP process with PHPUnit already bootstrapped. **The boot is paid once; every call inherits that warm framework via a fork and skips re-parsing it.** Your own source and test classes are re-read on each call (in the forked child) so an edit is never missed — you get the boot savings without the staleness.

Install
-------

[](#install)

```
composer global require dpt/mcp-phpunit-warm
```

Makes `mcp-phpunit-warm` available on `$PATH`.

Requires PHP 8.2+. Pulls PHPUnit ^10 || ^11 || ^12 as a real Composer dep.

Use it
------

[](#use-it)

### Claude Desktop

[](#claude-desktop)

Edit `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) or `%APPDATA%\Claude\claude_desktop_config.json` (Windows):

```
{
  "mcpServers": {
    "phpunit": {
      "command": "mcp-phpunit-warm",
      "args": [
        "--working-dir=/path/to/your/project",
        "--config=/path/to/your/project/phpunit.xml"
      ]
    }
  }
}
```

Restart Claude. Ask: *"Run the tests for UserServiceTest"*.

### Cline / Continue / Cursor / Zed / any MCP client

[](#cline--continue--cursor--zed--any-mcp-client)

Same `command` + `args` shape. The server speaks plain MCP over stdio — no client-specific glue.

### Standalone

[](#standalone)

```
mcp-phpunit-warm --working-dir=/path/to/project --config=/path/to/project/phpunit.xml
```

Reads MCP JSON-RPC on stdin, writes responses on stdout.

Benchmark
---------

[](#benchmark)

Measured on a real DVSI codebase, single-test invocations:

SetupPer call (steady-state)`vendor/bin/phpunit` (fresh CLI each call)~1600ms**mcp-phpunit-warm (daemon warm)****~300ms**First call into a fresh daemon pays the boot once (~1400ms). All subsequent calls reuse the warm autoloader and singletons.

Results are captured in-memory via `PHPUnit\Event\Facade` subscribers — no JUnit XML temp file, no per-call printer setup.

The win is cold-start amortization: autoloader bootstrap, XML config parsing, and test suite construction happen once. Subsequent calls skip all of it. Smaller win than [mcp-rector-warm](https://github.com/Digital-Process-Tools/mcp-rector-warm) (~14× per edit) because PHPUnit cold is already faster than Rector cold.

Compatibility
-------------

[](#compatibility)

ClientStatusClaude Desktop✅ stdio MCPCline (VS Code)✅ stdio MCPContinue (VS Code / JetBrains)✅ stdio MCPCursor✅ stdio MCPZed✅ stdio MCPCustom (Python/Node/Go MCP clients)✅ standard protocolPHPUnitStatus^10✅ tested^11✅ tested^12✅ testedTools exposed
-------------

[](#tools-exposed)

### `phpunit_run`

[](#phpunit_run)

Run PHPUnit tests.

ArgumentTypeDefaultDescription`testFile`string|null`null`Absolute path to a test file or directory. Omit to run the full suite.`filter`string|null`null``--filter` pattern to run specific tests`group`string|null`null``--group` name to restrict executionReturns:

```
{
  "exit_code": 0,
  "output": "{\"tests\":3,\"assertions\":5,\"failures\":[],\"errors\":[],\"skipped\":[],\"time\":0.012}",
  "warm_boot": true
}
```

`warm_boot: true` ⇒ a previous run already warmed this daemon (the forked child inherits the warm framework). `false` ⇒ first call on this daemon.

`output` is a JSON string with `{tests, assertions, failures: [{class, method, file, line, message}], errors: […], skipped: […], time}`. Captured in-process via `PHPUnit\Event\Facade` subscribers — no temp file, no XML parse.

How it works
------------

[](#how-it-works)

Four decisions worth knowing:

1. **One daemon per project, not per call.** Config + working dir pin at server startup via `--config` and `--working-dir`. The PHPUnit framework stays bootstrapped for the daemon's whole life.
2. **Fork per call — warm framework, fresh code.** A long-lived PHP process can never reload a class once it's autoloaded (PHP forbids redeclaration), so an in-process warm runner would keep executing the *first* version of every class it ever loaded and silently ignore your edits. Instead, the daemon parent boots only the framework and *never loads a user source/test class*; each `phpunit_run` **forks a child** that autoloads your classes fresh from disk, runs them, ships the result back over a socket, and dies. The child inherits the parent's compiled framework via copy-on-write (warm) yet sees the current code every time (fresh). The child `SIGKILL`s itself once its result is on the wire so no teardown hook can write to the parent's stdio channel. On a platform without `pcntl` the daemon falls back to in-process execution (warm but stale-after-edit).
3. **Static singleton reset before each run.** PHPUnit 10/11/12 uses sealed singletons (`EventFacade`, `Registry`, `OutputFacade`, `CodeCoverage`) that are reset via Reflection before a run, so `Application::run()` can be invoked without hitting `EventFacadeIsSealedException` — needed in the prewarm probe and the fork-less fallback.
4. **In-memory results via `EventFacade` subscribers.** PHPUnit's `DefaultPrinter` writes to `php://stdout` using `fwrite()`, which bypasses PHP's output buffer and would corrupt the MCP stdio transport. We force `--no-output` to silence the printer, then register subscribers on `PHPUnit\Event\Facade` (`PreparedSubscriber`, `FailedSubscriber`, `ErroredSubscriber`, …) that collect results in memory during the run. No temp file. No XML round-trip.

FAQ
---

[](#faq)

**Does this replace `vendor/bin/phpunit`?** No. Use it from MCP clients (Claude Desktop, agents). For one-off CLI runs the regular binary is simpler.

**Why JSON output instead of JUnit XML?** v0.1 used JUnit XML via `--log-junit` to a temp file. v0.2 captures results in-memory via `EventFacade` subscribers and serializes to JSON — no file I/O, no XML parse, smaller payload. The shape mirrors what JUnit XML had, just easier for agents to consume.

**Does it support `--filter`?** Yes — pass `filter: "testMyMethod"` as an argument to the tool.

**Do I lose the warm speedup if every call forks?** No. The fork is cheap (copy-on-write) and the child inherits the parent's already-compiled PHPUnit framework, so it skips the framework boot. The only per-call cost is re-reading *your* source/test classes — which is exactly what makes the result trustworthy after an edit.

**What if I edit a file between two calls?** The next call sees it. Each run executes in a fresh forked child that autoloads your classes from disk, so there's no stale-class problem (the bug that motivated v0.4.0: [claude-supertool#265](https://github.com/Digital-Process-Tools/claude-supertool/issues/265)). **One caveat:** classes loaded by your `phpunit.xml` *bootstrap* itself (e.g. a bootstrap that eagerly `require`s application classes) are loaded into the long-lived parent and inherited by every forked child — so edits to *those* specific classes won't be picked up until the daemon restarts. Lazy autoloaders (the common case) are unaffected; keep heavy eager-loading out of the bootstrap.

**`--prewarm` flag?** On by default now. At startup the daemon runs a single bundled throwaway probe test through your config under `--no-output` — that fires the `phpunit.xml` bootstrap and loads the framework (which forked calls then inherit) **without** loading any of your classes and without dumping to `php://stdout`. The old `--list-tests` prewarm was opt-in precisely because large suites flooded stdout and corrupted the MCP transport; the probe avoids that. Disable with `--no-prewarm`.

**Memory?** The daemon sets `memory_limit = -1`. Idle daemon ≈ 30–60 MB resident depending on project bootstrap.

**Does it survive PHPUnit version updates?** The static reset targets known property names. If PHPUnit renames a singleton property in a future version, the reset skips it silently (caught via `ReflectionException`) and the run may fail with a sealed-facade error. Pin PHPUnit in your own `composer.json` if you need determinism.

**Alpha status?** The `warm_boot: true` guarantee is verified by the integration test suite. That said, PHPUnit internals (`@internal`) can change — treat this as beta until PHPUnit 11/12 compatibility is confirmed in CI.

Credits
-------

[](#credits)

- **[PHPUnit](https://phpunit.de/)** by [Sebastian Bergmann](https://sebastian-bergmann.de/) and contributors — the engine doing all the real work. If you ship PHP, [sponsor him](https://github.com/sponsors/sebastianbergmann).
- **[Model Context Protocol](https://modelcontextprotocol.io/)** by Anthropic — the protocol that makes this kind of tool integration possible.
- **[mcp/sdk](https://github.com/modelcontextprotocol/php-sdk)** — official PHP SDK, used here for stdio transport + tool discovery.

Related
-------

[](#related)

- **[PHPUnit docs](https://docs.phpunit.de/)** — configuration, assertions, extensions.
- **[PHPUnit on Packagist](https://packagist.org/packages/phpunit/phpunit)** — the upstream package.
- **[mcp-rector-warm](https://github.com/Digital-Process-Tools/mcp-rector-warm)** — same warm-process pattern for Rector refactoring.
- **[claude-supertool](https://github.com/Digital-Process-Tools/claude-supertool)** — DPT's batched-ops Claude Code companion.

License
-------

[](#license)

Community License — see [LICENSE](LICENSE). Built by [Digital Process Tools](https://github.com/Digital-Process-Tools).

###  Health Score

43

—

FairBetter than 89% of packages

Maintenance97

Actively maintained with recent releases

Popularity21

Limited adoption so far

Community7

Small or concentrated contributor base

Maturity38

Early-stage or recently created project

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~0 days

Total

2

Last Release

18d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/f66a351c6f0ebbc539c3ef7b59a2aa0a1921f04406119001c1978cff06975721?d=identicon)[fdaviddpt](/maintainers/fdaviddpt)

---

Top Contributors

[![fdaviddpt](https://avatars.githubusercontent.com/u/150798857?v=4)](https://github.com/fdaviddpt "fdaviddpt (1 commits)")

---

Tags

ai-toolsclaudeclaude-codedeveloper-toolsmcpmodel-context-protocolphpphpunittestingphptestingphpunitmcpclaudeModel Context Protocolai-tools

### Embed Badge

![Health badge](/badges/dpt-mcp-phpunit-warm/health.svg)

```
[![Health](https://phpackages.com/badges/dpt-mcp-phpunit-warm/health.svg)](https://phpackages.com/packages/dpt-mcp-phpunit-warm)
```

###  Alternatives

[brianium/paratest

Parallel testing for PHP

2.5k129.9M905](/packages/brianium-paratest)[blastcloud/guzzler

Supercharge your app or SDK with a testing library specifically for Guzzle.

272451.0k40](/packages/blastcloud-guzzler)[robiningelbrecht/phpunit-pretty-print

Prettify PHPUnit output

76514.9k15](/packages/robiningelbrecht-phpunit-pretty-print)[juampi92/test-seo

Easy way to test your SEO

26344.3k](/packages/juampi92-test-seo)[robiningelbrecht/phpunit-coverage-tools

PHPUnit coverage tools

17114.2k44](/packages/robiningelbrecht-phpunit-coverage-tools)[testo/testo

A lightweight PHP testing framework.

1843.4k30](/packages/testo-testo)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
