1. Go to this page and download the library: Download milpa/tool-runtime library. Choose the download type require.
2. Extract the ZIP file and open the index.php.
3. Add this code to the index.php.
<?php
require_once('vendor/autoload.php');
/* Start to develop here. Best regards https://php-download.com/ */
milpa / tool-runtime example snippets
use Milpa\ToolRuntime\Attributes\Param;
use Milpa\ToolRuntime\Attributes\Tool;
use Milpa\ToolRuntime\ToolResult;
final class NoteTools
{
#[Tool('list_notes', 'List saved notes', scopes: ['notes:read'])]
public function listNotes(
#[Param('Page number', clamp: [1, 1000])] int $page = 1
): ToolResult {
return ToolResult::success(['notes' => [], 'page' => $page]);
}
}
use Milpa\ToolRuntime\Contracts\ToolContext;
use Milpa\ToolRuntime\ToolRegistry;
use Milpa\ToolRuntime\ToolScanner;
use Psr\Log\NullLogger;
$registry = new ToolRegistry(new NullLogger());
(new ToolScanner($registry))->scan(new NoteTools());
$result = $registry->call('list_notes', ['page' => 1], ToolContext::cli());
$result->success; // true
$result->data; // ['notes' => [], 'page' => 1]
$result->toJson(); // {"success":true,"data":{...},"message":null,"error":null,"meta":{...}}
#[Tool('update_post', 'Update fields on a post')]
public function updatePost(
int $post_id,
#[Param('Fields to update', type: 'object', properties: [
'title' => ['type' => 'string'],
'body' => ['type' => 'string'],
])]
array $updates
): ToolResult {
// $updates arrives as a plain associative array — ['title' => ..., 'body' => ...] —
// no manual json_decode() needed; the host's JSON transport already decoded it that way.
return ToolResult::success(['post_id' => $post_id, 'updates' => $updates]);
}
use Milpa\Events\InterceptionSlot;
use Milpa\ToolRuntime\Events\ToolExecutingEvent;
use Milpa\ToolRuntime\ToolResult;
$dispatcher->subscribe('tool.executing', function (string $eventName, array $payload): void {
/** @var ToolExecutingEvent $event */
$event = $payload['event'];
/** @var InterceptionSlot $slot */
$slot = $payload['slot'];
$cached = $myCache->get($event->name, $event->args);
if ($cached !== null) {
// Short-circuit: the real callback never runs; ToolRegistry::call() returns this
// result instead. tool.executed STILL fires afterwards, marked cacheServed: true —
// a cache hit is never invisible to audit/metrics listeners.
$slot->shortCircuit(ToolResult::success($cached));
}
});
$registry = new ToolRegistry($logger, $dispatcher);
use Milpa\ToolRuntime\Verification\HumanVerifier;
use Milpa\ToolRuntime\Verification\VerificationTool;
(new VerificationTool(new HumanVerifier()))->register($registry);
$request = $registry->call('request_verification', [
'subject' => 'gate:report.publish',
], $ctx);
// -> ToolResult success, data: [
// 'subject' => 'gate:report.publish', 'policy' => 'single',
// 'request_id' => '06a1dda5-...',
// ]
// handleRequest() ran on THIS call — HumanVerifier::verify() ran and dispatched
// `verification.requested`. No confirm_token anywhere: the registry gate never ran.
$registry->call('resolve_verification', [
'request_id' => $request->data['request_id'],
'decision' => 'grant',
'principal' => 'agent:claude',
], $ctx);
// -> ToolResult success, data: [
// 'status' => 'passed', 'reason' => null, 'verifier' => 'human_verifier',
// 'principal' => 'agent:claude', 'missing' => [], 'metadata' => [],
// ]
// HumanVerifier::grant() ran and dispatched `verification.granted`.
use Milpa\ToolRuntime\Contracts\ToolContext;
use Milpa\ToolRuntime\Verification\HumanVerifier;
use Milpa\ToolRuntime\Verification\VerificationTool;
// request_verification stays open (empty scopes, the pre-split default); resolve_verification
// ipal: 'agent:reviewer',
channel: 'mcp',
scopes: ['tasks:write', 'verification:resolve'],
);
$request = $registry->call('request_verification', ['subject' => 'gate:report.publish'], $worker);
// -> success: $worker has no 'verification:resolve' scope, but request_verification never checks it.
$registry->call('resolve_verification', [
'request_id' => $request->data['request_id'], 'decision' => 'grant', 'principal' => 'agent:worker',
], $worker);
// -> FORBIDDEN: "Missing
use Milpa\ToolRuntime\Verification\HumanVerifier;
use Milpa\ToolRuntime\Verification\VerificationTool;
$tool = new VerificationTool(new HumanVerifier($eventDispatcher));
$request = $tool->handleRequest(['subject' => 'gate:report.publish']);
// -> ToolResult::confirmation(), $request->data['request_id'] === '06a1dda5-...'
// HumanVerifier::verify() ran and dispatched `verification.requested`.
$tool->handleResolve([
'request_id' => $request->data['request_id'],
'decision' => 'grant',
'principal' => 'agent:claude',
]);
// -> ToolResult::success(), data: ['status' => 'passed', 'principal' => 'agent:claude', ...]
// HumanVerifier::grant() ran and dispatched `verification.granted`.