@obov/mext 0.3.1 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/build-cli.js +2 -1
- package/dist/build-cli.js.map +1 -1
- package/dist/build.js +67 -4
- package/dist/build.js.map +1 -1
- package/dist/cli/ask-url.d.ts +16 -0
- package/dist/cli/ask-url.js +27 -0
- package/dist/cli/ask-url.js.map +1 -0
- package/dist/cli/build.js +9 -2
- package/dist/cli/build.js.map +1 -1
- package/dist/cli/dev-server.d.ts +7 -1
- package/dist/cli/dev-server.js +15 -2
- package/dist/cli/dev-server.js.map +1 -1
- package/dist/cli/dev.js +22 -7
- package/dist/cli/dev.js.map +1 -1
- package/dist/cli/login.d.ts +5 -0
- package/dist/cli/login.js +11 -7
- package/dist/cli/login.js.map +1 -1
- package/dist/cli/logout.js +4 -6
- package/dist/cli/logout.js.map +1 -1
- package/dist/cli/output.d.ts +1 -1
- package/dist/cli/output.js.map +1 -1
- package/dist/cli/run.js +7 -3
- package/dist/cli/run.js.map +1 -1
- package/dist/client/cache.d.ts +14 -4
- package/dist/client/cache.js +10 -3
- package/dist/client/cache.js.map +1 -1
- package/dist/client/main.js +11 -10
- package/dist/client/main.js.map +1 -1
- package/dist/client/manifest.d.ts +13 -1
- package/dist/client/manifest.js +43 -0
- package/dist/client/manifest.js.map +1 -1
- package/dist/client/runner.d.ts +3 -1
- package/dist/client/runner.js +80 -19
- package/dist/client/runner.js.map +1 -1
- package/dist/client/task.d.ts +47 -0
- package/dist/client/task.js +126 -0
- package/dist/client/task.js.map +1 -0
- package/dist/command.d.ts +30 -3
- package/dist/command.js +16 -0
- package/dist/command.js.map +1 -1
- package/dist/config.d.ts +40 -6
- package/dist/config.js +57 -8
- package/dist/config.js.map +1 -1
- package/dist/errors.d.ts +3 -1
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +17 -5
- package/dist/index.js +9 -2
- package/dist/index.js.map +1 -1
- package/dist/instrumentation.d.ts +14 -2
- package/dist/instrumentation.js +12 -4
- package/dist/instrumentation.js.map +1 -1
- package/dist/invoke.d.ts +38 -0
- package/dist/invoke.js +72 -3
- package/dist/invoke.js.map +1 -1
- package/dist/local-bin.js +5 -3
- package/dist/local-bin.js.map +1 -1
- package/dist/manifest.d.ts +48 -3
- package/dist/manifest.js +14 -3
- package/dist/manifest.js.map +1 -1
- package/dist/mcp/constants.d.ts +4 -0
- package/dist/mcp/constants.js +4 -0
- package/dist/mcp/constants.js.map +1 -1
- package/dist/mcp/dispatch.d.ts +21 -7
- package/dist/mcp/dispatch.js +42 -7
- package/dist/mcp/dispatch.js.map +1 -1
- package/dist/mcp/http.d.ts +23 -5
- package/dist/mcp/http.js +64 -9
- package/dist/mcp/http.js.map +1 -1
- package/dist/mcp/index.d.ts +14 -6
- package/dist/mcp/index.js +7 -3
- package/dist/mcp/index.js.map +1 -1
- package/dist/mcp/install.d.ts +1 -1
- package/dist/mcp/install.js +3 -2
- package/dist/mcp/install.js.map +1 -1
- package/dist/mcp/mrtr.d.ts +40 -8
- package/dist/mcp/mrtr.js +32 -11
- package/dist/mcp/mrtr.js.map +1 -1
- package/dist/mcp/prompts.d.ts +25 -0
- package/dist/mcp/prompts.js +164 -0
- package/dist/mcp/prompts.js.map +1 -0
- package/dist/mcp/resources.d.ts +48 -0
- package/dist/mcp/resources.js +142 -0
- package/dist/mcp/resources.js.map +1 -0
- package/dist/mcp/server.d.ts +30 -5
- package/dist/mcp/server.js +143 -16
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/stdio.d.ts +5 -0
- package/dist/mcp/stdio.js +41 -3
- package/dist/mcp/stdio.js.map +1 -1
- package/dist/mcp/subscriptions.d.ts +61 -0
- package/dist/mcp/subscriptions.js +188 -0
- package/dist/mcp/subscriptions.js.map +1 -0
- package/dist/mcp/tasks.d.ts +127 -0
- package/dist/mcp/tasks.js +448 -0
- package/dist/mcp/tasks.js.map +1 -0
- package/dist/module-files.d.ts +24 -0
- package/dist/module-files.js +97 -0
- package/dist/module-files.js.map +1 -0
- package/dist/prompt.d.ts +133 -0
- package/dist/prompt.js +222 -0
- package/dist/prompt.js.map +1 -0
- package/dist/registry.d.ts +6 -0
- package/dist/registry.js.map +1 -1
- package/dist/remote/client.d.ts +17 -1
- package/dist/remote/client.js +18 -2
- package/dist/remote/client.js.map +1 -1
- package/dist/resource.d.ts +169 -0
- package/dist/resource.js +301 -0
- package/dist/resource.js.map +1 -0
- package/dist/route.d.ts +71 -0
- package/dist/route.js +280 -0
- package/dist/route.js.map +1 -0
- package/dist/runtime.d.ts +10 -0
- package/dist/runtime.js +11 -0
- package/dist/runtime.js.map +1 -0
- package/dist/start.d.ts +14 -3
- package/dist/start.js +76 -11
- package/dist/start.js.map +1 -1
- package/dist/state.d.ts +38 -0
- package/dist/state.js +98 -0
- package/dist/state.js.map +1 -0
- package/package.json +3 -2
package/dist/manifest.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"manifest.js","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash } from \"node:crypto\"\nimport type { JsonSchema, RegisteredCommand, Registry } from \"./registry.ts\"\nimport { loadRegistry } from \"./scan.ts\"\n\nexport const MANIFEST_SCHEMA_VERSION = 1\n\nexport interface ManifestCommand {\n id: string\n description: string\n /** 별칭 전체 ID */\n aliases: string[]\n /** commands/ 기준 상대 경로 (posix) */\n file?: string\n readOnly: boolean\n destructive: boolean\n requiresConfirmation: boolean\n scopes: string[]\n positional: string[]\n examples: unknown[]\n /** JSON Schema 2020-12 */\n inputSchema: JsonSchema\n outputSchema?: JsonSchema\n}\n\nexport interface ManifestNamespace {\n id: string\n /** _index.md 내용 */\n description?: string\n}\n\nexport interface Manifest {\n schemaVersion: typeof MANIFEST_SCHEMA_VERSION\n /** commands/_index.md 내용 */\n description?: string\n /** 루트 제외, ID 순 */\n namespaces: ManifestNamespace[]\n /** ID 순 */\n commands: ManifestCommand[]\n}\n\n/** 명령어 하나의 메타데이터 (manifest · help 공통) */\nexport function describeCommand(entry: RegisteredCommand): ManifestCommand {\n const c = entry.command\n const out: ManifestCommand = {\n id: entry.id,\n description: c.description,\n aliases: [...entry.aliases],\n readOnly: c.readOnly,\n destructive: c.destructive,\n requiresConfirmation: c.requiresConfirmation,\n scopes: [...c.scopes],\n positional: [...c.positional],\n examples: [...c.examples],\n inputSchema: entry.inputSchema,\n }\n if (entry.file !== undefined) out.file = entry.file\n if (entry.outputSchema !== undefined) out.outputSchema = entry.outputSchema\n return out\n}\n\nexport function buildManifest(registry: Registry): Manifest {\n const manifest: Manifest = {\n schemaVersion: MANIFEST_SCHEMA_VERSION,\n namespaces: [],\n commands: [...registry.commands.values()].map(describeCommand),\n }\n for (const ns of registry.namespaces.values()) {\n if (ns.id === \"\") {\n if (ns.description !== undefined) manifest.description = ns.description\n continue\n }\n manifest.namespaces.push(ns.description === undefined ? { id: ns.id } : { id: ns.id, description: ns.description })\n }\n return manifest\n}\n\n/** 빌드 시 manifest 생성: commands/ 스캔 → Manifest (JSON 직렬화 가능) */\nexport async function generateManifest(dir: string): Promise<Manifest> {\n return buildManifest(await loadRegistry(dir))\n}\n\n/** mext build 의 .mext/manifest.json 명령어: Manifest 명령어 + 번들 모듈 경로 */\nexport interface BuildManifestCommand extends ManifestCommand {\n /** .mext/ 기준 상대 경로 (posix). commands/users/get.ts → \"commands/users/get.js\". default export = defineCommand 결과 */\n module: string\n}\n\n/**\n * .mext/manifest.json (mext build 산출, mext start · handler.js 가 읽는다).\n * namespaces · description = Manifest 와 같음\n */\nexport interface BuildManifest extends Omit<Manifest, \"commands\"> {\n /** 빌드한 @obov/mext 버전. 실행 시 설치된 버전과 다르면 시작 오류 */\n mextVersion: string\n /** 빌드 식별자 (D3: mextVersion · manifestHash · 번들 코드의 sha256 앞 16자, 같은 입력 = 같은 값) */\n buildId: string\n /** manifestHash(commands) */\n manifestHash: string\n /** mext.config.ts 번들 (.mext/ 기준). 설정 파일이 없으면 생략 */\n config?: string\n /** instrumentation.ts 번들 (.mext/ 기준). 없으면 생략 */\n instrumentation?: string\n /** ID 순 */\n commands: BuildManifestCommand[]\n}\n\n/**\n * 명령어 계약의 지문: ID · 별칭 · positional · 입력 스키마 · 안전성 메타데이터 (readOnly · destructive ·\n * requiresConfirmation · scopes) 의 정규화 JSON (키 정렬, ID 순) sha256 hex.\n * 설명 · examples · outputSchema · 파일 경로는 제외 (클라이언트의 help 캐시 · 플래그 파싱에 영향 없음).\n * 빌드 manifest · 서버 (_meta[\"dev.mext/manifestHash\"]) · 클라이언트가 같은 함수로 계산한다\n */\nexport function manifestHash(commands: readonly ManifestCommand[]): string {\n const normalized = [...commands]\n .sort((a, b) => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0))\n .map((c) => ({\n id: c.id,\n aliases: [...c.aliases].sort(),\n positional: c.positional,\n inputSchema: c.inputSchema,\n readOnly: c.readOnly,\n destructive: c.destructive,\n requiresConfirmation: c.requiresConfirmation,\n scopes: [...c.scopes].sort(),\n }))\n return createHash(\"sha256\").update(canonicalJson(normalized)).digest(\"hex\")\n}\n\n/** 키 정렬 JSON (배열 순서 유지, undefined 속성 생략) */\nfunction canonicalJson(value: unknown): string {\n if (value === null || typeof value !== \"object\") return JSON.stringify(value) ?? \"null\"\n if (Array.isArray(value)) return `[${value.map((v) => (v === undefined ? \"null\" : canonicalJson(v))).join(\",\")}]`\n const entries = Object.entries(value as Record<string, unknown>)\n .filter(([, v]) => v !== undefined)\n .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))\n return `{${entries.map(([k, v]) => `${JSON.stringify(k)}:${canonicalJson(v)}`).join(\",\")}}`\n}\n"]}
|
|
1
|
+
{"version":3,"file":"manifest.js","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash } from \"node:crypto\"\nimport type { ToolExposure } from \"./mcp/dispatch.ts\"\nimport type { JsonSchema, RegisteredCommand, Registry } from \"./registry.ts\"\nimport type { ManifestPrompt } from \"./prompt.ts\"\nimport type { ManifestResource } from \"./resource.ts\"\nimport type { RouteMethod } from \"./route.ts\"\nimport { loadRegistry } from \"./scan.ts\"\n\nexport const MANIFEST_SCHEMA_VERSION = 1\n\nexport interface ManifestCommand {\n id: string\n description: string\n /** 별칭 전체 ID */\n aliases: string[]\n /** commands/ 기준 상대 경로 (posix) */\n file?: string\n readOnly: boolean\n destructive: boolean\n requiresConfirmation: boolean\n scopes: string[]\n positional: string[]\n examples: unknown[]\n /** JSON Schema 2020-12 */\n inputSchema: JsonSchema\n outputSchema?: JsonSchema\n /** task 명령어 (§12.6). 없음 = 동기 명령어 */\n task?: { ttlMs: number }\n}\n\nexport interface ManifestNamespace {\n id: string\n /** _index.md 내용 */\n description?: string\n}\n\nexport interface Manifest {\n schemaVersion: typeof MANIFEST_SCHEMA_VERSION\n /** commands/_index.md 내용 */\n description?: string\n /** 루트 제외, ID 순 */\n namespaces: ManifestNamespace[]\n /** ID 순 */\n commands: ManifestCommand[]\n}\n\n/** 명령어 하나의 메타데이터 (manifest · help 공통) */\nexport function describeCommand(entry: RegisteredCommand): ManifestCommand {\n const c = entry.command\n const out: ManifestCommand = {\n id: entry.id,\n description: c.description,\n aliases: [...entry.aliases],\n readOnly: c.readOnly,\n destructive: c.destructive,\n requiresConfirmation: c.requiresConfirmation,\n scopes: [...c.scopes],\n positional: [...c.positional],\n examples: [...c.examples],\n inputSchema: entry.inputSchema,\n }\n if (entry.file !== undefined) out.file = entry.file\n if (entry.outputSchema !== undefined) out.outputSchema = entry.outputSchema\n if (c.task !== undefined) out.task = { ttlMs: c.task.ttlMs }\n return out\n}\n\nexport function buildManifest(registry: Registry): Manifest {\n const manifest: Manifest = {\n schemaVersion: MANIFEST_SCHEMA_VERSION,\n namespaces: [],\n commands: [...registry.commands.values()].map(describeCommand),\n }\n for (const ns of registry.namespaces.values()) {\n if (ns.id === \"\") {\n if (ns.description !== undefined) manifest.description = ns.description\n continue\n }\n manifest.namespaces.push(ns.description === undefined ? { id: ns.id } : { id: ns.id, description: ns.description })\n }\n return manifest\n}\n\n/** 빌드 시 manifest 생성: commands/ 스캔 → Manifest (JSON 직렬화 가능) */\nexport async function generateManifest(dir: string): Promise<Manifest> {\n return buildManifest(await loadRegistry(dir))\n}\n\n/** mext build 의 .mext/manifest.json 명령어: Manifest 명령어 + 번들 모듈 경로 */\nexport interface BuildManifestCommand extends ManifestCommand {\n /** .mext/ 기준 상대 경로 (posix). commands/users/get.ts → \"commands/users/get.js\". default export = defineCommand 결과 */\n module: string\n}\n\n/** mext build 의 .mext/manifest.json 자원: 자원 메타데이터 + 번들 모듈 경로 */\nexport interface BuildManifestResource extends ManifestResource {\n /** .mext/ 기준 상대 경로 (posix). resources/users/profile.ts → \"resources/users/profile.js\". default export = defineResource 결과 */\n module: string\n}\n\n/** mext build 의 .mext/manifest.json 프롬프트: 프롬프트 메타데이터 + 번들 모듈 경로 */\nexport interface BuildManifestPrompt extends ManifestPrompt {\n /** .mext/ 기준 상대 경로 (posix). prompts/github/review.ts → \"prompts/github/review.js\". default export = definePrompt 결과 */\n module: string\n}\n\n/**\n * .mext/manifest.json (mext build 산출, mext start · handler.js 가 읽는다).\n * namespaces · description = Manifest 와 같음\n */\nexport interface BuildManifest extends Omit<Manifest, \"commands\"> {\n /** 빌드한 @obov/mext 버전. 실행 시 설치된 버전과 다르면 시작 오류 */\n mextVersion: string\n /** 빌드 식별자 (D3: mextVersion · manifestHash · 번들 코드의 sha256 앞 16자, 같은 입력 = 같은 값) */\n buildId: string\n /** manifestHash(commands, { resources: 자원 uri, prompts: 이름 · argsSchema }) */\n manifestHash: string\n /** MCP 도구 노출 방식 (mext.config.ts tools, 기본 execute). 고객 CLI 번들 형태의 호출 방식 (§12.2) */\n tools: ToolExposure\n /** mext.config.ts 번들 (.mext/ 기준). 설정 파일이 없으면 생략 */\n config?: string\n /** instrumentation.ts 번들 (.mext/ 기준). 없으면 생략 */\n instrumentation?: string\n /** ID 순 */\n commands: BuildManifestCommand[]\n /** MCP 자원 (§12.3). uri 순. 자원이 없으면 생략 */\n resources?: BuildManifestResource[]\n /** MCP 프롬프트 (§12.3). 이름 순. 프롬프트가 없으면 생략 */\n prompts?: BuildManifestPrompt[]\n /** routes/ (§12.4). 경로 순. route 가 없으면 생략. manifestHash 에 포함하지 않는다 (명령어 계약 아님) */\n routes?: BuildManifestRoute[]\n}\n\n/** .mext/manifest.json 의 route */\nexport interface BuildManifestRoute {\n /** URL 경로 패턴 (\"/items/[id]\") */\n path: string\n /** routes/ 기준 상대 경로 (posix) */\n file: string\n /** export 한 메서드. 그 외 = 405 (모듈 import 없음) */\n methods: RouteMethod[]\n /** .mext/ 기준 상대 경로. routes/items/[id].ts → \"routes/items/[id].js\" */\n module: string\n}\n\n/**\n * 명령어 계약의 지문: ID · 별칭 · positional · 입력 스키마 · 안전성 메타데이터 (readOnly · destructive ·\n * requiresConfirmation · scopes) 의 정규화 JSON (키 정렬, ID 순) sha256 hex.\n * 설명 · examples · outputSchema · task · 파일 경로는 제외 (클라이언트의 help 캐시 · 플래그 파싱에 영향 없음).\n * extra.resources = 자원 uri (고정 URI · 템플릿 원문, §12.3), extra.prompts = 프롬프트 이름 · args JSON Schema (§12.3).\n * 둘 다 없거나 비면 명령어만의 값과 같다.\n * 빌드 manifest · 서버 (_meta[\"dev.mext/manifestHash\"]) · 클라이언트가 같은 함수로 계산한다\n */\nexport function manifestHash(\n commands: readonly ManifestCommand[],\n extra: { resources?: readonly string[]; prompts?: readonly { name: string; argsSchema?: JsonSchema | undefined }[] } = {},\n): string {\n const normalized = [...commands]\n .sort((a, b) => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0))\n .map((c) => ({\n id: c.id,\n aliases: [...c.aliases].sort(),\n positional: c.positional,\n inputSchema: c.inputSchema,\n readOnly: c.readOnly,\n destructive: c.destructive,\n requiresConfirmation: c.requiresConfirmation,\n scopes: [...c.scopes].sort(),\n }))\n const resources = [...(extra.resources ?? [])].sort()\n const prompts = [...(extra.prompts ?? [])]\n .sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0))\n .map((p) => ({ name: p.name, argsSchema: p.argsSchema }))\n const value =\n resources.length > 0 || prompts.length > 0\n ? { commands: normalized, ...(resources.length > 0 ? { resources } : {}), ...(prompts.length > 0 ? { prompts } : {}) }\n : normalized\n return createHash(\"sha256\").update(canonicalJson(value)).digest(\"hex\")\n}\n\n/** 키 정렬 JSON (배열 순서 유지, undefined 속성 생략) */\nfunction canonicalJson(value: unknown): string {\n if (value === null || typeof value !== \"object\") return JSON.stringify(value) ?? \"null\"\n if (Array.isArray(value)) return `[${value.map((v) => (v === undefined ? \"null\" : canonicalJson(v))).join(\",\")}]`\n const entries = Object.entries(value as Record<string, unknown>)\n .filter(([, v]) => v !== undefined)\n .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))\n return `{${entries.map(([k, v]) => `${JSON.stringify(k)}:${canonicalJson(v)}`).join(\",\")}}`\n}\n"]}
|
package/dist/mcp/constants.d.ts
CHANGED
|
@@ -7,3 +7,7 @@ export declare const DEFAULT_PORT = 3100;
|
|
|
7
7
|
* 고객 CLI 번들도 import (의존 없는 모듈)
|
|
8
8
|
*/
|
|
9
9
|
export declare const MANIFEST_HASH_META_KEY = "dev.mext/manifestHash";
|
|
10
|
+
/** server/discover 결과 _meta 의 도구 노출 방식 (execute · split · command). 원격 CLI 범용 형태가 호출 방식을 고른다 (§12.2) */
|
|
11
|
+
export declare const TOOLS_META_KEY = "dev.mext/tools";
|
|
12
|
+
/** command 노출 도구의 tools/list _meta: 명령어 메타데이터 (aliases · 안전성 · scopes · positional · examples) */
|
|
13
|
+
export declare const COMMAND_META_KEY = "dev.mext/command";
|
package/dist/mcp/constants.js
CHANGED
|
@@ -7,4 +7,8 @@ export const DEFAULT_PORT = 3100;
|
|
|
7
7
|
* 고객 CLI 번들도 import (의존 없는 모듈)
|
|
8
8
|
*/
|
|
9
9
|
export const MANIFEST_HASH_META_KEY = "dev.mext/manifestHash";
|
|
10
|
+
/** server/discover 결과 _meta 의 도구 노출 방식 (execute · split · command). 원격 CLI 범용 형태가 호출 방식을 고른다 (§12.2) */
|
|
11
|
+
export const TOOLS_META_KEY = "dev.mext/tools";
|
|
12
|
+
/** command 노출 도구의 tools/list _meta: 명령어 메타데이터 (aliases · 안전성 · scopes · positional · examples) */
|
|
13
|
+
export const COMMAND_META_KEY = "dev.mext/command";
|
|
10
14
|
//# sourceMappingURL=constants.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constants.js","sourceRoot":"","sources":["../../src/mcp/constants.ts"],"names":[],"mappings":"AAAA,4BAA4B;AAC5B,MAAM,CAAC,MAAM,QAAQ,GAAG,MAAM,CAAA;AAC9B,MAAM,CAAC,MAAM,YAAY,GAAG,WAAW,CAAA;AACvC,MAAM,CAAC,MAAM,YAAY,GAAG,IAAI,CAAA;AAEhC;;;GAGG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG,uBAAuB,CAAA","sourcesContent":["/** MCP 엔드포인트 경로 (POST 만) */\nexport const MCP_PATH = \"/mcp\"\nexport const DEFAULT_HOST = \"127.0.0.1\"\nexport const DEFAULT_PORT = 3100\n\n/**\n * server/discover · tools/call 결과 _meta 의 현재 manifestHash 키 (스키마 drift 감지, §5).\n * 고객 CLI 번들도 import (의존 없는 모듈)\n */\nexport const MANIFEST_HASH_META_KEY = \"dev.mext/manifestHash\"\n"]}
|
|
1
|
+
{"version":3,"file":"constants.js","sourceRoot":"","sources":["../../src/mcp/constants.ts"],"names":[],"mappings":"AAAA,4BAA4B;AAC5B,MAAM,CAAC,MAAM,QAAQ,GAAG,MAAM,CAAA;AAC9B,MAAM,CAAC,MAAM,YAAY,GAAG,WAAW,CAAA;AACvC,MAAM,CAAC,MAAM,YAAY,GAAG,IAAI,CAAA;AAEhC;;;GAGG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG,uBAAuB,CAAA;AAE7D,wGAAwG;AACxG,MAAM,CAAC,MAAM,cAAc,GAAG,gBAAgB,CAAA;AAE9C,kGAAkG;AAClG,MAAM,CAAC,MAAM,gBAAgB,GAAG,kBAAkB,CAAA","sourcesContent":["/** MCP 엔드포인트 경로 (POST 만) */\nexport const MCP_PATH = \"/mcp\"\nexport const DEFAULT_HOST = \"127.0.0.1\"\nexport const DEFAULT_PORT = 3100\n\n/**\n * server/discover · tools/call 결과 _meta 의 현재 manifestHash 키 (스키마 drift 감지, §5).\n * 고객 CLI 번들도 import (의존 없는 모듈)\n */\nexport const MANIFEST_HASH_META_KEY = \"dev.mext/manifestHash\"\n\n/** server/discover 결과 _meta 의 도구 노출 방식 (execute · split · command). 원격 CLI 범용 형태가 호출 방식을 고른다 (§12.2) */\nexport const TOOLS_META_KEY = \"dev.mext/tools\"\n\n/** command 노출 도구의 tools/list _meta: 명령어 메타데이터 (aliases · 안전성 · scopes · positional · examples) */\nexport const COMMAND_META_KEY = \"dev.mext/command\"\n"]}
|
package/dist/mcp/dispatch.d.ts
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
import { type InvokeOptions } from "../invoke.ts";
|
|
2
2
|
import type { JsonSchema, Registry } from "../registry.ts";
|
|
3
3
|
import { type InputRequiredResult, type InputRound, type RequestStateSigner } from "./mrtr.ts";
|
|
4
|
+
import type { CreateTaskResult } from "./tasks.ts";
|
|
4
5
|
/**
|
|
5
6
|
* MCP 어댑터의 transport 무관 부분: dispatcher 도구 정의 · tools/call 실행 · help · 오류 → 결과 변환.
|
|
6
7
|
* SDK · HTTP 에 의존하지 않는다 (HTTP · stdio 가 공유).
|
|
7
8
|
*/
|
|
8
|
-
/**
|
|
9
|
-
|
|
9
|
+
/**
|
|
10
|
+
* execute = 도구 1개 (기본), split = query (readOnly · help) + mutate (그 외),
|
|
11
|
+
* command = 명령어 1개 = 도구 1개 (도구 이름 = 명령어 ID, outputSchema, help 없음, §12.2)
|
|
12
|
+
*/
|
|
13
|
+
export type ToolExposure = "execute" | "split" | "command";
|
|
10
14
|
export type DispatcherToolName = "execute" | "query" | "mutate";
|
|
11
15
|
/** MCP ToolAnnotations 중 mext 가 쓰는 항목 */
|
|
12
16
|
export interface DispatcherAnnotations {
|
|
@@ -16,14 +20,22 @@ export interface DispatcherAnnotations {
|
|
|
16
20
|
idempotentHint?: boolean;
|
|
17
21
|
openWorldHint?: boolean;
|
|
18
22
|
}
|
|
23
|
+
/** tools/list 도구 하나. dispatcher (execute · query · mutate) 또는 명령어별 도구 (command 노출) */
|
|
19
24
|
export interface DispatcherTool {
|
|
20
|
-
|
|
25
|
+
/** dispatcher = DispatcherToolName, command 노출 = 명령어 ID */
|
|
26
|
+
name: string;
|
|
21
27
|
description: string;
|
|
22
|
-
/** JSON Schema 2020-12. command 에 x-mcp-header: "Command" */
|
|
28
|
+
/** JSON Schema 2020-12. dispatcher = command 에 x-mcp-header: "Command", command 노출 = 명령어 input */
|
|
23
29
|
inputSchema: JsonSchema;
|
|
24
30
|
annotations: DispatcherAnnotations;
|
|
25
31
|
/** readOnly 명령어만 실행 (split 의 query). 그 외 = PERMISSION_DENIED */
|
|
26
32
|
readOnly: boolean;
|
|
33
|
+
/** command 노출: 이 도구가 실행하는 명령어 ID (인자 = 명령어 input 그대로). 없음 = dispatcher ({ command, args }) */
|
|
34
|
+
command?: string;
|
|
35
|
+
/** command 노출: output 의 JSON Schema 2020-12 (output 이 있을 때) */
|
|
36
|
+
outputSchema?: JsonSchema;
|
|
37
|
+
/** command 노출: tools/list 도구 _meta (COMMAND_META_KEY = CLI 원격의 help · 플래그 파싱용 메타데이터) */
|
|
38
|
+
_meta?: Record<string, unknown>;
|
|
27
39
|
}
|
|
28
40
|
export interface TextContent {
|
|
29
41
|
type: "text";
|
|
@@ -38,7 +50,7 @@ export interface ToolCallResult {
|
|
|
38
50
|
}
|
|
39
51
|
/** 핸들러 예외 (MextError 외) 의 결과 code. CLI --json 과 같음 */
|
|
40
52
|
export declare const EXECUTION_ERROR = "EXECUTION_ERROR";
|
|
41
|
-
/** 노출 방식별
|
|
53
|
+
/** 노출 방식별 도구 (정렬 순서 고정: execute · query, mutate · 명령어 ID 순) */
|
|
42
54
|
export declare function dispatcherTools(registry: Registry, exposure?: ToolExposure): DispatcherTool[];
|
|
43
55
|
export declare function findDispatcherTool(tools: readonly DispatcherTool[], name: string): DispatcherTool | undefined;
|
|
44
56
|
export interface DispatchOptions extends Omit<InvokeOptions, "readOnly"> {
|
|
@@ -52,14 +64,16 @@ export interface DispatchOptions extends Omit<InvokeOptions, "readOnly"> {
|
|
|
52
64
|
signer: RequestStateSigner;
|
|
53
65
|
};
|
|
54
66
|
}
|
|
55
|
-
/** tools/call 결과: 완료 (CallToolResult 형태)
|
|
56
|
-
export type DispatchResult = ToolCallResult | InputRequiredResult;
|
|
67
|
+
/** tools/call 결과: 완료 (CallToolResult 형태) · 입력 요청 (InputRequiredResult) · task 생성 (options.detach, §12.6) */
|
|
68
|
+
export type DispatchResult = ToolCallResult | InputRequiredResult | CreateTaskResult;
|
|
57
69
|
/**
|
|
58
70
|
* dispatcher 도구 호출: { command, args } → help 또는 invoke.
|
|
59
71
|
* MextError · 핸들러 예외는 isError 결과로 (throw 하지 않음). 인증 (UNAUTHENTICATED → 401) 은 transport 몫.
|
|
60
72
|
* options.input 이 있으면 ctx.ask 가 InputRequiredResult 를 돌려준다 (mrtr.ts).
|
|
61
73
|
*/
|
|
62
74
|
export declare function callDispatcher(registry: Registry, tool: DispatcherTool, rawArguments: unknown, options: DispatchOptions): Promise<DispatchResult>;
|
|
75
|
+
/** 핸들러 반환값 → 성공 결과 (undefined = null). task 완료 결과도 같은 변환 */
|
|
76
|
+
export declare function valueResult(value: unknown): ToolCallResult;
|
|
63
77
|
/** 오류 → isError 결과. MextError · CommandError = toJSON, 그 외 = EXECUTION_ERROR (message 만) */
|
|
64
78
|
export declare function errorResult(error: unknown): ToolCallResult;
|
|
65
79
|
/** dispatcher 도구 inputSchema. command 를 Mcp-Param-Command 헤더로 복사 (SEP-2243) */
|
package/dist/mcp/dispatch.js
CHANGED
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
import { isDeclaredError, MextError } from "../errors.js";
|
|
2
2
|
import { invoke } from "../invoke.js";
|
|
3
|
+
import { describeCommand } from "../manifest.js";
|
|
4
|
+
import { COMMAND_META_KEY } from "./constants.js";
|
|
3
5
|
import { formatHelp, help, HELP_COMMAND } from "./help.js";
|
|
4
6
|
import { createAskSession, hasPending, inputRequiredResult, } from "./mrtr.js";
|
|
5
7
|
/** 핸들러 예외 (MextError 외) 의 결과 code. CLI --json 과 같음 */
|
|
6
8
|
export const EXECUTION_ERROR = "EXECUTION_ERROR";
|
|
7
9
|
/** 도구 설명에 나열할 명령어 수 상한. 넘으면 help 안내만 */
|
|
8
10
|
const DESCRIPTION_COMMAND_LIMIT = 50;
|
|
9
|
-
/** 노출 방식별
|
|
11
|
+
/** 노출 방식별 도구 (정렬 순서 고정: execute · query, mutate · 명령어 ID 순) */
|
|
10
12
|
export function dispatcherTools(registry, exposure = "execute") {
|
|
11
13
|
const all = [...registry.commands.values()];
|
|
14
|
+
if (exposure === "command")
|
|
15
|
+
return all.map(commandTool);
|
|
12
16
|
if (exposure === "split") {
|
|
13
17
|
const reads = all.filter((e) => e.command.readOnly);
|
|
14
18
|
const writes = all.filter((e) => !e.command.readOnly);
|
|
@@ -43,6 +47,28 @@ export function dispatcherTools(registry, exposure = "execute") {
|
|
|
43
47
|
},
|
|
44
48
|
];
|
|
45
49
|
}
|
|
50
|
+
/**
|
|
51
|
+
* command 노출의 명령어별 도구 (§12.2). 이름 = 정식 ID (별칭은 도구 아님), annotations = execute 와 같은 계산을 명령어 하나에.
|
|
52
|
+
* _meta[COMMAND_META_KEY] = tools/list 만으로 CLI 원격의 help · 별칭 · positional 을 만들 메타데이터
|
|
53
|
+
*/
|
|
54
|
+
function commandTool(entry) {
|
|
55
|
+
const { id, description: _d, inputSchema: _i, outputSchema: _o, file: _f, ...meta } = describeCommand(entry);
|
|
56
|
+
const c = entry.command;
|
|
57
|
+
const tool = {
|
|
58
|
+
name: id,
|
|
59
|
+
description: c.description,
|
|
60
|
+
inputSchema: entry.inputSchema,
|
|
61
|
+
annotations: c.readOnly
|
|
62
|
+
? { title: id, readOnlyHint: true, destructiveHint: false }
|
|
63
|
+
: { title: id, readOnlyHint: false, destructiveHint: c.destructive },
|
|
64
|
+
readOnly: false,
|
|
65
|
+
command: id,
|
|
66
|
+
_meta: { [COMMAND_META_KEY]: meta },
|
|
67
|
+
};
|
|
68
|
+
if (entry.outputSchema !== undefined)
|
|
69
|
+
tool.outputSchema = entry.outputSchema;
|
|
70
|
+
return tool;
|
|
71
|
+
}
|
|
46
72
|
export function findDispatcherTool(tools, name) {
|
|
47
73
|
return tools.find((t) => t.name === name);
|
|
48
74
|
}
|
|
@@ -53,7 +79,8 @@ export function findDispatcherTool(tools, name) {
|
|
|
53
79
|
*/
|
|
54
80
|
export async function callDispatcher(registry, tool, rawArguments, options) {
|
|
55
81
|
try {
|
|
56
|
-
|
|
82
|
+
// command 노출: 인자 = 명령어 input 그대로 (검증 = invoke 의 Zod safeParse)
|
|
83
|
+
const { command, args } = tool.command !== undefined ? { command: tool.command, args: rawArguments } : parseDispatcherArguments(tool, rawArguments);
|
|
57
84
|
if (command === HELP_COMMAND) {
|
|
58
85
|
const target = args?.target;
|
|
59
86
|
if (target !== undefined && typeof target !== "string") {
|
|
@@ -73,20 +100,28 @@ export async function callDispatcher(registry, tool, rawArguments, options) {
|
|
|
73
100
|
: await invoke(registry, command, args, { ...invokeOptions, readOnly: tool.readOnly });
|
|
74
101
|
if ("resultType" in result)
|
|
75
102
|
return result;
|
|
76
|
-
|
|
77
|
-
|
|
103
|
+
if (result.detached !== undefined)
|
|
104
|
+
return result.detached;
|
|
105
|
+
return valueResult(result.value);
|
|
78
106
|
}
|
|
79
107
|
catch (error) {
|
|
80
108
|
return errorResult(error);
|
|
81
109
|
}
|
|
82
110
|
}
|
|
111
|
+
/** 핸들러 반환값 → 성공 결과 (undefined = null). task 완료 결과도 같은 변환 */
|
|
112
|
+
export function valueResult(value) {
|
|
113
|
+
const structured = value === undefined ? null : value;
|
|
114
|
+
return { content: [{ type: "text", text: stringify(structured) }], structuredContent: structured };
|
|
115
|
+
}
|
|
83
116
|
/**
|
|
84
117
|
* MRTR 한 라운드: requestState 검증 → ask 세션으로 invoke. 답 없는 ask 가 있으면 (핸들러가 오류를 삼켜도)
|
|
85
|
-
* 결과를 버리고 InputRequiredResult. elicitation 미선언 클라이언트 = ask 미주입 (INPUT_UNAVAILABLE)
|
|
118
|
+
* 결과를 버리고 InputRequiredResult. elicitation 미선언 클라이언트 = ask 미주입 (INPUT_UNAVAILABLE).
|
|
119
|
+
* 선언한 mode 만 요청 (form · url). 미선언 mode 의 ask = INPUT_UNAVAILABLE
|
|
86
120
|
*/
|
|
87
121
|
async function invokeRound(registry, tool, command, args, options, input) {
|
|
88
122
|
const { ask: _ask, ...rest } = options;
|
|
89
|
-
|
|
123
|
+
const modes = { form: input.elicitation, url: input.urlElicitation === true };
|
|
124
|
+
if (!modes.form && !modes.url)
|
|
90
125
|
return invoke(registry, command, args, { ...rest, readOnly: tool.readOnly });
|
|
91
126
|
const binding = {
|
|
92
127
|
principal: `${options.principal.kind}:${options.principal.id}`,
|
|
@@ -101,7 +136,7 @@ async function invokeRound(registry, tool, command, args, options, input) {
|
|
|
101
136
|
throw invalidRequestState(tool, command);
|
|
102
137
|
answers = { ...answers, ...prior };
|
|
103
138
|
}
|
|
104
|
-
const session = createAskSession(answers);
|
|
139
|
+
const session = createAskSession(answers, modes);
|
|
105
140
|
const signal = options.signal ? AbortSignal.any([options.signal, session.signal]) : session.signal;
|
|
106
141
|
try {
|
|
107
142
|
const result = await invoke(registry, command, args, { ...rest, signal, ask: session.ask, readOnly: tool.readOnly });
|
package/dist/mcp/dispatch.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dispatch.js","sourceRoot":"","sources":["../../src/mcp/dispatch.ts"],"names":[],"mappings":"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","sourcesContent":["import { isDeclaredError, MextError } from \"../errors.ts\"\nimport { invoke, type InvokeOptions } from \"../invoke.ts\"\nimport type { JsonSchema, RegisteredCommand, Registry } from \"../registry.ts\"\nimport { formatHelp, help, HELP_COMMAND } from \"./help.ts\"\nimport {\n createAskSession,\n hasPending,\n inputRequiredResult,\n type InputRequiredResult,\n type InputRound,\n type RequestStateBinding,\n type RequestStateSigner,\n} from \"./mrtr.ts\"\n\n/**\n * MCP 어댑터의 transport 무관 부분: dispatcher 도구 정의 · tools/call 실행 · help · 오류 → 결과 변환.\n * SDK · HTTP 에 의존하지 않는다 (HTTP · stdio 가 공유).\n */\n\n/** execute = 도구 1개 (기본), split = query (readOnly · help) + mutate (그 외) */\nexport type ToolExposure = \"execute\" | \"split\"\n\nexport type DispatcherToolName = \"execute\" | \"query\" | \"mutate\"\n\n/** MCP ToolAnnotations 중 mext 가 쓰는 항목 */\nexport interface DispatcherAnnotations {\n title?: string\n readOnlyHint: boolean\n destructiveHint?: boolean\n idempotentHint?: boolean\n openWorldHint?: boolean\n}\n\nexport interface DispatcherTool {\n name: DispatcherToolName\n description: string\n /** JSON Schema 2020-12. command 에 x-mcp-header: \"Command\" */\n inputSchema: JsonSchema\n annotations: DispatcherAnnotations\n /** readOnly 명령어만 실행 (split 의 query). 그 외 = PERMISSION_DENIED */\n readOnly: boolean\n}\n\nexport interface TextContent {\n type: \"text\"\n text: string\n}\n\n/** MCP CallToolResult 와 같은 형태 (SDK 타입 비의존) */\nexport interface ToolCallResult {\n content: TextContent[]\n /** 성공 = 핸들러 반환값 (undefined 는 null), 오류 = { error: MextError.toJSON() } */\n structuredContent: unknown\n isError?: true\n}\n\n/** 핸들러 예외 (MextError 외) 의 결과 code. CLI --json 과 같음 */\nexport const EXECUTION_ERROR = \"EXECUTION_ERROR\"\n\n/** 도구 설명에 나열할 명령어 수 상한. 넘으면 help 안내만 */\nconst DESCRIPTION_COMMAND_LIMIT = 50\n\n/** 노출 방식별 dispatcher 도구 (정렬 순서 고정: execute · query, mutate) */\nexport function dispatcherTools(registry: Registry, exposure: ToolExposure = \"execute\"): DispatcherTool[] {\n const all = [...registry.commands.values()]\n if (exposure === \"split\") {\n const reads = all.filter((e) => e.command.readOnly)\n const writes = all.filter((e) => !e.command.readOnly)\n return [\n {\n name: \"query\",\n description: describeTool(\n \"query\",\n \"Run a read-only command.\",\n reads,\n 'Commands that are not read-only are rejected here; call them with the \"mutate\" tool.',\n ),\n inputSchema: dispatcherInputSchema(),\n annotations: { title: \"Query\", readOnlyHint: true, destructiveHint: false, idempotentHint: true },\n readOnly: true,\n },\n {\n name: \"mutate\",\n description: describeTool(\"mutate\", \"Run a command that changes state.\", writes),\n inputSchema: dispatcherInputSchema(),\n annotations: { title: \"Mutate\", readOnlyHint: false, destructiveHint: true },\n readOnly: false,\n },\n ]\n }\n const readOnly = all.every((e) => e.command.readOnly)\n return [\n {\n name: \"execute\",\n description: describeTool(\"execute\", \"Run a command.\", all),\n inputSchema: dispatcherInputSchema(),\n // 단일 dispatcher 는 최악값 (researches/others.md 9)\n annotations: readOnly\n ? { title: \"Execute\", readOnlyHint: true, destructiveHint: false }\n : { title: \"Execute\", readOnlyHint: false, destructiveHint: all.some((e) => e.command.destructive) },\n readOnly: false,\n },\n ]\n}\n\nexport function findDispatcherTool(tools: readonly DispatcherTool[], name: string): DispatcherTool | undefined {\n return tools.find((t) => t.name === name)\n}\n\nexport interface DispatchOptions extends Omit<InvokeOptions, \"readOnly\"> {\n /** help 의 tool 표시용. dispatcherTools 결과 */\n tools?: readonly DispatcherTool[]\n /**\n * MRTR (MCP 2026-07-28). 지정하면 ctx.ask = InputRequiredResult 반환 · 재시도의 inputResponses 로 resolve\n * (ask 옵션 무시). requiresConfirmation 확인도 이 경로\n */\n input?: InputRound & { signer: RequestStateSigner }\n}\n\n/** tools/call 결과: 완료 (CallToolResult 형태) 또는 입력 요청 (InputRequiredResult) */\nexport type DispatchResult = ToolCallResult | InputRequiredResult\n\n/**\n * dispatcher 도구 호출: { command, args } → help 또는 invoke.\n * MextError · 핸들러 예외는 isError 결과로 (throw 하지 않음). 인증 (UNAUTHENTICATED → 401) 은 transport 몫.\n * options.input 이 있으면 ctx.ask 가 InputRequiredResult 를 돌려준다 (mrtr.ts).\n */\nexport async function callDispatcher(\n registry: Registry,\n tool: DispatcherTool,\n rawArguments: unknown,\n options: DispatchOptions,\n): Promise<DispatchResult> {\n try {\n const { command, args } = parseDispatcherArguments(tool, rawArguments)\n if (command === HELP_COMMAND) {\n const target = (args as { target?: unknown } | undefined)?.target\n if (target !== undefined && typeof target !== \"string\") {\n throw dispatcherArgumentError(tool, \"help\", \"args.target must be a string\", [], {\n path: \"args.target\",\n code: \"invalid_type\",\n message: \"expected string\",\n })\n }\n const toolFor = splitToolFor(options.tools)\n const value = help(registry, target, toolFor ? { toolFor } : {})\n return { content: [{ type: \"text\", text: formatHelp(value, tool.name) }], structuredContent: value }\n }\n const { input, tools: _tools, ...invokeOptions } = options\n const result = input\n ? await invokeRound(registry, tool, command, args, invokeOptions, input)\n : await invoke(registry, command, args, { ...invokeOptions, readOnly: tool.readOnly })\n if (\"resultType\" in result) return result\n const value = result.value === undefined ? null : result.value\n return { content: [{ type: \"text\", text: stringify(value) }], structuredContent: value }\n } catch (error) {\n return errorResult(error)\n }\n}\n\n/**\n * MRTR 한 라운드: requestState 검증 → ask 세션으로 invoke. 답 없는 ask 가 있으면 (핸들러가 오류를 삼켜도)\n * 결과를 버리고 InputRequiredResult. elicitation 미선언 클라이언트 = ask 미주입 (INPUT_UNAVAILABLE)\n */\nasync function invokeRound(\n registry: Registry,\n tool: DispatcherTool,\n command: string,\n args: unknown,\n options: Omit<InvokeOptions, \"readOnly\">,\n input: NonNullable<DispatchOptions[\"input\"]>,\n): Promise<{ command: string; value: unknown } | InputRequiredResult> {\n const { ask: _ask, ...rest } = options\n if (!input.elicitation) return invoke(registry, command, args, { ...rest, readOnly: tool.readOnly })\n const binding: RequestStateBinding = {\n principal: `${options.principal.kind}:${options.principal.id}`,\n tool: tool.name,\n command,\n args: args ?? {},\n }\n let answers: Record<string, unknown> = { ...input.inputResponses }\n if (input.requestState !== undefined) {\n const prior = input.signer.verify(input.requestState, binding)\n if (!prior) throw invalidRequestState(tool, command)\n answers = { ...answers, ...prior }\n }\n const session = createAskSession(answers)\n const signal = options.signal ? AbortSignal.any([options.signal, session.signal]) : session.signal\n try {\n const result = await invoke(registry, command, args, { ...rest, signal, ask: session.ask, readOnly: tool.readOnly })\n return hasPending(session) ? inputRequiredResult(session, input.signer, binding) : result\n } catch (error) {\n if (hasPending(session)) return inputRequiredResult(session, input.signer, binding)\n throw error\n }\n}\n\n/** 서명 · 만료 · 바인딩 검증 실패한 requestState. 처음부터 다시 호출하도록 안내 */\nfunction invalidRequestState(tool: DispatcherTool, command: string): MextError<\"INVALID_ARGUMENT\"> {\n return new MextError(\n \"INVALID_ARGUMENT\",\n `Invalid arguments for ${command}: requestState is invalid or expired. Call ${tool.name} again without requestState and inputResponses.`,\n {\n command,\n missing_fields: [],\n issues: [\n {\n path: \"requestState\",\n code: \"invalid_request_state\",\n message: \"requestState failed verification (tampered, expired, or for a different call or server key)\",\n },\n ],\n expected_schema: tool.inputSchema,\n examples: [],\n },\n )\n}\n\n/** 오류 → isError 결과. MextError · CommandError = toJSON, 그 외 = EXECUTION_ERROR (message 만) */\nexport function errorResult(error: unknown): ToolCallResult {\n const body: Record<string, unknown> & { code: string; message: string } = isDeclaredError(error)\n ? error.toJSON()\n : { code: EXECUTION_ERROR, message: error instanceof Error ? error.message : String(error) }\n return {\n content: [{ type: \"text\", text: `${body.code}: ${body.message}\\n${stringify(body)}` }],\n structuredContent: { error: body },\n isError: true,\n }\n}\n\n/** dispatcher 도구 inputSchema. command 를 Mcp-Param-Command 헤더로 복사 (SEP-2243) */\nexport function dispatcherInputSchema(): JsonSchema {\n return {\n type: \"object\",\n properties: {\n command: {\n type: \"string\",\n description: 'Command ID (e.g. \"users.get\"), or \"help\" to list namespaces and commands.',\n \"x-mcp-header\": \"Command\",\n },\n args: {\n type: \"object\",\n description: 'Command input. For \"help\": { \"target\": \"<namespace or command ID>\" }.',\n additionalProperties: true,\n },\n },\n required: [\"command\"],\n additionalProperties: false,\n }\n}\n\nfunction parseDispatcherArguments(tool: DispatcherTool, raw: unknown): { command: string; args: unknown } {\n const input = raw === undefined ? {} : raw\n if (input === null || typeof input !== \"object\" || Array.isArray(input)) {\n throw dispatcherArgumentError(tool, tool.name, \"arguments must be an object\", [], {\n path: \"\",\n code: \"invalid_type\",\n message: \"expected object\",\n })\n }\n const { command, args, ...rest } = input as Record<string, unknown>\n if (command === undefined) {\n throw dispatcherArgumentError(tool, tool.name, \"missing required: command\", [\"command\"], {\n path: \"command\",\n code: \"invalid_type\",\n message: \"expected string, received undefined\",\n })\n }\n if (typeof command !== \"string\") {\n throw dispatcherArgumentError(tool, tool.name, \"command must be a string\", [], {\n path: \"command\",\n code: \"invalid_type\",\n message: \"expected string\",\n })\n }\n const unknownKeys = Object.keys(rest)\n if (unknownKeys.length > 0) {\n throw dispatcherArgumentError(tool, tool.name, `unrecognized keys: ${unknownKeys.join(\", \")}`, [], {\n path: \"\",\n code: \"unrecognized_keys\",\n message: `Unrecognized keys: ${unknownKeys.join(\", \")}. Put command input under \"args\".`,\n })\n }\n if (args !== undefined && (args === null || typeof args !== \"object\" || Array.isArray(args))) {\n throw dispatcherArgumentError(tool, command, \"args must be an object\", [], {\n path: \"args\",\n code: \"invalid_type\",\n message: \"expected object\",\n })\n }\n return { command, args }\n}\n\nfunction dispatcherArgumentError(\n tool: DispatcherTool,\n command: string,\n summary: string,\n missing: string[],\n issue: { path: string; code: string; message: string },\n): MextError<\"INVALID_ARGUMENT\"> {\n return new MextError(\"INVALID_ARGUMENT\", `Invalid arguments for ${command}: ${summary}`, {\n command,\n missing_fields: missing,\n issues: [issue],\n expected_schema: tool.inputSchema,\n examples: [{ command: HELP_COMMAND }],\n })\n}\n\nfunction splitToolFor(tools: readonly DispatcherTool[] | undefined): ((e: RegisteredCommand) => string) | undefined {\n if (!tools || tools.length < 2) return undefined\n const query = tools.find((t) => t.readOnly)?.name\n const mutate = tools.find((t) => !t.readOnly)?.name\n if (query === undefined || mutate === undefined) return undefined\n return (e) => (e.command.readOnly ? query : mutate)\n}\n\nfunction describeTool(name: string, lead: string, commands: readonly RegisteredCommand[], note?: string): string {\n const lines = [\n lead,\n `Arguments: { \"command\": \"<command ID>\", \"args\": { ...input } }. ` +\n `Call ${name}({ \"command\": \"help\" }) to list namespaces and commands, ` +\n `${name}({ \"command\": \"help\", \"args\": { \"target\": \"<namespace or command ID>\" } }) for input schemas and examples.`,\n ]\n if (note) lines.push(note)\n if (commands.length > 0 && commands.length <= DESCRIPTION_COMMAND_LIMIT) {\n lines.push(\"\", \"Commands:\")\n for (const e of commands) lines.push(`- ${e.id}: ${firstLine(e.command.description)}`)\n }\n return lines.join(\"\\n\")\n}\n\nfunction firstLine(text: string): string {\n return text.split(\"\\n\", 1)[0]!.trim()\n}\n\nfunction stringify(value: unknown): string {\n try {\n return JSON.stringify(value, null, 2) ?? \"null\"\n } catch {\n return String(value)\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"dispatch.js","sourceRoot":"","sources":["../../src/mcp/dispatch.ts"],"names":[],"mappings":"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","sourcesContent":["import { isDeclaredError, MextError } from \"../errors.ts\"\nimport { invoke, type InvokeOptions, type InvokeResult } from \"../invoke.ts\"\nimport type { JsonSchema, RegisteredCommand, Registry } from \"../registry.ts\"\nimport { describeCommand } from \"../manifest.ts\"\nimport { COMMAND_META_KEY } from \"./constants.ts\"\nimport { formatHelp, help, HELP_COMMAND } from \"./help.ts\"\nimport {\n createAskSession,\n hasPending,\n inputRequiredResult,\n type InputRequiredResult,\n type InputRound,\n type RequestStateBinding,\n type RequestStateSigner,\n} from \"./mrtr.ts\"\nimport type { CreateTaskResult } from \"./tasks.ts\"\n\n/**\n * MCP 어댑터의 transport 무관 부분: dispatcher 도구 정의 · tools/call 실행 · help · 오류 → 결과 변환.\n * SDK · HTTP 에 의존하지 않는다 (HTTP · stdio 가 공유).\n */\n\n/**\n * execute = 도구 1개 (기본), split = query (readOnly · help) + mutate (그 외),\n * command = 명령어 1개 = 도구 1개 (도구 이름 = 명령어 ID, outputSchema, help 없음, §12.2)\n */\nexport type ToolExposure = \"execute\" | \"split\" | \"command\"\n\nexport type DispatcherToolName = \"execute\" | \"query\" | \"mutate\"\n\n/** MCP ToolAnnotations 중 mext 가 쓰는 항목 */\nexport interface DispatcherAnnotations {\n title?: string\n readOnlyHint: boolean\n destructiveHint?: boolean\n idempotentHint?: boolean\n openWorldHint?: boolean\n}\n\n/** tools/list 도구 하나. dispatcher (execute · query · mutate) 또는 명령어별 도구 (command 노출) */\nexport interface DispatcherTool {\n /** dispatcher = DispatcherToolName, command 노출 = 명령어 ID */\n name: string\n description: string\n /** JSON Schema 2020-12. dispatcher = command 에 x-mcp-header: \"Command\", command 노출 = 명령어 input */\n inputSchema: JsonSchema\n annotations: DispatcherAnnotations\n /** readOnly 명령어만 실행 (split 의 query). 그 외 = PERMISSION_DENIED */\n readOnly: boolean\n /** command 노출: 이 도구가 실행하는 명령어 ID (인자 = 명령어 input 그대로). 없음 = dispatcher ({ command, args }) */\n command?: string\n /** command 노출: output 의 JSON Schema 2020-12 (output 이 있을 때) */\n outputSchema?: JsonSchema\n /** command 노출: tools/list 도구 _meta (COMMAND_META_KEY = CLI 원격의 help · 플래그 파싱용 메타데이터) */\n _meta?: Record<string, unknown>\n}\n\nexport interface TextContent {\n type: \"text\"\n text: string\n}\n\n/** MCP CallToolResult 와 같은 형태 (SDK 타입 비의존) */\nexport interface ToolCallResult {\n content: TextContent[]\n /** 성공 = 핸들러 반환값 (undefined 는 null), 오류 = { error: MextError.toJSON() } */\n structuredContent: unknown\n isError?: true\n}\n\n/** 핸들러 예외 (MextError 외) 의 결과 code. CLI --json 과 같음 */\nexport const EXECUTION_ERROR = \"EXECUTION_ERROR\"\n\n/** 도구 설명에 나열할 명령어 수 상한. 넘으면 help 안내만 */\nconst DESCRIPTION_COMMAND_LIMIT = 50\n\n/** 노출 방식별 도구 (정렬 순서 고정: execute · query, mutate · 명령어 ID 순) */\nexport function dispatcherTools(registry: Registry, exposure: ToolExposure = \"execute\"): DispatcherTool[] {\n const all = [...registry.commands.values()]\n if (exposure === \"command\") return all.map(commandTool)\n if (exposure === \"split\") {\n const reads = all.filter((e) => e.command.readOnly)\n const writes = all.filter((e) => !e.command.readOnly)\n return [\n {\n name: \"query\",\n description: describeTool(\n \"query\",\n \"Run a read-only command.\",\n reads,\n 'Commands that are not read-only are rejected here; call them with the \"mutate\" tool.',\n ),\n inputSchema: dispatcherInputSchema(),\n annotations: { title: \"Query\", readOnlyHint: true, destructiveHint: false, idempotentHint: true },\n readOnly: true,\n },\n {\n name: \"mutate\",\n description: describeTool(\"mutate\", \"Run a command that changes state.\", writes),\n inputSchema: dispatcherInputSchema(),\n annotations: { title: \"Mutate\", readOnlyHint: false, destructiveHint: true },\n readOnly: false,\n },\n ]\n }\n const readOnly = all.every((e) => e.command.readOnly)\n return [\n {\n name: \"execute\",\n description: describeTool(\"execute\", \"Run a command.\", all),\n inputSchema: dispatcherInputSchema(),\n // 단일 dispatcher 는 최악값 (researches/others.md 9)\n annotations: readOnly\n ? { title: \"Execute\", readOnlyHint: true, destructiveHint: false }\n : { title: \"Execute\", readOnlyHint: false, destructiveHint: all.some((e) => e.command.destructive) },\n readOnly: false,\n },\n ]\n}\n\n/**\n * command 노출의 명령어별 도구 (§12.2). 이름 = 정식 ID (별칭은 도구 아님), annotations = execute 와 같은 계산을 명령어 하나에.\n * _meta[COMMAND_META_KEY] = tools/list 만으로 CLI 원격의 help · 별칭 · positional 을 만들 메타데이터\n */\nfunction commandTool(entry: RegisteredCommand): DispatcherTool {\n const { id, description: _d, inputSchema: _i, outputSchema: _o, file: _f, ...meta } = describeCommand(entry)\n const c = entry.command\n const tool: DispatcherTool = {\n name: id,\n description: c.description,\n inputSchema: entry.inputSchema,\n annotations: c.readOnly\n ? { title: id, readOnlyHint: true, destructiveHint: false }\n : { title: id, readOnlyHint: false, destructiveHint: c.destructive },\n readOnly: false,\n command: id,\n _meta: { [COMMAND_META_KEY]: meta },\n }\n if (entry.outputSchema !== undefined) tool.outputSchema = entry.outputSchema\n return tool\n}\n\nexport function findDispatcherTool(tools: readonly DispatcherTool[], name: string): DispatcherTool | undefined {\n return tools.find((t) => t.name === name)\n}\n\nexport interface DispatchOptions extends Omit<InvokeOptions, \"readOnly\"> {\n /** help 의 tool 표시용. dispatcherTools 결과 */\n tools?: readonly DispatcherTool[]\n /**\n * MRTR (MCP 2026-07-28). 지정하면 ctx.ask = InputRequiredResult 반환 · 재시도의 inputResponses 로 resolve\n * (ask 옵션 무시). requiresConfirmation 확인도 이 경로\n */\n input?: InputRound & { signer: RequestStateSigner }\n}\n\n/** tools/call 결과: 완료 (CallToolResult 형태) · 입력 요청 (InputRequiredResult) · task 생성 (options.detach, §12.6) */\nexport type DispatchResult = ToolCallResult | InputRequiredResult | CreateTaskResult\n\n/**\n * dispatcher 도구 호출: { command, args } → help 또는 invoke.\n * MextError · 핸들러 예외는 isError 결과로 (throw 하지 않음). 인증 (UNAUTHENTICATED → 401) 은 transport 몫.\n * options.input 이 있으면 ctx.ask 가 InputRequiredResult 를 돌려준다 (mrtr.ts).\n */\nexport async function callDispatcher(\n registry: Registry,\n tool: DispatcherTool,\n rawArguments: unknown,\n options: DispatchOptions,\n): Promise<DispatchResult> {\n try {\n // command 노출: 인자 = 명령어 input 그대로 (검증 = invoke 의 Zod safeParse)\n const { command, args } =\n tool.command !== undefined ? { command: tool.command, args: rawArguments } : parseDispatcherArguments(tool, rawArguments)\n if (command === HELP_COMMAND) {\n const target = (args as { target?: unknown } | undefined)?.target\n if (target !== undefined && typeof target !== \"string\") {\n throw dispatcherArgumentError(tool, \"help\", \"args.target must be a string\", [], {\n path: \"args.target\",\n code: \"invalid_type\",\n message: \"expected string\",\n })\n }\n const toolFor = splitToolFor(options.tools)\n const value = help(registry, target, toolFor ? { toolFor } : {})\n return { content: [{ type: \"text\", text: formatHelp(value, tool.name) }], structuredContent: value }\n }\n const { input, tools: _tools, ...invokeOptions } = options\n const result = input\n ? await invokeRound(registry, tool, command, args, invokeOptions, input)\n : await invoke(registry, command, args, { ...invokeOptions, readOnly: tool.readOnly })\n if (\"resultType\" in result) return result\n if (result.detached !== undefined) return result.detached as CreateTaskResult\n return valueResult(result.value)\n } catch (error) {\n return errorResult(error)\n }\n}\n\n/** 핸들러 반환값 → 성공 결과 (undefined = null). task 완료 결과도 같은 변환 */\nexport function valueResult(value: unknown): ToolCallResult {\n const structured = value === undefined ? null : value\n return { content: [{ type: \"text\", text: stringify(structured) }], structuredContent: structured }\n}\n\n/**\n * MRTR 한 라운드: requestState 검증 → ask 세션으로 invoke. 답 없는 ask 가 있으면 (핸들러가 오류를 삼켜도)\n * 결과를 버리고 InputRequiredResult. elicitation 미선언 클라이언트 = ask 미주입 (INPUT_UNAVAILABLE).\n * 선언한 mode 만 요청 (form · url). 미선언 mode 의 ask = INPUT_UNAVAILABLE\n */\nasync function invokeRound(\n registry: Registry,\n tool: DispatcherTool,\n command: string,\n args: unknown,\n options: Omit<InvokeOptions, \"readOnly\">,\n input: NonNullable<DispatchOptions[\"input\"]>,\n): Promise<InvokeResult | InputRequiredResult> {\n const { ask: _ask, ...rest } = options\n const modes = { form: input.elicitation, url: input.urlElicitation === true }\n if (!modes.form && !modes.url) return invoke(registry, command, args, { ...rest, readOnly: tool.readOnly })\n const binding: RequestStateBinding = {\n principal: `${options.principal.kind}:${options.principal.id}`,\n tool: tool.name,\n command,\n args: args ?? {},\n }\n let answers: Record<string, unknown> = { ...input.inputResponses }\n if (input.requestState !== undefined) {\n const prior = input.signer.verify(input.requestState, binding)\n if (!prior) throw invalidRequestState(tool, command)\n answers = { ...answers, ...prior }\n }\n const session = createAskSession(answers, modes)\n const signal = options.signal ? AbortSignal.any([options.signal, session.signal]) : session.signal\n try {\n const result = await invoke(registry, command, args, { ...rest, signal, ask: session.ask, readOnly: tool.readOnly })\n return hasPending(session) ? inputRequiredResult(session, input.signer, binding) : result\n } catch (error) {\n if (hasPending(session)) return inputRequiredResult(session, input.signer, binding)\n throw error\n }\n}\n\n/** 서명 · 만료 · 바인딩 검증 실패한 requestState. 처음부터 다시 호출하도록 안내 */\nfunction invalidRequestState(tool: DispatcherTool, command: string): MextError<\"INVALID_ARGUMENT\"> {\n return new MextError(\n \"INVALID_ARGUMENT\",\n `Invalid arguments for ${command}: requestState is invalid or expired. Call ${tool.name} again without requestState and inputResponses.`,\n {\n command,\n missing_fields: [],\n issues: [\n {\n path: \"requestState\",\n code: \"invalid_request_state\",\n message: \"requestState failed verification (tampered, expired, or for a different call or server key)\",\n },\n ],\n expected_schema: tool.inputSchema,\n examples: [],\n },\n )\n}\n\n/** 오류 → isError 결과. MextError · CommandError = toJSON, 그 외 = EXECUTION_ERROR (message 만) */\nexport function errorResult(error: unknown): ToolCallResult {\n const body: Record<string, unknown> & { code: string; message: string } = isDeclaredError(error)\n ? error.toJSON()\n : { code: EXECUTION_ERROR, message: error instanceof Error ? error.message : String(error) }\n return {\n content: [{ type: \"text\", text: `${body.code}: ${body.message}\\n${stringify(body)}` }],\n structuredContent: { error: body },\n isError: true,\n }\n}\n\n/** dispatcher 도구 inputSchema. command 를 Mcp-Param-Command 헤더로 복사 (SEP-2243) */\nexport function dispatcherInputSchema(): JsonSchema {\n return {\n type: \"object\",\n properties: {\n command: {\n type: \"string\",\n description: 'Command ID (e.g. \"users.get\"), or \"help\" to list namespaces and commands.',\n \"x-mcp-header\": \"Command\",\n },\n args: {\n type: \"object\",\n description: 'Command input. For \"help\": { \"target\": \"<namespace or command ID>\" }.',\n additionalProperties: true,\n },\n },\n required: [\"command\"],\n additionalProperties: false,\n }\n}\n\nfunction parseDispatcherArguments(tool: DispatcherTool, raw: unknown): { command: string; args: unknown } {\n const input = raw === undefined ? {} : raw\n if (input === null || typeof input !== \"object\" || Array.isArray(input)) {\n throw dispatcherArgumentError(tool, tool.name, \"arguments must be an object\", [], {\n path: \"\",\n code: \"invalid_type\",\n message: \"expected object\",\n })\n }\n const { command, args, ...rest } = input as Record<string, unknown>\n if (command === undefined) {\n throw dispatcherArgumentError(tool, tool.name, \"missing required: command\", [\"command\"], {\n path: \"command\",\n code: \"invalid_type\",\n message: \"expected string, received undefined\",\n })\n }\n if (typeof command !== \"string\") {\n throw dispatcherArgumentError(tool, tool.name, \"command must be a string\", [], {\n path: \"command\",\n code: \"invalid_type\",\n message: \"expected string\",\n })\n }\n const unknownKeys = Object.keys(rest)\n if (unknownKeys.length > 0) {\n throw dispatcherArgumentError(tool, tool.name, `unrecognized keys: ${unknownKeys.join(\", \")}`, [], {\n path: \"\",\n code: \"unrecognized_keys\",\n message: `Unrecognized keys: ${unknownKeys.join(\", \")}. Put command input under \"args\".`,\n })\n }\n if (args !== undefined && (args === null || typeof args !== \"object\" || Array.isArray(args))) {\n throw dispatcherArgumentError(tool, command, \"args must be an object\", [], {\n path: \"args\",\n code: \"invalid_type\",\n message: \"expected object\",\n })\n }\n return { command, args }\n}\n\nfunction dispatcherArgumentError(\n tool: DispatcherTool,\n command: string,\n summary: string,\n missing: string[],\n issue: { path: string; code: string; message: string },\n): MextError<\"INVALID_ARGUMENT\"> {\n return new MextError(\"INVALID_ARGUMENT\", `Invalid arguments for ${command}: ${summary}`, {\n command,\n missing_fields: missing,\n issues: [issue],\n expected_schema: tool.inputSchema,\n examples: [{ command: HELP_COMMAND }],\n })\n}\n\nfunction splitToolFor(tools: readonly DispatcherTool[] | undefined): ((e: RegisteredCommand) => string) | undefined {\n if (!tools || tools.length < 2) return undefined\n const query = tools.find((t) => t.readOnly)?.name\n const mutate = tools.find((t) => !t.readOnly)?.name\n if (query === undefined || mutate === undefined) return undefined\n return (e) => (e.command.readOnly ? query : mutate)\n}\n\nfunction describeTool(name: string, lead: string, commands: readonly RegisteredCommand[], note?: string): string {\n const lines = [\n lead,\n `Arguments: { \"command\": \"<command ID>\", \"args\": { ...input } }. ` +\n `Call ${name}({ \"command\": \"help\" }) to list namespaces and commands, ` +\n `${name}({ \"command\": \"help\", \"args\": { \"target\": \"<namespace or command ID>\" } }) for input schemas and examples.`,\n ]\n if (note) lines.push(note)\n if (commands.length > 0 && commands.length <= DESCRIPTION_COMMAND_LIMIT) {\n lines.push(\"\", \"Commands:\")\n for (const e of commands) lines.push(`- ${e.id}: ${firstLine(e.command.description)}`)\n }\n return lines.join(\"\\n\")\n}\n\nfunction firstLine(text: string): string {\n return text.split(\"\\n\", 1)[0]!.trim()\n}\n\nfunction stringify(value: unknown): string {\n try {\n return JSON.stringify(value, null, 2) ?? \"null\"\n } catch {\n return String(value)\n }\n}\n"]}
|
package/dist/mcp/http.d.ts
CHANGED
|
@@ -2,9 +2,11 @@ import { type IncomingMessage, type ServerResponse } from "node:http";
|
|
|
2
2
|
import type { MextApp } from "../command.ts";
|
|
3
3
|
import { type Principal } from "../principal.ts";
|
|
4
4
|
import type { Registry } from "../registry.ts";
|
|
5
|
+
import { type RouteEntry } from "../route.ts";
|
|
5
6
|
import type { Instrumentation } from "../instrumentation.ts";
|
|
6
7
|
import type { ToolExposure } from "./dispatch.ts";
|
|
7
8
|
import { type McpCoreOptions } from "./server.ts";
|
|
9
|
+
import type { MextState } from "../state.ts";
|
|
8
10
|
/** 요청 본문 상한 (SDK 기본과 같음) */
|
|
9
11
|
export declare const MAX_BODY_BYTES: number;
|
|
10
12
|
export interface McpAuthRequest {
|
|
@@ -53,6 +55,18 @@ export interface McpHttpOptions {
|
|
|
53
55
|
* 클라이언트 (mext login · MCP 클라이언트) 가 인가 서버를 찾는 데 쓴다
|
|
54
56
|
*/
|
|
55
57
|
oauth?: OAuthResourceOptions;
|
|
58
|
+
/** routes/ (§12.4). MCP 엔드포인트 밖 경로. Origin 검사 · auth 없음. ctx.app = MCP 와 같은 인스턴스 */
|
|
59
|
+
routes?: readonly RouteEntry[];
|
|
60
|
+
/** 주입 state (§12.1). 자원 구독 · ctx.resourceUpdated (§12.5) */
|
|
61
|
+
state?: MextState;
|
|
62
|
+
/** McpCoreOptions.taskTiming (테스트) */
|
|
63
|
+
taskTiming?: McpCoreOptions["taskTiming"];
|
|
64
|
+
/** McpCoreOptions.taskExecution. handler.js = "waitUntil" */
|
|
65
|
+
taskExecution?: McpCoreOptions["taskExecution"];
|
|
66
|
+
}
|
|
67
|
+
/** fetch 의 두 번째 인자. waitUntil = 응답 후 실행 유지 (handler.js 플랫폼, tasks §10) */
|
|
68
|
+
export interface McpFetchContext {
|
|
69
|
+
waitUntil?: (promise: Promise<unknown>) => void;
|
|
56
70
|
}
|
|
57
71
|
export interface OAuthResourceOptions {
|
|
58
72
|
/** 인가 서버 issuer URL (1개 이상) */
|
|
@@ -64,19 +78,23 @@ export interface OAuthResourceOptions {
|
|
|
64
78
|
/** RFC 9728 well-known 경로 */
|
|
65
79
|
export declare const PROTECTED_RESOURCE_METADATA_PATH = "/.well-known/oauth-protected-resource";
|
|
66
80
|
export type McpHttpListener = ((req: IncomingMessage, res: ServerResponse) => void) & {
|
|
67
|
-
/** 진행 중인 요청 abort · SDK handler 정리 */
|
|
81
|
+
/** subscriptions/listen 스트림을 완료 result 후 닫고 → 진행 중인 요청 abort · SDK handler 정리 */
|
|
68
82
|
close(): Promise<void>;
|
|
83
|
+
/** subscriptions/listen 스트림만 완료 result 후 닫는다 (mext dev 자식 교체). 이후 listen 요청은 계속 받는다 */
|
|
84
|
+
closeSubscriptions(): Promise<void>;
|
|
69
85
|
};
|
|
70
86
|
/** Web 표준 (Request → Response) MCP 처리기. handler.js · 다른 런타임 · node:http 어댑터가 공유 */
|
|
71
87
|
export interface McpFetchHandler {
|
|
72
|
-
/** 거부하지 않는다 (내부 오류 = 500 JSON-RPC 오류). request.signal abort = 핸들러 ctx.signal abort */
|
|
73
|
-
fetch(request: Request): Promise<Response>;
|
|
74
|
-
/** SDK handler 정리 */
|
|
88
|
+
/** 거부하지 않는다 (내부 오류 = 500 JSON-RPC 오류). request.signal abort = 핸들러 ctx.signal abort (task 제외) */
|
|
89
|
+
fetch(request: Request, context?: McpFetchContext): Promise<Response>;
|
|
90
|
+
/** SDK handler 정리 (subscriptions/listen 스트림 = 완료 result 후 닫음) · 실행 중 task 취소 (상한 5초) */
|
|
75
91
|
close(): Promise<void>;
|
|
92
|
+
/** 열린 subscriptions/listen 스트림을 완료 result 후 닫는다. 소비자가 끝까지 읽으면 resolve (상한 1초) */
|
|
93
|
+
closeSubscriptions(): Promise<void>;
|
|
76
94
|
}
|
|
77
95
|
/**
|
|
78
96
|
* Streamable HTTP (2026-07-28) 의 transport 무관 처리 (Web Request → Response).
|
|
79
|
-
* 순서: 경로 (404) → Origin (403) → 메서드 (POST 외 405) → 본문 (413) → auth (401) → SDK createMcpHandler.
|
|
97
|
+
* 순서: 경로 (routes · 404) → Origin (403) → 메서드 (POST 외 405) → 본문 (413) → auth (401) → SDK createMcpHandler.
|
|
80
98
|
* SDK 가 요청별 버전 · 헤더 검증 (-32020) · server/discover · Mcp-Param-Command · 2025 레거시 stateless 를 처리.
|
|
81
99
|
* progressToken 있는 2026-07-28 요청 = SSE 응답 (진행률 알림), 그 외 = JSON.
|
|
82
100
|
*/
|
package/dist/mcp/http.js
CHANGED
|
@@ -1,24 +1,33 @@
|
|
|
1
1
|
import { createServer } from "node:http";
|
|
2
2
|
import { Readable } from "node:stream";
|
|
3
|
-
import { createMcpHandler, isLegacyRequest } from "@modelcontextprotocol/server";
|
|
3
|
+
import { createMcpHandler, InMemoryServerEventBus, isLegacyRequest } from "@modelcontextprotocol/server";
|
|
4
4
|
import { isMextError } from "../errors.js";
|
|
5
5
|
import { ANONYMOUS } from "../principal.js";
|
|
6
|
+
import { createRouteDispatcher, onceApp } from "../route.js";
|
|
6
7
|
import { DEFAULT_HOST, DEFAULT_PORT, MCP_PATH } from "./constants.js";
|
|
7
8
|
import { createMcpServerFactory, principalAuthInfo } from "./server.js";
|
|
9
|
+
import { bridgeResourceUpdates, narrowListenRequest, resourceUpdatedFn, trackBody } from "./subscriptions.js";
|
|
10
|
+
import { hasTaskCommands, requestsTask } from "./tasks.js";
|
|
8
11
|
/** 요청 본문 상한 (SDK 기본과 같음) */
|
|
9
12
|
export const MAX_BODY_BYTES = 4 * 1024 * 1024;
|
|
10
13
|
/** RFC 9728 well-known 경로 */
|
|
11
14
|
export const PROTECTED_RESOURCE_METADATA_PATH = "/.well-known/oauth-protected-resource";
|
|
12
15
|
/**
|
|
13
16
|
* Streamable HTTP (2026-07-28) 의 transport 무관 처리 (Web Request → Response).
|
|
14
|
-
* 순서: 경로 (404) → Origin (403) → 메서드 (POST 외 405) → 본문 (413) → auth (401) → SDK createMcpHandler.
|
|
17
|
+
* 순서: 경로 (routes · 404) → Origin (403) → 메서드 (POST 외 405) → 본문 (413) → auth (401) → SDK createMcpHandler.
|
|
15
18
|
* SDK 가 요청별 버전 · 헤더 검증 (-32020) · server/discover · Mcp-Param-Command · 2025 레거시 stateless 를 처리.
|
|
16
19
|
* progressToken 있는 2026-07-28 요청 = SSE 응답 (진행률 알림), 그 외 = JSON.
|
|
17
20
|
*/
|
|
18
21
|
export function createMcpFetchHandler(options) {
|
|
22
|
+
// ctx.app = 프로세스당 1회. 명령어 · route 가 공유
|
|
23
|
+
const app = options.app ? onceApp(options.app) : undefined;
|
|
24
|
+
const resourceUpdated = resourceUpdatedFn(options.state);
|
|
25
|
+
const routes = options.routes?.length
|
|
26
|
+
? createRouteDispatcher({ routes: options.routes, app: onceApp(app), resourceUpdated })
|
|
27
|
+
: undefined;
|
|
19
28
|
const factory = createMcpServerFactory({
|
|
20
29
|
registry: options.registry,
|
|
21
|
-
...(
|
|
30
|
+
...(app ? { app } : {}),
|
|
22
31
|
...(options.tools ? { tools: options.tools } : {}),
|
|
23
32
|
...(options.name ? { name: options.name } : {}),
|
|
24
33
|
...(options.instructions !== undefined ? { instructions: options.instructions } : {}),
|
|
@@ -27,15 +36,26 @@ export function createMcpFetchHandler(options) {
|
|
|
27
36
|
...(options.progressInterval !== undefined ? { progressInterval: options.progressInterval } : {}),
|
|
28
37
|
...(options.instrumentation ? { instrumentation: options.instrumentation } : {}),
|
|
29
38
|
...(options.manifestHash !== undefined ? { manifestHash: options.manifestHash } : {}),
|
|
39
|
+
...(options.state ? { state: options.state } : {}),
|
|
40
|
+
...(options.taskTiming ? { taskTiming: options.taskTiming } : {}),
|
|
41
|
+
...(options.taskExecution ? { taskExecution: options.taskExecution } : {}),
|
|
30
42
|
transport: "http",
|
|
31
43
|
});
|
|
44
|
+
const tasks = options.state?.store !== undefined && hasTaskCommands(options.registry);
|
|
32
45
|
const report = reporter(options.onerror);
|
|
33
46
|
const sdkOptions = options.onerror ? { onerror: report } : {};
|
|
47
|
+
// subscriptions/listen (§12.5) = 전용 SDK handler (ack · 15초 keepalive · 완료 result). 갱신 = state 허브 → SDK bus
|
|
48
|
+
const listenBus = new InMemoryServerEventBus(report);
|
|
49
|
+
const subscriptions = { state: options.state, resources: options.registry.resources };
|
|
50
|
+
const unbridge = bridgeResourceUpdates(listenBus, { ...subscriptions, onerror: report });
|
|
51
|
+
const createListenHandler = () => createMcpHandler(factory, { ...sdkOptions, bus: listenBus });
|
|
52
|
+
let listenHandler = createListenHandler();
|
|
53
|
+
const listenBodies = new Set();
|
|
34
54
|
// 진행률 없는 요청 = JSON (auto: 관련 메시지가 없으면 단일 JSON), 진행률 있는 요청 = SSE
|
|
35
55
|
const jsonHandler = createMcpHandler(factory, { ...sdkOptions, responseMode: "auto" });
|
|
36
56
|
const sseHandler = createMcpHandler(factory, { ...sdkOptions, responseMode: "sse" });
|
|
37
57
|
const allowedOrigins = options.server?.allowedOrigins;
|
|
38
|
-
const handle = async (request) => {
|
|
58
|
+
const handle = async (request, context) => {
|
|
39
59
|
const url = new URL(request.url);
|
|
40
60
|
const oauth = options.oauth;
|
|
41
61
|
if (oauth && (url.pathname === PROTECTED_RESOURCE_METADATA_PATH || url.pathname === protectedResourceMetadataPath())) {
|
|
@@ -43,6 +63,11 @@ export function createMcpFetchHandler(options) {
|
|
|
43
63
|
return jsonResponse(405, { error: "method_not_allowed" }, { Allow: "GET" });
|
|
44
64
|
return jsonResponse(200, protectedResourceMetadata(oauth, url));
|
|
45
65
|
}
|
|
66
|
+
if (routes && url.pathname !== MCP_PATH) {
|
|
67
|
+
const routed = await routes(request, url);
|
|
68
|
+
if (routed)
|
|
69
|
+
return routed;
|
|
70
|
+
}
|
|
46
71
|
if (url.pathname !== MCP_PATH)
|
|
47
72
|
return jsonResponse(404, rpcError(-32_000, `Not found. The MCP endpoint is POST ${MCP_PATH}.`));
|
|
48
73
|
const origin = request.headers.get("origin") ?? undefined;
|
|
@@ -79,27 +104,53 @@ export function createMcpFetchHandler(options) {
|
|
|
79
104
|
const parsedBody = parsed === NOT_JSON ? undefined : parsed;
|
|
80
105
|
// 2025-11-25 이하 (envelope 없음) = SDK stateless 레거시 경로. 진행률 기반 SSE 전환은 2026-07-28 요청만
|
|
81
106
|
const legacy = await isLegacyRequest(sdkRequest, parsedBody, { maxRequestBodySize: MAX_BODY_BYTES });
|
|
82
|
-
const
|
|
107
|
+
const listen = legacy ? undefined : narrowListenRequest(parsedBody, subscriptions, principal);
|
|
108
|
+
if (listen !== undefined) {
|
|
109
|
+
const { response, done } = trackBody(await listenHandler.fetch(sdkRequest, { authInfo: principalAuthInfo(principal), parsedBody: listen }));
|
|
110
|
+
listenBodies.add(done);
|
|
111
|
+
void done.then(() => listenBodies.delete(done));
|
|
112
|
+
return response;
|
|
113
|
+
}
|
|
114
|
+
// task 가 될 수 있는 tools/call = application/json (CreateTaskResult, §12.6)
|
|
115
|
+
const sse = !legacy && hasProgressToken(parsedBody) && !(tasks && requestsTask(options.registry, parsedBody));
|
|
116
|
+
const handler = sse ? sseHandler : jsonHandler;
|
|
83
117
|
return handler.fetch(sdkRequest, {
|
|
84
|
-
authInfo: principalAuthInfo(principal),
|
|
118
|
+
authInfo: principalAuthInfo(principal, context?.waitUntil),
|
|
85
119
|
...(parsedBody !== undefined ? { parsedBody } : {}),
|
|
86
120
|
});
|
|
87
121
|
};
|
|
88
122
|
return {
|
|
89
|
-
async fetch(request) {
|
|
123
|
+
async fetch(request, context) {
|
|
90
124
|
try {
|
|
91
|
-
return await handle(request);
|
|
125
|
+
return await handle(request, context);
|
|
92
126
|
}
|
|
93
127
|
catch (error) {
|
|
94
128
|
report(error);
|
|
95
129
|
return jsonResponse(500, rpcError(-32_603, "Internal server error"));
|
|
96
130
|
}
|
|
97
131
|
},
|
|
132
|
+
closeSubscriptions,
|
|
98
133
|
async close() {
|
|
99
|
-
|
|
134
|
+
unbridge();
|
|
135
|
+
await closeSubscriptions();
|
|
136
|
+
await Promise.all([listenHandler.close(), jsonHandler.close(), sseHandler.close(), factory.close()]);
|
|
100
137
|
},
|
|
101
138
|
};
|
|
139
|
+
async function closeSubscriptions() {
|
|
140
|
+
// SDK handler.close = 열린 listen 스트림마다 완료 result 후 닫음. 이후 listen 은 새 handler 가 받는다
|
|
141
|
+
const closing = listenHandler;
|
|
142
|
+
listenHandler = createListenHandler();
|
|
143
|
+
await closing.close();
|
|
144
|
+
const pending = [...listenBodies];
|
|
145
|
+
if (pending.length === 0)
|
|
146
|
+
return;
|
|
147
|
+
let timer;
|
|
148
|
+
await Promise.race([Promise.all(pending), new Promise((resolve) => (timer = setTimeout(resolve, LISTEN_DRAIN_MS)))]);
|
|
149
|
+
clearTimeout(timer);
|
|
150
|
+
}
|
|
102
151
|
}
|
|
152
|
+
/** 서버 종료 시 listen 스트림의 완료 result 를 클라이언트가 읽기까지 기다리는 상한 (ms) */
|
|
153
|
+
const LISTEN_DRAIN_MS = 1_000;
|
|
103
154
|
/** createMcpFetchHandler 의 node:http 어댑터. 응답 · SSE 를 스트리밍, 클라이언트 연결 종료 = request.signal abort */
|
|
104
155
|
export function createMcpHttpHandler(options) {
|
|
105
156
|
const core = createMcpFetchHandler(options);
|
|
@@ -131,7 +182,11 @@ export function createMcpHttpHandler(options) {
|
|
|
131
182
|
res.destroy();
|
|
132
183
|
});
|
|
133
184
|
});
|
|
185
|
+
listener.closeSubscriptions = () => core.closeSubscriptions();
|
|
134
186
|
listener.close = async () => {
|
|
187
|
+
await core.closeSubscriptions();
|
|
188
|
+
// 완료 result 를 쓴 응답의 end (sendResponse) 가 연결 정리 전에 실행되도록
|
|
189
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
135
190
|
for (const c of inflight)
|
|
136
191
|
c.abort();
|
|
137
192
|
inflight.clear();
|