@obov/mext 0.5.0 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (177) hide show
  1. package/dist/approval.d.ts +33 -0
  2. package/dist/approval.js +36 -0
  3. package/dist/approval.js.map +1 -0
  4. package/dist/audit.d.ts +123 -0
  5. package/dist/audit.js +221 -0
  6. package/dist/audit.js.map +1 -0
  7. package/dist/build-cli.d.ts +1 -0
  8. package/dist/build-cli.js.map +1 -1
  9. package/dist/build.d.ts +2 -0
  10. package/dist/build.js +46 -5
  11. package/dist/build.js.map +1 -1
  12. package/dist/caller.d.ts +53 -0
  13. package/dist/caller.js +51 -0
  14. package/dist/caller.js.map +1 -0
  15. package/dist/cli/args.js +1 -2
  16. package/dist/cli/args.js.map +1 -1
  17. package/dist/cli/build.js +26 -4
  18. package/dist/cli/build.js.map +1 -1
  19. package/dist/cli/dev-audit.d.ts +25 -0
  20. package/dist/cli/dev-audit.js +84 -0
  21. package/dist/cli/dev-audit.js.map +1 -0
  22. package/dist/cli/dev-lock.d.ts +3 -1
  23. package/dist/cli/dev-lock.js +12 -5
  24. package/dist/cli/dev-lock.js.map +1 -1
  25. package/dist/cli/dev-server.d.ts +9 -0
  26. package/dist/cli/dev-server.js +27 -3
  27. package/dist/cli/dev-server.js.map +1 -1
  28. package/dist/cli/dev-tap.d.ts +20 -0
  29. package/dist/cli/dev-tap.js +70 -0
  30. package/dist/cli/dev-tap.js.map +1 -0
  31. package/dist/cli/dev.d.ts +6 -0
  32. package/dist/cli/dev.js +20 -8
  33. package/dist/cli/dev.js.map +1 -1
  34. package/dist/cli/help.js +2 -0
  35. package/dist/cli/help.js.map +1 -1
  36. package/dist/cli/inspect.d.ts +7 -0
  37. package/dist/cli/inspect.js +699 -0
  38. package/dist/cli/inspect.js.map +1 -0
  39. package/dist/cli/main.js +10 -0
  40. package/dist/cli/main.js.map +1 -1
  41. package/dist/cli/media.d.ts +19 -0
  42. package/dist/cli/media.js +77 -0
  43. package/dist/cli/media.js.map +1 -0
  44. package/dist/cli/output.d.ts +1 -1
  45. package/dist/cli/output.js +3 -2
  46. package/dist/cli/output.js.map +1 -1
  47. package/dist/cli/run.js +33 -4
  48. package/dist/cli/run.js.map +1 -1
  49. package/dist/cli/serve.d.ts +10 -0
  50. package/dist/cli/serve.js +114 -0
  51. package/dist/cli/serve.js.map +1 -0
  52. package/dist/cli/types.d.ts +9 -0
  53. package/dist/cli/types.js +77 -0
  54. package/dist/cli/types.js.map +1 -0
  55. package/dist/client/index.d.ts +11 -0
  56. package/dist/client/index.js +7 -0
  57. package/dist/client/index.js.map +1 -0
  58. package/dist/client/manifest.js +6 -2
  59. package/dist/client/manifest.js.map +1 -1
  60. package/dist/client/runner.js +40 -3
  61. package/dist/client/runner.js.map +1 -1
  62. package/dist/client/typed.d.ts +56 -0
  63. package/dist/client/typed.js +526 -0
  64. package/dist/client/typed.js.map +1 -0
  65. package/dist/client/types.d.ts +225 -0
  66. package/dist/client/types.js +2 -0
  67. package/dist/client/types.js.map +1 -0
  68. package/dist/command.d.ts +61 -5
  69. package/dist/command.js +42 -1
  70. package/dist/command.js.map +1 -1
  71. package/dist/config.d.ts +59 -10
  72. package/dist/config.js +67 -7
  73. package/dist/config.js.map +1 -1
  74. package/dist/content.d.ts +65 -0
  75. package/dist/content.js +99 -0
  76. package/dist/content.js.map +1 -0
  77. package/dist/errors.d.ts +21 -3
  78. package/dist/errors.js +13 -0
  79. package/dist/errors.js.map +1 -1
  80. package/dist/expose.d.ts +9 -0
  81. package/dist/expose.js +52 -0
  82. package/dist/expose.js.map +1 -0
  83. package/dist/index.d.ts +18 -7
  84. package/dist/index.js +8 -2
  85. package/dist/index.js.map +1 -1
  86. package/dist/inspect/agent.d.ts +104 -0
  87. package/dist/inspect/agent.js +125 -0
  88. package/dist/inspect/agent.js.map +1 -0
  89. package/dist/inspect/audit.d.ts +46 -0
  90. package/dist/inspect/audit.js +133 -0
  91. package/dist/inspect/audit.js.map +1 -0
  92. package/dist/inspect/compare.d.ts +12 -0
  93. package/dist/inspect/compare.js +31 -0
  94. package/dist/inspect/compare.js.map +1 -0
  95. package/dist/inspect/index.d.ts +17 -0
  96. package/dist/inspect/index.js +12 -0
  97. package/dist/inspect/index.js.map +1 -0
  98. package/dist/inspect/inspector.d.ts +168 -0
  99. package/dist/inspect/inspector.js +392 -0
  100. package/dist/inspect/inspector.js.map +1 -0
  101. package/dist/inspect/profiles.d.ts +95 -0
  102. package/dist/inspect/profiles.js +235 -0
  103. package/dist/inspect/profiles.js.map +1 -0
  104. package/dist/inspect/scenario.d.ts +126 -0
  105. package/dist/inspect/scenario.js +555 -0
  106. package/dist/inspect/scenario.js.map +1 -0
  107. package/dist/inspect/ui-server.d.ts +31 -0
  108. package/dist/inspect/ui-server.js +378 -0
  109. package/dist/inspect/ui-server.js.map +1 -0
  110. package/dist/inspect/ui.html +917 -0
  111. package/dist/instrumentation.d.ts +13 -2
  112. package/dist/instrumentation.js +11 -1
  113. package/dist/instrumentation.js.map +1 -1
  114. package/dist/invoke.d.ts +36 -5
  115. package/dist/invoke.js +162 -21
  116. package/dist/invoke.js.map +1 -1
  117. package/dist/manifest.d.ts +19 -3
  118. package/dist/manifest.js +5 -1
  119. package/dist/manifest.js.map +1 -1
  120. package/dist/mcp/constants.d.ts +7 -0
  121. package/dist/mcp/constants.js +7 -0
  122. package/dist/mcp/constants.js.map +1 -1
  123. package/dist/mcp/dispatch.d.ts +10 -4
  124. package/dist/mcp/dispatch.js +24 -7
  125. package/dist/mcp/dispatch.js.map +1 -1
  126. package/dist/mcp/help.d.ts +2 -0
  127. package/dist/mcp/help.js +6 -1
  128. package/dist/mcp/help.js.map +1 -1
  129. package/dist/mcp/host.d.ts +14 -0
  130. package/dist/mcp/host.js +48 -0
  131. package/dist/mcp/host.js.map +1 -0
  132. package/dist/mcp/http.d.ts +38 -7
  133. package/dist/mcp/http.js +199 -23
  134. package/dist/mcp/http.js.map +1 -1
  135. package/dist/mcp/index.d.ts +2 -1
  136. package/dist/mcp/index.js +2 -1
  137. package/dist/mcp/index.js.map +1 -1
  138. package/dist/mcp/server.d.ts +12 -1
  139. package/dist/mcp/server.js +63 -17
  140. package/dist/mcp/server.js.map +1 -1
  141. package/dist/mcp/stdio.d.ts +4 -0
  142. package/dist/mcp/stdio.js +3 -0
  143. package/dist/mcp/stdio.js.map +1 -1
  144. package/dist/mcp/tasks.d.ts +3 -1
  145. package/dist/mcp/tasks.js +7 -4
  146. package/dist/mcp/tasks.js.map +1 -1
  147. package/dist/principal.d.ts +22 -0
  148. package/dist/principal.js +29 -0
  149. package/dist/principal.js.map +1 -1
  150. package/dist/registry.d.ts +5 -0
  151. package/dist/registry.js +21 -0
  152. package/dist/registry.js.map +1 -1
  153. package/dist/remote/client.d.ts +26 -1
  154. package/dist/remote/client.js +180 -55
  155. package/dist/remote/client.js.map +1 -1
  156. package/dist/remote/index.d.ts +1 -1
  157. package/dist/remote/index.js.map +1 -1
  158. package/dist/remote/oauth.d.ts +1 -1
  159. package/dist/remote/oauth.js +1 -1
  160. package/dist/remote/oauth.js.map +1 -1
  161. package/dist/route.d.ts +40 -2
  162. package/dist/route.js +128 -8
  163. package/dist/route.js.map +1 -1
  164. package/dist/start.js +9 -4
  165. package/dist/start.js.map +1 -1
  166. package/dist/suggest.js +1 -1
  167. package/dist/suggest.js.map +1 -1
  168. package/dist/typegen.d.ts +27 -0
  169. package/dist/typegen.js +62 -0
  170. package/dist/typegen.js.map +1 -0
  171. package/dist/version-constant.d.ts +5 -0
  172. package/dist/version-constant.js +6 -0
  173. package/dist/version-constant.js.map +1 -0
  174. package/dist/version.d.ts +1 -2
  175. package/dist/version.js +1 -2
  176. package/dist/version.js.map +1 -1
  177. package/package.json +12 -2
package/dist/mcp/help.js CHANGED
@@ -50,6 +50,8 @@ function commandSummary(entry, options) {
50
50
  destructive: c.destructive,
51
51
  requiresConfirmation: c.requiresConfirmation,
52
52
  };
53
+ if (c.needsApproval !== undefined)
54
+ out.needsApproval = "conditional";
53
55
  const tool = options.toolFor?.(entry);
54
56
  if (tool !== undefined)
55
57
  out.tool = tool;
@@ -61,7 +63,8 @@ function commandHelp(entry, options) {
61
63
  const errors = ["INVALID_ARGUMENT"];
62
64
  if (!c.readOnly || c.scopes.length > 0)
63
65
  errors.push("UNAUTHENTICATED", "PERMISSION_DENIED");
64
- if (c.requiresConfirmation)
66
+ // 승인 (항상 · 조건식) = ask 방식의 확인 오류. APPROVAL_REQUIRED 는 표면 설정 · 행위자에 따라 승인 명령어 모두에서 가능 (§4 승인)
67
+ if (c.requiresConfirmation || c.needsApproval !== undefined)
65
68
  errors.push("INPUT_UNAVAILABLE", "INPUT_DECLINED");
66
69
  const out = { kind: "command", ...meta, errors };
67
70
  const tool = options.toolFor?.(entry);
@@ -131,6 +134,8 @@ function safetyFlags(c) {
131
134
  flags.push("destructive");
132
135
  if (c.requiresConfirmation)
133
136
  flags.push("requires confirmation");
137
+ else if (c.needsApproval !== undefined)
138
+ flags.push("may require confirmation");
134
139
  return flags.join(", ");
135
140
  }
136
141
  //# sourceMappingURL=help.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"help.js","sourceRoot":"","sources":["../../src/mcp/help.ts"],"names":[],"mappings":"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","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"]}
1
+ {"version":3,"file":"help.js","sourceRoot":"","sources":["../../src/mcp/help.ts"],"names":[],"mappings":"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","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 /** 승인이 조건식일 때만 (§4 승인) */\n needsApproval?: \"conditional\"\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 if (c.needsApproval !== undefined) out.needsApproval = \"conditional\"\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 // 승인 (항상 · 조건식) = ask 방식의 확인 오류. APPROVAL_REQUIRED 는 표면 설정 · 행위자에 따라 승인 명령어 모두에서 가능 (§4 승인)\n if (c.requiresConfirmation || c.needsApproval !== undefined) 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; needsApproval?: \"conditional\" }): 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 else if (c.needsApproval !== undefined) flags.push(\"may require confirmation\")\n return flags.join(\", \")\n}\n"]}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * loopback 판정 · Host 허용 목록 (§6 보안). mcp/http.ts (Origin · Host 검사) · config.ts (allowedHosts 형식 검사) 가 공유.
3
+ * Node 내장 모듈 · SDK import 없음
4
+ */
5
+ export declare const LOOPBACK_HOSTNAMES: ReadonlySet<string>;
6
+ /** allowedOrigins · allowedHosts 의 loopback 항목 */
7
+ export declare const LOOPBACK_ENTRY = "loopback";
8
+ /** allowedHosts 항목 형식 문제. 없으면 undefined (config 검사) */
9
+ export declare function allowedHostProblem(entry: unknown): string | undefined;
10
+ /**
11
+ * Host 허용 여부 (server.allowedHosts). "*" = 전체, "loopback" = loopback 호스트 (포트 무관),
12
+ * 포트 없는 항목 = 그 호스트의 모든 포트, 포트 있는 항목 = 그 포트만. 대소문자 · 끝 "." 무시. 형식 오류 = false
13
+ */
14
+ export declare function hostAllowed(host: string, allowed: readonly string[]): boolean;
@@ -0,0 +1,48 @@
1
+ /**
2
+ * loopback 판정 · Host 허용 목록 (§6 보안). mcp/http.ts (Origin · Host 검사) · config.ts (allowedHosts 형식 검사) 가 공유.
3
+ * Node 내장 모듈 · SDK import 없음
4
+ */
5
+ export const LOOPBACK_HOSTNAMES = new Set(["localhost", "127.0.0.1", "[::1]"]);
6
+ /** allowedOrigins · allowedHosts 의 loopback 항목 */
7
+ export const LOOPBACK_ENTRY = "loopback";
8
+ /** Host 값 → 소문자 hostname (끝 "." 제거) · port (없으면 ""). 형식 오류 = undefined */
9
+ function parseHost(value) {
10
+ if (value === "" || /[\s/?#@\\]/.test(value))
11
+ return undefined;
12
+ let url;
13
+ try {
14
+ url = new URL(`http://${value}`);
15
+ }
16
+ catch {
17
+ return undefined;
18
+ }
19
+ // URL 이 기본 포트 (80) 를 지우므로 원문의 포트를 쓴다
20
+ const port = /:(\d+)$/.exec(value)?.[1] ?? "";
21
+ return { hostname: url.hostname.replace(/\.$/, ""), port };
22
+ }
23
+ /** allowedHosts 항목 형식 문제. 없으면 undefined (config 검사) */
24
+ export function allowedHostProblem(entry) {
25
+ if (entry === "*" || entry === LOOPBACK_ENTRY)
26
+ return undefined;
27
+ if (typeof entry !== "string" || parseHost(entry) === undefined)
28
+ return `invalid host ${JSON.stringify(entry)}`;
29
+ return undefined;
30
+ }
31
+ /**
32
+ * Host 허용 여부 (server.allowedHosts). "*" = 전체, "loopback" = loopback 호스트 (포트 무관),
33
+ * 포트 없는 항목 = 그 호스트의 모든 포트, 포트 있는 항목 = 그 포트만. 대소문자 · 끝 "." 무시. 형식 오류 = false
34
+ */
35
+ export function hostAllowed(host, allowed) {
36
+ if (allowed.includes("*"))
37
+ return true;
38
+ const actual = parseHost(host);
39
+ if (actual === undefined)
40
+ return false;
41
+ if (LOOPBACK_HOSTNAMES.has(actual.hostname) && allowed.includes(LOOPBACK_ENTRY))
42
+ return true;
43
+ return allowed.some((entry) => {
44
+ const expected = entry === LOOPBACK_ENTRY ? undefined : parseHost(entry);
45
+ return expected !== undefined && expected.hostname === actual.hostname && (expected.port === "" || expected.port === actual.port);
46
+ });
47
+ }
48
+ //# sourceMappingURL=host.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"host.js","sourceRoot":"","sources":["../../src/mcp/host.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,CAAC,MAAM,kBAAkB,GAAwB,IAAI,GAAG,CAAC,CAAC,WAAW,EAAE,WAAW,EAAE,OAAO,CAAC,CAAC,CAAA;AAEnG,kDAAkD;AAClD,MAAM,CAAC,MAAM,cAAc,GAAG,UAAU,CAAA;AAExC,0EAA0E;AAC1E,SAAS,SAAS,CAAC,KAAa;IAC9B,IAAI,KAAK,KAAK,EAAE,IAAI,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC;QAAE,OAAO,SAAS,CAAA;IAC9D,IAAI,GAAQ,CAAA;IACZ,IAAI,CAAC;QACH,GAAG,GAAG,IAAI,GAAG,CAAC,UAAU,KAAK,EAAE,CAAC,CAAA;IAClC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAA;IAClB,CAAC;IACD,qCAAqC;IACrC,MAAM,IAAI,GAAG,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAA;IAC7C,OAAO,EAAE,QAAQ,EAAE,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,EAAE,IAAI,EAAE,CAAA;AAC5D,CAAC;AAED,uDAAuD;AACvD,MAAM,UAAU,kBAAkB,CAAC,KAAc;IAC/C,IAAI,KAAK,KAAK,GAAG,IAAI,KAAK,KAAK,cAAc;QAAE,OAAO,SAAS,CAAA;IAC/D,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,SAAS,CAAC,KAAK,CAAC,KAAK,SAAS;QAAE,OAAO,gBAAgB,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE,CAAA;IAC/G,OAAO,SAAS,CAAA;AAClB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,WAAW,CAAC,IAAY,EAAE,OAA0B;IAClE,IAAI,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC;QAAE,OAAO,IAAI,CAAA;IACtC,MAAM,MAAM,GAAG,SAAS,CAAC,IAAI,CAAC,CAAA;IAC9B,IAAI,MAAM,KAAK,SAAS;QAAE,OAAO,KAAK,CAAA;IACtC,IAAI,kBAAkB,CAAC,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAC;QAAE,OAAO,IAAI,CAAA;IAC5F,OAAO,OAAO,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE;QAC5B,MAAM,QAAQ,GAAG,KAAK,KAAK,cAAc,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,CAAA;QACxE,OAAO,QAAQ,KAAK,SAAS,IAAI,QAAQ,CAAC,QAAQ,KAAK,MAAM,CAAC,QAAQ,IAAI,CAAC,QAAQ,CAAC,IAAI,KAAK,EAAE,IAAI,QAAQ,CAAC,IAAI,KAAK,MAAM,CAAC,IAAI,CAAC,CAAA;IACnI,CAAC,CAAC,CAAA;AACJ,CAAC","sourcesContent":["/**\n * loopback 판정 · Host 허용 목록 (§6 보안). mcp/http.ts (Origin · Host 검사) · config.ts (allowedHosts 형식 검사) 가 공유.\n * Node 내장 모듈 · SDK import 없음\n */\n\nexport const LOOPBACK_HOSTNAMES: ReadonlySet<string> = new Set([\"localhost\", \"127.0.0.1\", \"[::1]\"])\n\n/** allowedOrigins · allowedHosts 의 loopback 항목 */\nexport const LOOPBACK_ENTRY = \"loopback\"\n\n/** Host 값 → 소문자 hostname (끝 \".\" 제거) · port (없으면 \"\"). 형식 오류 = undefined */\nfunction parseHost(value: string): { hostname: string; port: string } | undefined {\n if (value === \"\" || /[\\s/?#@\\\\]/.test(value)) return undefined\n let url: URL\n try {\n url = new URL(`http://${value}`)\n } catch {\n return undefined\n }\n // URL 이 기본 포트 (80) 를 지우므로 원문의 포트를 쓴다\n const port = /:(\\d+)$/.exec(value)?.[1] ?? \"\"\n return { hostname: url.hostname.replace(/\\.$/, \"\"), port }\n}\n\n/** allowedHosts 항목 형식 문제. 없으면 undefined (config 검사) */\nexport function allowedHostProblem(entry: unknown): string | undefined {\n if (entry === \"*\" || entry === LOOPBACK_ENTRY) return undefined\n if (typeof entry !== \"string\" || parseHost(entry) === undefined) return `invalid host ${JSON.stringify(entry)}`\n return undefined\n}\n\n/**\n * Host 허용 여부 (server.allowedHosts). \"*\" = 전체, \"loopback\" = loopback 호스트 (포트 무관),\n * 포트 없는 항목 = 그 호스트의 모든 포트, 포트 있는 항목 = 그 포트만. 대소문자 · 끝 \".\" 무시. 형식 오류 = false\n */\nexport function hostAllowed(host: string, allowed: readonly string[]): boolean {\n if (allowed.includes(\"*\")) return true\n const actual = parseHost(host)\n if (actual === undefined) return false\n if (LOOPBACK_HOSTNAMES.has(actual.hostname) && allowed.includes(LOOPBACK_ENTRY)) return true\n return allowed.some((entry) => {\n const expected = entry === LOOPBACK_ENTRY ? undefined : parseHost(entry)\n return expected !== undefined && expected.hostname === actual.hostname && (expected.port === \"\" || expected.port === actual.port)\n })\n}\n"]}
@@ -1,17 +1,29 @@
1
1
  import { type IncomingMessage, type ServerResponse } from "node:http";
2
2
  import type { MextApp } from "../command.ts";
3
+ import { type BuiltinSurface, type ReportedClient } from "../caller.ts";
3
4
  import { type Principal } from "../principal.ts";
4
5
  import type { Registry } from "../registry.ts";
5
- import { type RouteEntry } from "../route.ts";
6
+ import { type RouteEntry, type RouteMethod, type RouteOptions } from "../route.ts";
7
+ import { MAX_BODY_BYTES } from "./constants.ts";
6
8
  import type { Instrumentation } from "../instrumentation.ts";
7
9
  import type { ToolExposure } from "./dispatch.ts";
8
10
  import { type McpCoreOptions } from "./server.ts";
9
11
  import type { MextState } from "../state.ts";
10
- /** 요청 본문 상한 (SDK 기본과 같음) */
11
- export declare const MAX_BODY_BYTES: number;
12
+ export { MAX_BODY_BYTES };
12
13
  export interface McpAuthRequest {
13
14
  /** Authorization 헤더 원문 */
14
15
  authorization: string | undefined;
16
+ /** 요청 헤더 (세션 쿠키 · 프록시가 붙인 헤더). 복사본 */
17
+ headers: Headers;
18
+ /** 어댑터 기본 표면. POST /mcp = "mcp". 반환 Principal.surface 로 논리 표면을 바꿀 수 있다 */
19
+ surface: BuiltinSurface;
20
+ /** 요청 _meta 의 clientInfo (2026-07-28). 자가 신고 값: 주체 · 행위자 · 표면 판정에 쓰지 않는다 */
21
+ client: ReportedClient | undefined;
22
+ /** routes/ 요청 (export auth, §12.4) 만. method = 요청 메서드 (HEAD = GET), path = route 파일 패턴 ("/items/[id]"). POST /mcp = 없음 */
23
+ route?: {
24
+ method: RouteMethod;
25
+ path: string;
26
+ };
15
27
  }
16
28
  export interface McpHttpOptions {
17
29
  registry: Registry;
@@ -28,10 +40,17 @@ export interface McpHttpOptions {
28
40
  /** 기본 3100. 0 = 임의 포트 */
29
41
  port?: number;
30
42
  /**
31
- * Origin 허용 목록 (https://app.example.com 형식, "*" = 전체).
43
+ * Origin 허용 목록 (https://app.example.com · chrome-extension://<id> 형식, "*" = 전체, "loopback" = loopback).
32
44
  * 미지정 = loopback (localhost · 127.0.0.1 · [::1], 포트 무관) 만. Origin 헤더 없는 요청은 항상 통과
33
45
  */
34
46
  allowedOrigins?: string[];
47
+ /**
48
+ * Host 허용 목록 (api.example.com = 모든 포트 · api.example.com:8443 = 그 포트만, "loopback" = loopback, "*" = 전체).
49
+ * 미지정 = 검사 없음. 지정 = 목록 밖 Host 의 모든 요청 (/mcp · routes · well-known) = 403 (DNS rebinding 방어)
50
+ */
51
+ allowedHosts?: string[];
52
+ /** POST /mcp 본문 상한 (바이트). 기본 MAX_BODY_BYTES (4 MiB). 넘으면 413. route 의 export maxBodyBytes = true 도 이 값 */
53
+ maxBodyBytes?: number;
35
54
  };
36
55
  /** serverInfo.name · 안내 문구. 기본 "mext" */
37
56
  name?: string;
@@ -55,14 +74,23 @@ export interface McpHttpOptions {
55
74
  * 클라이언트 (mext login · MCP 클라이언트) 가 인가 서버를 찾는 데 쓴다
56
75
  */
57
76
  oauth?: OAuthResourceOptions;
58
- /** routes/ (§12.4). MCP 엔드포인트 밖 경로. Origin 검사 · auth 없음. ctx.app = MCP 와 같은 인스턴스 */
77
+ /**
78
+ * routes/ (§12.4). MCP 엔드포인트 밖 경로. ctx.app = MCP 와 같은 인스턴스.
79
+ * Origin 검사 · auth = 기본 없음. 모듈의 export origin · auth (없으면 routeDefaults) 가 있으면 allowedOrigins · auth 를 /mcp 와 같은 규칙으로
80
+ */
59
81
  routes?: readonly RouteEntry[];
82
+ /** 설정 routes (§12.4): 모든 route 의 auth · origin · maxBodyBytes 기본값. 모듈 export 가 덮어쓴다 */
83
+ routeDefaults?: RouteOptions;
60
84
  /** 주입 state (§12.1). 자원 구독 · ctx.resourceUpdated (§12.5) */
61
85
  state?: MextState;
62
86
  /** McpCoreOptions.taskTiming (테스트) */
63
87
  taskTiming?: McpCoreOptions["taskTiming"];
64
88
  /** McpCoreOptions.taskExecution. handler.js = "waitUntil" */
65
89
  taskExecution?: McpCoreOptions["taskExecution"];
90
+ /** 감사 (config.audit, §11.1) */
91
+ audit?: McpCoreOptions["audit"];
92
+ /** 승인의 표면별 확인 방식 (config.approval, §4 승인) */
93
+ approval?: McpCoreOptions["approval"];
66
94
  }
67
95
  /** fetch 의 두 번째 인자. waitUntil = 응답 후 실행 유지 (handler.js 플랫폼, tasks §10) */
68
96
  export interface McpFetchContext {
@@ -94,7 +122,7 @@ export interface McpFetchHandler {
94
122
  }
95
123
  /**
96
124
  * Streamable HTTP (2026-07-28) 의 transport 무관 처리 (Web Request → Response).
97
- * 순서: 경로 (routes · 404) → Origin (403) → 메서드 (POST 외 405) → 본문 (413) → auth (401) → SDK createMcpHandler.
125
+ * 순서: Host (allowedHosts, 모든 경로 403) → 경로 (routes · 404) → Origin (403) → 메서드 (POST 외 405) → 본문 (413) → auth (401) → SDK createMcpHandler.
98
126
  * SDK 가 요청별 버전 · 헤더 검증 (-32020) · server/discover · Mcp-Param-Command · 2025 레거시 stateless 를 처리.
99
127
  * progressToken 있는 2026-07-28 요청 = SSE 응답 (진행률 알림), 그 외 = JSON.
100
128
  */
@@ -110,5 +138,8 @@ export interface McpHttpServer {
110
138
  }
111
139
  /** createMcpHttpHandler + node:http listen. 기본 127.0.0.1:3100 */
112
140
  export declare function startMcpHttpServer(options: McpHttpOptions): Promise<McpHttpServer>;
113
- /** Origin 허용 여부. allowed 미지정 = loopback 만, 지정 = 목록의 origin 과 정확히 일치 ("*" = 전체) */
141
+ /**
142
+ * Origin 허용 여부. allowed 미지정 = loopback 만, 지정 = 목록의 origin 과 정확히 일치 ("*" = 전체, "loopback" = loopback).
143
+ * http · https 외 scheme (chrome-extension://<id>) = URL.origin 이 "null" 이라 목록과 문자열 비교
144
+ */
114
145
  export declare function originAllowed(origin: string, allowed: readonly string[] | undefined): boolean;
package/dist/mcp/http.js CHANGED
@@ -1,20 +1,22 @@
1
1
  import { createServer } from "node:http";
2
- import { Readable } from "node:stream";
3
- import { createMcpHandler, InMemoryServerEventBus, isLegacyRequest } from "@modelcontextprotocol/server";
4
- import { isMextError } from "../errors.js";
5
- import { ANONYMOUS } from "../principal.js";
2
+ import { CLIENT_INFO_META_KEY, createMcpHandler, InMemoryServerEventBus, isLegacyRequest } from "@modelcontextprotocol/server";
3
+ import { auditHidden } from "../audit.js";
4
+ import { isMextError, unauthenticated } from "../errors.js";
5
+ import { defaultSurface, reportedClient, surfaceOf } from "../caller.js";
6
+ import { exposedRegistry } from "../expose.js";
7
+ import { ANONYMOUS, assertCallerFields } from "../principal.js";
6
8
  import { createRouteDispatcher, onceApp } from "../route.js";
7
- import { DEFAULT_HOST, DEFAULT_PORT, MCP_PATH } from "./constants.js";
9
+ import { DEFAULT_HOST, DEFAULT_PORT, MAX_BODY_BYTES, MCP_PATH } from "./constants.js";
10
+ import { hostAllowed, LOOPBACK_ENTRY, LOOPBACK_HOSTNAMES } from "./host.js";
8
11
  import { createMcpServerFactory, principalAuthInfo } from "./server.js";
9
12
  import { bridgeResourceUpdates, narrowListenRequest, resourceUpdatedFn, trackBody } from "./subscriptions.js";
10
13
  import { hasTaskCommands, requestsTask } from "./tasks.js";
11
- /** 요청 본문 상한 (SDK 기본과 같음) */
12
- export const MAX_BODY_BYTES = 4 * 1024 * 1024;
14
+ export { MAX_BODY_BYTES };
13
15
  /** RFC 9728 well-known 경로 */
14
16
  export const PROTECTED_RESOURCE_METADATA_PATH = "/.well-known/oauth-protected-resource";
15
17
  /**
16
18
  * Streamable HTTP (2026-07-28) 의 transport 무관 처리 (Web Request → Response).
17
- * 순서: 경로 (routes · 404) → Origin (403) → 메서드 (POST 외 405) → 본문 (413) → auth (401) → SDK createMcpHandler.
19
+ * 순서: Host (allowedHosts, 모든 경로 403) → 경로 (routes · 404) → Origin (403) → 메서드 (POST 외 405) → 본문 (413) → auth (401) → SDK createMcpHandler.
18
20
  * SDK 가 요청별 버전 · 헤더 검증 (-32020) · server/discover · Mcp-Param-Command · 2025 레거시 stateless 를 처리.
19
21
  * progressToken 있는 2026-07-28 요청 = SSE 응답 (진행률 알림), 그 외 = JSON.
20
22
  */
@@ -22,8 +24,17 @@ export function createMcpFetchHandler(options) {
22
24
  // ctx.app = 프로세스당 1회. 명령어 · route 가 공유
23
25
  const app = options.app ? onceApp(options.app) : undefined;
24
26
  const resourceUpdated = resourceUpdatedFn(options.state);
27
+ const report = reporter(options.onerror);
28
+ const allowedOrigins = options.server?.allowedOrigins;
25
29
  const routes = options.routes?.length
26
- ? createRouteDispatcher({ routes: options.routes, app: onceApp(app), resourceUpdated })
30
+ ? createRouteDispatcher({
31
+ routes: options.routes,
32
+ app: onceApp(app),
33
+ resourceUpdated,
34
+ guard: routeGuard(options, report),
35
+ maxBodyBytes: options.server?.maxBodyBytes ?? MAX_BODY_BYTES,
36
+ ...(options.routeDefaults ? { defaults: options.routeDefaults } : {}),
37
+ })
27
38
  : undefined;
28
39
  const factory = createMcpServerFactory({
29
40
  registry: options.registry,
@@ -39,11 +50,13 @@ export function createMcpFetchHandler(options) {
39
50
  ...(options.state ? { state: options.state } : {}),
40
51
  ...(options.taskTiming ? { taskTiming: options.taskTiming } : {}),
41
52
  ...(options.taskExecution ? { taskExecution: options.taskExecution } : {}),
53
+ ...(options.audit ? { audit: options.audit } : {}),
54
+ ...(options.approval ? { approval: options.approval } : {}),
42
55
  transport: "http",
43
56
  });
44
57
  const tasks = options.state?.store !== undefined && hasTaskCommands(options.registry);
45
- const report = reporter(options.onerror);
46
- const sdkOptions = options.onerror ? { onerror: report } : {};
58
+ const maxBodyBytes = options.server?.maxBodyBytes ?? MAX_BODY_BYTES;
59
+ const sdkOptions = { ...(options.onerror ? { onerror: report } : {}), maxRequestBodySize: maxBodyBytes };
47
60
  // subscriptions/listen (§12.5) = 전용 SDK handler (ack · 15초 keepalive · 완료 result). 갱신 = state 허브 → SDK bus
48
61
  const listenBus = new InMemoryServerEventBus(report);
49
62
  const subscriptions = { state: options.state, resources: options.registry.resources };
@@ -54,9 +67,19 @@ export function createMcpFetchHandler(options) {
54
67
  // 진행률 없는 요청 = JSON (auto: 관련 메시지가 없으면 단일 JSON), 진행률 있는 요청 = SSE
55
68
  const jsonHandler = createMcpHandler(factory, { ...sdkOptions, responseMode: "auto" });
56
69
  const sseHandler = createMcpHandler(factory, { ...sdkOptions, responseMode: "sse" });
57
- const allowedOrigins = options.server?.allowedOrigins;
70
+ const allowedHosts = options.server?.allowedHosts;
58
71
  const handle = async (request, context) => {
59
72
  const url = new URL(request.url);
73
+ if (allowedHosts !== undefined) {
74
+ const host = request.headers.get("host") ?? url.host;
75
+ if (!hostAllowed(host, allowedHosts)) {
76
+ await request.body?.cancel().catch(() => { });
77
+ const message = `Forbidden: host ${host} is not allowed.`;
78
+ if (url.pathname === MCP_PATH)
79
+ return jsonResponse(403, rpcError(-32_000, message));
80
+ return jsonResponse(403, { error: { code: "REQUEST_REJECTED", message, reason: "host" } });
81
+ }
82
+ }
60
83
  const oauth = options.oauth;
61
84
  if (oauth && (url.pathname === PROTECTED_RESOURCE_METADATA_PATH || url.pathname === protectedResourceMetadataPath())) {
62
85
  if (request.method !== "GET")
@@ -77,7 +100,7 @@ export function createMcpFetchHandler(options) {
77
100
  if (request.method !== "POST") {
78
101
  return jsonResponse(405, rpcError(-32_000, "Method not allowed."), { Allow: "POST" });
79
102
  }
80
- const body = await readBody(request.body, MAX_BODY_BYTES);
103
+ const body = await readBody(request.body, maxBodyBytes);
81
104
  if (body === "too-large")
82
105
  return jsonResponse(413, rpcError(-32_000, "Request body too large."));
83
106
  const parsed = parseJson(body);
@@ -85,7 +108,14 @@ export function createMcpFetchHandler(options) {
85
108
  let principal = ANONYMOUS;
86
109
  if (options.auth) {
87
110
  try {
88
- principal = await options.auth({ authorization: request.headers.get("authorization") ?? undefined });
111
+ principal = await options.auth({
112
+ authorization: request.headers.get("authorization") ?? undefined,
113
+ headers: new Headers(request.headers),
114
+ surface: defaultSurface("http"),
115
+ client: reportedClientOf(parsed),
116
+ });
117
+ // actor · surface 형식 오류 = auth 예외와 같음 (500 + onerror)
118
+ assertCallerFields(principal, "auth");
89
119
  }
90
120
  catch (error) {
91
121
  if (isMextError(error, "UNAUTHENTICATED")) {
@@ -103,7 +133,7 @@ export function createMcpFetchHandler(options) {
103
133
  });
104
134
  const parsedBody = parsed === NOT_JSON ? undefined : parsed;
105
135
  // 2025-11-25 이하 (envelope 없음) = SDK stateless 레거시 경로. 진행률 기반 SSE 전환은 2026-07-28 요청만
106
- const legacy = await isLegacyRequest(sdkRequest, parsedBody, { maxRequestBodySize: MAX_BODY_BYTES });
136
+ const legacy = await isLegacyRequest(sdkRequest, parsedBody, { maxRequestBodySize: maxBodyBytes });
107
137
  const listen = legacy ? undefined : narrowListenRequest(parsedBody, subscriptions, principal);
108
138
  if (listen !== undefined) {
109
139
  const { response, done } = trackBody(await listenHandler.fetch(sdkRequest, { authInfo: principalAuthInfo(principal), parsedBody: listen }));
@@ -111,8 +141,24 @@ export function createMcpFetchHandler(options) {
111
141
  void done.then(() => listenBodies.delete(done));
112
142
  return response;
113
143
  }
144
+ // tools: "command" 의 숨긴 명령어 호출 = SDK 가 등록되지 않은 도구로 거부해 mext 콜백이 없다 → 본문을 넘기기 전에 기록
145
+ // (§11.1 includeHidden). 응답은 그대로 SDK 가 만들고, 전달은 기다리지 않는다
146
+ if (options.audit?.includeHidden === true && options.tools === "command") {
147
+ const hidden = hiddenToolCall(options.registry, principal, parsedBody);
148
+ const work = hidden &&
149
+ auditHidden(hidden, {
150
+ audit: options.audit,
151
+ principal,
152
+ transport: "http",
153
+ ...optional("client", reportedClientOf(parsedBody)),
154
+ ...optional("traceContext", metaOf(parsedBody)),
155
+ });
156
+ if (work)
157
+ context?.waitUntil?.(work);
158
+ }
114
159
  // task 가 될 수 있는 tools/call = application/json (CreateTaskResult, §12.6)
115
- const sse = !legacy && hasProgressToken(parsedBody) && !(tasks && requestsTask(options.registry, parsedBody));
160
+ // 대상 판정 = 주체의 표면에서 보이는 명령어 (숨긴 task 명령어는 없는 명령어와 같은 응답 형식, §4 expose)
161
+ const sse = !legacy && hasProgressToken(parsedBody) && !(tasks && requestsTask(exposedRegistry(options.registry, surfaceOf(principal, "http")), parsedBody));
116
162
  const handler = sse ? sseHandler : jsonHandler;
117
163
  return handler.fetch(sdkRequest, {
118
164
  authInfo: principalAuthInfo(principal, context?.waitUntil),
@@ -168,7 +214,7 @@ export function createMcpHttpHandler(options) {
168
214
  const request = new Request(requestUrl(req), {
169
215
  method: req.method ?? "GET",
170
216
  headers: nodeHeaders(req),
171
- ...(hasBody ? { body: Readable.toWeb(req), duplex: "half" } : {}),
217
+ ...(hasBody ? { body: nodeBody(req), duplex: "half" } : {}),
172
218
  signal: controller.signal,
173
219
  });
174
220
  core
@@ -205,6 +251,56 @@ function reporter(onerror) {
205
251
  }
206
252
  };
207
253
  }
254
+ /**
255
+ * routes/ 의 export origin · auth 판정 (§12.4). /mcp 와 같은 allowedOrigins · auth · WWW-Authenticate.
256
+ * 본문 = { error: { code, message, ...details } } (specs/http.md §3.3 오류 본문 모양)
257
+ */
258
+ function routeGuard(options, report) {
259
+ const warned = new Set();
260
+ const challenge = (error, request) => {
261
+ const metadata = options.oauth ? resourceMetadataUrl(options.oauth, new URL(request.url)) : undefined;
262
+ return jsonResponse(401, { error: error.toJSON() }, { "WWW-Authenticate": bearerChallenge(error, metadata) });
263
+ };
264
+ return {
265
+ origin(request) {
266
+ const origin = request.headers.get("origin") ?? undefined;
267
+ if (origin === undefined || origin === "" || originAllowed(origin, options.server?.allowedOrigins))
268
+ return undefined;
269
+ return jsonResponse(403, { error: { code: "REQUEST_REJECTED", message: `Forbidden: origin ${origin} is not allowed.`, reason: "origin" } });
270
+ },
271
+ async authenticate(request, mode, route) {
272
+ if (!options.auth) {
273
+ if (mode === "optional")
274
+ return ANONYMOUS;
275
+ if (!warned.has(route.path)) {
276
+ warned.add(route.path);
277
+ report(new Error(`route ${route.path}: auth = true (export auth or routes.auth) but mext.config.ts has no auth. Every request is 401`));
278
+ }
279
+ return challenge(unauthenticated("missing_token"), request);
280
+ }
281
+ let principal;
282
+ try {
283
+ principal = await options.auth({
284
+ authorization: request.headers.get("authorization") ?? undefined,
285
+ headers: new Headers(request.headers),
286
+ surface: defaultSurface("http"),
287
+ client: undefined,
288
+ route,
289
+ });
290
+ assertCallerFields(principal, "auth");
291
+ }
292
+ catch (error) {
293
+ if (isMextError(error, "UNAUTHENTICATED"))
294
+ return challenge(error, request);
295
+ report(error);
296
+ return new Response("Internal Server Error\n", { status: 500, headers: { "Content-Type": "text/plain; charset=utf-8" } });
297
+ }
298
+ if (mode === true && principal.kind === "anonymous")
299
+ return challenge(unauthenticated("missing_token"), request);
300
+ return principal;
301
+ },
302
+ };
303
+ }
208
304
  /** createMcpHttpHandler + node:http listen. 기본 127.0.0.1:3100 */
209
305
  export async function startMcpHttpServer(options) {
210
306
  const listener = createMcpHttpHandler(options);
@@ -231,8 +327,10 @@ export async function startMcpHttpServer(options) {
231
327
  };
232
328
  }
233
329
  /* Origin */
234
- const LOOPBACK_HOSTNAMES = new Set(["localhost", "127.0.0.1", "[::1]"]);
235
- /** Origin 허용 여부. allowed 미지정 = loopback 만, 지정 = 목록의 origin 과 정확히 일치 ("*" = 전체) */
330
+ /**
331
+ * Origin 허용 여부. allowed 미지정 = loopback 만, 지정 = 목록의 origin 과 정확히 일치 ("*" = 전체, "loopback" = loopback).
332
+ * http · https 외 scheme (chrome-extension://<id>) = URL.origin 이 "null" 이라 목록과 문자열 비교
333
+ */
236
334
  export function originAllowed(origin, allowed) {
237
335
  if (allowed?.includes("*"))
238
336
  return true;
@@ -244,9 +342,11 @@ export function originAllowed(origin, allowed) {
244
342
  return false;
245
343
  }
246
344
  if (url.protocol !== "http:" && url.protocol !== "https:")
247
- return false;
345
+ return allowed?.includes(origin) ?? false;
346
+ if (LOOPBACK_HOSTNAMES.has(url.hostname) && (allowed === undefined || allowed.includes(LOOPBACK_ENTRY)))
347
+ return true;
248
348
  if (allowed === undefined)
249
- return LOOPBACK_HOSTNAMES.has(url.hostname);
349
+ return false;
250
350
  return allowed.some((entry) => normalizeOrigin(entry) === url.origin);
251
351
  }
252
352
  function normalizeOrigin(entry) {
@@ -266,6 +366,45 @@ function statelessHeaders(source) {
266
366
  headers.delete(name);
267
367
  return headers;
268
368
  }
369
+ /**
370
+ * IncomingMessage → 요청 본문 스트림 (backpressure = pause · resume).
371
+ * Readable.toWeb 대신 직접: Node 22 의 toWeb 스트림은 cancel (413 · 403 조기 거부) 뒤 남은 본문이 도착하면
372
+ * 닫힌 controller 에 enqueue 해 프로세스가 죽는다 (ERR_INVALID_STATE). cancel = 남은 본문 버림 (Node 기본 동작과 같음)
373
+ */
374
+ function nodeBody(req) {
375
+ let done = false;
376
+ return new ReadableStream({
377
+ start(controller) {
378
+ req.on("data", (chunk) => {
379
+ if (done)
380
+ return;
381
+ controller.enqueue(new Uint8Array(chunk));
382
+ if ((controller.desiredSize ?? 0) <= 0)
383
+ req.pause();
384
+ });
385
+ req.once("end", () => {
386
+ if (done)
387
+ return;
388
+ done = true;
389
+ controller.close();
390
+ });
391
+ req.once("error", (error) => {
392
+ if (done)
393
+ return;
394
+ done = true;
395
+ controller.error(error);
396
+ });
397
+ req.pause();
398
+ },
399
+ pull() {
400
+ req.resume();
401
+ },
402
+ cancel() {
403
+ done = true;
404
+ req.resume();
405
+ },
406
+ });
407
+ }
269
408
  function nodeHeaders(req) {
270
409
  const headers = new Headers();
271
410
  for (const [name, value] of Object.entries(req.headers)) {
@@ -358,13 +497,16 @@ function jsonResponse(status, body, headers = {}) {
358
497
  return new Response(JSON.stringify(body), { status, headers: { "Content-Type": "application/json", ...headers } });
359
498
  }
360
499
  function unauthenticatedResponse(error, id, resourceMetadata) {
500
+ return jsonResponse(401, rpcError(-32_001, error.message, error.toJSON(), id), { "WWW-Authenticate": bearerChallenge(error, resourceMetadata) });
501
+ }
502
+ /** 401 의 WWW-Authenticate (/mcp · routes 공통) */
503
+ function bearerChallenge(error, resourceMetadata) {
361
504
  const params = [];
362
505
  if (error.details.reason === "invalid_token")
363
506
  params.push('error="invalid_token"');
364
507
  if (resourceMetadata !== undefined)
365
508
  params.push(`resource_metadata="${resourceMetadata}"`);
366
- const challenge = params.length > 0 ? `Bearer ${params.join(", ")}` : "Bearer";
367
- return jsonResponse(401, rpcError(-32_001, error.message, error.toJSON(), id), { "WWW-Authenticate": challenge });
509
+ return params.length > 0 ? `Bearer ${params.join(", ")}` : "Bearer";
368
510
  }
369
511
  /* OAuth 보호 자원 메타데이터 (RFC 9728) */
370
512
  /** MCP 엔드포인트 경로를 삽입한 well-known 경로 (/.well-known/oauth-protected-resource/mcp) */
@@ -411,6 +553,40 @@ function requestId(body) {
411
553
  return null;
412
554
  return typeof id === "string" || typeof id === "number" ? id : null;
413
555
  }
556
+ /** 요청 본문 params._meta 의 clientInfo (2026-07-28 envelope). 자가 신고 값. 없음 · 형식 오류 = undefined */
557
+ function reportedClientOf(body) {
558
+ if (body === null || typeof body !== "object" || Array.isArray(body))
559
+ return undefined;
560
+ const meta = body.params?._meta;
561
+ if (meta === null || typeof meta !== "object")
562
+ return undefined;
563
+ return reportedClient(meta[CLIENT_INFO_META_KEY]);
564
+ }
565
+ /** tools/call 의 params._meta (traceparent 등) */
566
+ function metaOf(body) {
567
+ if (body === null || typeof body !== "object" || Array.isArray(body))
568
+ return undefined;
569
+ const meta = body.params?._meta;
570
+ return meta !== null && typeof meta === "object" && !Array.isArray(meta) ? meta : undefined;
571
+ }
572
+ function optional(key, value) {
573
+ return (value === undefined ? {} : { [key]: value });
574
+ }
575
+ /**
576
+ * tools: "command" 의 tools/call 이 가리키는 명령어가 주체의 표면에서 숨긴 명령어면 그 항목. 도구 이름 = 정식 ID 만 (별칭은 도구가 아니다).
577
+ * 보이는 명령어 · 없는 이름 = undefined
578
+ */
579
+ function hiddenToolCall(registry, principal, body) {
580
+ if (body === null || typeof body !== "object" || Array.isArray(body))
581
+ return undefined;
582
+ const { method, params } = body;
583
+ if (method !== "tools/call" || typeof params?.name !== "string")
584
+ return undefined;
585
+ const entry = registry.commands.get(params.name);
586
+ if (!entry)
587
+ return undefined;
588
+ return exposedRegistry(registry, surfaceOf(principal, "http")).commands.has(entry.id) ? undefined : entry;
589
+ }
414
590
  function hasProgressToken(body) {
415
591
  if (body === null || typeof body !== "object" || Array.isArray(body))
416
592
  return false;