@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,249 @@
1
+ import { isMextError, MextError } from "../errors.js";
2
+ import { invoke } from "../invoke.js";
3
+ import { formatHelp, help, HELP_COMMAND } from "./help.js";
4
+ import { createAskSession, hasPending, inputRequiredResult, } from "./mrtr.js";
5
+ /** 핸들러 예외 (MextError 외) 의 결과 code. CLI --json 과 같음 */
6
+ export const EXECUTION_ERROR = "EXECUTION_ERROR";
7
+ /** 도구 설명에 나열할 명령어 수 상한. 넘으면 help 안내만 */
8
+ const DESCRIPTION_COMMAND_LIMIT = 50;
9
+ /** 노출 방식별 dispatcher 도구 (정렬 순서 고정: execute · query, mutate) */
10
+ export function dispatcherTools(registry, exposure = "execute") {
11
+ const all = [...registry.commands.values()];
12
+ if (exposure === "split") {
13
+ const reads = all.filter((e) => e.command.readOnly);
14
+ const writes = all.filter((e) => !e.command.readOnly);
15
+ return [
16
+ {
17
+ name: "query",
18
+ description: describeTool("query", "Run a read-only command.", reads, 'Commands that are not read-only are rejected here; call them with the "mutate" tool.'),
19
+ inputSchema: dispatcherInputSchema(),
20
+ annotations: { title: "Query", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
21
+ readOnly: true,
22
+ },
23
+ {
24
+ name: "mutate",
25
+ description: describeTool("mutate", "Run a command that changes state.", writes),
26
+ inputSchema: dispatcherInputSchema(),
27
+ annotations: { title: "Mutate", readOnlyHint: false, destructiveHint: true },
28
+ readOnly: false,
29
+ },
30
+ ];
31
+ }
32
+ const readOnly = all.every((e) => e.command.readOnly);
33
+ return [
34
+ {
35
+ name: "execute",
36
+ description: describeTool("execute", "Run a command.", all),
37
+ inputSchema: dispatcherInputSchema(),
38
+ // 단일 dispatcher 는 최악값 (researches/others.md 9)
39
+ annotations: readOnly
40
+ ? { title: "Execute", readOnlyHint: true, destructiveHint: false }
41
+ : { title: "Execute", readOnlyHint: false, destructiveHint: all.some((e) => e.command.destructive) },
42
+ readOnly: false,
43
+ },
44
+ ];
45
+ }
46
+ export function findDispatcherTool(tools, name) {
47
+ return tools.find((t) => t.name === name);
48
+ }
49
+ /**
50
+ * dispatcher 도구 호출: { command, args } → help 또는 invoke.
51
+ * MextError · 핸들러 예외는 isError 결과로 (throw 하지 않음). 인증 (UNAUTHENTICATED → 401) 은 transport 몫.
52
+ * options.input 이 있으면 ctx.ask 가 InputRequiredResult 를 돌려준다 (mrtr.ts).
53
+ */
54
+ export async function callDispatcher(registry, tool, rawArguments, options) {
55
+ try {
56
+ const { command, args } = parseDispatcherArguments(tool, rawArguments);
57
+ if (command === HELP_COMMAND) {
58
+ const target = args?.target;
59
+ if (target !== undefined && typeof target !== "string") {
60
+ throw dispatcherArgumentError(tool, "help", "args.target must be a string", [], {
61
+ path: "args.target",
62
+ code: "invalid_type",
63
+ message: "expected string",
64
+ });
65
+ }
66
+ const toolFor = splitToolFor(options.tools);
67
+ const value = help(registry, target, toolFor ? { toolFor } : {});
68
+ return { content: [{ type: "text", text: formatHelp(value, tool.name) }], structuredContent: value };
69
+ }
70
+ const { input, tools: _tools, ...invokeOptions } = options;
71
+ const result = input
72
+ ? await invokeRound(registry, tool, command, args, invokeOptions, input)
73
+ : await invoke(registry, command, args, { ...invokeOptions, readOnly: tool.readOnly });
74
+ if ("resultType" in result)
75
+ return result;
76
+ const value = result.value === undefined ? null : result.value;
77
+ return { content: [{ type: "text", text: stringify(value) }], structuredContent: value };
78
+ }
79
+ catch (error) {
80
+ return errorResult(error);
81
+ }
82
+ }
83
+ /**
84
+ * MRTR 한 라운드: requestState 검증 → ask 세션으로 invoke. 답 없는 ask 가 있으면 (핸들러가 오류를 삼켜도)
85
+ * 결과를 버리고 InputRequiredResult. elicitation 미선언 클라이언트 = ask 미주입 (INPUT_UNAVAILABLE)
86
+ */
87
+ async function invokeRound(registry, tool, command, args, options, input) {
88
+ const { ask: _ask, ...rest } = options;
89
+ if (!input.elicitation)
90
+ return invoke(registry, command, args, { ...rest, readOnly: tool.readOnly });
91
+ const binding = {
92
+ principal: `${options.principal.kind}:${options.principal.id}`,
93
+ tool: tool.name,
94
+ command,
95
+ args: args ?? {},
96
+ };
97
+ let answers = { ...input.inputResponses };
98
+ if (input.requestState !== undefined) {
99
+ const prior = input.signer.verify(input.requestState, binding);
100
+ if (!prior)
101
+ throw invalidRequestState(tool, command);
102
+ answers = { ...answers, ...prior };
103
+ }
104
+ const session = createAskSession(answers);
105
+ const signal = options.signal ? AbortSignal.any([options.signal, session.signal]) : session.signal;
106
+ try {
107
+ const result = await invoke(registry, command, args, { ...rest, signal, ask: session.ask, readOnly: tool.readOnly });
108
+ return hasPending(session) ? inputRequiredResult(session, input.signer, binding) : result;
109
+ }
110
+ catch (error) {
111
+ if (hasPending(session))
112
+ return inputRequiredResult(session, input.signer, binding);
113
+ throw error;
114
+ }
115
+ }
116
+ /** 서명 · 만료 · 바인딩 검증 실패한 requestState. 처음부터 다시 호출하도록 안내 */
117
+ function invalidRequestState(tool, command) {
118
+ return new MextError("INVALID_ARGUMENT", `Invalid arguments for ${command}: requestState is invalid or expired. Call ${tool.name} again without requestState and inputResponses.`, {
119
+ command,
120
+ missing_fields: [],
121
+ issues: [
122
+ {
123
+ path: "requestState",
124
+ code: "invalid_request_state",
125
+ message: "requestState failed verification (tampered, expired, or for a different call or server key)",
126
+ },
127
+ ],
128
+ expected_schema: tool.inputSchema,
129
+ examples: [],
130
+ });
131
+ }
132
+ /** 오류 → isError 결과. MextError = toJSON, 그 외 = EXECUTION_ERROR */
133
+ export function errorResult(error) {
134
+ const body = isMextError(error)
135
+ ? error.toJSON()
136
+ : { code: EXECUTION_ERROR, message: error instanceof Error ? error.message : String(error) };
137
+ return {
138
+ content: [{ type: "text", text: `${body.code}: ${body.message}\n${stringify(body)}` }],
139
+ structuredContent: { error: body },
140
+ isError: true,
141
+ };
142
+ }
143
+ /** dispatcher 도구 inputSchema. command 를 Mcp-Param-Command 헤더로 복사 (SEP-2243) */
144
+ export function dispatcherInputSchema() {
145
+ return {
146
+ type: "object",
147
+ properties: {
148
+ command: {
149
+ type: "string",
150
+ description: 'Command ID (e.g. "users.get"), or "help" to list namespaces and commands.',
151
+ "x-mcp-header": "Command",
152
+ },
153
+ args: {
154
+ type: "object",
155
+ description: 'Command input. For "help": { "target": "<namespace or command ID>" }.',
156
+ additionalProperties: true,
157
+ },
158
+ },
159
+ required: ["command"],
160
+ additionalProperties: false,
161
+ };
162
+ }
163
+ function parseDispatcherArguments(tool, raw) {
164
+ const input = raw === undefined ? {} : raw;
165
+ if (input === null || typeof input !== "object" || Array.isArray(input)) {
166
+ throw dispatcherArgumentError(tool, tool.name, "arguments must be an object", [], {
167
+ path: "",
168
+ code: "invalid_type",
169
+ message: "expected object",
170
+ });
171
+ }
172
+ const { command, args, ...rest } = input;
173
+ if (command === undefined) {
174
+ throw dispatcherArgumentError(tool, tool.name, "missing required: command", ["command"], {
175
+ path: "command",
176
+ code: "invalid_type",
177
+ message: "expected string, received undefined",
178
+ });
179
+ }
180
+ if (typeof command !== "string") {
181
+ throw dispatcherArgumentError(tool, tool.name, "command must be a string", [], {
182
+ path: "command",
183
+ code: "invalid_type",
184
+ message: "expected string",
185
+ });
186
+ }
187
+ const unknownKeys = Object.keys(rest);
188
+ if (unknownKeys.length > 0) {
189
+ throw dispatcherArgumentError(tool, tool.name, `unrecognized keys: ${unknownKeys.join(", ")}`, [], {
190
+ path: "",
191
+ code: "unrecognized_keys",
192
+ message: `Unrecognized keys: ${unknownKeys.join(", ")}. Put command input under "args".`,
193
+ });
194
+ }
195
+ if (args !== undefined && (args === null || typeof args !== "object" || Array.isArray(args))) {
196
+ throw dispatcherArgumentError(tool, command, "args must be an object", [], {
197
+ path: "args",
198
+ code: "invalid_type",
199
+ message: "expected object",
200
+ });
201
+ }
202
+ return { command, args };
203
+ }
204
+ function dispatcherArgumentError(tool, command, summary, missing, issue) {
205
+ return new MextError("INVALID_ARGUMENT", `Invalid arguments for ${command}: ${summary}`, {
206
+ command,
207
+ missing_fields: missing,
208
+ issues: [issue],
209
+ expected_schema: tool.inputSchema,
210
+ examples: [{ command: HELP_COMMAND }],
211
+ });
212
+ }
213
+ function splitToolFor(tools) {
214
+ if (!tools || tools.length < 2)
215
+ return undefined;
216
+ const query = tools.find((t) => t.readOnly)?.name;
217
+ const mutate = tools.find((t) => !t.readOnly)?.name;
218
+ if (query === undefined || mutate === undefined)
219
+ return undefined;
220
+ return (e) => (e.command.readOnly ? query : mutate);
221
+ }
222
+ function describeTool(name, lead, commands, note) {
223
+ const lines = [
224
+ lead,
225
+ `Arguments: { "command": "<command ID>", "args": { ...input } }. ` +
226
+ `Call ${name}({ "command": "help" }) to list namespaces and commands, ` +
227
+ `${name}({ "command": "help", "args": { "target": "<namespace or command ID>" } }) for input schemas and examples.`,
228
+ ];
229
+ if (note)
230
+ lines.push(note);
231
+ if (commands.length > 0 && commands.length <= DESCRIPTION_COMMAND_LIMIT) {
232
+ lines.push("", "Commands:");
233
+ for (const e of commands)
234
+ lines.push(`- ${e.id}: ${firstLine(e.command.description)}`);
235
+ }
236
+ return lines.join("\n");
237
+ }
238
+ function firstLine(text) {
239
+ return text.split("\n", 1)[0].trim();
240
+ }
241
+ function stringify(value) {
242
+ try {
243
+ return JSON.stringify(value, null, 2) ?? "null";
244
+ }
245
+ catch {
246
+ return String(value);
247
+ }
248
+ }
249
+ //# sourceMappingURL=dispatch.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dispatch.js","sourceRoot":"","sources":["../../src/mcp/dispatch.ts"],"names":[],"mappings":"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","sourcesContent":["import { isMextError, MextError } from \"../errors.ts\"\nimport { invoke, type InvokeOptions } from \"../invoke.ts\"\nimport type { JsonSchema, RegisteredCommand, Registry } from \"../registry.ts\"\nimport { formatHelp, help, HELP_COMMAND } from \"./help.ts\"\nimport {\n createAskSession,\n hasPending,\n inputRequiredResult,\n type InputRequiredResult,\n type InputRound,\n type RequestStateBinding,\n type RequestStateSigner,\n} from \"./mrtr.ts\"\n\n/**\n * MCP 어댑터의 transport 무관 부분: dispatcher 도구 정의 · tools/call 실행 · help · 오류 → 결과 변환.\n * SDK · HTTP 에 의존하지 않는다 (HTTP · stdio 가 공유).\n */\n\n/** execute = 도구 1개 (기본), split = query (readOnly · help) + mutate (그 외) */\nexport type ToolExposure = \"execute\" | \"split\"\n\nexport type DispatcherToolName = \"execute\" | \"query\" | \"mutate\"\n\n/** MCP ToolAnnotations 중 mext 가 쓰는 항목 */\nexport interface DispatcherAnnotations {\n title?: string\n readOnlyHint: boolean\n destructiveHint?: boolean\n idempotentHint?: boolean\n openWorldHint?: boolean\n}\n\nexport interface DispatcherTool {\n name: DispatcherToolName\n description: string\n /** JSON Schema 2020-12. command 에 x-mcp-header: \"Command\" */\n inputSchema: JsonSchema\n annotations: DispatcherAnnotations\n /** readOnly 명령어만 실행 (split 의 query). 그 외 = PERMISSION_DENIED */\n readOnly: boolean\n}\n\nexport interface TextContent {\n type: \"text\"\n text: string\n}\n\n/** MCP CallToolResult 와 같은 형태 (SDK 타입 비의존) */\nexport interface ToolCallResult {\n content: TextContent[]\n /** 성공 = 핸들러 반환값 (undefined 는 null), 오류 = { error: MextError.toJSON() } */\n structuredContent: unknown\n isError?: true\n}\n\n/** 핸들러 예외 (MextError 외) 의 결과 code. CLI --json 과 같음 */\nexport const EXECUTION_ERROR = \"EXECUTION_ERROR\"\n\n/** 도구 설명에 나열할 명령어 수 상한. 넘으면 help 안내만 */\nconst DESCRIPTION_COMMAND_LIMIT = 50\n\n/** 노출 방식별 dispatcher 도구 (정렬 순서 고정: execute · query, mutate) */\nexport function dispatcherTools(registry: Registry, exposure: ToolExposure = \"execute\"): DispatcherTool[] {\n const all = [...registry.commands.values()]\n if (exposure === \"split\") {\n const reads = all.filter((e) => e.command.readOnly)\n const writes = all.filter((e) => !e.command.readOnly)\n return [\n {\n name: \"query\",\n description: describeTool(\n \"query\",\n \"Run a read-only command.\",\n reads,\n 'Commands that are not read-only are rejected here; call them with the \"mutate\" tool.',\n ),\n inputSchema: dispatcherInputSchema(),\n annotations: { title: \"Query\", readOnlyHint: true, destructiveHint: false, idempotentHint: true },\n readOnly: true,\n },\n {\n name: \"mutate\",\n description: describeTool(\"mutate\", \"Run a command that changes state.\", writes),\n inputSchema: dispatcherInputSchema(),\n annotations: { title: \"Mutate\", readOnlyHint: false, destructiveHint: true },\n readOnly: false,\n },\n ]\n }\n const readOnly = all.every((e) => e.command.readOnly)\n return [\n {\n name: \"execute\",\n description: describeTool(\"execute\", \"Run a command.\", all),\n inputSchema: dispatcherInputSchema(),\n // 단일 dispatcher 는 최악값 (researches/others.md 9)\n annotations: readOnly\n ? { title: \"Execute\", readOnlyHint: true, destructiveHint: false }\n : { title: \"Execute\", readOnlyHint: false, destructiveHint: all.some((e) => e.command.destructive) },\n readOnly: false,\n },\n ]\n}\n\nexport function findDispatcherTool(tools: readonly DispatcherTool[], name: string): DispatcherTool | undefined {\n return tools.find((t) => t.name === name)\n}\n\nexport interface DispatchOptions extends Omit<InvokeOptions, \"readOnly\"> {\n /** help 의 tool 표시용. dispatcherTools 결과 */\n tools?: readonly DispatcherTool[]\n /**\n * MRTR (MCP 2026-07-28). 지정하면 ctx.ask = InputRequiredResult 반환 · 재시도의 inputResponses 로 resolve\n * (ask 옵션 무시). requiresConfirmation 확인도 이 경로\n */\n input?: InputRound & { signer: RequestStateSigner }\n}\n\n/** tools/call 결과: 완료 (CallToolResult 형태) 또는 입력 요청 (InputRequiredResult) */\nexport type DispatchResult = ToolCallResult | InputRequiredResult\n\n/**\n * dispatcher 도구 호출: { command, args } → help 또는 invoke.\n * MextError · 핸들러 예외는 isError 결과로 (throw 하지 않음). 인증 (UNAUTHENTICATED → 401) 은 transport 몫.\n * options.input 이 있으면 ctx.ask 가 InputRequiredResult 를 돌려준다 (mrtr.ts).\n */\nexport async function callDispatcher(\n registry: Registry,\n tool: DispatcherTool,\n rawArguments: unknown,\n options: DispatchOptions,\n): Promise<DispatchResult> {\n try {\n const { command, args } = parseDispatcherArguments(tool, rawArguments)\n if (command === HELP_COMMAND) {\n const target = (args as { target?: unknown } | undefined)?.target\n if (target !== undefined && typeof target !== \"string\") {\n throw dispatcherArgumentError(tool, \"help\", \"args.target must be a string\", [], {\n path: \"args.target\",\n code: \"invalid_type\",\n message: \"expected string\",\n })\n }\n const toolFor = splitToolFor(options.tools)\n const value = help(registry, target, toolFor ? { toolFor } : {})\n return { content: [{ type: \"text\", text: formatHelp(value, tool.name) }], structuredContent: value }\n }\n const { input, tools: _tools, ...invokeOptions } = options\n const result = input\n ? await invokeRound(registry, tool, command, args, invokeOptions, input)\n : await invoke(registry, command, args, { ...invokeOptions, readOnly: tool.readOnly })\n if (\"resultType\" in result) return result\n const value = result.value === undefined ? null : result.value\n return { content: [{ type: \"text\", text: stringify(value) }], structuredContent: value }\n } catch (error) {\n return errorResult(error)\n }\n}\n\n/**\n * MRTR 한 라운드: requestState 검증 → ask 세션으로 invoke. 답 없는 ask 가 있으면 (핸들러가 오류를 삼켜도)\n * 결과를 버리고 InputRequiredResult. elicitation 미선언 클라이언트 = ask 미주입 (INPUT_UNAVAILABLE)\n */\nasync function invokeRound(\n registry: Registry,\n tool: DispatcherTool,\n command: string,\n args: unknown,\n options: Omit<InvokeOptions, \"readOnly\">,\n input: NonNullable<DispatchOptions[\"input\"]>,\n): Promise<{ command: string; value: unknown } | InputRequiredResult> {\n const { ask: _ask, ...rest } = options\n if (!input.elicitation) return invoke(registry, command, args, { ...rest, readOnly: tool.readOnly })\n const binding: RequestStateBinding = {\n principal: `${options.principal.kind}:${options.principal.id}`,\n tool: tool.name,\n command,\n args: args ?? {},\n }\n let answers: Record<string, unknown> = { ...input.inputResponses }\n if (input.requestState !== undefined) {\n const prior = input.signer.verify(input.requestState, binding)\n if (!prior) throw invalidRequestState(tool, command)\n answers = { ...answers, ...prior }\n }\n const session = createAskSession(answers)\n const signal = options.signal ? AbortSignal.any([options.signal, session.signal]) : session.signal\n try {\n const result = await invoke(registry, command, args, { ...rest, signal, ask: session.ask, readOnly: tool.readOnly })\n return hasPending(session) ? inputRequiredResult(session, input.signer, binding) : result\n } catch (error) {\n if (hasPending(session)) return inputRequiredResult(session, input.signer, binding)\n throw error\n }\n}\n\n/** 서명 · 만료 · 바인딩 검증 실패한 requestState. 처음부터 다시 호출하도록 안내 */\nfunction invalidRequestState(tool: DispatcherTool, command: string): MextError<\"INVALID_ARGUMENT\"> {\n return new MextError(\n \"INVALID_ARGUMENT\",\n `Invalid arguments for ${command}: requestState is invalid or expired. Call ${tool.name} again without requestState and inputResponses.`,\n {\n command,\n missing_fields: [],\n issues: [\n {\n path: \"requestState\",\n code: \"invalid_request_state\",\n message: \"requestState failed verification (tampered, expired, or for a different call or server key)\",\n },\n ],\n expected_schema: tool.inputSchema,\n examples: [],\n },\n )\n}\n\n/** 오류 → isError 결과. MextError = toJSON, 그 외 = EXECUTION_ERROR */\nexport function errorResult(error: unknown): ToolCallResult {\n const body = isMextError(error)\n ? error.toJSON()\n : { code: EXECUTION_ERROR, message: error instanceof Error ? error.message : String(error) }\n return {\n content: [{ type: \"text\", text: `${body.code}: ${body.message}\\n${stringify(body)}` }],\n structuredContent: { error: body },\n isError: true,\n }\n}\n\n/** dispatcher 도구 inputSchema. command 를 Mcp-Param-Command 헤더로 복사 (SEP-2243) */\nexport function dispatcherInputSchema(): JsonSchema {\n return {\n type: \"object\",\n properties: {\n command: {\n type: \"string\",\n description: 'Command ID (e.g. \"users.get\"), or \"help\" to list namespaces and commands.',\n \"x-mcp-header\": \"Command\",\n },\n args: {\n type: \"object\",\n description: 'Command input. For \"help\": { \"target\": \"<namespace or command ID>\" }.',\n additionalProperties: true,\n },\n },\n required: [\"command\"],\n additionalProperties: false,\n }\n}\n\nfunction parseDispatcherArguments(tool: DispatcherTool, raw: unknown): { command: string; args: unknown } {\n const input = raw === undefined ? {} : raw\n if (input === null || typeof input !== \"object\" || Array.isArray(input)) {\n throw dispatcherArgumentError(tool, tool.name, \"arguments must be an object\", [], {\n path: \"\",\n code: \"invalid_type\",\n message: \"expected object\",\n })\n }\n const { command, args, ...rest } = input as Record<string, unknown>\n if (command === undefined) {\n throw dispatcherArgumentError(tool, tool.name, \"missing required: command\", [\"command\"], {\n path: \"command\",\n code: \"invalid_type\",\n message: \"expected string, received undefined\",\n })\n }\n if (typeof command !== \"string\") {\n throw dispatcherArgumentError(tool, tool.name, \"command must be a string\", [], {\n path: \"command\",\n code: \"invalid_type\",\n message: \"expected string\",\n })\n }\n const unknownKeys = Object.keys(rest)\n if (unknownKeys.length > 0) {\n throw dispatcherArgumentError(tool, tool.name, `unrecognized keys: ${unknownKeys.join(\", \")}`, [], {\n path: \"\",\n code: \"unrecognized_keys\",\n message: `Unrecognized keys: ${unknownKeys.join(\", \")}. Put command input under \"args\".`,\n })\n }\n if (args !== undefined && (args === null || typeof args !== \"object\" || Array.isArray(args))) {\n throw dispatcherArgumentError(tool, command, \"args must be an object\", [], {\n path: \"args\",\n code: \"invalid_type\",\n message: \"expected object\",\n })\n }\n return { command, args }\n}\n\nfunction dispatcherArgumentError(\n tool: DispatcherTool,\n command: string,\n summary: string,\n missing: string[],\n issue: { path: string; code: string; message: string },\n): MextError<\"INVALID_ARGUMENT\"> {\n return new MextError(\"INVALID_ARGUMENT\", `Invalid arguments for ${command}: ${summary}`, {\n command,\n missing_fields: missing,\n issues: [issue],\n expected_schema: tool.inputSchema,\n examples: [{ command: HELP_COMMAND }],\n })\n}\n\nfunction splitToolFor(tools: readonly DispatcherTool[] | undefined): ((e: RegisteredCommand) => string) | undefined {\n if (!tools || tools.length < 2) return undefined\n const query = tools.find((t) => t.readOnly)?.name\n const mutate = tools.find((t) => !t.readOnly)?.name\n if (query === undefined || mutate === undefined) return undefined\n return (e) => (e.command.readOnly ? query : mutate)\n}\n\nfunction describeTool(name: string, lead: string, commands: readonly RegisteredCommand[], note?: string): string {\n const lines = [\n lead,\n `Arguments: { \"command\": \"<command ID>\", \"args\": { ...input } }. ` +\n `Call ${name}({ \"command\": \"help\" }) to list namespaces and commands, ` +\n `${name}({ \"command\": \"help\", \"args\": { \"target\": \"<namespace or command ID>\" } }) for input schemas and examples.`,\n ]\n if (note) lines.push(note)\n if (commands.length > 0 && commands.length <= DESCRIPTION_COMMAND_LIMIT) {\n lines.push(\"\", \"Commands:\")\n for (const e of commands) lines.push(`- ${e.id}: ${firstLine(e.command.description)}`)\n }\n return lines.join(\"\\n\")\n}\n\nfunction firstLine(text: string): string {\n return text.split(\"\\n\", 1)[0]!.trim()\n}\n\nfunction stringify(value: unknown): string {\n try {\n return JSON.stringify(value, null, 2) ?? \"null\"\n } catch {\n return String(value)\n }\n}\n"]}
@@ -0,0 +1,57 @@
1
+ import { type ManifestCommand } from "../manifest.ts";
2
+ import type { RegisteredCommand, Registry } from "../registry.ts";
3
+ /** help 의 예약 명령어 이름. 루트 세그먼트 help 는 Registry 에서 금지 */
4
+ export declare const HELP_COMMAND = "help";
5
+ export interface HelpNamespaceSummary {
6
+ id: string;
7
+ /** _index.md 첫 줄 */
8
+ description?: string;
9
+ }
10
+ export interface HelpCommandSummary {
11
+ id: string;
12
+ description: string;
13
+ readOnly: boolean;
14
+ destructive: boolean;
15
+ requiresConfirmation: boolean;
16
+ /** split 노출에서 이 명령어를 호출할 도구 (query · mutate). execute 노출에서는 없음 */
17
+ tool?: string;
18
+ }
19
+ /** execute({ command: "help" }) */
20
+ export interface RootHelp {
21
+ kind: "root";
22
+ /** commands/_index.md */
23
+ description?: string;
24
+ namespaces: HelpNamespaceSummary[];
25
+ /** 루트 직속 명령어 */
26
+ commands: HelpCommandSummary[];
27
+ }
28
+ /** execute({ command: "help", args: { target: "users" } }) */
29
+ export interface NamespaceHelp {
30
+ kind: "namespace";
31
+ id: string;
32
+ /** _index.md 전체 */
33
+ description?: string;
34
+ namespaces: HelpNamespaceSummary[];
35
+ commands: HelpCommandSummary[];
36
+ /** 같은 ID 의 명령어 (users/index.ts) */
37
+ command?: CommandHelp;
38
+ }
39
+ /** execute({ command: "help", args: { target: "users.get" } }) */
40
+ export interface CommandHelp extends Omit<ManifestCommand, "file"> {
41
+ kind: "command";
42
+ /** 이 명령어 호출이 돌려줄 수 있는 구조화 오류 code */
43
+ errors: string[];
44
+ tool?: string;
45
+ }
46
+ export type Help = RootHelp | NamespaceHelp | CommandHelp;
47
+ export interface HelpOptions {
48
+ /** 명령어 → 호출할 도구 이름. split 노출이 지정 */
49
+ toolFor?: (entry: RegisteredCommand) => string | undefined;
50
+ }
51
+ /**
52
+ * help 3단계: target 없음 = 루트 (네임스페이스 · 루트 명령어), 네임스페이스 ID = 그 명령어 목록,
53
+ * 명령어 ID · 별칭 = 설명 · 입력 스키마 · 예시 · 오류 · 권한. 없는 target = UNKNOWN_COMMAND.
54
+ */
55
+ export declare function help(registry: Registry, target: string | undefined, options?: HelpOptions): Help;
56
+ /** help 의 사람용 텍스트 (MCP text content). 구조는 structuredContent 가 원본 */
57
+ export declare function formatHelp(value: Help, call?: string): string;
@@ -0,0 +1,136 @@
1
+ import { findCommand } from "../invoke.js";
2
+ import { describeCommand } from "../manifest.js";
3
+ /** help 의 예약 명령어 이름. 루트 세그먼트 help 는 Registry 에서 금지 */
4
+ export const HELP_COMMAND = "help";
5
+ /**
6
+ * help 3단계: target 없음 = 루트 (네임스페이스 · 루트 명령어), 네임스페이스 ID = 그 명령어 목록,
7
+ * 명령어 ID · 별칭 = 설명 · 입력 스키마 · 예시 · 오류 · 권한. 없는 target = UNKNOWN_COMMAND.
8
+ */
9
+ export function help(registry, target, options = {}) {
10
+ const id = target?.trim() ?? "";
11
+ if (id === "") {
12
+ const root = registry.namespaces.get("");
13
+ const out = {
14
+ kind: "root",
15
+ namespaces: root.namespaces.map((n) => namespaceSummary(registry, n)),
16
+ commands: root.commands.map((c) => commandSummary(registry.commands.get(c), options)),
17
+ };
18
+ if (root.description !== undefined)
19
+ out.description = root.description;
20
+ return out;
21
+ }
22
+ const ns = registry.namespaces.get(id);
23
+ if (ns) {
24
+ const out = {
25
+ kind: "namespace",
26
+ id,
27
+ namespaces: ns.namespaces.map((n) => namespaceSummary(registry, n)),
28
+ commands: ns.commands.map((c) => commandSummary(registry.commands.get(c), options)),
29
+ };
30
+ if (ns.description !== undefined)
31
+ out.description = ns.description;
32
+ const self = registry.commands.get(id);
33
+ if (self)
34
+ out.command = commandHelp(self, options);
35
+ return out;
36
+ }
37
+ return commandHelp(findCommand(registry, id), options);
38
+ }
39
+ function namespaceSummary(registry, id) {
40
+ const description = registry.namespaces.get(id)?.description;
41
+ const first = description?.split("\n").find((l) => l.trim() !== "");
42
+ return first === undefined ? { id } : { id, description: first.replace(/^#+\s*/, "").trim() };
43
+ }
44
+ function commandSummary(entry, options) {
45
+ const c = entry.command;
46
+ const out = {
47
+ id: entry.id,
48
+ description: c.description,
49
+ readOnly: c.readOnly,
50
+ destructive: c.destructive,
51
+ requiresConfirmation: c.requiresConfirmation,
52
+ };
53
+ const tool = options.toolFor?.(entry);
54
+ if (tool !== undefined)
55
+ out.tool = tool;
56
+ return out;
57
+ }
58
+ function commandHelp(entry, options) {
59
+ const { file: _file, ...meta } = describeCommand(entry);
60
+ const c = entry.command;
61
+ const errors = ["INVALID_ARGUMENT"];
62
+ if (!c.readOnly || c.scopes.length > 0)
63
+ errors.push("UNAUTHENTICATED", "PERMISSION_DENIED");
64
+ if (c.requiresConfirmation)
65
+ errors.push("INPUT_UNAVAILABLE", "INPUT_DECLINED");
66
+ const out = { kind: "command", ...meta, errors };
67
+ const tool = options.toolFor?.(entry);
68
+ if (tool !== undefined)
69
+ out.tool = tool;
70
+ return out;
71
+ }
72
+ /** help 의 사람용 텍스트 (MCP text content). 구조는 structuredContent 가 원본 */
73
+ export function formatHelp(value, call = "execute") {
74
+ const lines = [];
75
+ const hint = (target) => `${call}({ command: "help", args: { target: "${target}" } })`;
76
+ const listCommands = (commands) => {
77
+ if (commands.length === 0)
78
+ return;
79
+ lines.push("Commands:");
80
+ const width = Math.max(...commands.map((c) => c.id.length));
81
+ for (const c of commands) {
82
+ const flags = safetyFlags(c);
83
+ const via = c.tool !== undefined ? ` (via ${c.tool})` : "";
84
+ lines.push(` ${c.id.padEnd(width)} ${c.description}${flags ? ` [${flags}]` : ""}${via}`);
85
+ }
86
+ };
87
+ const listNamespaces = (namespaces) => {
88
+ if (namespaces.length === 0)
89
+ return;
90
+ lines.push("Namespaces:");
91
+ const width = Math.max(...namespaces.map((n) => n.id.length));
92
+ for (const n of namespaces)
93
+ lines.push(` ${n.id.padEnd(width)} ${n.description ?? ""}`.trimEnd());
94
+ };
95
+ if (value.kind === "command") {
96
+ lines.push(`${value.id}: ${value.description}`);
97
+ if (value.aliases.length > 0)
98
+ lines.push(`Aliases: ${value.aliases.join(", ")}`);
99
+ const flags = safetyFlags(value);
100
+ lines.push(`Safety: ${flags || "none"}`);
101
+ if (value.scopes.length > 0)
102
+ lines.push(`Scopes: ${value.scopes.join(", ")}`);
103
+ if (value.tool !== undefined)
104
+ lines.push(`Tool: ${value.tool}`);
105
+ lines.push("Input schema:", JSON.stringify(value.inputSchema, null, 2));
106
+ if (value.examples.length > 0) {
107
+ lines.push("Examples:");
108
+ for (const e of value.examples)
109
+ lines.push(` ${JSON.stringify(e)}`);
110
+ }
111
+ lines.push(`Errors: ${value.errors.join(", ")}`);
112
+ return lines.join("\n");
113
+ }
114
+ if (value.kind === "namespace")
115
+ lines.push(value.id);
116
+ if (value.description !== undefined)
117
+ lines.push(value.description, "");
118
+ listNamespaces(value.namespaces);
119
+ listCommands(value.commands);
120
+ if (value.kind === "namespace" && value.command !== undefined) {
121
+ lines.push("", formatHelp(value.command, call));
122
+ }
123
+ lines.push("", `Details: ${hint(value.kind === "root" ? "<namespace or command>" : `${value.id}.<command>`)}`);
124
+ return lines.join("\n").replace(/^\n+/, "");
125
+ }
126
+ function safetyFlags(c) {
127
+ const flags = [];
128
+ if (c.readOnly)
129
+ flags.push("read-only");
130
+ if (c.destructive)
131
+ flags.push("destructive");
132
+ if (c.requiresConfirmation)
133
+ flags.push("requires confirmation");
134
+ return flags.join(", ");
135
+ }
136
+ //# sourceMappingURL=help.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"help.js","sourceRoot":"","sources":["../../src/mcp/help.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAC1C,OAAO,EAAE,eAAe,EAAwB,MAAM,gBAAgB,CAAA;AAGtE,sDAAsD;AACtD,MAAM,CAAC,MAAM,YAAY,GAAG,MAAM,CAAA;AAuDlC;;;GAGG;AACH,MAAM,UAAU,IAAI,CAAC,QAAkB,EAAE,MAA0B,EAAE,OAAO,GAAgB,EAAE;IAC5F,MAAM,EAAE,GAAG,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,CAAA;IAC/B,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;QACd,MAAM,IAAI,GAAG,QAAQ,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAE,CAAA;QACzC,MAAM,GAAG,GAAa;YACpB,IAAI,EAAE,MAAM;YACZ,UAAU,EAAE,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,gBAAgB,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC;YACrE,QAAQ,EAAE,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,cAAc,CAAC,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAE,EAAE,OAAO,CAAC,CAAC;SACvF,CAAA;QACD,IAAI,IAAI,CAAC,WAAW,KAAK,SAAS;YAAE,GAAG,CAAC,WAAW,GAAG,IAAI,CAAC,WAAW,CAAA;QACtE,OAAO,GAAG,CAAA;IACZ,CAAC;IACD,MAAM,EAAE,GAAG,QAAQ,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC,CAAA;IACtC,IAAI,EAAE,EAAE,CAAC;QACP,MAAM,GAAG,GAAkB;YACzB,IAAI,EAAE,WAAW;YACjB,EAAE;YACF,UAAU,EAAE,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,gBAAgB,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC;YACnE,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,cAAc,CAAC,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAE,EAAE,OAAO,CAAC,CAAC;SACrF,CAAA;QACD,IAAI,EAAE,CAAC,WAAW,KAAK,SAAS;YAAE,GAAG,CAAC,WAAW,GAAG,EAAE,CAAC,WAAW,CAAA;QAClE,MAAM,IAAI,GAAG,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,CAAA;QACtC,IAAI,IAAI;YAAE,GAAG,CAAC,OAAO,GAAG,WAAW,CAAC,IAAI,EAAE,OAAO,CAAC,CAAA;QAClD,OAAO,GAAG,CAAA;IACZ,CAAC;IACD,OAAO,WAAW,CAAC,WAAW,CAAC,QAAQ,EAAE,EAAE,CAAC,EAAE,OAAO,CAAC,CAAA;AACxD,CAAC;AAED,SAAS,gBAAgB,CAAC,QAAkB,EAAE,EAAU;IACtD,MAAM,WAAW,GAAG,QAAQ,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,WAAW,CAAA;IAC5D,MAAM,KAAK,GAAG,WAAW,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,CAAA;IACnE,OAAO,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,WAAW,EAAE,KAAK,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,EAAE,CAAA;AAC/F,CAAC;AAED,SAAS,cAAc,CAAC,KAAwB,EAAE,OAAoB;IACpE,MAAM,CAAC,GAAG,KAAK,CAAC,OAAO,CAAA;IACvB,MAAM,GAAG,GAAuB;QAC9B,EAAE,EAAE,KAAK,CAAC,EAAE;QACZ,WAAW,EAAE,CAAC,CAAC,WAAW;QAC1B,QAAQ,EAAE,CAAC,CAAC,QAAQ;QACpB,WAAW,EAAE,CAAC,CAAC,WAAW;QAC1B,oBAAoB,EAAE,CAAC,CAAC,oBAAoB;KAC7C,CAAA;IACD,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC,KAAK,CAAC,CAAA;IACrC,IAAI,IAAI,KAAK,SAAS;QAAE,GAAG,CAAC,IAAI,GAAG,IAAI,CAAA;IACvC,OAAO,GAAG,CAAA;AACZ,CAAC;AAED,SAAS,WAAW,CAAC,KAAwB,EAAE,OAAoB;IACjE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,IAAI,EAAE,GAAG,eAAe,CAAC,KAAK,CAAC,CAAA;IACvD,MAAM,CAAC,GAAG,KAAK,CAAC,OAAO,CAAA;IACvB,MAAM,MAAM,GAAG,CAAC,kBAAkB,CAAC,CAAA;IACnC,IAAI,CAAC,CAAC,CAAC,QAAQ,IAAI,CAAC,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC;QAAE,MAAM,CAAC,IAAI,CAAC,iBAAiB,EAAE,mBAAmB,CAAC,CAAA;IAC3F,IAAI,CAAC,CAAC,oBAAoB;QAAE,MAAM,CAAC,IAAI,CAAC,mBAAmB,EAAE,gBAAgB,CAAC,CAAA;IAC9E,MAAM,GAAG,GAAgB,EAAE,IAAI,EAAE,SAAS,EAAE,GAAG,IAAI,EAAE,MAAM,EAAE,CAAA;IAC7D,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC,KAAK,CAAC,CAAA;IACrC,IAAI,IAAI,KAAK,SAAS;QAAE,GAAG,CAAC,IAAI,GAAG,IAAI,CAAA;IACvC,OAAO,GAAG,CAAA;AACZ,CAAC;AAED,oEAAoE;AACpE,MAAM,UAAU,UAAU,CAAC,KAAW,EAAE,IAAI,GAAG,SAAS;IACtD,MAAM,KAAK,GAAa,EAAE,CAAA;IAC1B,MAAM,IAAI,GAAG,CAAC,MAAc,EAAE,EAAE,CAAC,GAAG,IAAI,wCAAwC,MAAM,QAAQ,CAAA;IAC9F,MAAM,YAAY,GAAG,CAAC,QAA8B,EAAE,EAAE;QACtD,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;YAAE,OAAM;QACjC,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,CAAA;QACvB,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,CAAA;QAC3D,KAAK,MAAM,CAAC,IAAI,QAAQ,EAAE,CAAC;YACzB,MAAM,KAAK,GAAG,WAAW,CAAC,CAAC,CAAC,CAAA;YAC5B,MAAM,GAAG,GAAG,CAAC,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,EAAE,CAAA;YAC1D,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,WAAW,GAAG,KAAK,CAAC,CAAC,CAAC,KAAK,KAAK,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,GAAG,EAAE,CAAC,CAAA;QAC5F,CAAC;IACH,CAAC,CAAA;IACD,MAAM,cAAc,GAAG,CAAC,UAAkC,EAAE,EAAE;QAC5D,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC;YAAE,OAAM;QACnC,KAAK,CAAC,IAAI,CAAC,aAAa,CAAC,CAAA;QACzB,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,CAAA;QAC7D,KAAK,MAAM,CAAC,IAAI,UAAU;YAAE,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,WAAW,IAAI,EAAE,EAAE,CAAC,OAAO,EAAE,CAAC,CAAA;IACrG,CAAC,CAAA;IAED,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;QAC7B,KAAK,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,EAAE,KAAK,KAAK,CAAC,WAAW,EAAE,CAAC,CAAA;QAC/C,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC;YAAE,KAAK,CAAC,IAAI,CAAC,YAAY,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;QAChF,MAAM,KAAK,GAAG,WAAW,CAAC,KAAK,CAAC,CAAA;QAChC,KAAK,CAAC,IAAI,CAAC,WAAW,KAAK,IAAI,MAAM,EAAE,CAAC,CAAA;QACxC,IAAI,KAAK,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC;YAAE,KAAK,CAAC,IAAI,CAAC,WAAW,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;QAC7E,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS;YAAE,KAAK,CAAC,IAAI,CAAC,SAAS,KAAK,CAAC,IAAI,EAAE,CAAC,CAAA;QAC/D,KAAK,CAAC,IAAI,CAAC,eAAe,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,WAAW,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAA;QACvE,IAAI,KAAK,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC9B,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,CAAA;YACvB,KAAK,MAAM,CAAC,IAAI,KAAK,CAAC,QAAQ;gBAAE,KAAK,CAAC,IAAI,CAAC,KAAK,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAA;QACtE,CAAC;QACD,KAAK,CAAC,IAAI,CAAC,WAAW,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;QAChD,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IACzB,CAAC;IAED,IAAI,KAAK,CAAC,IAAI,KAAK,WAAW;QAAE,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAA;IACpD,IAAI,KAAK,CAAC,WAAW,KAAK,SAAS;QAAE,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC,CAAA;IACtE,cAAc,CAAC,KAAK,CAAC,UAAU,CAAC,CAAA;IAChC,YAAY,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAA;IAC5B,IAAI,KAAK,CAAC,IAAI,KAAK,WAAW,IAAI,KAAK,CAAC,OAAO,KAAK,SAAS,EAAE,CAAC;QAC9D,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,UAAU,CAAC,KAAK,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,CAAA;IACjD,CAAC;IACD,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,YAAY,IAAI,CAAC,KAAK,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,wBAAwB,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,EAAE,YAAY,CAAC,EAAE,CAAC,CAAA;IAC9G,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAA;AAC7C,CAAC;AAED,SAAS,WAAW,CAAC,CAA6E;IAChG,MAAM,KAAK,GAAa,EAAE,CAAA;IAC1B,IAAI,CAAC,CAAC,QAAQ;QAAE,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,CAAA;IACvC,IAAI,CAAC,CAAC,WAAW;QAAE,KAAK,CAAC,IAAI,CAAC,aAAa,CAAC,CAAA;IAC5C,IAAI,CAAC,CAAC,oBAAoB;QAAE,KAAK,CAAC,IAAI,CAAC,uBAAuB,CAAC,CAAA;IAC/D,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;AACzB,CAAC","sourcesContent":["import { findCommand } from \"../invoke.ts\"\nimport { describeCommand, type ManifestCommand } from \"../manifest.ts\"\nimport type { RegisteredCommand, Registry } from \"../registry.ts\"\n\n/** help 의 예약 명령어 이름. 루트 세그먼트 help 는 Registry 에서 금지 */\nexport const HELP_COMMAND = \"help\"\n\nexport interface HelpNamespaceSummary {\n id: string\n /** _index.md 첫 줄 */\n description?: string\n}\n\nexport interface HelpCommandSummary {\n id: string\n description: string\n readOnly: boolean\n destructive: boolean\n requiresConfirmation: boolean\n /** split 노출에서 이 명령어를 호출할 도구 (query · mutate). execute 노출에서는 없음 */\n tool?: string\n}\n\n/** execute({ command: \"help\" }) */\nexport interface RootHelp {\n kind: \"root\"\n /** commands/_index.md */\n description?: string\n namespaces: HelpNamespaceSummary[]\n /** 루트 직속 명령어 */\n commands: HelpCommandSummary[]\n}\n\n/** execute({ command: \"help\", args: { target: \"users\" } }) */\nexport interface NamespaceHelp {\n kind: \"namespace\"\n id: string\n /** _index.md 전체 */\n description?: string\n namespaces: HelpNamespaceSummary[]\n commands: HelpCommandSummary[]\n /** 같은 ID 의 명령어 (users/index.ts) */\n command?: CommandHelp\n}\n\n/** execute({ command: \"help\", args: { target: \"users.get\" } }) */\nexport interface CommandHelp extends Omit<ManifestCommand, \"file\"> {\n kind: \"command\"\n /** 이 명령어 호출이 돌려줄 수 있는 구조화 오류 code */\n errors: string[]\n tool?: string\n}\n\nexport type Help = RootHelp | NamespaceHelp | CommandHelp\n\nexport interface HelpOptions {\n /** 명령어 → 호출할 도구 이름. split 노출이 지정 */\n toolFor?: (entry: RegisteredCommand) => string | undefined\n}\n\n/**\n * help 3단계: target 없음 = 루트 (네임스페이스 · 루트 명령어), 네임스페이스 ID = 그 명령어 목록,\n * 명령어 ID · 별칭 = 설명 · 입력 스키마 · 예시 · 오류 · 권한. 없는 target = UNKNOWN_COMMAND.\n */\nexport function help(registry: Registry, target: string | undefined, options: HelpOptions = {}): Help {\n const id = target?.trim() ?? \"\"\n if (id === \"\") {\n const root = registry.namespaces.get(\"\")!\n const out: RootHelp = {\n kind: \"root\",\n namespaces: root.namespaces.map((n) => namespaceSummary(registry, n)),\n commands: root.commands.map((c) => commandSummary(registry.commands.get(c)!, options)),\n }\n if (root.description !== undefined) out.description = root.description\n return out\n }\n const ns = registry.namespaces.get(id)\n if (ns) {\n const out: NamespaceHelp = {\n kind: \"namespace\",\n id,\n namespaces: ns.namespaces.map((n) => namespaceSummary(registry, n)),\n commands: ns.commands.map((c) => commandSummary(registry.commands.get(c)!, options)),\n }\n if (ns.description !== undefined) out.description = ns.description\n const self = registry.commands.get(id)\n if (self) out.command = commandHelp(self, options)\n return out\n }\n return commandHelp(findCommand(registry, id), options)\n}\n\nfunction namespaceSummary(registry: Registry, id: string): HelpNamespaceSummary {\n const description = registry.namespaces.get(id)?.description\n const first = description?.split(\"\\n\").find((l) => l.trim() !== \"\")\n return first === undefined ? { id } : { id, description: first.replace(/^#+\\s*/, \"\").trim() }\n}\n\nfunction commandSummary(entry: RegisteredCommand, options: HelpOptions): HelpCommandSummary {\n const c = entry.command\n const out: HelpCommandSummary = {\n id: entry.id,\n description: c.description,\n readOnly: c.readOnly,\n destructive: c.destructive,\n requiresConfirmation: c.requiresConfirmation,\n }\n const tool = options.toolFor?.(entry)\n if (tool !== undefined) out.tool = tool\n return out\n}\n\nfunction commandHelp(entry: RegisteredCommand, options: HelpOptions): CommandHelp {\n const { file: _file, ...meta } = describeCommand(entry)\n const c = entry.command\n const errors = [\"INVALID_ARGUMENT\"]\n if (!c.readOnly || c.scopes.length > 0) errors.push(\"UNAUTHENTICATED\", \"PERMISSION_DENIED\")\n if (c.requiresConfirmation) errors.push(\"INPUT_UNAVAILABLE\", \"INPUT_DECLINED\")\n const out: CommandHelp = { kind: \"command\", ...meta, errors }\n const tool = options.toolFor?.(entry)\n if (tool !== undefined) out.tool = tool\n return out\n}\n\n/** help 의 사람용 텍스트 (MCP text content). 구조는 structuredContent 가 원본 */\nexport function formatHelp(value: Help, call = \"execute\"): string {\n const lines: string[] = []\n const hint = (target: string) => `${call}({ command: \"help\", args: { target: \"${target}\" } })`\n const listCommands = (commands: HelpCommandSummary[]) => {\n if (commands.length === 0) return\n lines.push(\"Commands:\")\n const width = Math.max(...commands.map((c) => c.id.length))\n for (const c of commands) {\n const flags = safetyFlags(c)\n const via = c.tool !== undefined ? ` (via ${c.tool})` : \"\"\n lines.push(` ${c.id.padEnd(width)} ${c.description}${flags ? ` [${flags}]` : \"\"}${via}`)\n }\n }\n const listNamespaces = (namespaces: HelpNamespaceSummary[]) => {\n if (namespaces.length === 0) return\n lines.push(\"Namespaces:\")\n const width = Math.max(...namespaces.map((n) => n.id.length))\n for (const n of namespaces) lines.push(` ${n.id.padEnd(width)} ${n.description ?? \"\"}`.trimEnd())\n }\n\n if (value.kind === \"command\") {\n lines.push(`${value.id}: ${value.description}`)\n if (value.aliases.length > 0) lines.push(`Aliases: ${value.aliases.join(\", \")}`)\n const flags = safetyFlags(value)\n lines.push(`Safety: ${flags || \"none\"}`)\n if (value.scopes.length > 0) lines.push(`Scopes: ${value.scopes.join(\", \")}`)\n if (value.tool !== undefined) lines.push(`Tool: ${value.tool}`)\n lines.push(\"Input schema:\", JSON.stringify(value.inputSchema, null, 2))\n if (value.examples.length > 0) {\n lines.push(\"Examples:\")\n for (const e of value.examples) lines.push(` ${JSON.stringify(e)}`)\n }\n lines.push(`Errors: ${value.errors.join(\", \")}`)\n return lines.join(\"\\n\")\n }\n\n if (value.kind === \"namespace\") lines.push(value.id)\n if (value.description !== undefined) lines.push(value.description, \"\")\n listNamespaces(value.namespaces)\n listCommands(value.commands)\n if (value.kind === \"namespace\" && value.command !== undefined) {\n lines.push(\"\", formatHelp(value.command, call))\n }\n lines.push(\"\", `Details: ${hint(value.kind === \"root\" ? \"<namespace or command>\" : `${value.id}.<command>`)}`)\n return lines.join(\"\\n\").replace(/^\\n+/, \"\")\n}\n\nfunction safetyFlags(c: { readOnly: boolean; destructive: boolean; requiresConfirmation: boolean }): string {\n const flags: string[] = []\n if (c.readOnly) flags.push(\"read-only\")\n if (c.destructive) flags.push(\"destructive\")\n if (c.requiresConfirmation) flags.push(\"requires confirmation\")\n return flags.join(\", \")\n}\n"]}
@@ -0,0 +1,96 @@
1
+ import { type IncomingMessage, type ServerResponse } from "node:http";
2
+ import type { MextApp } from "../command.ts";
3
+ import { type Principal } from "../principal.ts";
4
+ import type { Registry } from "../registry.ts";
5
+ import type { Instrumentation } from "../instrumentation.ts";
6
+ import type { ToolExposure } from "./dispatch.ts";
7
+ import { type McpCoreOptions } from "./server.ts";
8
+ /** 요청 본문 상한 (SDK 기본과 같음) */
9
+ export declare const MAX_BODY_BYTES: number;
10
+ export interface McpAuthRequest {
11
+ /** Authorization 헤더 원문 */
12
+ authorization: string | undefined;
13
+ }
14
+ export interface McpHttpOptions {
15
+ registry: Registry;
16
+ app?: () => MextApp | Promise<MextApp>;
17
+ /**
18
+ * 요청마다 Authorization → Principal. 없으면 모든 요청 = ANONYMOUS (readOnly 명령어만).
19
+ * 잘못된 토큰은 unauthenticated() 를 던진다 → HTTP 401. anonymous 로 낮추지 않는다
20
+ */
21
+ auth?: (req: McpAuthRequest) => Principal | Promise<Principal>;
22
+ tools?: ToolExposure;
23
+ server?: {
24
+ /** 기본 127.0.0.1 */
25
+ host?: string;
26
+ /** 기본 3100. 0 = 임의 포트 */
27
+ port?: number;
28
+ /**
29
+ * Origin 허용 목록 (https://app.example.com 형식, "*" = 전체).
30
+ * 미지정 = loopback (localhost · 127.0.0.1 · [::1], 포트 무관) 만. Origin 헤더 없는 요청은 항상 통과
31
+ */
32
+ allowedOrigins?: string[];
33
+ };
34
+ /** serverInfo.name · 안내 문구. 기본 "mext" */
35
+ name?: string;
36
+ /** initialize · server/discover 결과 instructions */
37
+ instructions?: string;
38
+ /** 서버 capabilities 에 병합 (experimental) */
39
+ capabilities?: McpCoreOptions["capabilities"];
40
+ /** 응답에 영향 없는 오류 보고 (auth 예외 · SDK 내부 오류) */
41
+ onerror?: (error: Error) => void;
42
+ /** MRTR requestState 서명 키. McpCoreOptions.requestStateKey */
43
+ requestStateKey?: McpCoreOptions["requestStateKey"];
44
+ /** 주기 progress 간격 (ms). McpCoreOptions.progressInterval */
45
+ progressInterval?: number;
46
+ /** loadInstrumentation 결과 (onCommandError · span, transport = "http") */
47
+ instrumentation?: Instrumentation;
48
+ /** _meta["dev.mext/manifestHash"]. McpCoreOptions.manifestHash (기본 = registry 로 계산) */
49
+ manifestHash?: string;
50
+ /**
51
+ * OAuth 보호 자원 메타데이터 (RFC 9728). 지정하면 GET /.well-known/oauth-protected-resource/mcp ·
52
+ * /.well-known/oauth-protected-resource 응답, 401 의 WWW-Authenticate 에 resource_metadata.
53
+ * 클라이언트 (mext login · MCP 클라이언트) 가 인가 서버를 찾는 데 쓴다
54
+ */
55
+ oauth?: OAuthResourceOptions;
56
+ }
57
+ export interface OAuthResourceOptions {
58
+ /** 인가 서버 issuer URL (1개 이상) */
59
+ authorizationServers: string[];
60
+ scopesSupported?: string[];
61
+ /** 토큰 대상 자원 ID (RFC 8707). 기본 = 요청 Host 기준 http://<host>/mcp (프록시 · TLS 뒤에서는 지정) */
62
+ resource?: string;
63
+ }
64
+ /** RFC 9728 well-known 경로 */
65
+ export declare const PROTECTED_RESOURCE_METADATA_PATH = "/.well-known/oauth-protected-resource";
66
+ export type McpHttpListener = ((req: IncomingMessage, res: ServerResponse) => void) & {
67
+ /** 진행 중인 요청 abort · SDK handler 정리 */
68
+ close(): Promise<void>;
69
+ };
70
+ /** Web 표준 (Request → Response) MCP 처리기. handler.js · 다른 런타임 · node:http 어댑터가 공유 */
71
+ export interface McpFetchHandler {
72
+ /** 거부하지 않는다 (내부 오류 = 500 JSON-RPC 오류). request.signal abort = 핸들러 ctx.signal abort */
73
+ fetch(request: Request): Promise<Response>;
74
+ /** SDK handler 정리 */
75
+ close(): Promise<void>;
76
+ }
77
+ /**
78
+ * Streamable HTTP (2026-07-28) 의 transport 무관 처리 (Web Request → Response).
79
+ * 순서: 경로 (404) → Origin (403) → 메서드 (POST 외 405) → 본문 (413) → auth (401) → SDK createMcpHandler.
80
+ * SDK 가 요청별 버전 · 헤더 검증 (-32020) · server/discover · Mcp-Param-Command · 2025 레거시 stateless 를 처리.
81
+ * progressToken 있는 2026-07-28 요청 = SSE 응답 (진행률 알림), 그 외 = JSON.
82
+ */
83
+ export declare function createMcpFetchHandler(options: McpHttpOptions): McpFetchHandler;
84
+ /** createMcpFetchHandler 의 node:http 어댑터. 응답 · SSE 를 스트리밍, 클라이언트 연결 종료 = request.signal abort */
85
+ export declare function createMcpHttpHandler(options: McpHttpOptions): McpHttpListener;
86
+ export interface McpHttpServer {
87
+ /** http://<host>:<port>/mcp */
88
+ url: string;
89
+ host: string;
90
+ port: number;
91
+ close(): Promise<void>;
92
+ }
93
+ /** createMcpHttpHandler + node:http listen. 기본 127.0.0.1:3100 */
94
+ export declare function startMcpHttpServer(options: McpHttpOptions): Promise<McpHttpServer>;
95
+ /** Origin 허용 여부. allowed 미지정 = loopback 만, 지정 = 목록의 origin 과 정확히 일치 ("*" = 전체) */
96
+ export declare function originAllowed(origin: string, allowed: readonly string[] | undefined): boolean;