-
Notifications
You must be signed in to change notification settings - Fork 0
fix(mcp): one tool registry, filled at boot, read by the server #23
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,6 +7,7 @@ | |
| use Core\Api\Models\ApiKey; | ||
| use Core\Mcp\Dependencies\HasDependencies; | ||
| use Core\Mcp\Exceptions\MissingDependencyException; | ||
| use Core\Mod\Agentic\Mcp\Data\ToolMetadata; | ||
| use Core\Mod\Agentic\Mcp\Services\ToolDependencyService; | ||
| use Core\Mod\Agentic\Mcp\Tools\Agent\Contracts\AgentToolInterface; | ||
| use Illuminate\Support\Collection; | ||
|
|
@@ -37,6 +38,16 @@ class AgentToolRegistry | |
| */ | ||
| public function register(AgentToolInterface $tool): self | ||
| { | ||
| // Absorbed from the registry this replaced: two tools claiming one name | ||
| // is a wiring mistake, and silently keeping the last one registered | ||
| // means the MCP surface serves whichever file happened to load second. | ||
| if (isset($this->tools[$tool->name()])) { | ||
| throw new \InvalidArgumentException(sprintf( | ||
| 'Tool [%s] is already registered.', | ||
| $tool->name(), | ||
| )); | ||
| } | ||
|
|
||
| $this->tools[$tool->name()] = $tool; | ||
|
|
||
| // Auto-register dependencies if tool declares them | ||
|
|
@@ -363,4 +374,81 @@ private function enforceAndRecordRateLimit(ApiKey $apiKey, string $toolName): vo | |
| ); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Every registered tool as MCP metadata. | ||
| * | ||
| * Absorbed from Mcp\Services\ToolRegistry, which the stdio agent server | ||
| * read while Boot filled this registry instead — so the server listed | ||
| * nothing. One registry now, so there is one answer to "what tools exist". | ||
| * | ||
| * @return array<int, ToolMetadata> | ||
| * | ||
| * @example | ||
| * $registry->listTools(); | ||
| */ | ||
| public function listTools(): array | ||
| { | ||
| return array_values(array_map( | ||
| static fn (AgentToolInterface $tool): ToolMetadata => ToolMetadata::from($tool), | ||
| $this->tools, | ||
| )); | ||
| } | ||
|
|
||
| /** | ||
| * Resolve one tool as MCP metadata, or null when it is not registered. | ||
| * | ||
| * @example | ||
| * $registry->resolve('plan_create'); | ||
| */ | ||
| public function resolve(string $name): ?ToolMetadata | ||
| { | ||
| $tool = $this->tools[$name] ?? null; | ||
|
|
||
| return $tool === null ? null : ToolMetadata::from($tool); | ||
| } | ||
|
|
||
| /** | ||
| * Map each tool name to the identifiers it declares as dependencies. | ||
| * | ||
| * @return array<string, array<int, string>> | ||
| * | ||
| * @example | ||
| * $registry->buildDependencyGraph(); | ||
| */ | ||
| public function buildDependencyGraph(): array | ||
| { | ||
| $graph = []; | ||
|
|
||
| foreach ($this->tools as $name => $tool) { | ||
| $graph[$name] = ToolMetadata::from($tool)->dependencyIdentifiers(); | ||
| } | ||
|
|
||
| return $graph; | ||
| } | ||
|
|
||
| /** | ||
| * Invoke a tool directly, without the permission and dependency checks | ||
| * execute() applies. | ||
| * | ||
| * Kept distinct from execute() rather than merged into it: the stdio | ||
| * transport has no API key to check scopes against and runs its own quota | ||
| * and audit passes around this call, whereas execute() is the governed | ||
| * path used where an ApiKey is present. | ||
| * | ||
| * @throws \InvalidArgumentException If the tool is not registered | ||
| * | ||
| * @example | ||
| * $registry->call('plan_list', [], ['workspace_id' => 'ws-1']); | ||
| */ | ||
| public function call(string $name, array $arguments = [], array $context = []): mixed | ||
| { | ||
| $tool = $this->get($name); | ||
|
|
||
| if (! $tool) { | ||
| throw new \InvalidArgumentException(sprintf('Unknown tool [%s].', $name)); | ||
| } | ||
|
|
||
| return $tool->handle($arguments, $context); | ||
|
Comment on lines
+430
to
+452
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
ast-grep outline php/Mcp/Console/McpAgentServerCommand.php --match handleToolCall --view expanded
rg -n -C 6 \
'function handleToolCall|validateDependencies|recordToolCall|\$toolRegistry->call' \
php/Mcp/Console/McpAgentServerCommand.php \
php/Mcp/Services/ToolDependencyService.phpRepository: dAppCore/agent Length of output: 5562 🏁 Script executed: #!/bin/bash
set -euo pipefail
sed -n '232,325p' php/Mcp/Console/McpAgentServerCommand.php
printf '\n--- Tool dependency service full missing methods ---\n'
sed -n '1,135p' php/Mcp/Services/ToolDependencyService.php
printf '\n--- AgentToolRegistry outline and relevant sections ---\n'
ast-grep outline php/Services/AgentToolRegistry.php --view expanded
sed -n '1,140p' php/Services/AgentToolRegistry.php
sed -n '390,470p' php/Services/AgentToolRegistry.phpRepository: dAppCore/agent Length of output: 14941 Add dependency validation and call recording to The stdio 🤖 Prompt for AI Agents |
||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Register missing built-in tools when the registry has custom tools.
At Line 238, one unrelated pre-registered tool makes this method return. If a host registers a custom tool before this provider, the built-in MCP tools are never registered.
Check registration per built-in tool. Keep the duplicate-name failure when a different implementation claims a built-in name.
Proposed fix
🤖 Prompt for AI Agents