Skip to content

fix(mcp): forward Symfony HTTP exception messages as JSON-RPC errors - #8505

Open
alexisLefebvre wants to merge 1 commit into
api-platform:4.3from
alexisLefebvre:fix/mcp-symfony-http-exception
Open

fix(mcp): forward Symfony HTTP exception messages as JSON-RPC errors#8505
alexisLefebvre wants to merge 1 commit into
api-platform:4.3from
alexisLefebvre:fix/mcp-symfony-http-exception

Conversation

@alexisLefebvre

@alexisLefebvre alexisLefebvre commented Sep 7, 2026

Copy link
Copy Markdown
Contributor
Q A
Branch? 4.3
Bug fix? yes
New feature? no
Deprecations? no
Issues -
License MIT
Doc PR -

What

The MCP handler converts a caller-facing HTTP exception from a state provider or processor into a JSON-RPC error carrying its message — but only API Platform's HttpExceptionInterface qualified. Symfony's HttpKernel\Exception\HttpExceptionInterface declares the same methods and is a distinct interface, so it fell through to the SDK's generic handler, which substitutes Internal server error..

Both interfaces now qualify. Every other throwable still reaches the generic handler, so arbitrary exception text is not leaked.

Why

Throwing NotFoundHttpException from a provider is the documented way to signal a missing resource. api-platform/metadata ships no 404 exception implementing its own interface — AccessDeniedException is the only class that does. So two caller-facing failures in one provider behaved differently by package of origin.

#[ApiResource(
    mcp: [
        'set_order_delivery_info' => new McpTool(
            provider: SetOrderDeliveryInfoProvider::class,
            processor: SetOrderDeliveryInfoProcessor::class,
        ),
    ],
)]
final class Order { /* ... */ }
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;

final readonly class SetOrderDeliveryInfoProvider implements ProviderInterface
{
    public function provide(Operation $operation, array $uriVariables = [], array $context = []): Order
    {
        $order = $this->orderRepository->find($context['mcp_data']['orderId'] ?? null);
        if (null === $order) {
            throw new NotFoundHttpException('Order not found.');
        }

        return $order;
    }
}

A tools/call with an unknown orderId:

// before
{"jsonrpc": "2.0", "id": 2, "error": {"code": -32603, "message": "Internal server error."}}
// after
{"jsonrpc": "2.0", "id": 2, "error": {"code": -32603, "message": "Order not found."}}

Otherwise an application must declare an exception extending Symfony's and implementing API Platform's interface, purely to get the message through. Only observable with mcp/sdk 0.8, which sanitises what its generic handler catches.

🤖 Generated with Claude Code

A state provider or processor signalling a caller-facing failure through
HttpKernel's HTTP exception contract has its message forwarded to the
client, the same as one using the metadata component's contract. Both
qualify at the two conversion sites; every other throwable still reaches
the SDK's generic handler, so unexpected exception text stays hidden.

Signalling a missing resource relies on the HttpKernel contract, since
the metadata component ships no equivalent for that status, and the SDK
replaces the message of anything it handles itself with a fixed
constant.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant