Every result now carries its type and server info for MCP 2026-07-28
Contributed by pushpak1300
Part of the mcp v1.0.1 release
Pull request #283 completes every JSON-RPC result laravel/mcp sends with the two members the MCP 2026-07-28 revision requires: a resultType that says whether the result is final, and a _meta entry under io.modelcontextprotocol/serverInfo that identifies which server produced it. Until now, results left the server as bare payloads, so a client on the new revision had no way to tell a finished result from an intermediate one, nor to attribute it to a server. The change is entirely server-side — nothing changes for anyone writing tools or resources — but it is a breaking change for clients that compare whole result objects.
What changed
All outbound traffic now flows through a single method. Where handleMessage() and both error paths previously called $this->transport->send() directly — with the two error paths skipping the envelope entirely — they now delegate to a new send() method that stamps the envelope onto any message carrying a result:
protected function send(JsonRpcResponse $response, ServerContext $context): void
{
if (array_key_exists('result', $response->content)) {
$result = (array) $response->content['result'];
$result['_meta'][MetaKey::SERVER_INFO->value] = $context->implementation->toArray();
$response->content['result'] = ['resultType' => 'complete', ...$result];
}
$this->transport->send($response->toJson());
}
A new MetaKey::SERVER_INFO case (io.modelcontextprotocol/serverInfo) joins the existing protocol-version and client-capabilities keys, and JsonRpcResponse::$content is now public so the server can write the envelope. Error responses and notifications pass through untouched, since they carry no result member — but they now take the same code path, closing the gap where error responses previously bypassed the envelope logic altogether. Streamed tool output is covered too: each message in the stream is enveloped as it is sent.
Why it matters
The 2026-07-28 revision makes both members required on every result. Without resultType, a client cannot distinguish a completed response from an intermediate one — which matters once multi-turn, input_required flows arrive. Without the server info _meta, a client connected to several MCP servers cannot tell which one answered. Routing every send through one method also means future protocol requirements only need to be implemented in one place rather than at each call site.
What it looks like on the wire
Before:
{ "result": { "tools": [] } }
After:
{
"result": {
"resultType": "complete",
"tools": [],
"_meta": {
"io.modelcontextprotocol/serverInfo": {
"name": "Laravel MCP Server",
"version": "0.0.1"
}
}
}
}
The same envelope now appears on every method the example server answers: discovery, tools/list, resources/list, resources/read, tools/call, streaming tools, and custom methods. Tool authors keep writing exactly what they wrote before, and the envelope is added on the way out:
public function handle(JsonRpcRequest $request, ServerContext $context): Response
{
return Response::text('Hello, John Doe!');
}
Precedence: what a handler can still control
Two precedence decisions are worth knowing. First, resultType is a default, not an override. Because the default is listed before the spread (['resultType' => 'complete', ...$result]), a handler that supplies its own resultType wins:
return JsonRpcResponse::result($request->id, [
'resultType' => 'input_required',
'_meta' => ['app/trace' => 'abc'],
]);
That response reaches the client as input_required, with _meta containing both app/trace and the server info. Building the multi-turn flow on top of this lands in #288. Second, server identity is assigned rather than merged: a handler cannot claim a different serverInfo, though any other _meta key it sets survives. The PR explicitly invites feedback if you would rather the handler win on that second point.
Upgrade impact
- Server authors: nothing to do. Handlers keep returning
Response::text(...)and friends; the envelope is applied centrally before the transport sees the message. - Client authors: this is the BC break flagged in the PR. If your integration tests or client code compare whole result objects — snapshots, strict equality, exact shape assertions — they will fail until you add
resultTypeand the_metaserver info to your expectations. Both members are mandatory on every result in2026-07-28, so there is no way to track the revision without them. - Identifiers: the server info values come from the server's implementation metadata, which is why the tests expect
Laravel MCP Serverat version0.0.1. AMetaKey::SERVER_INFOenum case is available if you inspect_metayourself.
Takeaways
- Every result now includes
resultType: 'complete'by default and_meta['io.modelcontextprotocol/serverInfo'], as required by MCP2026-07-28. - Handlers may set their own
resultTypeand additional_metakeys;serverInfoitself stays under server control. - Errors and notifications are unchanged in shape but now flow through the same central send path.
- Update any client code or tests that assert on whole result objects.
- Thanks to @JordanDalton, whose #275 mapped out the
2026-07-28work this builds on.