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

Schema Laravel Package

php-mcp/schema

A PHP package for defining and working with Model Context Protocol (MCP) schemas. It provides typed schema objects and helpers for describing tools, resources, and prompts, making it easier to build and validate MCP servers cleanly in PHP applications.

Deep Wiki
Context7

Getting Started

Install the schema package alongside whatever MCP transport/server layer you are building:

composer require php-mcp/schema

The package is a pure data layer: DTO classes and backed enums that mirror the Model Context Protocol spec. There is no runtime server or client inside it. Look at these first:

  • src/ — top-level message DTOs (initialize request/response, tool/resource/prompt definitions, results, JSON-RPC envelopes).
  • The enum directory — typically Role (user/assistant), content Type (text/image/resource), and logging levels. These are the values you should never hard-code as strings.
  • composer.json — confirm the minimum PHP version and whether it pulls in a serializer. Most schema-only packages have zero runtime dependencies, which is the main reason to use them.

First use case: describing a tool and returning a tools/list style result.

use PhpMcp\Schema\Tool;
use PhpMcp\Schema\Content\TextContent;
use PhpMcp\Schema\Result\CallToolResult;

$tool = new Tool(
    name: 'get_weather',
    description: 'Look up current weather for a city',
    inputSchema: [
        'type' => 'object',
        'properties' => ['city' => ['type' => 'string']],
        'required' => ['city'],
    ],
);

$result = new CallToolResult(
    content: [new TextContent(type: Type::Text, text: '22°C and clear')],
    isError: false,
);

echo json_encode($result, JSON_THROW_ON_ERROR);

If json_encode produces the structure from the spec examples, the package is wired correctly and you can build on it.

Implementation Patterns

Use DTOs as your tool/resource registry. Register each MCP capability as a schema DTO in a Laravel service provider, then let your server loop read the registry instead of hand-building arrays in every endpoint:

// AppServiceProvider
$this->app->singleton(ToolRegistry::class, fn () => (new ToolRegistry())
    ->add(new Tool(name: 'search', description: '...', inputSchema: [...]))
    ->add(new Tool(name: 'fetch',  description: '...', inputSchema: [...])));

Hydrate incoming JSON-RPC into DTOs at the edge. Decode the request once, switch on the method, and construct the typed message. Keep controller actions thin and push mapping into a small factory:

$payload = json_decode($request->getContent(), true, flags: JSON_THROW_ON_ERROR);

$toolCall = match ($payload['method']) {
    'tools/call' => CallToolRequest::fromArray($payload['params']),
    'resources/read' => ReadResourceRequest::fromArray($payload['params']),
    default => throw new InvalidArgumentException('Unsupported method'),
};

If the package does not ship a fromArray, keep a local mapper class; it is a handful of lines and gives you one place to absorb spec changes.

Serialize with the enum's backed value, not the enum itself. In hand-written serializers, do $content['type'] = $type->value. Where the DTO implements JsonSerializable, delegate to json_encode($dto) so enum handling stays consistent across every endpoint.

Compose results rather than mutating them. CallToolResult should hold an immutable array of content parts; build new instances per call. This keeps long-running workers (Octane, queue workers, a php artisan mcp:serve loop) from leaking state between requests.

Test against fixtures from the spec. Store canonical request/response JSON in tests/Fixtures, hydrate to a DTO, re-serialize, and assert array equality. This catches casing and nullability regressions when the package updates.

Gotchas and Tips

  • null vs omitted. The spec distinguishes absent optional fields from explicit nulls. Avoid blanket array_filter on serialized output; filter only keys you know are optional, otherwise you will silently drop legitimate false and 0 values.
  • Discriminated unions need manual dispatch. Content is effectively a union keyed on type. A generic recursive hydrator will not choose TextContent vs ImageContent correctly; switch on the type enum before constructing.
  • Casing mismatches are the most common bug. Protocol keys like jsonrpc, protocolVersion, and inputSchema are not snake_case. Verify the DTO's serialization once with print_r(json_decode(json_encode($dto), true)) and build a fixture around it.
  • Backed enums and json_encode. A bare enum encodes as its name, not its value, in older PHP serialization contexts. Always convert with ->value or rely on DTOs that implement JsonSerializable. Prefer Role::User->value over (string) Role::User for clarity.
  • Spec drift. MCP is evolving quickly; pin a version constraint ("php-mcp/schema": "^1.0") and read the changelog before upgrading. Add a schema_version constant to your own code so mismatches surface early.
  • No validation included. DTOs are transport shapes, not validators. Validate inputSchema and tool arguments with Laravel's validator or a JSON-Schema library before invoking handlers.
  • PHP version floor. Match the package's composer.json require.php against your app; readonly properties and enums need 8.1+, and named arguments in examples need 8.0+.
  • Extension points are limited. If classes are final or have no extension hooks, do not subclass. Wrap them in your own application-level value objects instead, and keep the schema DTOs at the boundary.
  • Debugging tip: when a client rejects your response, log both json_encode($dto) and the raw wire bytes. Whitespace, trailing commas, and numeric vs string IDs ("1" vs 1) are frequent culprits, and the DTO layer cannot fix an ID type you set incorrectly.
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.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource