Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Php Mcp Schema Laravel Package

wordpress/php-mcp-schema

PHP implementation of the Model Context Protocol (MCP) schema, offering lightweight, strictly typed classes, enums, and helpers for building MCP servers and clients. Use it to connect WordPress and PHP applications with MCP-compatible AI tools, agents, and services.

View on GitHub
Deep Wiki
Context7

Getting Started

  • composer require wordpress/php-mcp-schema — v0.2.0 is a plain, dependency-free runtime. There is no service provider, no config file to publish, and nothing to register in bootstrap/providers.php.
  • Look first at three namespaces and nothing else: Record\ (concrete protocol objects you receive and branch on), Contract\ (object unions), Value\ (enum-like string constants for methods, error codes, roles). The old Client\, Common\, Server\ namespaces and the generated skill/ type reference no longer exist — ignore any snippet, blog post, or LLM completion that references them.
  • Pin your revision explicitly. Only 2025-11-25 and 2026-07-28 exist; anything else throws UnsupportedRevisionException from forVersion(). Put it in config/mcp.php as 'revision' => env('MCP_REVISION', '2025-11-25').
  • Bind one schema instance as a singleton rather than rebuilding per request:
// App\Providers\McpServiceProvider::register()
$this->app->singleton(Schema::class, fn () => Schemas::create()->forVersion(config('mcp.revision')));
  • First use case: an inbound initialize then tools/call. Take the raw request body, hydrate it with fromJson() (or fromArray() off $request->all()), and branch on the resulting Record\* class. That single flow exercises the whole API surface you'll use day-to-day.
  • Validation is always on and exact for the selected revision. There is no validate: true flag to pass — if hydration returns, the payload conformed.

Implementation Patterns

  • Route + middleware, not controllers. Register a single POST /mcp route guarded by an Mcp\ValidateMessage middleware that hydrates the body and stashes the immutable record on the request. Controllers then type-hint the Record\ class they expect; hydration failures short-circuit to a JSON-RPC error before any tool code runs.
  • Dispatch on record class, not on method strings.
$message = $schema->fromJson($request->getContent());

return match (true) {
    $message instanceof Record\InitializeRequest => $this->initialize($message),
    $message instanceof Record\ToolsCallRequest   => $this->dispatchTool($message),
    $message instanceof Record\ToolsListRequest   => $this->listTools(),
    default                                       => $this->methodNotFound($message),
};
  • You own serialization now. toArray() variants were removed with no aliases. Write one App\Mcp\Presenter (or per-record toArray() mappers) and use it everywhere outbound; keep snapshot tests on the JSON so protocol drift is caught in CI rather than by a client.
  • Tool registry as a container map. config/mcp.php maps tool names to invokable classes; dispatchTool resolves from the container, validates $record->arguments against that tool's own JSON schema, and returns the result wrapped in the response record. This is the seam where Laravel DI, policies, and rate limiting enter.
  • Multi-revision endpoints. If you serve both revisions, key the container binding per revision (or resolve lazily inside the middleware from a client-negotiated header) instead of a single singleton. Records from different revisions are not interchangeable — don't cache one against the other.
  • Long-running tools on the queue. Hydrate inline, persist the request id and parsed arguments, dispatch a job, and stream the result back over SSE. Because records are immutable, they serialize cleanly into job payloads.
  • Error mapping. Every runtime exception extends \InvalidArgumentException. Catch it in the middleware and emit a Record\Error with code -32602; reserve -32603 for your own handler failures. Hand-rolled error arrays still hydrate to Record\Error, so instanceof Record\Error is a reliable guard.
  • Testing. Keep real MCP session transcripts in tests/Fixtures/mcp/*.json (one folder per revision) and assert hydrate-then-present round-trips. That is the cheapest regression net for a package that regenerates its object model between releases.

Gotchas and Tips

  • Assume every v0.1.x snippet is broken. DTO constructors, factories, class-based enums, validate: flags, and all toArray() variants are gone with no compatibility aliases. Read docs/MIGRATION.md before touching call sites; budget this as a rewrite of the hydration layer, not a patch bump.
  • \InvalidArgumentException is very broad. Don't catch it globally in bootstrap/app.php — scope the handler to the package's exception namespace so real TypeError-class bugs and your own argument errors don't get reported as invalid params.
  • Union ambiguity is the sharpest edge. Overlapping object unions hydrate the valid record declaring the most input keys (canonical order breaks ties). A payload with a few extra keys can quietly land on an unexpected Record\ class. Assert instanceof at your dispatch boundary and reject unexpected extras in your own tool schemas rather than trusting the union resolver.
  • Numbers are not integers. 2025-11-25 NumberSchema accepts fractional default/minimum/maximum, and 2026-07-28 JSONValue accepts fractional numbers and null. Numeric-literal getters faithfully preserve integral floats like -32700.0, so $record->code === -32700 can fail. Cast explicitly before using values as JSON-RPC error codes or array keys.
  • Hard input limits, twice. The runtime is much leaner (an 8 MiB payload fits a 128 MiB memory_limit), but rejection is cheaper than validation. Cap request bodies at the web server and again in middleware so a hostile client can't reach hydration at all.
  • Hostile-key and numeric hygiene is enforced for you — NUL-prefixed object keys, non-finite floats, and out-of-native-range integral floats are rejected. If your tool arguments come from INF/NAN-prone computations, sanitize on the way out rather than discovering it as a parse error.
  • Fail at boot, not at request time. UnsupportedRevisionException is thrown by forVersion(), so resolve the schema in a health check or deploy smoke test — a typo in MCP_REVISION should surface as a failed deploy, not a 500 on the first client handshake.
  • Octane-safe. Records are immutable and the schema binding carries no request state, so the singleton pattern holds under Octane. Do not memoize per-request records on the schema instance.
  • You lost the skill/ type lookup. With per-class @since annotations and the generated reference removed, the source tree and the changelog are now the only authority on what a field means in a given revision. Diffing Record\ between the two revision branches is the fastest way to answer "does 2025-11-25 support this?" — worth writing down for the team.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib