@ainecto/mcp 0.5.0 → 0.6.0

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
@@ -3,19 +3,8 @@
3
3
  // src/core/config/endpoints.ts
4
4
  var DEFAULT_ENDPOINTS = {
5
5
  prod: "https://ainecto.com/mcp",
6
- dev: "https://dev.ai-erd.com/mcp"
6
+ dev: "https://dev.ainecto.com/mcp"
7
7
  };
8
- var LEGACY_PROD_ENDPOINT = "https://ai-erd.com/mcp";
9
- var LEGACY_DEFAULT_ENDPOINTS = [
10
- LEGACY_PROD_ENDPOINT,
11
- "https://dev.ainecto.com/mcp"
12
- ];
13
- var TOKEN_SLOT_FALLBACKS = {
14
- [DEFAULT_ENDPOINTS.prod]: [LEGACY_PROD_ENDPOINT]
15
- };
16
- function fallbackTokenEndpointsFor(endpoint) {
17
- return TOKEN_SLOT_FALLBACKS[normalizeEndpoint(endpoint)] ?? [];
18
- }
19
8
  function resolveEndpoint(input = {}) {
20
9
  const envVars = input.envVars ?? process.env;
21
10
  const env = input.env ?? "prod";
@@ -35,7 +24,7 @@ function normalizeEndpoint(endpoint) {
35
24
  }
36
25
  function isDefaultEndpoint(endpoint) {
37
26
  const normalized = normalizeEndpoint(endpoint);
38
- return Object.values(DEFAULT_ENDPOINTS).includes(normalized) || LEGACY_DEFAULT_ENDPOINTS.includes(normalized);
27
+ return Object.values(DEFAULT_ENDPOINTS).includes(normalized);
39
28
  }
40
29
  function assertTrustedEndpointUrl(value, label = "URL") {
41
30
  const url = typeof value === "string" ? new URL(value) : value;
@@ -75,20 +64,17 @@ import { dirname, join } from "path";
75
64
 
76
65
  // src/core/harness/role.ts
77
66
  var HARNESS_ROLES = ["design", "development", "test", "validation"];
78
- var ROLE_SCOPE_PREFIX = "ai-erd:role:";
79
- var ROLE_HEADER = "X-AI-ERD-Role";
67
+ var ROLE_SCOPE_PREFIX = "ainecto:role:";
68
+ var ROLE_HEADER = "X-AiNecto-Role";
80
69
  var ROLE_ENV_VAR = "AINECTO_ROLE";
81
- var LEGACY_ROLE_ENV_VAR = "AI_ERD_ROLE";
82
70
  function resolveRole(input = {}) {
83
71
  if (input.role !== void 0) {
84
72
  return { role: assertRole(input.role), source: "flag" };
85
73
  }
86
74
  const envVars = input.envVars ?? process.env;
87
- for (const envVar of [ROLE_ENV_VAR, LEGACY_ROLE_ENV_VAR]) {
88
- const fromEnv = envVars[envVar];
89
- if (fromEnv && fromEnv.trim()) {
90
- return { role: assertRole(fromEnv), source: "env", envVar };
91
- }
75
+ const fromEnv = envVars[ROLE_ENV_VAR];
76
+ if (fromEnv && fromEnv.trim()) {
77
+ return { role: assertRole(fromEnv), source: "env" };
92
78
  }
93
79
  return { source: "none" };
94
80
  }
@@ -246,7 +232,7 @@ var OAuthClient = class {
246
232
  assertEnvTokenEndpointAllowed(this.options.endpoint, this.envVars);
247
233
  return envToken;
248
234
  }
249
- const stored = await this.loadStored();
235
+ const stored = await this.options.tokenStore.load(this.options.endpoint);
250
236
  if (!stored) {
251
237
  return void 0;
252
238
  }
@@ -262,7 +248,7 @@ var OAuthClient = class {
262
248
  return this.refreshStoredToken({ ...stored, refreshToken: stored.refreshToken });
263
249
  }
264
250
  async refreshAfterUnauthorized() {
265
- const stored = await this.loadStored();
251
+ const stored = await this.options.tokenStore.load(this.options.endpoint);
266
252
  if (this.options.readOnly || !stored?.refreshToken) {
267
253
  return void 0;
268
254
  }
@@ -307,29 +293,8 @@ var OAuthClient = class {
307
293
  await loopback.close();
308
294
  }
309
295
  }
310
- /**
311
- * ★대체로 읽는 옛 칸도 함께 지운다. 새 칸만 지우면 다음 읽기가 곧바로 옛 칸으로 대체돼
312
- * «로그아웃했는데 여전히 로그인됨»이 된다. 로그인은 기계당 한 번이므로 옛 CLI 의 칸도 같이 끝난다.
313
- */
314
296
  async logout() {
315
297
  await this.options.tokenStore.delete(this.options.endpoint);
316
- for (const fallback of fallbackTokenEndpointsFor(this.options.endpoint)) {
317
- await this.options.tokenStore.delete(fallback);
318
- }
319
- }
320
- /** 이 주소의 칸, 비었으면 옛 기본 주소의 칸({@link fallbackTokenEndpointsFor}). 쓰기는 늘 이 주소의 칸이다. */
321
- async loadStored() {
322
- const own = await this.options.tokenStore.load(this.options.endpoint);
323
- if (own) {
324
- return own;
325
- }
326
- for (const fallback of fallbackTokenEndpointsFor(this.options.endpoint)) {
327
- const stored = await this.options.tokenStore.load(fallback);
328
- if (stored) {
329
- return stored;
330
- }
331
- }
332
- return void 0;
333
298
  }
334
299
  /** 부르는 쪽이 refresh token 이 있음을 이미 확인했다 — 타입이 그것을 요구한다. */
335
300
  async refreshStoredToken(stored) {
@@ -588,7 +553,7 @@ function assertEnvTokenEndpointAllowed(endpoint, envVars) {
588
553
  }
589
554
 
590
555
  // src/core/version.ts
591
- var CLI_VERSION = true ? "0.5.0" : "dev";
556
+ var CLI_VERSION = true ? "0.6.0" : "dev";
592
557
  var PACKAGE_NAME = "@ainecto/mcp";
593
558
  var CLI_USER_AGENT = `${PACKAGE_NAME} ${CLI_VERSION}`;
594
559
 
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/core/config/endpoints.ts","../../src/core/auth/tokenStore.ts","../../src/core/harness/role.ts","../../src/core/auth/oauth.ts","../../src/core/auth/clientRegistration.ts","../../src/core/version.ts","../../src/core/mcp/rpcClient.ts","../../src/core/mcp/stdioBridge.ts","../../src/adapters/mcp/connector.ts","../../src/core/output/render.ts","../../src/adapters/mcp/args.ts","../../src/bin/mcp.ts"],"sourcesContent":["export type AinectoEnv = \"prod\" | \"dev\";\n\nexport interface EndpointResolutionInput {\n env?: AinectoEnv;\n endpoint?: string;\n envVars?: NodeJS.ProcessEnv;\n}\n\nexport interface ResolvedEndpoint {\n env: AinectoEnv;\n endpoint: string;\n source: \"flag\" | \"env\" | \"default\";\n}\n\n/**\n * 기본 MCP 엔드포인트.\n *\n * ★2026-10-11 AiNecto 복귀(2단계)로 «사람이 보는 주소»가 다시 ainecto.com 이다. /mcp 는 두 도메인\n * 모두에서 동일하게 서빙되고(같은 인스턴스), ai-erd.com 의 기계 경로는 301 하지 않고 계속 받는다.\n * OAuth 메타데이터(issuer·resource·authorization_servers)는 줄곧 ainecto.com 이었다 — 이제 기본 주소와 맞는다.\n *\n * ⚠dev 는 바꾸지 않는다(dev.ai-erd.com) — 복귀 계획서 범위 밖.\n */\nconst DEFAULT_ENDPOINTS: Record<AinectoEnv, string> = {\n prod: \"https://ainecto.com/mcp\",\n dev: \"https://dev.ai-erd.com/mcp\",\n};\n\n/**\n * 옛 기본값 — 이미 설정·클라이언트에 적어둔 사용자가 있다. isDefaultEndpoint 가 이것도 «기본»으로 본다\n * (그래서 AINECTO_TOKEN 도 이 주소에 보낸다). ai-erd.com 은 2026-09-21~10-11 의 기본값이었다.\n */\nconst LEGACY_PROD_ENDPOINT = \"https://ai-erd.com/mcp\";\nconst LEGACY_DEFAULT_ENDPOINTS: readonly string[] = [\n LEGACY_PROD_ENDPOINT,\n \"https://dev.ainecto.com/mcp\",\n];\n\n/**\n * ★<b>토큰 칸 대체 — 판정은 여기 한 곳이다.</b> 이 주소의 칸이 비었을 때 «대신 읽을» 옛 칸.\n *\n * <p>토큰 칸은 주소마다 하나라(tokenStore {@code tokenKey}), 기본 주소가 ai-erd.com → ainecto.com 으로\n * 돌아가면 ai-erd.com 칸에 로그인해 둔 사람이 새 칸에서 안 보인다. 서버의 토큰 검증은 issuer 와 고정\n * audience(mcp-server)만 보고 요청 호스트와 무관하므로, 옛 칸의 토큰은 새 주소에서도 유효하다.\n *\n * <p>쌍은 «새 prod 기본 ← 옛 prod 기본» 하나뿐이다. 사용자 지정 주소(--endpoint)·dev 에는 대체가 없다.\n * 읽기만 대체한다 — 갱신·새 로그인은 새 칸에 쓰고, 옛 칸은 지우지 않는다(옛 CLI 가 계속 쓴다).\n */\nconst TOKEN_SLOT_FALLBACKS: Readonly<Record<string, readonly string[]>> = {\n [DEFAULT_ENDPOINTS.prod]: [LEGACY_PROD_ENDPOINT],\n};\n\nexport function fallbackTokenEndpointsFor(endpoint: string): readonly string[] {\n return TOKEN_SLOT_FALLBACKS[normalizeEndpoint(endpoint)] ?? [];\n}\n\nexport function resolveEndpoint(input: EndpointResolutionInput = {}): ResolvedEndpoint {\n const envVars = input.envVars ?? process.env;\n const env = input.env ?? \"prod\";\n\n if (input.endpoint) {\n return { env, endpoint: normalizeEndpoint(input.endpoint), source: \"flag\" };\n }\n\n if (envVars.AINECTO_MCP_ENDPOINT) {\n return { env, endpoint: normalizeEndpoint(envVars.AINECTO_MCP_ENDPOINT), source: \"env\" };\n }\n\n return { env, endpoint: DEFAULT_ENDPOINTS[env], source: \"default\" };\n}\n\nexport function normalizeEndpoint(endpoint: string): string {\n const url = new URL(endpoint);\n assertTrustedEndpointUrl(url, \"MCP endpoint\");\n url.hash = \"\";\n return url.toString().replace(/\\/$/, \"\");\n}\n\n/**\n * ★<b>이 env 의 기본 주소와 «정확히» 같은가.</b>\n *\n * <p>{@link isDefaultEndpoint} 는 prod·dev·옛 주소를 전부 «기본»으로 보는 넓은 판정이라,\n * 「env=prod 인데 endpoint 만 dev」 같은 입력을 기본으로 착각한다. 그 판정으로 설정에서\n * {@code --endpoint} 를 빼면, 조회한 주소와 세션이 붙는 주소가 갈라진다\n * (2026-09-23 독립 재리뷰 I7).\n */\nexport function isDefaultEndpointFor(env: AinectoEnv, endpoint: string): boolean {\n return normalizeEndpoint(endpoint) === DEFAULT_ENDPOINTS[env];\n}\n\nexport function isDefaultEndpoint(endpoint: string): boolean {\n const normalized = normalizeEndpoint(endpoint);\n return Object.values(DEFAULT_ENDPOINTS).includes(normalized)\n || LEGACY_DEFAULT_ENDPOINTS.includes(normalized);\n}\n\nexport function assertTrustedEndpointUrl(value: string | URL, label = \"URL\"): void {\n const url = typeof value === \"string\" ? new URL(value) : value;\n if (url.protocol === \"https:\") {\n return;\n }\n if (url.protocol === \"http:\" && isLoopbackHostname(url.hostname)) {\n return;\n }\n throw new Error(`${label} must use https, except http is allowed for localhost loopback endpoints.`);\n}\n\nexport function assertLoopbackRedirectUrl(value: string): void {\n const url = new URL(value);\n if (url.protocol === \"http:\" && isLoopbackHostname(url.hostname)) {\n return;\n }\n throw new Error(\"OAuth redirect URI must be an http localhost loopback URL.\");\n}\n\nfunction isLoopbackHostname(hostname: string): boolean {\n return hostname === \"localhost\"\n || hostname === \"127.0.0.1\"\n || hostname === \"::1\"\n || hostname === \"[::1]\";\n}\n\nexport function assertEnv(value: string | undefined): AinectoEnv | undefined {\n if (value === undefined) {\n return undefined;\n }\n if (value === \"prod\" || value === \"dev\") {\n return value;\n }\n throw new Error(`Invalid --env value \"${value}\". Expected \"prod\" or \"dev\".`);\n}\n","import { createHash } from \"node:crypto\";\nimport { mkdir, readFile, rename, rm, stat, writeFile, chmod } from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\nimport { ROLE_SCOPE_PREFIX } from \"../harness/role\";\n\nexport interface StoredTokenSet {\n endpoint: string;\n accessToken: string;\n refreshToken?: string;\n tokenType?: string;\n expiresAt?: number;\n scope?: string;\n clientId?: string;\n clientSecret?: string;\n authorizationServer?: string;\n updatedAt: string;\n}\n\n/**\n * 토큰 저장소 — ★서버(endpoint)마다 «한 칸»이다(설계 0.4.2 §2-4).\n *\n * <p>역할은 더는 토큰 칸이 정하지 않는다. 역할은 저장소(init 이 쓴 --role)에서 와서 요청 헤더로 실린다.\n * 0.4.1 이하가 쓴 역할별 칸은 읽지도 지우지도 않는다(사용자 결정 «마이그레이션은 고려하지 마»).\n */\nexport interface TokenStore {\n load(endpoint: string): Promise<StoredTokenSet | undefined>;\n save(endpoint: string, token: StoredTokenSet): Promise<void>;\n delete(endpoint: string): Promise<void>;\n}\n\ntype TokenFile = Record<string, StoredTokenSet>;\n\nexport class FileTokenStore implements TokenStore {\n readonly filePath: string;\n\n constructor(rootDir = join(homedir(), \".ainecto\")) {\n this.filePath = join(rootDir, \"tokens.json\");\n }\n\n async load(endpoint: string): Promise<StoredTokenSet | undefined> {\n const file = await this.readFile();\n return file[tokenKey(endpoint)];\n }\n\n async save(endpoint: string, token: StoredTokenSet): Promise<void> {\n assertNoRoleScope(token);\n const file = await this.readFile();\n file[tokenKey(endpoint)] = { ...token, endpoint, updatedAt: new Date().toISOString() };\n await this.writeFile(file);\n }\n\n async delete(endpoint: string): Promise<void> {\n const file = await this.readFile();\n delete file[tokenKey(endpoint)];\n await this.writeFile(file);\n }\n\n private async readFile(): Promise<TokenFile> {\n try {\n await this.hardenExistingPermissions();\n const raw = await readFile(this.filePath, \"utf8\");\n const parsed = JSON.parse(raw) as unknown;\n if (!parsed || typeof parsed !== \"object\" || Array.isArray(parsed)) {\n throw new Error(\"Token file must contain an object.\");\n }\n return parsed as TokenFile;\n } catch (error) {\n if (isNodeError(error, \"ENOENT\")) {\n return {};\n }\n throw error;\n }\n }\n\n private async writeFile(file: TokenFile): Promise<void> {\n await mkdir(dirname(this.filePath), { recursive: true, mode: 0o700 });\n await chmod(dirname(this.filePath), 0o700);\n const tmpPath = `${this.filePath}.${process.pid}.${Date.now()}.tmp`;\n await writeFile(tmpPath, `${JSON.stringify(file, null, 2)}\\n`, { mode: 0o600 });\n await chmod(tmpPath, 0o600);\n await rename(tmpPath, this.filePath);\n await chmod(this.filePath, 0o600);\n }\n\n private async hardenExistingPermissions(): Promise<void> {\n const dirPath = dirname(this.filePath);\n await chmodIfPermissive(dirPath, 0o700);\n await chmodIfPermissive(this.filePath, 0o600);\n }\n}\n\n/** 서버 하나의 칸. 0.4.1 이하의 «역할 없는» 칸과 같은 키다 — 그때 역할 없이 로그인해 둔 사람은 그대로 쓴다. */\nexport function tokenKey(endpoint: string): string {\n return createHash(\"sha256\").update(endpoint).digest(\"hex\");\n}\n\n/**\n * ★역할 scope 가 든 토큰은 이 칸에 두지 않는다(설계 0.4.2 §2-4, 리뷰 P0-1).\n *\n * <p>토큰에 역할이 박혀 있으면 서버는 헤더보다 토큰을 믿는다. 그런 토큰이 이 한 칸에 들어가면 «모든»\n * 저장소에서 그 역할이 헤더를 이겨 역할이 뒤바뀐다. 지금은 그런 토큰을 받을 경로가 없지만(로그인에 역할을\n * 싣지 않는다), 저장 경계에서 막아 두면 새 경로가 생겨도 조용히 새지 않는다.\n */\nexport function assertNoRoleScope(token: Pick<StoredTokenSet, \"scope\">): void {\n if (token.scope?.split(/\\s+/).some((scope) => scope.startsWith(ROLE_SCOPE_PREFIX))) {\n throw new Error(\n \"Refusing to store a role-scoped access token: sign-in is one per machine and the role comes \"\n + \"from the repository. Run `ainecto auth login` again.\",\n );\n }\n}\n\nexport async function tokenStorePermissions(path: string): Promise<{ mode: number } | undefined> {\n try {\n const result = await stat(path);\n return { mode: result.mode & 0o777 };\n } catch (error) {\n if (isNodeError(error, \"ENOENT\")) {\n return undefined;\n }\n throw error;\n }\n}\n\nexport async function tokenStoreDirectoryPermissions(path: string): Promise<{ mode: number } | undefined> {\n return tokenStorePermissions(dirname(path));\n}\n\nexport async function removeTokenStoreForTests(path: string): Promise<void> {\n await rm(dirname(path), { recursive: true, force: true });\n}\n\nfunction isNodeError(error: unknown, code: string): error is NodeJS.ErrnoException {\n return error instanceof Error && \"code\" in error && error.code === code;\n}\n\nasync function chmodIfPermissive(path: string, mode: number): Promise<void> {\n try {\n const result = await stat(path);\n if ((result.mode & 0o077) !== 0) {\n await chmod(path, mode);\n }\n } catch (error) {\n if (isNodeError(error, \"ENOENT\")) {\n return;\n }\n throw error;\n }\n}\n","/**\n * 세션 역할 — AiNecto Harness 의 「하나의 세션은 하나의 역할만 가진다」를 클라이언트 쪽에서\n * 실어 나르는 부분.\n *\n * ★0.4.2: 역할의 «저장 위치»는 하나다 — init 이 저장소에 쓴 {@code .mcp.json}·{@code .cursor/mcp.json}\n * 의 {@code ainecto} 항목(0.4.x 가 쓴 {@code ai-erd} 항목도)의 {@code --role}(판정은 {@code repositoryRole.ts} 한 곳). 로그인은 서버마다\n * 한 번이고 토큰은 역할을 싣지 않는다. 역할은 요청마다 {@link ROLE_HEADER} 로 실리고, 서버는 그 헤더로\n * 도구 목록·거절·인자 판정을 전부 적용한다. 역할은 작업 가드레일이지 보안 경계가 아니다(2026-09-29\n * 사용자 결정 Q1) — 같은 토큰으로 헤더를 빼면 제한이 없다.\n *\n * - 브리지({@code ainecto mcp}): ① {@code --role} 인자(init 이 MCP 설정에 쓴다) ② {@code AINECTO_ROLE}\n * (옛 이름 {@code AI_ERD_ROLE} 도 읽는다)\n * (Codex 처럼 설정이 전역뿐인 에이전트). 세션 시작 때 정해지고 세션 동안 바뀌지 않는다.\n * - 셸 명령({@code ainecto tools call …} 등): 저장소 역할. ①② 를 주면 저장소 역할과 같아야 한다(다르면 거절).\n * - 저장소 밖: 역할 없음 = 제한 없음.\n *\n * ★에이전트가 «사용자의 답으로» 다음 세션의 역할을 적용하는 것은 허용한다(Q2): 고르는 것은 사람,\n * 적용({@code ainecto init --role <role>})은 에이전트. 떠 있는 세션의 역할은 바뀌지 않는다.\n *\n * ⛔«도구로 역할을 선언»하는 길은 택하지 않았다. 에이전트가 «세션 도중에» 스스로 바꿀 수 있으면\n * 제약이 아니다 — 막히는 순간 「역할을 바꿔서 하겠습니다」가 가장 자연스러운 다음 수가 된다.\n *\n * ⚠강제하는 쪽은 서버다. 이 파일은 역할을 «전달»만 한다.\n */\n\nexport const HARNESS_ROLES = [\"design\", \"development\", \"test\", \"validation\"] as const;\n\nexport type HarnessRole = (typeof HARNESS_ROLES)[number];\n\n/**\n * 역할을 싣던 OAuth scope 접두사(0.4.1 이하). 서버의 McpSessionRole.ROLE_SCOPE_PREFIX 와 같다.\n *\n * <p>0.4.2 부터 로그인은 이 scope 를 요구하지 않는다. 남은 쓰임은 하나 — 역할 없는 토큰 칸에\n * 이 scope 가 든 토큰을 «저장하지 않는» 가드({@code tokenStore.assertNoRoleScope}).\n */\n// ⛔이름을 바꾸지 않는다(2026-10-11 사용자 결정) — 서버·발급된 토큰과 맞물린 값이고 사람에게 안 보인다.\nexport const ROLE_SCOPE_PREFIX = \"ai-erd:role:\";\n\n/**\n * 서버가 읽는 헤더 이름. 서버의 McpSessionRoleResolver.ROLE_HEADER 와 같아야 한다.\n * ⛔이름을 바꾸지 않는다(2026-10-11 사용자 결정) — 사람에게 안 보이고, 옛 CLI 도 계속 이 헤더를 보낸다.\n */\nexport const ROLE_HEADER = \"X-AI-ERD-Role\";\n\n/** 설정이 전역뿐인 에이전트(Codex 등)를 위한 환경변수. */\nexport const ROLE_ENV_VAR = \"AINECTO_ROLE\";\n\n/**\n * 0.4.x 의 이름. ★계속 읽는다 — Codex 셸 프로필 등 사용자 기계에 이미 박혀 있다.\n * 둘 다 있으면 {@link ROLE_ENV_VAR} 가 이긴다(새 이름을 적은 쪽이 나중의 뜻이다). 그 값이 저장소 역할과\n * 어긋나면 셸 명령은 기존 «역할 충돌» 규칙대로 거절한다(ainectoCli.commandRole).\n */\nexport const LEGACY_ROLE_ENV_VAR = \"AI_ERD_ROLE\";\n\nexport interface RoleResolutionInput {\n role?: string;\n envVars?: NodeJS.ProcessEnv;\n}\n\nexport interface ResolvedRole {\n role?: HarnessRole;\n source: \"flag\" | \"env\" | \"none\";\n /** {@code source===\"env\"} 일 때 값을 읽은 환경변수 이름 — 거절 문장이 사용자가 실제로 둔 이름을 말하게. */\n envVar?: string;\n}\n\nexport function resolveRole(input: RoleResolutionInput = {}): ResolvedRole {\n if (input.role !== undefined) {\n return { role: assertRole(input.role), source: \"flag\" };\n }\n const envVars = input.envVars ?? process.env;\n for (const envVar of [ROLE_ENV_VAR, LEGACY_ROLE_ENV_VAR]) {\n const fromEnv = envVars[envVar];\n if (fromEnv && fromEnv.trim()) {\n return { role: assertRole(fromEnv), source: \"env\", envVar };\n }\n }\n return { source: \"none\" };\n}\n\n/**\n * ⚠여기서는 «틀리면 멈춘다». 서버는 모르는 값을 FULL 로 받아 넘기지만, 그건 남의 설정이\n * 조용히 죽지 않게 하려는 것이고 — 이쪽은 방금 사람이 친 값이라 오타를 알려주는 편이 낫다.\n * 조용히 «역할 없음»으로 도는 것이 이 도구에서 가장 나쁜 실패다.\n */\nexport function assertRole(value: string): HarnessRole {\n const normalized = value.trim().toLowerCase();\n if ((HARNESS_ROLES as readonly string[]).includes(normalized)) {\n return normalized as HarnessRole;\n }\n throw new Error(\n `Invalid role \"${value}\". Expected one of: ${HARNESS_ROLES.join(\", \")}.`,\n );\n}\n\n/** 사람이 읽는 이름. 서버의 거부 안내문과 같은 어휘를 쓴다. */\nexport function roleDisplayName(role: HarnessRole): string {\n return `${role.charAt(0).toUpperCase()}${role.slice(1)} Session`;\n}\n","import { createHash, randomBytes } from \"node:crypto\";\nimport { createServer } from \"node:http\";\nimport { spawn, type ChildProcess, type SpawnOptions } from \"node:child_process\";\nimport { URLSearchParams } from \"node:url\";\nimport type { StoredTokenSet, TokenStore } from \"./tokenStore\";\nimport { registerPublicClient, type ClientRegistrationResult } from \"./clientRegistration\";\nimport { assertTrustedEndpointUrl, fallbackTokenEndpointsFor, isDefaultEndpoint } from \"../config/endpoints\";\n\n/**\n * ★로그인은 서버마다 한 번이다(설계 0.4.2). 토큰은 역할을 싣지 않고, 역할은 저장소에서 와서\n * 요청 헤더로 실린다({@link McpRpcClient}). 그래서 이 클라이언트에는 역할이 없다.\n */\nexport interface OAuthClientOptions {\n endpoint: string;\n tokenStore: TokenStore;\n fetchImpl?: typeof fetch;\n envVars?: NodeJS.ProcessEnv;\n /**\n * 브라우저를 연다. <b>열기에 실패하면 거절</b>하고, 성공이 확인되면(또는 아직 모르면) 가만히 있는다.\n * login 은 이 약속을 «기다리지 않고» 콜백 대기와 경주시킨다 — 여는 명령이 브라우저가 닫힐 때까지\n * 안 끝나는 환경에서 순서대로 기다리면 교착한다.\n */\n openBrowser?: (url: string) => Promise<void>;\n /**\n * 인가 URL 을 «브라우저를 열기 전에» 알린다. CLI 는 stderr 에 한 줄 쓴다 — 브라우저가 안 열려도\n * 사람이 직접 열 수 있고, 에이전트가 실행했다면 명령이 끝날 때 그 줄이 에이전트에게 닿는다.\n */\n onAuthorizeUrl?: (url: string) => void;\n /** 콜백을 기다리는 한도. 기본 5분 — 사람이 로그인하기엔 넉넉하고, 에이전트 셸 도구 최대 한도보다 짧다. */\n loginTimeoutMs?: number;\n /**\n * 토큰 저장소를 «읽기만» 한다 — 갱신하지 않는다(네트워크도, 토큰 파일 쓰기도 없다).\n * {@code init --dry-run} 이 쓴다: 「아무것도 바꾸지 않는다」에 사용자의 로그인 상태도 든다.\n * 갱신이 필요한(만료된) 토큰은 «없음»으로 본다.\n */\n readOnly?: boolean;\n}\n\n/** 로그인 대기 기본 한도(5분). */\nexport const DEFAULT_LOGIN_TIMEOUT_MS = 300_000;\n\n/**\n * 로그인이 «사람 쪽 사정으로» 끝나지 못한 이유. {@code code} 는 {@code --json} 의 {@code error.code} 로\n * 그대로 나간다(종료 코드는 다른 실패와 같은 1 — 받는 쪽은 메시지를 읽고 사람에게 옮긴다).\n */\nexport class OAuthLoginError extends Error {\n constructor(readonly code: \"LOGIN_TIMEOUT\" | \"BROWSER_UNAVAILABLE\" | \"CALLBACK_UNAVAILABLE\", message: string) {\n super(message);\n this.name = \"OAuthLoginError\";\n }\n}\n\nexport interface OAuthMetadata {\n authorizationEndpoint: string;\n tokenEndpoint: string;\n registrationEndpoint?: string;\n issuer?: string;\n codeChallengeMethodsSupported: string[];\n}\n\nexport interface TokenProvider {\n getAccessToken(): Promise<string | undefined>;\n refreshAfterUnauthorized(): Promise<string | undefined>;\n}\n\nexport class OAuthClient implements TokenProvider {\n private readonly fetchImpl: typeof fetch;\n private readonly envVars: NodeJS.ProcessEnv;\n private readonly openBrowserImpl: (url: string) => Promise<void>;\n\n constructor(private readonly options: OAuthClientOptions) {\n this.fetchImpl = options.fetchImpl ?? fetch;\n this.envVars = options.envVars ?? process.env;\n this.openBrowserImpl = options.openBrowser ?? openBrowser;\n }\n\n async getAccessToken(): Promise<string | undefined> {\n const envToken = this.envVars.AINECTO_TOKEN;\n if (envToken) {\n assertEnvTokenEndpointAllowed(this.options.endpoint, this.envVars);\n return envToken;\n }\n\n const stored = await this.loadStored();\n if (!stored) {\n return undefined;\n }\n\n if (!shouldRefresh(stored)) {\n return stored.accessToken;\n }\n if (this.options.readOnly) {\n return isExpired(stored) ? undefined : stored.accessToken;\n }\n // ★만료됐는데 갱신할 수단이 없으면 «토큰 없음»이다. 예전엔 만료된 토큰을 그대로 돌려줘서,\n // init 은 로그인 대신 401 안내로 떨어졌고 다른 경로는 받을 게 뻔한 401 을 받으러 갔다\n // (2026-09-29 독립 리뷰 P1-2). 아직 60초 창 안이면 쓸 수 있으므로 돌려준다.\n if (!stored.refreshToken) {\n return isExpired(stored) ? undefined : stored.accessToken;\n }\n\n return this.refreshStoredToken({ ...stored, refreshToken: stored.refreshToken });\n }\n\n async refreshAfterUnauthorized(): Promise<string | undefined> {\n const stored = await this.loadStored();\n if (this.options.readOnly || !stored?.refreshToken) {\n return undefined;\n }\n return this.refreshStoredToken({ ...stored, refreshToken: stored.refreshToken });\n }\n\n async login(): Promise<StoredTokenSet> {\n const pkce = generatePkcePair();\n const state = generateOAuthState();\n const loopback = await createLoopbackReceiver(state);\n\n try {\n const metadata = await discoverOAuthMetadata(this.options.endpoint, this.fetchImpl);\n const registration = metadata.registrationEndpoint\n ? await registerPublicClient(metadata.registrationEndpoint, loopback.redirectUri, this.fetchImpl)\n : { clientId: \"ainecto-cli\" };\n const authorizeUrl = new URL(metadata.authorizationEndpoint);\n authorizeUrl.searchParams.set(\"response_type\", \"code\");\n authorizeUrl.searchParams.set(\"client_id\", registration.clientId);\n authorizeUrl.searchParams.set(\"redirect_uri\", loopback.redirectUri);\n authorizeUrl.searchParams.set(\"code_challenge\", pkce.codeChallenge);\n authorizeUrl.searchParams.set(\"code_challenge_method\", \"S256\");\n authorizeUrl.searchParams.set(\"resource\", this.options.endpoint);\n authorizeUrl.searchParams.set(\"state\", state);\n // ★역할을 scope 에 싣지 않는다(설계 0.4.2) — 로그인은 서버마다 한 번이고, 역할은 저장소에서\n // 요청 헤더로 온다. 역할은 작업 가드레일이지 보안 경계가 아니다(2026-09-29 사용자 결정 Q1).\n\n assertTrustedEndpointUrl(authorizeUrl, \"OAuth authorization URL\");\n const url = authorizeUrl.toString();\n this.options.onAuthorizeUrl?.(url);\n // ★셋 중 먼저 오는 것이 결과다 — 콜백 도착 / 브라우저 열기 실패 / 시간 한도.\n // 예전엔 한도가 없고 열기 실패를 못 봐서, 브라우저가 없는 기계에서 말없이 영원히 기다렸다\n // (2026-09-29 독립 리뷰 P1-2). 비TTY(에이전트가 실행)에서는 그 대기가 곧 멈춤이다.\n const browserFailure = this.openBrowserImpl(url).then(() => new Promise<never>(() => undefined));\n const code = await withTimeout(\n Promise.race([loopback.waitForCode(), browserFailure]),\n this.options.loginTimeoutMs ?? DEFAULT_LOGIN_TIMEOUT_MS,\n );\n const token = await exchangeToken(metadata.tokenEndpoint, {\n grant_type: \"authorization_code\",\n code,\n redirect_uri: loopback.redirectUri,\n client_id: registration.clientId,\n code_verifier: pkce.codeVerifier,\n resource: this.options.endpoint,\n ...(registration.clientSecret ? { client_secret: registration.clientSecret } : {}),\n }, this.fetchImpl);\n\n const stored = toStoredToken(this.options.endpoint, token, metadata, registration);\n await this.options.tokenStore.save(this.options.endpoint, stored);\n return stored;\n } finally {\n await loopback.close();\n }\n }\n\n /**\n * ★대체로 읽는 옛 칸도 함께 지운다. 새 칸만 지우면 다음 읽기가 곧바로 옛 칸으로 대체돼\n * «로그아웃했는데 여전히 로그인됨»이 된다. 로그인은 기계당 한 번이므로 옛 CLI 의 칸도 같이 끝난다.\n */\n async logout(): Promise<void> {\n await this.options.tokenStore.delete(this.options.endpoint);\n for (const fallback of fallbackTokenEndpointsFor(this.options.endpoint)) {\n await this.options.tokenStore.delete(fallback);\n }\n }\n\n /** 이 주소의 칸, 비었으면 옛 기본 주소의 칸({@link fallbackTokenEndpointsFor}). 쓰기는 늘 이 주소의 칸이다. */\n private async loadStored(): Promise<StoredTokenSet | undefined> {\n const own = await this.options.tokenStore.load(this.options.endpoint);\n if (own) {\n return own;\n }\n for (const fallback of fallbackTokenEndpointsFor(this.options.endpoint)) {\n const stored = await this.options.tokenStore.load(fallback);\n if (stored) {\n return stored;\n }\n }\n return undefined;\n }\n\n /** 부르는 쪽이 refresh token 이 있음을 이미 확인했다 — 타입이 그것을 요구한다. */\n private async refreshStoredToken(stored: StoredTokenSet & { refreshToken: string }): Promise<string | undefined> {\n const metadata = await discoverOAuthMetadata(this.options.endpoint, this.fetchImpl);\n const token = await exchangeToken(metadata.tokenEndpoint, {\n grant_type: \"refresh_token\",\n refresh_token: stored.refreshToken,\n client_id: stored.clientId ?? \"ainecto-cli\",\n ...(stored.clientSecret ? { client_secret: stored.clientSecret } : {}),\n resource: this.options.endpoint,\n }, this.fetchImpl);\n const next = toStoredToken(this.options.endpoint, token, metadata, stored);\n await this.options.tokenStore.save(this.options.endpoint, next);\n return next.accessToken;\n }\n}\n\nexport function generatePkcePair(): { codeVerifier: string; codeChallenge: string } {\n const codeVerifier = randomBytes(32).toString(\"base64url\");\n const codeChallenge = createHash(\"sha256\").update(codeVerifier).digest(\"base64url\");\n return { codeVerifier, codeChallenge };\n}\n\nexport function generateOAuthState(): string {\n return randomBytes(32).toString(\"base64url\");\n}\n\n/** 이미 만료됐는가. 만료 시각이 없으면 만료되지 않은 것으로 본다(서버가 수명을 안 알려 준 토큰). */\nexport function isExpired(token: Pick<StoredTokenSet, \"expiresAt\">, now = Date.now()): boolean {\n return token.expiresAt !== undefined && token.expiresAt <= now;\n}\n\nasync function withTimeout<T>(work: Promise<T>, timeoutMs: number): Promise<T> {\n let timer: NodeJS.Timeout | undefined;\n const timeout = new Promise<never>((_, reject) => {\n timer = setTimeout(() => {\n const minutes = timeoutMs / 60_000;\n const span = Number.isInteger(minutes) ? `${minutes} minute${minutes === 1 ? \"\" : \"s\"}` : `${timeoutMs} ms`;\n reject(new OAuthLoginError(\n \"LOGIN_TIMEOUT\",\n `Sign-in was not completed within ${span}. Nothing was saved — run the same command again.`,\n ));\n }, timeoutMs);\n });\n try {\n return await Promise.race([work, timeout]);\n } finally {\n clearTimeout(timer);\n }\n}\n\nexport function shouldRefresh(token: Pick<StoredTokenSet, \"expiresAt\">, now = Date.now()): boolean {\n if (!token.expiresAt) {\n return false;\n }\n return token.expiresAt - now < 60_000;\n}\n\nexport async function discoverOAuthMetadata(endpoint: string, fetchImpl: typeof fetch = fetch): Promise<OAuthMetadata> {\n assertTrustedEndpointUrl(endpoint, \"MCP endpoint\");\n const resourceMetadataUrl = await discoverResourceMetadataUrl(endpoint, fetchImpl);\n assertTrustedEndpointUrl(resourceMetadataUrl, \"OAuth protected resource metadata URL\");\n const resourceMetadata = await fetchJson<Record<string, unknown>>(resourceMetadataUrl, fetchImpl);\n const authorizationServers = resourceMetadata.authorization_servers;\n if (!Array.isArray(authorizationServers) || typeof authorizationServers[0] !== \"string\") {\n throw new Error(\"OAuth protected resource metadata did not include authorization_servers.\");\n }\n const authServer = authorizationServers[0];\n assertTrustedEndpointUrl(authServer, \"OAuth authorization server\");\n const metadataUrl = authServer.includes(\"/.well-known/\")\n ? authServer\n : `${authServer.replace(/\\/$/, \"\")}/.well-known/oauth-authorization-server`;\n assertTrustedEndpointUrl(metadataUrl, \"OAuth authorization server metadata URL\");\n const metadata = await fetchJson<Record<string, unknown>>(metadataUrl, fetchImpl);\n if (typeof metadata.authorization_endpoint !== \"string\" || typeof metadata.token_endpoint !== \"string\") {\n throw new Error(\"OAuth authorization server metadata is missing authorization_endpoint or token_endpoint.\");\n }\n assertTrustedEndpointUrl(metadata.authorization_endpoint, \"OAuth authorization endpoint\");\n assertTrustedEndpointUrl(metadata.token_endpoint, \"OAuth token endpoint\");\n if (typeof metadata.registration_endpoint === \"string\") {\n assertTrustedEndpointUrl(metadata.registration_endpoint, \"OAuth client registration endpoint\");\n }\n if (typeof metadata.issuer === \"string\") {\n assertTrustedEndpointUrl(metadata.issuer, \"OAuth issuer\");\n }\n const codeChallengeMethodsSupported = parseStringArray(metadata.code_challenge_methods_supported);\n if (!codeChallengeMethodsSupported.includes(\"S256\")) {\n throw new Error(\"OAuth authorization server metadata must support PKCE S256.\");\n }\n return {\n authorizationEndpoint: metadata.authorization_endpoint,\n tokenEndpoint: metadata.token_endpoint,\n registrationEndpoint: typeof metadata.registration_endpoint === \"string\" ? metadata.registration_endpoint : undefined,\n issuer: typeof metadata.issuer === \"string\" ? metadata.issuer : undefined,\n codeChallengeMethodsSupported,\n };\n}\n\nasync function discoverResourceMetadataUrl(endpoint: string, fetchImpl: typeof fetch): Promise<string> {\n const challengeUrl = new URL(endpoint);\n assertTrustedEndpointUrl(challengeUrl, \"MCP endpoint\");\n const response = await fetchImpl(challengeUrl, { method: \"GET\" });\n const challenge = response.headers.get(\"www-authenticate\");\n const fromChallenge = challenge ? parseBearerChallengeParam(challenge, \"resource_metadata\") : undefined;\n if (fromChallenge) {\n assertTrustedEndpointUrl(fromChallenge, \"OAuth protected resource metadata URL\");\n return fromChallenge;\n }\n const origin = new URL(endpoint).origin;\n const fallback = `${origin}/.well-known/oauth-protected-resource`;\n assertTrustedEndpointUrl(fallback, \"OAuth protected resource metadata URL\");\n return fallback;\n}\n\nexport function parseBearerChallengeParam(header: string, name: string): string | undefined {\n const pattern = new RegExp(`${name}=\"([^\"]+)\"`);\n return pattern.exec(header)?.[1];\n}\n\nasync function exchangeToken(\n tokenEndpoint: string,\n params: Record<string, string>,\n fetchImpl: typeof fetch,\n): Promise<Record<string, unknown>> {\n assertTrustedEndpointUrl(tokenEndpoint, \"OAuth token endpoint\");\n const response = await fetchImpl(tokenEndpoint, {\n method: \"POST\",\n headers: { \"content-type\": \"application/x-www-form-urlencoded\" },\n body: new URLSearchParams(params),\n });\n if (!response.ok) {\n throw new Error(`OAuth token exchange failed with HTTP ${response.status}.`);\n }\n return response.json() as Promise<Record<string, unknown>>;\n}\n\ntype TokenContext = Partial<Pick<StoredTokenSet, \"refreshToken\" | \"tokenType\" | \"expiresAt\" | \"scope\" | \"clientId\" | \"clientSecret\">>\n & Partial<ClientRegistrationResult>;\n\nfunction toStoredToken(\n endpoint: string,\n token: Record<string, unknown>,\n metadata: OAuthMetadata,\n previous?: TokenContext,\n): StoredTokenSet {\n if (typeof token.access_token !== \"string\") {\n throw new Error(\"OAuth token response did not include access_token.\");\n }\n const expiresIn = typeof token.expires_in === \"number\" ? token.expires_in : undefined;\n return {\n endpoint,\n accessToken: token.access_token,\n refreshToken: typeof token.refresh_token === \"string\" ? token.refresh_token : previous?.refreshToken,\n tokenType: typeof token.token_type === \"string\" ? token.token_type : previous?.tokenType,\n expiresAt: expiresIn ? Date.now() + expiresIn * 1000 : previous?.expiresAt,\n scope: typeof token.scope === \"string\" ? token.scope : previous?.scope,\n clientId: previous?.clientId ?? \"ainecto-cli\",\n clientSecret: previous?.clientSecret,\n authorizationServer: metadata.issuer,\n updatedAt: new Date().toISOString(),\n };\n}\n\n/**\n * 로그인 콜백을 받는 loopback 서버. {@code host} 는 시험이 «바인딩 실패»를 만들 때만 바꾼다.\n */\nexport async function createLoopbackReceiver(expectedState: string, host = \"127.0.0.1\"): Promise<{\n redirectUri: string;\n waitForCode(): Promise<string>;\n close(): Promise<void>;\n}> {\n let resolveCode!: (code: string) => void;\n let rejectCode!: (error: Error) => void;\n const codePromise = new Promise<string>((resolve, reject) => {\n resolveCode = resolve;\n rejectCode = reject;\n });\n codePromise.catch(() => undefined);\n\n const server = createServer((req, res) => {\n const url = new URL(req.url ?? \"/\", \"http://127.0.0.1\");\n const error = url.searchParams.get(\"error\");\n const code = url.searchParams.get(\"code\");\n const state = url.searchParams.get(\"state\");\n if (error) {\n res.writeHead(400, { \"content-type\": \"text/plain\" });\n res.end(\"AiNecto CLI authorization failed. You can close this tab.\");\n rejectCode(new Error(`OAuth authorization failed: ${error}`));\n return;\n }\n if (!state || state !== expectedState) {\n res.writeHead(400, { \"content-type\": \"text/plain\" });\n res.end(\"AiNecto CLI authorization state mismatch. You can close this tab.\");\n rejectCode(new Error(\"OAuth state mismatch.\"));\n return;\n }\n if (!code) {\n res.writeHead(400, { \"content-type\": \"text/plain\" });\n res.end(\"Missing OAuth code.\");\n rejectCode(new Error(\"OAuth redirect did not include a code.\"));\n return;\n }\n res.writeHead(200, { \"content-type\": \"text/plain\" });\n res.end(\"AiNecto CLI authorization complete. You can close this tab.\");\n resolveCode(code);\n });\n\n // ★바인딩이 막히면(에이전트 sandbox 등) 예전엔 error 리스너가 없어 처리되지 않은 예외로 죽었다\n // (2026-09-29 코드 리뷰 P1). 사람이 할 일을 말하는 오류로 바꾼다.\n await new Promise<void>((resolve, reject) => {\n server.once(\"error\", (error) => reject(new OAuthLoginError(\n \"CALLBACK_UNAVAILABLE\",\n `Could not start the local sign-in callback on ${host} (${error.message}). `\n + \"If this command runs in a sandbox, allow local network binding or run it outside the sandbox.\",\n )));\n server.listen(0, host, () => resolve());\n });\n const address = server.address();\n if (!address || typeof address === \"string\") {\n throw new Error(\"Loopback OAuth server did not bind to a TCP port.\");\n }\n return {\n redirectUri: `http://127.0.0.1:${address.port}/callback`,\n waitForCode: () => codePromise,\n close: () => new Promise<void>((resolve, reject) => {\n // Node 18 은 브라우저가 붙들고 있는 keep-alive 연결이 끝날 때까지 close 가 늦어진다.\n server.closeAllConnections?.();\n server.close((error) => error ? reject(error) : resolve());\n }),\n };\n}\n\nfunction parseStringArray(value: unknown): string[] {\n return Array.isArray(value) ? value.filter((item): item is string => typeof item === \"string\") : [];\n}\n\nasync function fetchJson<T>(url: string, fetchImpl: typeof fetch): Promise<T> {\n assertTrustedEndpointUrl(url, \"OAuth metadata URL\");\n const response = await fetchImpl(url);\n if (!response.ok) {\n throw new Error(`Failed to fetch OAuth metadata from ${url}: HTTP ${response.status}.`);\n }\n return response.json() as Promise<T>;\n}\n\nexport function getBrowserOpenCommand(\n url: string,\n platform: NodeJS.Platform = process.platform,\n): { command: string; args: string[] } {\n assertTrustedEndpointUrl(url, \"OAuth authorization URL\");\n if (platform === \"darwin\") {\n return { command: \"open\", args: [url] };\n }\n if (platform === \"win32\") {\n return { command: \"explorer.exe\", args: [url] };\n }\n return { command: \"xdg-open\", args: [url] };\n}\n\nexport interface LaunchBrowserDeps {\n platform?: NodeJS.Platform;\n spawnImpl?: (command: string, args: string[], options: SpawnOptions) => ChildProcess;\n}\n\n/**\n * 기본 브라우저 열기. <b>여는 명령이 없거나(ENOENT) 0 이 아닌 코드로 끝나면 거절</b>한다.\n *\n * <p>★예전엔 {@code error} 리스너가 없어 명령이 없으면 처리되지 않은 오류로 죽었고, 명령은 있는데\n * 브라우저가 없으면({@code xdg-open} 비0 종료) 아무 말 없이 콜백을 영원히 기다렸다\n * (2026-09-29 독립 리뷰 P1-2).\n *\n * <p>⚠<b>Windows 는 종료 코드를 보지 않는다.</b> {@code explorer.exe} 는 성공해도 1 로 끝나는 것으로\n * 알려져 있어, 그 코드로 판정하면 정상 환경을 실패로 만든다 — 명령이 «떴다»는 것만 본다\n * (설계 §8-1, Windows 실측 V14 전까지 추정).\n */\nexport function launchBrowser(url: string, deps: LaunchBrowserDeps = {}): Promise<void> {\n const platform = deps.platform ?? process.platform;\n const { command, args } = getBrowserOpenCommand(url, platform);\n const spawnImpl = deps.spawnImpl ?? spawn;\n return new Promise<void>((resolve, reject) => {\n const unavailable = (reason: string) => new OAuthLoginError(\n \"BROWSER_UNAVAILABLE\",\n `Could not open a browser on this machine (${command}: ${reason}). Sign-in needs a browser here.`,\n );\n let child: ChildProcess;\n try {\n child = spawnImpl(command, args, { stdio: \"ignore\", detached: true });\n } catch (error) {\n reject(unavailable(error instanceof Error ? error.message : String(error)));\n return;\n }\n child.once(\"error\", (error) => reject(unavailable(error.message)));\n if (platform === \"win32\") {\n child.once(\"spawn\", () => resolve());\n } else {\n child.once(\"exit\", (code, signal) => {\n if (code === 0) {\n resolve();\n } else {\n reject(unavailable(code === null ? `stopped by ${signal ?? \"a signal\"}` : `exited with code ${code}`));\n }\n });\n }\n child.unref();\n });\n}\n\nfunction openBrowser(url: string): Promise<void> {\n return launchBrowser(url);\n}\n\nfunction assertEnvTokenEndpointAllowed(endpoint: string, envVars: NodeJS.ProcessEnv): void {\n if (envVars.AINECTO_ALLOW_CUSTOM_ENDPOINT_TOKEN === \"1\") {\n return;\n }\n if (isDefaultEndpoint(endpoint)) {\n return;\n }\n 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.\");\n}\n","import { assertLoopbackRedirectUrl, assertTrustedEndpointUrl } from \"../config/endpoints\";\n\nexport interface ClientRegistrationResult {\n clientId: string;\n clientSecret?: string;\n}\n\nexport async function registerPublicClient(\n registrationEndpoint: string,\n redirectUri: string,\n fetchImpl: typeof fetch = fetch,\n): Promise<ClientRegistrationResult> {\n assertTrustedEndpointUrl(registrationEndpoint, \"OAuth client registration endpoint\");\n assertLoopbackRedirectUrl(redirectUri);\n const response = await fetchImpl(registrationEndpoint, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\", accept: \"application/json\" },\n body: JSON.stringify({\n client_name: \"AiNecto CLI\",\n redirect_uris: [redirectUri],\n token_endpoint_auth_method: \"none\",\n grant_types: [\"authorization_code\", \"refresh_token\"],\n response_types: [\"code\"],\n }),\n });\n if (!response.ok) {\n throw new Error(`OAuth client registration failed with HTTP ${response.status}.`);\n }\n const body = await response.json() as unknown;\n if (!isRecord(body) || typeof body.client_id !== \"string\") {\n throw new Error(\"OAuth client registration response did not include client_id.\");\n }\n return {\n clientId: body.client_id,\n clientSecret: typeof body.client_secret === \"string\" ? body.client_secret : undefined,\n };\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n","/**\n * CLI 버전. ★진실원은 {@code package.json} 이고 빌드(tsup define)가 그 값을 박는다 —\n * 상수로 또 적으면 릴리스 때 한쪽만 올라가 조용히 거짓이 된다.\n * 번들을 거치지 않는 실행(vitest·tsx)에서는 값이 없으므로 \"dev\" 로 떨어진다.\n */\ndeclare const __AINECTO_CLI_VERSION__: string | undefined;\n\nexport const CLI_VERSION: string =\n typeof __AINECTO_CLI_VERSION__ === \"string\" ? __AINECTO_CLI_VERSION__ : \"dev\";\n\n/**\n * npm 주 이름. ★이 이름을 박는 곳은 여기 하나다 — init 이 쓰는 {@code npx -y <이름> --role …},\n * MCP {@code clientInfo}, User-Agent, init 기록의 {@code generatedBy} 가 모두 이것을 쓴다.\n * {@code package.json} 의 name·{@code server.json} 의 패키지 식별자와 같아야 한다(시험이 대조한다).\n *\n * <p>옛 이름 {@code @ai-erd/mcp}·{@code ai-erd}·{@code @ai-erd/cli} 에도 같은 코드가 올라가지만\n * (scripts/publish.mjs), 그 판으로 init 해도 새 이름을 쓴다 — 새 저장소 설정이 옛 이름에 묶이지 않게.\n */\nexport const PACKAGE_NAME = \"@ainecto/mcp\";\n\n/** 0.4.x 의 주 이름. 이미 깔린 저장소의 MCP 설정에 남아 있다 — «우리 브리지»로 알아볼 때만 쓴다. */\nexport const LEGACY_PACKAGE_NAME = \"@ai-erd/mcp\";\n\nexport const CLI_USER_AGENT = `${PACKAGE_NAME} ${CLI_VERSION}`;\n","import type { TokenProvider } from \"../auth/oauth\";\nimport { ROLE_HEADER, type HarnessRole } from \"../harness/role\";\nimport { CLI_VERSION, PACKAGE_NAME } from \"../version\";\n\nexport interface JsonRpcRequest {\n jsonrpc: \"2.0\";\n id?: string | number | null;\n method: string;\n params?: unknown;\n}\n\nexport interface JsonRpcSuccess {\n jsonrpc: \"2.0\";\n id?: string | number | null;\n result: unknown;\n}\n\nexport interface JsonRpcFailure {\n jsonrpc: \"2.0\";\n id?: string | number | null;\n error: {\n code: number;\n message: string;\n data?: unknown;\n };\n}\n\nexport type JsonRpcResponse = JsonRpcSuccess | JsonRpcFailure;\n\nexport interface McpRpcClientOptions {\n endpoint: string;\n tokenProvider?: TokenProvider;\n fetchImpl?: typeof fetch;\n envVars?: NodeJS.ProcessEnv;\n /**\n * 세션 역할. ★필수다 — 역할이 없으면 {@code null} 을 «명시»한다(설계 0.4.2 §2-3, 리뷰 P2).\n * 저장소 안에서 이것을 빠뜨리면 요청이 조용히 «제한 없음»으로 간다. 그 빠뜨림을 타입이 잡는다.\n * 값이 있으면 모든 요청의 {@code X-AI-ERD-Role} 헤더로 실린다 — 강제는 서버가 한다.\n */\n role: HarnessRole | null;\n}\n\nexport class McpRpcError extends Error {\n constructor(\n message: string,\n readonly code: string,\n readonly details?: unknown,\n ) {\n super(message);\n }\n}\n\nexport class McpRpcClient {\n private readonly fetchImpl: typeof fetch;\n private nextId = 1;\n\n constructor(private readonly options: McpRpcClientOptions) {\n this.fetchImpl = options.fetchImpl ?? fetch;\n }\n\n initialize(): Promise<unknown> {\n return this.request(\"initialize\", {\n protocolVersion: \"2024-11-05\",\n capabilities: {},\n clientInfo: { name: PACKAGE_NAME, version: CLI_VERSION },\n });\n }\n\n async toolsList(): Promise<unknown[]> {\n const result = await this.request(\"tools/list\", {});\n if (!isRecord(result) || !Array.isArray(result.tools)) {\n throw new McpRpcError(\"tools/list response did not include a tools array.\", \"MCP_PROTOCOL_ERROR\", result);\n }\n return result.tools;\n }\n\n async toolsCall(name: string, args: unknown): Promise<unknown> {\n return this.request(\"tools/call\", { name, arguments: args ?? {} });\n }\n\n async request(method: string, params?: unknown): Promise<unknown> {\n const response = await this.sendRaw({\n jsonrpc: \"2.0\",\n id: this.nextId++,\n method,\n params,\n });\n if (!response) {\n return undefined;\n }\n if (\"error\" in response) {\n throw new McpRpcError(response.error.message, \"MCP_ERROR\", response.error);\n }\n return response.result;\n }\n\n async sendRaw(request: JsonRpcRequest): Promise<JsonRpcResponse | undefined> {\n const token = await this.options.tokenProvider?.getAccessToken();\n let response = await this.postJson(request, token);\n if (response.status === 401 && this.options.tokenProvider) {\n const refreshed = await this.options.tokenProvider.refreshAfterUnauthorized();\n if (refreshed) {\n response = await this.postJson(request, refreshed);\n }\n }\n\n if (response.status === 204 || response.status === 202) {\n return undefined;\n }\n const text = await response.text();\n if (!response.ok) {\n throw new McpRpcError(\n `MCP HTTP request failed with HTTP ${response.status}.`,\n \"MCP_HTTP_ERROR\",\n httpErrorDetails(response.status, text, this.options.envVars ?? process.env),\n );\n }\n if (!text.trim()) {\n return undefined;\n }\n const parsed = JSON.parse(text) as unknown;\n if (!isJsonRpcResponse(parsed)) {\n throw new McpRpcError(\"MCP response was not a JSON-RPC response.\", \"MCP_PROTOCOL_ERROR\", parsed);\n }\n return parsed;\n }\n\n private postJson(request: JsonRpcRequest, token: string | undefined): Promise<Response> {\n const headers: Record<string, string> = {\n \"content-type\": \"application/json\",\n accept: \"application/json\",\n };\n if (token) {\n headers.authorization = `Bearer ${token}`;\n }\n if (this.options.role) {\n headers[ROLE_HEADER.toLowerCase()] = this.options.role;\n }\n return this.fetchImpl(this.options.endpoint, {\n method: \"POST\",\n headers,\n body: JSON.stringify(request),\n });\n }\n}\n\n/**\n * ★MCP 는 «도구가 거부/실패했다»를 JSON-RPC error 가 아니라 결과 안의 {@code isError} 로\n * 알린다(2025-06-18 규격). 그래서 최상위 error 만 보면 실패가 성공으로 보고된다 —\n * 셸 자동화는 exit 0 을 받고 다음 단계로 간다(2026-09-22 독립 리뷰 I9).\n */\nexport function isToolError(result: unknown): boolean {\n return isRecord(result) && result.isError === true;\n}\n\nfunction isJsonRpcResponse(value: unknown): value is JsonRpcResponse {\n return isRecord(value)\n && value.jsonrpc === \"2.0\"\n && (\"result\" in value || \"error\" in value);\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction httpErrorDetails(status: number, body: string, envVars: NodeJS.ProcessEnv): Record<string, unknown> {\n const details: Record<string, unknown> = { status };\n if (envVars.AINECTO_CLI_DEBUG === \"1\" || envVars.AINECTO_DEBUG === \"1\") {\n details.body = safeBodyPreview(body);\n }\n return details;\n}\n\nexport function safeBodyPreview(body: string): string {\n return body.length > 500 ? `${body.slice(0, 500)}...` : body;\n}\n","import { createInterface } from \"node:readline\";\nimport type { Readable, Writable } from \"node:stream\";\nimport { McpRpcClient, type JsonRpcRequest } from \"./rpcClient\";\n\nexport interface StdioBridgeOptions {\n input: Readable;\n output: Writable;\n errorOutput: Writable;\n client: McpRpcClient;\n}\n\nexport async function runStdioBridge(options: StdioBridgeOptions): Promise<void> {\n const lines = createInterface({ input: options.input, crlfDelay: Infinity });\n\n for await (const line of lines) {\n if (!line.trim()) {\n continue;\n }\n try {\n const request = parseJsonRpcRequest(line);\n const response = await options.client.sendRaw(request);\n if (response && request.id !== undefined) {\n options.output.write(`${JSON.stringify(response)}\\n`);\n }\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n options.output.write(`${JSON.stringify({\n jsonrpc: \"2.0\",\n id: null,\n error: { code: -32603, message },\n })}\\n`);\n }\n }\n}\n\nfunction parseJsonRpcRequest(line: string): JsonRpcRequest {\n const parsed = JSON.parse(line) as unknown;\n if (!isRecord(parsed) || parsed.jsonrpc !== \"2.0\" || typeof parsed.method !== \"string\") {\n throw new Error(\"Invalid JSON-RPC request.\");\n }\n return {\n jsonrpc: \"2.0\",\n id: typeof parsed.id === \"string\" || typeof parsed.id === \"number\" || parsed.id === null ? parsed.id : undefined,\n method: parsed.method,\n params: parsed.params,\n };\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n","import { FileTokenStore } from \"../../core/auth/tokenStore\";\nimport { OAuthClient } from \"../../core/auth/oauth\";\nimport { McpRpcClient } from \"../../core/mcp/rpcClient\";\nimport { runStdioBridge } from \"../../core/mcp/stdioBridge\";\nimport type { HarnessRole } from \"../../core/harness/role\";\nimport type { Readable, Writable } from \"node:stream\";\n\nexport interface ConnectorOptions {\n endpoint: string;\n /** 브리지의 역할 — MCP 설정의 `--role` 인자나 `AINECTO_ROLE`(옛 `AI_ERD_ROLE`) 에서만 온다. 없으면 null. */\n role: HarnessRole | null;\n input: Readable;\n output: Writable;\n errorOutput: Writable;\n}\n\nexport async function runConnector(options: ConnectorOptions): Promise<void> {\n // ★토큰은 서버마다 한 칸이다(설계 0.4.2 §2-4). 역할은 토큰이 아니라 요청 헤더로 실린다 —\n // 브리지의 역할은 시작할 때 인자로 정해지고 세션 동안 바뀌지 않는다.\n const auth = new OAuthClient({ endpoint: options.endpoint, tokenStore: new FileTokenStore() });\n const client = new McpRpcClient({\n endpoint: options.endpoint,\n tokenProvider: auth,\n role: options.role,\n });\n await runStdioBridge({\n input: options.input,\n output: options.output,\n errorOutput: options.errorOutput,\n client,\n });\n}\n","export interface CliErrorShape {\n ok: false;\n error: {\n code: string;\n message: string;\n details?: unknown;\n };\n}\n\nexport interface CliSuccessShape {\n ok: true;\n data: unknown;\n warnings?: string[];\n}\n\nexport function renderSuccess(data: unknown, options: { json?: boolean; warnings?: string[] } = {}): string {\n if (options.json) {\n const shape: CliSuccessShape = { ok: true, data };\n if (options.warnings?.length) {\n shape.warnings = options.warnings;\n }\n return `${JSON.stringify(shape, null, 2)}\\n`;\n }\n const warnings = options.warnings?.length ? `${options.warnings.map((warning) => `Warning: ${warning}`).join(\"\\n\")}\\n` : \"\";\n return `${warnings}${renderHuman(data)}\\n`;\n}\n\nexport function renderError(error: unknown, options: { json?: boolean } = {}): string {\n const shape = toErrorShape(error);\n if (options.json) {\n return `${JSON.stringify(shape, null, 2)}\\n`;\n }\n return `Error: ${shape.error.message}\\n`;\n}\n\nexport function toErrorShape(error: unknown): CliErrorShape {\n if (error instanceof Error) {\n const maybe = error as Error & { code?: string; details?: unknown };\n return {\n ok: false,\n error: {\n code: maybe.code ?? \"CLI_ERROR\",\n message: error.message,\n details: redactSecrets(maybe.details),\n },\n };\n }\n return { ok: false, error: { code: \"CLI_ERROR\", message: String(error) } };\n}\n\nexport function redactSecrets(value: unknown): unknown {\n return redactSecretsInner(value, new WeakSet<object>());\n}\n\nfunction renderHuman(data: unknown): string {\n if (Array.isArray(data)) {\n return data.map((item) => renderHuman(item)).join(\"\\n\");\n }\n if (typeof data === \"string\") {\n return data;\n }\n return JSON.stringify(data, null, 2);\n}\n\nfunction redactSecretsInner(value: unknown, seen: WeakSet<object>): unknown {\n if (typeof value === \"string\") {\n return redactSecretString(value);\n }\n if (Array.isArray(value)) {\n return value.map((item) => redactSecretsInner(item, seen));\n }\n if (value && typeof value === \"object\") {\n if (seen.has(value)) {\n return \"[circular]\";\n }\n seen.add(value);\n const output: Record<string, unknown> = {};\n for (const [key, child] of Object.entries(value)) {\n output[key] = isSecretKey(key) ? \"[redacted]\" : redactSecretsInner(child, seen);\n }\n return output;\n }\n return value;\n}\n\nfunction isSecretKey(key: string): boolean {\n const normalized = key.toLowerCase().replace(/-/g, \"_\");\n return normalized === \"authorization\"\n || normalized.includes(\"access_token\")\n || normalized.includes(\"refresh_token\")\n || normalized.includes(\"client_secret\")\n || normalized.includes(\"token\");\n}\n\nfunction redactSecretString(value: string): string {\n return value\n .replace(/(\"(?:authorization|access_token|refresh_token|client_secret|token)\"\\s*:\\s*\")([^\"]*)(\")/gi, \"$1[redacted]$3\")\n .replace(/((?:authorization|access_token|refresh_token|client_secret|token)=)([^&\\s\"']+)/gi, \"$1[redacted]\")\n .replace(/(authorization:\\s*bearer\\s+)([^\\s,}]+)/gi, \"$1[redacted]\")\n .replace(/(bearer\\s+)(eyJ[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+)/gi, \"$1[redacted]\");\n}\n","import { assertEnv, type AinectoEnv } from \"../../core/config/endpoints\";\nimport { assertRole, type HarnessRole } from \"../../core/harness/role\";\n\nexport interface McpBinArgs {\n env?: AinectoEnv;\n endpoint?: string;\n role?: HarnessRole;\n help: boolean;\n}\n\nexport function parseMcpArgs(argv: string[]): McpBinArgs {\n const result: McpBinArgs = { help: false };\n for (let index = 0; index < argv.length; index += 1) {\n const arg = argv[index];\n if (!arg) {\n continue;\n }\n if (arg === \"--env\") {\n result.env = assertEnv(requireFlagValue(arg, argv[++index]));\n } else if (arg === \"--endpoint\") {\n result.endpoint = requireFlagValue(arg, argv[++index]);\n } else if (arg === \"--role\") {\n result.role = assertRole(requireFlagValue(arg, argv[++index]));\n } else if (arg === \"--help\" || arg === \"-h\") {\n result.help = true;\n } else {\n throw new Error(`Unknown argument: ${arg}`);\n }\n }\n return result;\n}\n\nfunction requireFlagValue(flag: string, value: string | undefined): string {\n if (!value || value.startsWith(\"--\")) {\n throw new Error(`${flag} requires a value.`);\n }\n return value;\n}\n","#!/usr/bin/env node\nimport { resolveEndpoint } from \"../core/config/endpoints\";\nimport { runConnector } from \"../adapters/mcp/connector\";\nimport { renderError } from \"../core/output/render\";\nimport { parseMcpArgs } from \"../adapters/mcp/args\";\nimport { resolveRole, HARNESS_ROLES } from \"../core/harness/role\";\n\ntry {\n const args = parseMcpArgs(process.argv.slice(2));\n if (args.help) {\n process.stdout.write(\n `Usage: mcp [--env prod|dev] [--endpoint URL] [--role ${HARNESS_ROLES.join(\"|\")}]\\n`,\n );\n process.exit(0);\n }\n const resolved = resolveEndpoint({ env: args.env, endpoint: args.endpoint });\n // 플래그가 없으면 환경변수 — Codex 처럼 MCP 설정이 전역뿐인 에이전트를 위한 길이다.\n const resolvedRole = resolveRole({ role: args.role });\n await runConnector({\n endpoint: resolved.endpoint,\n role: resolvedRole.role ?? null,\n input: process.stdin,\n output: process.stdout,\n errorOutput: process.stderr,\n });\n} catch (error) {\n process.stderr.write(renderError(error));\n process.exitCode = 1;\n}\n"],"mappings":"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","names":["createHash","createHash","isRecord","isRecord","isRecord"]}
1
+ {"version":3,"sources":["../../src/core/config/endpoints.ts","../../src/core/auth/tokenStore.ts","../../src/core/harness/role.ts","../../src/core/auth/oauth.ts","../../src/core/auth/clientRegistration.ts","../../src/core/version.ts","../../src/core/mcp/rpcClient.ts","../../src/core/mcp/stdioBridge.ts","../../src/adapters/mcp/connector.ts","../../src/core/output/render.ts","../../src/adapters/mcp/args.ts","../../src/bin/mcp.ts"],"sourcesContent":["export type AinectoEnv = \"prod\" | \"dev\";\n\nexport interface EndpointResolutionInput {\n env?: AinectoEnv;\n endpoint?: string;\n envVars?: NodeJS.ProcessEnv;\n}\n\nexport interface ResolvedEndpoint {\n env: AinectoEnv;\n endpoint: string;\n source: \"flag\" | \"env\" | \"default\";\n}\n\n/**\n * 기본 MCP 엔드포인트.\n *\n * OAuth 메타데이터(issuer·resource·authorization_servers)도 ainecto.com 이다.\n */\nconst DEFAULT_ENDPOINTS: Record<AinectoEnv, string> = {\n prod: \"https://ainecto.com/mcp\",\n dev: \"https://dev.ainecto.com/mcp\",\n};\n\nexport function resolveEndpoint(input: EndpointResolutionInput = {}): ResolvedEndpoint {\n const envVars = input.envVars ?? process.env;\n const env = input.env ?? \"prod\";\n\n if (input.endpoint) {\n return { env, endpoint: normalizeEndpoint(input.endpoint), source: \"flag\" };\n }\n\n if (envVars.AINECTO_MCP_ENDPOINT) {\n return { env, endpoint: normalizeEndpoint(envVars.AINECTO_MCP_ENDPOINT), source: \"env\" };\n }\n\n return { env, endpoint: DEFAULT_ENDPOINTS[env], source: \"default\" };\n}\n\nexport function normalizeEndpoint(endpoint: string): string {\n const url = new URL(endpoint);\n assertTrustedEndpointUrl(url, \"MCP endpoint\");\n url.hash = \"\";\n return url.toString().replace(/\\/$/, \"\");\n}\n\n/**\n * ★<b>이 env 의 기본 주소와 «정확히» 같은가.</b>\n *\n * <p>{@link isDefaultEndpoint} 는 prod·dev 를 둘 다 «기본»으로 보는 넓은 판정이라,\n * 「env=prod 인데 endpoint 만 dev」 같은 입력을 기본으로 착각한다. 그 판정으로 설정에서\n * {@code --endpoint} 를 빼면, 조회한 주소와 세션이 붙는 주소가 갈라진다\n * (2026-09-23 독립 재리뷰 I7).\n */\nexport function isDefaultEndpointFor(env: AinectoEnv, endpoint: string): boolean {\n return normalizeEndpoint(endpoint) === DEFAULT_ENDPOINTS[env];\n}\n\nexport function isDefaultEndpoint(endpoint: string): boolean {\n const normalized = normalizeEndpoint(endpoint);\n return Object.values(DEFAULT_ENDPOINTS).includes(normalized);\n}\n\nexport function assertTrustedEndpointUrl(value: string | URL, label = \"URL\"): void {\n const url = typeof value === \"string\" ? new URL(value) : value;\n if (url.protocol === \"https:\") {\n return;\n }\n if (url.protocol === \"http:\" && isLoopbackHostname(url.hostname)) {\n return;\n }\n throw new Error(`${label} must use https, except http is allowed for localhost loopback endpoints.`);\n}\n\nexport function assertLoopbackRedirectUrl(value: string): void {\n const url = new URL(value);\n if (url.protocol === \"http:\" && isLoopbackHostname(url.hostname)) {\n return;\n }\n throw new Error(\"OAuth redirect URI must be an http localhost loopback URL.\");\n}\n\nfunction isLoopbackHostname(hostname: string): boolean {\n return hostname === \"localhost\"\n || hostname === \"127.0.0.1\"\n || hostname === \"::1\"\n || hostname === \"[::1]\";\n}\n\nexport function assertEnv(value: string | undefined): AinectoEnv | undefined {\n if (value === undefined) {\n return undefined;\n }\n if (value === \"prod\" || value === \"dev\") {\n return value;\n }\n throw new Error(`Invalid --env value \"${value}\". Expected \"prod\" or \"dev\".`);\n}\n","import { createHash } from \"node:crypto\";\nimport { mkdir, readFile, rename, rm, stat, writeFile, chmod } from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\nimport { ROLE_SCOPE_PREFIX } from \"../harness/role\";\n\nexport interface StoredTokenSet {\n endpoint: string;\n accessToken: string;\n refreshToken?: string;\n tokenType?: string;\n expiresAt?: number;\n scope?: string;\n clientId?: string;\n clientSecret?: string;\n authorizationServer?: string;\n updatedAt: string;\n}\n\n/**\n * 토큰 저장소 — ★서버(endpoint)마다 «한 칸»이다(설계 0.4.2 §2-4).\n *\n * <p>역할은 더는 토큰 칸이 정하지 않는다. 역할은 저장소(init 이 쓴 --role)에서 와서 요청 헤더로 실린다.\n * 0.4.1 이하가 쓴 역할별 칸은 읽지도 지우지도 않는다(사용자 결정 «마이그레이션은 고려하지 마»).\n */\nexport interface TokenStore {\n load(endpoint: string): Promise<StoredTokenSet | undefined>;\n save(endpoint: string, token: StoredTokenSet): Promise<void>;\n delete(endpoint: string): Promise<void>;\n}\n\ntype TokenFile = Record<string, StoredTokenSet>;\n\nexport class FileTokenStore implements TokenStore {\n readonly filePath: string;\n\n constructor(rootDir = join(homedir(), \".ainecto\")) {\n this.filePath = join(rootDir, \"tokens.json\");\n }\n\n async load(endpoint: string): Promise<StoredTokenSet | undefined> {\n const file = await this.readFile();\n return file[tokenKey(endpoint)];\n }\n\n async save(endpoint: string, token: StoredTokenSet): Promise<void> {\n assertNoRoleScope(token);\n const file = await this.readFile();\n file[tokenKey(endpoint)] = { ...token, endpoint, updatedAt: new Date().toISOString() };\n await this.writeFile(file);\n }\n\n async delete(endpoint: string): Promise<void> {\n const file = await this.readFile();\n delete file[tokenKey(endpoint)];\n await this.writeFile(file);\n }\n\n private async readFile(): Promise<TokenFile> {\n try {\n await this.hardenExistingPermissions();\n const raw = await readFile(this.filePath, \"utf8\");\n const parsed = JSON.parse(raw) as unknown;\n if (!parsed || typeof parsed !== \"object\" || Array.isArray(parsed)) {\n throw new Error(\"Token file must contain an object.\");\n }\n return parsed as TokenFile;\n } catch (error) {\n if (isNodeError(error, \"ENOENT\")) {\n return {};\n }\n throw error;\n }\n }\n\n private async writeFile(file: TokenFile): Promise<void> {\n await mkdir(dirname(this.filePath), { recursive: true, mode: 0o700 });\n await chmod(dirname(this.filePath), 0o700);\n const tmpPath = `${this.filePath}.${process.pid}.${Date.now()}.tmp`;\n await writeFile(tmpPath, `${JSON.stringify(file, null, 2)}\\n`, { mode: 0o600 });\n await chmod(tmpPath, 0o600);\n await rename(tmpPath, this.filePath);\n await chmod(this.filePath, 0o600);\n }\n\n private async hardenExistingPermissions(): Promise<void> {\n const dirPath = dirname(this.filePath);\n await chmodIfPermissive(dirPath, 0o700);\n await chmodIfPermissive(this.filePath, 0o600);\n }\n}\n\n/** 서버 하나의 칸. 0.4.1 이하의 «역할 없는» 칸과 같은 키다 — 그때 역할 없이 로그인해 둔 사람은 그대로 쓴다. */\nexport function tokenKey(endpoint: string): string {\n return createHash(\"sha256\").update(endpoint).digest(\"hex\");\n}\n\n/**\n * ★역할 scope 가 든 토큰은 이 칸에 두지 않는다(설계 0.4.2 §2-4, 리뷰 P0-1).\n *\n * <p>토큰에 역할이 박혀 있으면 서버는 헤더보다 토큰을 믿는다. 그런 토큰이 이 한 칸에 들어가면 «모든»\n * 저장소에서 그 역할이 헤더를 이겨 역할이 뒤바뀐다. 지금은 그런 토큰을 받을 경로가 없지만(로그인에 역할을\n * 싣지 않는다), 저장 경계에서 막아 두면 새 경로가 생겨도 조용히 새지 않는다.\n */\nexport function assertNoRoleScope(token: Pick<StoredTokenSet, \"scope\">): void {\n if (token.scope?.split(/\\s+/).some((scope) => scope.startsWith(ROLE_SCOPE_PREFIX))) {\n throw new Error(\n \"Refusing to store a role-scoped access token: sign-in is one per machine and the role comes \"\n + \"from the repository. Run `ainecto auth login` again.\",\n );\n }\n}\n\nexport async function tokenStorePermissions(path: string): Promise<{ mode: number } | undefined> {\n try {\n const result = await stat(path);\n return { mode: result.mode & 0o777 };\n } catch (error) {\n if (isNodeError(error, \"ENOENT\")) {\n return undefined;\n }\n throw error;\n }\n}\n\nexport async function tokenStoreDirectoryPermissions(path: string): Promise<{ mode: number } | undefined> {\n return tokenStorePermissions(dirname(path));\n}\n\nexport async function removeTokenStoreForTests(path: string): Promise<void> {\n await rm(dirname(path), { recursive: true, force: true });\n}\n\nfunction isNodeError(error: unknown, code: string): error is NodeJS.ErrnoException {\n return error instanceof Error && \"code\" in error && error.code === code;\n}\n\nasync function chmodIfPermissive(path: string, mode: number): Promise<void> {\n try {\n const result = await stat(path);\n if ((result.mode & 0o077) !== 0) {\n await chmod(path, mode);\n }\n } catch (error) {\n if (isNodeError(error, \"ENOENT\")) {\n return;\n }\n throw error;\n }\n}\n","/**\n * 세션 역할 — AiNecto Harness 의 「하나의 세션은 하나의 역할만 가진다」를 클라이언트 쪽에서\n * 실어 나르는 부분.\n *\n * ★0.4.2: 역할의 «저장 위치»는 하나다 — init 이 저장소에 쓴 {@code .mcp.json}·{@code .cursor/mcp.json}\n * 의 {@code ainecto} 항목의 {@code --role}(판정은 {@code repositoryRole.ts} 한 곳). 로그인은 서버마다\n * 한 번이고 토큰은 역할을 싣지 않는다. 역할은 요청마다 {@link ROLE_HEADER} 로 실리고, 서버는 그 헤더로\n * 도구 목록·거절·인자 판정을 전부 적용한다. 역할은 작업 가드레일이지 보안 경계가 아니다(2026-09-29\n * 사용자 결정 Q1) — 같은 토큰으로 헤더를 빼면 제한이 없다.\n *\n * - 브리지({@code ainecto mcp}): ① {@code --role} 인자(init 이 MCP 설정에 쓴다) ② {@code AINECTO_ROLE}\n * (Codex 처럼 설정이 전역뿐인 에이전트). 세션 시작 때 정해지고 세션 동안 바뀌지 않는다.\n * - 셸 명령({@code ainecto tools call …} 등): 저장소 역할. ①② 를 주면 저장소 역할과 같아야 한다(다르면 거절).\n * - 저장소 밖: 역할 없음 = 제한 없음.\n *\n * ★에이전트가 «사용자의 답으로» 다음 세션의 역할을 적용하는 것은 허용한다(Q2): 고르는 것은 사람,\n * 적용({@code ainecto init --role <role>})은 에이전트. 떠 있는 세션의 역할은 바뀌지 않는다.\n *\n * ⛔«도구로 역할을 선언»하는 길은 택하지 않았다. 에이전트가 «세션 도중에» 스스로 바꿀 수 있으면\n * 제약이 아니다 — 막히는 순간 「역할을 바꿔서 하겠습니다」가 가장 자연스러운 다음 수가 된다.\n *\n * ⚠강제하는 쪽은 서버다. 이 파일은 역할을 «전달»만 한다.\n */\n\nexport const HARNESS_ROLES = [\"design\", \"development\", \"test\", \"validation\"] as const;\n\nexport type HarnessRole = (typeof HARNESS_ROLES)[number];\n\n/**\n * 역할을 싣던 OAuth scope 접두사(0.4.1 이하). 서버의 McpSessionRole.ROLE_SCOPE_PREFIX 와 같다.\n *\n * <p>0.4.2 부터 로그인은 이 scope 를 요구하지 않는다. 남은 쓰임은 하나 — 역할 없는 토큰 칸에\n * 이 scope 가 든 토큰을 «저장하지 않는» 가드({@code tokenStore.assertNoRoleScope}).\n */\nexport const ROLE_SCOPE_PREFIX = \"ainecto:role:\";\n\n/**\n * 서버가 읽는 헤더 이름. 서버의 McpSessionRoleResolver.ROLE_HEADER 와 같아야 한다.\n */\nexport const ROLE_HEADER = \"X-AiNecto-Role\";\n\n/** 설정이 전역뿐인 에이전트(Codex 등)를 위한 환경변수. */\nexport const ROLE_ENV_VAR = \"AINECTO_ROLE\";\n\nexport interface RoleResolutionInput {\n role?: string;\n envVars?: NodeJS.ProcessEnv;\n}\n\nexport interface ResolvedRole {\n role?: HarnessRole;\n source: \"flag\" | \"env\" | \"none\";\n}\n\nexport function resolveRole(input: RoleResolutionInput = {}): ResolvedRole {\n if (input.role !== undefined) {\n return { role: assertRole(input.role), source: \"flag\" };\n }\n const envVars = input.envVars ?? process.env;\n const fromEnv = envVars[ROLE_ENV_VAR];\n if (fromEnv && fromEnv.trim()) {\n return { role: assertRole(fromEnv), source: \"env\" };\n }\n return { source: \"none\" };\n}\n\n/**\n * ⚠여기서는 «틀리면 멈춘다». 서버는 모르는 값을 FULL 로 받아 넘기지만, 그건 남의 설정이\n * 조용히 죽지 않게 하려는 것이고 — 이쪽은 방금 사람이 친 값이라 오타를 알려주는 편이 낫다.\n * 조용히 «역할 없음»으로 도는 것이 이 도구에서 가장 나쁜 실패다.\n */\nexport function assertRole(value: string): HarnessRole {\n const normalized = value.trim().toLowerCase();\n if ((HARNESS_ROLES as readonly string[]).includes(normalized)) {\n return normalized as HarnessRole;\n }\n throw new Error(\n `Invalid role \"${value}\". Expected one of: ${HARNESS_ROLES.join(\", \")}.`,\n );\n}\n\n/** 사람이 읽는 이름. 서버의 거부 안내문과 같은 어휘를 쓴다. */\nexport function roleDisplayName(role: HarnessRole): string {\n return `${role.charAt(0).toUpperCase()}${role.slice(1)} Session`;\n}\n","import { createHash, randomBytes } from \"node:crypto\";\nimport { createServer } from \"node:http\";\nimport { spawn, type ChildProcess, type SpawnOptions } from \"node:child_process\";\nimport { URLSearchParams } from \"node:url\";\nimport type { StoredTokenSet, TokenStore } from \"./tokenStore\";\nimport { registerPublicClient, type ClientRegistrationResult } from \"./clientRegistration\";\nimport { assertTrustedEndpointUrl, isDefaultEndpoint } from \"../config/endpoints\";\n\n/**\n * ★로그인은 서버마다 한 번이다(설계 0.4.2). 토큰은 역할을 싣지 않고, 역할은 저장소에서 와서\n * 요청 헤더로 실린다({@link McpRpcClient}). 그래서 이 클라이언트에는 역할이 없다.\n */\nexport interface OAuthClientOptions {\n endpoint: string;\n tokenStore: TokenStore;\n fetchImpl?: typeof fetch;\n envVars?: NodeJS.ProcessEnv;\n /**\n * 브라우저를 연다. <b>열기에 실패하면 거절</b>하고, 성공이 확인되면(또는 아직 모르면) 가만히 있는다.\n * login 은 이 약속을 «기다리지 않고» 콜백 대기와 경주시킨다 — 여는 명령이 브라우저가 닫힐 때까지\n * 안 끝나는 환경에서 순서대로 기다리면 교착한다.\n */\n openBrowser?: (url: string) => Promise<void>;\n /**\n * 인가 URL 을 «브라우저를 열기 전에» 알린다. CLI 는 stderr 에 한 줄 쓴다 — 브라우저가 안 열려도\n * 사람이 직접 열 수 있고, 에이전트가 실행했다면 명령이 끝날 때 그 줄이 에이전트에게 닿는다.\n */\n onAuthorizeUrl?: (url: string) => void;\n /** 콜백을 기다리는 한도. 기본 5분 — 사람이 로그인하기엔 넉넉하고, 에이전트 셸 도구 최대 한도보다 짧다. */\n loginTimeoutMs?: number;\n /**\n * 토큰 저장소를 «읽기만» 한다 — 갱신하지 않는다(네트워크도, 토큰 파일 쓰기도 없다).\n * {@code init --dry-run} 이 쓴다: 「아무것도 바꾸지 않는다」에 사용자의 로그인 상태도 든다.\n * 갱신이 필요한(만료된) 토큰은 «없음»으로 본다.\n */\n readOnly?: boolean;\n}\n\n/** 로그인 대기 기본 한도(5분). */\nexport const DEFAULT_LOGIN_TIMEOUT_MS = 300_000;\n\n/**\n * 로그인이 «사람 쪽 사정으로» 끝나지 못한 이유. {@code code} 는 {@code --json} 의 {@code error.code} 로\n * 그대로 나간다(종료 코드는 다른 실패와 같은 1 — 받는 쪽은 메시지를 읽고 사람에게 옮긴다).\n */\nexport class OAuthLoginError extends Error {\n constructor(readonly code: \"LOGIN_TIMEOUT\" | \"BROWSER_UNAVAILABLE\" | \"CALLBACK_UNAVAILABLE\", message: string) {\n super(message);\n this.name = \"OAuthLoginError\";\n }\n}\n\nexport interface OAuthMetadata {\n authorizationEndpoint: string;\n tokenEndpoint: string;\n registrationEndpoint?: string;\n issuer?: string;\n codeChallengeMethodsSupported: string[];\n}\n\nexport interface TokenProvider {\n getAccessToken(): Promise<string | undefined>;\n refreshAfterUnauthorized(): Promise<string | undefined>;\n}\n\nexport class OAuthClient implements TokenProvider {\n private readonly fetchImpl: typeof fetch;\n private readonly envVars: NodeJS.ProcessEnv;\n private readonly openBrowserImpl: (url: string) => Promise<void>;\n\n constructor(private readonly options: OAuthClientOptions) {\n this.fetchImpl = options.fetchImpl ?? fetch;\n this.envVars = options.envVars ?? process.env;\n this.openBrowserImpl = options.openBrowser ?? openBrowser;\n }\n\n async getAccessToken(): Promise<string | undefined> {\n const envToken = this.envVars.AINECTO_TOKEN;\n if (envToken) {\n assertEnvTokenEndpointAllowed(this.options.endpoint, this.envVars);\n return envToken;\n }\n\n const stored = await this.options.tokenStore.load(this.options.endpoint);\n if (!stored) {\n return undefined;\n }\n\n if (!shouldRefresh(stored)) {\n return stored.accessToken;\n }\n if (this.options.readOnly) {\n return isExpired(stored) ? undefined : stored.accessToken;\n }\n // ★만료됐는데 갱신할 수단이 없으면 «토큰 없음»이다. 예전엔 만료된 토큰을 그대로 돌려줘서,\n // init 은 로그인 대신 401 안내로 떨어졌고 다른 경로는 받을 게 뻔한 401 을 받으러 갔다\n // (2026-09-29 독립 리뷰 P1-2). 아직 60초 창 안이면 쓸 수 있으므로 돌려준다.\n if (!stored.refreshToken) {\n return isExpired(stored) ? undefined : stored.accessToken;\n }\n\n return this.refreshStoredToken({ ...stored, refreshToken: stored.refreshToken });\n }\n\n async refreshAfterUnauthorized(): Promise<string | undefined> {\n const stored = await this.options.tokenStore.load(this.options.endpoint);\n if (this.options.readOnly || !stored?.refreshToken) {\n return undefined;\n }\n return this.refreshStoredToken({ ...stored, refreshToken: stored.refreshToken });\n }\n\n async login(): Promise<StoredTokenSet> {\n const pkce = generatePkcePair();\n const state = generateOAuthState();\n const loopback = await createLoopbackReceiver(state);\n\n try {\n const metadata = await discoverOAuthMetadata(this.options.endpoint, this.fetchImpl);\n const registration = metadata.registrationEndpoint\n ? await registerPublicClient(metadata.registrationEndpoint, loopback.redirectUri, this.fetchImpl)\n : { clientId: \"ainecto-cli\" };\n const authorizeUrl = new URL(metadata.authorizationEndpoint);\n authorizeUrl.searchParams.set(\"response_type\", \"code\");\n authorizeUrl.searchParams.set(\"client_id\", registration.clientId);\n authorizeUrl.searchParams.set(\"redirect_uri\", loopback.redirectUri);\n authorizeUrl.searchParams.set(\"code_challenge\", pkce.codeChallenge);\n authorizeUrl.searchParams.set(\"code_challenge_method\", \"S256\");\n authorizeUrl.searchParams.set(\"resource\", this.options.endpoint);\n authorizeUrl.searchParams.set(\"state\", state);\n // ★역할을 scope 에 싣지 않는다(설계 0.4.2) — 로그인은 서버마다 한 번이고, 역할은 저장소에서\n // 요청 헤더로 온다. 역할은 작업 가드레일이지 보안 경계가 아니다(2026-09-29 사용자 결정 Q1).\n\n assertTrustedEndpointUrl(authorizeUrl, \"OAuth authorization URL\");\n const url = authorizeUrl.toString();\n this.options.onAuthorizeUrl?.(url);\n // ★셋 중 먼저 오는 것이 결과다 — 콜백 도착 / 브라우저 열기 실패 / 시간 한도.\n // 예전엔 한도가 없고 열기 실패를 못 봐서, 브라우저가 없는 기계에서 말없이 영원히 기다렸다\n // (2026-09-29 독립 리뷰 P1-2). 비TTY(에이전트가 실행)에서는 그 대기가 곧 멈춤이다.\n const browserFailure = this.openBrowserImpl(url).then(() => new Promise<never>(() => undefined));\n const code = await withTimeout(\n Promise.race([loopback.waitForCode(), browserFailure]),\n this.options.loginTimeoutMs ?? DEFAULT_LOGIN_TIMEOUT_MS,\n );\n const token = await exchangeToken(metadata.tokenEndpoint, {\n grant_type: \"authorization_code\",\n code,\n redirect_uri: loopback.redirectUri,\n client_id: registration.clientId,\n code_verifier: pkce.codeVerifier,\n resource: this.options.endpoint,\n ...(registration.clientSecret ? { client_secret: registration.clientSecret } : {}),\n }, this.fetchImpl);\n\n const stored = toStoredToken(this.options.endpoint, token, metadata, registration);\n await this.options.tokenStore.save(this.options.endpoint, stored);\n return stored;\n } finally {\n await loopback.close();\n }\n }\n\n async logout(): Promise<void> {\n await this.options.tokenStore.delete(this.options.endpoint);\n }\n\n /** 부르는 쪽이 refresh token 이 있음을 이미 확인했다 — 타입이 그것을 요구한다. */\n private async refreshStoredToken(stored: StoredTokenSet & { refreshToken: string }): Promise<string | undefined> {\n const metadata = await discoverOAuthMetadata(this.options.endpoint, this.fetchImpl);\n const token = await exchangeToken(metadata.tokenEndpoint, {\n grant_type: \"refresh_token\",\n refresh_token: stored.refreshToken,\n client_id: stored.clientId ?? \"ainecto-cli\",\n ...(stored.clientSecret ? { client_secret: stored.clientSecret } : {}),\n resource: this.options.endpoint,\n }, this.fetchImpl);\n const next = toStoredToken(this.options.endpoint, token, metadata, stored);\n await this.options.tokenStore.save(this.options.endpoint, next);\n return next.accessToken;\n }\n}\n\nexport function generatePkcePair(): { codeVerifier: string; codeChallenge: string } {\n const codeVerifier = randomBytes(32).toString(\"base64url\");\n const codeChallenge = createHash(\"sha256\").update(codeVerifier).digest(\"base64url\");\n return { codeVerifier, codeChallenge };\n}\n\nexport function generateOAuthState(): string {\n return randomBytes(32).toString(\"base64url\");\n}\n\n/** 이미 만료됐는가. 만료 시각이 없으면 만료되지 않은 것으로 본다(서버가 수명을 안 알려 준 토큰). */\nexport function isExpired(token: Pick<StoredTokenSet, \"expiresAt\">, now = Date.now()): boolean {\n return token.expiresAt !== undefined && token.expiresAt <= now;\n}\n\nasync function withTimeout<T>(work: Promise<T>, timeoutMs: number): Promise<T> {\n let timer: NodeJS.Timeout | undefined;\n const timeout = new Promise<never>((_, reject) => {\n timer = setTimeout(() => {\n const minutes = timeoutMs / 60_000;\n const span = Number.isInteger(minutes) ? `${minutes} minute${minutes === 1 ? \"\" : \"s\"}` : `${timeoutMs} ms`;\n reject(new OAuthLoginError(\n \"LOGIN_TIMEOUT\",\n `Sign-in was not completed within ${span}. Nothing was saved — run the same command again.`,\n ));\n }, timeoutMs);\n });\n try {\n return await Promise.race([work, timeout]);\n } finally {\n clearTimeout(timer);\n }\n}\n\nexport function shouldRefresh(token: Pick<StoredTokenSet, \"expiresAt\">, now = Date.now()): boolean {\n if (!token.expiresAt) {\n return false;\n }\n return token.expiresAt - now < 60_000;\n}\n\nexport async function discoverOAuthMetadata(endpoint: string, fetchImpl: typeof fetch = fetch): Promise<OAuthMetadata> {\n assertTrustedEndpointUrl(endpoint, \"MCP endpoint\");\n const resourceMetadataUrl = await discoverResourceMetadataUrl(endpoint, fetchImpl);\n assertTrustedEndpointUrl(resourceMetadataUrl, \"OAuth protected resource metadata URL\");\n const resourceMetadata = await fetchJson<Record<string, unknown>>(resourceMetadataUrl, fetchImpl);\n const authorizationServers = resourceMetadata.authorization_servers;\n if (!Array.isArray(authorizationServers) || typeof authorizationServers[0] !== \"string\") {\n throw new Error(\"OAuth protected resource metadata did not include authorization_servers.\");\n }\n const authServer = authorizationServers[0];\n assertTrustedEndpointUrl(authServer, \"OAuth authorization server\");\n const metadataUrl = authServer.includes(\"/.well-known/\")\n ? authServer\n : `${authServer.replace(/\\/$/, \"\")}/.well-known/oauth-authorization-server`;\n assertTrustedEndpointUrl(metadataUrl, \"OAuth authorization server metadata URL\");\n const metadata = await fetchJson<Record<string, unknown>>(metadataUrl, fetchImpl);\n if (typeof metadata.authorization_endpoint !== \"string\" || typeof metadata.token_endpoint !== \"string\") {\n throw new Error(\"OAuth authorization server metadata is missing authorization_endpoint or token_endpoint.\");\n }\n assertTrustedEndpointUrl(metadata.authorization_endpoint, \"OAuth authorization endpoint\");\n assertTrustedEndpointUrl(metadata.token_endpoint, \"OAuth token endpoint\");\n if (typeof metadata.registration_endpoint === \"string\") {\n assertTrustedEndpointUrl(metadata.registration_endpoint, \"OAuth client registration endpoint\");\n }\n if (typeof metadata.issuer === \"string\") {\n assertTrustedEndpointUrl(metadata.issuer, \"OAuth issuer\");\n }\n const codeChallengeMethodsSupported = parseStringArray(metadata.code_challenge_methods_supported);\n if (!codeChallengeMethodsSupported.includes(\"S256\")) {\n throw new Error(\"OAuth authorization server metadata must support PKCE S256.\");\n }\n return {\n authorizationEndpoint: metadata.authorization_endpoint,\n tokenEndpoint: metadata.token_endpoint,\n registrationEndpoint: typeof metadata.registration_endpoint === \"string\" ? metadata.registration_endpoint : undefined,\n issuer: typeof metadata.issuer === \"string\" ? metadata.issuer : undefined,\n codeChallengeMethodsSupported,\n };\n}\n\nasync function discoverResourceMetadataUrl(endpoint: string, fetchImpl: typeof fetch): Promise<string> {\n const challengeUrl = new URL(endpoint);\n assertTrustedEndpointUrl(challengeUrl, \"MCP endpoint\");\n const response = await fetchImpl(challengeUrl, { method: \"GET\" });\n const challenge = response.headers.get(\"www-authenticate\");\n const fromChallenge = challenge ? parseBearerChallengeParam(challenge, \"resource_metadata\") : undefined;\n if (fromChallenge) {\n assertTrustedEndpointUrl(fromChallenge, \"OAuth protected resource metadata URL\");\n return fromChallenge;\n }\n const origin = new URL(endpoint).origin;\n const fallback = `${origin}/.well-known/oauth-protected-resource`;\n assertTrustedEndpointUrl(fallback, \"OAuth protected resource metadata URL\");\n return fallback;\n}\n\nexport function parseBearerChallengeParam(header: string, name: string): string | undefined {\n const pattern = new RegExp(`${name}=\"([^\"]+)\"`);\n return pattern.exec(header)?.[1];\n}\n\nasync function exchangeToken(\n tokenEndpoint: string,\n params: Record<string, string>,\n fetchImpl: typeof fetch,\n): Promise<Record<string, unknown>> {\n assertTrustedEndpointUrl(tokenEndpoint, \"OAuth token endpoint\");\n const response = await fetchImpl(tokenEndpoint, {\n method: \"POST\",\n headers: { \"content-type\": \"application/x-www-form-urlencoded\" },\n body: new URLSearchParams(params),\n });\n if (!response.ok) {\n throw new Error(`OAuth token exchange failed with HTTP ${response.status}.`);\n }\n return response.json() as Promise<Record<string, unknown>>;\n}\n\ntype TokenContext = Partial<Pick<StoredTokenSet, \"refreshToken\" | \"tokenType\" | \"expiresAt\" | \"scope\" | \"clientId\" | \"clientSecret\">>\n & Partial<ClientRegistrationResult>;\n\nfunction toStoredToken(\n endpoint: string,\n token: Record<string, unknown>,\n metadata: OAuthMetadata,\n previous?: TokenContext,\n): StoredTokenSet {\n if (typeof token.access_token !== \"string\") {\n throw new Error(\"OAuth token response did not include access_token.\");\n }\n const expiresIn = typeof token.expires_in === \"number\" ? token.expires_in : undefined;\n return {\n endpoint,\n accessToken: token.access_token,\n refreshToken: typeof token.refresh_token === \"string\" ? token.refresh_token : previous?.refreshToken,\n tokenType: typeof token.token_type === \"string\" ? token.token_type : previous?.tokenType,\n expiresAt: expiresIn ? Date.now() + expiresIn * 1000 : previous?.expiresAt,\n scope: typeof token.scope === \"string\" ? token.scope : previous?.scope,\n clientId: previous?.clientId ?? \"ainecto-cli\",\n clientSecret: previous?.clientSecret,\n authorizationServer: metadata.issuer,\n updatedAt: new Date().toISOString(),\n };\n}\n\n/**\n * 로그인 콜백을 받는 loopback 서버. {@code host} 는 시험이 «바인딩 실패»를 만들 때만 바꾼다.\n */\nexport async function createLoopbackReceiver(expectedState: string, host = \"127.0.0.1\"): Promise<{\n redirectUri: string;\n waitForCode(): Promise<string>;\n close(): Promise<void>;\n}> {\n let resolveCode!: (code: string) => void;\n let rejectCode!: (error: Error) => void;\n const codePromise = new Promise<string>((resolve, reject) => {\n resolveCode = resolve;\n rejectCode = reject;\n });\n codePromise.catch(() => undefined);\n\n const server = createServer((req, res) => {\n const url = new URL(req.url ?? \"/\", \"http://127.0.0.1\");\n const error = url.searchParams.get(\"error\");\n const code = url.searchParams.get(\"code\");\n const state = url.searchParams.get(\"state\");\n if (error) {\n res.writeHead(400, { \"content-type\": \"text/plain\" });\n res.end(\"AiNecto CLI authorization failed. You can close this tab.\");\n rejectCode(new Error(`OAuth authorization failed: ${error}`));\n return;\n }\n if (!state || state !== expectedState) {\n res.writeHead(400, { \"content-type\": \"text/plain\" });\n res.end(\"AiNecto CLI authorization state mismatch. You can close this tab.\");\n rejectCode(new Error(\"OAuth state mismatch.\"));\n return;\n }\n if (!code) {\n res.writeHead(400, { \"content-type\": \"text/plain\" });\n res.end(\"Missing OAuth code.\");\n rejectCode(new Error(\"OAuth redirect did not include a code.\"));\n return;\n }\n res.writeHead(200, { \"content-type\": \"text/plain\" });\n res.end(\"AiNecto CLI authorization complete. You can close this tab.\");\n resolveCode(code);\n });\n\n // ★바인딩이 막히면(에이전트 sandbox 등) 예전엔 error 리스너가 없어 처리되지 않은 예외로 죽었다\n // (2026-09-29 코드 리뷰 P1). 사람이 할 일을 말하는 오류로 바꾼다.\n await new Promise<void>((resolve, reject) => {\n server.once(\"error\", (error) => reject(new OAuthLoginError(\n \"CALLBACK_UNAVAILABLE\",\n `Could not start the local sign-in callback on ${host} (${error.message}). `\n + \"If this command runs in a sandbox, allow local network binding or run it outside the sandbox.\",\n )));\n server.listen(0, host, () => resolve());\n });\n const address = server.address();\n if (!address || typeof address === \"string\") {\n throw new Error(\"Loopback OAuth server did not bind to a TCP port.\");\n }\n return {\n redirectUri: `http://127.0.0.1:${address.port}/callback`,\n waitForCode: () => codePromise,\n close: () => new Promise<void>((resolve, reject) => {\n // Node 18 은 브라우저가 붙들고 있는 keep-alive 연결이 끝날 때까지 close 가 늦어진다.\n server.closeAllConnections?.();\n server.close((error) => error ? reject(error) : resolve());\n }),\n };\n}\n\nfunction parseStringArray(value: unknown): string[] {\n return Array.isArray(value) ? value.filter((item): item is string => typeof item === \"string\") : [];\n}\n\nasync function fetchJson<T>(url: string, fetchImpl: typeof fetch): Promise<T> {\n assertTrustedEndpointUrl(url, \"OAuth metadata URL\");\n const response = await fetchImpl(url);\n if (!response.ok) {\n throw new Error(`Failed to fetch OAuth metadata from ${url}: HTTP ${response.status}.`);\n }\n return response.json() as Promise<T>;\n}\n\nexport function getBrowserOpenCommand(\n url: string,\n platform: NodeJS.Platform = process.platform,\n): { command: string; args: string[] } {\n assertTrustedEndpointUrl(url, \"OAuth authorization URL\");\n if (platform === \"darwin\") {\n return { command: \"open\", args: [url] };\n }\n if (platform === \"win32\") {\n return { command: \"explorer.exe\", args: [url] };\n }\n return { command: \"xdg-open\", args: [url] };\n}\n\nexport interface LaunchBrowserDeps {\n platform?: NodeJS.Platform;\n spawnImpl?: (command: string, args: string[], options: SpawnOptions) => ChildProcess;\n}\n\n/**\n * 기본 브라우저 열기. <b>여는 명령이 없거나(ENOENT) 0 이 아닌 코드로 끝나면 거절</b>한다.\n *\n * <p>★예전엔 {@code error} 리스너가 없어 명령이 없으면 처리되지 않은 오류로 죽었고, 명령은 있는데\n * 브라우저가 없으면({@code xdg-open} 비0 종료) 아무 말 없이 콜백을 영원히 기다렸다\n * (2026-09-29 독립 리뷰 P1-2).\n *\n * <p>⚠<b>Windows 는 종료 코드를 보지 않는다.</b> {@code explorer.exe} 는 성공해도 1 로 끝나는 것으로\n * 알려져 있어, 그 코드로 판정하면 정상 환경을 실패로 만든다 — 명령이 «떴다»는 것만 본다\n * (설계 §8-1, Windows 실측 V14 전까지 추정).\n */\nexport function launchBrowser(url: string, deps: LaunchBrowserDeps = {}): Promise<void> {\n const platform = deps.platform ?? process.platform;\n const { command, args } = getBrowserOpenCommand(url, platform);\n const spawnImpl = deps.spawnImpl ?? spawn;\n return new Promise<void>((resolve, reject) => {\n const unavailable = (reason: string) => new OAuthLoginError(\n \"BROWSER_UNAVAILABLE\",\n `Could not open a browser on this machine (${command}: ${reason}). Sign-in needs a browser here.`,\n );\n let child: ChildProcess;\n try {\n child = spawnImpl(command, args, { stdio: \"ignore\", detached: true });\n } catch (error) {\n reject(unavailable(error instanceof Error ? error.message : String(error)));\n return;\n }\n child.once(\"error\", (error) => reject(unavailable(error.message)));\n if (platform === \"win32\") {\n child.once(\"spawn\", () => resolve());\n } else {\n child.once(\"exit\", (code, signal) => {\n if (code === 0) {\n resolve();\n } else {\n reject(unavailable(code === null ? `stopped by ${signal ?? \"a signal\"}` : `exited with code ${code}`));\n }\n });\n }\n child.unref();\n });\n}\n\nfunction openBrowser(url: string): Promise<void> {\n return launchBrowser(url);\n}\n\nfunction assertEnvTokenEndpointAllowed(endpoint: string, envVars: NodeJS.ProcessEnv): void {\n if (envVars.AINECTO_ALLOW_CUSTOM_ENDPOINT_TOKEN === \"1\") {\n return;\n }\n if (isDefaultEndpoint(endpoint)) {\n return;\n }\n 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.\");\n}\n","import { assertLoopbackRedirectUrl, assertTrustedEndpointUrl } from \"../config/endpoints\";\n\nexport interface ClientRegistrationResult {\n clientId: string;\n clientSecret?: string;\n}\n\nexport async function registerPublicClient(\n registrationEndpoint: string,\n redirectUri: string,\n fetchImpl: typeof fetch = fetch,\n): Promise<ClientRegistrationResult> {\n assertTrustedEndpointUrl(registrationEndpoint, \"OAuth client registration endpoint\");\n assertLoopbackRedirectUrl(redirectUri);\n const response = await fetchImpl(registrationEndpoint, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\", accept: \"application/json\" },\n body: JSON.stringify({\n client_name: \"AiNecto CLI\",\n redirect_uris: [redirectUri],\n token_endpoint_auth_method: \"none\",\n grant_types: [\"authorization_code\", \"refresh_token\"],\n response_types: [\"code\"],\n }),\n });\n if (!response.ok) {\n throw new Error(`OAuth client registration failed with HTTP ${response.status}.`);\n }\n const body = await response.json() as unknown;\n if (!isRecord(body) || typeof body.client_id !== \"string\") {\n throw new Error(\"OAuth client registration response did not include client_id.\");\n }\n return {\n clientId: body.client_id,\n clientSecret: typeof body.client_secret === \"string\" ? body.client_secret : undefined,\n };\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n","/**\n * CLI 버전. ★진실원은 {@code package.json} 이고 빌드(tsup define)가 그 값을 박는다 —\n * 상수로 또 적으면 릴리스 때 한쪽만 올라가 조용히 거짓이 된다.\n * 번들을 거치지 않는 실행(vitest·tsx)에서는 값이 없으므로 \"dev\" 로 떨어진다.\n */\ndeclare const __AINECTO_CLI_VERSION__: string | undefined;\n\nexport const CLI_VERSION: string =\n typeof __AINECTO_CLI_VERSION__ === \"string\" ? __AINECTO_CLI_VERSION__ : \"dev\";\n\n/**\n * npm 주 이름. ★이 이름을 박는 곳은 여기 하나다 — init 이 쓰는 {@code npx -y <이름> --role …},\n * MCP {@code clientInfo}, User-Agent, init 기록의 {@code generatedBy} 가 모두 이것을 쓴다.\n * {@code package.json} 의 name·{@code server.json} 의 패키지 식별자와 같아야 한다(시험이 대조한다).\n *\n * <p>다른 이름 {@code ainecto}·{@code @ainecto/cli} 에도 같은 코드가 올라가지만(scripts/publish.mjs),\n * 그 판으로 init 해도 이 이름을 쓴다.\n */\nexport const PACKAGE_NAME = \"@ainecto/mcp\";\n\nexport const CLI_USER_AGENT = `${PACKAGE_NAME} ${CLI_VERSION}`;\n","import type { TokenProvider } from \"../auth/oauth\";\nimport { ROLE_HEADER, type HarnessRole } from \"../harness/role\";\nimport { CLI_VERSION, PACKAGE_NAME } from \"../version\";\n\nexport interface JsonRpcRequest {\n jsonrpc: \"2.0\";\n id?: string | number | null;\n method: string;\n params?: unknown;\n}\n\nexport interface JsonRpcSuccess {\n jsonrpc: \"2.0\";\n id?: string | number | null;\n result: unknown;\n}\n\nexport interface JsonRpcFailure {\n jsonrpc: \"2.0\";\n id?: string | number | null;\n error: {\n code: number;\n message: string;\n data?: unknown;\n };\n}\n\nexport type JsonRpcResponse = JsonRpcSuccess | JsonRpcFailure;\n\nexport interface McpRpcClientOptions {\n endpoint: string;\n tokenProvider?: TokenProvider;\n fetchImpl?: typeof fetch;\n envVars?: NodeJS.ProcessEnv;\n /**\n * 세션 역할. ★필수다 — 역할이 없으면 {@code null} 을 «명시»한다(설계 0.4.2 §2-3, 리뷰 P2).\n * 저장소 안에서 이것을 빠뜨리면 요청이 조용히 «제한 없음»으로 간다. 그 빠뜨림을 타입이 잡는다.\n * 값이 있으면 모든 요청의 {@code X-AiNecto-Role} 헤더로 실린다 — 강제는 서버가 한다.\n */\n role: HarnessRole | null;\n}\n\nexport class McpRpcError extends Error {\n constructor(\n message: string,\n readonly code: string,\n readonly details?: unknown,\n ) {\n super(message);\n }\n}\n\nexport class McpRpcClient {\n private readonly fetchImpl: typeof fetch;\n private nextId = 1;\n\n constructor(private readonly options: McpRpcClientOptions) {\n this.fetchImpl = options.fetchImpl ?? fetch;\n }\n\n initialize(): Promise<unknown> {\n return this.request(\"initialize\", {\n protocolVersion: \"2024-11-05\",\n capabilities: {},\n clientInfo: { name: PACKAGE_NAME, version: CLI_VERSION },\n });\n }\n\n async toolsList(): Promise<unknown[]> {\n const result = await this.request(\"tools/list\", {});\n if (!isRecord(result) || !Array.isArray(result.tools)) {\n throw new McpRpcError(\"tools/list response did not include a tools array.\", \"MCP_PROTOCOL_ERROR\", result);\n }\n return result.tools;\n }\n\n async toolsCall(name: string, args: unknown): Promise<unknown> {\n return this.request(\"tools/call\", { name, arguments: args ?? {} });\n }\n\n async request(method: string, params?: unknown): Promise<unknown> {\n const response = await this.sendRaw({\n jsonrpc: \"2.0\",\n id: this.nextId++,\n method,\n params,\n });\n if (!response) {\n return undefined;\n }\n if (\"error\" in response) {\n throw new McpRpcError(response.error.message, \"MCP_ERROR\", response.error);\n }\n return response.result;\n }\n\n async sendRaw(request: JsonRpcRequest): Promise<JsonRpcResponse | undefined> {\n const token = await this.options.tokenProvider?.getAccessToken();\n let response = await this.postJson(request, token);\n if (response.status === 401 && this.options.tokenProvider) {\n const refreshed = await this.options.tokenProvider.refreshAfterUnauthorized();\n if (refreshed) {\n response = await this.postJson(request, refreshed);\n }\n }\n\n if (response.status === 204 || response.status === 202) {\n return undefined;\n }\n const text = await response.text();\n if (!response.ok) {\n throw new McpRpcError(\n `MCP HTTP request failed with HTTP ${response.status}.`,\n \"MCP_HTTP_ERROR\",\n httpErrorDetails(response.status, text, this.options.envVars ?? process.env),\n );\n }\n if (!text.trim()) {\n return undefined;\n }\n const parsed = JSON.parse(text) as unknown;\n if (!isJsonRpcResponse(parsed)) {\n throw new McpRpcError(\"MCP response was not a JSON-RPC response.\", \"MCP_PROTOCOL_ERROR\", parsed);\n }\n return parsed;\n }\n\n private postJson(request: JsonRpcRequest, token: string | undefined): Promise<Response> {\n const headers: Record<string, string> = {\n \"content-type\": \"application/json\",\n accept: \"application/json\",\n };\n if (token) {\n headers.authorization = `Bearer ${token}`;\n }\n if (this.options.role) {\n headers[ROLE_HEADER.toLowerCase()] = this.options.role;\n }\n return this.fetchImpl(this.options.endpoint, {\n method: \"POST\",\n headers,\n body: JSON.stringify(request),\n });\n }\n}\n\n/**\n * ★MCP 는 «도구가 거부/실패했다»를 JSON-RPC error 가 아니라 결과 안의 {@code isError} 로\n * 알린다(2025-06-18 규격). 그래서 최상위 error 만 보면 실패가 성공으로 보고된다 —\n * 셸 자동화는 exit 0 을 받고 다음 단계로 간다(2026-09-22 독립 리뷰 I9).\n */\nexport function isToolError(result: unknown): boolean {\n return isRecord(result) && result.isError === true;\n}\n\nfunction isJsonRpcResponse(value: unknown): value is JsonRpcResponse {\n return isRecord(value)\n && value.jsonrpc === \"2.0\"\n && (\"result\" in value || \"error\" in value);\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction httpErrorDetails(status: number, body: string, envVars: NodeJS.ProcessEnv): Record<string, unknown> {\n const details: Record<string, unknown> = { status };\n if (envVars.AINECTO_CLI_DEBUG === \"1\" || envVars.AINECTO_DEBUG === \"1\") {\n details.body = safeBodyPreview(body);\n }\n return details;\n}\n\nexport function safeBodyPreview(body: string): string {\n return body.length > 500 ? `${body.slice(0, 500)}...` : body;\n}\n","import { createInterface } from \"node:readline\";\nimport type { Readable, Writable } from \"node:stream\";\nimport { McpRpcClient, type JsonRpcRequest } from \"./rpcClient\";\n\nexport interface StdioBridgeOptions {\n input: Readable;\n output: Writable;\n errorOutput: Writable;\n client: McpRpcClient;\n}\n\nexport async function runStdioBridge(options: StdioBridgeOptions): Promise<void> {\n const lines = createInterface({ input: options.input, crlfDelay: Infinity });\n\n for await (const line of lines) {\n if (!line.trim()) {\n continue;\n }\n try {\n const request = parseJsonRpcRequest(line);\n const response = await options.client.sendRaw(request);\n if (response && request.id !== undefined) {\n options.output.write(`${JSON.stringify(response)}\\n`);\n }\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n options.output.write(`${JSON.stringify({\n jsonrpc: \"2.0\",\n id: null,\n error: { code: -32603, message },\n })}\\n`);\n }\n }\n}\n\nfunction parseJsonRpcRequest(line: string): JsonRpcRequest {\n const parsed = JSON.parse(line) as unknown;\n if (!isRecord(parsed) || parsed.jsonrpc !== \"2.0\" || typeof parsed.method !== \"string\") {\n throw new Error(\"Invalid JSON-RPC request.\");\n }\n return {\n jsonrpc: \"2.0\",\n id: typeof parsed.id === \"string\" || typeof parsed.id === \"number\" || parsed.id === null ? parsed.id : undefined,\n method: parsed.method,\n params: parsed.params,\n };\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n","import { FileTokenStore } from \"../../core/auth/tokenStore\";\nimport { OAuthClient } from \"../../core/auth/oauth\";\nimport { McpRpcClient } from \"../../core/mcp/rpcClient\";\nimport { runStdioBridge } from \"../../core/mcp/stdioBridge\";\nimport type { HarnessRole } from \"../../core/harness/role\";\nimport type { Readable, Writable } from \"node:stream\";\n\nexport interface ConnectorOptions {\n endpoint: string;\n /** 브리지의 역할 — MCP 설정의 `--role` 인자나 `AINECTO_ROLE` 에서만 온다. 없으면 null. */\n role: HarnessRole | null;\n input: Readable;\n output: Writable;\n errorOutput: Writable;\n}\n\nexport async function runConnector(options: ConnectorOptions): Promise<void> {\n // ★토큰은 서버마다 한 칸이다(설계 0.4.2 §2-4). 역할은 토큰이 아니라 요청 헤더로 실린다 —\n // 브리지의 역할은 시작할 때 인자로 정해지고 세션 동안 바뀌지 않는다.\n const auth = new OAuthClient({ endpoint: options.endpoint, tokenStore: new FileTokenStore() });\n const client = new McpRpcClient({\n endpoint: options.endpoint,\n tokenProvider: auth,\n role: options.role,\n });\n await runStdioBridge({\n input: options.input,\n output: options.output,\n errorOutput: options.errorOutput,\n client,\n });\n}\n","export interface CliErrorShape {\n ok: false;\n error: {\n code: string;\n message: string;\n details?: unknown;\n };\n}\n\nexport interface CliSuccessShape {\n ok: true;\n data: unknown;\n warnings?: string[];\n}\n\nexport function renderSuccess(data: unknown, options: { json?: boolean; warnings?: string[] } = {}): string {\n if (options.json) {\n const shape: CliSuccessShape = { ok: true, data };\n if (options.warnings?.length) {\n shape.warnings = options.warnings;\n }\n return `${JSON.stringify(shape, null, 2)}\\n`;\n }\n const warnings = options.warnings?.length ? `${options.warnings.map((warning) => `Warning: ${warning}`).join(\"\\n\")}\\n` : \"\";\n return `${warnings}${renderHuman(data)}\\n`;\n}\n\nexport function renderError(error: unknown, options: { json?: boolean } = {}): string {\n const shape = toErrorShape(error);\n if (options.json) {\n return `${JSON.stringify(shape, null, 2)}\\n`;\n }\n return `Error: ${shape.error.message}\\n`;\n}\n\nexport function toErrorShape(error: unknown): CliErrorShape {\n if (error instanceof Error) {\n const maybe = error as Error & { code?: string; details?: unknown };\n return {\n ok: false,\n error: {\n code: maybe.code ?? \"CLI_ERROR\",\n message: error.message,\n details: redactSecrets(maybe.details),\n },\n };\n }\n return { ok: false, error: { code: \"CLI_ERROR\", message: String(error) } };\n}\n\nexport function redactSecrets(value: unknown): unknown {\n return redactSecretsInner(value, new WeakSet<object>());\n}\n\nfunction renderHuman(data: unknown): string {\n if (Array.isArray(data)) {\n return data.map((item) => renderHuman(item)).join(\"\\n\");\n }\n if (typeof data === \"string\") {\n return data;\n }\n return JSON.stringify(data, null, 2);\n}\n\nfunction redactSecretsInner(value: unknown, seen: WeakSet<object>): unknown {\n if (typeof value === \"string\") {\n return redactSecretString(value);\n }\n if (Array.isArray(value)) {\n return value.map((item) => redactSecretsInner(item, seen));\n }\n if (value && typeof value === \"object\") {\n if (seen.has(value)) {\n return \"[circular]\";\n }\n seen.add(value);\n const output: Record<string, unknown> = {};\n for (const [key, child] of Object.entries(value)) {\n output[key] = isSecretKey(key) ? \"[redacted]\" : redactSecretsInner(child, seen);\n }\n return output;\n }\n return value;\n}\n\nfunction isSecretKey(key: string): boolean {\n const normalized = key.toLowerCase().replace(/-/g, \"_\");\n return normalized === \"authorization\"\n || normalized.includes(\"access_token\")\n || normalized.includes(\"refresh_token\")\n || normalized.includes(\"client_secret\")\n || normalized.includes(\"token\");\n}\n\nfunction redactSecretString(value: string): string {\n return value\n .replace(/(\"(?:authorization|access_token|refresh_token|client_secret|token)\"\\s*:\\s*\")([^\"]*)(\")/gi, \"$1[redacted]$3\")\n .replace(/((?:authorization|access_token|refresh_token|client_secret|token)=)([^&\\s\"']+)/gi, \"$1[redacted]\")\n .replace(/(authorization:\\s*bearer\\s+)([^\\s,}]+)/gi, \"$1[redacted]\")\n .replace(/(bearer\\s+)(eyJ[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+)/gi, \"$1[redacted]\");\n}\n","import { assertEnv, type AinectoEnv } from \"../../core/config/endpoints\";\nimport { assertRole, type HarnessRole } from \"../../core/harness/role\";\n\nexport interface McpBinArgs {\n env?: AinectoEnv;\n endpoint?: string;\n role?: HarnessRole;\n help: boolean;\n}\n\nexport function parseMcpArgs(argv: string[]): McpBinArgs {\n const result: McpBinArgs = { help: false };\n for (let index = 0; index < argv.length; index += 1) {\n const arg = argv[index];\n if (!arg) {\n continue;\n }\n if (arg === \"--env\") {\n result.env = assertEnv(requireFlagValue(arg, argv[++index]));\n } else if (arg === \"--endpoint\") {\n result.endpoint = requireFlagValue(arg, argv[++index]);\n } else if (arg === \"--role\") {\n result.role = assertRole(requireFlagValue(arg, argv[++index]));\n } else if (arg === \"--help\" || arg === \"-h\") {\n result.help = true;\n } else {\n throw new Error(`Unknown argument: ${arg}`);\n }\n }\n return result;\n}\n\nfunction requireFlagValue(flag: string, value: string | undefined): string {\n if (!value || value.startsWith(\"--\")) {\n throw new Error(`${flag} requires a value.`);\n }\n return value;\n}\n","#!/usr/bin/env node\nimport { resolveEndpoint } from \"../core/config/endpoints\";\nimport { runConnector } from \"../adapters/mcp/connector\";\nimport { renderError } from \"../core/output/render\";\nimport { parseMcpArgs } from \"../adapters/mcp/args\";\nimport { resolveRole, HARNESS_ROLES } from \"../core/harness/role\";\n\ntry {\n const args = parseMcpArgs(process.argv.slice(2));\n if (args.help) {\n process.stdout.write(\n `Usage: mcp [--env prod|dev] [--endpoint URL] [--role ${HARNESS_ROLES.join(\"|\")}]\\n`,\n );\n process.exit(0);\n }\n const resolved = resolveEndpoint({ env: args.env, endpoint: args.endpoint });\n // 플래그가 없으면 환경변수 — Codex 처럼 MCP 설정이 전역뿐인 에이전트를 위한 길이다.\n const resolvedRole = resolveRole({ role: args.role });\n await runConnector({\n endpoint: resolved.endpoint,\n role: resolvedRole.role ?? null,\n input: process.stdin,\n output: process.stdout,\n errorOutput: process.stderr,\n });\n} catch (error) {\n process.stderr.write(renderError(error));\n process.exitCode = 1;\n}\n"],"mappings":"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","names":["createHash","createHash","isRecord","isRecord","isRecord"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ainecto/mcp",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "AiNecto CLI — connect your coding agent to AiNecto. Set up a repository with: npx -y ainecto@latest init (agents: ask the user for the role first).",
5
5
  "mcpName": "io.github.codelives/ainecto",
6
6
  "type": "module",
@@ -16,9 +16,7 @@
16
16
  },
17
17
  "bin": {
18
18
  "mcp": "dist/bin/mcp.js",
19
- "ainecto": "dist/bin/ainecto.js",
20
- "ai-erd": "dist/bin/ainecto.js",
21
- "ai-erd-mcp": "dist/bin/mcp.js"
19
+ "ainecto": "dist/bin/ainecto.js"
22
20
  },
23
21
  "files": [
24
22
  "dist"