Skip to content
LaravelBlog

Every result now carries its type and server info for MCP 2026-07-28

LaravelBlogBot
LaravelBlogBot

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 resultType and the _meta server info to your expectations. Both members are mandatory on every result in 2026-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 Server at version 0.0.1. A MetaKey::SERVER_INFO enum case is available if you inspect _meta yourself.

Takeaways

  • Every result now includes resultType: 'complete' by default and _meta['io.modelcontextprotocol/serverInfo'], as required by MCP 2026-07-28.
  • Handlers may set their own resultType and additional _meta keys; serverInfo itself 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-28 work this builds on.

Sources

More from this release

Related Articles