Skip to main content
Version: Next

Providing MCP Tools

An MCP tool is a single capability (read a data object, run a search, …) that a bundle contributes to the Studio Backend. A tool is an SDK-native #[McpTool] service — the same shape the mcp/sdk package and the Pimcore Agent bundle use — that you opt in with a tag. Administrators then assign it to a runtime-configured MCP server.

This is the tool-author counterpart to the lower-level "implement a whole MCP server in a bundle" recipe in MCP Server Infrastructure: you provide tools, and which servers expose them and who may use them is configured at runtime.

Experimental. The feature is under active development and may change between minor versions.

The contract​

A tool is a plain service with a public method carrying the Mcp\Capability\Attribute\McpTool attribute; the method returns an Mcp\Schema\Result\CallToolResult. There is no bundle-specific interface — you use the SDK types directly. Parameters are described with Mcp\Capability\Attribute\Schema, and the input JSON Schema is inferred from them.

namespace App\Mcp\Tool;

use Mcp\Capability\Attribute\McpTool;
use Mcp\Capability\Attribute\Schema;
use Mcp\Schema\Content\TextContent;
use Mcp\Schema\Result\CallToolResult;
use Mcp\Schema\ToolAnnotations;
use Pimcore\Model\DataObject;

final class GetObjectNameTool
{
#[McpTool(
name: 'get_object_name',
title: 'Get Object Name',
description: 'Returns the key of a data object by id.',
annotations: new ToolAnnotations(readOnlyHint: true, idempotentHint: true),
)]
public function execute(
#[Schema(type: 'integer', description: 'The data object id.', minimum: 1)]
int $id,
): CallToolResult {
$object = DataObject::getById($id);
if ($object === null) {
return new CallToolResult([new TextContent('No object with that id.')], isError: true);
}

return new CallToolResult([new TextContent($object->getKey())]);
}
}

A service may carry more than one #[McpTool] method; each becomes a separate tool. The name defaults to the method name and the description to the method's DocBlock (summary and description) when omitted.

Registering the tool​

Tools are collected explicitly by tag — so a bundle chooses which of its #[McpTool] services are exposed as assignable studio tools:

# config/services.yaml
services:
App\Mcp\Tool\GetObjectNameTool:
autowire: true
tags: ['pimcore.studio_backend.mcp_tool']

A compiler pass reflects each tagged service's #[McpTool] methods into the tool registry and builds a service locator the MCP server uses to resolve the backing service at call time. Requirements enforced at build time:

  • a tagged service must expose at least one #[McpTool] method (otherwise the container fails to compile);
  • tool names must be unique across all tagged services.

The tool then appears in the catalogue (GET /pimcore-studio/api/mcp/tools) and becomes assignable to a server.

Annotations and the required scope​

ToolAnnotations mirrors the MCP annotation set and determines the tool's required OAuth scope:

AnnotationMeaning
readOnlyHintThe tool does not modify state. true ⇒ scope mcp:read; otherwise mcp:write.
destructiveHintThe tool may perform destructive updates (hint).
idempotentHintRepeated identical calls have no additional effect (hint).
openWorldHintThe tool interacts with the outside world (hint).

The fail-safe default (no readOnlyHint, or false) is treated as a write. A server's advertised scopes are the union of its tools' required scopes, unless the server declares its own. They are what the server's OAuth protected resource advertises as scopes_supported; a granted scope is not yet checked against the tool being called.

Results and errors​

Return a CallToolResult built from TextContent (or other Mcp\Schema\Content\* types). For a tool-level failure the caller should see, return a result with isError: true and a message you are happy to expose, or throw Mcp\Exception\ToolCallException (the SDK converts it to an error result). Uncaught exceptions are sanitized by a shared terminal error boundary that forwards no internal exception message, so never rely on an exception message reaching the caller.