-
Notifications
You must be signed in to change notification settings - Fork 33
Closed
Description
Registering a tool using an invokable class results in a runtime error when the tool is called:
ReflectionException(code: 0): Function App\\Mcp\\Tools\\AskNexaTool() does not exist at /var/www/html/vendor/php-mcp/server/src/Elements/RegisteredElement.php:36)
I tried to track down the error:
In ServerBuilder.php, the registration happens; note that it correctly uuses HandlerResolver::resolve()
to inspect the handler:
foreach ($this->manualTools as $data) {
try {
$reflection = HandlerResolver::resolve($data['handler']);
if ($reflection instanceof \ReflectionFunction) {
$name = $data['name'] ?? 'closure_tool_' . spl_object_id($data['handler']);
$description = $data['description'] ?? null;
} else {
$classShortName = $reflection->getDeclaringClass()->getShortName();
$methodName = $reflection->getName();
$docBlock = $docBlockParser->parseDocBlock($reflection->getDocComment() ?? null);
$name = $data['name'] ?? ($methodName === '__invoke' ? $classShortName : $methodName);
$description = $data['description'] ?? $docBlockParser->getSummary($docBlock) ?? null;
}
$inputSchema = $data['inputSchema'] ?? $schemaGenerator->generate($reflection);
$tool = Tool::make($name, $inputSchema, $description, $data['annotations']);
$registry->registerTool($tool, $data['handler'], true);
$handlerDesc = $data['handler'] instanceof \Closure ? 'Closure' : (is_array($data['handler']) ? implode('::', $data['handler']) : $data['handler']);
$logger->debug("Registered manual tool {$name} from handler {$handlerDesc}");
} catch (Throwable $e) {
$logger->error('Failed to register manual tool', ['handler' => $data['handler'], 'name' => $data['name'], 'exception' => $e]);
throw new ConfigurationException("Error registering manual tool '{$data['name']}': {$e->getMessage()}", 0, $e);
}
}
But when registering the tool:
$registry->registerTool($tool, $data['handler'], true);
…the original handler string (App\Mcp\Tools\AskNexaTool) is passed down to RegisteredTool > RegisteredElement.
public function registerTool(Tool $tool, callable|array|string $handler, bool $isManual = false): void
{
$toolName = $tool->name;
$existing = $this->tools[$toolName] ?? null;
if ($existing && ! $isManual && $existing->isManual) {
$this->logger->debug("Ignoring discovered tool '{$toolName}' as it conflicts with a manually registered one.");
return;
}
$this->tools[$toolName] = RegisteredTool::make($tool, $handler, $isManual);
$this->checkAndEmitChange('tools', $this->tools);
}
In RegisteredElement::handle()
, this happens:
public function handle(ContainerInterface $container, array $arguments): mixed
{
if (is_string($this->handler)) {
$reflection = new \ReflectionFunction($this->handler); // <--- breaks here
$arguments = $this->prepareArguments($reflection, $arguments);
$instance = $container->get($this->handler);
return call_user_func($instance, ...$arguments);
}
.....
}
Metadata
Metadata
Assignees
Labels
No labels