Skip to content

Commit f9fe7c1

Browse files
committed
mcp
1 parent 68c3f02 commit f9fe7c1

2 files changed

Lines changed: 308 additions & 0 deletions

File tree

core/mcp.md

Lines changed: 307 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,307 @@
1+
# MCP: Exposing Your API to AI Agents
2+
3+
API Platform integrates with the [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) to expose your API resources as tools and resources that AI agents (LLMs) can discover and interact with.
4+
5+
MCP defines a standard way for AI models to discover available tools, understand their input/output schemas, and invoke them. API Platform leverages its existing metadata system to automatically generate MCP tool definitions from your resource classes.
6+
7+
## Installation
8+
9+
The MCP component requires the `mcp/sdk` PHP package:
10+
11+
```console
12+
composer require mcp/sdk
13+
```
14+
15+
On Symfony, you also need the [MCP Bundle](https://github.com/symfony-tools/mcp-bundle):
16+
17+
```console
18+
composer require symfony/mcp-bundle
19+
```
20+
21+
## Configuring the MCP Server
22+
23+
### Symfony
24+
25+
Enable the MCP server and configure the transport in your `config/packages/api_platform.yaml`:
26+
27+
```yaml
28+
# config/packages/api_platform.yaml
29+
api_platform:
30+
# ...
31+
32+
mcp:
33+
client_transports:
34+
http: true
35+
stdio: false
36+
http:
37+
path: '/mcp'
38+
session:
39+
store: 'file'
40+
directory: '%kernel.cache_dir%/mcp'
41+
ttl: 3600
42+
```
43+
44+
### Laravel
45+
46+
MCP is enabled by default in the Laravel configuration:
47+
48+
```php
49+
// config/api-platform.php
50+
return [
51+
// ...
52+
'mcp' => [
53+
'enabled' => true,
54+
],
55+
];
56+
```
57+
58+
The MCP endpoint is automatically registered at `/mcp`.
59+
60+
## Declaring MCP Tools
61+
62+
MCP tools let AI agents call operations on your API. Use the `McpTool` attribute inside the `mcp` option of `ApiResource`. Each tool gets a name (the array key), and its input schema is automatically derived from the class properties.
63+
64+
### Using `mcp` on an ApiResource
65+
66+
```php
67+
<?php
68+
// api/src/ApiResource/Book.php with Symfony or app/ApiResource/Book.php with Laravel
69+
namespace App\ApiResource;
70+
71+
use ApiPlatform\Metadata\ApiResource;
72+
use ApiPlatform\Metadata\McpTool;
73+
74+
#[ApiResource(
75+
operations: [],
76+
mcp: [
77+
'get_book_info' => new McpTool(
78+
provider: [self::class, 'provide']
79+
),
80+
'update_book_status' => new McpTool(
81+
processor: [self::class, 'process']
82+
),
83+
]
84+
)]
85+
class Book
86+
{
87+
public function __construct(
88+
private ?string $title = null,
89+
private ?string $isbn = null,
90+
private ?string $status = null,
91+
) {}
92+
93+
// getters and setters...
94+
95+
public static function provide(): self
96+
{
97+
return new self(title: 'API Platform Guide', isbn: '978-1234567890', status: 'available');
98+
}
99+
100+
public static function process($data): mixed
101+
{
102+
$data->setStatus('updated');
103+
104+
return $data;
105+
}
106+
}
107+
```
108+
109+
This declares two MCP tools: `get_book_info` (read via a [state provider](state-providers.md)) and `update_book_status` (write via a [state processor](state-processors.md)). The input schema for each tool is generated from the class properties.
110+
111+
Note that `operations: []` means no HTTP operations are exposed — you can combine MCP tools with regular REST/GraphQL operations on the same resource if needed.
112+
113+
### Using `McpTool` Directly as a Class Attribute
114+
115+
For standalone tools that don't need REST operations, you can use `McpTool` as a class-level attribute:
116+
117+
```php
118+
<?php
119+
namespace App\ApiResource;
120+
121+
use ApiPlatform\Metadata\McpTool;
122+
123+
#[McpTool(
124+
name: 'process_message',
125+
description: 'Process a message with priority',
126+
processor: [self::class, 'process']
127+
)]
128+
class ProcessMessage
129+
{
130+
public function __construct(
131+
private string $message,
132+
private int $priority = 1,
133+
) {}
134+
135+
// getters and setters...
136+
137+
public static function process($data): mixed
138+
{
139+
$data->setMessage('Processed: ' . $data->getMessage());
140+
141+
return $data;
142+
}
143+
}
144+
```
145+
146+
### Returning Custom Results
147+
148+
By default, tool results are serialized using API Platform's [serialization](serialization.md) system with structured content (JSON). If you need full control over the response, return a `CallToolResult` directly from your processor:
149+
150+
```php
151+
<?php
152+
namespace App\ApiResource;
153+
154+
use ApiPlatform\Metadata\ApiResource;
155+
use ApiPlatform\Metadata\McpTool;
156+
use Mcp\Schema\Content\TextContent;
157+
use Mcp\Schema\Result\CallToolResult;
158+
159+
#[ApiResource(
160+
operations: [],
161+
mcp: [
162+
'generate_report' => new McpTool(
163+
description: 'Generate a markdown report',
164+
processor: [self::class, 'process'],
165+
structuredContent: false
166+
),
167+
]
168+
)]
169+
class Report
170+
{
171+
public function __construct(
172+
private string $title,
173+
private string $content,
174+
) {}
175+
176+
// getters and setters...
177+
178+
public static function process($data): CallToolResult
179+
{
180+
$markdown = "# {$data->getTitle()}\n\n{$data->getContent()}";
181+
182+
return new CallToolResult(
183+
[new TextContent($markdown)],
184+
false
185+
);
186+
}
187+
}
188+
```
189+
190+
Setting `structuredContent: false` disables the automatic JSON serialization. When returning a `CallToolResult`, the response is sent as-is to the AI agent.
191+
192+
## Declaring MCP Resources
193+
194+
MCP resources expose read-only content that AI agents can retrieve — documentation, configuration files, reference data, etc. Use the `McpResource` attribute:
195+
196+
```php
197+
<?php
198+
namespace App\ApiResource;
199+
200+
use ApiPlatform\Metadata\ApiResource;
201+
use ApiPlatform\Metadata\McpResource;
202+
203+
#[ApiResource(
204+
operations: [],
205+
mcp: [
206+
'api_docs' => new McpResource(
207+
uri: 'resource://my-app/documentation',
208+
name: 'App-Documentation',
209+
description: 'Application API documentation',
210+
mimeType: 'text/markdown',
211+
provider: [self::class, 'provide']
212+
),
213+
]
214+
)]
215+
class Documentation
216+
{
217+
public function __construct(
218+
private string $content,
219+
private string $uri,
220+
) {}
221+
222+
// getters and setters...
223+
224+
public static function provide(): self
225+
{
226+
return new self(
227+
content: '# My API Documentation\n\nWelcome to the API.',
228+
uri: 'resource://my-app/documentation'
229+
);
230+
}
231+
}
232+
```
233+
234+
The `uri` must be unique across the MCP server and follows the `resource://` URI scheme.
235+
236+
## Structured Content
237+
238+
By default, MCP tools return structured content: the result is serialized to JSON using API Platform's normalizers and included alongside the text representation. This gives AI agents both a human-readable and a machine-parseable version of the response.
239+
240+
You can disable this per-tool with `structuredContent: false`, which is useful when the tool produces plain text or markdown that doesn't benefit from JSON serialization.
241+
242+
## Validation
243+
244+
MCP tool inputs support validation, using the same mechanisms as regular API Platform operations.
245+
246+
On Symfony, use [Symfony Validator constraints](../symfony/validation.md):
247+
248+
```php
249+
use Symfony\Component\Validator\Constraints as Assert;
250+
251+
class ContactForm
252+
{
253+
#[Assert\NotBlank]
254+
#[Assert\Length(min: 3, max: 50)]
255+
private ?string $name = null;
256+
257+
#[Assert\NotNull]
258+
#[Assert\Email]
259+
private ?string $email = null;
260+
}
261+
```
262+
263+
On Laravel, use [validation rules](../laravel/validation.md):
264+
265+
```php
266+
#[ApiResource(
267+
operations: [],
268+
mcp: [
269+
'submit_contact' => new McpTool(
270+
processor: [self::class, 'process'],
271+
rules: [
272+
'name' => 'required|min:3|max:50',
273+
'email' => 'required|email',
274+
]
275+
),
276+
]
277+
)]
278+
```
279+
280+
## McpTool Options
281+
282+
The `McpTool` attribute accepts all standard [operation options](operations.md) plus:
283+
284+
| Option | Description |
285+
|---|---|
286+
| `name` | Tool name exposed to AI agents (defaults to the `mcp` array key or method name) |
287+
| `description` | Human-readable description of the tool (defaults to class DocBlock) |
288+
| `structuredContent` | Whether to include JSON structured content in responses (default: `true`) |
289+
| `annotations` | MCP tool annotations describing behavior hints |
290+
| `icons` | List of icon URLs representing the tool |
291+
| `meta` | Arbitrary metadata |
292+
293+
## McpResource Options
294+
295+
The `McpResource` attribute accepts all standard [operation options](operations.md) plus:
296+
297+
| Option | Description |
298+
|---|---|
299+
| `uri` | Unique URI identifying this resource (required, uses `resource://` scheme) |
300+
| `name` | Human-readable name for the resource |
301+
| `description` | Description of the resource (defaults to class DocBlock) |
302+
| `structuredContent` | Whether to include JSON structured content (default: `true`) |
303+
| `mimeType` | MIME type of the resource content |
304+
| `size` | Size in bytes, if known |
305+
| `annotations` | MCP resource annotations |
306+
| `icons` | List of icon URLs |
307+
| `meta` | Arbitrary metadata |

outline.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ chapters:
5555
- dto
5656
- openapi
5757
- json-schema
58+
- mcp
5859
- mercure
5960
- push-relations
6061
- errors

0 commit comments

Comments
 (0)