isthmus-cli 0.6.0 → 0.7.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/README.ko.md +135 -31
- package/README.md +134 -16
- package/Skills/isthmus/SKILL.md +17 -7
- package/compatibility.json +10 -0
- package/dist/cli/check-command.d.ts +2 -2
- package/dist/cli/check-command.js +22 -9
- package/dist/cli/check-command.js.map +1 -1
- package/dist/cli/command-support.d.ts +19 -1
- package/dist/cli/command-support.js +37 -8
- package/dist/cli/command-support.js.map +1 -1
- package/dist/cli/diff-command.js +4 -3
- package/dist/cli/diff-command.js.map +1 -1
- package/dist/cli/doctor-command.d.ts +11 -0
- package/dist/cli/doctor-command.js +128 -0
- package/dist/cli/doctor-command.js.map +1 -0
- package/dist/cli/extract-js-command.d.ts +33 -0
- package/dist/cli/extract-js-command.js +233 -0
- package/dist/cli/extract-js-command.js.map +1 -0
- package/dist/cli/graph-command.js +13 -6
- package/dist/cli/graph-command.js.map +1 -1
- package/dist/cli/init-command.d.ts +11 -0
- package/dist/cli/init-command.js +115 -0
- package/dist/cli/init-command.js.map +1 -0
- package/dist/cli/main.js +52 -1
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/mcp-server.d.ts +16 -0
- package/dist/cli/mcp-server.js +534 -0
- package/dist/cli/mcp-server.js.map +1 -0
- package/dist/cli/query-command.js +13 -6
- package/dist/cli/query-command.js.map +1 -1
- package/dist/cli/retentions-command.js +14 -7
- package/dist/cli/retentions-command.js.map +1 -1
- package/dist/cli/serve-command.d.ts +13 -0
- package/dist/cli/serve-command.js +43 -0
- package/dist/cli/serve-command.js.map +1 -0
- package/dist/exchange/capture-config.d.ts +18 -0
- package/dist/exchange/capture-config.js +100 -0
- package/dist/exchange/capture-config.js.map +1 -0
- package/dist/exchange/messages.d.ts +9 -24
- package/dist/exchange/messages.js +25 -55
- package/dist/exchange/messages.js.map +1 -1
- package/dist/exchange/parse.d.ts +61 -0
- package/dist/exchange/parse.js +115 -12
- package/dist/exchange/parse.js.map +1 -1
- package/dist/extract/js-document.d.ts +22 -0
- package/dist/extract/js-document.js +230 -0
- package/dist/extract/js-document.js.map +1 -0
- package/dist/extract/js-scan.d.ts +93 -0
- package/dist/extract/js-scan.js +1205 -0
- package/dist/extract/js-scan.js.map +1 -0
- package/dist/extract/lexer.d.ts +26 -0
- package/dist/extract/lexer.js +316 -0
- package/dist/extract/lexer.js.map +1 -0
- package/dist/join/join.d.ts +66 -1
- package/dist/join/join.js +171 -1
- package/dist/join/join.js.map +1 -1
- package/dist/join/messages.d.ts +7 -4
- package/dist/join/messages.js +15 -8
- package/dist/join/messages.js.map +1 -1
- package/dist/report/check-report.d.ts +20 -3
- package/dist/report/check-report.js +240 -8
- package/dist/report/check-report.js.map +1 -1
- package/dist/report/codequality.d.ts +45 -0
- package/dist/report/codequality.js +74 -0
- package/dist/report/codequality.js.map +1 -0
- package/dist/report/diff.d.ts +25 -3
- package/dist/report/diff.js +91 -11
- package/dist/report/diff.js.map +1 -1
- package/dist/report/graph.d.ts +10 -4
- package/dist/report/graph.js +60 -6
- package/dist/report/graph.js.map +1 -1
- package/dist/report/preflight-view.js +28 -1
- package/dist/report/preflight-view.js.map +1 -1
- package/dist/report/preflight.d.ts +4 -4
- package/dist/report/preflight.js +81 -46
- package/dist/report/preflight.js.map +1 -1
- package/dist/report/query.d.ts +11 -4
- package/dist/report/query.js +84 -17
- package/dist/report/query.js.map +1 -1
- package/dist/report/retentions.d.ts +17 -5
- package/dist/report/retentions.js +85 -8
- package/dist/report/retentions.js.map +1 -1
- package/dist/report/rules.d.ts +16 -0
- package/dist/report/rules.js +30 -0
- package/dist/report/rules.js.map +1 -0
- package/dist/report/sarif.d.ts +2 -8
- package/dist/report/sarif.js +2 -10
- package/dist/report/sarif.js.map +1 -1
- package/docs/BRIDGE-EVENTS.md +87 -0
- package/docs/BRIDGE-MESSAGES.md +23 -8
- package/docs/GRAPH-EXCHANGE.md +141 -14
- package/docs/IMPACT.md +1 -1
- package/docs/MCP.md +62 -0
- package/docs/PREFLIGHT.md +24 -8
- package/docs/RUNTIME.md +2 -2
- package/docs/TOOLCHAIN.md +5 -5
- package/package.json +5 -2
- package/scripts/capture-preflight.mjs +32 -44
- package/scripts/run-child.mjs +1 -0
package/dist/cli/main.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"main.js","sourceRoot":"","sources":["../../src/cli/main.ts"],"names":[],"mappings":"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","sourcesContent":["#!/usr/bin/env node\n\nimport { readFile } from 'node:fs/promises';\n\nimport { writeTextAtomically } from './atomic-write.ts';\nimport { checkUsage, runCheckCommand } from './check-command.ts';\nimport type { CommandResult } from './command-support.ts';\nimport { graphUsage, runGraphCommand } from './graph-command.ts';\nimport { diffUsage, runDiffCommand } from './diff-command.ts';\nimport { queryUsage, runQueryCommand } from './query-command.ts';\nimport { impactUsage, runImpactCommand } from './impact-command.ts';\nimport { runtimeUsage, runRuntimeCommand } from './runtime-command.ts';\nimport { preflightUsage, runPreflightCommand } from './preflight-command.ts';\nimport {\n retentionUsage,\n runRetentionsCommand,\n} from './retentions-command.ts';\n\nprocess.stdout.on('error', handleStreamError);\nprocess.stderr.on('error', handleStreamError);\n\nconst commandUsages = new Map([\n ['check', checkUsage],\n ['graph', graphUsage],\n ['diff', diffUsage],\n ['query', queryUsage],\n ['impact', impactUsage],\n ['preflight', preflightUsage],\n ['verify-runtime', runtimeUsage],\n ['retentions', retentionUsage],\n]);\n\nconst rootHelp = `Usage: isthmus <command> [options]\n\nCommands:\n check Report unmatched bridge calls and handlers\n query Find both sides of a channel or method\n impact Inspect bridge dependencies before changing files or symbols\n preflight Trace cross-language impact from producer analysis context\n verify-runtime Verify recorded calls against scenario expectations\n graph Render matched boundary edges\n diff Compare bridge observations before and after a change\n retentions Produce external retention evidence\n help Show command help\n\nOptions:\n -h, --help Show help; after a command it shows that command's usage\n --version Show version\n`;\n\nconst arguments_ = process.argv.slice(2);\nconst readTextFile = (path: string) => readFile(path, 'utf8');\nconst writeTextFile = (path: string, text: string) =>\n writeTextAtomically(path, text);\nconst informationalResult = await runInformationalCommand(arguments_);\nconst result = informationalResult ?? await dispatchCommand(arguments_);\n\nprocess.stdout.write(result.standardOutput);\nprocess.stderr.write(result.standardError);\nprocess.exitCode = result.exitCode;\n\n/** 파이프 소비자가 먼저 닫힌 정상 상황만 조용히 종료한다. */\nfunction handleStreamError(error: NodeJS.ErrnoException): void {\n if (error.code === 'EPIPE') {\n process.exitCode = 0;\n return;\n }\n throw error;\n}\n\n/** 도움말과 버전처럼 입력 문서가 필요 없는 명령을 실행한다. */\nasync function runInformationalCommand(\n commandArguments: readonly string[],\n): Promise<CommandResult | undefined> {\n // 도움말 플래그는 어느 위치에 있든 이긴다. 긴 호출 끝에 붙여도 사용법을 본다.\n const helpFlagIndex = commandArguments.findIndex(\n (argument) => argument === '--help' || argument === '-h',\n );\n if (helpFlagIndex >= 0) return commandHelp(commandArguments[0]);\n if (commandArguments[0] === 'help') {\n return commandHelp(commandArguments[1]);\n }\n if (\n commandArguments.length === 1 &&\n commandArguments[0] === '--version'\n ) {\n return readVersion();\n }\n return undefined;\n}\n\n/** 명령 이름에 맞는 사용법을, 모르면 루트 도움말을 돌려준다. */\nfunction commandHelp(command: string | undefined): CommandResult {\n if (command === undefined) return successfulText(rootHelp);\n const usage = commandUsages.get(command);\n return usage === undefined\n ? successfulText(rootHelp)\n : successfulText(`${usage}\\n`);\n}\n\n/** 알려진 분석 하위 명령으로만 라우팅한다. */\nasync function dispatchCommand(\n commandArguments: readonly string[],\n): Promise<CommandResult> {\n switch (commandArguments[0]) {\n case 'check':\n // SARIF driver에만 필요하므로 메타데이터 부재는 json 보고서를 막지 않는다.\n return runCheckCommand(\n commandArguments,\n readTextFile,\n writeTextFile,\n undefined,\n await readPackageVersion(),\n );\n case 'graph':\n return runGraphCommand(commandArguments, readTextFile);\n case 'diff':\n return runDiffCommand(commandArguments, readTextFile);\n case 'query':\n return runQueryCommand(commandArguments, readTextFile);\n case 'impact':\n return runImpactCommand(commandArguments, readTextFile);\n case 'preflight':\n return runPreflightCommand(commandArguments, readTextFile);\n case 'verify-runtime':\n return runRuntimeCommand(commandArguments, readTextFile);\n case 'retentions': {\n const version = await readPackageVersion();\n return version === undefined\n ? packageMetadataError()\n : runRetentionsCommand(\n commandArguments,\n readTextFile,\n () => new Date(),\n version,\n );\n }\n default:\n return {\n standardOutput: '',\n standardError: rootHelp,\n exitCode: 64,\n };\n }\n}\n\n/** 설치된 package.json을 단일 출처로 사용해 버전을 출력한다. */\nasync function readVersion(): Promise<CommandResult> {\n const version = await readPackageVersion();\n return version === undefined\n ? packageMetadataError()\n : successfulText(`${version}\\n`);\n}\n\n/** 설치된 package.json에서 검증된 버전 문자열을 읽는다. */\nasync function readPackageVersion(): Promise<string | undefined> {\n try {\n const text = await readFile(new URL('../../package.json', import.meta.url), 'utf8');\n const document: unknown = JSON.parse(text);\n return isVersionDocument(document) ? document.version : undefined;\n } catch {\n return undefined;\n }\n}\n\n/** package metadata가 비어 있지 않은 버전을 제공하는지 확인한다. */\nfunction isVersionDocument(value: unknown): value is { readonly version: string } {\n return (\n typeof value === 'object' &&\n value !== null &&\n 'version' in value &&\n typeof value.version === 'string' &&\n value.version.length > 0\n );\n}\n\n/** stdout 텍스트와 성공 종료 코드를 만든다. */\nfunction successfulText(standardOutput: string): CommandResult {\n return { standardOutput, standardError: '', exitCode: 0 };\n}\n\n/** package metadata 실패를 민감정보 없는 코드 2로 바꾼다. */\nfunction packageMetadataError(): CommandResult {\n return {\n standardOutput: '',\n standardError: 'Unable to read package metadata.\\n',\n exitCode: 2,\n };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"main.js","sourceRoot":"","sources":["../../src/cli/main.ts"],"names":[],"mappings":"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","sourcesContent":["#!/usr/bin/env node\n\nimport { readdir, readFile, realpath, stat } from 'node:fs/promises';\n\nimport { writeTextAtomically } from './atomic-write.ts';\nimport { checkUsage, runCheckCommand } from './check-command.ts';\nimport type { CaptureFileSystem, CommandResult } from './command-support.ts';\nimport { doctorUsage, runDoctorCommand } from './doctor-command.ts';\nimport { initUsage, runInitCommand } from './init-command.ts';\nimport {\n extractJsUsage,\n type ExtractJsFileSystem,\n runExtractJsCommand,\n} from './extract-js-command.ts';\nimport { graphUsage, runGraphCommand } from './graph-command.ts';\nimport { diffUsage, runDiffCommand } from './diff-command.ts';\nimport { queryUsage, runQueryCommand } from './query-command.ts';\nimport { impactUsage, runImpactCommand } from './impact-command.ts';\nimport { runtimeUsage, runRuntimeCommand } from './runtime-command.ts';\nimport { preflightUsage, runPreflightCommand } from './preflight-command.ts';\nimport {\n retentionUsage,\n runRetentionsCommand,\n} from './retentions-command.ts';\nimport { runServeCommand, serveUsage } from './serve-command.ts';\n\nprocess.stdout.on('error', handleStreamError);\nprocess.stderr.on('error', handleStreamError);\n\nconst commandUsages = new Map([\n ['check', checkUsage],\n ['graph', graphUsage],\n ['diff', diffUsage],\n ['query', queryUsage],\n ['impact', impactUsage],\n ['preflight', preflightUsage],\n ['verify-runtime', runtimeUsage],\n ['retentions', retentionUsage],\n ['serve', serveUsage],\n ['extract-js', extractJsUsage],\n ['doctor', doctorUsage],\n ['init', initUsage],\n]);\n\nconst rootHelp = `Usage: isthmus <command> [options]\n\nCommands:\n check Report unmatched bridge calls and handlers\n query Find both sides of a channel or method\n impact Inspect bridge dependencies before changing files or symbols\n preflight Trace cross-language impact from producer analysis context\n verify-runtime Verify recorded calls against scenario expectations\n graph Render matched boundary edges\n diff Compare bridge observations before and after a change\n retentions Produce external retention evidence\n extract-js Extract React Native caller-side bridge facts from JS/TS\n doctor Validate a capture config and its executable paths\n init Write a capture config scaffold\n serve Speak MCP over stdio for agent clients\n help Show command help\n\nOptions:\n -h, --help Show help; after a command it shows that command's usage\n --version Show version\n`;\n\nconst arguments_ = process.argv.slice(2);\nconst readTextFile = (path: string) => readFile(path, 'utf8');\nconst writeTextFile = (path: string, text: string) =>\n writeTextAtomically(path, text);\n\n/** extract-js가 쓰는 실제 파일시스템 구현이다 — 테스트는 계약만 주입한다. */\nconst extractJsFileSystem: ExtractJsFileSystem = {\n statPath: async (path) => {\n try {\n const entry = await stat(path);\n return entry.isDirectory() ? 'directory' : 'file';\n } catch {\n return 'missing';\n }\n },\n listDirectory: async (path) =>\n (await readdir(path, { withFileTypes: true })).map((entry) => ({\n name: entry.name,\n isFile: entry.isFile(),\n isDirectory: entry.isDirectory(),\n })),\n realPath: (path) => realpath(path),\n};\n/** doctor·init이 쓰는 실제 파일시스템이다. producer는 실행하지 않는다. */\nconst captureFileSystem: CaptureFileSystem = {\n statPath: async (path) => {\n try {\n const entry = await stat(path);\n return entry.isDirectory() ? 'directory' : 'file';\n } catch {\n return 'missing';\n }\n },\n realPath: (path) => realpath(path),\n};\nconst informationalResult = await runInformationalCommand(arguments_);\nconst result = informationalResult ?? await dispatchCommand(arguments_);\n\nprocess.stdout.write(result.standardOutput);\nprocess.stderr.write(result.standardError);\nprocess.exitCode = result.exitCode;\n\n/** 파이프 소비자가 먼저 닫힌 정상 상황만 조용히 종료한다. */\nfunction handleStreamError(error: NodeJS.ErrnoException): void {\n if (error.code === 'EPIPE') {\n process.exitCode = 0;\n return;\n }\n throw error;\n}\n\n/** 도움말과 버전처럼 입력 문서가 필요 없는 명령을 실행한다. */\nasync function runInformationalCommand(\n commandArguments: readonly string[],\n): Promise<CommandResult | undefined> {\n // 도움말 플래그는 어느 위치에 있든 이긴다. 긴 호출 끝에 붙여도 사용법을 본다.\n const helpFlagIndex = commandArguments.findIndex(\n (argument) => argument === '--help' || argument === '-h',\n );\n if (helpFlagIndex >= 0) return commandHelp(commandArguments[0]);\n if (commandArguments[0] === 'help') {\n return commandHelp(commandArguments[1]);\n }\n if (\n commandArguments.length === 1 &&\n commandArguments[0] === '--version'\n ) {\n return readVersion();\n }\n return undefined;\n}\n\n/** 명령 이름에 맞는 사용법을, 모르면 루트 도움말을 돌려준다. */\nfunction commandHelp(command: string | undefined): CommandResult {\n if (command === undefined) return successfulText(rootHelp);\n const usage = commandUsages.get(command);\n return usage === undefined\n ? successfulText(rootHelp)\n : successfulText(`${usage}\\n`);\n}\n\n/** 알려진 분석 하위 명령으로만 라우팅한다. */\nasync function dispatchCommand(\n commandArguments: readonly string[],\n): Promise<CommandResult> {\n switch (commandArguments[0]) {\n case 'check':\n // SARIF driver에만 필요하므로 메타데이터 부재는 json 보고서를 막지 않는다.\n return runCheckCommand(\n commandArguments,\n readTextFile,\n writeTextFile,\n undefined,\n await readPackageVersion(),\n );\n case 'graph':\n return runGraphCommand(commandArguments, readTextFile);\n case 'diff':\n return runDiffCommand(commandArguments, readTextFile);\n case 'query':\n return runQueryCommand(commandArguments, readTextFile);\n case 'impact':\n return runImpactCommand(commandArguments, readTextFile);\n case 'preflight':\n return runPreflightCommand(commandArguments, readTextFile);\n case 'verify-runtime':\n return runRuntimeCommand(commandArguments, readTextFile);\n case 'extract-js':\n return runExtractJsCommand(\n commandArguments,\n extractJsFileSystem,\n readTextFile,\n () => new Date(),\n await readPackageVersion(),\n );\n case 'doctor':\n return runDoctorCommand(\n commandArguments,\n readTextFile,\n captureFileSystem,\n process.cwd(),\n process.env.PATH ?? '',\n );\n case 'init':\n return runInitCommand(\n commandArguments,\n readTextFile,\n writeTextFile,\n captureFileSystem,\n process.cwd(),\n );\n case 'serve':\n return runServeCommand(\n commandArguments,\n readTextFile,\n writeTextFile,\n await readPackageVersion(),\n );\n case 'retentions': {\n const version = await readPackageVersion();\n return version === undefined\n ? packageMetadataError()\n : runRetentionsCommand(\n commandArguments,\n readTextFile,\n () => new Date(),\n version,\n );\n }\n default:\n return {\n standardOutput: '',\n standardError: rootHelp,\n exitCode: 64,\n };\n }\n}\n\n/** 설치된 package.json을 단일 출처로 사용해 버전을 출력한다. */\nasync function readVersion(): Promise<CommandResult> {\n const version = await readPackageVersion();\n return version === undefined\n ? packageMetadataError()\n : successfulText(`${version}\\n`);\n}\n\n/** 설치된 package.json에서 검증된 버전 문자열을 읽는다. */\nasync function readPackageVersion(): Promise<string | undefined> {\n try {\n const text = await readFile(new URL('../../package.json', import.meta.url), 'utf8');\n const document: unknown = JSON.parse(text);\n return isVersionDocument(document) ? document.version : undefined;\n } catch {\n return undefined;\n }\n}\n\n/** package metadata가 비어 있지 않은 버전을 제공하는지 확인한다. */\nfunction isVersionDocument(value: unknown): value is { readonly version: string } {\n return (\n typeof value === 'object' &&\n value !== null &&\n 'version' in value &&\n typeof value.version === 'string' &&\n value.version.length > 0\n );\n}\n\n/** stdout 텍스트와 성공 종료 코드를 만든다. */\nfunction successfulText(standardOutput: string): CommandResult {\n return { standardOutput, standardError: '', exitCode: 0 };\n}\n\n/** package metadata 실패를 민감정보 없는 코드 2로 바꾼다. */\nfunction packageMetadataError(): CommandResult {\n return {\n standardOutput: '',\n standardError: 'Unable to read package metadata.\\n',\n exitCode: 2,\n };\n}\n"]}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { ReadTextFile, WriteTextFile } from './command-support.ts';
|
|
2
|
+
/** 세션이 외부에 필요로 하는 주입이다. */
|
|
3
|
+
export interface McpServerDependencies {
|
|
4
|
+
readonly readTextFile: ReadTextFile;
|
|
5
|
+
readonly writeTextFile?: WriteTextFile | undefined;
|
|
6
|
+
readonly producerVersion?: string | undefined;
|
|
7
|
+
}
|
|
8
|
+
/** JSON-RPC 메시지 한 건을 처리해 응답 라인을 반환하는 세션이다. */
|
|
9
|
+
export interface McpSession {
|
|
10
|
+
readonly handleLine: (line: string) => Promise<string | undefined>;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* MCP 세션을 만든다. 반환된 handleLine은 한 줄의 JSON-RPC 메시지를 받아
|
|
14
|
+
* 응답이 필요하면 직렬화된 한 줄을, 알림이면 undefined를 돌려준다.
|
|
15
|
+
*/
|
|
16
|
+
export declare function createMcpSession(dependencies: McpServerDependencies): McpSession;
|
|
@@ -0,0 +1,534 @@
|
|
|
1
|
+
import { runCheckCommand } from "./check-command.js";
|
|
2
|
+
import { runDiffCommand } from "./diff-command.js";
|
|
3
|
+
import { runGraphCommand } from "./graph-command.js";
|
|
4
|
+
import { runImpactCommand } from "./impact-command.js";
|
|
5
|
+
import { runPreflightCommand } from "./preflight-command.js";
|
|
6
|
+
import { runQueryCommand } from "./query-command.js";
|
|
7
|
+
import { runRetentionsCommand } from "./retentions-command.js";
|
|
8
|
+
/**
|
|
9
|
+
* stdio 위의 newline-delimited JSON-RPC 2.0 세션이다.
|
|
10
|
+
*
|
|
11
|
+
* MCP(Model Context Protocol) 클라이언트가 `isthmus serve`를 자식 프로세스로
|
|
12
|
+
* 띄우고 한 줄에 메시지 하나씩 주고받는다. 이 모듈은 프레이밍만 담고, 각
|
|
13
|
+
* 도구 호출은 CLI 명령의 argv로 변환해 같은 실행 경로를 재사용한다 —
|
|
14
|
+
* 보고서 형식·종료 코드·한계 보고가 명령과 정확히 일치하도록 하기 위해서다.
|
|
15
|
+
*/
|
|
16
|
+
/** 현재 서버가 협상할 수 있는 MCP 프로토콜 개정 목록이다. */
|
|
17
|
+
const SUPPORTED_PROTOCOL_VERSIONS = ['2025-06-18', '2025-03-26', '2024-11-05'];
|
|
18
|
+
/** JSON-RPC 오류 코드다. 알 수 없는 상수를 만들지 않는다. */
|
|
19
|
+
const PARSE_ERROR = -32700;
|
|
20
|
+
const INVALID_REQUEST = -32600;
|
|
21
|
+
const METHOD_NOT_FOUND = -32601;
|
|
22
|
+
const INVALID_PARAMS = -32602;
|
|
23
|
+
const INTERNAL_ERROR = -32603;
|
|
24
|
+
const DOCUMENTS_PROPERTY = {
|
|
25
|
+
type: 'array',
|
|
26
|
+
items: { type: 'string' },
|
|
27
|
+
minItems: 2,
|
|
28
|
+
description: 'Bridge fact document paths to join (GRAPH-EXCHANGE v1/v2 JSON).',
|
|
29
|
+
};
|
|
30
|
+
const TOOL_DEFINITIONS = [
|
|
31
|
+
{
|
|
32
|
+
name: 'check',
|
|
33
|
+
description: 'Join bridge fact documents and report unmatched calls and handlers. '
|
|
34
|
+
+ 'Findings are review candidates with limitations preserved, not '
|
|
35
|
+
+ 'delete approvals.',
|
|
36
|
+
inputSchema: {
|
|
37
|
+
type: 'object',
|
|
38
|
+
properties: {
|
|
39
|
+
documents: DOCUMENTS_PROPERTY,
|
|
40
|
+
strict: {
|
|
41
|
+
type: 'boolean',
|
|
42
|
+
description: 'Exit nonzero when any finding exists.',
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
required: ['documents'],
|
|
46
|
+
additionalProperties: false,
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
name: 'query',
|
|
51
|
+
description: 'Find both sides of a bridge channel or method name across the joined '
|
|
52
|
+
+ 'documents.',
|
|
53
|
+
inputSchema: {
|
|
54
|
+
type: 'object',
|
|
55
|
+
properties: {
|
|
56
|
+
name: {
|
|
57
|
+
type: 'string',
|
|
58
|
+
minLength: 1,
|
|
59
|
+
description: 'Channel, method, or qualifiedName to look up.',
|
|
60
|
+
},
|
|
61
|
+
documents: DOCUMENTS_PROPERTY,
|
|
62
|
+
},
|
|
63
|
+
required: ['name', 'documents'],
|
|
64
|
+
additionalProperties: false,
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
name: 'graph',
|
|
69
|
+
description: 'Render matched bridge boundary edges as a JSON, DOT, or Mermaid graph.',
|
|
70
|
+
inputSchema: {
|
|
71
|
+
type: 'object',
|
|
72
|
+
properties: {
|
|
73
|
+
documents: DOCUMENTS_PROPERTY,
|
|
74
|
+
format: {
|
|
75
|
+
type: 'string',
|
|
76
|
+
enum: ['json', 'dot', 'mermaid'],
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
required: ['documents'],
|
|
80
|
+
additionalProperties: false,
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
name: 'diff',
|
|
85
|
+
description: 'Compare bridge observations before and after a change. Reports '
|
|
86
|
+
+ 'observed differences only; it does not infer revision order.',
|
|
87
|
+
inputSchema: {
|
|
88
|
+
type: 'object',
|
|
89
|
+
properties: {
|
|
90
|
+
before: DOCUMENTS_PROPERTY,
|
|
91
|
+
after: DOCUMENTS_PROPERTY,
|
|
92
|
+
strict: {
|
|
93
|
+
type: 'boolean',
|
|
94
|
+
description: 'Exit nonzero only for new errors.',
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
required: ['before', 'after'],
|
|
98
|
+
additionalProperties: false,
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
name: 'impact',
|
|
103
|
+
description: 'Inspect which bridge dependencies a changed file or symbol reaches. '
|
|
104
|
+
+ 'Exactly one of file, symbol, or changes selects the subject.',
|
|
105
|
+
inputSchema: {
|
|
106
|
+
type: 'object',
|
|
107
|
+
properties: {
|
|
108
|
+
documents: DOCUMENTS_PROPERTY,
|
|
109
|
+
file: { type: 'string', description: 'Changed relative file path.' },
|
|
110
|
+
symbol: { type: 'string', description: 'Changed symbol name or USR.' },
|
|
111
|
+
changes: {
|
|
112
|
+
type: 'string',
|
|
113
|
+
description: 'Path to a changes.json document.',
|
|
114
|
+
},
|
|
115
|
+
runtime: {
|
|
116
|
+
type: 'string',
|
|
117
|
+
description: 'Optional runtime evidence document path.',
|
|
118
|
+
},
|
|
119
|
+
revision: { type: 'string' },
|
|
120
|
+
strict: { type: 'boolean' },
|
|
121
|
+
compact: { type: 'boolean' },
|
|
122
|
+
},
|
|
123
|
+
required: ['documents'],
|
|
124
|
+
additionalProperties: false,
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
name: 'preflight',
|
|
129
|
+
description: 'Trace cross-language impact from a producer analysis context. The '
|
|
130
|
+
+ 'context JSON is produced by scripts/capture-preflight.mjs.',
|
|
131
|
+
inputSchema: {
|
|
132
|
+
type: 'object',
|
|
133
|
+
properties: {
|
|
134
|
+
context: {
|
|
135
|
+
type: 'string',
|
|
136
|
+
description: 'Path to the captured preflight context JSON.',
|
|
137
|
+
},
|
|
138
|
+
runtime: {
|
|
139
|
+
type: 'array',
|
|
140
|
+
items: { type: 'string' },
|
|
141
|
+
description: 'Optional runtime evidence document paths.',
|
|
142
|
+
},
|
|
143
|
+
expectations: { type: 'string' },
|
|
144
|
+
revision: { type: 'string' },
|
|
145
|
+
summary: { type: 'boolean' },
|
|
146
|
+
limit: { type: 'integer', minimum: 1, maximum: 100 },
|
|
147
|
+
explain: { type: 'string' },
|
|
148
|
+
strict: { type: 'boolean' },
|
|
149
|
+
compact: { type: 'boolean' },
|
|
150
|
+
},
|
|
151
|
+
required: ['context'],
|
|
152
|
+
additionalProperties: false,
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
name: 'retentions',
|
|
157
|
+
description: 'Produce external retention evidence for a native analyzer. Pass the '
|
|
158
|
+
+ 'result to cartograph dead --external-retentions.',
|
|
159
|
+
inputSchema: {
|
|
160
|
+
type: 'object',
|
|
161
|
+
properties: {
|
|
162
|
+
documents: DOCUMENTS_PROPERTY,
|
|
163
|
+
producer: {
|
|
164
|
+
type: 'string',
|
|
165
|
+
enum: ['cartograph'],
|
|
166
|
+
description: 'Only cartograph is supported today.',
|
|
167
|
+
},
|
|
168
|
+
},
|
|
169
|
+
required: ['documents', 'producer'],
|
|
170
|
+
additionalProperties: false,
|
|
171
|
+
},
|
|
172
|
+
},
|
|
173
|
+
];
|
|
174
|
+
const TOOL_NAMES = new Map(TOOL_DEFINITIONS.map((tool) => [tool.name, tool]));
|
|
175
|
+
/**
|
|
176
|
+
* MCP 세션을 만든다. 반환된 handleLine은 한 줄의 JSON-RPC 메시지를 받아
|
|
177
|
+
* 응답이 필요하면 직렬화된 한 줄을, 알림이면 undefined를 돌려준다.
|
|
178
|
+
*/
|
|
179
|
+
export function createMcpSession(dependencies) {
|
|
180
|
+
return {
|
|
181
|
+
handleLine: (line) => handleMessage(dependencies, line),
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
async function handleMessage(dependencies, line) {
|
|
185
|
+
let message;
|
|
186
|
+
try {
|
|
187
|
+
message = JSON.parse(line);
|
|
188
|
+
}
|
|
189
|
+
catch {
|
|
190
|
+
return JSON.stringify(errorResponse(undefined, PARSE_ERROR, 'Parse error'));
|
|
191
|
+
}
|
|
192
|
+
if (Array.isArray(message)) {
|
|
193
|
+
// MCP는 배치를 쓰지 않는다. 첫 요소의 id도 모르므로 요청 전체를 거부한다.
|
|
194
|
+
return JSON.stringify(errorResponse(undefined, INVALID_REQUEST, 'Batch requests are not supported'));
|
|
195
|
+
}
|
|
196
|
+
if (!isRequestMessage(message)) {
|
|
197
|
+
// JSON-RPC 2.0은 검출 가능한 id를 그대로 에코하라고 요구한다 — 형식이
|
|
198
|
+
// 스칼라인 요청의 id는 거절 응답에도 보존한다.
|
|
199
|
+
return JSON.stringify(errorResponse(scalarId(message), INVALID_REQUEST, 'Invalid request'));
|
|
200
|
+
}
|
|
201
|
+
if (message.id === undefined) {
|
|
202
|
+
// 알림은 응답하지 않는다. initialized 등 어떤 알림이든 조용히 무시한다.
|
|
203
|
+
return undefined;
|
|
204
|
+
}
|
|
205
|
+
if (!isScalarId(message.id)) {
|
|
206
|
+
return JSON.stringify(errorResponse(null, INVALID_REQUEST, 'Request id must be a string, number, or null'));
|
|
207
|
+
}
|
|
208
|
+
let response;
|
|
209
|
+
try {
|
|
210
|
+
response = await dispatchRequest(dependencies, message.method, message.id, message.params);
|
|
211
|
+
}
|
|
212
|
+
catch {
|
|
213
|
+
// 명령 실행이나 파일 읽기의 내부 예외로 서버가 죽으면 안 된다 — 세션을 유지하며
|
|
214
|
+
// -32603으로 돌려준다. 원인 본문은 입력 경로·내용을 담을 수 있어 노출하지 않는다.
|
|
215
|
+
response = errorResponse(message.id, INTERNAL_ERROR, 'Internal error');
|
|
216
|
+
}
|
|
217
|
+
return JSON.stringify(response);
|
|
218
|
+
}
|
|
219
|
+
function isScalarId(value) {
|
|
220
|
+
return value === null || typeof value === 'string' || typeof value === 'number';
|
|
221
|
+
}
|
|
222
|
+
function scalarId(message) {
|
|
223
|
+
if (typeof message !== 'object' || message === null)
|
|
224
|
+
return null;
|
|
225
|
+
const id = message.id;
|
|
226
|
+
return isScalarId(id) ? id : null;
|
|
227
|
+
}
|
|
228
|
+
function isRequestMessage(value) {
|
|
229
|
+
if (typeof value !== 'object' || value === null)
|
|
230
|
+
return false;
|
|
231
|
+
const candidate = value;
|
|
232
|
+
return candidate.jsonrpc === '2.0' && typeof candidate.method === 'string';
|
|
233
|
+
}
|
|
234
|
+
async function dispatchRequest(dependencies, method, id, params) {
|
|
235
|
+
switch (method) {
|
|
236
|
+
case 'initialize':
|
|
237
|
+
return {
|
|
238
|
+
jsonrpc: '2.0',
|
|
239
|
+
id,
|
|
240
|
+
result: initializeResult(dependencies, params),
|
|
241
|
+
};
|
|
242
|
+
case 'ping':
|
|
243
|
+
return { jsonrpc: '2.0', id, result: {} };
|
|
244
|
+
case 'tools/list':
|
|
245
|
+
return {
|
|
246
|
+
jsonrpc: '2.0',
|
|
247
|
+
id,
|
|
248
|
+
result: { tools: TOOL_DEFINITIONS },
|
|
249
|
+
};
|
|
250
|
+
case 'tools/call':
|
|
251
|
+
return toolsCallResult(dependencies, id, params);
|
|
252
|
+
default:
|
|
253
|
+
return errorResponse(id, METHOD_NOT_FOUND, 'Method not found');
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
/** initialize 요청의 프로토콜 버전을 협상하고 서버 정보를 돌려준다. */
|
|
257
|
+
function initializeResult(dependencies, params) {
|
|
258
|
+
const requested = isInitializeParams(params) ? params.protocolVersion : undefined;
|
|
259
|
+
const protocolVersion = requested !== undefined
|
|
260
|
+
&& SUPPORTED_PROTOCOL_VERSIONS.includes(requested)
|
|
261
|
+
? requested
|
|
262
|
+
: SUPPORTED_PROTOCOL_VERSIONS[0];
|
|
263
|
+
return {
|
|
264
|
+
protocolVersion,
|
|
265
|
+
capabilities: { tools: {} },
|
|
266
|
+
serverInfo: {
|
|
267
|
+
name: 'isthmus',
|
|
268
|
+
version: dependencies.producerVersion ?? 'unknown',
|
|
269
|
+
},
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
function isInitializeParams(value) {
|
|
273
|
+
if (typeof value !== 'object' || value === null)
|
|
274
|
+
return false;
|
|
275
|
+
return typeof value.protocolVersion === 'string';
|
|
276
|
+
}
|
|
277
|
+
async function toolsCallResult(dependencies, id, params) {
|
|
278
|
+
if (!isToolsCallParams(params)) {
|
|
279
|
+
return errorResponse(id, INVALID_PARAMS, 'tools/call requires name and arguments');
|
|
280
|
+
}
|
|
281
|
+
const tool = TOOL_NAMES.get(params.name);
|
|
282
|
+
if (tool === undefined) {
|
|
283
|
+
return errorResponse(id, INVALID_PARAMS, `Unknown tool: ${params.name}`);
|
|
284
|
+
}
|
|
285
|
+
const invalid = validateToolArguments(tool, params.arguments ?? {});
|
|
286
|
+
if (invalid !== undefined) {
|
|
287
|
+
return errorResponse(id, INVALID_PARAMS, invalid);
|
|
288
|
+
}
|
|
289
|
+
const argv = buildToolArgv(params.name, params.arguments ?? {});
|
|
290
|
+
if (argv === undefined) {
|
|
291
|
+
return errorResponse(id, INVALID_PARAMS, 'Invalid tool arguments');
|
|
292
|
+
}
|
|
293
|
+
const result = await runToolCommand(dependencies, argv);
|
|
294
|
+
return { jsonrpc: '2.0', id, result: commandResultContent(result) };
|
|
295
|
+
}
|
|
296
|
+
function isToolsCallParams(value) {
|
|
297
|
+
if (typeof value !== 'object' || value === null)
|
|
298
|
+
return false;
|
|
299
|
+
const candidate = value;
|
|
300
|
+
return typeof candidate.name === 'string';
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* tools/list에 광고한 inputSchema 그대로 인자를 검증한다. 스키마와 검증이
|
|
304
|
+
* 같은 정의에서 파생되므로 둘이 어긋날 수 없다 — 알 수 없는 키·타입 위반·
|
|
305
|
+
* enum 밖 값·범위 밖 정수는 명령 실행 전에 -32602로 거부한다.
|
|
306
|
+
*/
|
|
307
|
+
function validateToolArguments(tool, args) {
|
|
308
|
+
const { properties, required = [] } = tool.inputSchema;
|
|
309
|
+
for (const key of Object.keys(args)) {
|
|
310
|
+
if (!(key in properties))
|
|
311
|
+
return `Unknown argument: ${key}`;
|
|
312
|
+
}
|
|
313
|
+
for (const key of required) {
|
|
314
|
+
if (args[key] === undefined)
|
|
315
|
+
return `Missing required argument: ${key}`;
|
|
316
|
+
}
|
|
317
|
+
for (const [key, value] of Object.entries(args)) {
|
|
318
|
+
const property = properties[key];
|
|
319
|
+
switch (property.type) {
|
|
320
|
+
case 'boolean':
|
|
321
|
+
if (typeof value !== 'boolean')
|
|
322
|
+
return `Argument ${key} must be a boolean`;
|
|
323
|
+
break;
|
|
324
|
+
case 'string':
|
|
325
|
+
if (typeof value !== 'string')
|
|
326
|
+
return `Argument ${key} must be a string`;
|
|
327
|
+
if (property.minLength !== undefined && value.length < property.minLength) {
|
|
328
|
+
return `Argument ${key} is too short`;
|
|
329
|
+
}
|
|
330
|
+
break;
|
|
331
|
+
case 'integer':
|
|
332
|
+
if (typeof value !== 'number' || !Number.isInteger(value)) {
|
|
333
|
+
return `Argument ${key} must be an integer`;
|
|
334
|
+
}
|
|
335
|
+
if (property.minimum !== undefined && value < property.minimum) {
|
|
336
|
+
return `Argument ${key} is below the minimum`;
|
|
337
|
+
}
|
|
338
|
+
if (property.maximum !== undefined && value > property.maximum) {
|
|
339
|
+
return `Argument ${key} exceeds the maximum`;
|
|
340
|
+
}
|
|
341
|
+
break;
|
|
342
|
+
case 'array':
|
|
343
|
+
if (!Array.isArray(value) || (property.items?.type === 'string'
|
|
344
|
+
&& !value.every((item) => typeof item === 'string'))) {
|
|
345
|
+
return `Argument ${key} must be an array of strings`;
|
|
346
|
+
}
|
|
347
|
+
break;
|
|
348
|
+
default:
|
|
349
|
+
break;
|
|
350
|
+
}
|
|
351
|
+
if (property.enum !== undefined && !property.enum.includes(value)) {
|
|
352
|
+
return `Argument ${key} is not an allowed value`;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
return undefined;
|
|
356
|
+
}
|
|
357
|
+
/**
|
|
358
|
+
* 도구 인자를 CLI argv로 변환한다. 스키마 검증을 통과한 인자만 들어오지만,
|
|
359
|
+
* selector 조합처럼 스키마로 표현 못 하는 제약은 여기서도 거른다.
|
|
360
|
+
*/
|
|
361
|
+
function buildToolArgv(toolName, args) {
|
|
362
|
+
switch (toolName) {
|
|
363
|
+
case 'check':
|
|
364
|
+
return withDocuments('check', args, (documents) => [
|
|
365
|
+
...booleanFlag('--strict', args.strict),
|
|
366
|
+
...documents,
|
|
367
|
+
]);
|
|
368
|
+
case 'query':
|
|
369
|
+
return typeof args.name === 'string' && args.name.length > 0
|
|
370
|
+
? withDocuments('query', args, (documents) => [args.name, ...documents])
|
|
371
|
+
: undefined;
|
|
372
|
+
case 'graph':
|
|
373
|
+
return withDocuments('graph', args, (documents) => [
|
|
374
|
+
...enumFlag('--format', args.format, ['json', 'dot', 'mermaid']),
|
|
375
|
+
...documents,
|
|
376
|
+
]);
|
|
377
|
+
case 'diff': {
|
|
378
|
+
const before = stringArray(args.before);
|
|
379
|
+
const after = stringArray(args.after);
|
|
380
|
+
if (before === undefined || after === undefined)
|
|
381
|
+
return undefined;
|
|
382
|
+
return [
|
|
383
|
+
'diff',
|
|
384
|
+
'--before',
|
|
385
|
+
...before,
|
|
386
|
+
'--after',
|
|
387
|
+
...after,
|
|
388
|
+
...booleanFlag('--strict', args.strict),
|
|
389
|
+
];
|
|
390
|
+
}
|
|
391
|
+
case 'impact': {
|
|
392
|
+
const selectorCount = ['file', 'symbol', 'changes']
|
|
393
|
+
.filter((key) => args[key] !== undefined).length;
|
|
394
|
+
if (selectorCount !== 1)
|
|
395
|
+
return undefined;
|
|
396
|
+
return withDocuments('impact', args, (documents) => [
|
|
397
|
+
...valueFlag('--file', args.file),
|
|
398
|
+
...valueFlag('--symbol', args.symbol),
|
|
399
|
+
...valueFlag('--changes', args.changes),
|
|
400
|
+
...documents,
|
|
401
|
+
...valueFlag('--runtime', args.runtime),
|
|
402
|
+
...valueFlag('--revision', args.revision),
|
|
403
|
+
...booleanFlag('--strict', args.strict),
|
|
404
|
+
...booleanFlag('--compact', args.compact),
|
|
405
|
+
]);
|
|
406
|
+
}
|
|
407
|
+
case 'preflight': {
|
|
408
|
+
if (typeof args.context !== 'string' || args.context.length === 0) {
|
|
409
|
+
return undefined;
|
|
410
|
+
}
|
|
411
|
+
const runtimePaths = args.runtime === undefined
|
|
412
|
+
? []
|
|
413
|
+
: stringArray(args.runtime);
|
|
414
|
+
if (runtimePaths === undefined)
|
|
415
|
+
return undefined;
|
|
416
|
+
const argv = [
|
|
417
|
+
'preflight',
|
|
418
|
+
args.context,
|
|
419
|
+
...runtimePaths,
|
|
420
|
+
...valueFlag('--expectations', args.expectations),
|
|
421
|
+
...valueFlag('--revision', args.revision),
|
|
422
|
+
...booleanFlag('--summary', args.summary),
|
|
423
|
+
...booleanFlag('--strict', args.strict),
|
|
424
|
+
...booleanFlag('--compact', args.compact),
|
|
425
|
+
];
|
|
426
|
+
if (typeof args.limit === 'number' && Number.isInteger(args.limit)) {
|
|
427
|
+
argv.push('--limit', String(args.limit));
|
|
428
|
+
}
|
|
429
|
+
argv.push(...valueFlag('--explain', args.explain));
|
|
430
|
+
return argv;
|
|
431
|
+
}
|
|
432
|
+
case 'retentions':
|
|
433
|
+
if (args.producer !== 'cartograph')
|
|
434
|
+
return undefined;
|
|
435
|
+
return withDocuments('retentions', args, (documents) => [
|
|
436
|
+
...documents,
|
|
437
|
+
'--for',
|
|
438
|
+
'cartograph',
|
|
439
|
+
]);
|
|
440
|
+
default:
|
|
441
|
+
return undefined;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
/** documents 인자를 검증하고 argv 앞에 명령 이름을 붙인다. */
|
|
445
|
+
function withDocuments(command, args, build) {
|
|
446
|
+
const documents = stringArray(args.documents);
|
|
447
|
+
if (documents === undefined || documents.length < 2)
|
|
448
|
+
return undefined;
|
|
449
|
+
return [command, ...build(documents)];
|
|
450
|
+
}
|
|
451
|
+
function stringArray(value) {
|
|
452
|
+
if (!Array.isArray(value))
|
|
453
|
+
return undefined;
|
|
454
|
+
return value.every((item) => typeof item === 'string')
|
|
455
|
+
? value
|
|
456
|
+
: undefined;
|
|
457
|
+
}
|
|
458
|
+
function booleanFlag(flag, value) {
|
|
459
|
+
return value === true ? [flag] : [];
|
|
460
|
+
}
|
|
461
|
+
function valueFlag(flag, value) {
|
|
462
|
+
return typeof value === 'string' && value.length > 0 ? [flag, value] : [];
|
|
463
|
+
}
|
|
464
|
+
function enumFlag(flag, value, allowed) {
|
|
465
|
+
return typeof value === 'string' && allowed.includes(value)
|
|
466
|
+
? [flag, value]
|
|
467
|
+
: [];
|
|
468
|
+
}
|
|
469
|
+
/** 변환된 argv를 대응하는 명령 실행 함수로 라우팅한다. */
|
|
470
|
+
async function runToolCommand(dependencies, argv) {
|
|
471
|
+
switch (argv[0]) {
|
|
472
|
+
case 'check':
|
|
473
|
+
return runCheckCommand(argv, dependencies.readTextFile, dependencies.writeTextFile, undefined, dependencies.producerVersion);
|
|
474
|
+
case 'query':
|
|
475
|
+
return runQueryCommand(argv, dependencies.readTextFile);
|
|
476
|
+
case 'graph':
|
|
477
|
+
return runGraphCommand(argv, dependencies.readTextFile);
|
|
478
|
+
case 'diff':
|
|
479
|
+
return runDiffCommand(argv, dependencies.readTextFile);
|
|
480
|
+
case 'impact':
|
|
481
|
+
return runImpactCommand(argv, dependencies.readTextFile);
|
|
482
|
+
case 'preflight':
|
|
483
|
+
return runPreflightCommand(argv, dependencies.readTextFile);
|
|
484
|
+
case 'retentions':
|
|
485
|
+
return dependencies.producerVersion === undefined
|
|
486
|
+
? {
|
|
487
|
+
standardOutput: '',
|
|
488
|
+
standardError: 'Unable to read package metadata.\n',
|
|
489
|
+
exitCode: 2,
|
|
490
|
+
}
|
|
491
|
+
: runRetentionsCommand(argv, dependencies.readTextFile, () => new Date(), dependencies.producerVersion);
|
|
492
|
+
default:
|
|
493
|
+
return {
|
|
494
|
+
standardOutput: '',
|
|
495
|
+
standardError: 'Unknown command.\n',
|
|
496
|
+
exitCode: 64,
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
/**
|
|
501
|
+
* 명령 결과를 MCP 도구 응답으로 옮긴다.
|
|
502
|
+
*
|
|
503
|
+
* stdout에 보고서 문서가 있으면 도구는 일을 한 것이므로 isError를 세지 않는다 —
|
|
504
|
+
* query notFound(64)나 check strict(1)의 정답 문서를 프로토콜 오류로 보내면
|
|
505
|
+
* 소비자가 한계와 조사 결과를 놓친다. 문서가 없는 실패(usage 64·internal 2)만
|
|
506
|
+
* isError로 표시하고 stderr를 함께 실어 원인을 남긴다.
|
|
507
|
+
*/
|
|
508
|
+
function commandResultContent(result) {
|
|
509
|
+
const content = [];
|
|
510
|
+
if (result.standardOutput.length > 0) {
|
|
511
|
+
content.push({ type: 'text', text: result.standardOutput });
|
|
512
|
+
}
|
|
513
|
+
if (result.standardError.length > 0) {
|
|
514
|
+
content.push({ type: 'text', text: result.standardError });
|
|
515
|
+
}
|
|
516
|
+
if (content.length === 0) {
|
|
517
|
+
content.push({ type: 'text', text: '(no output)\n' });
|
|
518
|
+
}
|
|
519
|
+
return {
|
|
520
|
+
content,
|
|
521
|
+
// 종료 코드 2는 항상 내부 오류다 — 부분 stdout이 있어도 도구 오류로 표시한다.
|
|
522
|
+
// 1/64는 문서가 있으면 정답(조사 결과·notFound)이고, 문서 없는 실패만 오류다.
|
|
523
|
+
isError: result.exitCode === 2
|
|
524
|
+
|| (result.exitCode !== 0 && result.standardOutput.length === 0),
|
|
525
|
+
};
|
|
526
|
+
}
|
|
527
|
+
function errorResponse(id, code, message) {
|
|
528
|
+
return {
|
|
529
|
+
jsonrpc: '2.0',
|
|
530
|
+
id: id ?? null,
|
|
531
|
+
error: { code, message },
|
|
532
|
+
};
|
|
533
|
+
}
|
|
534
|
+
//# sourceMappingURL=mcp-server.js.map
|