The Model Context Protocol (MCP) is an open standard that allows AI agents to discover and invoke external tools at runtime. For a Drupal site, this means your CMS is no longer just a content repository - it becomes an active participant in an AI’s reasoning process.
An AI agent on its own can reason, but it lacks access to your private data, business logic, or real-time state. By implementing MCP tools in Drupal, you provide the "hands" the AI needs to interact with your system securely and predictably. To get started with the foundation of this integration, you can explore the official guide on implementing MCP for Drupal.
The most effective way to build MCP tools in Drupal is to follow a Service-Oriented Architecture (SOA). We treat the MCP layer as a thin communication bridge between the AI and your internal Drupal services.
In Drupal, MCP tools are implemented as Plugins. This layer should be kept "thin." Its only responsibilities are:
This is where the heavy lifting happens. By keeping the business logic in a standard Drupal Service (registered in services.yml), you ensure that the code is:
The drupal/mcp module uses a plugin manager to discover all classes annotated with the #[Mcp] attribute. When an AI agent connects, it uses getTools() to build the tool manifest, and routes calls using executeTool().
Create the Plugin File and Structure
The key rule for maintaining a future-proof interface is to keep the plugin thin: Validate → call service → format output. Everything else belongs in the service.
my_module/
├── my_module.info.yml (declare mcp:mcp as dependency)
├── my_module.services.yml (register your business logic service)
└── src/
├── Plugin/
│ └── Mcp/
│ └── YourPlugin.php (the MCP plugin — thin wrapper)
└── Service/
└── MyService.php (business logic goes here)src/Plugin/Mcp/YourPlugin.php in your custom module.Add the #[Mcp] attribute: Discovery is automatic via this attribute.
#[Mcp(
id: 'your-plugin-id', // unique, kebab-case
name: new TranslatableMarkup('Human Name'),
description: new TranslatableMarkup('What this plugin does.'),
)]
class YourPlugin extends McpPluginBase {
// ...
}Extend McpPluginBase and Inject services via create():
public static function create(ContainerInterface $container, ...): static {
$instance = parent::create(...);
$instance->myService = $container->get('my_module.my_service');
return $instance;
}Declare and Execute Tools
Declare tools in getTools(): Return one or more Tool objects, defining the name, description, and essential inputSchema.
Implement executeTool():
Check the tool ID matches (using sanitizeToolName()).
Validate the required arguments.
Call your service ($this->myService->doLogic($args)).
Return a structured response: ['structuredContent' => [...], 'content' => [['type' => 'text', 'text' => '...']]].
While the pattern applies to any tool (e.g., checking order status, creating a node, or fetching user profile data), a common use case is Semantic Search using a Vector Database.
In this scenario, we use the architecture to bridge an AI agent with a vector engine (like AWS Bedrock or Pinecone):
vector_search tool with a natural language query.SearchService.The description field in your tool schema is the "documentation" the LLM reads at runtime.
"id": "The entity ID.""id": "The unique UUID of the article you wish to retrieve. Use this when the user asks for a specific document by name."Always return data in a structured format (JSON). While most MCP clients can handle plain text, providing structured objects allows the AI to parse specific fields (like URLs or IDs) more reliably, reducing "hallucinations."
AI agents perform better when given full context rather than snippets. When returning data from a tool, avoid aggressive truncation. It is better to return three high-quality, full paragraphs than ten one-sentence snippets.
Building MCP tools in Drupal is a strategic shift toward Agentic Workflows. By keeping your plugins thin and your services robust, you create a future-proof interface that allows any AI agent to understand and interact with your Drupal ecosystem.
Final Steps for Deployment and Testing
ddev drush cr (The plugin manager needs to rediscover the new class).Test with Drush: Verify your service logic and plugin integration without expensive LLM calls. This is a crucial step for Testing and Verification.
ddev drush php-eval '
$plugin = \Drupal::service("plugin.manager.mcp")->createInstance("your-plugin-id", []);
$result = $plugin->executeTool("your_tool_name", ["arg" => "value"]);
print_r($result);