@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.
- package/dist/approval.d.ts +33 -0
- package/dist/approval.js +36 -0
- package/dist/approval.js.map +1 -0
- package/dist/audit.d.ts +123 -0
- package/dist/audit.js +221 -0
- package/dist/audit.js.map +1 -0
- package/dist/build-cli.d.ts +1 -0
- package/dist/build-cli.js.map +1 -1
- package/dist/build.d.ts +2 -0
- package/dist/build.js +46 -5
- package/dist/build.js.map +1 -1
- package/dist/caller.d.ts +53 -0
- package/dist/caller.js +51 -0
- package/dist/caller.js.map +1 -0
- package/dist/cli/args.js +1 -2
- package/dist/cli/args.js.map +1 -1
- package/dist/cli/build.js +26 -4
- package/dist/cli/build.js.map +1 -1
- package/dist/cli/dev-audit.d.ts +25 -0
- package/dist/cli/dev-audit.js +84 -0
- package/dist/cli/dev-audit.js.map +1 -0
- package/dist/cli/dev-lock.d.ts +3 -1
- package/dist/cli/dev-lock.js +12 -5
- package/dist/cli/dev-lock.js.map +1 -1
- package/dist/cli/dev-server.d.ts +9 -0
- package/dist/cli/dev-server.js +27 -3
- package/dist/cli/dev-server.js.map +1 -1
- package/dist/cli/dev-tap.d.ts +20 -0
- package/dist/cli/dev-tap.js +70 -0
- package/dist/cli/dev-tap.js.map +1 -0
- package/dist/cli/dev.d.ts +6 -0
- package/dist/cli/dev.js +20 -8
- package/dist/cli/dev.js.map +1 -1
- package/dist/cli/help.js +2 -0
- package/dist/cli/help.js.map +1 -1
- package/dist/cli/inspect.d.ts +7 -0
- package/dist/cli/inspect.js +699 -0
- package/dist/cli/inspect.js.map +1 -0
- package/dist/cli/main.js +10 -0
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/media.d.ts +19 -0
- package/dist/cli/media.js +77 -0
- package/dist/cli/media.js.map +1 -0
- package/dist/cli/output.d.ts +1 -1
- package/dist/cli/output.js +3 -2
- package/dist/cli/output.js.map +1 -1
- package/dist/cli/run.js +33 -4
- package/dist/cli/run.js.map +1 -1
- package/dist/cli/serve.d.ts +10 -0
- package/dist/cli/serve.js +114 -0
- package/dist/cli/serve.js.map +1 -0
- package/dist/cli/types.d.ts +9 -0
- package/dist/cli/types.js +77 -0
- package/dist/cli/types.js.map +1 -0
- package/dist/client/index.d.ts +11 -0
- package/dist/client/index.js +7 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client/manifest.js +6 -2
- package/dist/client/manifest.js.map +1 -1
- package/dist/client/runner.js +40 -3
- package/dist/client/runner.js.map +1 -1
- package/dist/client/typed.d.ts +56 -0
- package/dist/client/typed.js +526 -0
- package/dist/client/typed.js.map +1 -0
- package/dist/client/types.d.ts +225 -0
- package/dist/client/types.js +2 -0
- package/dist/client/types.js.map +1 -0
- package/dist/command.d.ts +61 -5
- package/dist/command.js +42 -1
- package/dist/command.js.map +1 -1
- package/dist/config.d.ts +59 -10
- package/dist/config.js +67 -7
- package/dist/config.js.map +1 -1
- package/dist/content.d.ts +65 -0
- package/dist/content.js +99 -0
- package/dist/content.js.map +1 -0
- package/dist/errors.d.ts +21 -3
- package/dist/errors.js +13 -0
- package/dist/errors.js.map +1 -1
- package/dist/expose.d.ts +9 -0
- package/dist/expose.js +52 -0
- package/dist/expose.js.map +1 -0
- package/dist/index.d.ts +18 -7
- package/dist/index.js +8 -2
- package/dist/index.js.map +1 -1
- package/dist/inspect/agent.d.ts +104 -0
- package/dist/inspect/agent.js +125 -0
- package/dist/inspect/agent.js.map +1 -0
- package/dist/inspect/audit.d.ts +46 -0
- package/dist/inspect/audit.js +133 -0
- package/dist/inspect/audit.js.map +1 -0
- package/dist/inspect/compare.d.ts +12 -0
- package/dist/inspect/compare.js +31 -0
- package/dist/inspect/compare.js.map +1 -0
- package/dist/inspect/index.d.ts +17 -0
- package/dist/inspect/index.js +12 -0
- package/dist/inspect/index.js.map +1 -0
- package/dist/inspect/inspector.d.ts +168 -0
- package/dist/inspect/inspector.js +392 -0
- package/dist/inspect/inspector.js.map +1 -0
- package/dist/inspect/profiles.d.ts +95 -0
- package/dist/inspect/profiles.js +235 -0
- package/dist/inspect/profiles.js.map +1 -0
- package/dist/inspect/scenario.d.ts +126 -0
- package/dist/inspect/scenario.js +555 -0
- package/dist/inspect/scenario.js.map +1 -0
- package/dist/inspect/ui-server.d.ts +31 -0
- package/dist/inspect/ui-server.js +378 -0
- package/dist/inspect/ui-server.js.map +1 -0
- package/dist/inspect/ui.html +917 -0
- package/dist/instrumentation.d.ts +13 -2
- package/dist/instrumentation.js +11 -1
- package/dist/instrumentation.js.map +1 -1
- package/dist/invoke.d.ts +36 -5
- package/dist/invoke.js +162 -21
- package/dist/invoke.js.map +1 -1
- package/dist/manifest.d.ts +19 -3
- package/dist/manifest.js +5 -1
- package/dist/manifest.js.map +1 -1
- package/dist/mcp/constants.d.ts +7 -0
- package/dist/mcp/constants.js +7 -0
- package/dist/mcp/constants.js.map +1 -1
- package/dist/mcp/dispatch.d.ts +10 -4
- package/dist/mcp/dispatch.js +24 -7
- package/dist/mcp/dispatch.js.map +1 -1
- package/dist/mcp/help.d.ts +2 -0
- package/dist/mcp/help.js +6 -1
- package/dist/mcp/help.js.map +1 -1
- package/dist/mcp/host.d.ts +14 -0
- package/dist/mcp/host.js +48 -0
- package/dist/mcp/host.js.map +1 -0
- package/dist/mcp/http.d.ts +38 -7
- package/dist/mcp/http.js +199 -23
- package/dist/mcp/http.js.map +1 -1
- package/dist/mcp/index.d.ts +2 -1
- package/dist/mcp/index.js +2 -1
- package/dist/mcp/index.js.map +1 -1
- package/dist/mcp/server.d.ts +12 -1
- package/dist/mcp/server.js +63 -17
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/stdio.d.ts +4 -0
- package/dist/mcp/stdio.js +3 -0
- package/dist/mcp/stdio.js.map +1 -1
- package/dist/mcp/tasks.d.ts +3 -1
- package/dist/mcp/tasks.js +7 -4
- package/dist/mcp/tasks.js.map +1 -1
- package/dist/principal.d.ts +22 -0
- package/dist/principal.js +29 -0
- package/dist/principal.js.map +1 -1
- package/dist/registry.d.ts +5 -0
- package/dist/registry.js +21 -0
- package/dist/registry.js.map +1 -1
- package/dist/remote/client.d.ts +26 -1
- package/dist/remote/client.js +180 -55
- package/dist/remote/client.js.map +1 -1
- package/dist/remote/index.d.ts +1 -1
- package/dist/remote/index.js.map +1 -1
- package/dist/remote/oauth.d.ts +1 -1
- package/dist/remote/oauth.js +1 -1
- package/dist/remote/oauth.js.map +1 -1
- package/dist/route.d.ts +40 -2
- package/dist/route.js +128 -8
- package/dist/route.js.map +1 -1
- package/dist/start.js +9 -4
- package/dist/start.js.map +1 -1
- package/dist/suggest.js +1 -1
- package/dist/suggest.js.map +1 -1
- package/dist/typegen.d.ts +27 -0
- package/dist/typegen.js +62 -0
- package/dist/typegen.js.map +1 -0
- package/dist/version-constant.d.ts +5 -0
- package/dist/version-constant.js +6 -0
- package/dist/version-constant.js.map +1 -0
- package/dist/version.d.ts +1 -2
- package/dist/version.js +1 -2
- package/dist/version.js.map +1 -1
- 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
|
-
|
|
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
|
package/dist/mcp/help.js.map
CHANGED
|
@@ -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;
|
package/dist/mcp/host.js
ADDED
|
@@ -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"]}
|
package/dist/mcp/http.d.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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 {
|
|
3
|
-
import {
|
|
4
|
-
import { isMextError } from "../errors.js";
|
|
5
|
-
import {
|
|
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
|
-
|
|
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({
|
|
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
|
|
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
|
|
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,
|
|
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({
|
|
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:
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
235
|
-
|
|
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
|
|
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
|
-
|
|
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;
|