@obov/mext 0.0.0-stage → 0.2.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.
Files changed (155) hide show
  1. package/dist/build-cli.d.ts +39 -0
  2. package/dist/build-cli.js +167 -0
  3. package/dist/build-cli.js.map +1 -0
  4. package/dist/build.d.ts +65 -0
  5. package/dist/build.js +252 -0
  6. package/dist/build.js.map +1 -0
  7. package/dist/cli/args.d.ts +107 -0
  8. package/dist/cli/args.js +621 -0
  9. package/dist/cli/args.js.map +1 -0
  10. package/dist/cli/bin.d.ts +2 -0
  11. package/dist/cli/bin.js +13 -0
  12. package/dist/cli/bin.js.map +1 -0
  13. package/dist/cli/build.d.ts +11 -0
  14. package/dist/cli/build.js +87 -0
  15. package/dist/cli/build.js.map +1 -0
  16. package/dist/cli/dev-lock.d.ts +23 -0
  17. package/dist/cli/dev-lock.js +68 -0
  18. package/dist/cli/dev-lock.js.map +1 -0
  19. package/dist/cli/dev-server.d.ts +20 -0
  20. package/dist/cli/dev-server.js +75 -0
  21. package/dist/cli/dev-server.js.map +1 -0
  22. package/dist/cli/dev.d.ts +21 -0
  23. package/dist/cli/dev.js +565 -0
  24. package/dist/cli/dev.js.map +1 -0
  25. package/dist/cli/help.d.ts +31 -0
  26. package/dist/cli/help.js +238 -0
  27. package/dist/cli/help.js.map +1 -0
  28. package/dist/cli/io.d.ts +26 -0
  29. package/dist/cli/io.js +74 -0
  30. package/dist/cli/io.js.map +1 -0
  31. package/dist/cli/login.d.ts +32 -0
  32. package/dist/cli/login.js +284 -0
  33. package/dist/cli/login.js.map +1 -0
  34. package/dist/cli/logout.d.ts +9 -0
  35. package/dist/cli/logout.js +105 -0
  36. package/dist/cli/logout.js.map +1 -0
  37. package/dist/cli/main.d.ts +19 -0
  38. package/dist/cli/main.js +77 -0
  39. package/dist/cli/main.js.map +1 -0
  40. package/dist/cli/mcp.d.ts +16 -0
  41. package/dist/cli/mcp.js +92 -0
  42. package/dist/cli/mcp.js.map +1 -0
  43. package/dist/cli/output.d.ts +78 -0
  44. package/dist/cli/output.js +229 -0
  45. package/dist/cli/output.js.map +1 -0
  46. package/dist/cli/remote.d.ts +17 -0
  47. package/dist/cli/remote.js +35 -0
  48. package/dist/cli/remote.js.map +1 -0
  49. package/dist/cli/run.d.ts +14 -0
  50. package/dist/cli/run.js +218 -0
  51. package/dist/cli/run.js.map +1 -0
  52. package/dist/cli/start.d.ts +8 -0
  53. package/dist/cli/start.js +37 -0
  54. package/dist/cli/start.js.map +1 -0
  55. package/dist/client/cache.d.ts +104 -0
  56. package/dist/client/cache.js +208 -0
  57. package/dist/client/cache.js.map +1 -0
  58. package/dist/client/main.d.ts +31 -0
  59. package/dist/client/main.js +101 -0
  60. package/dist/client/main.js.map +1 -0
  61. package/dist/client/manifest.d.ts +19 -0
  62. package/dist/client/manifest.js +68 -0
  63. package/dist/client/manifest.js.map +1 -0
  64. package/dist/client/runner.d.ts +52 -0
  65. package/dist/client/runner.js +563 -0
  66. package/dist/client/runner.js.map +1 -0
  67. package/dist/command.d.ts +75 -0
  68. package/dist/command.js +44 -0
  69. package/dist/command.js.map +1 -0
  70. package/dist/config.d.ts +111 -0
  71. package/dist/config.js +197 -0
  72. package/dist/config.js.map +1 -0
  73. package/dist/errors.d.ts +74 -0
  74. package/dist/errors.js +38 -0
  75. package/dist/errors.js.map +1 -0
  76. package/dist/ids.d.ts +6 -0
  77. package/dist/ids.js +12 -0
  78. package/dist/ids.js.map +1 -0
  79. package/dist/index.d.ts +46 -0
  80. package/dist/index.js +27 -0
  81. package/dist/index.js.map +1 -0
  82. package/dist/instrumentation.d.ts +97 -0
  83. package/dist/instrumentation.js +165 -0
  84. package/dist/instrumentation.js.map +1 -0
  85. package/dist/invoke.d.ts +64 -0
  86. package/dist/invoke.js +152 -0
  87. package/dist/invoke.js.map +1 -0
  88. package/dist/lookup.d.ts +9 -0
  89. package/dist/lookup.js +27 -0
  90. package/dist/lookup.js.map +1 -0
  91. package/dist/manifest.d.ts +68 -0
  92. package/dist/manifest.js +77 -0
  93. package/dist/manifest.js.map +1 -0
  94. package/dist/mcp/constants.d.ts +9 -0
  95. package/dist/mcp/constants.js +10 -0
  96. package/dist/mcp/constants.js.map +1 -0
  97. package/dist/mcp/dispatch.d.ts +66 -0
  98. package/dist/mcp/dispatch.js +249 -0
  99. package/dist/mcp/dispatch.js.map +1 -0
  100. package/dist/mcp/help.d.ts +57 -0
  101. package/dist/mcp/help.js +136 -0
  102. package/dist/mcp/help.js.map +1 -0
  103. package/dist/mcp/http.d.ts +96 -0
  104. package/dist/mcp/http.js +368 -0
  105. package/dist/mcp/http.js.map +1 -0
  106. package/dist/mcp/index.d.ts +15 -0
  107. package/dist/mcp/index.js +9 -0
  108. package/dist/mcp/index.js.map +1 -0
  109. package/dist/mcp/install.d.ts +17 -0
  110. package/dist/mcp/install.js +36 -0
  111. package/dist/mcp/install.js.map +1 -0
  112. package/dist/mcp/mrtr.d.ts +82 -0
  113. package/dist/mcp/mrtr.js +130 -0
  114. package/dist/mcp/mrtr.js.map +1 -0
  115. package/dist/mcp/server.d.ts +63 -0
  116. package/dist/mcp/server.js +210 -0
  117. package/dist/mcp/server.js.map +1 -0
  118. package/dist/mcp/stdio.d.ts +75 -0
  119. package/dist/mcp/stdio.js +222 -0
  120. package/dist/mcp/stdio.js.map +1 -0
  121. package/dist/principal.d.ts +16 -0
  122. package/dist/principal.js +24 -0
  123. package/dist/principal.js.map +1 -0
  124. package/dist/registry.d.ts +90 -0
  125. package/dist/registry.js +169 -0
  126. package/dist/registry.js.map +1 -0
  127. package/dist/remote/browser.d.ts +15 -0
  128. package/dist/remote/browser.js +47 -0
  129. package/dist/remote/browser.js.map +1 -0
  130. package/dist/remote/client.d.ts +113 -0
  131. package/dist/remote/client.js +312 -0
  132. package/dist/remote/client.js.map +1 -0
  133. package/dist/remote/credentials.d.ts +48 -0
  134. package/dist/remote/credentials.js +196 -0
  135. package/dist/remote/credentials.js.map +1 -0
  136. package/dist/remote/index.d.ts +8 -0
  137. package/dist/remote/index.js +5 -0
  138. package/dist/remote/index.js.map +1 -0
  139. package/dist/remote/oauth.d.ts +186 -0
  140. package/dist/remote/oauth.js +507 -0
  141. package/dist/remote/oauth.js.map +1 -0
  142. package/dist/scan.d.ts +34 -0
  143. package/dist/scan.js +155 -0
  144. package/dist/scan.js.map +1 -0
  145. package/dist/start.d.ts +63 -0
  146. package/dist/start.js +269 -0
  147. package/dist/start.js.map +1 -0
  148. package/dist/suggest.d.ts +7 -0
  149. package/dist/suggest.js +45 -0
  150. package/dist/suggest.js.map +1 -0
  151. package/dist/version.d.ts +4 -0
  152. package/dist/version.js +13 -0
  153. package/dist/version.js.map +1 -0
  154. package/package.json +49 -4
  155. package/README.md +0 -3
@@ -0,0 +1,222 @@
1
+ import { Writable } from "node:stream";
2
+ import { serveStdio, StdioServerTransport } from "@modelcontextprotocol/server/stdio";
3
+ import { localPrincipal } from "../principal.js";
4
+ import { createMcpServerFactory } from "./server.js";
5
+ /**
6
+ * MCP stdio (2026-07-28 · 2025 레거시 모두 SDK serveStdio). 줄 단위 JSON-RPC, 연결 하나 = McpServer 하나.
7
+ * notifications/cancelled → ctx.signal abort (SDK). stdin EOF = 연결 종료, 진행 중 요청은 abort 되고 응답하지 않는다.
8
+ * 헤더 검증 · Origin · 바인딩 · Authorization 은 해당 없음. 주체 = 로컬 (kind local).
9
+ */
10
+ export function serveMcpStdio(options) {
11
+ const core = createMcpServerFactory({
12
+ registry: options.registry,
13
+ principal: options.principal ?? localPrincipal(),
14
+ ...(options.app ? { app: options.app } : {}),
15
+ ...(options.tools ? { tools: options.tools } : {}),
16
+ ...(options.name ? { name: options.name } : {}),
17
+ ...(options.instructions !== undefined ? { instructions: options.instructions } : {}),
18
+ ...(options.capabilities ? { capabilities: options.capabilities } : {}),
19
+ ...(options.requestStateKey !== undefined ? { requestStateKey: options.requestStateKey } : {}),
20
+ ...(options.progressInterval !== undefined ? { progressInterval: options.progressInterval } : {}),
21
+ ...(options.instrumentation ? { instrumentation: options.instrumentation } : {}),
22
+ ...(options.manifestHash !== undefined ? { manifestHash: options.manifestHash } : {}),
23
+ transport: "stdio",
24
+ });
25
+ const onConnect = options.onConnect;
26
+ const report = (error) => options.onerror?.(error instanceof Error ? error : new Error(String(error)));
27
+ /** 인스턴스별 정리 완료 (onConnect settle + cleanup). closed 는 이것들을 기다린다 */
28
+ const settled = new Set();
29
+ const factory = onConnect
30
+ ? async (ctx) => {
31
+ const server = (await core(ctx));
32
+ settled.add(attachConnection(server, ctx.era, onConnect, report));
33
+ return server;
34
+ }
35
+ : core;
36
+ const transport = new StdioServerTransport(options.stdin, options.stdout);
37
+ let resolveClosed;
38
+ const closed = new Promise((resolve) => {
39
+ resolveClosed = resolve;
40
+ });
41
+ let finishing = false;
42
+ const finish = () => {
43
+ if (finishing)
44
+ return;
45
+ finishing = true;
46
+ void waitCleanups(settled, report).then(resolveClosed);
47
+ };
48
+ const wrapClose = () => {
49
+ const sdkOnClose = transport.onclose;
50
+ transport.onclose = () => {
51
+ try {
52
+ sdkOnClose?.();
53
+ }
54
+ finally {
55
+ finish();
56
+ }
57
+ };
58
+ };
59
+ let handle;
60
+ const era = options.era ?? (options.capabilities?.experimental?.[CLAUDE_CHANNEL] !== undefined ? "legacy" : "auto");
61
+ if (era === "legacy") {
62
+ // serveStdio 는 server/discover 를 받으면 2026-07-28 로 고정한다 → 우회.
63
+ // SDK 기본 supportedProtocolVersions = 2025 버전만 → McpServer 생성자가 server/discover 를 등록하지 않는다
64
+ const ready = Promise.resolve(factory({ era: "legacy" })).then(async (product) => {
65
+ const server = product;
66
+ server.server.onerror = report;
67
+ await server.connect(transport);
68
+ wrapClose();
69
+ return server;
70
+ });
71
+ ready.catch((error) => {
72
+ report(error);
73
+ finish();
74
+ });
75
+ handle = {
76
+ async close() {
77
+ const server = await ready.catch(() => undefined);
78
+ await server?.close();
79
+ },
80
+ };
81
+ }
82
+ else {
83
+ handle = serveStdio(factory, options.onerror ? { transport, onerror: options.onerror } : { transport });
84
+ // serveStdio 가 onclose 를 동기적으로 설정한 뒤 감싼다 (stdin 이벤트는 비동기)
85
+ wrapClose();
86
+ }
87
+ return {
88
+ closed,
89
+ async close() {
90
+ await handle.close();
91
+ finish();
92
+ await closed;
93
+ },
94
+ };
95
+ }
96
+ /** Claude Code channel capability. 선언하면 stdio 기본 era = legacy */
97
+ const CLAUDE_CHANNEL = "claude/channel";
98
+ /** 연결 종료 후 onConnect cleanup 대기 상한 (ms). 넘으면 오류 보고 후 종료 진행 */
99
+ export const CLEANUP_TIMEOUT_MS = 5_000;
100
+ async function waitCleanups(settled, report) {
101
+ if (settled.size === 0)
102
+ return;
103
+ let timer;
104
+ const timeout = new Promise((resolve) => {
105
+ timer = setTimeout(() => resolve("timeout"), CLEANUP_TIMEOUT_MS);
106
+ timer.unref?.();
107
+ });
108
+ const result = await Promise.race([Promise.all(settled), timeout]);
109
+ clearTimeout(timer);
110
+ if (result === "timeout")
111
+ report(new Error(`mcp.onConnect cleanup did not finish within ${CLEANUP_TIMEOUT_MS}ms`));
112
+ }
113
+ /**
114
+ * McpServer 인스턴스 하나 = stdio 연결 하나. onConnect 시점: 2025 = notifications/initialized
115
+ * (initialize 응답 전 알림은 클라이언트가 버릴 수 있음), 2026-07-28 = connect 직후 (handshake 없음).
116
+ * SDK 가 era 판별용 probe 인스턴스를 버리면 그 인스턴스의 onclose 로 cleanup 된다.
117
+ * 반환 = 인스턴스 종료 후 (onConnect settle · cleanup 완료) resolve. 시작 전 종료 = 바로 resolve
118
+ */
119
+ function attachConnection(server, era, onConnect, report) {
120
+ const inner = server.server;
121
+ let connected;
122
+ let resolveDone;
123
+ const done = new Promise((resolve) => {
124
+ resolveDone = resolve;
125
+ });
126
+ let closed = false;
127
+ const handlers = new Map();
128
+ // SDK 가 등록하지 않은 method 만 fallback 으로 온다 (사양 알림은 SDK handler · era 필터가 먼저)
129
+ inner.fallbackNotificationHandler = async (notification) => {
130
+ const handler = handlers.get(notification.method);
131
+ if (!handler)
132
+ return;
133
+ const params = (notification.params ?? {});
134
+ try {
135
+ await handler(params);
136
+ }
137
+ catch (error) {
138
+ report(error);
139
+ }
140
+ };
141
+ const conn = {
142
+ era,
143
+ notify: (method, params) => inner.notification({ method, ...(params !== undefined ? { params } : {}) }),
144
+ onNotification(method, handler) {
145
+ handlers.set(method, handler);
146
+ return () => {
147
+ if (handlers.get(method) === handler)
148
+ handlers.delete(method);
149
+ };
150
+ },
151
+ };
152
+ const start = () => {
153
+ if (connected || closed)
154
+ return;
155
+ connected = Promise.resolve().then(() => onConnect(conn));
156
+ connected.catch(report);
157
+ };
158
+ const previousClose = inner.onclose;
159
+ inner.onclose = () => {
160
+ previousClose?.();
161
+ if (closed)
162
+ return;
163
+ closed = true;
164
+ if (!connected)
165
+ return resolveDone();
166
+ connected
167
+ .then(async (cleanup) => {
168
+ if (typeof cleanup === "function")
169
+ await cleanup();
170
+ }, () => { })
171
+ .catch(report)
172
+ .finally(resolveDone);
173
+ };
174
+ if (era === "legacy") {
175
+ const previousInit = inner.oninitialized;
176
+ inner.oninitialized = () => {
177
+ previousInit?.();
178
+ start();
179
+ };
180
+ }
181
+ else {
182
+ const connect = server.connect.bind(server);
183
+ server.connect = async (transport) => {
184
+ await connect(transport);
185
+ start();
186
+ };
187
+ }
188
+ return done;
189
+ }
190
+ /**
191
+ * D4: stdio 모드에서 transport 외의 stdout 쓰기 (console.log · process.stdout.write) 를 stderr 로 돌린다.
192
+ * 반환된 wire 만 실제 stdout 에 쓴다. 명령어 모듈 import 전에 호출해야 top-level 출력도 막는다.
193
+ * 자식 프로세스 · fd 1 직접 쓰기 (fs.writeSync(1)) 는 막지 못한다.
194
+ */
195
+ export function redirectStdout() {
196
+ const stdout = process.stdout;
197
+ const original = stdout.write;
198
+ const toStderr = (...args) => process.stderr.write.apply(process.stderr, args);
199
+ stdout.write = toStderr;
200
+ // EPIPE 등은 wire 의 write 콜백으로 transport 에 전달된다. process.stdout 의 미처리 error 로 죽지 않게
201
+ const ignore = () => { };
202
+ stdout.on("error", ignore);
203
+ const wire = new Writable({
204
+ write(chunk, _encoding, callback) {
205
+ const write = original;
206
+ write.call(stdout, chunk, (error) => callback(error ?? null));
207
+ },
208
+ });
209
+ let restored = false;
210
+ return {
211
+ wire,
212
+ restore() {
213
+ if (restored)
214
+ return;
215
+ restored = true;
216
+ if (stdout.write === toStderr)
217
+ stdout.write = original;
218
+ stdout.off("error", ignore);
219
+ },
220
+ };
221
+ }
222
+ //# sourceMappingURL=stdio.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stdio.js","sourceRoot":"","sources":["../../src/mcp/stdio.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Readable } from \"node:stream\"\nimport { Writable } from \"node:stream\"\nimport type { McpServer, McpServerFactory } from \"@modelcontextprotocol/server\"\nimport { serveStdio, StdioServerTransport } from \"@modelcontextprotocol/server/stdio\"\nimport type { MextApp } from \"../command.ts\"\nimport type { McpCapabilities, McpConnectHandler, McpConnection } from \"../config.ts\"\nimport { localPrincipal, type Principal } from \"../principal.ts\"\nimport type { Registry } from \"../registry.ts\"\nimport type { Instrumentation } from \"../instrumentation.ts\"\nimport type { ToolExposure } from \"./dispatch.ts\"\nimport { createMcpServerFactory, type McpCoreOptions } from \"./server.ts\"\n\nexport interface McpStdioOptions {\n registry: Registry\n app?: () => MextApp | Promise<MextApp>\n tools?: ToolExposure\n /** serverInfo.name. 기본 \"mext\" */\n name?: string\n /** initialize · server/discover 결과 instructions */\n instructions?: string\n /** 서버 capabilities 에 병합 (experimental) */\n capabilities?: McpCapabilities\n /**\n * 연결 하나에 1회 (2025 = notifications/initialized 후, 2026-07-28 = 연결 직후).\n * conn.notify = 요청과 무관한 서버 발신 알림. 반환 함수 = 연결 종료 시 cleanup. 예외 = onerror\n */\n onConnect?: McpConnectHandler\n /**\n * auto = 2026-07-28 · 2025 모두 (SDK serveStdio 가 판별). legacy = 2025 만:\n * server/discover = -32601 → 클라이언트가 initialize 로 재시도 (Claude Code channel 은 2025 연결에서만 전달).\n * 기본 = capabilities.experimental[\"claude/channel\"] 선언 시 legacy, 그 외 auto\n */\n era?: \"auto\" | \"legacy\"\n /** 연결 주체 (로컬 모드). 기본 localPrincipal() */\n principal?: Principal\n /** 기본 process.stdin */\n stdin?: Readable\n /**\n * MCP 메시지 출력. 기본 process.stdout.\n * D4: process.stdout 을 쓰는 경우 redirectStdout() 의 wire 를 넘겨 핸들러 출력이 메시지와 섞이지 않게 한다\n */\n stdout?: Writable\n /** 응답에 영향 없는 오류 보고 (SDK 내부 오류 · 깨진 메시지) */\n onerror?: (error: Error) => void\n /** MRTR requestState 서명 키 (createMcpServerFactory 와 같음) */\n requestStateKey?: McpCoreOptions[\"requestStateKey\"]\n /** 주기 progress 간격 (ms). 0 = 끔 */\n progressInterval?: number\n /** loadInstrumentation 결과 (onCommandError · span, transport = \"stdio\") */\n instrumentation?: Instrumentation\n /** _meta[\"dev.mext/manifestHash\"]. McpCoreOptions.manifestHash (기본 = registry 로 계산) */\n manifestHash?: string\n}\n\nexport interface McpStdioServer {\n /** stdin EOF · stdout 오류 · close() 로 연결이 끝나면 resolve */\n closed: Promise<void>\n close(): Promise<void>\n}\n\n/**\n * MCP stdio (2026-07-28 · 2025 레거시 모두 SDK serveStdio). 줄 단위 JSON-RPC, 연결 하나 = McpServer 하나.\n * notifications/cancelled → ctx.signal abort (SDK). stdin EOF = 연결 종료, 진행 중 요청은 abort 되고 응답하지 않는다.\n * 헤더 검증 · Origin · 바인딩 · Authorization 은 해당 없음. 주체 = 로컬 (kind local).\n */\nexport function serveMcpStdio(options: McpStdioOptions): McpStdioServer {\n const core = createMcpServerFactory({\n registry: options.registry,\n principal: options.principal ?? localPrincipal(),\n ...(options.app ? { app: options.app } : {}),\n ...(options.tools ? { tools: options.tools } : {}),\n ...(options.name ? { name: options.name } : {}),\n ...(options.instructions !== undefined ? { instructions: options.instructions } : {}),\n ...(options.capabilities ? { capabilities: options.capabilities } : {}),\n ...(options.requestStateKey !== undefined ? { requestStateKey: options.requestStateKey } : {}),\n ...(options.progressInterval !== undefined ? { progressInterval: options.progressInterval } : {}),\n ...(options.instrumentation ? { instrumentation: options.instrumentation } : {}),\n ...(options.manifestHash !== undefined ? { manifestHash: options.manifestHash } : {}),\n transport: \"stdio\",\n })\n const onConnect = options.onConnect\n const report = (error: unknown): void => options.onerror?.(error instanceof Error ? error : new Error(String(error)))\n /** 인스턴스별 정리 완료 (onConnect settle + cleanup). closed 는 이것들을 기다린다 */\n const settled = new Set<Promise<void>>()\n const factory: McpServerFactory = onConnect\n ? async (ctx) => {\n const server = (await core(ctx)) as McpServer\n settled.add(attachConnection(server, ctx.era, onConnect, report))\n return server\n }\n : core\n const transport = new StdioServerTransport(options.stdin, options.stdout)\n let resolveClosed!: () => void\n const closed = new Promise<void>((resolve) => {\n resolveClosed = resolve\n })\n let finishing = false\n const finish = (): void => {\n if (finishing) return\n finishing = true\n void waitCleanups(settled, report).then(resolveClosed)\n }\n const wrapClose = (): void => {\n const sdkOnClose = transport.onclose\n transport.onclose = () => {\n try {\n sdkOnClose?.()\n } finally {\n finish()\n }\n }\n }\n let handle: { close(): Promise<void> }\n const era = options.era ?? (options.capabilities?.experimental?.[CLAUDE_CHANNEL] !== undefined ? \"legacy\" : \"auto\")\n if (era === \"legacy\") {\n // serveStdio 는 server/discover 를 받으면 2026-07-28 로 고정한다 → 우회.\n // SDK 기본 supportedProtocolVersions = 2025 버전만 → McpServer 생성자가 server/discover 를 등록하지 않는다\n const ready = Promise.resolve(factory({ era: \"legacy\" } as Parameters<McpServerFactory>[0])).then(async (product) => {\n const server = product as McpServer\n server.server.onerror = report\n await server.connect(transport)\n wrapClose()\n return server\n })\n ready.catch((error: unknown) => {\n report(error)\n finish()\n })\n handle = {\n async close() {\n const server = await ready.catch(() => undefined)\n await server?.close()\n },\n }\n } else {\n handle = serveStdio(factory, options.onerror ? { transport, onerror: options.onerror } : { transport })\n // serveStdio 가 onclose 를 동기적으로 설정한 뒤 감싼다 (stdin 이벤트는 비동기)\n wrapClose()\n }\n return {\n closed,\n async close() {\n await handle.close()\n finish()\n await closed\n },\n }\n}\n\n/** Claude Code channel capability. 선언하면 stdio 기본 era = legacy */\nconst CLAUDE_CHANNEL = \"claude/channel\"\n\n/** 연결 종료 후 onConnect cleanup 대기 상한 (ms). 넘으면 오류 보고 후 종료 진행 */\nexport const CLEANUP_TIMEOUT_MS = 5_000\n\nasync function waitCleanups(settled: ReadonlySet<Promise<void>>, report: (error: unknown) => void): Promise<void> {\n if (settled.size === 0) return\n let timer: ReturnType<typeof setTimeout> | undefined\n const timeout = new Promise<\"timeout\">((resolve) => {\n timer = setTimeout(() => resolve(\"timeout\"), CLEANUP_TIMEOUT_MS)\n timer.unref?.()\n })\n const result = await Promise.race([Promise.all(settled), timeout])\n clearTimeout(timer)\n if (result === \"timeout\") report(new Error(`mcp.onConnect cleanup did not finish within ${CLEANUP_TIMEOUT_MS}ms`))\n}\n\n/**\n * McpServer 인스턴스 하나 = stdio 연결 하나. onConnect 시점: 2025 = notifications/initialized\n * (initialize 응답 전 알림은 클라이언트가 버릴 수 있음), 2026-07-28 = connect 직후 (handshake 없음).\n * SDK 가 era 판별용 probe 인스턴스를 버리면 그 인스턴스의 onclose 로 cleanup 된다.\n * 반환 = 인스턴스 종료 후 (onConnect settle · cleanup 완료) resolve. 시작 전 종료 = 바로 resolve\n */\nfunction attachConnection(\n server: McpServer,\n era: McpConnection[\"era\"],\n onConnect: McpConnectHandler,\n report: (error: unknown) => void,\n): Promise<void> {\n const inner = server.server\n let connected: Promise<unknown> | undefined\n let resolveDone!: () => void\n const done = new Promise<void>((resolve) => {\n resolveDone = resolve\n })\n let closed = false\n const handlers = new Map<string, (params: Record<string, unknown>) => void | Promise<void>>()\n // SDK 가 등록하지 않은 method 만 fallback 으로 온다 (사양 알림은 SDK handler · era 필터가 먼저)\n inner.fallbackNotificationHandler = async (notification) => {\n const handler = handlers.get(notification.method)\n if (!handler) return\n const params = (notification.params ?? {}) as Record<string, unknown>\n try {\n await handler(params)\n } catch (error) {\n report(error)\n }\n }\n const conn: McpConnection = {\n era,\n notify: (method, params) => inner.notification({ method, ...(params !== undefined ? { params } : {}) } as never),\n onNotification(method, handler) {\n handlers.set(method, handler)\n return () => {\n if (handlers.get(method) === handler) handlers.delete(method)\n }\n },\n }\n const start = (): void => {\n if (connected || closed) return\n connected = Promise.resolve().then(() => onConnect(conn))\n connected.catch(report)\n }\n const previousClose = inner.onclose\n inner.onclose = () => {\n previousClose?.()\n if (closed) return\n closed = true\n if (!connected) return resolveDone()\n connected\n .then(async (cleanup) => {\n if (typeof cleanup === \"function\") await cleanup()\n }, () => {})\n .catch(report)\n .finally(resolveDone)\n }\n if (era === \"legacy\") {\n const previousInit = inner.oninitialized\n inner.oninitialized = () => {\n previousInit?.()\n start()\n }\n } else {\n const connect = server.connect.bind(server)\n server.connect = async (transport) => {\n await connect(transport)\n start()\n }\n }\n return done\n}\n\nexport interface StdoutRedirect {\n /** 원래 process.stdout 으로 쓰는 스트림. MCP transport 전용 */\n wire: Writable\n /** process.stdout.write 원복 */\n restore(): void\n}\n\n/**\n * D4: stdio 모드에서 transport 외의 stdout 쓰기 (console.log · process.stdout.write) 를 stderr 로 돌린다.\n * 반환된 wire 만 실제 stdout 에 쓴다. 명령어 모듈 import 전에 호출해야 top-level 출력도 막는다.\n * 자식 프로세스 · fd 1 직접 쓰기 (fs.writeSync(1)) 는 막지 못한다.\n */\nexport function redirectStdout(): StdoutRedirect {\n const stdout = process.stdout\n const original = stdout.write\n const toStderr = (...args: unknown[]): boolean =>\n (process.stderr.write as (...a: unknown[]) => boolean).apply(process.stderr, args)\n stdout.write = toStderr as typeof stdout.write\n // EPIPE 등은 wire 의 write 콜백으로 transport 에 전달된다. process.stdout 의 미처리 error 로 죽지 않게\n const ignore = (): void => {}\n stdout.on(\"error\", ignore)\n const wire = new Writable({\n write(chunk: Buffer | string, _encoding, callback) {\n const write = original as (chunk: Buffer | string, done: (error?: Error | null) => void) => boolean\n write.call(stdout, chunk, (error) => callback(error ?? null))\n },\n })\n let restored = false\n return {\n wire,\n restore() {\n if (restored) return\n restored = true\n if (stdout.write === (toStderr as typeof stdout.write)) stdout.write = original\n stdout.off(\"error\", ignore)\n },\n }\n}\n"]}
@@ -0,0 +1,16 @@
1
+ /** 요청 주체. 서버가 요청마다 생성 (HTTP · CLI 원격), 로컬 · stdio = kind local */
2
+ export interface Principal {
3
+ /** "local:obov" | "user_123" | "anonymous" */
4
+ id: string;
5
+ kind: "local" | "token" | "anonymous";
6
+ /** "*" = 전체 */
7
+ scopes: string[];
8
+ /** 검증된 토큰 내용. 토큰 원문은 포함하지 않는다 */
9
+ claims?: Record<string, unknown>;
10
+ }
11
+ /** 인증 없는 주체. scopes 없음 → readOnly 명령어만 */
12
+ export declare const ANONYMOUS: Principal;
13
+ /** 로컬 · stdio 실행의 기본 주체: { id: "local:<OS 사용자>", kind: "local", scopes: ["*"] } */
14
+ export declare function localPrincipal(user?: string): Principal;
15
+ /** "*" = 전체 */
16
+ export declare function hasScope(principal: Principal, scope: string): boolean;
@@ -0,0 +1,24 @@
1
+ import { userInfo } from "node:os";
2
+ /** 인증 없는 주체. scopes 없음 → readOnly 명령어만 */
3
+ export const ANONYMOUS = Object.freeze({
4
+ id: "anonymous",
5
+ kind: "anonymous",
6
+ scopes: Object.freeze([]),
7
+ });
8
+ /** 로컬 · stdio 실행의 기본 주체: { id: "local:<OS 사용자>", kind: "local", scopes: ["*"] } */
9
+ export function localPrincipal(user = osUser()) {
10
+ return { id: `local:${user}`, kind: "local", scopes: ["*"] };
11
+ }
12
+ /** "*" = 전체 */
13
+ export function hasScope(principal, scope) {
14
+ return principal.scopes.includes("*") || principal.scopes.includes(scope);
15
+ }
16
+ function osUser() {
17
+ try {
18
+ return userInfo().username;
19
+ }
20
+ catch {
21
+ return process.env.USER ?? process.env.USERNAME ?? "unknown";
22
+ }
23
+ }
24
+ //# sourceMappingURL=principal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"principal.js","sourceRoot":"","sources":["../src/principal.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAA;AAalC,0CAA0C;AAC1C,MAAM,CAAC,MAAM,SAAS,GAAc,MAAM,CAAC,MAAM,CAAC;IAChD,EAAE,EAAE,WAAW;IACf,IAAI,EAAE,WAAW;IACjB,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,EAAE,CAAwB;CACjD,CAAc,CAAA;AAEf,mFAAmF;AACnF,MAAM,UAAU,cAAc,CAAC,IAAI,GAAW,MAAM,EAAE;IACpD,OAAO,EAAE,EAAE,EAAE,SAAS,IAAI,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,GAAG,CAAC,EAAE,CAAA;AAC9D,CAAC;AAED,eAAe;AACf,MAAM,UAAU,QAAQ,CAAC,SAAoB,EAAE,KAAa;IAC1D,OAAO,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAA;AAC3E,CAAC;AAED,SAAS,MAAM;IACb,IAAI,CAAC;QACH,OAAO,QAAQ,EAAE,CAAC,QAAQ,CAAA;IAC5B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,OAAO,CAAC,GAAG,CAAC,QAAQ,IAAI,SAAS,CAAA;IAC9D,CAAC;AACH,CAAC","sourcesContent":["import { userInfo } from \"node:os\"\n\n/** 요청 주체. 서버가 요청마다 생성 (HTTP · CLI 원격), 로컬 · stdio = kind local */\nexport interface Principal {\n /** \"local:obov\" | \"user_123\" | \"anonymous\" */\n id: string\n kind: \"local\" | \"token\" | \"anonymous\"\n /** \"*\" = 전체 */\n scopes: string[]\n /** 검증된 토큰 내용. 토큰 원문은 포함하지 않는다 */\n claims?: Record<string, unknown>\n}\n\n/** 인증 없는 주체. scopes 없음 → readOnly 명령어만 */\nexport const ANONYMOUS: Principal = Object.freeze({\n id: \"anonymous\",\n kind: \"anonymous\",\n scopes: Object.freeze([]) as unknown as string[],\n}) as Principal\n\n/** 로컬 · stdio 실행의 기본 주체: { id: \"local:<OS 사용자>\", kind: \"local\", scopes: [\"*\"] } */\nexport function localPrincipal(user: string = osUser()): Principal {\n return { id: `local:${user}`, kind: \"local\", scopes: [\"*\"] }\n}\n\n/** \"*\" = 전체 */\nexport function hasScope(principal: Principal, scope: string): boolean {\n return principal.scopes.includes(\"*\") || principal.scopes.includes(scope)\n}\n\nfunction osUser(): string {\n try {\n return userInfo().username\n } catch {\n return process.env.USER ?? process.env.USERNAME ?? \"unknown\"\n }\n}\n"]}
@@ -0,0 +1,90 @@
1
+ import { type Command } from "./command.ts";
2
+ import { type BuildIssue } from "./errors.ts";
3
+ export type JsonSchema = Record<string, unknown>;
4
+ export interface RegistryCommandInput {
5
+ /** 정식 ID. 점 구분 kebab-case 세그먼트 */
6
+ id: string;
7
+ command: Command;
8
+ /** commands/ 기준 상대 경로 (posix). 스캔 결과에만 있음 */
9
+ file?: string;
10
+ }
11
+ export interface RegistryNamespaceInput {
12
+ /** "" = 루트 */
13
+ id: string;
14
+ description?: string;
15
+ }
16
+ export interface RegistryInput {
17
+ commands: readonly RegistryCommandInput[];
18
+ namespaces?: readonly RegistryNamespaceInput[];
19
+ }
20
+ export interface RegisteredCommand {
21
+ id: string;
22
+ /**
23
+ * 메타데이터 (description · 안전성 · scopes · examples · positional · aliases) 의 원본.
24
+ * eager (createRegistry) = 실행 가능한 명령어 그대로. lazy (createLazyRegistry) = manifest 로 만든 메타데이터만
25
+ * (input · execute 는 load() 결과에서)
26
+ */
27
+ command: Command;
28
+ /** lazy Registry: 명령어 모듈 import (1회 캐시) → 실행 가능한 명령어. 없음 = command 가 실행 가능. loadCommand 로 호출 */
29
+ load?: () => Promise<Command>;
30
+ /** 별칭의 전체 ID (정렬) */
31
+ aliases: readonly string[];
32
+ file: string | undefined;
33
+ /** input 의 JSON Schema 2020-12 (io: input) */
34
+ inputSchema: JsonSchema;
35
+ /** output 의 JSON Schema 2020-12 (io: output) */
36
+ outputSchema: JsonSchema | undefined;
37
+ }
38
+ export interface NamespaceInfo {
39
+ /** "" = 루트 */
40
+ id: string;
41
+ /** _index.md 내용 */
42
+ description: string | undefined;
43
+ /** 직속 명령어 정식 ID (정렬). index.ts 명령어 (네임스페이스와 같은 ID) 는 상위 네임스페이스에 속한다 */
44
+ commands: readonly string[];
45
+ /** 직속 하위 네임스페이스 ID (정렬) */
46
+ namespaces: readonly string[];
47
+ }
48
+ export interface Registry {
49
+ /** 정식 ID → 명령어. ID 순 */
50
+ readonly commands: ReadonlyMap<string, RegisteredCommand>;
51
+ /** 별칭 전체 ID → 정식 ID */
52
+ readonly aliases: ReadonlyMap<string, string>;
53
+ /** 네임스페이스 ID → 정보. 루트 ("") 포함, ID 순 */
54
+ readonly namespaces: ReadonlyMap<string, NamespaceInfo>;
55
+ }
56
+ /** 명령어 목록에서 Registry 생성. ID · 별칭 · 스키마 규칙 위반은 MextBuildError */
57
+ export declare function createRegistry(input: RegistryInput): Registry;
58
+ /**
59
+ * createRegistry 의 수집형 (mext build 의 일괄 검증용). 던지지 않는다.
60
+ * registry = 규칙을 통과한 명령어만, issues = 위반 전부
61
+ */
62
+ export declare function collectRegistry(input: RegistryInput): {
63
+ registry: Registry;
64
+ issues: BuildIssue[];
65
+ };
66
+ /** lazy Registry 의 명령어: manifest 메타데이터 + 모듈 로더 (규칙 검증은 mext build 가 이미 함) */
67
+ export interface LazyCommandInput {
68
+ id: string;
69
+ /** 메타데이터만 있는 명령어 (input · execute 는 load 결과). RegisteredCommand.command */
70
+ command: Command;
71
+ /** 별칭의 전체 ID */
72
+ aliases: readonly string[];
73
+ file?: string;
74
+ inputSchema: JsonSchema;
75
+ outputSchema?: JsonSchema;
76
+ /** 실행 시 1회 호출 (결과 캐시, 실패는 다음 호출에서 재시도) */
77
+ load: () => Promise<Command>;
78
+ }
79
+ /**
80
+ * manifest (mext build 산출) 로 Registry 생성. 명령어 모듈을 import 하지 않는다 (mext start · handler.js).
81
+ * help · tools/list · UNKNOWN_COMMAND · 권한 검사 = 메타데이터만, 입력 검증 · execute = loadCommand 후
82
+ */
83
+ export declare function createLazyRegistry(input: {
84
+ commands: readonly LazyCommandInput[];
85
+ namespaces?: readonly RegistryNamespaceInput[];
86
+ }): Registry;
87
+ /** 실행 가능한 명령어. eager = entry.command, lazy = entry.load() (모듈 import) */
88
+ export declare function loadCommand(entry: RegisteredCommand): Promise<Command>;
89
+ /** 정식 ID 또는 별칭으로 명령어 찾기 */
90
+ export declare function resolveCommand(registry: Registry, id: string): RegisteredCommand | undefined;
@@ -0,0 +1,169 @@
1
+ import * as z from "zod";
2
+ import { isCommand } from "./command.js";
3
+ import { MextBuildError } from "./errors.js";
4
+ import { isSegment, parentNamespace, RESERVED_ROOT_SEGMENTS } from "./ids.js";
5
+ /** 명령어 목록에서 Registry 생성. ID · 별칭 · 스키마 규칙 위반은 MextBuildError */
6
+ export function createRegistry(input) {
7
+ const { registry, issues } = collectRegistry(input);
8
+ if (issues.length > 0)
9
+ throw new MextBuildError(issues);
10
+ return registry;
11
+ }
12
+ /**
13
+ * createRegistry 의 수집형 (mext build 의 일괄 검증용). 던지지 않는다.
14
+ * registry = 규칙을 통과한 명령어만, issues = 위반 전부
15
+ */
16
+ export function collectRegistry(input) {
17
+ const issues = [];
18
+ const accepted = [];
19
+ const seen = new Map();
20
+ for (const entry of input.commands) {
21
+ const where = { id: entry.id, file: entry.file };
22
+ if (!isCommand(entry.command)) {
23
+ issues.push({ code: "INVALID_COMMAND", message: "not a defineCommand result", ...where });
24
+ continue;
25
+ }
26
+ const segments = entry.id.split(".");
27
+ const bad = segments.find((s) => !isSegment(s));
28
+ if (bad !== undefined) {
29
+ issues.push({ code: "INVALID_SEGMENT", message: `segment "${bad}" is not kebab-case`, ...where });
30
+ continue;
31
+ }
32
+ if (RESERVED_ROOT_SEGMENTS.includes(segments[0])) {
33
+ issues.push({ code: "RESERVED_ID", message: `root segment "${segments[0]}" is reserved`, ...where });
34
+ continue;
35
+ }
36
+ const prev = seen.get(entry.id);
37
+ if (prev) {
38
+ issues.push({ code: "DUPLICATE_ID", message: `duplicate ID (also ${prev.file ?? prev.id})`, ...where });
39
+ continue;
40
+ }
41
+ seen.set(entry.id, entry);
42
+ accepted.push(entry);
43
+ }
44
+ // 네임스페이스 = 명령어 ID 의 모든 상위 경로
45
+ const namespaceIds = new Set([""]);
46
+ for (const { id } of accepted) {
47
+ for (let ns = parentNamespace(id); ns !== ""; ns = parentNamespace(ns))
48
+ namespaceIds.add(ns);
49
+ }
50
+ const aliases = new Map();
51
+ const aliasesOf = new Map();
52
+ for (const entry of accepted) {
53
+ const where = { id: entry.id, file: entry.file };
54
+ const parent = parentNamespace(entry.id);
55
+ const own = [];
56
+ for (const alias of entry.command.aliases) {
57
+ if (!isSegment(alias)) {
58
+ issues.push({ code: "INVALID_ALIAS", message: `alias "${alias}" is not a kebab-case segment`, ...where });
59
+ continue;
60
+ }
61
+ if (parent === "") {
62
+ issues.push({ code: "INVALID_ALIAS", message: `alias "${alias}": aliases are not allowed at the root`, ...where });
63
+ continue;
64
+ }
65
+ const full = `${parent}.${alias}`;
66
+ const owner = seen.has(full) ? full : aliases.get(full);
67
+ if (owner !== undefined || namespaceIds.has(full)) {
68
+ issues.push({
69
+ code: "DUPLICATE_ID",
70
+ message: `alias ${full} duplicates ${owner !== undefined ? owner : "a namespace"}`,
71
+ ...where,
72
+ });
73
+ continue;
74
+ }
75
+ aliases.set(full, entry.id);
76
+ own.push(full);
77
+ }
78
+ aliasesOf.set(entry.id, own.sort());
79
+ }
80
+ const commands = new Map();
81
+ for (const entry of [...accepted].sort((a, b) => compareIds(a.id, b.id))) {
82
+ const where = { id: entry.id, file: entry.file };
83
+ let inputSchema;
84
+ let outputSchema;
85
+ try {
86
+ inputSchema = toJsonSchema(entry.command.input, "input");
87
+ outputSchema = entry.command.output ? toJsonSchema(entry.command.output, "output") : undefined;
88
+ }
89
+ catch (error) {
90
+ issues.push({ code: "SCHEMA_UNREPRESENTABLE", message: errorMessage(error), ...where });
91
+ continue;
92
+ }
93
+ commands.set(entry.id, {
94
+ id: entry.id,
95
+ command: entry.command,
96
+ aliases: aliasesOf.get(entry.id) ?? [],
97
+ file: entry.file,
98
+ inputSchema,
99
+ outputSchema,
100
+ });
101
+ }
102
+ const namespaces = namespaceTable(namespaceIds, [...commands.keys()], input.namespaces);
103
+ return { registry: { commands, aliases, namespaces }, issues };
104
+ }
105
+ /**
106
+ * manifest (mext build 산출) 로 Registry 생성. 명령어 모듈을 import 하지 않는다 (mext start · handler.js).
107
+ * help · tools/list · UNKNOWN_COMMAND · 권한 검사 = 메타데이터만, 입력 검증 · execute = loadCommand 후
108
+ */
109
+ export function createLazyRegistry(input) {
110
+ const commands = new Map();
111
+ const aliases = new Map();
112
+ for (const entry of [...input.commands].sort((a, b) => compareIds(a.id, b.id))) {
113
+ const load = entry.load;
114
+ let pending;
115
+ commands.set(entry.id, {
116
+ id: entry.id,
117
+ command: entry.command,
118
+ aliases: [...entry.aliases].sort(),
119
+ file: entry.file,
120
+ inputSchema: entry.inputSchema,
121
+ outputSchema: entry.outputSchema,
122
+ load: () => (pending ??= load().catch((error) => {
123
+ pending = undefined;
124
+ throw error;
125
+ })),
126
+ });
127
+ for (const alias of entry.aliases)
128
+ aliases.set(alias, entry.id);
129
+ }
130
+ const namespaceIds = new Set([""]);
131
+ for (const id of commands.keys()) {
132
+ for (let ns = parentNamespace(id); ns !== ""; ns = parentNamespace(ns))
133
+ namespaceIds.add(ns);
134
+ }
135
+ return { commands, aliases, namespaces: namespaceTable(namespaceIds, [...commands.keys()], input.namespaces) };
136
+ }
137
+ /** 실행 가능한 명령어. eager = entry.command, lazy = entry.load() (모듈 import) */
138
+ export function loadCommand(entry) {
139
+ return entry.load ? entry.load() : Promise.resolve(entry.command);
140
+ }
141
+ function namespaceTable(namespaceIds, commandIds, inputs) {
142
+ const descriptions = new Map();
143
+ for (const ns of inputs ?? [])
144
+ descriptions.set(ns.id, ns.description);
145
+ const namespaces = new Map();
146
+ for (const id of [...namespaceIds].sort(compareIds)) {
147
+ namespaces.set(id, {
148
+ id,
149
+ description: descriptions.get(id),
150
+ commands: commandIds.filter((c) => parentNamespace(c) === id),
151
+ namespaces: [...namespaceIds].filter((n) => n !== "" && parentNamespace(n) === id).sort(compareIds),
152
+ });
153
+ }
154
+ return namespaces;
155
+ }
156
+ /** 정식 ID 또는 별칭으로 명령어 찾기 */
157
+ export function resolveCommand(registry, id) {
158
+ return registry.commands.get(id) ?? registry.commands.get(registry.aliases.get(id) ?? "");
159
+ }
160
+ function toJsonSchema(schema, io) {
161
+ return z.toJSONSchema(schema, { target: "draft-2020-12", io, unrepresentable: "throw" });
162
+ }
163
+ function compareIds(a, b) {
164
+ return a < b ? -1 : a > b ? 1 : 0;
165
+ }
166
+ function errorMessage(error) {
167
+ return error instanceof Error ? error.message : String(error);
168
+ }
169
+ //# sourceMappingURL=registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.js","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"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","sourcesContent":["import * as z from \"zod\"\nimport { isCommand, type Command } from \"./command.ts\"\nimport { MextBuildError, type BuildIssue } from \"./errors.ts\"\nimport { isSegment, parentNamespace, RESERVED_ROOT_SEGMENTS } from \"./ids.ts\"\n\nexport type JsonSchema = Record<string, unknown>\n\nexport interface RegistryCommandInput {\n /** 정식 ID. 점 구분 kebab-case 세그먼트 */\n id: string\n command: Command\n /** commands/ 기준 상대 경로 (posix). 스캔 결과에만 있음 */\n file?: string\n}\n\nexport interface RegistryNamespaceInput {\n /** \"\" = 루트 */\n id: string\n description?: string\n}\n\nexport interface RegistryInput {\n commands: readonly RegistryCommandInput[]\n namespaces?: readonly RegistryNamespaceInput[]\n}\n\nexport interface RegisteredCommand {\n id: string\n /**\n * 메타데이터 (description · 안전성 · scopes · examples · positional · aliases) 의 원본.\n * eager (createRegistry) = 실행 가능한 명령어 그대로. lazy (createLazyRegistry) = manifest 로 만든 메타데이터만\n * (input · execute 는 load() 결과에서)\n */\n command: Command\n /** lazy Registry: 명령어 모듈 import (1회 캐시) → 실행 가능한 명령어. 없음 = command 가 실행 가능. loadCommand 로 호출 */\n load?: () => Promise<Command>\n /** 별칭의 전체 ID (정렬) */\n aliases: readonly string[]\n file: string | undefined\n /** input 의 JSON Schema 2020-12 (io: input) */\n inputSchema: JsonSchema\n /** output 의 JSON Schema 2020-12 (io: output) */\n outputSchema: JsonSchema | undefined\n}\n\nexport interface NamespaceInfo {\n /** \"\" = 루트 */\n id: string\n /** _index.md 내용 */\n description: string | undefined\n /** 직속 명령어 정식 ID (정렬). index.ts 명령어 (네임스페이스와 같은 ID) 는 상위 네임스페이스에 속한다 */\n commands: readonly string[]\n /** 직속 하위 네임스페이스 ID (정렬) */\n namespaces: readonly string[]\n}\n\nexport interface Registry {\n /** 정식 ID → 명령어. ID 순 */\n readonly commands: ReadonlyMap<string, RegisteredCommand>\n /** 별칭 전체 ID → 정식 ID */\n readonly aliases: ReadonlyMap<string, string>\n /** 네임스페이스 ID → 정보. 루트 (\"\") 포함, ID 순 */\n readonly namespaces: ReadonlyMap<string, NamespaceInfo>\n}\n\n/** 명령어 목록에서 Registry 생성. ID · 별칭 · 스키마 규칙 위반은 MextBuildError */\nexport function createRegistry(input: RegistryInput): Registry {\n const { registry, issues } = collectRegistry(input)\n if (issues.length > 0) throw new MextBuildError(issues)\n return registry\n}\n\n/**\n * createRegistry 의 수집형 (mext build 의 일괄 검증용). 던지지 않는다.\n * registry = 규칙을 통과한 명령어만, issues = 위반 전부\n */\nexport function collectRegistry(input: RegistryInput): { registry: Registry; issues: BuildIssue[] } {\n const issues: BuildIssue[] = []\n const accepted: RegistryCommandInput[] = []\n const seen = new Map<string, RegistryCommandInput>()\n\n for (const entry of input.commands) {\n const where = { id: entry.id, file: entry.file }\n if (!isCommand(entry.command)) {\n issues.push({ code: \"INVALID_COMMAND\", message: \"not a defineCommand result\", ...where })\n continue\n }\n const segments = entry.id.split(\".\")\n const bad = segments.find((s) => !isSegment(s))\n if (bad !== undefined) {\n issues.push({ code: \"INVALID_SEGMENT\", message: `segment \"${bad}\" is not kebab-case`, ...where })\n continue\n }\n if (RESERVED_ROOT_SEGMENTS.includes(segments[0]!)) {\n issues.push({ code: \"RESERVED_ID\", message: `root segment \"${segments[0]}\" is reserved`, ...where })\n continue\n }\n const prev = seen.get(entry.id)\n if (prev) {\n issues.push({ code: \"DUPLICATE_ID\", message: `duplicate ID (also ${prev.file ?? prev.id})`, ...where })\n continue\n }\n seen.set(entry.id, entry)\n accepted.push(entry)\n }\n\n // 네임스페이스 = 명령어 ID 의 모든 상위 경로\n const namespaceIds = new Set<string>([\"\"])\n for (const { id } of accepted) {\n for (let ns = parentNamespace(id); ns !== \"\"; ns = parentNamespace(ns)) namespaceIds.add(ns)\n }\n\n const aliases = new Map<string, string>()\n const aliasesOf = new Map<string, string[]>()\n for (const entry of accepted) {\n const where = { id: entry.id, file: entry.file }\n const parent = parentNamespace(entry.id)\n const own: string[] = []\n for (const alias of entry.command.aliases) {\n if (!isSegment(alias)) {\n issues.push({ code: \"INVALID_ALIAS\", message: `alias \"${alias}\" is not a kebab-case segment`, ...where })\n continue\n }\n if (parent === \"\") {\n issues.push({ code: \"INVALID_ALIAS\", message: `alias \"${alias}\": aliases are not allowed at the root`, ...where })\n continue\n }\n const full = `${parent}.${alias}`\n const owner = seen.has(full) ? full : aliases.get(full)\n if (owner !== undefined || namespaceIds.has(full)) {\n issues.push({\n code: \"DUPLICATE_ID\",\n message: `alias ${full} duplicates ${owner !== undefined ? owner : \"a namespace\"}`,\n ...where,\n })\n continue\n }\n aliases.set(full, entry.id)\n own.push(full)\n }\n aliasesOf.set(entry.id, own.sort())\n }\n\n const commands = new Map<string, RegisteredCommand>()\n for (const entry of [...accepted].sort((a, b) => compareIds(a.id, b.id))) {\n const where = { id: entry.id, file: entry.file }\n let inputSchema: JsonSchema\n let outputSchema: JsonSchema | undefined\n try {\n inputSchema = toJsonSchema(entry.command.input, \"input\")\n outputSchema = entry.command.output ? toJsonSchema(entry.command.output, \"output\") : undefined\n } catch (error) {\n issues.push({ code: \"SCHEMA_UNREPRESENTABLE\", message: errorMessage(error), ...where })\n continue\n }\n commands.set(entry.id, {\n id: entry.id,\n command: entry.command,\n aliases: aliasesOf.get(entry.id) ?? [],\n file: entry.file,\n inputSchema,\n outputSchema,\n })\n }\n\n const namespaces = namespaceTable(namespaceIds, [...commands.keys()], input.namespaces)\n return { registry: { commands, aliases, namespaces }, issues }\n}\n\n/** lazy Registry 의 명령어: manifest 메타데이터 + 모듈 로더 (규칙 검증은 mext build 가 이미 함) */\nexport interface LazyCommandInput {\n id: string\n /** 메타데이터만 있는 명령어 (input · execute 는 load 결과). RegisteredCommand.command */\n command: Command\n /** 별칭의 전체 ID */\n aliases: readonly string[]\n file?: string\n inputSchema: JsonSchema\n outputSchema?: JsonSchema\n /** 실행 시 1회 호출 (결과 캐시, 실패는 다음 호출에서 재시도) */\n load: () => Promise<Command>\n}\n\n/**\n * manifest (mext build 산출) 로 Registry 생성. 명령어 모듈을 import 하지 않는다 (mext start · handler.js).\n * help · tools/list · UNKNOWN_COMMAND · 권한 검사 = 메타데이터만, 입력 검증 · execute = loadCommand 후\n */\nexport function createLazyRegistry(input: {\n commands: readonly LazyCommandInput[]\n namespaces?: readonly RegistryNamespaceInput[]\n}): Registry {\n const commands = new Map<string, RegisteredCommand>()\n const aliases = new Map<string, string>()\n for (const entry of [...input.commands].sort((a, b) => compareIds(a.id, b.id))) {\n const load = entry.load\n let pending: Promise<Command> | undefined\n commands.set(entry.id, {\n id: entry.id,\n command: entry.command,\n aliases: [...entry.aliases].sort(),\n file: entry.file,\n inputSchema: entry.inputSchema,\n outputSchema: entry.outputSchema,\n load: () =>\n (pending ??= load().catch((error: unknown) => {\n pending = undefined\n throw error\n })),\n })\n for (const alias of entry.aliases) aliases.set(alias, entry.id)\n }\n const namespaceIds = new Set<string>([\"\"])\n for (const id of commands.keys()) {\n for (let ns = parentNamespace(id); ns !== \"\"; ns = parentNamespace(ns)) namespaceIds.add(ns)\n }\n return { commands, aliases, namespaces: namespaceTable(namespaceIds, [...commands.keys()], input.namespaces) }\n}\n\n/** 실행 가능한 명령어. eager = entry.command, lazy = entry.load() (모듈 import) */\nexport function loadCommand(entry: RegisteredCommand): Promise<Command> {\n return entry.load ? entry.load() : Promise.resolve(entry.command)\n}\n\nfunction namespaceTable(\n namespaceIds: ReadonlySet<string>,\n commandIds: readonly string[],\n inputs: readonly RegistryNamespaceInput[] | undefined,\n): Map<string, NamespaceInfo> {\n const descriptions = new Map<string, string | undefined>()\n for (const ns of inputs ?? []) descriptions.set(ns.id, ns.description)\n const namespaces = new Map<string, NamespaceInfo>()\n for (const id of [...namespaceIds].sort(compareIds)) {\n namespaces.set(id, {\n id,\n description: descriptions.get(id),\n commands: commandIds.filter((c) => parentNamespace(c) === id),\n namespaces: [...namespaceIds].filter((n) => n !== \"\" && parentNamespace(n) === id).sort(compareIds),\n })\n }\n return namespaces\n}\n\n/** 정식 ID 또는 별칭으로 명령어 찾기 */\nexport function resolveCommand(registry: Registry, id: string): RegisteredCommand | undefined {\n return registry.commands.get(id) ?? registry.commands.get(registry.aliases.get(id) ?? \"\")\n}\n\nfunction toJsonSchema(schema: z.ZodType, io: \"input\" | \"output\"): JsonSchema {\n return z.toJSONSchema(schema, { target: \"draft-2020-12\", io, unrepresentable: \"throw\" }) as JsonSchema\n}\n\nfunction compareIds(a: string, b: string): number {\n return a < b ? -1 : a > b ? 1 : 0\n}\n\nfunction errorMessage(error: unknown): string {\n return error instanceof Error ? error.message : String(error)\n}\n"]}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * login 의 브라우저 판정 · 열기 (spec §5 login 브라우저 판정 · headless).
3
+ * 열지 않음 = SSH 세션 (SSH_CONNECTION · SSH_CLIENT · SSH_TTY) · Linux 에서 DISPLAY · WAYLAND_DISPLAY 없음.
4
+ * 여는 명령 = BROWSER (있으면) → open (macOS) · start (Windows) · xdg-open.
5
+ */
6
+ export interface BrowserDecision {
7
+ open: boolean;
8
+ /** 열지 않는 이유 (안내 출력용) */
9
+ reason?: "ssh" | "no-display";
10
+ }
11
+ /** URL 을 브라우저로 연다. 실패는 무시 (URL 은 항상 stderr 에 출력) */
12
+ export type BrowserOpener = (url: string) => void;
13
+ export declare function browserDecision(env: Record<string, string | undefined>, platform?: NodeJS.Platform): BrowserDecision;
14
+ /** 기본 opener. BROWSER = 명령 (공백 구분 인자, %s 가 있으면 URL 로 치환 · 없으면 끝에 URL) */
15
+ export declare function systemBrowserOpener(env: Record<string, string | undefined>, platform?: NodeJS.Platform): BrowserOpener;