You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
|**Old client → new server**|[`optionA`](./optionAShimMrtrCanonical.ts)–[`optionE`](./optionEDegradeOnly.ts) in this folder | Five — server handler shape is genuinely contested |
11
-
|**New client → old server**|[`clientDualPath.ts`](../../../client/src/mrtr-dual-path/clientDualPath.ts) (app, ~55 lines) + [`sdkLib.ts`](../../../client/src/mrtr-dual-path/sdkLib.ts) (SDK machinery) | One — handler signature is identical on both paths, SDK just routes |
|**Old client → new server**|[`optionA`](./server/src/mrtr-dual-path/optionAShimMrtrCanonical.ts)–[`optionE`](./server/src/mrtr-dual-path/optionEDegradeOnly.ts) in `server/src/mrtr-dual-path/`| Five — server handler shape is genuinely contested |
11
+
|**New client → old server**|[`clientDualPath.ts`](./client/src/mrtr-dual-path/clientDualPath.ts) (app, ~55 lines) + [`sdkLib.ts`](./client/src/mrtr-dual-path/sdkLib.ts) (SDK machinery)| One — handler signature is identical on both paths, SDK just routes |
12
12
13
13
The asymmetry is real: the server-side control flow changes between SSE-elicit (`await` inline) and MRTR (`return IncompleteResult`), so there are trade-offs to argue about. The client-side handler shape is the same either way (`(params) => Promise<ElicitResult>`), so there's
14
14
nothing to choose.
@@ -17,20 +17,20 @@ nothing to choose.
17
17
18
18
| Server infra | 2025-11 client | 2026-06 client |
| Can hold SSE |**← this folder**| just use MRTR |
20
+
| Can hold SSE |**← options A–E**| just use MRTR |
21
21
| MRTR-only | tool fails (unresolvable) | just use MRTR |
22
22
23
23
Bottom-left is discounted: no amount of SDK work fills it when the server infra can't hold SSE. These demos are about whether the top-left is worth filling, and if so, how.
24
24
25
25
## Options
26
26
27
-
|| Author writes | SDK does | Hidden re-entry | Old client gets |
|[A](./optionAShimMrtrCanonical.ts)| MRTR-native only | Emulates retry loop over SSE | Yes, but safe (guard is explicit in source) | Full elicitation |
30
-
|[B](./optionBShimAwaitCanonical.ts)|`await elicit()` only | Exception → `IncompleteResult`| Yes, **unsafe** (invisible in source) | Full elicitation |
31
-
|[C](./optionCExplicitVersionBranch.ts)| One handler, `if (mrtr)` branch | Version accessor | No | Full elicitation |
32
-
|[D](./optionDDualRegistration.ts)| Two handlers | Picks by version | No | Full elicitation |
33
-
|[E](./optionEDegradeOnly.ts)| MRTR-native only | Nothing | No | Error ("requires newer client") |
27
+
|| Author writes | SDK does | Hidden re-entry | Old client gets |
|[A](./server/src/mrtr-dual-path/optionAShimMrtrCanonical.ts)| MRTR-native only | Emulates retry loop over SSE | Yes, but safe (guard is explicit in source) | Full elicitation |
30
+
|[B](./server/src/mrtr-dual-path/optionBShimAwaitCanonical.ts)|`await elicit()` only | Exception → `IncompleteResult`| Yes, **unsafe** (invisible in source) | Full elicitation |
31
+
|[C](./server/src/mrtr-dual-path/optionCExplicitVersionBranch.ts)| One handler, `if (mrtr)` branch | Version accessor | No | Full elicitation |
32
+
|[D](./server/src/mrtr-dual-path/optionDDualRegistration.ts)| Two handlers | Picks by version | No | Full elicitation |
33
+
|[E](./server/src/mrtr-dual-path/optionEDegradeOnly.ts)| MRTR-native only | Nothing | No | Error ("requires newer client") |
34
34
35
35
"Hidden re-entry" = the handler function is invoked more than once for a single logical tool call, and the author can't tell from the source text. A is safe because MRTR-native code has the re-entry guard (`if (!prefs) return`) visible in the source even though the _loop_ is
36
36
hidden. B is unsafe because `await elicit()` looks like a suspension point but is actually a re-entry point on MRTR sessions — see the `auditLog` landmine in that file.
@@ -41,9 +41,8 @@ None. All five options present identical wire behaviour to each client version.
41
41
in every case. The server's internal choice doesn't leak. This is the cleanest argument against per-feature `-mrtr` capability flags: there's nothing for them to signal, because the client's behaviour is already fully determined by `protocolVersion` plus the existing
42
42
`elicitation`/`sampling` capabilities.
43
43
44
-
For the reverse direction — new client SDK connecting to an old server — see `examples/client/src/mrtr-dual-path/`. Split into two files to make the boundary explicit: [`clientDualPath.ts`](../../../client/src/mrtr-dual-path/clientDualPath.ts) is ~55 lines of what the app
45
-
developer writes (one `handleElicitation` function, one registration, one tool call); [`sdkLib.ts`](../../../client/src/mrtr-dual-path/sdkLib.ts) is the retry loop + `IncompleteResult` parsing the SDK would ship. The app file is small on purpose — the delta from today's client
46
-
code is zero.
44
+
For the reverse direction — new client SDK connecting to an old server — see `examples/client/src/mrtr-dual-path/`. Split into two files to make the boundary explicit: [`clientDualPath.ts`](./client/src/mrtr-dual-path/clientDualPath.ts) is ~55 lines of what the app developer
45
+
writes (one `handleElicitation` function, one registration, one tool call); [`sdkLib.ts`](./client/src/mrtr-dual-path/sdkLib.ts) is the retry loop + `IncompleteResult` parsing the SDK would ship. The app file is small on purpose — the delta from today's client code is zero.
47
46
48
47
## Trade-offs
49
48
@@ -61,14 +60,20 @@ the dual-path burden on the tool author rather than the SDK.
61
60
62
61
## Running
63
62
64
-
All demos use `DEMO_PROTOCOL_VERSION` to simulate the negotiated version, since the real SDK doesn't surface it to handlers yet:
63
+
All demos use `DEMO_PROTOCOL_VERSION` to simulate the negotiated version, since the real SDK doesn't surface it to handlers yet. Server demos run from `examples/server`:
`IncompleteResult` is smuggled through the current `registerTool` signature as a JSON text block (same hack as #1597). A real implementation emits `JSONRPCIncompleteResultResponse` at the protocol layer — see `shims.ts:wrap()`.
70
+
The client demo spawns the server itself (run from `examples/client`):
`IncompleteResult` is smuggled through the current `registerTool` signature as a JSON text block (same hack as #1597). A real implementation emits `JSONRPCIncompleteResultResponse` at the protocol layer — see `server/src/mrtr-dual-path/shims.ts:wrap()`.
0 commit comments