---
title: 'Transport Layer'
source: 'https://academia.sh/en/courses/the-model-context-protocol-and-tool-ecosystem/transport-layer'
course: 'The Model Context Protocol and Tool Ecosystem'
language: en
updated: '2026-10-21T05:02:35+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - "Defines local and remote transport as a layer separate from the protocol's data layer."
    - 'Shows by counting that round trips double while the request count stays fixed on the same workload.'
    - "Verifies that the envelope's token cost is independent of the transport form."
    - 'Counts separately the transport cost paid for a server dropped by version skew.'
---

# Transport Layer

Comparing local and remote transport in round trips on the same workload: the same three-tool chain gives 844 with 6 requests, 6 responses and 179 envelope tokens in all three transport setups, but round trips are 6 all local, 9 mixed and 12 all remote. Requests stay fixed while round trips exactly double. In a twelve-subscriber batch requests hold at 39 while round trips go from 39 to 78. The dropped kayit server still costs 1 round trip local and 2 remote. This lesson is the first example of the course's second claim: standardizing also breaks.

The previous lesson changed the placement, not the transport: `olcum` (the measurement server) sat
local, `tarife` (the tariff server) and `kayit` (the records server) sat remote, and every added
message was counted at the local price. That table had an oddity. The correct distribution sent
**6 requests** but counted **9 round trips**; the two columns did not move together. A request is
one unit of counting, a round trip is another, and the difference between them is set by **how the
message is carried**.

The protocol is therefore split into two layers. The **data layer** says what the message is:
request or response, which tool, which fields. The **transport layer** says how that message
reaches the other side, and it has two forms: in **local transport** the server runs as a process
on the same machine and messages pass through streams between processes; in **remote transport**
the server is across the network. This lesson runs the same workload in three transport setups and
asks a single question: with the request count fixed, what happens to the round trips?

- **PS18.** The environment, the three servers and the protocol simulator are **fictional**; no
  real socket, process or network connection is opened. Seed **20260218**. The envelope's fixed
  cost is eight tokens.
- **PS19.** On local transport a request counts as **one** round trip, on remote transport as
  **two**. This setup is a scale; what is measured is not the absolute value but **how the count
  changes on the same workload**.
- **PS20.** The procedure of communication between processes and of calls over a network was
  covered in the Linux and System Administration and Service Architectures courses and is **not repeated**; here only the
  round trips that transport adds to the protocol are counted.
- **PS21.** The transport form **does not change** the tool definition, the handler or the result;
  it changes only how the message is carried.
- **PS22.** Three setups are compared: **all local**, **mixed** (`olcum` local, `tarife` and
  `kayit` remote) and **all remote**. The version check is independent of transport; `kayit` is
  dropped in all three setups.
- **PS23.** The envelope's token count is **independent of transport**; transport does not change
  the body.
- **PS24.** Time is not written in a raw unit; what is counted is the **round trip**.
- **PS25.** The second measurement is taken on a batch of twelve subscribers. Counts do not depend
  on the run, and this lesson prints **counts**, not ratios.

## Three Transport Setups

The two forms differ in more than speed; they also differ in **the server's lifetime and who can
reach it**. In local transport the client usually starts the server, and the server ends when the
host shuts down; only a client on the same machine can reach it. In remote transport the server's
lifetime is independent and clients on other machines can connect to the same server. This
difference directly affects the portability counted in the first lesson: plugging the same server
into four clients and keeping the definition in one place find their real meaning **with remote
transport**; in local transport those four clients must be on the same machine.

The code below sets up the fictional environment and simulator of the earlier lessons, then forces
the servers' transport form and runs the same three-tool chain three times. The `with_transport`
function saves the original values before changing the form and writes them back at the end of the
run; the tools' code, field names and computation never change.

```python
SEED, M32 = 20260218, 0xFFFFFFFF


def rng(t):
    x = ((t ^ (t >> 16)) * 2246822507) & M32
    x = ((x ^ (x >> 13)) * 3266489909) & M32
    s = [(x ^ (x >> 16)) & M32]

    def next_value():
        s[0] = (s[0] * 1664525 + 1013904223) & M32
        return s[0] / 4294967296
    return next_value


def tokenize(m):
    b = []
    for s in str(m).lower().replace(",", " , ").split():
        while len(s) > 5:
            b.append(s[:5])
            s = s[5:]
        b.append(s)
    return b


# ---- FICTIONAL environment: subscriber records of the measurement network
REGION = ["dogu", "bati", "kuzey", "guney", "merkez"]
TYPE = ["mesken", "isyeri"]
SUBSCRIBERS = {}
for i in range(60):
    r = rng(SEED + 31 * i)
    previous = 10000 + int(r() * 80000)
    SUBSCRIBERS[f"A{i + 1:03d}"] = {"bolge": REGION[int(r() * 5)], "tip": TYPE[int(r() * 2)],
                                    "onceki": previous, "simdiki": previous + int(r() * 70)}
TIERS, ABOVE, COMMERCIAL_FACTOR = ((10, 4), (25, 7), (40, 11)), 16, 2


def _charge(m3, type_):
    remaining, lower, total = m3, 0, 0
    for upper, unit in TIERS:
        band = min(remaining, upper - lower)
        if band <= 0:
            break
        total += band * unit
        remaining -= band
        lower = upper
    return (total + remaining * ABOVE) * (COMMERCIAL_FACTOR if type_ == "isyeri" else 1)


def t_meter(abone=None, **_):
    a = SUBSCRIBERS.get(abone)
    return {"hata": "abone yok"} if a is None else {
        "onceki": a["onceki"], "simdiki": a["simdiki"], "tip": a["tip"]}


def t_consumption(onceki=None, simdiki=None, **_):
    if onceki is None or simdiki is None:
        return {"hata": "eksik alan"}
    f = simdiki - onceki
    return {"m3": f + 100000 if f < 0 else f}


def t_tariff(m3=None, tip="mesken", **_):
    return {"hata": "eksik alan"} if m3 is None else {"tutar": _charge(m3, tip)}


def t_region(bolge=None, **_):
    if bolge not in REGION:
        return {"hata": "bolge yok"}
    n = sum(1 for a in SUBSCRIBERS.values() if a["bolge"] == bolge)
    return {"bolge": bolge, "abone": n}


def t_record(abone=None, sutun=None, deger=None, **_):
    if not (abone and sutun and deger is not None):
        return {"hata": "eksik alan"}
    return {"yazildi": True}


# ---- FICTIONAL servers: each one declares its capabilities
SERVERS = {
    "olcum": {"version": 2, "transport": "local",
              "tools": {"sayac_oku": {"function": t_meter, "fields": ("abone",), "side_effect": False},
                        "tuketim": {"function": t_consumption, "fields": ("onceki", "simdiki"),
                                    "side_effect": False}},
              "resources": ("abone_listesi",), "prompts": ()},
    "tarife": {"version": 1, "transport": "remote",
               "tools": {"tarife": {"function": t_tariff, "fields": ("m3", "tip"),
                                    "side_effect": False}},
               "resources": ("basamak_tablosu",), "prompts": ("bedel_aciklamasi",)},
    "kayit": {"version": 3, "transport": "remote",
              "tools": {"bolge_ozet": {"function": t_region, "fields": ("bolge",),
                                       "side_effect": False},
                        "kayit_yaz": {"function": t_record, "fields": ("abone", "sutun", "deger"),
                                      "side_effect": True}},
              "resources": (), "prompts": ()},
}
CLIENT_VERSION = {"olcum": 2, "tarife": 1, "kayit": 2}      # the kayit server is ahead
ALLOW = ("sayac_oku", "tuketim", "tarife", "bolge_ozet")     # kayit_yaz is not on the allowlist

COUNT = {"request": 0, "response": 0, "notification": 0, "tokens": 0, "round_trips": 0}


def message(kind, body, transport="local"):
    """FICTIONAL envelope. No real protocol is implemented; what is counted is the cost of the envelope."""
    COUNT[kind] += 1
    b = len(tokenize(body)) + 8                          # envelope constant
    COUNT["tokens"] += b
    if kind == "request":
        COUNT["round_trips"] += 2 if transport == "remote" else 1
    return b


def handshake(client_version=None, allow=None):
    """Capability negotiation: a version mismatch and the allowlist narrow the tool set."""
    client_version = CLIENT_VERSION if client_version is None else client_version
    allow = ALLOW if allow is None else allow
    agreed, dropped = {}, []
    for name, s in SERVERS.items():
        message("request", f"yetenek sor {name}", s["transport"])
        message("response", f"yetenek bildir {name} surum {s['version']} "
                            + " ".join(s["tools"]) + " " + " ".join(s["resources"]))
        if client_version.get(name, 0) < s["version"]:
            dropped.append((name, "version", s["version"], client_version.get(name, 0)))
            continue
        for t, spec in s["tools"].items():
            if t not in allow:
                dropped.append((name, "allowlist", t, None))
                continue
            agreed[t] = dict(spec, server=name, transport=s["transport"])
    return agreed, dropped


def call(agreed, name, state):
    if name not in agreed:
        return {"hata": "arac bildirilmedi"}
    t = agreed[name]
    kw = {a: state[a] for a in t["fields"] if a in state}
    message("request", f"{name} " + " ".join(f"{k} {v}" for k, v in kw.items()), t["transport"])
    s = t["function"](**kw)
    message("response", " ".join(f"{k} {v}" for k, v in s.items()))
    return s


DEFAULT = {name: s["transport"] for name, s in SERVERS.items()}
FORMS = (("all local", "local"), ("mixed", None), ("all remote", "remote"))


def with_transport(force, subscriber_list):
    """The same workload, with the servers' transport form changed."""
    for name, s in SERVERS.items():
        s["transport"] = DEFAULT[name] if force is None else force
    for k in COUNT:
        COUNT[k] = 0
    agreed, _ = handshake()
    hs = dict(COUNT)
    result = []
    for subscriber in subscriber_list:
        state = {"abone": subscriber}
        for name in ("sayac_oku", "tuketim", "tarife"):
            state.update(call(agreed, name, state))
        result.append(state["tutar"])
    for name, s in SERVERS.items():
        s["transport"] = DEFAULT[name]
    return result, hs, dict(COUNT)


print("transport    result requests responses envelope tokens handshake rt chain rt total rt")
for label, force in FORMS:
    r, hs, tot = with_transport(force, ["A017"])
    print(f"{label:<12} {r[0]:>6} {tot['request']:>8} {tot['response']:>9} {tot['tokens']:>15}"
          f" {hs['round_trips']:>12} {tot['round_trips'] - hs['round_trips']:>8}"
          f" {tot['round_trips']:>8}")
print("dropped kayit server's handshake cost : local 1, remote 2 round trips")
```

```
transport    result requests responses envelope tokens handshake rt chain rt total rt
all local       844        6         6             179            3        3        6
mixed           844        6         6             179            5        4        9
all remote      844        6         6             179            6        6       12
dropped kayit server's handshake cost : local 1, remote 2 round trips
```

## Requests Fixed, Round Trips Doubled

The first four columns of the table are the same in all three rows: result **844**, requests
**6**, responses **6**, envelope tokens **179**. The tools' code did not change, the field names
did not change, the message bodies did not change. From the fifth column on, though, the figures
doubled: round trips went **from 6 to 12**. The mixed setup in between stands exactly in the
middle at **9**, because one of the three servers stays local.

This is the first example of the course's second claim: **standardizing also breaks.** Moving a
tool beyond the network makes it shareable; the same move doubles the round trip count without
gaining anything. The gain and the cost come out of the same decision and cannot be separated.

That the envelope token count is **179** in all three rows is also worth noting. The transport
layer sits on top of the data layer and does not touch the body of the message; how many tokens a
message costs does not depend on where it passes through. This is numerical proof that the two
layers are really separate, and it yields a design conclusion: the cost of remote transport
**cannot be reduced by cutting from the token budget**, because it has nothing to do with the
token budget. What can be reduced is **the request count**.

The last line is a loss. The `kayit` server is dropped by version skew in all three setups and
gains no tool; even so, transport cost is paid for the handshake: **1** round trip local, **2**
remote. In the all-remote setup, **two** of the handshake's six round trips go to this server. The
cost of a connection is paid whether or not that connection turns out to be useful.

## As the Workload Grows

A single-subscriber chain is a narrow measure. The same measure is also taken on a chain repeated
for twelve subscribers after the same handshake: as the batch grows, the fixed share of the
handshake melts away and only the transport cost of the calls remains.

```python
BATCH = [f"A{1 + 5 * i:03d}" for i in range(12)]
print("subscribers  local: requests rt  mixed: requests rt  remote: requests rt"
      "  remote/local rt difference")
for k in (1, 4, 12):
    row, rt = [], {}
    for label, force in FORMS:
        _, _, tot = with_transport(force, BATCH[:k])
        row += [tot["request"], tot["round_trips"]]
        rt[label] = tot["round_trips"]
    print(f"{k:>11}  {row[0]:>15} {row[1]:>2}  {row[2]:>15} {row[3]:>2}"
          f"  {row[4]:>16} {row[5]:>2}  {rt['all remote'] - rt['all local']:>26}")
local_res, _, local_cnt = with_transport("local", BATCH)
remote_res, _, remote_cnt = with_transport("remote", BATCH)
print(f"twelve subscribers : results same {local_res == remote_res} ; envelope tokens"
      f" local {local_cnt['tokens']} remote {remote_cnt['tokens']} ;"
      f" round trips {local_cnt['round_trips']} and {remote_cnt['round_trips']}")
```

```
subscribers  local: requests rt  mixed: requests rt  remote: requests rt  remote/local rt difference
          1                6  6                6  9                 6 12                           6
          4               15 15               15 21                15 30                          15
         12               39 39               39 53                39 78                          39
twelve subscribers : results same True ; envelope tokens local 1059 remote 1059 ; round trips 39 and 78
```

The request column stays the same across the three setups: 6 for one subscriber, 15 for four, 39
for twelve. The round trip column equals the requests in local, and exactly double in remote. The
last column reduces this relationship to one number: the difference between remote and local is
**always equal to the request count**. The mixed setup stays between the two and moves toward the
remote side as it grows, because every chain in the batch contains a remote call.

The twelve-subscriber batch gives the same results too, and the envelope token count is **1059**
in both setups. The result lists of the twelve subscribers are identical in local and remote;
there is no nondeterminism in the simulator and the counts do not depend on the run. The transport
layer determines not **what** the work is but **how many times it goes back and forth**.

The split between the fixed share and the variable share can be read from this table as well. In
the all-remote setup the handshake costs **6 round trips** and stays at 6 however large the batch
grows; the chains' share is 6 for one subscriber, 24 for four and 72 for twelve. With a single
subscriber half of the total goes to establishing the connection, with twelve one thirteenth. In
the local setup the handshake stays fixed at 3 and the chain share rises from 3 to 36; the shape of
the split is the same, its scale half. The handshake is a cost paid once, and the longer the
session is used, the smaller its share per call; re-establishing the session for every job makes
that share be paid again each time.

The engineering decision that follows is about reducing round trips, and it has two ways. The
first is to bring the server local; this makes that server unreachable for clients beyond the
network and gives portability back. The second is to lower the request count; the requests staying
at 39 in the twelve-subscriber batch comes from the handshake being made **once**. The fixed price
paid per session gets cheaper the longer the session is used.

## Summary

- The protocol is split into two layers: the data layer says what the message is, the transport
  layer says how it is carried; in local transport the server is on the same machine, in remote
  transport it is across the network.
- The same three-tool chain gives 844 with 6 requests, 6 responses and 179 envelope tokens in all
  three transport setups; the only thing that changes is the round trip: 6 all local, 9 mixed, 12
  all remote.
- The envelope's token cost is independent of transport; the cost of remote transport cannot be
  reduced by cutting from the token budget, what must be reduced is the request count.
- In the twelve-subscriber batch requests stay fixed at 39 while round trips go from 39 to 78, and
  the difference is exactly the request count at every scale.
- The `kayit` server dropped by version skew gains no tool yet uses 1 round trip local and 2
  remote for the handshake.
- Moving a tool beyond the network makes it shareable and the same move doubles the round trips;
  the gain and the cost come out of the same decision.

## Next Step

In three lessons how the connection is **established** was measured: roles were named,
responsibilities distributed, the price of transport counted. Throughout the messages the same two
kinds were used, a request went and a response came, and whether anything existed outside this pair
was never asked. Yet a message that awaits a response in the handshake and a message that only
tells the other side a status are not the same thing; the second is responseless, halves the round
trip and makes something invisible in return. The next lesson looks **inside** the envelope: it
counts the separate cost of the request, response and notification kinds and measures what a
response-less message gains and what it hides.
