agentsclimarketplace

Api platform mcp

Skill api-platform/skillset/skills/api-platform-mcp

API Platform agent skills

Install
npx -y skills add api-platform/skillset --skill api-platform-mcp

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

2 things to look at

  • no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
  • 24 stars24 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

Exposes API Platform resources to AI agents (LLMs) via the Model Context Protocol — declaring MCP tools with `#[McpTool]` / `McpToolCollection`, read-only `#[McpResource]`, the `mcp:` array on `#[ApiResource]`, input/output DTOs, structured vs custom (CallToolResult) responses, JSON-Schema overrides via `#[ApiProperty]`, and MCP-specific validation. Use whenever the user wants an LLM/AI agent to discover or call their API, mentions MCP, Model Context Protocol, tools/resources for an agent, `tools/call`, `tools/list`, or asks to make a resource 'callable by an AI' — even if they don't name MCP explicitly.

SKILL.md

11.5 KB, as published. Nobody here has run it

MCP: Exposing Your API to AI Agents

API Platform turns PHP classes into Model Context Protocol tools and resources. It reuses the existing metadata stack — state processors/providers, validation, serialization, JSON Schema — so an MCP tool is just an operation with no HTTP route.

@experimental. API may change between minor releases.

Install & enable

Symfony — composer require api-platform/mcp symfony/mcp-bundle (bundle namespace is Symfony\AI\McpBundle, composer name symfony/mcp-bundle):

# config/packages/mcp.yaml
mcp:
    client_transports:
        http: true
        stdio: false
    http:
        path: "/mcp"
        session: { store: "file", directory: "%kernel.cache_dir%/mcp", ttl: 3600 }
# config/packages/api_platform.yaml
api_platform:
    mcp:
        enabled: true     # default: true
        format: jsonld    # default; must be a registered api_platform.format (json, jsonapi, …)

format sets the serialization format for structured content. jsonld emits @context, @id, @type. Laravel — enabled by default, endpoint auto-registered at /mcp:

// config/api-platform.php
return [ /* … */ 'mcp' => ['enabled' => true] ];

API Platform registers its own SDK Handler; supports() returns true only for tools/resources in its registry, otherwise the SDK's own handler chain runs. API Platform tools and native mcp/sdk tools coexist on the same server.

Two ways to declare a tool

Standalone #[McpTool] class attribute — convenient for a one-off tool. Class properties become the inputSchema; a state processor handles the command (CQRS-style — the agent's input is processed through your logic):

use ApiPlatform\Metadata\McpTool;

#[McpTool(
    name: 'process_message',
    description: 'Process a message with priority',
    processor: [self::class, 'process'],
)]
class ProcessMessage
{
    public function __construct(
        private string $message,
        private int $priority = 1,
    ) {}

    // getters + setters drive (de)serialization

    public static function process($data): mixed
    {
        $data->setMessage('Processed: '.$data->getMessage());
        return $data;
    }
}

The agent's arguments are deserialized into a ProcessMessage, passed to the processor; the returned object is serialized back as structured content. processor is any callable or a ProcessorInterface service.

mcp: array on #[ApiResource] — use when the class exposes no HTTP endpoints (operations: []), combines several MCP operations, or needs resource-level control. Keys are the tool names, so omit name: inside each new McpTool(...):

use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\McpTool;
use App\State\ReadHydraResourceProcessor;

#[ApiResource(
    operations: [],
    mcp: [
        'read_hydra_resource' => new McpTool(
            description: 'Navigate to a Hydra API resource by URI.',
            processor: ReadHydraResourceProcessor::class,
            structuredContent: false,
        ),
    ],
)]
class ReadHydraResource
{
    public string $uri;
}

Separate input DTO

When the input schema must differ from the result class, point input: at a DTO. That DTO drives inputSchema; the host class describes the output:

#[McpTool(
    name: 'search_books',
    description: 'Search books by keyword',
    input: SearchQuery::class,        // what the agent sends
    processor: SearchBooksProcessor::class,
)]
class BookSearchResult
{
    public int $id;
    public string $title;
    public string $isbn;
}

The processor receives a SearchQuery instance and returns the result(s).

Collections — McpToolCollection

Use McpToolCollection (extends McpTool, implements CollectionOperationInterface) when a tool returns a list — the serializer and schema factory emit collection-shaped output:

use ApiPlatform\Metadata\McpToolCollection;

#[ApiResource(operations: [], mcp: [
    'list_books' => new McpToolCollection(
        description: 'List Books',
        input: SearchQuery::class,
        processor: SearchBooksProcessor::class,
        structuredContent: true,
    ),
])]
class Book { public ?int $id = null; public ?string $title = null; public ?string $isbn = null; }

With structuredContent: true and the default jsonld format, the response carries @context, hydra:totalItems, and a hydra:member array of serialized items; the published outputSchema reflects that shape. Output schemas are flattened for MCP compliance — no $ref, allOf, $schema, or definitions.

Custom responses — CallToolResult

Default: results serialize through API Platform's serialization as structured JSON. For full control, set structuredContent: false and return a CallToolResult:

use Mcp\Schema\Content\TextContent;
use Mcp\Schema\Result\CallToolResult;

#[McpTool(name: 'generate_report', description: 'Generate a markdown report',
    processor: [self::class, 'process'], structuredContent: false)]
class Report
{
    public function __construct(private string $title, private string $content) {}
    // getters/setters …

    public static function process($data): CallToolResult
    {
        return new CallToolResult(
            [new TextContent("# {$data->getTitle()}\n\n{$data->getContent()}")],
            false,               // isError
            // 3rd arg: optional _meta array
        );
    }
}

structuredContent: false disables automatic JSON serialization; the CallToolResult is sent as-is, with no structuredContent / _meta key in the response.

Schema overrides — the nullable-union gotcha

PHP nullable types generate union schemas like {"type": ["array", "null"]}. Some LLM providers reject these. Override per property with #[ApiProperty(schema: [...])]:

use ApiPlatform\Metadata\ApiProperty;

class InvokeHydraOperation
{
    public string $uri;
    public string $method;
    #[ApiProperty(schema: ['type' => 'object', 'description' => 'JSON payload for the request'])]
    public ?array $payload = null;   // without override: {"type": ["array","null"]}
}

Validation — off by default

The MCP SDK validates inputs against the JSON Schema at transport level (types, required fields). API Platform's own validation pipeline is disabled for MCP tools. Set validate: true to run business constraints (email format, length, custom rules):

use Symfony\Component\Validator\Constraints as Assert;

#[McpTool(name: 'submit_contact', description: 'Submit a contact form',
    processor: [self::class, 'process'], validate: true)]
class ContactForm
{
    #[Assert\NotBlank] #[Assert\Length(min: 3, max: 50)]
    private ?string $name = null;
    #[Assert\NotNull] #[Assert\Email]
    private ?string $email = null;
}

Laravel — same validate: true, plus rules: instead of constraints:

#[McpTool(name: 'submit_contact', description: '…', processor: [self::class, 'process'],
    validate: true, rules: ['name' => 'required|min:3|max:50', 'email' => 'required|email'])]

Read-only resources — #[McpResource]

Expose retrievable content (docs, config, reference data) backed by a state provider. uri must be unique and use the resource:// scheme:

use ApiPlatform\Metadata\McpResource;

#[ApiResource(operations: [], mcp: [
    'api_docs' => new McpResource(
        uri: 'resource://my-app/documentation',
        name: 'App-Documentation',
        description: 'Application API documentation',
        mimeType: 'text/markdown',
        provider: [self::class, 'provide'],
    ),
])]
class Documentation
{
    public function __construct(private string $content, private string $uri) {}
    public static function provide(): self
    {
        return new self('# My API Documentation\n\nWelcome.', 'resource://my-app/documentation');
    }
}

Options

McpTool and McpResource extend HttpOperation, so they accept all standard operation options. MCP-specific / notable ones:

OptionToolResNotes
nameexposed to agents; defaults to class short name. Omit in mcp: array (key wins)
descriptiondefaults to class DocBlock
structuredContentinclude JSON structured content (default true)
input / outputseparate DTO for input schema / output representation
inputFormats / outputFormats(de)serialization formats, e.g. ['json'], ['jsonld']
contentNegotiationdefault false for MCP
validaterun validation pipeline (default false for MCP)
rulesLaravel validation rules (Laravel only)
annotations/icons/metabehavior hints / icon URLs / arbitrary metadata
urirequired, resource:// scheme, unique across server
mimeType / sizecontent MIME type / byte size if known

Wire protocol (for testing)

Everything is JSON-RPC 2.0 POSTed to /mcp with Accept: application/json, text/event-stream. initialize returns an mcp-session-id header; send it back on every subsequent call. tools/list / resources/list enumerate; tools/call (params: {name, arguments}) and resources/read (params: {uri}) invoke. The handler overrides Accept to the operation's output format, so a jsonld tool always responds JSON-LD regardless of use_symfony_listeners.

Laravel

  • Config under mcp in config/api-platform.php; endpoint auto-registered at /mcp.
  • Validation uses rules: (string/array/closure/FormRequest), not Symfony constraints.
  • MongoDB is not supported for MCP.

Checklist

  • api-platform/mcp + symfony/mcp-bundle installed, /mcp transport configured (Symfony)
  • Tool class has a processor (write) or McpResource has a provider (read)
  • operations: [] when the class is MCP-only (no HTTP routes)
  • name: omitted inside mcp: array entries (the array key is the name)
  • McpToolCollection (not McpTool) for list-returning tools
  • structuredContent: false + CallToolResult when bypassing JSON serialization
  • Nullable/union properties overridden with #[ApiProperty(schema: …)] if the LLM rejects them
  • validate: true set explicitly where business constraints must run
  • McpResource uri is unique and uses resource://

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.