@once-agent/sdk 0.1.11 → 0.1.12

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 (160) hide show
  1. package/README.md +76 -1
  2. package/dist/cli.js +50 -1
  3. package/dist/cli.js.map +1 -1
  4. package/dist/connect/identity.d.ts +22 -0
  5. package/dist/connect/identity.d.ts.map +1 -0
  6. package/dist/connect/identity.js +159 -0
  7. package/dist/connect/identity.js.map +1 -0
  8. package/dist/connect/index.d.ts +17 -0
  9. package/dist/connect/index.d.ts.map +1 -0
  10. package/dist/connect/index.js +9 -0
  11. package/dist/connect/index.js.map +1 -0
  12. package/dist/connect/local-agent-tool.d.ts +30 -7
  13. package/dist/connect/local-agent-tool.d.ts.map +1 -1
  14. package/dist/connect/local-agent-tool.js +81 -9
  15. package/dist/connect/local-agent-tool.js.map +1 -1
  16. package/dist/connect/manifest.d.ts +55 -0
  17. package/dist/connect/manifest.d.ts.map +1 -0
  18. package/dist/connect/manifest.js +166 -0
  19. package/dist/connect/manifest.js.map +1 -0
  20. package/dist/connect/openai-function-tools.d.ts +66 -0
  21. package/dist/connect/openai-function-tools.d.ts.map +1 -0
  22. package/dist/connect/openai-function-tools.js +182 -0
  23. package/dist/connect/openai-function-tools.js.map +1 -0
  24. package/dist/connect/payload.d.ts +30 -0
  25. package/dist/connect/payload.d.ts.map +1 -0
  26. package/dist/connect/payload.js +122 -0
  27. package/dist/connect/payload.js.map +1 -0
  28. package/dist/connect/tool-classifier.d.ts +40 -0
  29. package/dist/connect/tool-classifier.d.ts.map +1 -0
  30. package/dist/connect/tool-classifier.js +271 -0
  31. package/dist/connect/tool-classifier.js.map +1 -0
  32. package/dist/connect/toolset.d.ts +42 -0
  33. package/dist/connect/toolset.d.ts.map +1 -0
  34. package/dist/connect/toolset.js +123 -0
  35. package/dist/connect/toolset.js.map +1 -0
  36. package/dist/discovery/index.d.ts +13 -0
  37. package/dist/discovery/index.d.ts.map +1 -0
  38. package/dist/discovery/index.js +8 -0
  39. package/dist/discovery/index.js.map +1 -0
  40. package/dist/execution-observation.d.ts +122 -0
  41. package/dist/execution-observation.d.ts.map +1 -0
  42. package/dist/execution-observation.js +324 -0
  43. package/dist/execution-observation.js.map +1 -0
  44. package/dist/gateway/index.d.ts +9 -0
  45. package/dist/gateway/index.d.ts.map +1 -0
  46. package/dist/gateway/index.js +5 -0
  47. package/dist/gateway/index.js.map +1 -0
  48. package/dist/gateway/local-toolset.d.ts +32 -0
  49. package/dist/gateway/local-toolset.d.ts.map +1 -0
  50. package/dist/gateway/local-toolset.js +79 -0
  51. package/dist/gateway/local-toolset.js.map +1 -0
  52. package/dist/gateway/openai-function-tools.d.ts +28 -0
  53. package/dist/gateway/openai-function-tools.d.ts.map +1 -0
  54. package/dist/gateway/openai-function-tools.js +117 -0
  55. package/dist/gateway/openai-function-tools.js.map +1 -0
  56. package/dist/gateway/planner.d.ts +53 -0
  57. package/dist/gateway/planner.d.ts.map +1 -0
  58. package/dist/gateway/planner.js +155 -0
  59. package/dist/gateway/planner.js.map +1 -0
  60. package/dist/gateway/tool-graph-binding.d.ts +51 -0
  61. package/dist/gateway/tool-graph-binding.d.ts.map +1 -0
  62. package/dist/gateway/tool-graph-binding.js +123 -0
  63. package/dist/gateway/tool-graph-binding.js.map +1 -0
  64. package/dist/langchain-execution-observer.d.ts +22 -0
  65. package/dist/langchain-execution-observer.d.ts.map +1 -0
  66. package/dist/langchain-execution-observer.js +128 -0
  67. package/dist/langchain-execution-observer.js.map +1 -0
  68. package/dist/langchain-runtime-discovery.d.ts +33 -0
  69. package/dist/langchain-runtime-discovery.d.ts.map +1 -0
  70. package/dist/langchain-runtime-discovery.js +214 -0
  71. package/dist/langchain-runtime-discovery.js.map +1 -0
  72. package/dist/mcp-live-discovery.d.ts +71 -0
  73. package/dist/mcp-live-discovery.d.ts.map +1 -0
  74. package/dist/mcp-live-discovery.js +710 -0
  75. package/dist/mcp-live-discovery.js.map +1 -0
  76. package/dist/mcp-live-report.d.ts +3 -0
  77. package/dist/mcp-live-report.d.ts.map +1 -0
  78. package/dist/mcp-live-report.js +50 -0
  79. package/dist/mcp-live-report.js.map +1 -0
  80. package/dist/mcp-tool-refresh-report.d.ts +3 -0
  81. package/dist/mcp-tool-refresh-report.d.ts.map +1 -0
  82. package/dist/mcp-tool-refresh-report.js +44 -0
  83. package/dist/mcp-tool-refresh-report.js.map +1 -0
  84. package/dist/mcp-tool-refresh.d.ts +33 -0
  85. package/dist/mcp-tool-refresh.d.ts.map +1 -0
  86. package/dist/mcp-tool-refresh.js +489 -0
  87. package/dist/mcp-tool-refresh.js.map +1 -0
  88. package/dist/openai-agents-execution-observer.d.ts +24 -0
  89. package/dist/openai-agents-execution-observer.d.ts.map +1 -0
  90. package/dist/openai-agents-execution-observer.js +163 -0
  91. package/dist/openai-agents-execution-observer.js.map +1 -0
  92. package/dist/otel-execution-observer.d.ts +16 -0
  93. package/dist/otel-execution-observer.d.ts.map +1 -0
  94. package/dist/otel-execution-observer.js +129 -0
  95. package/dist/otel-execution-observer.js.map +1 -0
  96. package/dist/runtime-tool-discovery.d.ts +86 -0
  97. package/dist/runtime-tool-discovery.d.ts.map +1 -0
  98. package/dist/runtime-tool-discovery.js +429 -0
  99. package/dist/runtime-tool-discovery.js.map +1 -0
  100. package/dist/tool-discovery.d.ts +51 -0
  101. package/dist/tool-discovery.d.ts.map +1 -0
  102. package/dist/tool-discovery.js +487 -0
  103. package/dist/tool-discovery.js.map +1 -0
  104. package/dist/tool-importance.d.ts +34 -0
  105. package/dist/tool-importance.d.ts.map +1 -0
  106. package/dist/tool-importance.js +269 -0
  107. package/dist/tool-importance.js.map +1 -0
  108. package/dist/tool-report.d.ts +3 -0
  109. package/dist/tool-report.d.ts.map +1 -0
  110. package/dist/tool-report.js +75 -0
  111. package/dist/tool-report.js.map +1 -0
  112. package/dist/vercel-execution-observer.d.ts +12 -0
  113. package/dist/vercel-execution-observer.d.ts.map +1 -0
  114. package/dist/vercel-execution-observer.js +97 -0
  115. package/dist/vercel-execution-observer.js.map +1 -0
  116. package/dist-cjs/connect/identity.js +162 -0
  117. package/dist-cjs/connect/identity.js.map +1 -0
  118. package/dist-cjs/connect/index.js +26 -0
  119. package/dist-cjs/connect/index.js.map +1 -0
  120. package/dist-cjs/connect/local-agent-tool.js +82 -9
  121. package/dist-cjs/connect/local-agent-tool.js.map +1 -1
  122. package/dist-cjs/connect/manifest.js +171 -0
  123. package/dist-cjs/connect/manifest.js.map +1 -0
  124. package/dist-cjs/connect/openai-function-tools.js +187 -0
  125. package/dist-cjs/connect/openai-function-tools.js.map +1 -0
  126. package/dist-cjs/connect/payload.js +125 -0
  127. package/dist-cjs/connect/payload.js.map +1 -0
  128. package/dist-cjs/connect/tool-classifier.js +276 -0
  129. package/dist-cjs/connect/tool-classifier.js.map +1 -0
  130. package/dist-cjs/connect/toolset.js +126 -0
  131. package/dist-cjs/connect/toolset.js.map +1 -0
  132. package/dist-cjs/discovery/index.js +26 -0
  133. package/dist-cjs/discovery/index.js.map +1 -0
  134. package/dist-cjs/execution-observation.js +330 -0
  135. package/dist-cjs/execution-observation.js.map +1 -0
  136. package/dist-cjs/gateway/index.js +16 -0
  137. package/dist-cjs/gateway/index.js.map +1 -0
  138. package/dist-cjs/gateway/local-toolset.js +84 -0
  139. package/dist-cjs/gateway/local-toolset.js.map +1 -0
  140. package/dist-cjs/gateway/openai-function-tools.js +120 -0
  141. package/dist-cjs/gateway/openai-function-tools.js.map +1 -0
  142. package/dist-cjs/gateway/planner.js +160 -0
  143. package/dist-cjs/gateway/planner.js.map +1 -0
  144. package/dist-cjs/gateway/tool-graph-binding.js +126 -0
  145. package/dist-cjs/gateway/tool-graph-binding.js.map +1 -0
  146. package/dist-cjs/langchain-execution-observer.js +131 -0
  147. package/dist-cjs/langchain-execution-observer.js.map +1 -0
  148. package/dist-cjs/langchain-runtime-discovery.js +218 -0
  149. package/dist-cjs/langchain-runtime-discovery.js.map +1 -0
  150. package/dist-cjs/openai-agents-execution-observer.js +166 -0
  151. package/dist-cjs/openai-agents-execution-observer.js.map +1 -0
  152. package/dist-cjs/otel-execution-observer.js +132 -0
  153. package/dist-cjs/otel-execution-observer.js.map +1 -0
  154. package/dist-cjs/runtime-tool-discovery.js +436 -0
  155. package/dist-cjs/runtime-tool-discovery.js.map +1 -0
  156. package/dist-cjs/tool-importance.js +272 -0
  157. package/dist-cjs/tool-importance.js.map +1 -0
  158. package/dist-cjs/vercel-execution-observer.js +100 -0
  159. package/dist-cjs/vercel-execution-observer.js.map +1 -0
  160. package/package.json +25 -3
@@ -0,0 +1,710 @@
1
+ import { createHash } from "node:crypto";
2
+ import { promises as fs } from "node:fs";
3
+ import os from "node:os";
4
+ import path from "node:path";
5
+ import { assessToolImportance, } from "./tool-importance.js";
6
+ const MODERN_PROTOCOL_VERSION = "2026-07-28";
7
+ const LEGACY_PROTOCOL_VERSION = "2025-11-25";
8
+ const DEFAULT_TIMEOUT_MS = 5_000;
9
+ const DEFAULT_MAX_PAGES = 20;
10
+ const DEFAULT_MAX_TOOLS = 1_000;
11
+ const DEFAULT_MAX_RESPONSE_BYTES = 2_000_000;
12
+ export class McpLiveDiscoveryError extends Error {
13
+ code;
14
+ constructor(code, message) {
15
+ super(message);
16
+ this.code = code;
17
+ this.name = "McpLiveDiscoveryError";
18
+ }
19
+ }
20
+ function asRecord(value) {
21
+ if (value &&
22
+ typeof value === "object" &&
23
+ !Array.isArray(value)) {
24
+ return value;
25
+ }
26
+ return undefined;
27
+ }
28
+ function stableId(prefix, value) {
29
+ const hash = createHash("sha256")
30
+ .update(JSON.stringify(value))
31
+ .digest("hex");
32
+ return `${prefix}:${hash}`;
33
+ }
34
+ function stripJsonComments(input) {
35
+ let output = "";
36
+ let inString = false;
37
+ let quote = "";
38
+ let escaped = false;
39
+ let lineComment = false;
40
+ let blockComment = false;
41
+ for (let index = 0; index < input.length; index++) {
42
+ const char = input[index] ?? "";
43
+ const next = input[index + 1] ?? "";
44
+ if (lineComment) {
45
+ if (char === "\n") {
46
+ lineComment = false;
47
+ output += char;
48
+ }
49
+ continue;
50
+ }
51
+ if (blockComment) {
52
+ if (char === "*" && next === "/") {
53
+ blockComment = false;
54
+ index++;
55
+ }
56
+ else if (char === "\n") {
57
+ output += char;
58
+ }
59
+ continue;
60
+ }
61
+ if (inString) {
62
+ output += char;
63
+ if (escaped) {
64
+ escaped = false;
65
+ continue;
66
+ }
67
+ if (char === "\\") {
68
+ escaped = true;
69
+ continue;
70
+ }
71
+ if (char === quote) {
72
+ inString = false;
73
+ quote = "";
74
+ }
75
+ continue;
76
+ }
77
+ if (char === '"' || char === "'") {
78
+ inString = true;
79
+ quote = char;
80
+ output += char;
81
+ continue;
82
+ }
83
+ if (char === "/" && next === "/") {
84
+ lineComment = true;
85
+ index++;
86
+ continue;
87
+ }
88
+ if (char === "/" && next === "*") {
89
+ blockComment = true;
90
+ index++;
91
+ continue;
92
+ }
93
+ output += char;
94
+ }
95
+ return output;
96
+ }
97
+ function serverMapsFromJson(parsed) {
98
+ const root = asRecord(parsed);
99
+ if (!root)
100
+ return [];
101
+ const maps = [];
102
+ for (const key of ["mcpServers", "servers"]) {
103
+ const value = asRecord(root[key]);
104
+ if (value)
105
+ maps.push(value);
106
+ }
107
+ const customizations = asRecord(root.customizations);
108
+ const vscode = asRecord(customizations?.vscode);
109
+ const mcp = asRecord(vscode?.mcp);
110
+ for (const key of ["mcpServers", "servers"]) {
111
+ const value = asRecord(mcp?.[key]);
112
+ if (value)
113
+ maps.push(value);
114
+ }
115
+ return maps;
116
+ }
117
+ function configPathToAbsolute(root, configPath) {
118
+ if (configPath === "~") {
119
+ return os.homedir();
120
+ }
121
+ if (configPath.startsWith("~/")) {
122
+ return path.join(os.homedir(), ...configPath.substring(2).split("/"));
123
+ }
124
+ if (path.isAbsolute(configPath)) {
125
+ return configPath;
126
+ }
127
+ return path.resolve(root, configPath);
128
+ }
129
+ function stringHeaders(value) {
130
+ const record = asRecord(value);
131
+ if (!record)
132
+ return {};
133
+ const headers = {};
134
+ for (const [key, item] of Object.entries(record)) {
135
+ if (typeof item === "string") {
136
+ headers[key] = item;
137
+ }
138
+ }
139
+ return headers;
140
+ }
141
+ function parseTomlString(raw) {
142
+ const value = raw.trim();
143
+ const match = value.match(/^(?:"((?:\\.|[^"])*)"|'([^']*)')$/);
144
+ if (!match)
145
+ return undefined;
146
+ const body = match[1] ?? match[2] ?? "";
147
+ return body.replace(/\\"/g, '"').replace(/\\\\/g, "\\");
148
+ }
149
+ async function resolveJsonTarget(root, source) {
150
+ const filePath = configPathToAbsolute(root, source.configPath);
151
+ let raw;
152
+ try {
153
+ raw = await fs.readFile(filePath, "utf8");
154
+ }
155
+ catch {
156
+ return undefined;
157
+ }
158
+ let parsed;
159
+ try {
160
+ parsed = JSON.parse(stripJsonComments(raw.replace(/^\uFEFF/, "")));
161
+ }
162
+ catch {
163
+ return undefined;
164
+ }
165
+ for (const serverMap of serverMapsFromJson(parsed)) {
166
+ const definition = asRecord(serverMap[source.name]);
167
+ if (!definition || typeof definition.url !== "string") {
168
+ continue;
169
+ }
170
+ return {
171
+ source,
172
+ url: definition.url,
173
+ headers: stringHeaders(definition.headers),
174
+ };
175
+ }
176
+ return undefined;
177
+ }
178
+ async function resolveCodexTarget(root, source) {
179
+ const filePath = configPathToAbsolute(root, source.configPath);
180
+ let raw;
181
+ try {
182
+ raw = await fs.readFile(filePath, "utf8");
183
+ }
184
+ catch {
185
+ return undefined;
186
+ }
187
+ const escapedName = source.name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
188
+ const sectionPattern = new RegExp(`^\\s*\\[mcp_servers\\.(?:${escapedName}|"${escapedName}"|'${escapedName}')\\]\\s*$`, "m");
189
+ const match = sectionPattern.exec(raw);
190
+ if (!match)
191
+ return undefined;
192
+ const sectionStart = (match.index ?? 0) + match[0].length;
193
+ const rest = raw.substring(sectionStart);
194
+ const nextSection = rest.search(/^\s*\[/m);
195
+ const section = nextSection >= 0 ? rest.substring(0, nextSection) : rest;
196
+ const urlMatch = section.match(/^\s*url\s*=\s*(.+?)\s*$/m);
197
+ if (!urlMatch)
198
+ return undefined;
199
+ const url = parseTomlString(urlMatch[1] ?? "");
200
+ if (!url)
201
+ return undefined;
202
+ return {
203
+ source,
204
+ url,
205
+ headers: {},
206
+ };
207
+ }
208
+ async function resolveRemoteTarget(root, source) {
209
+ if (source.host === "codex" || source.configPath.endsWith(".toml")) {
210
+ return resolveCodexTarget(root, source);
211
+ }
212
+ return resolveJsonTarget(root, source);
213
+ }
214
+ function safeEndpointLabel(rawUrl) {
215
+ const url = new URL(rawUrl);
216
+ return `${url.origin}${url.pathname}`;
217
+ }
218
+ function validateRemoteUrl(rawUrl) {
219
+ let url;
220
+ try {
221
+ url = new URL(rawUrl);
222
+ }
223
+ catch {
224
+ throw new McpLiveDiscoveryError("INVALID_ENDPOINT", "Configured MCP endpoint is not a valid URL.");
225
+ }
226
+ if (url.protocol !== "http:" && url.protocol !== "https:") {
227
+ throw new McpLiveDiscoveryError("UNSUPPORTED_ENDPOINT_SCHEME", "Phase 11B live enumeration supports HTTP(S) endpoints only.");
228
+ }
229
+ return url;
230
+ }
231
+ async function readResponseBodyLimited(response, maxBytes) {
232
+ const declaredLength = Number(response.headers.get("content-length"));
233
+ if (Number.isFinite(declaredLength) && declaredLength > maxBytes) {
234
+ throw new McpLiveDiscoveryError("RESPONSE_TOO_LARGE", "MCP response exceeded the configured response-size limit.");
235
+ }
236
+ if (!response.body)
237
+ return "";
238
+ const reader = response.body.getReader();
239
+ const decoder = new TextDecoder();
240
+ let total = 0;
241
+ let text = "";
242
+ while (true) {
243
+ const chunk = await reader.read();
244
+ if (chunk.done)
245
+ break;
246
+ total += chunk.value.byteLength;
247
+ if (total > maxBytes) {
248
+ await reader.cancel();
249
+ throw new McpLiveDiscoveryError("RESPONSE_TOO_LARGE", "MCP response exceeded the configured response-size limit.");
250
+ }
251
+ text += decoder.decode(chunk.value, { stream: true });
252
+ }
253
+ text += decoder.decode();
254
+ return text;
255
+ }
256
+ function parseRpcPayload(text, contentType) {
257
+ const trimmed = text.trim();
258
+ if (!trimmed) {
259
+ throw new McpLiveDiscoveryError("EMPTY_RESPONSE", "MCP endpoint returned an empty RPC response.");
260
+ }
261
+ if (contentType.toLowerCase().includes("text/event-stream")) {
262
+ const candidates = trimmed
263
+ .split(/\r?\n/)
264
+ .filter(line => line.startsWith("data:"))
265
+ .map(line => line.substring(5).trim())
266
+ .filter(Boolean);
267
+ for (const candidate of candidates) {
268
+ try {
269
+ const parsed = JSON.parse(candidate);
270
+ const record = asRecord(parsed);
271
+ if (record)
272
+ return record;
273
+ }
274
+ catch {
275
+ // Ignore non-JSON SSE data frames.
276
+ }
277
+ }
278
+ throw new McpLiveDiscoveryError("INVALID_SSE_RESPONSE", "MCP SSE response did not contain a JSON-RPC data frame.");
279
+ }
280
+ let parsed;
281
+ try {
282
+ parsed = JSON.parse(trimmed);
283
+ }
284
+ catch {
285
+ throw new McpLiveDiscoveryError("INVALID_JSON_RESPONSE", "MCP endpoint returned invalid JSON.");
286
+ }
287
+ const record = asRecord(parsed);
288
+ if (!record) {
289
+ throw new McpLiveDiscoveryError("INVALID_RPC_RESPONSE", "MCP endpoint returned a non-object RPC response.");
290
+ }
291
+ return record;
292
+ }
293
+ async function postRpc(target, payload, method, timeoutMs, maxResponseBytes, extraHeaders = {}) {
294
+ const url = validateRemoteUrl(target.url);
295
+ const controller = new AbortController();
296
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
297
+ let response;
298
+ try {
299
+ response = await fetch(url, {
300
+ method: "POST",
301
+ redirect: "manual",
302
+ signal: controller.signal,
303
+ headers: {
304
+ ...target.headers,
305
+ ...extraHeaders,
306
+ "Content-Type": "application/json",
307
+ "Accept": "application/json, text/event-stream",
308
+ "Mcp-Method": method,
309
+ },
310
+ body: JSON.stringify(payload),
311
+ });
312
+ }
313
+ catch (error) {
314
+ if (error instanceof Error && error.name === "AbortError") {
315
+ throw new McpLiveDiscoveryError("TIMEOUT", "MCP request exceeded the configured timeout.");
316
+ }
317
+ throw new McpLiveDiscoveryError("NETWORK_ERROR", "MCP endpoint could not be reached.");
318
+ }
319
+ finally {
320
+ clearTimeout(timer);
321
+ }
322
+ if (response.status >= 300 && response.status < 400) {
323
+ throw new McpLiveDiscoveryError("REDIRECT_BLOCKED", "MCP endpoint returned a redirect; Phase 11B does not follow redirects.");
324
+ }
325
+ if (!response.ok) {
326
+ throw new McpLiveDiscoveryError(`HTTP_${response.status}`, `MCP endpoint returned HTTP ${response.status}.`);
327
+ }
328
+ const text = await readResponseBodyLimited(response, maxResponseBytes);
329
+ const rpc = parseRpcPayload(text, response.headers.get("content-type") ?? "");
330
+ return {
331
+ payload: rpc,
332
+ sessionId: response.headers.get("mcp-session-id") ?? undefined,
333
+ };
334
+ }
335
+ async function postNotification(target, payload, method, timeoutMs, extraHeaders = {}) {
336
+ const url = validateRemoteUrl(target.url);
337
+ const controller = new AbortController();
338
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
339
+ let response;
340
+ try {
341
+ response = await fetch(url, {
342
+ method: "POST",
343
+ redirect: "manual",
344
+ signal: controller.signal,
345
+ headers: {
346
+ ...target.headers,
347
+ ...extraHeaders,
348
+ "Content-Type": "application/json",
349
+ "Accept": "application/json, text/event-stream",
350
+ "Mcp-Method": method,
351
+ },
352
+ body: JSON.stringify(payload),
353
+ });
354
+ }
355
+ catch (error) {
356
+ if (error instanceof Error && error.name === "AbortError") {
357
+ throw new McpLiveDiscoveryError("TIMEOUT", "MCP notification exceeded the configured timeout.");
358
+ }
359
+ throw new McpLiveDiscoveryError("NETWORK_ERROR", "MCP endpoint could not be reached.");
360
+ }
361
+ finally {
362
+ clearTimeout(timer);
363
+ }
364
+ if (response.status >= 300 && response.status < 400) {
365
+ throw new McpLiveDiscoveryError("REDIRECT_BLOCKED", "MCP endpoint returned a redirect; Phase 11B does not follow redirects.");
366
+ }
367
+ if (!response.ok) {
368
+ throw new McpLiveDiscoveryError(`HTTP_${response.status}`, `MCP endpoint returned HTTP ${response.status}.`);
369
+ }
370
+ await response.body?.cancel();
371
+ }
372
+ function rpcResult(payload) {
373
+ const error = asRecord(payload.error);
374
+ if (error) {
375
+ const code = typeof error.code === "number" ? error.code : undefined;
376
+ throw new McpLiveDiscoveryError(code === -32601 ? "RPC_METHOD_NOT_FOUND" : "RPC_ERROR", "MCP endpoint returned a JSON-RPC error.");
377
+ }
378
+ const result = asRecord(payload.result);
379
+ if (!result) {
380
+ throw new McpLiveDiscoveryError("INVALID_RPC_RESULT", "MCP endpoint returned no object result.");
381
+ }
382
+ return result;
383
+ }
384
+ function modernMeta() {
385
+ return {
386
+ "io.modelcontextprotocol/protocolVersion": MODERN_PROTOCOL_VERSION,
387
+ "io.modelcontextprotocol/clientCapabilities": {},
388
+ "io.modelcontextprotocol/clientInfo": {
389
+ name: "once-tool-discovery",
390
+ version: "phase11b-v1",
391
+ },
392
+ };
393
+ }
394
+ async function discoverModern(target, timeoutMs, maxResponseBytes) {
395
+ const response = await postRpc(target, {
396
+ jsonrpc: "2.0",
397
+ id: "once-discover-1",
398
+ method: "server/discover",
399
+ params: {
400
+ _meta: modernMeta(),
401
+ },
402
+ }, "server/discover", timeoutMs, maxResponseBytes, {
403
+ "MCP-Protocol-Version": MODERN_PROTOCOL_VERSION,
404
+ });
405
+ const result = rpcResult(response.payload);
406
+ const supported = Array.isArray(result.supportedVersions)
407
+ ? result.supportedVersions.filter(item => typeof item === "string")
408
+ : [];
409
+ if (!supported.includes(MODERN_PROTOCOL_VERSION)) {
410
+ throw new McpLiveDiscoveryError("MODERN_VERSION_UNSUPPORTED", "MCP server discovery did not advertise the modern protocol version.");
411
+ }
412
+ return MODERN_PROTOCOL_VERSION;
413
+ }
414
+ async function initializeLegacy(target, timeoutMs, maxResponseBytes) {
415
+ const response = await postRpc(target, {
416
+ jsonrpc: "2.0",
417
+ id: "once-initialize-1",
418
+ method: "initialize",
419
+ params: {
420
+ protocolVersion: LEGACY_PROTOCOL_VERSION,
421
+ capabilities: {},
422
+ clientInfo: {
423
+ name: "once-tool-discovery",
424
+ version: "phase11b-v1",
425
+ },
426
+ },
427
+ }, "initialize", timeoutMs, maxResponseBytes, {
428
+ "MCP-Protocol-Version": LEGACY_PROTOCOL_VERSION,
429
+ });
430
+ const result = rpcResult(response.payload);
431
+ const protocolVersion = typeof result.protocolVersion === "string"
432
+ ? result.protocolVersion
433
+ : LEGACY_PROTOCOL_VERSION;
434
+ const sessionHeaders = {
435
+ "MCP-Protocol-Version": protocolVersion,
436
+ };
437
+ if (response.sessionId) {
438
+ sessionHeaders["Mcp-Session-Id"] = response.sessionId;
439
+ }
440
+ await postNotification(target, {
441
+ jsonrpc: "2.0",
442
+ method: "notifications/initialized",
443
+ params: {},
444
+ }, "notifications/initialized", timeoutMs, sessionHeaders);
445
+ return {
446
+ protocolVersion,
447
+ sessionId: response.sessionId,
448
+ };
449
+ }
450
+ function sanitizeAnnotations(value) {
451
+ const source = asRecord(value);
452
+ if (!source)
453
+ return undefined;
454
+ const annotations = {};
455
+ if (typeof source.title === "string") {
456
+ annotations.title = source.title;
457
+ }
458
+ for (const key of [
459
+ "readOnlyHint",
460
+ "destructiveHint",
461
+ "idempotentHint",
462
+ "openWorldHint",
463
+ ]) {
464
+ if (typeof source[key] === "boolean") {
465
+ annotations[key] = source[key];
466
+ }
467
+ }
468
+ return Object.keys(annotations).length > 0 ? annotations : undefined;
469
+ }
470
+ function toolRecord(target, endpoint, rawTool) {
471
+ const tool = asRecord(rawTool);
472
+ if (!tool || typeof tool.name !== "string" || tool.name.trim() === "") {
473
+ return undefined;
474
+ }
475
+ const name = tool.name;
476
+ const description = typeof tool.description === "string" ? tool.description : "";
477
+ const annotations = sanitizeAnnotations(tool.annotations);
478
+ return {
479
+ toolId: stableId("tool", {
480
+ kind: "mcp",
481
+ sourceId: target.source.sourceId,
482
+ name,
483
+ }),
484
+ canonicalName: name,
485
+ displayName: annotations?.title ?? name,
486
+ description,
487
+ origin: {
488
+ kind: "mcp",
489
+ host: target.source.host,
490
+ server: target.source.name,
491
+ scope: target.source.scope,
492
+ },
493
+ source: {
494
+ sourceId: target.source.sourceId,
495
+ endpoint,
496
+ },
497
+ evidence: {
498
+ level: "SERVER_AUTHORITATIVE",
499
+ source: `mcp:${target.source.host}/${target.source.name}`,
500
+ },
501
+ visibility: {
502
+ configured: true,
503
+ runtimeRegistered: false,
504
+ modelVisible: false,
505
+ executed: false,
506
+ },
507
+ ...(tool.inputSchema !== undefined
508
+ ? { inputSchema: tool.inputSchema }
509
+ : {}),
510
+ ...(annotations ? { annotations } : {}),
511
+ once: assessToolImportance({
512
+ name,
513
+ description,
514
+ evidence: "SERVER_AUTHORITATIVE",
515
+ readOnlyHint: annotations?.readOnlyHint,
516
+ }),
517
+ };
518
+ }
519
+ async function listTools(target, era, protocolVersion, sessionId, options) {
520
+ const tools = [];
521
+ const seenCursors = new Set();
522
+ const endpoint = safeEndpointLabel(target.url);
523
+ let cursor;
524
+ let pages = 0;
525
+ while (true) {
526
+ if (pages >= options.maxPages) {
527
+ throw new McpLiveDiscoveryError("PAGINATION_LIMIT", "MCP tools/list exceeded the configured page limit.");
528
+ }
529
+ const params = {};
530
+ if (cursor)
531
+ params.cursor = cursor;
532
+ if (era === "MODERN_2026") {
533
+ params._meta = modernMeta();
534
+ }
535
+ const headers = {
536
+ "MCP-Protocol-Version": protocolVersion,
537
+ };
538
+ if (sessionId) {
539
+ headers["Mcp-Session-Id"] = sessionId;
540
+ }
541
+ const response = await postRpc(target, {
542
+ jsonrpc: "2.0",
543
+ id: `once-tools-${pages + 1}`,
544
+ method: "tools/list",
545
+ params,
546
+ }, "tools/list", options.timeoutMs, options.maxResponseBytes, headers);
547
+ const result = rpcResult(response.payload);
548
+ const pageTools = Array.isArray(result.tools) ? result.tools : [];
549
+ for (const rawTool of pageTools) {
550
+ const record = toolRecord(target, endpoint, rawTool);
551
+ if (record)
552
+ tools.push(record);
553
+ if (tools.length > options.maxTools) {
554
+ throw new McpLiveDiscoveryError("TOOL_LIMIT", "MCP tools/list exceeded the configured tool limit.");
555
+ }
556
+ }
557
+ pages++;
558
+ const nextCursor = typeof result.nextCursor === "string" && result.nextCursor !== ""
559
+ ? result.nextCursor
560
+ : undefined;
561
+ if (!nextCursor)
562
+ break;
563
+ if (seenCursors.has(nextCursor)) {
564
+ throw new McpLiveDiscoveryError("CURSOR_LOOP", "MCP tools/list repeated a pagination cursor.");
565
+ }
566
+ seenCursors.add(nextCursor);
567
+ cursor = nextCursor;
568
+ }
569
+ return { pages, tools };
570
+ }
571
+ async function enumerateTarget(target, options) {
572
+ const endpoint = safeEndpointLabel(target.url);
573
+ let era;
574
+ let protocolVersion;
575
+ let sessionId;
576
+ try {
577
+ protocolVersion = await discoverModern(target, options.timeoutMs, options.maxResponseBytes);
578
+ era = "MODERN_2026";
579
+ }
580
+ catch (error) {
581
+ const code = error instanceof McpLiveDiscoveryError ? error.code : "UNKNOWN";
582
+ if (code !== "RPC_METHOD_NOT_FOUND" &&
583
+ code !== "MODERN_VERSION_UNSUPPORTED" &&
584
+ code !== "HTTP_400" &&
585
+ code !== "HTTP_404" &&
586
+ code !== "HTTP_405") {
587
+ throw error;
588
+ }
589
+ const legacy = await initializeLegacy(target, options.timeoutMs, options.maxResponseBytes);
590
+ era = "LEGACY_2025";
591
+ protocolVersion = legacy.protocolVersion;
592
+ sessionId = legacy.sessionId;
593
+ }
594
+ const listed = await listTools(target, era, protocolVersion, sessionId, options);
595
+ return {
596
+ probe: {
597
+ sourceId: target.source.sourceId,
598
+ source: `${target.source.host}/${target.source.name}`,
599
+ status: "ENUMERATED",
600
+ endpoint,
601
+ protocolEra: era,
602
+ protocolVersion,
603
+ pages: listed.pages,
604
+ toolCount: listed.tools.length,
605
+ },
606
+ tools: listed.tools,
607
+ };
608
+ }
609
+ function normalizedOptions(options) {
610
+ return {
611
+ timeoutMs: options.timeoutMs ?? DEFAULT_TIMEOUT_MS,
612
+ maxPages: options.maxPages ?? DEFAULT_MAX_PAGES,
613
+ maxTools: options.maxTools ?? DEFAULT_MAX_TOOLS,
614
+ maxResponseBytes: options.maxResponseBytes ?? DEFAULT_MAX_RESPONSE_BYTES,
615
+ };
616
+ }
617
+ function selectSources(sources, selectors) {
618
+ if (selectors.length === 0) {
619
+ throw new McpLiveDiscoveryError("EXPLICIT_SELECTION_REQUIRED", "Live MCP enumeration requires at least one explicit configured server selector.");
620
+ }
621
+ const selected = [];
622
+ for (const selector of selectors) {
623
+ const exact = sources.filter(source => source.sourceId === selector ||
624
+ `${source.host}/${source.name}` === selector);
625
+ const matches = exact.length > 0
626
+ ? exact
627
+ : sources.filter(source => source.name === selector);
628
+ if (matches.length === 0) {
629
+ throw new McpLiveDiscoveryError("SELECTOR_NOT_FOUND", `Configured MCP server selector not found: ${selector}`);
630
+ }
631
+ if (matches.length > 1) {
632
+ throw new McpLiveDiscoveryError("SELECTOR_AMBIGUOUS", `Configured MCP server selector is ambiguous; use host/name: ${selector}`);
633
+ }
634
+ if (!selected.some(source => source.sourceId === matches[0]?.sourceId)) {
635
+ selected.push(matches[0]);
636
+ }
637
+ }
638
+ return selected;
639
+ }
640
+ export async function discoverLiveMcpTools(root, configuredSources, selectors, options = {}) {
641
+ const selected = selectSources(configuredSources, selectors);
642
+ const resolvedOptions = normalizedOptions(options);
643
+ const probes = [];
644
+ const tools = [];
645
+ let externalServersContacted = 0;
646
+ for (const source of selected) {
647
+ if (source.transport === "STDIO") {
648
+ probes.push({
649
+ sourceId: source.sourceId,
650
+ source: `${source.host}/${source.name}`,
651
+ status: "SKIPPED_STDIO",
652
+ pages: 0,
653
+ toolCount: 0,
654
+ });
655
+ continue;
656
+ }
657
+ if (source.transport === "SSE") {
658
+ probes.push({
659
+ sourceId: source.sourceId,
660
+ source: `${source.host}/${source.name}`,
661
+ status: "SKIPPED_UNSUPPORTED_TRANSPORT",
662
+ pages: 0,
663
+ toolCount: 0,
664
+ errorCode: "SSE_NOT_SUPPORTED_IN_11B_V1",
665
+ });
666
+ continue;
667
+ }
668
+ const target = await resolveRemoteTarget(root, source);
669
+ if (!target) {
670
+ probes.push({
671
+ sourceId: source.sourceId,
672
+ source: `${source.host}/${source.name}`,
673
+ status: "FAILED",
674
+ pages: 0,
675
+ toolCount: 0,
676
+ errorCode: "REMOTE_ENDPOINT_NOT_RESOLVED",
677
+ });
678
+ continue;
679
+ }
680
+ externalServersContacted++;
681
+ try {
682
+ const enumerated = await enumerateTarget(target, resolvedOptions);
683
+ probes.push(enumerated.probe);
684
+ tools.push(...enumerated.tools);
685
+ }
686
+ catch (error) {
687
+ probes.push({
688
+ sourceId: source.sourceId,
689
+ source: `${source.host}/${source.name}`,
690
+ status: "FAILED",
691
+ endpoint: safeEndpointLabel(target.url),
692
+ pages: 0,
693
+ toolCount: 0,
694
+ errorCode: error instanceof McpLiveDiscoveryError
695
+ ? error.code
696
+ : "UNKNOWN_ERROR",
697
+ });
698
+ }
699
+ }
700
+ return {
701
+ mode: "EXPLICIT_REMOTE_HTTP",
702
+ externalServersContacted,
703
+ stdioServersLaunched: false,
704
+ redirectsFollowed: false,
705
+ secretValuesRetained: false,
706
+ probes,
707
+ tools,
708
+ };
709
+ }
710
+ //# sourceMappingURL=mcp-live-discovery.js.map