@praxis-ai/praxis 0.1.3 → 0.1.5

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.
Files changed (56) hide show
  1. package/dist/agentCore/index.d.ts +11 -3
  2. package/dist/applicationLayer/applicationRuntime.js +7 -1
  3. package/dist/basetool/authoring.d.ts +2 -0
  4. package/dist/basetool/authoring.js +2 -0
  5. package/dist/basetool/catalog.d.ts +1 -1
  6. package/dist/basetool/catalog.js +86 -4
  7. package/dist/basetool/core/index.d.ts +4 -2
  8. package/dist/basetool/core/index.js +8 -0
  9. package/dist/basetool/core/mcpCompletions.d.ts +2 -0
  10. package/dist/basetool/core/mcpCompletions.js +70 -0
  11. package/dist/basetool/core/mcpPrompts.d.ts +2 -0
  12. package/dist/basetool/core/mcpPrompts.js +48 -0
  13. package/dist/basetool/core/mcpResources.js +41 -5
  14. package/dist/basetool/profiles.js +15 -1
  15. package/dist/basetool/registry.d.ts +1 -1
  16. package/dist/basetool/supportCatalog.js +23 -6
  17. package/dist/runtimeImplementation/praxisRuntimeKernel.js +1696 -1499
  18. package/dist/runtimeImplementation/runtime.execEngine/baseToolApprovalScope.js +11 -0
  19. package/dist/runtimeImplementation/runtime.execEngine/baseToolExecutorPortFactory.js +13 -1
  20. package/dist/runtimeImplementation/runtime.execEngine/baseToolPolicyAdjudicator.js +14 -0
  21. package/dist/runtimeImplementation/runtime.execEngine/mcpRuntimeAdapter.d.ts +27 -0
  22. package/dist/runtimeImplementation/runtime.execEngine/mcpRuntimeAdapter.js +648 -56
  23. package/dist/runtimeImplementation/runtime.execEngine/promptContextAssembly.d.ts +1 -0
  24. package/dist/runtimeImplementation/runtime.execEngine/promptContextAssembly.js +18 -0
  25. package/dist/runtimeImplementation/runtime.mcpPlane/index.d.ts +20 -7
  26. package/dist/runtimeImplementation/runtime.mcpPlane/index.js +105 -89
  27. package/dist/toolBase/catalog.d.ts +24 -0
  28. package/dist/toolBase/catalog.js +41 -3
  29. package/dist/toolBase/profiles.d.ts +3 -3
  30. package/dist/toolBase/profiles.js +2 -0
  31. package/dist/toolBase/types.d.ts +1 -1
  32. package/examples/raxode-mcp-plus-ten-server.config.json +229 -0
  33. package/examples/scripts/README.md +8 -2
  34. package/examples/scripts/mcp-plus-native-smoke.ts +1296 -0
  35. package/package.json +4 -2
  36. package/raxode-tui/dist/raxode-cli/backend/agents/codingAgent/prompts/tool-use.md +1 -1
  37. package/raxode-tui/dist/raxode-cli/backend/application/mcpConfig.d.ts +9 -0
  38. package/raxode-tui/dist/raxode-cli/backend/application/mcpConfig.js +65 -0
  39. package/raxode-tui/dist/raxode-cli/backend/application/mcpReadinessSummary.d.ts +28 -0
  40. package/raxode-tui/dist/raxode-cli/backend/application/mcpReadinessSummary.js +57 -0
  41. package/raxode-tui/dist/raxode-cli/backend/application/runtimeReadiness.d.ts +5 -1
  42. package/raxode-tui/dist/raxode-cli/backend/application/runtimeReadiness.js +40 -0
  43. package/raxode-tui/dist/raxode-cli/backend/application/stdioApplicationServer.js +6 -0
  44. package/raxode-tui/dist/raxode-cli/backend/directApplicationBackend.d.ts +4 -0
  45. package/raxode-tui/dist/raxode-cli/backend/directApplicationBackend.js +14 -0
  46. package/raxode-tui/dist/raxode-cli/backend/raxodeBackend.d.ts +1 -1
  47. package/raxode-tui/dist/raxode-cli/backend/raxodeBackend.js +16 -1
  48. package/raxode-tui/dist/raxode-cli/contracts.d.ts +1 -0
  49. package/raxode-tui/dist/raxode-cli/frontend/bridge/readiness.js +24 -0
  50. package/raxode-tui/dist/raxode-cli/frontend/tui/app/direct-tui.js +35 -0
  51. package/raxode-tui/dist/raxode-cli/frontend/tui/cli/raxode-cli.d.ts +2 -0
  52. package/raxode-tui/dist/raxode-cli/frontend/tui/cli/raxode-cli.js +8 -0
  53. package/raxode-tui/dist/raxode-cli/frontend/tui/config/raxode-config.d.ts +31 -0
  54. package/raxode-tui/dist/raxode-cli/frontend/tui/config/raxode-config.js +129 -0
  55. package/raxode-tui/dist/raxode-cli/index.d.ts +1 -0
  56. package/raxode-tui/package.json +1 -1
@@ -1,4 +1,6 @@
1
1
  import { spawn } from "node:child_process";
2
+ const MCP_PROTOCOL_VERSION = "2025-06-18";
3
+ const MCP_SESSION_ID_HEADER = "mcp-session-id";
2
4
  function success(output, metadata) {
3
5
  return { ok: true, output, metadata };
4
6
  }
@@ -20,6 +22,10 @@ function contentLengthFrame(payload) {
20
22
  const body = JSON.stringify(payload);
21
23
  return `Content-Length: ${Buffer.byteLength(body, "utf8")}\r\n\r\n${body}`;
22
24
  }
25
+ function writeStdioPayload(connection, payload) {
26
+ const framing = connection.profile.transport === "stdio" ? connection.profile.framing ?? "line-json" : undefined;
27
+ connection.child?.stdin.write(framing === "line-json" ? `${JSON.stringify(payload)}\n` : contentLengthFrame(payload));
28
+ }
23
29
  function extractContentLengthFrames(buffer) {
24
30
  const messages = [];
25
31
  let rest = buffer;
@@ -70,9 +76,60 @@ function extractLineJsonFrames(buffer) {
70
76
  }
71
77
  return { messages, rest };
72
78
  }
79
+ function jsonRpcMessagesFromUnknown(value) {
80
+ if (Array.isArray(value))
81
+ return value.filter(isObject);
82
+ return isObject(value) ? [value] : [];
83
+ }
84
+ function extractServerSentEvents(buffer) {
85
+ const events = [];
86
+ const chunks = buffer.split(/\r?\n\r?\n/u);
87
+ const rest = chunks.pop() ?? "";
88
+ for (const event of chunks) {
89
+ let eventName;
90
+ const dataLines = [];
91
+ for (const line of event.split(/\r?\n/u)) {
92
+ if (line.startsWith("event:")) {
93
+ const eventData = line.slice("event:".length);
94
+ eventName = eventData.startsWith(" ") ? eventData.slice(1) : eventData;
95
+ }
96
+ else if (line.startsWith("data:")) {
97
+ const data = line.slice("data:".length);
98
+ dataLines.push(data.startsWith(" ") ? data.slice(1) : data);
99
+ }
100
+ }
101
+ if (dataLines.length > 0)
102
+ events.push({ event: eventName, data: dataLines.join("\n") });
103
+ }
104
+ return { events, rest };
105
+ }
106
+ function parseServerSentEventMessages(text) {
107
+ const messages = [];
108
+ for (const event of extractServerSentEvents(text).events) {
109
+ try {
110
+ messages.push(...jsonRpcMessagesFromUnknown(JSON.parse(event.data)));
111
+ }
112
+ catch {
113
+ // Ignore malformed SSE data frames; the caller will fail if no response frame is present.
114
+ }
115
+ }
116
+ return messages;
117
+ }
118
+ function parseHttpJsonRpcMessages(contentType, text) {
119
+ if (contentType?.toLowerCase().includes("text/event-stream") === true) {
120
+ return parseServerSentEventMessages(text);
121
+ }
122
+ try {
123
+ return jsonRpcMessagesFromUnknown(JSON.parse(text));
124
+ }
125
+ catch {
126
+ return undefined;
127
+ }
128
+ }
73
129
  export function createMcpRuntimeAdapter(options) {
74
130
  const profiles = new Map(options.servers.map((profile) => [profile.serverId, profile]));
75
131
  const connections = new Map();
132
+ const rootsByServerId = new Map();
76
133
  const metadata = (profile, extra = {}) => ({
77
134
  serverId: profile.serverId,
78
135
  transport: profile.transport,
@@ -80,6 +137,20 @@ export function createMcpRuntimeAdapter(options) {
80
137
  ...extra,
81
138
  });
82
139
  const getProfile = (serverId) => profiles.get(serverId);
140
+ const subscriptionResourceUri = (requestInput) => {
141
+ if (typeof requestInput.uri === "string" && requestInput.uri.trim().length > 0)
142
+ return requestInput.uri;
143
+ if (typeof requestInput.subject === "string" && requestInput.subject.trim().length > 0)
144
+ return requestInput.subject;
145
+ if (typeof requestInput.subscriptionId !== "string")
146
+ return undefined;
147
+ const marker = ":subscription:resource:";
148
+ const markerIndex = requestInput.subscriptionId.indexOf(marker);
149
+ if (markerIndex < 0)
150
+ return undefined;
151
+ const uri = requestInput.subscriptionId.slice(markerIndex + marker.length);
152
+ return uri.trim().length > 0 ? uri : undefined;
153
+ };
83
154
  const getConnection = async (serverId, connectionId) => {
84
155
  const profile = getProfile(serverId);
85
156
  if (profile === undefined)
@@ -98,7 +169,309 @@ export function createMcpRuntimeAdapter(options) {
98
169
  if (connection.profile.transport === "stdio") {
99
170
  return requestStdio(connection, method, params);
100
171
  }
101
- return requestHttp(connection.profile, method, params);
172
+ if (connection.profile.transport === "sse") {
173
+ return requestSse(connection, method, params);
174
+ }
175
+ const requested = await requestHttp(connection.profile, method, params, connection.sessionId, (message) => {
176
+ recordNotification(connection, message);
177
+ });
178
+ if (requested.ok && typeof requested.metadata?.mcpSessionId === "string") {
179
+ connection.sessionId = requested.metadata.mcpSessionId;
180
+ }
181
+ return requested;
182
+ };
183
+ const notify = async (connection, method, params = {}) => {
184
+ if (connection.profile.transport === "stdio") {
185
+ return notifyStdio(connection, method, params);
186
+ }
187
+ if (connection.profile.transport === "sse") {
188
+ return notifySse(connection, method, params);
189
+ }
190
+ return notifyHttp(connection.profile, method, params, connection.sessionId);
191
+ };
192
+ const closeConnection = (connection) => {
193
+ for (const [, pending] of connection.pending) {
194
+ clearTimeout(pending.timeout);
195
+ pending.reject(new Error(`MCP connection '${connection.connectionId}' was closed.`));
196
+ }
197
+ connection.pending.clear();
198
+ connection.child?.kill();
199
+ connection.sseAbort?.abort();
200
+ };
201
+ const stdioClientCapabilities = () => ({
202
+ roots: { listChanged: true },
203
+ ...(options.host?.createSamplingMessage === undefined ? {} : { sampling: {} }),
204
+ ...(options.host?.elicit === undefined ? {} : { elicitation: { form: {}, url: {} } }),
205
+ });
206
+ const writeStdioResult = (connection, id, result) => {
207
+ if (id === undefined || id === null)
208
+ return;
209
+ writeStdioPayload(connection, { jsonrpc: "2.0", id, result });
210
+ };
211
+ const writeStdioError = (connection, id, code, message, data) => {
212
+ if (id === undefined || id === null)
213
+ return;
214
+ writeStdioPayload(connection, {
215
+ jsonrpc: "2.0",
216
+ id,
217
+ error: { code, message, ...(data === undefined ? {} : { data }) },
218
+ });
219
+ };
220
+ const hostRequest = (connection, message) => ({
221
+ serverId: connection.profile.serverId,
222
+ connectionId: connection.connectionId,
223
+ method: message.method ?? "",
224
+ requestId: message.id,
225
+ params: resultObject(message.params),
226
+ });
227
+ const recordNotification = (connection, message) => {
228
+ if (typeof message.method !== "string")
229
+ return;
230
+ const notification = {
231
+ serverId: connection.profile.serverId,
232
+ connectionId: connection.connectionId,
233
+ method: message.method,
234
+ params: resultObject(message.params),
235
+ };
236
+ connection.notifications.push(notification);
237
+ if (connection.notifications.length > 100)
238
+ connection.notifications.splice(0, connection.notifications.length - 100);
239
+ const notified = options.host?.onNotification?.(notification);
240
+ if (notified !== undefined)
241
+ void Promise.resolve(notified).catch(() => undefined);
242
+ };
243
+ const handleServerRequest = async (connection, message, writeResult, writeError) => {
244
+ if (typeof message.method !== "string") {
245
+ await writeError(message.id, -32600, "Invalid MCP JSON-RPC request.");
246
+ return;
247
+ }
248
+ try {
249
+ if (message.method === "ping") {
250
+ await writeResult(message.id, {});
251
+ return;
252
+ }
253
+ if (message.method === "roots/list") {
254
+ const requestInput = hostRequest(connection, message);
255
+ const roots = options.host?.listRoots === undefined
256
+ ? rootsByServerId.get(connection.profile.serverId) ?? []
257
+ : await options.host.listRoots(requestInput);
258
+ await writeResult(message.id, { roots });
259
+ return;
260
+ }
261
+ if (message.method === "sampling/createMessage") {
262
+ if (options.host?.createSamplingMessage === undefined) {
263
+ await writeError(message.id, -32601, "MCP sampling is not configured for this Praxis runtime.");
264
+ return;
265
+ }
266
+ await writeResult(message.id, await options.host.createSamplingMessage(hostRequest(connection, message)));
267
+ return;
268
+ }
269
+ if (message.method === "elicitation/create") {
270
+ if (options.host?.elicit === undefined) {
271
+ await writeError(message.id, -32601, "MCP elicitation is not configured for this Praxis runtime.");
272
+ return;
273
+ }
274
+ await writeResult(message.id, await options.host.elicit(hostRequest(connection, message)));
275
+ return;
276
+ }
277
+ await writeError(message.id, -32601, `MCP client method '${message.method}' is not supported by this Praxis runtime.`);
278
+ }
279
+ catch (error) {
280
+ await writeError(message.id, -32603, textFromUnknown(error.message ?? error));
281
+ }
282
+ };
283
+ const handleStdioRequest = async (connection, message) => {
284
+ await handleServerRequest(connection, message, (id, result) => writeStdioResult(connection, id, result), (id, code, messageText, data) => writeStdioError(connection, id, code, messageText, data));
285
+ };
286
+ const handleStdioMessage = (connection, message) => {
287
+ if (typeof message.method === "string" && message.id !== undefined && message.id !== null) {
288
+ void handleStdioRequest(connection, message);
289
+ return;
290
+ }
291
+ if (typeof message.method === "string") {
292
+ recordNotification(connection, message);
293
+ return;
294
+ }
295
+ if (message.id === undefined || message.id === null)
296
+ return;
297
+ const pending = connection.pending.get(message.id);
298
+ if (pending === undefined)
299
+ return;
300
+ clearTimeout(pending.timeout);
301
+ connection.pending.delete(message.id);
302
+ pending.resolve(message);
303
+ };
304
+ const handleSseMessage = (connection, message) => {
305
+ if (typeof message.method === "string" && message.id !== undefined && message.id !== null) {
306
+ void handleServerRequest(connection, message, async (id, result) => {
307
+ await postSsePayload(connection, { jsonrpc: "2.0", id, result });
308
+ }, async (id, code, messageText, data) => {
309
+ await postSsePayload(connection, {
310
+ jsonrpc: "2.0",
311
+ id,
312
+ error: { code, message: messageText, ...(data === undefined ? {} : { data }) },
313
+ });
314
+ });
315
+ return;
316
+ }
317
+ if (typeof message.method === "string") {
318
+ recordNotification(connection, message);
319
+ return;
320
+ }
321
+ if (message.id === undefined || message.id === null)
322
+ return;
323
+ const pending = connection.pending.get(message.id);
324
+ if (pending === undefined)
325
+ return;
326
+ clearTimeout(pending.timeout);
327
+ connection.pending.delete(message.id);
328
+ pending.resolve(message);
329
+ };
330
+ const postSsePayload = async (connection, payload) => {
331
+ if (connection.profile.transport !== "sse")
332
+ return failure("MCP_SSE_NOT_CONNECTED", "MCP SSE transport is not connected.");
333
+ const endpointUrl = connection.sseEndpointUrl ?? connection.profile.url;
334
+ const posted = await postJsonRpcPayload(connection.profile, endpointUrl, payload, connection.sessionId);
335
+ if (!posted.ok)
336
+ return posted;
337
+ if (typeof posted.metadata?.mcpSessionId === "string")
338
+ connection.sessionId = posted.metadata.mcpSessionId;
339
+ for (const message of posted.output.messages) {
340
+ handleSseMessage(connection, message);
341
+ }
342
+ return success({ status: "posted" });
343
+ };
344
+ const requestSse = async (connection, method, params) => {
345
+ if (connection.profile.transport !== "sse")
346
+ return failure("MCP_SSE_NOT_CONNECTED", "MCP SSE transport is not connected.");
347
+ const id = connection.nextId++;
348
+ const timeoutMs = connection.profile.timeoutMs ?? 5_000;
349
+ const response = new Promise((resolve, reject) => {
350
+ const timeout = setTimeout(() => {
351
+ connection.pending.delete(id);
352
+ reject(new Error(`MCP SSE request '${method}' timed out after ${timeoutMs}ms.`));
353
+ }, timeoutMs);
354
+ connection.pending.set(id, { resolve, reject, timeout });
355
+ });
356
+ const posted = await postSsePayload(connection, { jsonrpc: "2.0", id, method, params });
357
+ if (!posted.ok) {
358
+ const pending = connection.pending.get(id);
359
+ if (pending !== undefined)
360
+ clearTimeout(pending.timeout);
361
+ connection.pending.delete(id);
362
+ return posted;
363
+ }
364
+ try {
365
+ return normalizeJsonRpcResponse(await response);
366
+ }
367
+ catch (error) {
368
+ return failure("MCP_SSE_REQUEST_FAILED", textFromUnknown(error.message ?? error));
369
+ }
370
+ };
371
+ const notifySse = async (connection, method, params) => {
372
+ const posted = await postSsePayload(connection, { jsonrpc: "2.0", method, params });
373
+ if (!posted.ok)
374
+ return posted;
375
+ return success({ status: "notified" });
376
+ };
377
+ const openSseConnection = async (connection) => {
378
+ if (connection.profile.transport !== "sse")
379
+ return failure("MCP_SSE_CONNECT_FAILED", "MCP SSE profile is not configured.");
380
+ const profile = connection.profile;
381
+ const controller = new AbortController();
382
+ connection.sseAbort = controller;
383
+ let settled = false;
384
+ let settle = () => undefined;
385
+ const ready = new Promise((resolve) => {
386
+ settle = resolve;
387
+ });
388
+ const readyTimeout = setTimeout(() => {
389
+ if (settled)
390
+ return;
391
+ settled = true;
392
+ controller.abort();
393
+ settle(failure("MCP_SSE_CONNECT_FAILED", "MCP SSE endpoint did not announce a message endpoint."));
394
+ }, profile.timeoutMs ?? 5_000);
395
+ const setEndpoint = (endpointUrl) => {
396
+ if (settled)
397
+ return;
398
+ settled = true;
399
+ clearTimeout(readyTimeout);
400
+ connection.sseEndpointUrl = endpointUrl;
401
+ settle(success({ endpointUrl }));
402
+ };
403
+ try {
404
+ const response = await fetch(profile.sseUrl ?? profile.url, {
405
+ headers: httpHeaders(profile),
406
+ signal: controller.signal,
407
+ });
408
+ if (!response.ok) {
409
+ clearTimeout(readyTimeout);
410
+ settled = true;
411
+ return failure("MCP_SSE_CONNECT_FAILED", `MCP SSE endpoint returned HTTP ${response.status}.`);
412
+ }
413
+ if (response.body === null) {
414
+ clearTimeout(readyTimeout);
415
+ settled = true;
416
+ return failure("MCP_SSE_CONNECT_FAILED", "MCP SSE endpoint did not return a readable event stream.");
417
+ }
418
+ const reader = response.body.getReader();
419
+ const decoder = new TextDecoder();
420
+ void (async () => {
421
+ let streamBuffer = "";
422
+ try {
423
+ while (true) {
424
+ const read = await reader.read();
425
+ if (read.done)
426
+ break;
427
+ streamBuffer += decoder.decode(read.value, { stream: true });
428
+ const extracted = extractServerSentEvents(streamBuffer);
429
+ streamBuffer = extracted.rest;
430
+ for (const event of extracted.events) {
431
+ if (event.event === "endpoint") {
432
+ setEndpoint(resolveLegacySseEndpointUrl(profile, event.data));
433
+ continue;
434
+ }
435
+ if (!settled)
436
+ setEndpoint(profile.url);
437
+ for (const message of parseServerSentEventMessages(`${event.event === undefined ? "" : `event: ${event.event}\n`}data: ${event.data}\n\n`)) {
438
+ handleSseMessage(connection, message);
439
+ }
440
+ }
441
+ }
442
+ if (!settled) {
443
+ settled = true;
444
+ clearTimeout(readyTimeout);
445
+ settle(failure("MCP_SSE_CONNECT_FAILED", "MCP SSE endpoint closed before announcing a message endpoint."));
446
+ }
447
+ for (const [id, pending] of connection.pending) {
448
+ clearTimeout(pending.timeout);
449
+ pending.reject(new Error(`MCP SSE stream closed before response ${String(id)}.`));
450
+ }
451
+ connection.pending.clear();
452
+ }
453
+ catch (error) {
454
+ if (controller.signal.aborted)
455
+ return;
456
+ if (!settled) {
457
+ settled = true;
458
+ clearTimeout(readyTimeout);
459
+ settle(failure("MCP_SSE_CONNECT_FAILED", textFromUnknown(error.message ?? error)));
460
+ }
461
+ for (const [id, pending] of connection.pending) {
462
+ clearTimeout(pending.timeout);
463
+ pending.reject(new Error(`MCP SSE stream failed before response ${String(id)}: ${textFromUnknown(error.message ?? error)}`));
464
+ }
465
+ connection.pending.clear();
466
+ }
467
+ })();
468
+ return ready;
469
+ }
470
+ catch (error) {
471
+ clearTimeout(readyTimeout);
472
+ settled = true;
473
+ return failure("MCP_SSE_CONNECT_FAILED", textFromUnknown(error.message ?? error));
474
+ }
102
475
  };
103
476
  const connectProfile = async (profile, connectionId) => {
104
477
  if (profile.transport === "stdio") {
@@ -107,25 +480,27 @@ export function createMcpRuntimeAdapter(options) {
107
480
  env: { ...process.env, ...(profile.env ?? {}) },
108
481
  stdio: ["pipe", "pipe", "pipe"],
109
482
  });
110
- const connection = { profile, connectionId, child, nextId: 1, pending: new Map(), buffer: "" };
483
+ const connection = { profile, connectionId, child, nextId: 1, pending: new Map(), buffer: "", notifications: [] };
111
484
  child.stdout.setEncoding("utf8");
112
485
  child.stdout.on("data", (chunk) => {
113
- const extracted = profile.framing === "line-json"
486
+ const extracted = (profile.framing ?? "line-json") === "line-json"
114
487
  ? extractLineJsonFrames(connection.buffer + chunk)
115
488
  : extractContentLengthFrames(connection.buffer + chunk);
116
489
  connection.buffer = extracted.rest;
117
490
  for (const message of extracted.messages) {
118
- if (message.id === undefined || message.id === null)
119
- continue;
120
- const pending = connection.pending.get(message.id);
121
- if (pending === undefined)
122
- continue;
491
+ handleStdioMessage(connection, message);
492
+ }
493
+ });
494
+ child.on("error", (error) => {
495
+ connections.delete(connectionId);
496
+ for (const [id, pending] of connection.pending) {
123
497
  clearTimeout(pending.timeout);
124
- connection.pending.delete(message.id);
125
- pending.resolve(message);
498
+ pending.reject(new Error(`MCP stdio server error before response ${String(id)}: ${textFromUnknown(error.message)}`));
126
499
  }
500
+ connection.pending.clear();
127
501
  });
128
502
  child.on("exit", () => {
503
+ connections.delete(connectionId);
129
504
  for (const [id, pending] of connection.pending) {
130
505
  clearTimeout(pending.timeout);
131
506
  pending.reject(new Error(`MCP stdio server exited before response ${String(id)}.`));
@@ -134,41 +509,66 @@ export function createMcpRuntimeAdapter(options) {
134
509
  });
135
510
  const initialized = await requestStdio(connection, "initialize", {
136
511
  protocolVersion: "2025-06-18",
137
- capabilities: {},
512
+ capabilities: stdioClientCapabilities(),
138
513
  clientInfo: { name: "praxis-agentcore", version: "0.1.0" },
139
514
  });
140
515
  if (!initialized.ok) {
141
516
  child.kill();
142
517
  return initialized;
143
518
  }
519
+ writeStdioPayload(connection, { jsonrpc: "2.0", method: "notifications/initialized", params: {} });
144
520
  return success(connection, metadata(profile, { connectionId, initialized: true }));
145
521
  }
146
- if (profile.transport === "sse" && profile.sseUrl !== undefined) {
147
- const controller = new AbortController();
148
- const timeout = setTimeout(() => controller.abort(), profile.timeoutMs ?? 2_000);
149
- try {
150
- const response = await fetch(profile.sseUrl, { headers: profile.headers, signal: controller.signal });
151
- if (!response.ok)
152
- return failure("MCP_SSE_CONNECT_FAILED", `MCP SSE endpoint returned HTTP ${response.status}.`);
153
- }
154
- catch (error) {
155
- return failure("MCP_SSE_CONNECT_FAILED", textFromUnknown(error.message ?? error));
522
+ const connection = { profile, connectionId, nextId: 1, pending: new Map(), buffer: "", notifications: [] };
523
+ if (profile.transport === "sse") {
524
+ const opened = await openSseConnection(connection);
525
+ if (!opened.ok)
526
+ return opened;
527
+ const initialized = await requestSse(connection, "initialize", {
528
+ protocolVersion: "2025-06-18",
529
+ capabilities: {},
530
+ clientInfo: { name: "praxis-agentcore", version: "0.1.0" },
531
+ });
532
+ if (!initialized.ok) {
533
+ closeConnection(connection);
534
+ return initialized;
156
535
  }
157
- finally {
158
- clearTimeout(timeout);
536
+ const notified = await notifySse(connection, "notifications/initialized", {});
537
+ if (!notified.ok) {
538
+ closeConnection(connection);
539
+ return notified;
159
540
  }
541
+ return success(connection, metadata(profile, { connectionId, initialized: true, endpointUrl: opened.output.endpointUrl }));
160
542
  }
161
- const connection = { profile, connectionId, nextId: 1, pending: new Map(), buffer: "" };
162
543
  const initialized = await requestHttp(profile, "initialize", {
163
544
  protocolVersion: "2025-06-18",
164
545
  capabilities: {},
165
546
  clientInfo: { name: "praxis-agentcore", version: "0.1.0" },
547
+ }, undefined, (message) => {
548
+ recordNotification(connection, message);
166
549
  });
167
550
  if (!initialized.ok)
168
551
  return initialized;
169
- return success(connection, metadata(profile, { connectionId, initialized: true }));
552
+ const sessionId = typeof initialized.metadata?.mcpSessionId === "string" ? initialized.metadata.mcpSessionId : undefined;
553
+ const notified = await notifyHttp(profile, "notifications/initialized", {}, sessionId);
554
+ if (!notified.ok)
555
+ return notified;
556
+ connection.sessionId = sessionId;
557
+ return success(connection, metadata(profile, { connectionId, initialized: true, sessionId }));
170
558
  };
171
559
  return {
560
+ async shutdown() {
561
+ const closedConnections = connections.size;
562
+ for (const [, connection] of connections) {
563
+ closeConnection(connection);
564
+ }
565
+ connections.clear();
566
+ return success({
567
+ status: "shutdown",
568
+ closedConnections,
569
+ serverIds: [...profiles.keys()],
570
+ });
571
+ },
172
572
  async authenticate(requestInput) {
173
573
  const profile = getProfile(requestInput.serverId);
174
574
  if (profile === undefined)
@@ -207,7 +607,7 @@ export function createMcpRuntimeAdapter(options) {
207
607
  const connection = connections.get(id);
208
608
  if (connection === undefined)
209
609
  return success({ connectionId: id, status: "not_found", serverId: requestInput.serverId, providerMetadata: metadata(profile, { connectionId: id }) });
210
- connection.child?.kill();
610
+ closeConnection(connection);
211
611
  connections.delete(id);
212
612
  return success({ connectionId: id, status: "disconnected", serverId: requestInput.serverId, providerMetadata: metadata(profile, { connectionId: id }) });
213
613
  },
@@ -215,13 +615,62 @@ export function createMcpRuntimeAdapter(options) {
215
615
  const connected = await getConnection(requestInput.serverId, requestInput.connectionId);
216
616
  if (!connected.ok)
217
617
  return connected;
218
- return success({ subscriptionId: `${requestInput.serverId}:subscription:${requestInput.subjectType}:${requestInput.subject}`, status: "subscribed", serverId: requestInput.serverId, connectionId: connected.output.connectionId, providerMetadata: metadata(connected.output.profile, { eventKinds: requestInput.eventKinds ?? [] }) });
618
+ const resourceUri = subscriptionResourceUri(requestInput);
619
+ if ((requestInput.subjectType === undefined || requestInput.subjectType === "resource") && resourceUri !== undefined) {
620
+ const subscribed = await request(connected.output, "resources/subscribe", { uri: resourceUri });
621
+ if (!subscribed.ok)
622
+ return subscribed;
623
+ return success({
624
+ subscriptionId: `${requestInput.serverId}:subscription:resource:${resourceUri}`,
625
+ status: "subscribed",
626
+ serverId: requestInput.serverId,
627
+ connectionId: connected.output.connectionId,
628
+ uri: resourceUri,
629
+ providerMetadata: metadata(connected.output.profile, {
630
+ method: "resources/subscribe",
631
+ eventKinds: requestInput.eventKinds ?? [],
632
+ }),
633
+ raw: subscribed.output,
634
+ });
635
+ }
636
+ return success({
637
+ subscriptionId: `${requestInput.serverId}:subscription:${requestInput.subjectType}:${requestInput.subject}`,
638
+ status: "subscribed",
639
+ serverId: requestInput.serverId,
640
+ connectionId: connected.output.connectionId,
641
+ providerMetadata: metadata(connected.output.profile, {
642
+ eventKinds: requestInput.eventKinds ?? [],
643
+ hostSemantic: "subscribe",
644
+ localOnly: true,
645
+ }),
646
+ });
219
647
  },
220
648
  async unsubscribe(requestInput) {
221
- const profile = getProfile(requestInput.serverId);
222
- if (profile === undefined)
223
- return failure("MCP_SERVER_NOT_CONFIGURED", `MCP server '${requestInput.serverId}' is not configured.`);
224
- return success({ subscriptionId: requestInput.subscriptionId, status: "unsubscribed", serverId: requestInput.serverId, providerMetadata: metadata(profile) });
649
+ const connected = await getConnection(requestInput.serverId, requestInput.connectionId);
650
+ if (!connected.ok)
651
+ return connected;
652
+ const resourceUri = subscriptionResourceUri(requestInput);
653
+ if (resourceUri !== undefined) {
654
+ const unsubscribed = await request(connected.output, "resources/unsubscribe", { uri: resourceUri });
655
+ if (!unsubscribed.ok)
656
+ return unsubscribed;
657
+ return success({
658
+ subscriptionId: requestInput.subscriptionId ?? `${requestInput.serverId}:subscription:resource:${resourceUri}`,
659
+ status: "unsubscribed",
660
+ serverId: requestInput.serverId,
661
+ connectionId: connected.output.connectionId,
662
+ uri: resourceUri,
663
+ providerMetadata: metadata(connected.output.profile, { method: "resources/unsubscribe" }),
664
+ raw: unsubscribed.output,
665
+ });
666
+ }
667
+ return success({
668
+ subscriptionId: requestInput.subscriptionId,
669
+ status: "unsubscribed",
670
+ serverId: requestInput.serverId,
671
+ connectionId: connected.output.connectionId,
672
+ providerMetadata: metadata(connected.output.profile, { hostSemantic: "unsubscribe", localOnly: true }),
673
+ });
225
674
  },
226
675
  async callTool(requestInput) {
227
676
  const connected = await getConnection(requestInput.serverId);
@@ -242,10 +691,16 @@ export function createMcpRuntimeAdapter(options) {
242
691
  return success({ executionId: `${requestInput.serverId}:execution:${requestInput.name}`, streamId: `${requestInput.serverId}:stream:${requestInput.name}`, status: "completed", channel: requestInput.channel ?? "chunks", chunks: [called.output], providerMetadata: metadata(connected.output.profile, { method: "tools/call", toolName: requestInput.name }) });
243
692
  },
244
693
  async cancelExecution(requestInput) {
245
- const profile = getProfile(requestInput.serverId);
246
- if (profile === undefined)
247
- return failure("MCP_SERVER_NOT_CONFIGURED", `MCP server '${requestInput.serverId}' is not configured.`);
248
- return success({ executionId: requestInput.executionId, status: "cancelled", serverId: requestInput.serverId, providerMetadata: metadata(profile) });
694
+ const connected = await getConnection(requestInput.serverId);
695
+ if (!connected.ok)
696
+ return connected;
697
+ const cancelled = await notify(connected.output, "notifications/cancelled", {
698
+ requestId: requestInput.executionId,
699
+ reason: requestInput.reason,
700
+ });
701
+ if (!cancelled.ok)
702
+ return cancelled;
703
+ return success({ executionId: requestInput.executionId, status: "cancelled", serverId: requestInput.serverId, providerMetadata: metadata(connected.output.profile, { method: "notifications/cancelled" }) });
249
704
  },
250
705
  async nativeExecute(requestInput) {
251
706
  const connected = await getConnection(requestInput.serverId);
@@ -260,7 +715,7 @@ export function createMcpRuntimeAdapter(options) {
260
715
  const connected = await getConnection(requestInput.serverId);
261
716
  if (!connected.ok)
262
717
  return connected;
263
- const listed = await request(connected.output, "tools/list", {});
718
+ const listed = await request(connected.output, "tools/list", requestInput.cursor === undefined ? {} : { cursor: requestInput.cursor });
264
719
  if (!listed.ok)
265
720
  return listed;
266
721
  const raw = resultObject(listed.output);
@@ -272,7 +727,12 @@ export function createMcpRuntimeAdapter(options) {
272
727
  namespace: requestInput.namespace,
273
728
  raw: tool,
274
729
  })).filter((tool) => tool.name.length > 0) : [];
275
- return success({ tools, providerMetadata: metadata(connected.output.profile, { method: "tools/list" }), raw: listed.output });
730
+ return success({
731
+ tools,
732
+ nextCursor: typeof raw.nextCursor === "string" ? raw.nextCursor : undefined,
733
+ providerMetadata: metadata(connected.output.profile, { method: "tools/list" }),
734
+ raw: listed.output,
735
+ });
276
736
  },
277
737
  async registerTool(requestInput) {
278
738
  const profile = getProfile(requestInput.serverId);
@@ -296,7 +756,7 @@ export function createMcpRuntimeAdapter(options) {
296
756
  const connected = await getConnection(requestInput.serverId);
297
757
  if (!connected.ok)
298
758
  return connected;
299
- const listed = await request(connected.output, "resources/list", {});
759
+ const listed = await request(connected.output, "resources/list", requestInput.cursor === undefined ? {} : { cursor: requestInput.cursor });
300
760
  if (!listed.ok)
301
761
  return listed;
302
762
  const raw = resultObject(listed.output);
@@ -306,13 +766,47 @@ export function createMcpRuntimeAdapter(options) {
306
766
  mimeType: typeof resource.mimeType === "string" ? resource.mimeType : typeof resource.mime_type === "string" ? resource.mime_type : undefined,
307
767
  raw: resource,
308
768
  })).filter((resource) => resource.uri.length > 0 && (requestInput.uriPrefix === undefined || resource.uri.startsWith(requestInput.uriPrefix))) : [];
309
- return success({ resources, exhausted: true, providerMetadata: metadata(connected.output.profile, { method: "resources/list" }), raw: listed.output });
769
+ const nextCursor = typeof raw.nextCursor === "string" ? raw.nextCursor : undefined;
770
+ return success({
771
+ resources,
772
+ nextCursor,
773
+ exhausted: nextCursor === undefined,
774
+ providerMetadata: metadata(connected.output.profile, { method: "resources/list" }),
775
+ raw: listed.output,
776
+ });
777
+ },
778
+ async listResourceTemplates(requestInput) {
779
+ const connected = await getConnection(requestInput.serverId);
780
+ if (!connected.ok)
781
+ return connected;
782
+ const listed = await request(connected.output, "resources/templates/list", requestInput.cursor === undefined ? {} : { cursor: requestInput.cursor });
783
+ if (!listed.ok)
784
+ return listed;
785
+ const raw = resultObject(listed.output);
786
+ const resourceTemplates = Array.isArray(raw.resourceTemplates) ? raw.resourceTemplates.filter(isObject).map((template) => ({
787
+ uriTemplate: String(template.uriTemplate ?? template.uri_template ?? ""),
788
+ name: typeof template.name === "string" ? template.name : undefined,
789
+ title: typeof template.title === "string" ? template.title : undefined,
790
+ description: typeof template.description === "string" ? template.description : undefined,
791
+ mimeType: typeof template.mimeType === "string" ? template.mimeType : typeof template.mime_type === "string" ? template.mime_type : undefined,
792
+ raw: template,
793
+ })).filter((template) => template.uriTemplate.length > 0) : [];
794
+ const nextCursor = typeof raw.nextCursor === "string" ? raw.nextCursor : undefined;
795
+ return success({
796
+ resourceTemplates,
797
+ templates: resourceTemplates,
798
+ nextCursor,
799
+ exhausted: nextCursor === undefined,
800
+ providerMetadata: metadata(connected.output.profile, { method: "resources/templates/list" }),
801
+ raw: listed.output,
802
+ });
310
803
  },
311
804
  async readResource(requestInput) {
312
805
  const connected = await getConnection(requestInput.serverId);
313
806
  if (!connected.ok)
314
807
  return connected;
315
- const read = await request(connected.output, "resources/read", { uri: requestInput.resourceUri });
808
+ const uri = typeof requestInput.uri === "string" ? requestInput.uri : requestInput.resourceUri;
809
+ const read = await request(connected.output, "resources/read", { uri });
316
810
  if (!read.ok)
317
811
  return read;
318
812
  const raw = resultObject(read.output);
@@ -322,7 +816,7 @@ export function createMcpRuntimeAdapter(options) {
322
816
  bytesBase64: typeof content.blob === "string" ? content.blob : undefined,
323
817
  raw: content,
324
818
  })) : [];
325
- return success({ uri: requestInput.resourceUri, contents, truncated: false, providerMetadata: metadata(connected.output.profile, { method: "resources/read" }), raw: read.output });
819
+ return success({ uri, contents, truncated: false, providerMetadata: metadata(connected.output.profile, { method: "resources/read" }), raw: read.output });
326
820
  },
327
821
  async listPrompts(requestInput) {
328
822
  const connected = await getConnection(requestInput.serverId);
@@ -350,11 +844,45 @@ export function createMcpRuntimeAdapter(options) {
350
844
  return read;
351
845
  return success(read.output, metadata(connected.output.profile, { method: "prompts/get", promptName: requestInput.name }));
352
846
  },
847
+ async complete(requestInput) {
848
+ const connected = await getConnection(requestInput.serverId);
849
+ if (!connected.ok)
850
+ return connected;
851
+ const params = {
852
+ ref: isObject(requestInput.ref) ? requestInput.ref : {},
853
+ argument: isObject(requestInput.argument) ? requestInput.argument : {},
854
+ ...(isObject(requestInput.context) ? { context: requestInput.context } : {}),
855
+ };
856
+ const completed = await request(connected.output, "completion/complete", params);
857
+ if (!completed.ok)
858
+ return completed;
859
+ const raw = resultObject(completed.output);
860
+ const completion = isObject(raw.completion) ? raw.completion : {};
861
+ return success({
862
+ completion,
863
+ values: Array.isArray(completion.values) ? completion.values.filter((value) => typeof value === "string") : [],
864
+ total: typeof completion.total === "number" ? completion.total : undefined,
865
+ hasMore: typeof completion.hasMore === "boolean" ? completion.hasMore : undefined,
866
+ providerMetadata: metadata(connected.output.profile, { method: "completion/complete" }),
867
+ raw: completed.output,
868
+ });
869
+ },
353
870
  async setRoots(requestInput) {
354
871
  const profile = getProfile(requestInput.serverId);
355
872
  if (profile === undefined)
356
873
  return failure("MCP_SERVER_NOT_CONFIGURED", `MCP server '${requestInput.serverId}' is not configured.`);
357
- return success({ serverId: requestInput.serverId, roots: requestInput.roots ?? [], status: "registered", providerMetadata: metadata(profile, { hostSemantic: "roots" }) });
874
+ const roots = (Array.isArray(requestInput.roots) ? requestInput.roots.filter(isObject).map((root) => ({
875
+ uri: String(root.uri ?? ""),
876
+ name: typeof root.name === "string" ? root.name : undefined,
877
+ _meta: isObject(root._meta) ? root._meta : undefined,
878
+ })).filter((root) => root.uri.length > 0) : []);
879
+ rootsByServerId.set(requestInput.serverId, roots);
880
+ for (const [, connection] of connections) {
881
+ if (connection.profile.serverId === requestInput.serverId) {
882
+ await notify(connection, "notifications/roots/list_changed", {});
883
+ }
884
+ }
885
+ return success({ serverId: requestInput.serverId, roots, status: "registered", providerMetadata: metadata(profile, { hostSemantic: "roots" }) });
358
886
  },
359
887
  async reportProgress(requestInput) {
360
888
  const profile = getProfile(requestInput.serverId);
@@ -375,10 +903,14 @@ export function createMcpRuntimeAdapter(options) {
375
903
  return success({ serverId: requestInput.serverId, status: "pending", request: requestInput, providerMetadata: metadata(profile, { hostSemantic: "elicitation" }) });
376
904
  },
377
905
  async setLoggingLevel(requestInput) {
378
- const profile = getProfile(requestInput.serverId);
379
- if (profile === undefined)
380
- return failure("MCP_SERVER_NOT_CONFIGURED", `MCP server '${requestInput.serverId}' is not configured.`);
381
- return success({ serverId: requestInput.serverId, level: requestInput.level ?? "info", status: "configured", providerMetadata: metadata(profile, { hostSemantic: "logging" }) });
906
+ const connected = await getConnection(requestInput.serverId);
907
+ if (!connected.ok)
908
+ return connected;
909
+ const level = requestInput.level ?? "info";
910
+ const configured = await request(connected.output, "logging/setLevel", { level });
911
+ if (!configured.ok)
912
+ return configured;
913
+ return success({ serverId: requestInput.serverId, level, status: "configured", providerMetadata: metadata(connected.output.profile, { method: "logging/setLevel" }), raw: configured.output });
382
914
  },
383
915
  async createResource(requestInput) {
384
916
  const profile = getProfile(requestInput.serverId);
@@ -427,8 +959,7 @@ async function requestStdio(connection, method, params) {
427
959
  }, timeoutMs);
428
960
  connection.pending.set(id, { resolve, reject, timeout });
429
961
  });
430
- const framing = connection.profile.transport === "stdio" ? connection.profile.framing : undefined;
431
- connection.child.stdin.write(framing === "line-json" ? `${JSON.stringify(payload)}\n` : contentLengthFrame(payload));
962
+ writeStdioPayload(connection, payload);
432
963
  try {
433
964
  return normalizeJsonRpcResponse(await response);
434
965
  }
@@ -436,22 +967,42 @@ async function requestStdio(connection, method, params) {
436
967
  return failure("MCP_STDIO_REQUEST_FAILED", textFromUnknown(error.message ?? error));
437
968
  }
438
969
  }
439
- async function requestHttp(profile, method, params) {
970
+ async function notifyStdio(connection, method, params) {
971
+ if (connection.child === undefined)
972
+ return failure("MCP_STDIO_NOT_CONNECTED", "MCP stdio child process is not connected.");
973
+ writeStdioPayload(connection, { jsonrpc: "2.0", method, params });
974
+ return success({ status: "notified" });
975
+ }
976
+ function httpHeaders(profile, sessionId) {
977
+ return {
978
+ "content-type": "application/json",
979
+ accept: "application/json, text/event-stream",
980
+ "mcp-protocol-version": MCP_PROTOCOL_VERSION,
981
+ ...(profile.headers ?? {}),
982
+ ...(sessionId === undefined ? {} : { [MCP_SESSION_ID_HEADER]: sessionId }),
983
+ };
984
+ }
985
+ function resolveLegacySseEndpointUrl(profile, endpoint) {
986
+ return new URL(endpoint, profile.sseUrl ?? profile.url).toString();
987
+ }
988
+ async function postJsonRpcPayload(profile, url, payload, sessionId) {
440
989
  const controller = new AbortController();
441
990
  const timeout = setTimeout(() => controller.abort(), profile.timeoutMs ?? 5_000);
442
991
  try {
443
- const response = await fetch(profile.url, {
992
+ const response = await fetch(url, {
444
993
  method: "POST",
445
- headers: { "content-type": "application/json", ...(profile.headers ?? {}) },
446
- body: JSON.stringify({ jsonrpc: "2.0", id: `${Date.now()}:${Math.random()}`, method, params }),
994
+ headers: httpHeaders(profile, sessionId),
995
+ body: JSON.stringify(payload),
447
996
  signal: controller.signal,
448
997
  });
449
998
  if (!response.ok)
450
999
  return failure("MCP_HTTP_REQUEST_FAILED", `MCP HTTP endpoint returned HTTP ${response.status}.`);
451
- const json = await response.json();
452
- if (!isObject(json))
453
- return failure("MCP_HTTP_RESPONSE_INVALID", "MCP HTTP response was not a JSON object.");
454
- return normalizeJsonRpcResponse(json);
1000
+ const body = await response.text();
1001
+ const messages = body.trim().length === 0 ? [] : parseHttpJsonRpcMessages(response.headers.get("content-type"), body);
1002
+ if (messages === undefined)
1003
+ return failure("MCP_HTTP_RESPONSE_INVALID", "MCP HTTP response was not valid JSON or SSE JSON-RPC.");
1004
+ const responseSessionId = response.headers.get(MCP_SESSION_ID_HEADER) ?? undefined;
1005
+ return success({ messages }, responseSessionId === undefined ? undefined : { mcpSessionId: responseSessionId });
455
1006
  }
456
1007
  catch (error) {
457
1008
  return failure("MCP_HTTP_REQUEST_FAILED", textFromUnknown(error.message ?? error));
@@ -460,6 +1011,47 @@ async function requestHttp(profile, method, params) {
460
1011
  clearTimeout(timeout);
461
1012
  }
462
1013
  }
1014
+ async function requestHttp(profile, method, params, sessionId, onNotification) {
1015
+ const requestId = `${Date.now()}:${Math.random()}`;
1016
+ const posted = await postJsonRpcPayload(profile, profile.url, { jsonrpc: "2.0", id: requestId, method, params }, sessionId);
1017
+ if (!posted.ok)
1018
+ return posted;
1019
+ const messages = posted.output.messages;
1020
+ for (const message of messages) {
1021
+ if (typeof message.method === "string" && (message.id === undefined || message.id === null)) {
1022
+ onNotification?.(message);
1023
+ }
1024
+ }
1025
+ const responseMessage = messages.find((message) => message.id === requestId)
1026
+ ?? messages.find((message) => message.id !== undefined && message.id !== null && message.method === undefined);
1027
+ if (responseMessage === undefined)
1028
+ return failure("MCP_HTTP_RESPONSE_INVALID", "MCP HTTP response did not include a JSON-RPC response for the request.");
1029
+ const normalized = normalizeJsonRpcResponse(responseMessage);
1030
+ if (!normalized.ok || posted.metadata?.mcpSessionId === undefined)
1031
+ return normalized;
1032
+ return success(normalized.output, { ...(normalized.metadata ?? {}), mcpSessionId: posted.metadata.mcpSessionId });
1033
+ }
1034
+ async function notifyHttp(profile, method, params, sessionId) {
1035
+ const controller = new AbortController();
1036
+ const timeout = setTimeout(() => controller.abort(), profile.timeoutMs ?? 5_000);
1037
+ try {
1038
+ const response = await fetch(profile.url, {
1039
+ method: "POST",
1040
+ headers: httpHeaders(profile, sessionId),
1041
+ body: JSON.stringify({ jsonrpc: "2.0", method, params }),
1042
+ signal: controller.signal,
1043
+ });
1044
+ if (!response.ok)
1045
+ return failure("MCP_HTTP_NOTIFICATION_FAILED", `MCP HTTP endpoint returned HTTP ${response.status}.`);
1046
+ return success({ status: "notified" });
1047
+ }
1048
+ catch (error) {
1049
+ return failure("MCP_HTTP_NOTIFICATION_FAILED", textFromUnknown(error.message ?? error));
1050
+ }
1051
+ finally {
1052
+ clearTimeout(timeout);
1053
+ }
1054
+ }
463
1055
  function normalizeJsonRpcResponse(response) {
464
1056
  if (response.error !== undefined) {
465
1057
  return failure("MCP_JSONRPC_ERROR", response.error.message ?? `MCP JSON-RPC error ${String(response.error.code ?? "unknown")}.`);