@apnex/network-adapter 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/hub-error.d.ts +42 -0
- package/dist/hub-error.js +74 -0
- package/dist/hub-error.js.map +1 -0
- package/dist/index.d.ts +29 -0
- package/dist/index.js +31 -0
- package/dist/index.js.map +1 -0
- package/dist/kernel/agent-client.d.ts +192 -0
- package/dist/kernel/agent-client.js +44 -0
- package/dist/kernel/agent-client.js.map +1 -0
- package/dist/kernel/event-router.d.ts +49 -0
- package/dist/kernel/event-router.js +150 -0
- package/dist/kernel/event-router.js.map +1 -0
- package/dist/kernel/handshake.d.ts +161 -0
- package/dist/kernel/handshake.js +257 -0
- package/dist/kernel/handshake.js.map +1 -0
- package/dist/kernel/instance.d.ts +40 -0
- package/dist/kernel/instance.js +79 -0
- package/dist/kernel/instance.js.map +1 -0
- package/dist/kernel/mcp-agent-client.d.ts +139 -0
- package/dist/kernel/mcp-agent-client.js +505 -0
- package/dist/kernel/mcp-agent-client.js.map +1 -0
- package/dist/kernel/poll-backstop.d.ts +108 -0
- package/dist/kernel/poll-backstop.js +243 -0
- package/dist/kernel/poll-backstop.js.map +1 -0
- package/dist/kernel/session-claim.d.ts +67 -0
- package/dist/kernel/session-claim.js +106 -0
- package/dist/kernel/session-claim.js.map +1 -0
- package/dist/kernel/state-sync.d.ts +43 -0
- package/dist/kernel/state-sync.js +85 -0
- package/dist/kernel/state-sync.js.map +1 -0
- package/dist/logger.d.ts +82 -0
- package/dist/logger.js +114 -0
- package/dist/logger.js.map +1 -0
- package/dist/notification-log.d.ts +29 -0
- package/dist/notification-log.js +66 -0
- package/dist/notification-log.js.map +1 -0
- package/dist/prompt-format.d.ts +38 -0
- package/dist/prompt-format.js +220 -0
- package/dist/prompt-format.js.map +1 -0
- package/dist/tool-manager/dispatcher.d.ts +180 -0
- package/dist/tool-manager/dispatcher.js +379 -0
- package/dist/tool-manager/dispatcher.js.map +1 -0
- package/dist/tool-manager/tool-catalog-cache.d.ts +85 -0
- package/dist/tool-manager/tool-catalog-cache.js +137 -0
- package/dist/tool-manager/tool-catalog-cache.js.map +1 -0
- package/dist/wire/mcp-transport.d.ts +120 -0
- package/dist/wire/mcp-transport.js +447 -0
- package/dist/wire/mcp-transport.js.map +1 -0
- package/dist/wire/transport.d.ts +174 -0
- package/dist/wire/transport.js +43 -0
- package/dist/wire/transport.js.map +1 -0
- package/package.json +32 -0
|
@@ -0,0 +1,447 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* McpTransport — first `ITransport` implementation, backed by the MCP
|
|
3
|
+
* SDK's `Client` + `StreamableHTTPClientTransport`.
|
|
4
|
+
*
|
|
5
|
+
* Phase 2 of the L4/L7 refactor: extracted alongside the existing
|
|
6
|
+
* `McpConnectionManager`, which stays intact for the rest of the
|
|
7
|
+
* migration. Nothing in the current runtime uses this file yet — it
|
|
8
|
+
* exists so the Phase-2 gap tests can exercise the L4 surface in
|
|
9
|
+
* isolation and prove the split holds before Phase 3 relocates the
|
|
10
|
+
* session FSM.
|
|
11
|
+
*
|
|
12
|
+
* ## What this class owns (L4)
|
|
13
|
+
*
|
|
14
|
+
* - The MCP wire: SDK `Client` + `StreamableHTTPClientTransport`.
|
|
15
|
+
* - SSE liveness: first-keepalive deadline, mid-stream watchdog,
|
|
16
|
+
* heartbeat POST loop.
|
|
17
|
+
* - Wire-level reconnection: on SSE death or heartbeat failure it
|
|
18
|
+
* tears down the SDK client and rebuilds a fresh one. Callers see
|
|
19
|
+
* only `reconnecting` + `reconnected` wire events.
|
|
20
|
+
* - Untyped `request(method, params)` over MCP `callTool`.
|
|
21
|
+
* - `listMethods()` via MCP `listTools`.
|
|
22
|
+
*
|
|
23
|
+
* ## What this class deliberately does NOT own
|
|
24
|
+
*
|
|
25
|
+
* - `register_role`. The bare register_role call that the legacy
|
|
26
|
+
* `McpConnectionManager` fires during `connect()` is a session
|
|
27
|
+
* concern — it binds the MCP session to an engineer role on the
|
|
28
|
+
* Hub. `McpTransport.connect()` returns once the MCP wire is
|
|
29
|
+
* initialized, *without* calling register_role. The caller
|
|
30
|
+
* (future `IAgentClient`) issues the role call via
|
|
31
|
+
* `transport.request("register_role", {role})` itself.
|
|
32
|
+
* - Session FSM. No synchronizing / streaming vocabulary, no
|
|
33
|
+
* sync buffer, no `completeSync()`.
|
|
34
|
+
* - `session_invalid` classification and retry. Callers see the
|
|
35
|
+
* raw error from `request()` and reconnect at the session layer.
|
|
36
|
+
*
|
|
37
|
+
* ## Hub-event push delivery
|
|
38
|
+
*
|
|
39
|
+
* SSE notifications land on the MCP client's `LoggingMessageNotification`
|
|
40
|
+
* handler. This class splits them into two streams:
|
|
41
|
+
*
|
|
42
|
+
* - `logger === "keepalive"` frames feed the SSE watchdog and are
|
|
43
|
+
* NOT forwarded to subscribers — they are wire noise.
|
|
44
|
+
* - `logger === "hub-event"` frames are emitted as
|
|
45
|
+
* `{ type: "push", method: "hub-event", payload }` via the
|
|
46
|
+
* `onWireEvent` stream. Classification, dedup, and routing happen
|
|
47
|
+
* above this layer.
|
|
48
|
+
*/
|
|
49
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
50
|
+
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
51
|
+
import { LoggingMessageNotificationSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
52
|
+
import { normalizeToILogger } from "../logger.js";
|
|
53
|
+
/**
|
|
54
|
+
* Wire-level reconnect backoff curve. Pure function so the invariant
|
|
55
|
+
* (start at `baseDelay`, double each consecutive failure, clamp at
|
|
56
|
+
* `maxDelay`) is unit-testable without spinning a real wire.
|
|
57
|
+
*
|
|
58
|
+
* The cap on the exponent (`6`) matches the legacy manager: after six
|
|
59
|
+
* consecutive failures we're pegged at 60s and further failures don't
|
|
60
|
+
* push the delay higher — they just sit at the cap until a keepalive
|
|
61
|
+
* arrives and resets `consecutiveReconnects` to zero.
|
|
62
|
+
*/
|
|
63
|
+
export function computeReconnectBackoff(consecutiveReconnects, baseDelay, maxDelay = 60_000) {
|
|
64
|
+
const exp = Math.min(Math.max(consecutiveReconnects, 0), 6);
|
|
65
|
+
return Math.min(baseDelay * Math.pow(2, exp), maxDelay);
|
|
66
|
+
}
|
|
67
|
+
export class McpTransport {
|
|
68
|
+
cfg;
|
|
69
|
+
log;
|
|
70
|
+
client = null;
|
|
71
|
+
sdkTransport = null;
|
|
72
|
+
_wireState = "disconnected";
|
|
73
|
+
handlers = [];
|
|
74
|
+
// SSE liveness
|
|
75
|
+
lastKeepaliveAt = 0;
|
|
76
|
+
sseVerified = false;
|
|
77
|
+
// Timers
|
|
78
|
+
heartbeatTimer = null;
|
|
79
|
+
sseWatchdogTimer = null;
|
|
80
|
+
firstKeepaliveTimer = null;
|
|
81
|
+
reconnectTimer = null;
|
|
82
|
+
// Metrics
|
|
83
|
+
totalReconnects = 0;
|
|
84
|
+
consecutiveReconnects = 0;
|
|
85
|
+
lastReconnectCause;
|
|
86
|
+
requestsInFlight = 0;
|
|
87
|
+
// Re-entrancy guard: prevents overlapping reconnects.
|
|
88
|
+
reconnecting = false;
|
|
89
|
+
// True once `close()` was called — suppresses further reconnect attempts.
|
|
90
|
+
closed = false;
|
|
91
|
+
constructor(config) {
|
|
92
|
+
this.cfg = {
|
|
93
|
+
url: config.url,
|
|
94
|
+
token: config.token,
|
|
95
|
+
heartbeatInterval: config.heartbeatInterval ?? 30_000,
|
|
96
|
+
sseKeepaliveTimeout: config.sseKeepaliveTimeout ?? 90_000,
|
|
97
|
+
firstKeepaliveDeadline: config.firstKeepaliveDeadline ?? 60_000,
|
|
98
|
+
sseWatchdogInterval: config.sseWatchdogInterval ?? 30_000,
|
|
99
|
+
reconnectDelay: config.reconnectDelay ?? 5_000,
|
|
100
|
+
getLastEventId: config.getLastEventId,
|
|
101
|
+
};
|
|
102
|
+
this.log = normalizeToILogger(config.logger, "McpTransport");
|
|
103
|
+
}
|
|
104
|
+
// ── ITransport: public surface ──────────────────────────────────────
|
|
105
|
+
get wireState() {
|
|
106
|
+
return this._wireState;
|
|
107
|
+
}
|
|
108
|
+
async connect() {
|
|
109
|
+
if (this._wireState === "connected" || this._wireState === "connecting") {
|
|
110
|
+
this.log.log("transport.connect.ignored", { wireState: this._wireState }, `connect() ignored in wireState=${this._wireState}`);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
this.closed = false;
|
|
114
|
+
this.transition("connecting");
|
|
115
|
+
try {
|
|
116
|
+
await this.createWire();
|
|
117
|
+
this.transition("connected");
|
|
118
|
+
this.startHeartbeat();
|
|
119
|
+
this.startSseWatchdog();
|
|
120
|
+
this.startFirstKeepaliveDeadline();
|
|
121
|
+
}
|
|
122
|
+
catch (err) {
|
|
123
|
+
this.log.log("transport.connect.failed", { error: String(err) }, `connect failed: ${err}`);
|
|
124
|
+
this.transition("disconnected");
|
|
125
|
+
throw err;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
async close() {
|
|
129
|
+
this.closed = true;
|
|
130
|
+
this.cancelReconnectTimer();
|
|
131
|
+
this.stopHeartbeat();
|
|
132
|
+
this.stopSseWatchdog();
|
|
133
|
+
this.cancelFirstKeepaliveDeadline();
|
|
134
|
+
const wasConnected = this._wireState !== "disconnected";
|
|
135
|
+
this.transition("disconnected");
|
|
136
|
+
await this.teardownWire();
|
|
137
|
+
if (wasConnected) {
|
|
138
|
+
this.emit({ type: "closed", reason: "caller-closed" });
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
async request(method, params) {
|
|
142
|
+
if (this._wireState !== "connected" || !this.client) {
|
|
143
|
+
throw new Error(`McpTransport.request: wire is ${this._wireState}`);
|
|
144
|
+
}
|
|
145
|
+
this.requestsInFlight++;
|
|
146
|
+
try {
|
|
147
|
+
const result = await this.client.callTool({
|
|
148
|
+
name: method,
|
|
149
|
+
arguments: params,
|
|
150
|
+
});
|
|
151
|
+
const content = result.content;
|
|
152
|
+
if (content?.[0]?.text) {
|
|
153
|
+
try {
|
|
154
|
+
return JSON.parse(content[0].text);
|
|
155
|
+
}
|
|
156
|
+
catch {
|
|
157
|
+
return content[0].text;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
finally {
|
|
163
|
+
this.requestsInFlight--;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
async listMethods() {
|
|
167
|
+
if (this._wireState !== "connected" || !this.client)
|
|
168
|
+
return [];
|
|
169
|
+
try {
|
|
170
|
+
const { tools } = await this.client.listTools();
|
|
171
|
+
return tools.map((t) => t.name);
|
|
172
|
+
}
|
|
173
|
+
catch {
|
|
174
|
+
return [];
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Escape hatch for shims that need full MCP tool definitions
|
|
179
|
+
* (name + inputSchema + description), not just method names.
|
|
180
|
+
* Consumed by proxy shims that re-advertise the Hub's tool surface
|
|
181
|
+
* to another MCP client (e.g. Claude Code stdio proxy).
|
|
182
|
+
*/
|
|
183
|
+
async listToolsRaw() {
|
|
184
|
+
if (this._wireState !== "connected" || !this.client)
|
|
185
|
+
return [];
|
|
186
|
+
try {
|
|
187
|
+
const { tools } = await this.client.listTools();
|
|
188
|
+
return tools;
|
|
189
|
+
}
|
|
190
|
+
catch {
|
|
191
|
+
return [];
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
onWireEvent(handler) {
|
|
195
|
+
this.handlers.push(handler);
|
|
196
|
+
}
|
|
197
|
+
getMetrics() {
|
|
198
|
+
return {
|
|
199
|
+
wireState: this._wireState,
|
|
200
|
+
totalReconnects: this.totalReconnects,
|
|
201
|
+
consecutiveReconnects: this.consecutiveReconnects,
|
|
202
|
+
lastReconnectCause: this.lastReconnectCause,
|
|
203
|
+
lastKeepaliveAt: this.lastKeepaliveAt || undefined,
|
|
204
|
+
requestsInFlight: this.requestsInFlight,
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Test-only helper. Session IDs are an MCP-specific concept exposed
|
|
209
|
+
* here so Phase-2 integration tests can assert that a wire reconnect
|
|
210
|
+
* rebuilds the session. Not part of `ITransport`.
|
|
211
|
+
*/
|
|
212
|
+
getSessionId() {
|
|
213
|
+
return this.sdkTransport?.sessionId ?? undefined;
|
|
214
|
+
}
|
|
215
|
+
// ── Internal: wire lifecycle ────────────────────────────────────────
|
|
216
|
+
async createWire() {
|
|
217
|
+
const headers = {
|
|
218
|
+
"Content-Type": "application/json",
|
|
219
|
+
Accept: "application/json, text/event-stream",
|
|
220
|
+
};
|
|
221
|
+
if (this.cfg.token) {
|
|
222
|
+
headers["Authorization"] = `Bearer ${this.cfg.token}`;
|
|
223
|
+
}
|
|
224
|
+
// Mission-56 W2.2: attach `Last-Event-ID` when the layer above
|
|
225
|
+
// (McpAgentClient) has observed at least one Hub event. The Hub's
|
|
226
|
+
// W1b wrapper backfills Messages with id > lastEventId before
|
|
227
|
+
// resuming live emit. Cold-start (provider returns undefined or no
|
|
228
|
+
// provider configured) falls through to the stream-all-with-soft-cap
|
|
229
|
+
// path on the Hub side.
|
|
230
|
+
const lastEventId = this.cfg.getLastEventId?.();
|
|
231
|
+
if (lastEventId !== undefined && lastEventId !== "") {
|
|
232
|
+
headers["Last-Event-ID"] = lastEventId;
|
|
233
|
+
}
|
|
234
|
+
// Same rationale as McpConnectionManager: disable the SDK's
|
|
235
|
+
// internal SSE reconnection so our watchdog is the single authority
|
|
236
|
+
// for liveness. A split-brain where the SDK reopens the stream
|
|
237
|
+
// internally would leave our notification handler wired to the
|
|
238
|
+
// dead one.
|
|
239
|
+
this.sdkTransport = new StreamableHTTPClientTransport(new URL(this.cfg.url), {
|
|
240
|
+
requestInit: { headers },
|
|
241
|
+
reconnectionOptions: {
|
|
242
|
+
initialReconnectionDelay: 1000,
|
|
243
|
+
maxReconnectionDelay: 30_000,
|
|
244
|
+
reconnectionDelayGrowFactor: 1.5,
|
|
245
|
+
maxRetries: 0,
|
|
246
|
+
},
|
|
247
|
+
});
|
|
248
|
+
this.client = new Client({ name: "ois-mcp-transport", version: "1.0.0" }, { capabilities: {} });
|
|
249
|
+
this.sseVerified = false;
|
|
250
|
+
this.lastKeepaliveAt = 0;
|
|
251
|
+
this.client.setNotificationHandler(LoggingMessageNotificationSchema, (notification) => {
|
|
252
|
+
const params = notification.params;
|
|
253
|
+
if (!params)
|
|
254
|
+
return;
|
|
255
|
+
if (params.logger === "keepalive") {
|
|
256
|
+
this.lastKeepaliveAt = Date.now();
|
|
257
|
+
if (!this.sseVerified) {
|
|
258
|
+
this.sseVerified = true;
|
|
259
|
+
this.cancelFirstKeepaliveDeadline();
|
|
260
|
+
this.log.log("transport.sse.verified", undefined, "SSE verified (first keepalive received)");
|
|
261
|
+
}
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
if (params.logger === "hub-event" && params.data) {
|
|
265
|
+
this.emit({
|
|
266
|
+
type: "push",
|
|
267
|
+
method: "hub-event",
|
|
268
|
+
payload: params.data,
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
});
|
|
272
|
+
this.sdkTransport.onclose = () => {
|
|
273
|
+
this.log.log("transport.sdk.onclose", undefined, "SDK transport onclose fired");
|
|
274
|
+
if (this._wireState === "connected") {
|
|
275
|
+
void this.reconnectWire("peer_closed");
|
|
276
|
+
}
|
|
277
|
+
};
|
|
278
|
+
this.sdkTransport.onerror = (err) => {
|
|
279
|
+
this.log.log("transport.sdk.onerror", { error: err?.message || String(err) }, `SDK transport onerror: ${err?.message || err}`);
|
|
280
|
+
if (this._wireState === "connected") {
|
|
281
|
+
void this.reconnectWire("wire_error");
|
|
282
|
+
}
|
|
283
|
+
};
|
|
284
|
+
this.client.onerror = (err) => {
|
|
285
|
+
this.log.log("transport.client.onerror", { error: err?.message || String(err) }, `SDK client onerror: ${err?.message || err}`);
|
|
286
|
+
};
|
|
287
|
+
await this.client.connect(this.sdkTransport);
|
|
288
|
+
this.log.log("transport.wire.initialized", undefined, "MCP wire initialized");
|
|
289
|
+
}
|
|
290
|
+
async teardownWire() {
|
|
291
|
+
const oldClient = this.client;
|
|
292
|
+
this.client = null;
|
|
293
|
+
this.sdkTransport = null;
|
|
294
|
+
this.stopHeartbeat();
|
|
295
|
+
this.stopSseWatchdog();
|
|
296
|
+
this.cancelFirstKeepaliveDeadline();
|
|
297
|
+
if (oldClient) {
|
|
298
|
+
try {
|
|
299
|
+
await oldClient.close();
|
|
300
|
+
}
|
|
301
|
+
catch {
|
|
302
|
+
// expected on dead wires
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
// Let any in-flight SSE callbacks settle before the next wire opens.
|
|
306
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* Wire-level reconnect loop. Entered on any wire-death signal
|
|
310
|
+
* (SSE drop, heartbeat failure, transport error). Emits
|
|
311
|
+
* `reconnecting` + (on success) `reconnected` wire events so the
|
|
312
|
+
* AgentClient above can re-run its L7 handshake.
|
|
313
|
+
*/
|
|
314
|
+
async reconnectWire(cause) {
|
|
315
|
+
if (this.reconnecting || this.closed)
|
|
316
|
+
return;
|
|
317
|
+
this.reconnecting = true;
|
|
318
|
+
this.totalReconnects++;
|
|
319
|
+
this.consecutiveReconnects++;
|
|
320
|
+
this.lastReconnectCause = cause;
|
|
321
|
+
this.log.log("transport.reconnect.start", { cause, consecutive: this.consecutiveReconnects }, `reconnectWire: cause=${cause}, consecutive=${this.consecutiveReconnects}`);
|
|
322
|
+
this.emit({ type: "reconnecting", cause });
|
|
323
|
+
this.transition("connecting");
|
|
324
|
+
await this.teardownWire();
|
|
325
|
+
try {
|
|
326
|
+
await this.createWire();
|
|
327
|
+
if (this.closed) {
|
|
328
|
+
// close() called mid-reconnect — bail out.
|
|
329
|
+
await this.teardownWire();
|
|
330
|
+
this.transition("disconnected");
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
this.transition("connected");
|
|
334
|
+
this.startHeartbeat();
|
|
335
|
+
this.startSseWatchdog();
|
|
336
|
+
this.startFirstKeepaliveDeadline();
|
|
337
|
+
this.emit({ type: "reconnected" });
|
|
338
|
+
}
|
|
339
|
+
catch (err) {
|
|
340
|
+
this.log.log("transport.reconnect.failed", { cause, error: String(err) }, `reconnectWire failed: ${err}`);
|
|
341
|
+
this.transition("disconnected");
|
|
342
|
+
this.scheduleReconnectTimer(cause);
|
|
343
|
+
}
|
|
344
|
+
finally {
|
|
345
|
+
this.reconnecting = false;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
scheduleReconnectTimer(cause) {
|
|
349
|
+
if (this.closed)
|
|
350
|
+
return;
|
|
351
|
+
this.cancelReconnectTimer();
|
|
352
|
+
const backoff = computeReconnectBackoff(this.consecutiveReconnects, this.cfg.reconnectDelay);
|
|
353
|
+
this.log.log("transport.reconnect.retry_scheduled", { cause, backoffMs: backoff }, `scheduling retry in ${backoff}ms`);
|
|
354
|
+
this.reconnectTimer = setTimeout(() => {
|
|
355
|
+
this.reconnectTimer = null;
|
|
356
|
+
void this.reconnectWire(cause);
|
|
357
|
+
}, backoff);
|
|
358
|
+
}
|
|
359
|
+
cancelReconnectTimer() {
|
|
360
|
+
if (this.reconnectTimer) {
|
|
361
|
+
clearTimeout(this.reconnectTimer);
|
|
362
|
+
this.reconnectTimer = null;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
// ── Internal: liveness timers ───────────────────────────────────────
|
|
366
|
+
startHeartbeat() {
|
|
367
|
+
this.stopHeartbeat();
|
|
368
|
+
this.heartbeatTimer = setInterval(async () => {
|
|
369
|
+
if (this._wireState !== "connected" || !this.client)
|
|
370
|
+
return;
|
|
371
|
+
try {
|
|
372
|
+
await this.client.listTools();
|
|
373
|
+
}
|
|
374
|
+
catch {
|
|
375
|
+
this.log.log("transport.heartbeat.failed", undefined, "heartbeat failed, triggering wire reconnect");
|
|
376
|
+
void this.reconnectWire("heartbeat_failed");
|
|
377
|
+
}
|
|
378
|
+
}, this.cfg.heartbeatInterval);
|
|
379
|
+
}
|
|
380
|
+
stopHeartbeat() {
|
|
381
|
+
if (this.heartbeatTimer) {
|
|
382
|
+
clearInterval(this.heartbeatTimer);
|
|
383
|
+
this.heartbeatTimer = null;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
startSseWatchdog() {
|
|
387
|
+
this.stopSseWatchdog();
|
|
388
|
+
this.sseWatchdogTimer = setInterval(() => {
|
|
389
|
+
if (this._wireState !== "connected")
|
|
390
|
+
return;
|
|
391
|
+
if (!this.sseVerified)
|
|
392
|
+
return;
|
|
393
|
+
const gap = Date.now() - this.lastKeepaliveAt;
|
|
394
|
+
if (gap > this.cfg.sseKeepaliveTimeout) {
|
|
395
|
+
this.log.log("transport.sse.watchdog.fired", { gapMs: gap, thresholdMs: this.cfg.sseKeepaliveTimeout }, `SSE watchdog: ${Math.round(gap / 1000)}s without keepalive (threshold ${this.cfg.sseKeepaliveTimeout / 1000}s)`);
|
|
396
|
+
void this.reconnectWire("sse_watchdog");
|
|
397
|
+
}
|
|
398
|
+
}, this.cfg.sseWatchdogInterval);
|
|
399
|
+
}
|
|
400
|
+
stopSseWatchdog() {
|
|
401
|
+
if (this.sseWatchdogTimer) {
|
|
402
|
+
clearInterval(this.sseWatchdogTimer);
|
|
403
|
+
this.sseWatchdogTimer = null;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
startFirstKeepaliveDeadline() {
|
|
407
|
+
this.cancelFirstKeepaliveDeadline();
|
|
408
|
+
this.firstKeepaliveTimer = setTimeout(() => {
|
|
409
|
+
this.firstKeepaliveTimer = null;
|
|
410
|
+
if (this._wireState !== "connected")
|
|
411
|
+
return;
|
|
412
|
+
if (this.sseVerified)
|
|
413
|
+
return;
|
|
414
|
+
this.log.log("transport.sse.never_opened", { deadlineMs: this.cfg.firstKeepaliveDeadline }, `first keepalive deadline: ${this.cfg.firstKeepaliveDeadline}ms elapsed without SSE`);
|
|
415
|
+
void this.reconnectWire("sse_never_opened");
|
|
416
|
+
}, this.cfg.firstKeepaliveDeadline);
|
|
417
|
+
}
|
|
418
|
+
cancelFirstKeepaliveDeadline() {
|
|
419
|
+
if (this.firstKeepaliveTimer) {
|
|
420
|
+
clearTimeout(this.firstKeepaliveTimer);
|
|
421
|
+
this.firstKeepaliveTimer = null;
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
// ── Internal: state + event emission ────────────────────────────────
|
|
425
|
+
transition(to) {
|
|
426
|
+
if (this._wireState === to)
|
|
427
|
+
return;
|
|
428
|
+
const from = this._wireState;
|
|
429
|
+
this._wireState = to;
|
|
430
|
+
if (to === "connected") {
|
|
431
|
+
this.consecutiveReconnects = 0;
|
|
432
|
+
}
|
|
433
|
+
this.log.log("transport.wire.state", { from, to }, `wire ${from} → ${to}`);
|
|
434
|
+
this.emit({ type: "state", from, to });
|
|
435
|
+
}
|
|
436
|
+
emit(event) {
|
|
437
|
+
for (const h of this.handlers) {
|
|
438
|
+
try {
|
|
439
|
+
h(event);
|
|
440
|
+
}
|
|
441
|
+
catch (err) {
|
|
442
|
+
this.log.log("transport.handler.error", { error: String(err) }, `wire-event handler error: ${err}`);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
//# sourceMappingURL=mcp-transport.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp-transport.js","sourceRoot":"","sources":["../../src/wire/mcp-transport.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ITransport — Layer-4 wire surface for the network-adapter package.
|
|
3
|
+
*
|
|
4
|
+
* Phase 1: types only. Zero runtime change. The real implementation
|
|
5
|
+
* (`McpTransport`) lands in Phase 2, extracted out of the current
|
|
6
|
+
* `McpConnectionManager`.
|
|
7
|
+
*
|
|
8
|
+
* ## Responsibility boundary
|
|
9
|
+
*
|
|
10
|
+
* A Transport owns exactly one logical wire to a Hub. It is responsible
|
|
11
|
+
* for:
|
|
12
|
+
*
|
|
13
|
+
* 1. Opening and closing that wire.
|
|
14
|
+
* 2. Shipping untyped request/response pairs over it (`request`).
|
|
15
|
+
* 3. Enumerating what method names the remote peer advertises
|
|
16
|
+
* (`listMethods` — used by the adapter for discovery translation).
|
|
17
|
+
* 4. Surfacing liveness: a coarse 3-value `wireState` and a stream of
|
|
18
|
+
* `WireEvent`s (connected, disconnected, peer-closed, etc.).
|
|
19
|
+
* 5. Wire-level reconnect policy: it transparently reconnects the
|
|
20
|
+
* underlying socket/HTTP/SSE stream on transient failure. It does
|
|
21
|
+
* NOT know about sessions, roles, handshakes, or FSM.
|
|
22
|
+
*
|
|
23
|
+
* It is NOT responsible for:
|
|
24
|
+
*
|
|
25
|
+
* - Session lifecycle (bind/rebind/teardown). That lives on the
|
|
26
|
+
* `IAgentClient` above it.
|
|
27
|
+
* - Authentication and handshake semantics (register_role, enriched handshake).
|
|
28
|
+
* - Event classification, dedup, or state sync.
|
|
29
|
+
* - Retry-on-session-invalid (that policy is above the wire).
|
|
30
|
+
*
|
|
31
|
+
* ## Why untyped `request(method, params)`
|
|
32
|
+
*
|
|
33
|
+
* The adapter and shim above must be transport-agnostic. By keeping the
|
|
34
|
+
* request surface a plain string-keyed call, the same adapter code can
|
|
35
|
+
* run against a future gRPC or QUIC transport without leaking protocol
|
|
36
|
+
* detail. MCP-specific encodings (tool calls, list_tools, etc.) are
|
|
37
|
+
* translated inside `McpTransport`, not exposed here.
|
|
38
|
+
*
|
|
39
|
+
* The trade-off: type safety on the wire is the transport's problem.
|
|
40
|
+
* Callers (the adapter) validate response shapes at the L7 seam.
|
|
41
|
+
*/
|
|
42
|
+
import type { ILogger, LegacyStringLogger } from "../logger.js";
|
|
43
|
+
/**
|
|
44
|
+
* Coarse-grained wire state.
|
|
45
|
+
*
|
|
46
|
+
* Deliberately only three values — the transport does NOT model a
|
|
47
|
+
* session FSM. "connected" means "the wire is up right now"; anything
|
|
48
|
+
* stronger (handshake done, session bound, streaming) lives on the
|
|
49
|
+
* AgentClient layer.
|
|
50
|
+
*/
|
|
51
|
+
export type WireState = "disconnected" | "connecting" | "connected";
|
|
52
|
+
/**
|
|
53
|
+
* Why the wire reconnected. A narrower vocabulary than
|
|
54
|
+
* `SessionReconnectReason` (in `agent-client.ts`), because the
|
|
55
|
+
* transport doesn't classify session-level failures.
|
|
56
|
+
*/
|
|
57
|
+
export type WireReconnectCause = "sse_watchdog" | "sse_never_opened" | "heartbeat_failed" | "peer_closed" | "wire_error";
|
|
58
|
+
/**
|
|
59
|
+
* Stream of wire-level events the AgentClient subscribes to. The
|
|
60
|
+
* transport emits these; the AgentClient uses them to drive its
|
|
61
|
+
* session FSM. Intentionally narrow — this is the entire L4→L7 event
|
|
62
|
+
* protocol.
|
|
63
|
+
*/
|
|
64
|
+
export type WireEvent = {
|
|
65
|
+
type: "state";
|
|
66
|
+
from: WireState;
|
|
67
|
+
to: WireState;
|
|
68
|
+
} | {
|
|
69
|
+
type: "reconnecting";
|
|
70
|
+
cause: WireReconnectCause;
|
|
71
|
+
} | {
|
|
72
|
+
type: "reconnected";
|
|
73
|
+
} | {
|
|
74
|
+
type: "closed";
|
|
75
|
+
reason?: string;
|
|
76
|
+
} | {
|
|
77
|
+
/**
|
|
78
|
+
* Unsolicited inbound message from the remote (push notification,
|
|
79
|
+
* server-sent event). The transport delivers the raw body and
|
|
80
|
+
* the method name; the adapter decides what to do with it.
|
|
81
|
+
*/
|
|
82
|
+
type: "push";
|
|
83
|
+
method: string;
|
|
84
|
+
payload: unknown;
|
|
85
|
+
};
|
|
86
|
+
export type WireEventHandler = (event: WireEvent) => void;
|
|
87
|
+
/**
|
|
88
|
+
* Transport configuration — minimal. Anything session- or role-related
|
|
89
|
+
* belongs in `IAgentClient` config, not here.
|
|
90
|
+
*/
|
|
91
|
+
export interface TransportConfig {
|
|
92
|
+
url: string;
|
|
93
|
+
token?: string;
|
|
94
|
+
heartbeatInterval?: number;
|
|
95
|
+
sseKeepaliveTimeout?: number;
|
|
96
|
+
firstKeepaliveDeadline?: number;
|
|
97
|
+
sseWatchdogInterval?: number;
|
|
98
|
+
reconnectDelay?: number;
|
|
99
|
+
/**
|
|
100
|
+
* Logger injection. Preferred is `ILogger`; the string variant is a
|
|
101
|
+
* Phase-2 migration aid so existing string-logger call sites can
|
|
102
|
+
* wire up a transport without being rewritten first.
|
|
103
|
+
*/
|
|
104
|
+
logger?: ILogger | LegacyStringLogger;
|
|
105
|
+
/**
|
|
106
|
+
* Mission-56 W2.2: optional provider for the most-recent Hub event id
|
|
107
|
+
* the layer above has observed. The transport reads this at
|
|
108
|
+
* (re)connect time and, when a value is returned, attaches it as the
|
|
109
|
+
* canonical SSE `Last-Event-ID` header on the inbound stream. The
|
|
110
|
+
* Hub-side W1b wrapper consumes the header to backfill missed
|
|
111
|
+
* Messages before resuming live emit. Returning `undefined` (no
|
|
112
|
+
* value yet) means cold-start — Hub falls through to the
|
|
113
|
+
* stream-all-with-soft-cap path.
|
|
114
|
+
*/
|
|
115
|
+
getLastEventId?: () => string | undefined;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Pull-style telemetry on a Transport. Deliberately a point-in-time
|
|
119
|
+
* snapshot — the log stream is the authoritative event record. These
|
|
120
|
+
* counters exist so an AgentClient (or operator) can report
|
|
121
|
+
* "how is this wire doing right now" without subscribing.
|
|
122
|
+
*/
|
|
123
|
+
export interface TransportMetrics {
|
|
124
|
+
readonly wireState: WireState;
|
|
125
|
+
readonly totalReconnects: number;
|
|
126
|
+
readonly consecutiveReconnects: number;
|
|
127
|
+
readonly lastReconnectCause?: WireReconnectCause;
|
|
128
|
+
readonly lastKeepaliveAt?: number;
|
|
129
|
+
readonly requestsInFlight: number;
|
|
130
|
+
}
|
|
131
|
+
export interface ITransport {
|
|
132
|
+
/** Current coarse wire state. */
|
|
133
|
+
readonly wireState: WireState;
|
|
134
|
+
/**
|
|
135
|
+
* Open the wire. Resolves when `wireState === "connected"`. Idempotent:
|
|
136
|
+
* calling `connect()` on an already-connected transport is a no-op.
|
|
137
|
+
*/
|
|
138
|
+
connect(): Promise<void>;
|
|
139
|
+
/**
|
|
140
|
+
* Close the wire cleanly. After this resolves, `wireState === "disconnected"`
|
|
141
|
+
* and no further `WireEvent`s will be delivered except a final
|
|
142
|
+
* `{type:"state", to:"disconnected"}` if one hasn't fired yet.
|
|
143
|
+
*/
|
|
144
|
+
close(): Promise<void>;
|
|
145
|
+
/**
|
|
146
|
+
* Ship an untyped request/response pair over the wire.
|
|
147
|
+
*
|
|
148
|
+
* Protocol framing is the transport's problem. Session-invalid
|
|
149
|
+
* classification is NOT — callers receive the raw error and decide
|
|
150
|
+
* whether to reconnect at the session layer.
|
|
151
|
+
*/
|
|
152
|
+
request(method: string, params: Record<string, unknown>): Promise<unknown>;
|
|
153
|
+
/**
|
|
154
|
+
* Enumerate the method names currently advertised by the remote peer.
|
|
155
|
+
* Used by the AgentClient for discovery translation: it rewrites this
|
|
156
|
+
* list into an `IAgentClient`-shaped descriptor set exposed to shims.
|
|
157
|
+
*
|
|
158
|
+
* The return shape is intentionally `string[]` rather than full
|
|
159
|
+
* JSON-schema descriptors — richer discovery, if needed, is a
|
|
160
|
+
* future extension via an optional second method. Keeping the minimum
|
|
161
|
+
* here means every transport can satisfy it cheaply.
|
|
162
|
+
*/
|
|
163
|
+
listMethods(): Promise<string[]>;
|
|
164
|
+
/** Subscribe to the wire-event stream. Multiple subscribers allowed. */
|
|
165
|
+
onWireEvent(handler: WireEventHandler): void;
|
|
166
|
+
/** Point-in-time snapshot of wire-level telemetry. */
|
|
167
|
+
getMetrics(): TransportMetrics;
|
|
168
|
+
/**
|
|
169
|
+
* Current wire-level session id, if any. For MCP this is the
|
|
170
|
+
* StreamableHTTP session id issued by the server; for a loopback
|
|
171
|
+
* transport it's a synthetic handle. Undefined before first `connect()`.
|
|
172
|
+
*/
|
|
173
|
+
getSessionId(): string | undefined;
|
|
174
|
+
}
|