Download the PHP package stevecoug/mcp-devkit without Composer
On this page you can find all versions of the php package stevecoug/mcp-devkit. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.
Download stevecoug/mcp-devkit
More information about stevecoug/mcp-devkit
Files in stevecoug/mcp-devkit
Package mcp-devkit
Short Description Developer tooling for PHP MCP servers built on mcp/sdk — hot-reload tools without restarting the client, and call or inspect them straight from the shell.
License MIT
Homepage https://github.com/stevecoug/mcp-devkit
Informations about the package mcp-devkit
mcp-devkit
Developer tooling for PHP MCP servers built on mcp/sdk — hot-reload your tools without restarting the client, and call or inspect them straight from the shell.
Why
Writing an MCP server in PHP with mcp/sdk is pleasant — attribute-discovered tool classes, a clean stdio transport. But two things slow the inner dev loop to a crawl:
- Every edit means a client restart. Change a tool, and the MCP client (Claude Code, Cursor, …) keeps serving the old tool list until you fully restart it. On a busy session that's death by a thousand relaunches.
- There's no quick way to poke a tool. To check that
list_issuesreturns what you expect, you either drive it through a full client or hand-craft JSON-RPC frames on a pipe.
mcp-devkit fixes both, as a thin layer on top of mcp/sdk — it uses only the SDK's public API, so there's no fork to maintain and nothing to patch.
What you get
reloadtool — re-execs the running server in place (same process, same stdio pipe). Edited tool code is re-discovered and the client is told to refresh its tool list. No client restart.- CLI test mode — call your tools like ordinary subcommands:
php server.php add 2 3. Arguments, types, defaults and help text are inferred from your tool classes, so every tool gets a usage line for free. - Conditional tools — hide a discovered tool whose preconditions aren't met, so it's absent from the tool list instead of erroring when called.
Requirements
- PHP 8.2+
mcp/sdk^0.7.1 || ^0.8 — both minors are supported and both are covered by CI, so adopting the devkit never forces an SDK bump. 0.7.0 is excluded deliberately: GHSA-7m52-jw36-44r3.symfony/finder^5.4 || ^6.4 || ^7.3 || ^8.0 — the SDK's attribute discovery needs it. The range matches the SDK's own, so the devkit never constrains which Symfony major your app is on; every major in it is covered by CI.ext-pcntl— required for thereloadtool only. Serving and the CLI mode work without it.
Install
If your project pins mcp/sdk to dev-main, that will fail to resolve — this package requires a tagged release. Move the SDK off dev-main in the same command:
This package also requires psr/container: ^1.1 || ^2.0 even though it never touches the interface itself. mcp/sdk allows ^1.0, but its Registry\Container::get(string $id) is signature-incompatible with psr/container 1.0.0 and fatals on load — so a consumer resolving lowest dependency versions would get an uninstallable tree. The tighter constraint here keeps that from happening.
Quick start
Wire the devkit into your server's entry script. It owns the registry and session, auto-registers the reload tool, advertises the toolsListChanged capability, and dispatches CLI flags — so your server.php stays this short:
Your tools are plain mcp/sdk tool classes — nothing devkit-specific:
Register it with your client the usual way:
Server instructions
Guidance that applies to the server as a whole, rather than to one tool, goes in instructions(). It's returned at initialize and clients surface it next to the tool list — Claude Code shows it as the server's instructions:
Standing norms like that have nowhere else to live — a per-tool description can only speak for its own tool.
Conditional tools
Some tools only make sense under a condition the server can check at startup — credentials that may or may not be configured, a service that may or may not be reachable. filterTools() drops the ones whose condition doesn't hold, so they're absent from the tool list rather than present and failing when called:
The predicate receives every discovered tool name and returns true to keep it. A dropped tool is gone for real — missing from tools/list, and a tools/call for it comes back Tool not found.
Two things worth knowing:
reloadis never offered to the predicate. Filtering it away would remove the one route back for the tools you just hid.- The predicate re-runs on every reload, because reload re-runs
build()in the new process image. A condition that becomes true mid-session — credentials added to the environment, a service coming up — surfaces its tools on the nextreload, with no client restart.
If your conditional tools happen to live in their own directory, you don't need this at all — discover() takes any number of roots and nothing resolves until build(), so a plain if around a second discover() call keeps them out from the start.
The reload tool
Once wired in, the client sees a reload tool. Call it after editing tool code and the new tools appear without relaunching the client.
Under the hood it pcntl_execs the same entry script (same PID; fds 0/1/2 survive the exec because they lack CLOEXEC, so the stdio pipe stays intact), re-runs attribute discovery, seeds a fresh session, answers the pending reload call, and emits notifications/tools/list_changed.
Call it while the pipe is idle — a deliberate, single call. A re-exec discards the process's read buffer, so a half-read pipelined request would be lost. Normal interactive use satisfies that.
CLI test mode
Name a tool on the command line and the devkit runs it instead of serving over stdio — no client, no hand-written JSON:
Nothing to declare: the argument list, types, defaults and help text come from the input schema mcp/sdk already generates from your method signature and docblock. Take a tool like this —
— and the CLI knows its whole interface:
Passing arguments
Positional arguments fill parameters in declared order; named arguments work in any order and are the way to skip an optional:
Values are coerced to the schema's type, so you get an error up front rather than a confusing failure inside the tool:
| Schema type | On the command line |
|---|---|
string |
verbatim |
integer, number |
must be numeric |
boolean |
--flag / --no-flag, or = true/false/1/0/yes/no |
enum |
must be one of the listed values |
array |
repeat the flag (--tag=a --tag=b), or pass a JSON array |
object |
a JSON object |
Missing a required argument, naming a parameter that doesn't exist, or passing an unusable value prints the tool's usage and exits non-zero (2); a tool that throws exits 1.
Invocation runs the exact same discovery and handler path the stdio transport would, so what you see on the CLI is what the client gets.
How it works
mcp-devkit leans entirely on mcp/sdk's public surface:
- CLI mode owns the
Registry(viaBuilder::setRegistry()), enumerates tools withRegistryInterface::getTools(), and reads each tool'sinputSchemato build its usage line and coerce your arguments. To invoke, it resolves the tool withgetTool()and runs it through the SDK'sReferenceHandler, wrapping the return in aCallToolResult— the same few steps the SDK's ownCallToolHandlerperforms. - Reload owns the
SessionManager(viaBuilder::setSession()) so the re-exec'd process can seed a valid session, and requirestoolsListChanged: trueto be advertised at the originalinitialize(the client only honors a laterlist_changedif it was promised up front). The devkit sets that for you. filterTools()prunes afterbuild()withRegistryInterface::unregisterTool(), rather than skipping registration — attribute-discovered tools are registered by the SDK, not by the devkit, so there is no earlier hook to skip. Supplying the registry is also what makes the timing work: the SDK loads a caller-supplied registry eagerly at build (it can't inject a loader into an instance it didn't construct), and that registry has no loader of its own, so nothing runs later that could put a pruned tool back.
Because none of this needs SDK internals, mcp-devkit is a drop-in dependency, not a fork.
Try it
example/ is a working server with a handful of tools:
Development
Argv parsing, argument binding, usage rendering and the reload environment are pure units with no I/O, so the suite covers them directly.
Status
Early (0.x). API may shift before 1.0.
License
MIT © Steve Meyers
All versions of mcp-devkit with dependencies
mcp/sdk Version ^0.7.1 || ^0.8
psr/container Version ^1.1 || ^2.0
symfony/finder Version ^5.4 || ^6.4 || ^7.3 || ^8.0