@praxis-ai/praxis 0.1.4 → 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.
- package/dist/agentCore/index.d.ts +11 -3
- package/dist/applicationLayer/applicationRuntime.js +7 -1
- package/dist/basetool/authoring.d.ts +2 -0
- package/dist/basetool/authoring.js +2 -0
- package/dist/basetool/catalog.d.ts +1 -1
- package/dist/basetool/catalog.js +86 -4
- package/dist/basetool/core/index.d.ts +4 -2
- package/dist/basetool/core/index.js +8 -0
- package/dist/basetool/core/mcpCompletions.d.ts +2 -0
- package/dist/basetool/core/mcpCompletions.js +70 -0
- package/dist/basetool/core/mcpPrompts.d.ts +2 -0
- package/dist/basetool/core/mcpPrompts.js +48 -0
- package/dist/basetool/core/mcpResources.js +41 -5
- package/dist/basetool/profiles.js +15 -1
- package/dist/basetool/registry.d.ts +1 -1
- package/dist/basetool/supportCatalog.js +23 -6
- package/dist/runtimeImplementation/praxisRuntimeKernel.js +1696 -1499
- package/dist/runtimeImplementation/runtime.execEngine/baseToolApprovalScope.js +11 -0
- package/dist/runtimeImplementation/runtime.execEngine/baseToolExecutorPortFactory.js +13 -1
- package/dist/runtimeImplementation/runtime.execEngine/baseToolPolicyAdjudicator.js +14 -0
- package/dist/runtimeImplementation/runtime.execEngine/mcpRuntimeAdapter.d.ts +27 -0
- package/dist/runtimeImplementation/runtime.execEngine/mcpRuntimeAdapter.js +648 -56
- package/dist/runtimeImplementation/runtime.execEngine/promptContextAssembly.d.ts +1 -0
- package/dist/runtimeImplementation/runtime.execEngine/promptContextAssembly.js +18 -0
- package/dist/runtimeImplementation/runtime.mcpPlane/index.d.ts +20 -7
- package/dist/runtimeImplementation/runtime.mcpPlane/index.js +105 -89
- package/dist/toolBase/catalog.d.ts +24 -0
- package/dist/toolBase/catalog.js +41 -3
- package/dist/toolBase/profiles.d.ts +3 -3
- package/dist/toolBase/profiles.js +2 -0
- package/dist/toolBase/types.d.ts +1 -1
- package/examples/raxode-mcp-plus-ten-server.config.json +229 -0
- package/examples/scripts/README.md +8 -2
- package/examples/scripts/mcp-plus-native-smoke.ts +1296 -0
- package/package.json +3 -1
- package/raxode-tui/dist/raxode-cli/backend/agents/codingAgent/prompts/tool-use.md +1 -1
- package/raxode-tui/dist/raxode-cli/backend/application/mcpConfig.d.ts +9 -0
- package/raxode-tui/dist/raxode-cli/backend/application/mcpConfig.js +65 -0
- package/raxode-tui/dist/raxode-cli/backend/application/mcpReadinessSummary.d.ts +28 -0
- package/raxode-tui/dist/raxode-cli/backend/application/mcpReadinessSummary.js +57 -0
- package/raxode-tui/dist/raxode-cli/backend/application/runtimeReadiness.d.ts +5 -1
- package/raxode-tui/dist/raxode-cli/backend/application/runtimeReadiness.js +40 -0
- package/raxode-tui/dist/raxode-cli/backend/application/stdioApplicationServer.js +6 -0
- package/raxode-tui/dist/raxode-cli/backend/directApplicationBackend.d.ts +4 -0
- package/raxode-tui/dist/raxode-cli/backend/directApplicationBackend.js +14 -0
- package/raxode-tui/dist/raxode-cli/backend/raxodeBackend.d.ts +1 -1
- package/raxode-tui/dist/raxode-cli/backend/raxodeBackend.js +16 -1
- package/raxode-tui/dist/raxode-cli/contracts.d.ts +1 -0
- package/raxode-tui/dist/raxode-cli/frontend/bridge/readiness.js +24 -0
- package/raxode-tui/dist/raxode-cli/frontend/tui/app/direct-tui.js +35 -0
- package/raxode-tui/dist/raxode-cli/frontend/tui/cli/raxode-cli.d.ts +2 -0
- package/raxode-tui/dist/raxode-cli/frontend/tui/cli/raxode-cli.js +8 -0
- package/raxode-tui/dist/raxode-cli/frontend/tui/config/raxode-config.d.ts +31 -0
- package/raxode-tui/dist/raxode-cli/frontend/tui/config/raxode-config.js +129 -0
- package/raxode-tui/dist/raxode-cli/index.d.ts +1 -0
|
@@ -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
|
-
|
|
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
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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
|
-
|
|
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
|
-
|
|
147
|
-
|
|
148
|
-
const
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
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
|
-
|
|
158
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
222
|
-
if (
|
|
223
|
-
return
|
|
224
|
-
|
|
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
|
|
246
|
-
if (
|
|
247
|
-
return
|
|
248
|
-
|
|
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({
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
379
|
-
if (
|
|
380
|
-
return
|
|
381
|
-
|
|
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
|
-
|
|
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
|
|
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(
|
|
992
|
+
const response = await fetch(url, {
|
|
444
993
|
method: "POST",
|
|
445
|
-
headers:
|
|
446
|
-
body: JSON.stringify(
|
|
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
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
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")}.`);
|