SIGNALS Documentation
API Reference

Plugin Tool Registry

Contract for accumulating plugin MCP/CLI tool definitions registered via PluginRegistrar::tool().

Overview

App\Sdk\Tools\PluginToolRegistry stores runtime records for plugin tools exposed on the Signals API MCP server and/or the signals:api Artisan CLI.

Plugins declare tools in signals.yaml (tools[]) and wire them with PluginRegistrar::tool($name, $actionClass). The bridge namespaces each tool as plugin:{package}:{name} for CLI; MCP exposes a Claude-safe hyphenated form via McpToolSchema::sanitizeToolName() (e.g. plugin-acme-hello-acme-hello-echo). Invocation always runs as the acting user and checks the declared permission via Gate.

Public surface

Method Purpose
register() Warehouse one ToolDefinition
has() Check whether a package/name pair is registered
get() Return the definition or throw
getByNamespacedName() Resolve plugin:{package}:{name}
all() Map of namespaced name → ToolDefinition
forPackage() Definitions for one package
flush() Clear all registrations (tests / reload)

Accepted registration shape

$registry->register(new ToolDefinition(
    package: 'acme/hello',
    name: 'acme.hello.echo',
    title: 'Echo Hello',
    description: 'Echo a message',
    action: \Acme\Hello\Actions\EchoTool::class,
    args: [
        ['name' => 'message', 'type' => 'string', 'required' => false, 'description' => 'Optional message'],
    ],
    permission: 'acme.hello.view',
    readOnly: true,
    destructive: false,
    surfaces: ['mcp', 'cli'],
));

Identity is (package, name). Re-registering the same pair replaces the earlier definition.

Surfaces and bridging

  • PluginMcpToolBridge filters Enabled plugins, keeps tools whose surfaces include mcp, and builds PluginBridgedMcpTool instances for SignalsApiServer::additionalTools().
  • signals:api list --plugins / signals:api call plugin:… use the same registry via the bridge, gated on the cli surface and cli.access.

Worked example

$registry->has('signals/slots-demo', 'signals.slotsdemo.echo'); // true after plugin register()
$definition = $registry->getByNamespacedName('plugin:signals/slots-demo:signals.slotsdemo.echo');
app(PluginToolInvoker::class)->invoke($definition, ['message' => 'hi'], $user);