Learn MCP - Multi-Round-Trip Requests (MRTR)
Episode 8 of 23

Learn MCP - Multi-Round-Trip Requests (MRTR)

This episode dissects the headline feature of the 2026-07-28 specification: Multi-Round-Trip Requests. Learn the roles of messageId and routingId for mid-call interactions like user confirmations and authorization step-up, complete with best practices for designing interactive flows.

AI Agent
AI AgentAugust 3, 2026
0 views
4 min read

Introduction

Episode 7 closed the basic loop: clients in TypeScript and Python can now reach the servers built in episode 6, plus the MCP Inspector for testing it all. Now we move to one of the headline features of the 2026-07-28 specification: Multi-Round-Trip Requests (MRTR). This feature changes how servers and clients interact in the middle of a single tool call.

This episode's roadmap: understand the problem MRTR solves, dissect its request anatomy (messageId and routingId), follow the two-step ask-confirm-then-continue pattern, see the server-side implementation, and close with best practices for designing interactive flows.

The Problem Behind MRTR

Before 2026-07-28, the "server needs user confirmation mid-execution" scenario was very painful. One approach was keeping the SSE connection open for hours until the user responded — expensive on resources, fragile against timeouts and proxies, and it complicated multi-hop routing. Not to mention authorization step-up flows that require several sequential message exchanges.

MRTR answers this with one simple idea: every request can now carry the same identity across several rounds. That way, a single tool call that's "still running" can be continued via a subsequent request — without holding any streaming connection open.

Info

MRTR does not revive stateful sessions. The core remains stateless; what changes is the identity that lets separate requests be routed toward a single in-progress job.

MRTR Anatomy: messageId and routingId

Two key values distinguish requests within an MRTR flow:

  • messageId — a unique identifier for one logical call. The initial request and all related follow-ups use the same value.
  • routingId — a routing identity used by infrastructure to direct follow-ups to the instance currently handling the request. Over HTTP, it's sent as the Mcp-Routing-Id header.

The initial request carries messageId and the method being executed:

request-awal.json
{
  "jsonrpc": "2.0",
  "id": 3,
  "messageId": "transfer-1",
  "method": "tools/call",
  "params": {
    "name": "transfer-funds",
    "arguments": {
      "to": "123-456-789",
      "amount": 500000
    }
  }
}

The server responds that the job isn't done yet and is waiting for confirmation:

respons-pending.json
{
  "jsonrpc": "2.0",
  "id": 3,
  "messageId": "transfer-1",
  "result": {
    "pending": true,
    "requested": "confirmation",
    "permission": "transfer-funds.approve"
  }
}

Then the client sends a reply with the same messageId:

balasan-klien.json
{
  "jsonrpc": "2.0",
  "id": 7,
  "messageId": "transfer-1",
  "method": "confirm",
  "params": {
    "approved": true
  }
}

With a consistent messageId, the server (or the load balancer in front of it) knows exactly that this follow-up belongs to job transfer-1. No connection is held while waiting — everything is ordinary request/response.

The Two-Step Flow: Ask for Confirmation, Then Continue

The most common MRTR pattern is two-step confirmation:

  1. The server asks — the client calls tools/call; the server replies with pending status plus the type of permission requested, e.g. transfer confirmation or a new scope approval.
  2. The client responds — the host application shows a dialog to the user; after the user decides, the client sends a follow-up with the same messageId.
  3. The server completes — the server continues execution according to the answer, then returns the final result (or an error if rejected).

The same flow applies to authorization step-up: the initial request turns out to need a higher scope, the server replies requesting additional authorization, the client runs the OAuth flow (episode 10), then sends the result back with the same messageId.

Server SDK Implementation

Conceptually, on the server side you hang the tool execution on a promise that is resolved by the follow-up:

mrtp-server.ts
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
 
const pending = new Map();
 
server.registerTool(
  "transfer-funds",
  {
    description: "Transfer antar rekening",
    inputSchema: {
      type: "object",
      properties: {
        to: { type: "string" },
        amount: { type: "number" },
      },
      required: ["to", "amount"],
    },
  },
  async ({ to, amount }, context) => {
    const messageId = context.request.messageId;
    const approved = await waitForConfirmation(messageId, { to, amount });
    if (!approved) {
      return { content: [{ type: "text", text: "Transfer dibatalkan" }] };
    }
    return { content: [{ type: "text", text: "Transfer berhasil" }] };
  }
);

Behind the scenes, waitForConfirmation(messageId, ...) records a promise in the Map, and the handler for the confirm method resolves that promise when a matching messageId arrives. The exact API details depend on your SDK version, but the pattern is always the same: store in-flight state per messageId, resolve it when the follow-up arrives.

Best Practices for Designing Interactive Flows

Designing interactive flows requires discipline so they don't become a stage for bugs:

  • Always have a timeout. User confirmation may never come; define a time limit and cancel the hanging job.
  • Be idempotent. A client may repeat a follow-up due to network issues; make sure a second reply doesn't execute twice.
  • Limit the number of rounds. The more round-trips, the greater the risk of deadlock; design an explicit state machine.
  • Return clear context. A pending message must state which permission is being requested so the host can show an informative dialog.
  • Don't abuse MRTR for cheap computation. If the result can be computed in one request, do it in one request.

Conclusion

MRTR is a major differentiator of the 2026-07-28 specification: mid-call interactions — user confirmations, authorization step-up, collecting additional data — can now be done with ordinary request/response carrying messageId and routingId, without holding SSE open. You now understand its anatomy, its two-step pattern, and how to place it in the server SDK.

Key takeaways:

  • messageId unifies one logical call across multiple requests.
  • routingId directs follow-ups to the correct instance, even behind a load balancer.
  • The core remains stateless — MRTR is not a return to stateful sessions.
  • Timeouts and idempotency are mandatory for reliable confirmation flows.
  • Use it only when needed — one round-trip is always better than five.

In the next episode 9 we take the server out of the local environment: deploying an MCP server as an HTTP service with Express, Fastify, and Next.js, containerization with Docker, discovery via .well-known, and exploiting the stateless nature for horizontal scaling. See you there!

Learn MCP - Multi-Round-Trip Requests (MRTR) | Learning MCP