Fluent PHP builder for MCP tool definitions
php-mcp-tool-definition
Zero-dependency PHP library for describing Model Context Protocol (MCP) tool definitions via typed PHP classes.
Produces the exact JSON structure that MCP clients and servers expect, with full support for all four official protocol versions.
Table of Contents
- Requirements
- Installation
- Protocol Version Support
- Quick Start
- Class Reference
- Usage Examples
- Development
- License
Requirements
- PHP >= 8.2
No runtime dependencies. PHPUnit 11 is required for running tests.
Installation
composer require sw9t/php-mcp-tool-definition
Protocol Version Support
The library ships one namespace per official MCP protocol version. Each version is a class that extends the previous one — fields are strictly additive, so you always get a superset of the older version's output.
| Namespace | Protocol date | New fields |
|--------------------------------------|---------------|------------------------------------------|
| McpToolDefinition\V20241105 | 2024-11-05 | name, description, inputSchema |
| McpToolDefinition\V20250326 | 2025-03-26 | annotations (title + behaviour hints) |
| McpToolDefinition\V20250618 | 2025-06-18 | title (top-level), outputSchema |
| McpToolDefinition\V20251125 | 2025-11-25 | execution (task support mode) |
| McpToolDefinition\Latest | always latest | alias — points to the most recent above |
McpSchema and McpProperty are shared across all versions — import them from the root namespace.
Quick Start
Use McpToolDefinition\Latest to always stay on the most recent protocol version,
or pick a specific versioned namespace if you need to pin to a particular protocol date.
use McpToolDefinition\McpProperty;
use McpToolDefinition\McpSchema;
use McpToolDefinition\PropertyType;
use McpToolDefinition\Latest\McpToolDefinition;
use McpToolDefinition\Latest\McpToolAnnotations;
use McpToolDefinition\Latest\McpToolExecution;
use McpToolDefinition\V20251125\TaskSupport; // enum — import from version namespace
$tool = new McpToolDefinition(
name: 'search_products',
description: 'Searches the product catalogue and returns matching items',
inputSchema: new McpSchema([
McpProperty::string('query', 'Search term', required: true),
McpProperty::integer('limit', 'Maximum number of results (default 20)'),
McpProperty::string('sort', 'Sort order', enum: ['asc', 'desc']),
]),
annotations: new McpToolAnnotations(readOnlyHint: true),
title: 'Search Products',
execution: new McpToolExecution(TaskSupport::Optional),
);
$payload = $tool->toArray(); // pass to json_encode() or your MCP server
Class Reference
Latest — always current version
The McpToolDefinition\Latest namespace is a stable alias that always points to the most
recent protocol version. Use this if you don't need to pin to a specific protocol version.
When a new MCP protocol version is released and added to the library, the Latest files are
updated to point to it — your use statements stay untouched.
use McpToolDefinition\Latest\McpToolDefinition; // → V20251125\McpToolDefinition
use McpToolDefinition\Latest\McpToolAnnotations; // → V20250326\McpToolAnnotations
use McpToolDefinition\Latest\McpToolExecution; // → V20251125\McpToolExecution
use McpToolDefinition\V20251125\TaskSupport; // enum — PHP cannot extend enums,
// import directly from version namespace
Why is
TaskSupportnot inLatest? PHP does not support extending enums, so it is impossible to create aLatest\TaskSupportthat is a subtype ofV20251125\TaskSupport. Import it directly from the version namespace — it is a simple backed enum and unlikely to change between versions.
McpSchema
Represents a JSON Schema object. Wraps an array of McpProperty instances and builds
the properties map and required array automatically.
use McpToolDefinition\McpSchema;
use McpToolDefinition\McpProperty;
$schema = new McpSchema([
McpProperty::string('name', 'Full name', required: true),
McpProperty::integer('age', 'Age in years'),
]);
When constructed with no arguments it produces {"type":"object","properties":{}} — a valid
empty schema for tools that accept no input.
McpProperty
Represents one property inside a schema. All factory methods accept $name, $description,
and $required. The string factory additionally accepts $enum.
| Factory | JSON type | Extra parameters |
|---|---|---|
| McpProperty::string($name, $desc, $req, $enum, $maxLength) | string | enum: list<string>, maxLength: int |
| McpProperty::integer($name, $desc, $req, $min, $max, $enum) | integer | minimum, maximum: int\|float, enum: list<int> |
| McpProperty::number($name, $desc, $req, $min, $max) | number | minimum, maximum: int\|float |
| McpProperty::boolean($name, $desc, $req) | boolean | — |
| McpProperty::object($name, McpSchema, $desc, $req) | object | — |
| McpProperty::array($name, McpSchema\|PropertyType, $desc, $req, $minItems, $maxItems) | array | minItems, maxItems: int |
Pass PropertyType::StringType (or any other PropertyType) as $items to produce a primitive item schema
(e.g. "items": {"type": "string"}). Pass a McpSchema for arrays of objects.
V20241105 — McpToolDefinition
Minimal tool definition. Suitable for servers running the initial 2024-11-05 protocol.
use McpToolDefinition\V20241105\McpToolDefinition;
new McpToolDefinition(
name: string, // required — programmatic identifier
description: string, // required — shown to the LLM
inputSchema: McpSchema, // optional — defaults to empty schema
)
V20250326 — McpToolDefinition + McpToolAnnotations
Adds annotations with a display title and four behavioural hints.
use McpToolDefinition\V20250326\McpToolDefinition;
use McpToolDefinition\V20250326\McpToolAnnotations;
new McpToolDefinition(/* ... */, annotations: McpToolAnnotations)
new McpToolAnnotations(
title: ?string, // display name shown in UIs
readOnlyHint: ?bool, // true → tool never modifies state
destructiveHint: ?bool, // true → tool may make irreversible changes
idempotentHint: ?bool, // true → repeated calls with same args are safe
openWorldHint: ?bool, // true → tool may call external systems
)
All annotation fields are optional; null values are omitted from the output.
V20250618 — McpToolDefinition
Adds a top-level title field and outputSchema.
Display name precedence (highest to lowest): title → annotations.title → name.
use McpToolDefinition\V20250618\McpToolDefinition;
new McpToolDefinition(/* ... */, title: ?string, outputSchema: ?McpSchema)
V20251125 — McpToolDefinition + McpToolExecution + TaskSupport
Adds execution to support long-running tools via the MCP task polling mechanism.
use McpToolDefinition\V20251125\McpToolDefinition;
use McpToolDefinition\V20251125\McpToolExecution;
use McpToolDefinition\V20251125\TaskSupport;
new McpToolDefinition(/* ... */, execution: ?McpToolExecution)
new McpToolExecution(taskSupport: TaskSupport)
TaskSupport::Forbidden // synchronous only (default)
TaskSupport::Optional // client chooses sync or task
TaskSupport::Required // task polling required
Usage Examples
Tool without parameters
use McpToolDefinition\V20241105\McpToolDefinition;
$tool = new McpToolDefinition(
name: 'ping',
description: 'Returns server uptime and current timestamp',
);
// toArray() output:
// {
// "name": "ping",
// "description": "Returns server uptime and current timestamp",
// "inputSchema": { "type": "object", "properties": {} }
// }
Tool with typed parameters
use McpToolDefinition\McpProperty;
use McpToolDefinition\McpSchema;
use McpToolDefinition\V20241105\McpToolDefinition;
$tool = new McpToolDefinition(
name: 'get_user',
description: 'Retrieves a user by ID',
inputSchema: new McpSchema([
McpProperty::string('id', 'User UUID', required: true),
McpProperty::boolean('full', 'Include full profile'),
]),
);
Enum constraint
// String enum
McpProperty::string(
name: 'status',
description: 'Filter by order status',
enum: ['pending', 'processing', 'shipped', 'delivered', 'cancelled'],
)
// Integer enum
McpProperty::integer(
name: 'mode',
description: '0 = include keywords containing the phrase, 1 = exclude them',
enum: [0, 1],
)
Numeric constraints
McpProperty::integer('limit', 'Results per page', minimum: 1, maximum: 100)
McpProperty::integer('offset', 'Pagination offset', minimum: 0)
McpProperty::number('score', 'Relevance score (0–1)', minimum: 0.0, maximum: 1.0)
McpProperty::string('slug', 'URL-friendly identifier', maxLength: 100)
Array of primitives
// Simple array of strings — emits "items": {"type": "string"}
McpProperty::array('tags', PropertyType::StringType, 'List of tags')
// Array of integers with length constraints
McpProperty::array('ids', PropertyType::IntegerType, 'Selected IDs', minItems: 1, maxItems: 50)
Nested object
use McpToolDefinition\McpProperty;
use McpToolDefinition\McpSchema;
McpProperty::object(
name: 'address',
properties: new McpSchema([
McpProperty::string('street', 'Street and house number', required: true),
McpProperty::string('city', 'City name', required: true),
McpProperty::string('zip', 'ZIP / postal code', required: true),
McpProperty::string('country','ISO 3166-1 alpha-2 code'),
]),
description: 'Shipping address',
required: true,
)
Array of objects
McpProperty::array(
name: 'line_items',
items: new McpSchema([
McpProperty::string('sku', 'Product SKU', required: true),
McpProperty::integer('qty', 'Quantity', required: true),
McpProperty::number('unit_price', 'Price per unit'),
]),
description: 'Items in the order',
required: true,
)
Annotations and hints (2025-03-26+)
use McpToolDefinition\V20250326\McpToolAnnotations;
use McpToolDefinition\V20250326\McpToolDefinition;
// Read-only search — safe to call at any time
$tool = new McpToolDefinition(
name: 'search_logs',
description: 'Searches application logs',
annotations: new McpToolAnnotations(
title: 'Search Logs',
readOnlyHint: true,
),
);
// Dangerous delete — client should warn the user
$tool = new McpToolDefinition(
name: 'delete_account',
description: 'Permanently deletes a user account and all associated data',
annotations: new McpToolAnnotations(
title: 'Delete Account',
destructiveHint: true,
idempotentHint: false,
),
);
Output schema (2025-06-18+)
use McpToolDefinition\McpProperty;
use McpToolDefinition\McpSchema;
use McpToolDefinition\V20250618\McpToolDefinition;
$tool = new McpToolDefinition(
name: 'create_invoice',
description: 'Creates and returns a new invoice',
title: 'Create Invoice',
inputSchema: new McpSchema([
McpProperty::string('customer_id', 'Customer ID', required: true),
McpProperty::number('amount', 'Invoice amount', required: true),
]),
outputSchema: new McpSchema([
McpProperty::string('invoice_id', 'Created invoice ID', required: true),
McpProperty::string('pdf_url', 'Download URL', required: true),
McpProperty::string('status', 'Invoice status', required: true),
]),
);
Long-running task execution (2025-11-25+)
use McpToolDefinition\V20251125\McpToolDefinition;
use McpToolDefinition\V20251125\McpToolExecution;
use McpToolDefinition\V20251125\TaskSupport;
// Tool that always needs task polling (e.g. a 30-second data export)
$tool = new McpToolDefinition(
name: 'export_report',
description: 'Generates and exports a full sales report — may take up to 2 minutes',
execution: new McpToolExecution(TaskSupport::Required),
);
// Tool that supports both sync and task modes
$tool = new McpToolDefinition(
name: 'send_email',
description: 'Sends an email via the configured SMTP provider',
execution: new McpToolExecution(TaskSupport::Optional),
);
Full example — latest version
use McpToolDefinition\McpProperty;
use McpToolDefinition\McpSchema;
use McpToolDefinition\Latest\McpToolAnnotations;
use McpToolDefinition\Latest\McpToolDefinition;
use McpToolDefinition\Latest\McpToolExecution;
use McpToolDefinition\V20251125\TaskSupport;
$tool = new McpToolDefinition(
name: 'process_order',
description: 'Validates, charges, and fulfils an order. May take 10–30 seconds.',
inputSchema: new McpSchema([
McpProperty::string('order_id', 'Order identifier', required: true),
McpProperty::string('coupon_code', 'Optional discount code'),
McpProperty::boolean('notify', 'Send confirmation email'),
]),
annotations: new McpToolAnnotations(
title: 'Process Order',
destructiveHint: true,
idempotentHint: false,
),
title: 'Process Order',
outputSchema: new McpSchema([
McpProperty::string('status', 'Fulfilment status', required: true),
McpProperty::string('tracking_id', 'Shipment tracking ID'),
]),
execution: new McpToolExecution(TaskSupport::Optional),
);
echo json_encode($tool->toArray(), JSON_PRETTY_PRINT);
Development
make install # install dependencies (vendor lands on host for IDE support)
make test # run the PHPUnit test suite inside Docker
make shell # open bash inside the container
make build # rebuild the Docker image
No local PHP installation required — all commands run inside Docker.
License
MIT — see LICENSE.