diff --git a/app/Http/Controllers/McpController.php b/app/Http/Controllers/McpController.php index f07754a7..62b444e7 100644 --- a/app/Http/Controllers/McpController.php +++ b/app/Http/Controllers/McpController.php @@ -10,6 +10,7 @@ use App\Services\PluginSearchService; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; +use Illuminate\Http\Response; class McpController extends Controller { @@ -21,8 +22,15 @@ public function __construct( /** * JSON-RPC message endpoint for tool calls */ - public function message(Request $request): JsonResponse + public function message(Request $request): JsonResponse|Response { + // A notification has no id and gets no JSON-RPC response. Over the + // Streamable HTTP transport, MCP answers it with 202 and no body. + // Clients such as Codex drop a server that sends a response here. + if (is_string($request->input('method')) && ! $request->has('id')) { + return response()->noContent(202); + } + $method = $request->input('method'); $params = $request->input('params', []); $id = $request->input('id'); @@ -30,7 +38,6 @@ public function message(Request $request): JsonResponse try { $result = match ($method) { 'initialize' => $this->handleInitialize($params), - 'notifications/initialized' => new \stdClass, 'ping' => new \stdClass, 'tools/list' => ['tools' => $this->getToolDefinitions()], 'tools/call' => $this->handleToolCall( diff --git a/tests/Feature/McpNotificationTest.php b/tests/Feature/McpNotificationTest.php new file mode 100644 index 00000000..9b87a076 --- /dev/null +++ b/tests/Feature/McpNotificationTest.php @@ -0,0 +1,46 @@ +postJson('/api/mcp/message', [ + 'jsonrpc' => '2.0', + 'method' => 'notifications/initialized', + ]); + + $response->assertNoContent(202); + } + + #[Test] + public function any_notification_gets_202_with_no_body(): void + { + $response = $this->postJson('/api/mcp/message', [ + 'jsonrpc' => '2.0', + 'method' => 'notifications/cancelled', + 'params' => ['requestId' => 1], + ]); + + $response->assertNoContent(202); + } + + #[Test] + public function message_without_a_method_still_gets_an_error(): void + { + $response = $this->postJson('/api/mcp/message', [ + 'jsonrpc' => '2.0', + ]); + + $response->assertOk(); + $response->assertJsonPath('error.code', -32000); + } +}