@ainecto/cli 0.12.0 → 0.12.1

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/bin/mcp.js CHANGED
@@ -169,6 +169,252 @@ 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.1" : 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(response.error.message, "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 serverError = parseServerError(text);
313
+ throw new McpRpcError(
314
+ `MCP HTTP request failed with HTTP ${response.status}${serverError ? ` \u2014 ${describeServerError(serverError)}` : "."}`,
315
+ "MCP_HTTP_ERROR",
316
+ { ...httpErrorDetails(response.status, text, this.options.envVars ?? process.env), ...serverError ? { error: serverError.error } : {} }
317
+ );
318
+ }
319
+ if (!text.trim()) {
320
+ return void 0;
321
+ }
322
+ const parsed = JSON.parse(text);
323
+ if (!isJsonRpcResponse(parsed)) {
324
+ throw new McpRpcError("MCP response was not a JSON-RPC response.", "MCP_PROTOCOL_ERROR", parsed);
325
+ }
326
+ return parsed;
327
+ }
328
+ postJson(request, token) {
329
+ const headers = {
330
+ "content-type": "application/json",
331
+ accept: "application/json"
332
+ };
333
+ if (token) {
334
+ headers.authorization = `Bearer ${token}`;
335
+ }
336
+ const legacyRole = printableHeaderValue(this.options.legacyRole);
337
+ if (legacyRole) {
338
+ headers[LEGACY_ROLE_HEADER.toLowerCase()] = legacyRole;
339
+ }
340
+ headers[CHANNEL_HEADER.toLowerCase()] = this.options.channel;
341
+ if (this.agent) {
342
+ headers[AGENT_HEADER.toLowerCase()] = this.agent;
343
+ }
344
+ if (this.options.agentName) {
345
+ headers[AGENT_NAME_HEADER.toLowerCase()] = this.options.agentName;
346
+ }
347
+ return this.fetchImpl(this.options.endpoint, {
348
+ method: "POST",
349
+ headers,
350
+ body: JSON.stringify(request)
351
+ });
352
+ }
353
+ };
354
+ function rateLimitedResponse(text, retryAfter) {
355
+ let parsed;
356
+ try {
357
+ parsed = JSON.parse(text);
358
+ } catch {
359
+ parsed = void 0;
360
+ }
361
+ if (isJsonRpcResponse(parsed) && "error" in parsed) {
362
+ const data = isRecord3(parsed.error.data) ? parsed.error.data : {};
363
+ const seconds = parseRetryAfter(retryAfter);
364
+ if (data.retryAfterSeconds === void 0 && seconds !== void 0) {
365
+ parsed.error.data = { ...data, retryAfterSeconds: seconds };
366
+ }
367
+ return parsed;
368
+ }
369
+ const details = rateLimitDetails(retryAfter, parsed);
370
+ throw new RateLimitedError(rateLimitMessage("MCP HTTP request", details), details);
371
+ }
372
+ function agentHeaderValue(clientName) {
373
+ return printableHeaderValue(clientName);
374
+ }
375
+ function printableHeaderValue(value) {
376
+ if (typeof value !== "string") {
377
+ return void 0;
378
+ }
379
+ const printable = value.replace(/[^\x20-\x7E]/g, "").trim().slice(0, MAX_AGENT_NAME_LENGTH).trim();
380
+ return printable || void 0;
381
+ }
382
+ function parseServerError(body) {
383
+ let parsed;
384
+ try {
385
+ parsed = JSON.parse(body);
386
+ } catch {
387
+ return void 0;
388
+ }
389
+ if (!isRecord3(parsed) || typeof parsed.error !== "string" || !parsed.error.trim()) {
390
+ return void 0;
391
+ }
392
+ return {
393
+ error: parsed.error.trim(),
394
+ description: typeof parsed.error_description === "string" && parsed.error_description.trim() ? parsed.error_description : void 0
395
+ };
396
+ }
397
+ function describeServerError(serverError) {
398
+ const line = serverError.description ? `${serverError.error}: ${serverError.description}` : serverError.error;
399
+ return line.replace(/\s+/g, " ").trim();
400
+ }
401
+ function isJsonRpcResponse(value) {
402
+ return isRecord3(value) && value.jsonrpc === "2.0" && ("result" in value || "error" in value);
403
+ }
404
+ function isRecord3(value) {
405
+ return typeof value === "object" && value !== null && !Array.isArray(value);
406
+ }
407
+ function httpErrorDetails(status, body, envVars) {
408
+ const details = { status };
409
+ if (envVars.AINECTO_CLI_DEBUG === "1" || envVars.AINECTO_DEBUG === "1") {
410
+ details.body = safeBodyPreview(body);
411
+ }
412
+ return details;
413
+ }
414
+ function safeBodyPreview(body) {
415
+ return body.length > 500 ? `${body.slice(0, 500)}...` : body;
416
+ }
417
+
172
418
  // src/core/auth/oauth.ts
173
419
  var DEFAULT_LOGIN_TIMEOUT_MS = 3e5;
174
420
  var OAuthLoginError = class extends Error {
@@ -382,6 +628,10 @@ async function exchangeToken(tokenEndpoint, params, fetchImpl) {
382
628
  headers: { "content-type": "application/x-www-form-urlencoded" },
383
629
  body: new URLSearchParams(params)
384
630
  });
631
+ if (response.status === 429) {
632
+ const details = rateLimitDetails(response.headers.get("retry-after"), await readJsonObject(response));
633
+ throw new RateLimitedError(rateLimitMessage("OAuth token request", details), details);
634
+ }
385
635
  if (!response.ok) {
386
636
  const oauthError = await readOAuthErrorCode(response);
387
637
  if (params.grant_type === "refresh_token" && oauthError !== void 0 && REAUTH_REQUIRED_ERRORS.has(oauthError)) {
@@ -391,6 +641,13 @@ async function exchangeToken(tokenEndpoint, params, fetchImpl) {
391
641
  }
392
642
  return response.json();
393
643
  }
644
+ async function readJsonObject(response) {
645
+ try {
646
+ return await response.json();
647
+ } catch {
648
+ return void 0;
649
+ }
650
+ }
394
651
  async function readOAuthErrorCode(response) {
395
652
  try {
396
653
  const body = await response.json();
@@ -537,184 +794,6 @@ function assertEnvTokenEndpointAllowed(endpoint, envVars) {
537
794
  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
795
  }
539
796
 
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
797
  // src/core/mcp/stdioBridge.ts
719
798
  import { createInterface } from "readline";
720
799
  async function runStdioBridge(options) {
@@ -744,15 +823,16 @@ async function runStdioBridge(options) {
744
823
  `);
745
824
  continue;
746
825
  }
747
- writeLine(options.output, failure(request.id, INTERNAL_ERROR, error));
826
+ writeLine(options.output, failure(request.id, error instanceof RateLimitedError ? RATE_LIMITED_CODE : INTERNAL_ERROR, error));
748
827
  }
749
828
  }
750
829
  }
751
830
  var PARSE_ERROR = -32700;
752
831
  var INVALID_REQUEST = -32600;
753
832
  var INTERNAL_ERROR = -32603;
833
+ var RATE_LIMITED_CODE = 429;
754
834
  function failure(id, code, error) {
755
- const data = error instanceof McpRpcError ? { code: error.code, ...isRecord3(error.details) ? error.details : {} } : void 0;
835
+ const data = error instanceof McpRpcError ? { code: error.code, ...isRecord4(error.details) ? error.details : {} } : void 0;
756
836
  return {
757
837
  jsonrpc: "2.0",
758
838
  id,
@@ -768,7 +848,7 @@ function writeLine(output, message) {
768
848
  }
769
849
  function parseJsonRpcRequest(line) {
770
850
  const parsed = JSON.parse(line);
771
- if (!isRecord3(parsed) || parsed.jsonrpc !== "2.0" || typeof parsed.method !== "string") {
851
+ if (!isRecord4(parsed) || parsed.jsonrpc !== "2.0" || typeof parsed.method !== "string") {
772
852
  throw new Error("Invalid JSON-RPC request.");
773
853
  }
774
854
  return {
@@ -779,9 +859,9 @@ function parseJsonRpcRequest(line) {
779
859
  };
780
860
  }
781
861
  function clientInfoName(params) {
782
- return isRecord3(params) && isRecord3(params.clientInfo) ? params.clientInfo.name : void 0;
862
+ return isRecord4(params) && isRecord4(params.clientInfo) ? params.clientInfo.name : void 0;
783
863
  }
784
- function isRecord3(value) {
864
+ function isRecord4(value) {
785
865
  return typeof value === "object" && value !== null && !Array.isArray(value);
786
866
  }
787
867