php-mcp/src/Mcp/Services/McpWebhookDispatcher.php
Snider 6f309979de refactor: move MCP module from Core\Mod\Mcp to Core\Mcp namespace
Relocates the MCP module to a top-level namespace as part of the
monorepo separation, removing the intermediate Mod directory layer.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-27 16:26:14 +00:00

128 lines
3.9 KiB
PHP

<?php
declare(strict_types=1);
namespace Core\Mcp\Services;
use Core\Mod\Api\Models\WebhookDelivery;
use Core\Mod\Api\Models\WebhookEndpoint;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
/**
* Dispatches webhooks for MCP tool execution events.
*/
class McpWebhookDispatcher
{
/**
* Dispatch tool.executed event to all subscribed endpoints.
*/
public function dispatchToolExecuted(
int $workspaceId,
string $serverId,
string $toolName,
array $arguments,
bool $success,
int $durationMs,
?string $errorMessage = null
): void {
$eventType = 'mcp.tool.executed';
$endpoints = WebhookEndpoint::query()
->forWorkspace($workspaceId)
->active()
->forEvent($eventType)
->get();
if ($endpoints->isEmpty()) {
return;
}
$payload = [
'event' => $eventType,
'timestamp' => now()->toIso8601String(),
'data' => [
'server_id' => $serverId,
'tool_name' => $toolName,
'arguments' => $arguments,
'success' => $success,
'duration_ms' => $durationMs,
'error' => $errorMessage,
],
];
foreach ($endpoints as $endpoint) {
$this->deliverWebhook($endpoint, $payload);
}
}
/**
* Deliver a webhook to an endpoint.
*/
protected function deliverWebhook(WebhookEndpoint $endpoint, array $payload): void
{
$payloadJson = json_encode($payload);
$signature = $endpoint->generateSignature($payloadJson);
$startTime = microtime(true);
try {
$response = Http::timeout(10)
->withHeaders([
'Content-Type' => 'application/json',
'X-Webhook-Signature' => $signature,
'X-Webhook-Event' => $payload['event'],
'X-Webhook-Timestamp' => $payload['timestamp'],
])
->withBody($payloadJson, 'application/json')
->post($endpoint->url);
$durationMs = (int) ((microtime(true) - $startTime) * 1000);
// Record delivery
WebhookDelivery::create([
'webhook_endpoint_id' => $endpoint->id,
'event_id' => 'evt_'.uniqid(),
'event_type' => $payload['event'],
'payload' => $payload,
'response_code' => $response->status(),
'response_body' => substr($response->body(), 0, 1000),
'status' => $response->successful() ? 'success' : 'failed',
'attempt' => 1,
'delivered_at' => $response->successful() ? now() : null,
]);
if ($response->successful()) {
$endpoint->recordSuccess();
} else {
$endpoint->recordFailure();
Log::warning('MCP Webhook delivery failed', [
'endpoint_id' => $endpoint->id,
'url' => $endpoint->url,
'status' => $response->status(),
]);
}
} catch (\Throwable $e) {
$durationMs = (int) ((microtime(true) - $startTime) * 1000);
WebhookDelivery::create([
'webhook_endpoint_id' => $endpoint->id,
'event_id' => 'evt_'.uniqid(),
'event_type' => $payload['event'],
'payload' => $payload,
'response_code' => 0,
'response_body' => $e->getMessage(),
'status' => 'failed',
'attempt' => 1,
]);
$endpoint->recordFailure();
Log::error('MCP Webhook delivery error', [
'endpoint_id' => $endpoint->id,
'url' => $endpoint->url,
'error' => $e->getMessage(),
]);
}
}
}