getMethod() !== 'POST') { return $handler->handle($request); } $payload = json_decode((string)$request->getBody(), true); $request->getBody()->rewind(); if (!is_array($payload)) { return $handler->handle($request); // the SDK answers malformed JSON-RPC itself } $messages = array_is_list($payload) ? $payload : [$payload]; $needWrite = false; $needRead = false; foreach ($messages as $message) { if (!is_array($message) || !is_string($message['method'] ?? null)) { continue; // responses to server->client requests carry no method } $method = $message['method']; if (in_array($method, self::LIFECYCLE_METHODS, true) || str_starts_with($method, 'notifications/')) { continue; } if ($method === 'tools/call' && ToolCatalog::isWriteTool((string)($message['params']['name'] ?? ''))) { $needWrite = true; } else { $needRead = true; } } $scopes = $request->getAttribute('oauth.scopes') ?? []; if ($needWrite && !McpIdentity::canWrite($scopes)) { return $this->insufficient(McpIdentity::SCOPE_WRITE, 'This operation requires the tickets:write scope.'); } if ($needRead && !McpIdentity::canRead($scopes)) { return $this->insufficient( $needWrite ? McpIdentity::SCOPE_WRITE : McpIdentity::SCOPE_READ, 'This operation requires the tickets:read scope.' ); } return $handler->handle($request); } private function insufficient(string $scope, string $description): ResponseInterface { return $this->responseFactory->createResponse(403)->withHeader( 'WWW-Authenticate', sprintf( 'Bearer error="insufficient_scope", scope="%s", resource_metadata="%s", error_description="%s"', $scope, $this->resourceMetadataUrl, $description ) ); } }