kinetis / mcp-protocol
Model Context Protocol 2025-06-18 over JSON-RPC 2.0 — envelope validation, typed tool and resource contracts, progress notifications, and checked newline-delimited stdio framing. Requires PHP only.
Requires
- php: ^8.4
Requires (Dev)
- infection/infection: ^0.35.0
- phpstan/phpstan: ^2.2.8
- phpunit/phpunit: ^13.3.3
- vimeo/psalm: ^6.17
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
kinetis/mcp-protocol
MCP 2025-06-18 over JSON-RPC, as a framework-agnostic library
Part of Kinetis, a non-blocking PHP framework for API-first applications, developed in the kinetis-dev/kinetis monorepo.
The Model Context Protocol mechanics three Kinetis servers share: JSON-RPC 2.0 envelopes, one MCP revision, typed tool and resource descriptions, progress notifications, and a checked newline-delimited stdio loop. It requires PHP and nothing else.
Install
composer require kinetis/mcp-protocol
What it owns
- One revision,
2025-06-18.initializealways selects it, whichever revision the client asks for — the specification's rule is that a server answers with a version it supports and the client decides whether to continue. - Envelope validation. One decode and structural-validation path for
every transport, preserving JSON object-versus-array identity so
{}and[]stay distinct all the way to a tool's arguments. initialize,ping,tools/list,tools/call,resources/list,resources/read, and the notification rules around them.- Stdio framing. Bounded reads, a 2 MiB payload cap, checked partial writes, and progress notifications ordered before the final response.
What it does not own
No framework, container, HTTP transport, attribute, reflection, discovery, documentation fetching, filesystem policy, application bootstrap, or connection session. It stores nothing between messages, which is what lets one instance serve a persistent stdio process and a stateless HTTP route at the same time.
Writing a server
A consumer implements McpApplication and hands it to McpServer:
use Kinetis\McpProtocol\McpApplication; use Kinetis\McpProtocol\McpServer; use Kinetis\McpProtocol\ProgressEmitter; use Kinetis\McpProtocol\ResourceDescription; use Kinetis\McpProtocol\ResourceResult; use Kinetis\McpProtocol\ServerInfo; use Kinetis\McpProtocol\StdioLoop; use Kinetis\McpProtocol\ToolDescription; use Kinetis\McpProtocol\ToolResult; final class Clock implements McpApplication { public function tools(): array { return [new ToolDescription( 'now', 'Returns the current UTC time.', ['type' => 'object', 'properties' => new stdClass(), 'additionalProperties' => false], )]; } public function resources(): array { return []; } public function callTool(string $name, stdClass $arguments, ProgressEmitter $progress, ?object $context): ToolResult { return ToolResult::text(new DateTimeImmutable('now', new DateTimeZone('UTC'))->format(DATE_ATOM)); } public function readResource(string $uri, ?object $context): ResourceResult { throw new LogicException('This server publishes no resources.'); } } $server = new McpServer(new ServerInfo('clock', '1.0.0'), new Clock()); new StdioLoop()->run($server, STDIN, STDOUT);
The server resolves name and uri against those two lists before it
calls anything, so callTool() and readResource() are only ever reached
for a name the consumer itself published. Capabilities are advertised from
the same lists: the server above offers tools and not resources.
A consumer needing a per-message unit of work — a container scope, a
transaction — implements MessageHandler around the server and passes its
own object as the opaque $context, which travels to callTool() and
readResource() untouched.
Documentation
Kinetis MCP guide · Appendix: MCP Reference
License
MIT