Allow simple creation of tools (discuss)
>>> [!note] Migrated issue <!-- Drupal.org comment --> <!-- Migrated from issue #3518120. --> Reported by: [marcus_johansson](https://www.drupal.org/user/385947) >>> <p>Create an extremely simple developer experience for creating tools that can be used by AI Agents, MCP but also perhaps ECA etc.</p> <h3 id="summary-problem-motivation">Problem/Motivation</h3> <p>Current system of using plugins for creation actions works well for what it was made for and working with the rest of Drupal but requires an understanding of Drupal's plugins that is not always necessary for very simple tools that do one thing are are controlled by systems such as agents or eca.</p> <p>By making it possible to create simple tools we open it up to no Drupal developers to simply add the tools they need and work inside drupal configuration to create their agents and agent swarms.</p> <h3>Proposed Addition</h3> <p>Note: This isn't to replace the current way of using plugins to create tools but provide a new simple approach that can be used where it makes sense (where the tool is onyl to be used by agents and eca and not general actions as part of a module).</p> <h3>Background motivations</h3> <p>This change is inspired with the changes in MCP. It's up for discussion to wait and see how MCP developers but the inspiration came from this in MCP</p> <p> I think in the new world of MCP this might be a good way of getting Drupal usage up, without necessarily needing to know PHP or Drupal. This issue is very much just for discussion at this time, since MCP is nowhere near production ready.</p> <p>With MCP (see <a href="https://www.drupal.org/project/mcp">https://www.drupal.org/project/mcp</a>) interoperabiility might actually be a solved concept, because it opens up for business processes (like ECA) and agents (like AI Agents) to talk directly to tools on other systems. So if a company for instance has a Python application that has self documentation, but are using Drupal for documentation, before MCP you could either manually publish updates or create some API on the Python side and some pull mechanism on the Drupal side. While some of this can be done with Feeds/Site Buidling, there might be some generic detail where you need to code and know Drupal.</p> <p>With MCP you could just generate a tool for it. </p> <p>MCP in Python for instance does this if you want a multiplcation tool:</p> <pre>@mcp.tool()<br>def add(a: int, b: int, sidenote: str) -&gt; int:<br>&nbsp;&nbsp;&nbsp; """<br>&nbsp;&nbsp;&nbsp; Adds two numbers.<br>&nbsp;&nbsp;&nbsp; """<br>&nbsp;&nbsp;&nbsp; return a + b</pre><p>MCP tools in nodejs does this</p> <pre>server.tool("add", "Add two numbers", { a: z.number(), b: z.number() }, async ({ a, b }) =&gt; ({<br>&nbsp; content: [{ type: "text", text: String(a + b) }],<br>}))</pre><p>Both are not super simple, but can be setup by someone with rudimental knowledge.</p> <p>However Drupal tools uses the plugin system at the current time and its quite complex to know where to put files. Say that you want to create a multpilcation tool, you would need to create the file src/Plugin/AiFunctionCall/Multiplicator.php with code like this (and note that this does not do Dependency Injection that makes it way harder):</p> <div class="codeblock"> <pre><span style="color: #000000"><span style="color: #0000BB">&lt;?php<br></span><span style="color: #007700">namespace </span><span style="color: #0000BB">Drupal</span><span style="color: #007700">\</span><span style="color: #0000BB">my_module</span><span style="color: #007700">\</span><span style="color: #0000BB">Plugin</span><span style="color: #007700">\</span><span style="color: #0000BB">AiFunctionCall</span><span style="color: #007700">;<br><br>use </span><span style="color: #0000BB">Drupal</span><span style="color: #007700">\</span><span style="color: #0000BB">ai</span><span style="color: #007700">\</span><span style="color: #0000BB">Attribute</span><span style="color: #007700">\</span><span style="color: #0000BB">FunctionCall</span><span style="color: #007700">;<br>use </span><span style="color: #0000BB">Drupal</span><span style="color: #007700">\</span><span style="color: #0000BB">Core</span><span style="color: #007700">\</span><span style="color: #0000BB">Plugin</span><span style="color: #007700">\</span><span style="color: #0000BB">Context</span><span style="color: #007700">\</span><span style="color: #0000BB">ContextDefinition</span><span style="color: #007700">;<br><br></span><span style="color: #FF8000">#[FunctionCall(<br>&nbsp; </span><span style="color: #0000BB">id</span><span style="color: #007700">: </span><span style="color: #DD0000">'my_module:multiplicator'</span><span style="color: #007700">,<br>&nbsp; </span><span style="color: #0000BB">function_name</span><span style="color: #007700">: </span><span style="color: #DD0000">'my_module_multiplicator'</span><span style="color: #007700">,<br>&nbsp; </span><span style="color: #0000BB">name</span><span style="color: #007700">: </span><span style="color: #DD0000">'Multiplicator'</span><span style="color: #007700">,<br>&nbsp; </span><span style="color: #0000BB">description</span><span style="color: #007700">: </span><span style="color: #DD0000">'This function adds two numbers together'</span><span style="color: #007700">,<br>&nbsp; </span><span style="color: #0000BB">group</span><span style="color: #007700">: </span><span style="color: #DD0000">'some_group'</span><span style="color: #007700">,<br>&nbsp; </span><span style="color: #0000BB">module_dependencies</span><span style="color: #007700">: [],<br>&nbsp; </span><span style="color: #0000BB">context_definitions</span><span style="color: #007700">: [<br>&nbsp;&nbsp;&nbsp; </span><span style="color: #DD0000">'number_a' </span><span style="color: #007700">=&gt; new </span><span style="color: #0000BB">ContextDefinition</span><span style="color: #007700">(<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span><span style="color: #0000BB">data_type</span><span style="color: #007700">: </span><span style="color: #DD0000">'integer'</span><span style="color: #007700">,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span><span style="color: #0000BB">label</span><span style="color: #007700">: new </span><span style="color: #0000BB">TranslatableMarkup</span><span style="color: #007700">(</span><span style="color: #DD0000">"Number A"</span><span style="color: #007700">),<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span><span style="color: #0000BB">description</span><span style="color: #007700">: new </span><span style="color: #0000BB">TranslatableMarkup</span><span style="color: #007700">(</span><span style="color: #DD0000">"The first number."</span><span style="color: #007700">),<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span><span style="color: #0000BB">required</span><span style="color: #007700">: </span><span style="color: #0000BB">TRUE</span><span style="color: #007700">,<br>&nbsp;&nbsp;&nbsp; ),<br>&nbsp;&nbsp;&nbsp; </span><span style="color: #DD0000">'number_b' </span><span style="color: #007700">=&gt; new </span><span style="color: #0000BB">ContextDefinition</span><span style="color: #007700">(<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span><span style="color: #0000BB">data_type</span><span style="color: #007700">: </span><span style="color: #DD0000">'integer'</span><span style="color: #007700">,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span><span style="color: #0000BB">label</span><span style="color: #007700">: new </span><span style="color: #0000BB">TranslatableMarkup</span><span style="color: #007700">(</span><span style="color: #DD0000">"Number B"</span><span style="color: #007700">),<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span><span style="color: #0000BB">description</span><span style="color: #007700">: new </span><span style="color: #0000BB">TranslatableMarkup</span><span style="color: #007700">(</span><span style="color: #DD0000">"The second number."</span><span style="color: #007700">),<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span><span style="color: #0000BB">required</span><span style="color: #007700">: </span><span style="color: #0000BB">TRUE</span><span style="color: #007700">,<br>&nbsp;&nbsp;&nbsp; ),<br>&nbsp; ),<br>)]<br>class </span><span style="color: #0000BB">Multiplicate </span><span style="color: #007700">extends </span><span style="color: #0000BB">FunctionCallBase </span><span style="color: #007700">implements </span><span style="color: #0000BB">ExecutableFunctionCallInterface </span><span style="color: #007700">{<br><br>&nbsp; </span><span style="color: #FF8000">/**<br>&nbsp;&nbsp; * The result.<br>&nbsp;&nbsp; *<br>&nbsp;&nbsp; * @var string<br>&nbsp;&nbsp; */<br>&nbsp; </span><span style="color: #007700">protected </span><span style="color: #0000BB">string $result</span><span style="color: #007700">;<br><br>&nbsp; </span><span style="color: #FF8000">/**<br>&nbsp;&nbsp; * {@inheritdoc}<br>&nbsp;&nbsp; */<br>&nbsp; </span><span style="color: #007700">public function </span><span style="color: #0000BB">execute</span><span style="color: #007700">() {<br>&nbsp;&nbsp;&nbsp; </span><span style="color: #0000BB">$number_a </span><span style="color: #007700">= </span><span style="color: #0000BB">$this</span><span style="color: #007700">-&gt;</span><span style="color: #0000BB">getContextValue</span><span style="color: #007700">(</span><span style="color: #DD0000">'number_a'</span><span style="color: #007700">);<br>&nbsp;&nbsp;&nbsp; </span><span style="color: #0000BB">$number_b </span><span style="color: #007700">= </span><span style="color: #0000BB">$this</span><span style="color: #007700">-&gt;</span><span style="color: #0000BB">getContextValue</span><span style="color: #007700">(</span><span style="color: #DD0000">'number_b'</span><span style="color: #007700">);<br>&nbsp;&nbsp;&nbsp; </span><span style="color: #0000BB">$this</span><span style="color: #007700">-&gt;</span><span style="color: #0000BB">result </span><span style="color: #007700">= </span><span style="color: #0000BB">$number_a </span><span style="color: #007700">+ </span><span style="color: #0000BB">$number_b</span><span style="color: #007700">;<br>&nbsp; }<br><br>&nbsp; </span><span style="color: #FF8000">/**<br>&nbsp;&nbsp; * {@inheritdoc}<br>&nbsp;&nbsp; */<br>&nbsp; </span><span style="color: #007700">public function </span><span style="color: #0000BB">getReadableOutput</span><span style="color: #007700">(): </span><span style="color: #0000BB">string </span><span style="color: #007700">{<br>&nbsp;&nbsp;&nbsp; return (string) </span><span style="color: #0000BB">$this</span><span style="color: #007700">-&gt;</span><span style="color: #0000BB">result</span><span style="color: #007700">;<br>&nbsp; }<br><br>}<br></span><span style="color: #0000BB">?&gt;</span></span></pre></div> <p>What if we instead could create the file tools/multiplicator.tool.php</p> <p>with the following code</p> <div class="codeblock"> <pre><span style="color: #000000"><span style="color: #0000BB">&lt;?php<br></span><span style="color: #FF8000">/**<br>&nbsp; * This tools multiplies two numbers.<br>&nbsp; *<br>&nbsp; *&nbsp; @param int $number_a<br>&nbsp; *&nbsp;&nbsp; The first number to multiply.<br>&nbsp; *&nbsp; @param int $number_b<br>&nbsp; *&nbsp;&nbsp;&nbsp; The second number to multiply.<br>&nbsp; *<br>&nbsp; *&nbsp; @return string<br>&nbsp; *&nbsp;&nbsp;&nbsp; The returned value.<br>&nbsp; */<br></span><span style="color: #007700">function </span><span style="color: #0000BB">my_module_multiplicator</span><span style="color: #007700">(</span><span style="color: #0000BB">int $number_a</span><span style="color: #007700">, </span><span style="color: #0000BB">int $number_b</span><span style="color: #007700">): </span><span style="color: #0000BB">string </span><span style="color: #007700">{<br>&nbsp; return </span><span style="color: #0000BB">$number_a </span><span style="color: #007700">+ </span><span style="color: #0000BB">$number_b<br></span><span style="color: #007700">}<br></span><span style="color: #0000BB">?&gt;</span></span></pre></div> <p>The phpdoc might not be obvious to people at the beginning, but it should be easy enough to log when its not following correct standards, so people know why there tool does not show up.</p> <p>We could make sure that these files are not in the autoloader and only included when used. Discovery would happen on cache similar to plugin system and they would have their own linter system. Multiple tools could be added in one file. They should be able to use Drupal static class abstraction, so do \Drupal::entityTypeManager-&gt;... etc.</p> <p>It would use a Plugin Decorator to actually create the correct plugin code.</p> <p>This would open up a new world for people that wants to add some smaller logic to Agents or ECA without having to know the whole of Drupal.</p> <p>Thoughts?</p>
issue