ainecto 0.12.0 → 0.12.2
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/README.md +1 -1
- package/dist/bin/ainecto.js +371 -253
- package/dist/bin/ainecto.js.map +1 -1
- package/dist/bin/mcp.js +283 -183
- package/dist/bin/mcp.js.map +1 -1
- package/package.json +2 -2
package/dist/bin/mcp.js
CHANGED
|
@@ -169,6 +169,272 @@ function isRecord(value) {
|
|
|
169
169
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
170
170
|
}
|
|
171
171
|
|
|
172
|
+
// src/core/http/rateLimit.ts
|
|
173
|
+
var RATE_LIMITED = "RATE_LIMITED";
|
|
174
|
+
function parseRetryAfter(value, now = Date.now()) {
|
|
175
|
+
if (value === null || value === void 0) return void 0;
|
|
176
|
+
const trimmed = value.trim();
|
|
177
|
+
if (/^\d+$/.test(trimmed)) return representable(Number(trimmed), now);
|
|
178
|
+
if (!/[a-z]/i.test(trimmed)) return void 0;
|
|
179
|
+
const at = Date.parse(trimmed);
|
|
180
|
+
if (Number.isNaN(at)) return void 0;
|
|
181
|
+
return Math.max(0, Math.ceil((at - now) / 1e3));
|
|
182
|
+
}
|
|
183
|
+
function representable(seconds, now) {
|
|
184
|
+
return Number.isNaN(new Date(now + seconds * 1e3).getTime()) ? void 0 : seconds;
|
|
185
|
+
}
|
|
186
|
+
function rateLimitDetails(headerValue, body, now = Date.now()) {
|
|
187
|
+
const fields = isRecord2(body) ? body : {};
|
|
188
|
+
const fromBody = typeof fields.retryAfterSeconds === "number" && Number.isFinite(fields.retryAfterSeconds) && fields.retryAfterSeconds >= 0 ? representable(Math.ceil(fields.retryAfterSeconds), now) : void 0;
|
|
189
|
+
const retryAfterSeconds = fromBody ?? parseRetryAfter(headerValue, now);
|
|
190
|
+
return {
|
|
191
|
+
status: 429,
|
|
192
|
+
...retryAfterSeconds !== void 0 ? { retryAfterSeconds, retryAt: new Date(now + retryAfterSeconds * 1e3).toISOString() } : {},
|
|
193
|
+
...typeof fields.scope === "string" ? { scope: fields.scope } : {},
|
|
194
|
+
...typeof fields.policyId === "string" ? { policyId: fields.policyId } : {}
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
function isRateLimitedRpcError(error) {
|
|
198
|
+
return isRecord2(error) && error.code === 429 && isRecord2(error.data) && error.data.code === RATE_LIMITED;
|
|
199
|
+
}
|
|
200
|
+
function rateLimitMessage(where, details) {
|
|
201
|
+
return details.retryAfterSeconds === void 0 ? `${where} was rate limited (HTTP 429). Wait before trying again.` : `${where} was rate limited (HTTP 429). Retry after ${details.retryAfterSeconds} seconds (${details.retryAt}).`;
|
|
202
|
+
}
|
|
203
|
+
function isRecord2(value) {
|
|
204
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// src/core/harness/legacyRole.ts
|
|
208
|
+
var LEGACY_ROLE_HEADER = "X-AiNecto-Role";
|
|
209
|
+
var LEGACY_ROLE_ENV_VAR = "AINECTO_ROLE";
|
|
210
|
+
var LEGACY_ROLE_NOTICE = "AiNecto session roles were removed; access follows your AiNecto permissions. Re-run `ainecto init` to tidy this repository.";
|
|
211
|
+
function legacyRoleInput(flag, envVars = process.env) {
|
|
212
|
+
const value = flag ?? envVars[LEGACY_ROLE_ENV_VAR];
|
|
213
|
+
return value !== void 0 && value.trim() ? value.trim() : void 0;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// src/core/version.ts
|
|
217
|
+
var CLI_VERSION = true ? "0.12.2" : UNBUNDLED_VERSION;
|
|
218
|
+
var PACKAGE_NAME = "@ainecto/mcp";
|
|
219
|
+
var CLI_USER_AGENT = `${PACKAGE_NAME} ${CLI_VERSION}`;
|
|
220
|
+
|
|
221
|
+
// src/core/mcp/rpcClient.ts
|
|
222
|
+
var CHANNEL_HEADER = "X-AiNecto-Channel";
|
|
223
|
+
var AGENT_HEADER = "X-AiNecto-Agent";
|
|
224
|
+
var AGENT_NAME_HEADER = "X-AiNecto-Agent-Name";
|
|
225
|
+
var MAX_AGENT_NAME_LENGTH = 100;
|
|
226
|
+
var McpRpcError = class extends Error {
|
|
227
|
+
constructor(message, code, details) {
|
|
228
|
+
super(message);
|
|
229
|
+
this.code = code;
|
|
230
|
+
this.details = details;
|
|
231
|
+
}
|
|
232
|
+
code;
|
|
233
|
+
details;
|
|
234
|
+
};
|
|
235
|
+
var RateLimitedError = class extends McpRpcError {
|
|
236
|
+
constructor(message, rateLimit) {
|
|
237
|
+
super(message, RATE_LIMITED, rateLimit);
|
|
238
|
+
this.rateLimit = rateLimit;
|
|
239
|
+
this.name = "RateLimitedError";
|
|
240
|
+
}
|
|
241
|
+
rateLimit;
|
|
242
|
+
};
|
|
243
|
+
var McpRpcClient = class {
|
|
244
|
+
constructor(options) {
|
|
245
|
+
this.options = options;
|
|
246
|
+
this.fetchImpl = options.fetchImpl ?? fetch;
|
|
247
|
+
}
|
|
248
|
+
options;
|
|
249
|
+
fetchImpl;
|
|
250
|
+
nextId = 1;
|
|
251
|
+
agent;
|
|
252
|
+
/**
|
|
253
|
+
* 브리지 전용 — 호스트 에이전트가 보낸 {@code initialize} 의 {@code clientInfo.name}(프로그램 고정 이름).
|
|
254
|
+
* 이후 모든 요청의 {@code X-AiNecto-Agent} 헤더로 싣는다. 직접 명령은 부르지 않는다(누가 셸을 돌렸는지 모른다).
|
|
255
|
+
*/
|
|
256
|
+
useAgent(clientName) {
|
|
257
|
+
this.agent = agentHeaderValue(clientName);
|
|
258
|
+
}
|
|
259
|
+
initialize() {
|
|
260
|
+
return this.request("initialize", {
|
|
261
|
+
protocolVersion: "2024-11-05",
|
|
262
|
+
capabilities: {},
|
|
263
|
+
clientInfo: { name: PACKAGE_NAME, version: CLI_VERSION }
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
async toolsList() {
|
|
267
|
+
const result = await this.request("tools/list", {});
|
|
268
|
+
if (!isRecord3(result) || !Array.isArray(result.tools)) {
|
|
269
|
+
throw new McpRpcError("tools/list response did not include a tools array.", "MCP_PROTOCOL_ERROR", result);
|
|
270
|
+
}
|
|
271
|
+
return result.tools;
|
|
272
|
+
}
|
|
273
|
+
async toolsCall(name, args) {
|
|
274
|
+
return this.request("tools/call", { name, arguments: args ?? {} });
|
|
275
|
+
}
|
|
276
|
+
async request(method, params) {
|
|
277
|
+
const response = await this.sendRaw({
|
|
278
|
+
jsonrpc: "2.0",
|
|
279
|
+
id: this.nextId++,
|
|
280
|
+
method,
|
|
281
|
+
params
|
|
282
|
+
});
|
|
283
|
+
if (!response) {
|
|
284
|
+
return void 0;
|
|
285
|
+
}
|
|
286
|
+
if ("error" in response) {
|
|
287
|
+
if (isRateLimitedRpcError(response.error)) {
|
|
288
|
+
const details = rateLimitDetails(void 0, response.error.data);
|
|
289
|
+
throw new RateLimitedError(rateLimitMessage(`MCP ${method}`, details), details);
|
|
290
|
+
}
|
|
291
|
+
throw new McpRpcError(describeRpcError(response.error), "MCP_ERROR", response.error);
|
|
292
|
+
}
|
|
293
|
+
return response.result;
|
|
294
|
+
}
|
|
295
|
+
async sendRaw(request) {
|
|
296
|
+
const token = await this.options.tokenProvider?.getAccessToken();
|
|
297
|
+
let response = await this.postJson(request, token);
|
|
298
|
+
if (response.status === 401 && this.options.tokenProvider) {
|
|
299
|
+
const refreshed = await this.options.tokenProvider.refreshAfterUnauthorized();
|
|
300
|
+
if (refreshed) {
|
|
301
|
+
response = await this.postJson(request, refreshed);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
if (response.status === 204 || response.status === 202) {
|
|
305
|
+
return void 0;
|
|
306
|
+
}
|
|
307
|
+
const text = await response.text();
|
|
308
|
+
if (response.status === 429) {
|
|
309
|
+
return rateLimitedResponse(text, response.headers.get("retry-after"));
|
|
310
|
+
}
|
|
311
|
+
if (!response.ok) {
|
|
312
|
+
const envelope = rpcErrorEnvelope(text);
|
|
313
|
+
if (envelope) {
|
|
314
|
+
return envelope;
|
|
315
|
+
}
|
|
316
|
+
const serverError = parseServerError(text);
|
|
317
|
+
throw new McpRpcError(
|
|
318
|
+
`MCP HTTP request failed with HTTP ${response.status}${serverError ? ` \u2014 ${describeServerError(serverError)}` : "."}`,
|
|
319
|
+
"MCP_HTTP_ERROR",
|
|
320
|
+
{ ...httpErrorDetails(response.status, text, this.options.envVars ?? process.env), ...serverError ? { error: serverError.error } : {} }
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
if (!text.trim()) {
|
|
324
|
+
return void 0;
|
|
325
|
+
}
|
|
326
|
+
const parsed = JSON.parse(text);
|
|
327
|
+
if (!isJsonRpcResponse(parsed)) {
|
|
328
|
+
throw new McpRpcError("MCP response was not a JSON-RPC response.", "MCP_PROTOCOL_ERROR", parsed);
|
|
329
|
+
}
|
|
330
|
+
return parsed;
|
|
331
|
+
}
|
|
332
|
+
postJson(request, token) {
|
|
333
|
+
const headers = {
|
|
334
|
+
"content-type": "application/json",
|
|
335
|
+
accept: "application/json"
|
|
336
|
+
};
|
|
337
|
+
if (token) {
|
|
338
|
+
headers.authorization = `Bearer ${token}`;
|
|
339
|
+
}
|
|
340
|
+
const legacyRole = printableHeaderValue(this.options.legacyRole);
|
|
341
|
+
if (legacyRole) {
|
|
342
|
+
headers[LEGACY_ROLE_HEADER.toLowerCase()] = legacyRole;
|
|
343
|
+
}
|
|
344
|
+
headers[CHANNEL_HEADER.toLowerCase()] = this.options.channel;
|
|
345
|
+
if (this.agent) {
|
|
346
|
+
headers[AGENT_HEADER.toLowerCase()] = this.agent;
|
|
347
|
+
}
|
|
348
|
+
if (this.options.agentName) {
|
|
349
|
+
headers[AGENT_NAME_HEADER.toLowerCase()] = this.options.agentName;
|
|
350
|
+
}
|
|
351
|
+
return this.fetchImpl(this.options.endpoint, {
|
|
352
|
+
method: "POST",
|
|
353
|
+
headers,
|
|
354
|
+
body: JSON.stringify(request)
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
};
|
|
358
|
+
function rateLimitedResponse(text, retryAfter) {
|
|
359
|
+
const envelope = rpcErrorEnvelope(text);
|
|
360
|
+
if (envelope) {
|
|
361
|
+
const data = isRecord3(envelope.error.data) ? envelope.error.data : {};
|
|
362
|
+
const seconds = parseRetryAfter(retryAfter);
|
|
363
|
+
if (data.retryAfterSeconds === void 0 && seconds !== void 0) {
|
|
364
|
+
envelope.error.data = { ...data, retryAfterSeconds: seconds };
|
|
365
|
+
}
|
|
366
|
+
return envelope;
|
|
367
|
+
}
|
|
368
|
+
const details = rateLimitDetails(retryAfter, parseJsonOrUndefined(text));
|
|
369
|
+
throw new RateLimitedError(rateLimitMessage("MCP HTTP request", details), details);
|
|
370
|
+
}
|
|
371
|
+
function rpcErrorEnvelope(text) {
|
|
372
|
+
const parsed = parseJsonOrUndefined(text);
|
|
373
|
+
return isJsonRpcResponse(parsed) && "error" in parsed && isRecord3(parsed.error) ? parsed : void 0;
|
|
374
|
+
}
|
|
375
|
+
function parseJsonOrUndefined(text) {
|
|
376
|
+
try {
|
|
377
|
+
return JSON.parse(text);
|
|
378
|
+
} catch {
|
|
379
|
+
return void 0;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
function describeRpcError(error) {
|
|
383
|
+
const data = isRecord3(error.data) ? error.data : {};
|
|
384
|
+
const messages = isRecord3(data.messages) ? data.messages : {};
|
|
385
|
+
const sentence = [error.message, data.message, messages.en, messages.ko].find((value) => typeof value === "string" && value.trim() !== "");
|
|
386
|
+
const code = typeof data.code === "string" && data.code.trim() ? data.code.trim() : void 0;
|
|
387
|
+
if (!code) {
|
|
388
|
+
return sentence ?? `JSON-RPC error ${error.code}`;
|
|
389
|
+
}
|
|
390
|
+
return describeServerError({ error: code, description: sentence });
|
|
391
|
+
}
|
|
392
|
+
function agentHeaderValue(clientName) {
|
|
393
|
+
return printableHeaderValue(clientName);
|
|
394
|
+
}
|
|
395
|
+
function printableHeaderValue(value) {
|
|
396
|
+
if (typeof value !== "string") {
|
|
397
|
+
return void 0;
|
|
398
|
+
}
|
|
399
|
+
const printable = value.replace(/[^\x20-\x7E]/g, "").trim().slice(0, MAX_AGENT_NAME_LENGTH).trim();
|
|
400
|
+
return printable || void 0;
|
|
401
|
+
}
|
|
402
|
+
function parseServerError(body) {
|
|
403
|
+
let parsed;
|
|
404
|
+
try {
|
|
405
|
+
parsed = JSON.parse(body);
|
|
406
|
+
} catch {
|
|
407
|
+
return void 0;
|
|
408
|
+
}
|
|
409
|
+
if (!isRecord3(parsed) || typeof parsed.error !== "string" || !parsed.error.trim()) {
|
|
410
|
+
return void 0;
|
|
411
|
+
}
|
|
412
|
+
return {
|
|
413
|
+
error: parsed.error.trim(),
|
|
414
|
+
description: typeof parsed.error_description === "string" && parsed.error_description.trim() ? parsed.error_description : void 0
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
function describeServerError(serverError) {
|
|
418
|
+
const line = serverError.description ? `${serverError.error}: ${serverError.description}` : serverError.error;
|
|
419
|
+
return line.replace(/\s+/g, " ").trim();
|
|
420
|
+
}
|
|
421
|
+
function isJsonRpcResponse(value) {
|
|
422
|
+
return isRecord3(value) && value.jsonrpc === "2.0" && ("result" in value || "error" in value);
|
|
423
|
+
}
|
|
424
|
+
function isRecord3(value) {
|
|
425
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
426
|
+
}
|
|
427
|
+
function httpErrorDetails(status, body, envVars) {
|
|
428
|
+
const details = { status };
|
|
429
|
+
if (envVars.AINECTO_CLI_DEBUG === "1" || envVars.AINECTO_DEBUG === "1") {
|
|
430
|
+
details.body = safeBodyPreview(body);
|
|
431
|
+
}
|
|
432
|
+
return details;
|
|
433
|
+
}
|
|
434
|
+
function safeBodyPreview(body) {
|
|
435
|
+
return body.length > 500 ? `${body.slice(0, 500)}...` : body;
|
|
436
|
+
}
|
|
437
|
+
|
|
172
438
|
// src/core/auth/oauth.ts
|
|
173
439
|
var DEFAULT_LOGIN_TIMEOUT_MS = 3e5;
|
|
174
440
|
var OAuthLoginError = class extends Error {
|
|
@@ -382,6 +648,10 @@ async function exchangeToken(tokenEndpoint, params, fetchImpl) {
|
|
|
382
648
|
headers: { "content-type": "application/x-www-form-urlencoded" },
|
|
383
649
|
body: new URLSearchParams(params)
|
|
384
650
|
});
|
|
651
|
+
if (response.status === 429) {
|
|
652
|
+
const details = rateLimitDetails(response.headers.get("retry-after"), await readJsonObject(response));
|
|
653
|
+
throw new RateLimitedError(rateLimitMessage("OAuth token request", details), details);
|
|
654
|
+
}
|
|
385
655
|
if (!response.ok) {
|
|
386
656
|
const oauthError = await readOAuthErrorCode(response);
|
|
387
657
|
if (params.grant_type === "refresh_token" && oauthError !== void 0 && REAUTH_REQUIRED_ERRORS.has(oauthError)) {
|
|
@@ -391,6 +661,13 @@ async function exchangeToken(tokenEndpoint, params, fetchImpl) {
|
|
|
391
661
|
}
|
|
392
662
|
return response.json();
|
|
393
663
|
}
|
|
664
|
+
async function readJsonObject(response) {
|
|
665
|
+
try {
|
|
666
|
+
return await response.json();
|
|
667
|
+
} catch {
|
|
668
|
+
return void 0;
|
|
669
|
+
}
|
|
670
|
+
}
|
|
394
671
|
async function readOAuthErrorCode(response) {
|
|
395
672
|
try {
|
|
396
673
|
const body = await response.json();
|
|
@@ -537,184 +814,6 @@ function assertEnvTokenEndpointAllowed(endpoint, envVars) {
|
|
|
537
814
|
throw new Error("AINECTO_TOKEN can only be used with the default prod/dev endpoints. Set AINECTO_ALLOW_CUSTOM_ENDPOINT_TOKEN=1 only when you intentionally trust the custom endpoint.");
|
|
538
815
|
}
|
|
539
816
|
|
|
540
|
-
// src/core/harness/legacyRole.ts
|
|
541
|
-
var LEGACY_ROLE_HEADER = "X-AiNecto-Role";
|
|
542
|
-
var LEGACY_ROLE_ENV_VAR = "AINECTO_ROLE";
|
|
543
|
-
var LEGACY_ROLE_NOTICE = "AiNecto session roles were removed; access follows your AiNecto permissions. Re-run `ainecto init` to tidy this repository.";
|
|
544
|
-
function legacyRoleInput(flag, envVars = process.env) {
|
|
545
|
-
const value = flag ?? envVars[LEGACY_ROLE_ENV_VAR];
|
|
546
|
-
return value !== void 0 && value.trim() ? value.trim() : void 0;
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
// src/core/version.ts
|
|
550
|
-
var CLI_VERSION = true ? "0.12.0" : UNBUNDLED_VERSION;
|
|
551
|
-
var PACKAGE_NAME = "@ainecto/mcp";
|
|
552
|
-
var CLI_USER_AGENT = `${PACKAGE_NAME} ${CLI_VERSION}`;
|
|
553
|
-
|
|
554
|
-
// src/core/mcp/rpcClient.ts
|
|
555
|
-
var CHANNEL_HEADER = "X-AiNecto-Channel";
|
|
556
|
-
var AGENT_HEADER = "X-AiNecto-Agent";
|
|
557
|
-
var AGENT_NAME_HEADER = "X-AiNecto-Agent-Name";
|
|
558
|
-
var MAX_AGENT_NAME_LENGTH = 100;
|
|
559
|
-
var McpRpcError = class extends Error {
|
|
560
|
-
constructor(message, code, details) {
|
|
561
|
-
super(message);
|
|
562
|
-
this.code = code;
|
|
563
|
-
this.details = details;
|
|
564
|
-
}
|
|
565
|
-
code;
|
|
566
|
-
details;
|
|
567
|
-
};
|
|
568
|
-
var McpRpcClient = class {
|
|
569
|
-
constructor(options) {
|
|
570
|
-
this.options = options;
|
|
571
|
-
this.fetchImpl = options.fetchImpl ?? fetch;
|
|
572
|
-
}
|
|
573
|
-
options;
|
|
574
|
-
fetchImpl;
|
|
575
|
-
nextId = 1;
|
|
576
|
-
agent;
|
|
577
|
-
/**
|
|
578
|
-
* 브리지 전용 — 호스트 에이전트가 보낸 {@code initialize} 의 {@code clientInfo.name}(프로그램 고정 이름).
|
|
579
|
-
* 이후 모든 요청의 {@code X-AiNecto-Agent} 헤더로 싣는다. 직접 명령은 부르지 않는다(누가 셸을 돌렸는지 모른다).
|
|
580
|
-
*/
|
|
581
|
-
useAgent(clientName) {
|
|
582
|
-
this.agent = agentHeaderValue(clientName);
|
|
583
|
-
}
|
|
584
|
-
initialize() {
|
|
585
|
-
return this.request("initialize", {
|
|
586
|
-
protocolVersion: "2024-11-05",
|
|
587
|
-
capabilities: {},
|
|
588
|
-
clientInfo: { name: PACKAGE_NAME, version: CLI_VERSION }
|
|
589
|
-
});
|
|
590
|
-
}
|
|
591
|
-
async toolsList() {
|
|
592
|
-
const result = await this.request("tools/list", {});
|
|
593
|
-
if (!isRecord2(result) || !Array.isArray(result.tools)) {
|
|
594
|
-
throw new McpRpcError("tools/list response did not include a tools array.", "MCP_PROTOCOL_ERROR", result);
|
|
595
|
-
}
|
|
596
|
-
return result.tools;
|
|
597
|
-
}
|
|
598
|
-
async toolsCall(name, args) {
|
|
599
|
-
return this.request("tools/call", { name, arguments: args ?? {} });
|
|
600
|
-
}
|
|
601
|
-
async request(method, params) {
|
|
602
|
-
const response = await this.sendRaw({
|
|
603
|
-
jsonrpc: "2.0",
|
|
604
|
-
id: this.nextId++,
|
|
605
|
-
method,
|
|
606
|
-
params
|
|
607
|
-
});
|
|
608
|
-
if (!response) {
|
|
609
|
-
return void 0;
|
|
610
|
-
}
|
|
611
|
-
if ("error" in response) {
|
|
612
|
-
throw new McpRpcError(response.error.message, "MCP_ERROR", response.error);
|
|
613
|
-
}
|
|
614
|
-
return response.result;
|
|
615
|
-
}
|
|
616
|
-
async sendRaw(request) {
|
|
617
|
-
const token = await this.options.tokenProvider?.getAccessToken();
|
|
618
|
-
let response = await this.postJson(request, token);
|
|
619
|
-
if (response.status === 401 && this.options.tokenProvider) {
|
|
620
|
-
const refreshed = await this.options.tokenProvider.refreshAfterUnauthorized();
|
|
621
|
-
if (refreshed) {
|
|
622
|
-
response = await this.postJson(request, refreshed);
|
|
623
|
-
}
|
|
624
|
-
}
|
|
625
|
-
if (response.status === 204 || response.status === 202) {
|
|
626
|
-
return void 0;
|
|
627
|
-
}
|
|
628
|
-
const text = await response.text();
|
|
629
|
-
if (!response.ok) {
|
|
630
|
-
const serverError = parseServerError(text);
|
|
631
|
-
throw new McpRpcError(
|
|
632
|
-
`MCP HTTP request failed with HTTP ${response.status}${serverError ? ` \u2014 ${describeServerError(serverError)}` : "."}`,
|
|
633
|
-
"MCP_HTTP_ERROR",
|
|
634
|
-
{ ...httpErrorDetails(response.status, text, this.options.envVars ?? process.env), ...serverError ? { error: serverError.error } : {} }
|
|
635
|
-
);
|
|
636
|
-
}
|
|
637
|
-
if (!text.trim()) {
|
|
638
|
-
return void 0;
|
|
639
|
-
}
|
|
640
|
-
const parsed = JSON.parse(text);
|
|
641
|
-
if (!isJsonRpcResponse(parsed)) {
|
|
642
|
-
throw new McpRpcError("MCP response was not a JSON-RPC response.", "MCP_PROTOCOL_ERROR", parsed);
|
|
643
|
-
}
|
|
644
|
-
return parsed;
|
|
645
|
-
}
|
|
646
|
-
postJson(request, token) {
|
|
647
|
-
const headers = {
|
|
648
|
-
"content-type": "application/json",
|
|
649
|
-
accept: "application/json"
|
|
650
|
-
};
|
|
651
|
-
if (token) {
|
|
652
|
-
headers.authorization = `Bearer ${token}`;
|
|
653
|
-
}
|
|
654
|
-
const legacyRole = printableHeaderValue(this.options.legacyRole);
|
|
655
|
-
if (legacyRole) {
|
|
656
|
-
headers[LEGACY_ROLE_HEADER.toLowerCase()] = legacyRole;
|
|
657
|
-
}
|
|
658
|
-
headers[CHANNEL_HEADER.toLowerCase()] = this.options.channel;
|
|
659
|
-
if (this.agent) {
|
|
660
|
-
headers[AGENT_HEADER.toLowerCase()] = this.agent;
|
|
661
|
-
}
|
|
662
|
-
if (this.options.agentName) {
|
|
663
|
-
headers[AGENT_NAME_HEADER.toLowerCase()] = this.options.agentName;
|
|
664
|
-
}
|
|
665
|
-
return this.fetchImpl(this.options.endpoint, {
|
|
666
|
-
method: "POST",
|
|
667
|
-
headers,
|
|
668
|
-
body: JSON.stringify(request)
|
|
669
|
-
});
|
|
670
|
-
}
|
|
671
|
-
};
|
|
672
|
-
function agentHeaderValue(clientName) {
|
|
673
|
-
return printableHeaderValue(clientName);
|
|
674
|
-
}
|
|
675
|
-
function printableHeaderValue(value) {
|
|
676
|
-
if (typeof value !== "string") {
|
|
677
|
-
return void 0;
|
|
678
|
-
}
|
|
679
|
-
const printable = value.replace(/[^\x20-\x7E]/g, "").trim().slice(0, MAX_AGENT_NAME_LENGTH).trim();
|
|
680
|
-
return printable || void 0;
|
|
681
|
-
}
|
|
682
|
-
function parseServerError(body) {
|
|
683
|
-
let parsed;
|
|
684
|
-
try {
|
|
685
|
-
parsed = JSON.parse(body);
|
|
686
|
-
} catch {
|
|
687
|
-
return void 0;
|
|
688
|
-
}
|
|
689
|
-
if (!isRecord2(parsed) || typeof parsed.error !== "string" || !parsed.error.trim()) {
|
|
690
|
-
return void 0;
|
|
691
|
-
}
|
|
692
|
-
return {
|
|
693
|
-
error: parsed.error.trim(),
|
|
694
|
-
description: typeof parsed.error_description === "string" && parsed.error_description.trim() ? parsed.error_description : void 0
|
|
695
|
-
};
|
|
696
|
-
}
|
|
697
|
-
function describeServerError(serverError) {
|
|
698
|
-
const line = serverError.description ? `${serverError.error}: ${serverError.description}` : serverError.error;
|
|
699
|
-
return line.replace(/\s+/g, " ").trim();
|
|
700
|
-
}
|
|
701
|
-
function isJsonRpcResponse(value) {
|
|
702
|
-
return isRecord2(value) && value.jsonrpc === "2.0" && ("result" in value || "error" in value);
|
|
703
|
-
}
|
|
704
|
-
function isRecord2(value) {
|
|
705
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
706
|
-
}
|
|
707
|
-
function httpErrorDetails(status, body, envVars) {
|
|
708
|
-
const details = { status };
|
|
709
|
-
if (envVars.AINECTO_CLI_DEBUG === "1" || envVars.AINECTO_DEBUG === "1") {
|
|
710
|
-
details.body = safeBodyPreview(body);
|
|
711
|
-
}
|
|
712
|
-
return details;
|
|
713
|
-
}
|
|
714
|
-
function safeBodyPreview(body) {
|
|
715
|
-
return body.length > 500 ? `${body.slice(0, 500)}...` : body;
|
|
716
|
-
}
|
|
717
|
-
|
|
718
817
|
// src/core/mcp/stdioBridge.ts
|
|
719
818
|
import { createInterface } from "readline";
|
|
720
819
|
async function runStdioBridge(options) {
|
|
@@ -744,15 +843,16 @@ async function runStdioBridge(options) {
|
|
|
744
843
|
`);
|
|
745
844
|
continue;
|
|
746
845
|
}
|
|
747
|
-
writeLine(options.output, failure(request.id, INTERNAL_ERROR, error));
|
|
846
|
+
writeLine(options.output, failure(request.id, error instanceof RateLimitedError ? RATE_LIMITED_CODE : INTERNAL_ERROR, error));
|
|
748
847
|
}
|
|
749
848
|
}
|
|
750
849
|
}
|
|
751
850
|
var PARSE_ERROR = -32700;
|
|
752
851
|
var INVALID_REQUEST = -32600;
|
|
753
852
|
var INTERNAL_ERROR = -32603;
|
|
853
|
+
var RATE_LIMITED_CODE = 429;
|
|
754
854
|
function failure(id, code, error) {
|
|
755
|
-
const data = error instanceof McpRpcError ? { code: error.code, ...
|
|
855
|
+
const data = error instanceof McpRpcError ? { code: error.code, ...isRecord4(error.details) ? error.details : {} } : void 0;
|
|
756
856
|
return {
|
|
757
857
|
jsonrpc: "2.0",
|
|
758
858
|
id,
|
|
@@ -768,7 +868,7 @@ function writeLine(output, message) {
|
|
|
768
868
|
}
|
|
769
869
|
function parseJsonRpcRequest(line) {
|
|
770
870
|
const parsed = JSON.parse(line);
|
|
771
|
-
if (!
|
|
871
|
+
if (!isRecord4(parsed) || parsed.jsonrpc !== "2.0" || typeof parsed.method !== "string") {
|
|
772
872
|
throw new Error("Invalid JSON-RPC request.");
|
|
773
873
|
}
|
|
774
874
|
return {
|
|
@@ -779,9 +879,9 @@ function parseJsonRpcRequest(line) {
|
|
|
779
879
|
};
|
|
780
880
|
}
|
|
781
881
|
function clientInfoName(params) {
|
|
782
|
-
return
|
|
882
|
+
return isRecord4(params) && isRecord4(params.clientInfo) ? params.clientInfo.name : void 0;
|
|
783
883
|
}
|
|
784
|
-
function
|
|
884
|
+
function isRecord4(value) {
|
|
785
885
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
786
886
|
}
|
|
787
887
|
|