@obov/mext 0.5.0 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/approval.d.ts +33 -0
- package/dist/approval.js +36 -0
- package/dist/approval.js.map +1 -0
- package/dist/audit.d.ts +123 -0
- package/dist/audit.js +221 -0
- package/dist/audit.js.map +1 -0
- package/dist/build-cli.d.ts +1 -0
- package/dist/build-cli.js.map +1 -1
- package/dist/build.d.ts +2 -0
- package/dist/build.js +46 -5
- package/dist/build.js.map +1 -1
- package/dist/caller.d.ts +53 -0
- package/dist/caller.js +51 -0
- package/dist/caller.js.map +1 -0
- package/dist/cli/args.js +1 -2
- package/dist/cli/args.js.map +1 -1
- package/dist/cli/build.js +26 -4
- package/dist/cli/build.js.map +1 -1
- package/dist/cli/dev-audit.d.ts +25 -0
- package/dist/cli/dev-audit.js +84 -0
- package/dist/cli/dev-audit.js.map +1 -0
- package/dist/cli/dev-lock.d.ts +3 -1
- package/dist/cli/dev-lock.js +12 -5
- package/dist/cli/dev-lock.js.map +1 -1
- package/dist/cli/dev-server.d.ts +9 -0
- package/dist/cli/dev-server.js +27 -3
- package/dist/cli/dev-server.js.map +1 -1
- package/dist/cli/dev-tap.d.ts +20 -0
- package/dist/cli/dev-tap.js +70 -0
- package/dist/cli/dev-tap.js.map +1 -0
- package/dist/cli/dev.d.ts +6 -0
- package/dist/cli/dev.js +20 -8
- package/dist/cli/dev.js.map +1 -1
- package/dist/cli/help.js +2 -0
- package/dist/cli/help.js.map +1 -1
- package/dist/cli/inspect.d.ts +7 -0
- package/dist/cli/inspect.js +699 -0
- package/dist/cli/inspect.js.map +1 -0
- package/dist/cli/main.js +10 -0
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/media.d.ts +19 -0
- package/dist/cli/media.js +77 -0
- package/dist/cli/media.js.map +1 -0
- package/dist/cli/output.d.ts +1 -1
- package/dist/cli/output.js +3 -2
- package/dist/cli/output.js.map +1 -1
- package/dist/cli/run.js +33 -4
- package/dist/cli/run.js.map +1 -1
- package/dist/cli/serve.d.ts +10 -0
- package/dist/cli/serve.js +114 -0
- package/dist/cli/serve.js.map +1 -0
- package/dist/cli/types.d.ts +9 -0
- package/dist/cli/types.js +77 -0
- package/dist/cli/types.js.map +1 -0
- package/dist/client/index.d.ts +11 -0
- package/dist/client/index.js +7 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client/manifest.js +6 -2
- package/dist/client/manifest.js.map +1 -1
- package/dist/client/runner.js +40 -3
- package/dist/client/runner.js.map +1 -1
- package/dist/client/typed.d.ts +56 -0
- package/dist/client/typed.js +526 -0
- package/dist/client/typed.js.map +1 -0
- package/dist/client/types.d.ts +225 -0
- package/dist/client/types.js +2 -0
- package/dist/client/types.js.map +1 -0
- package/dist/command.d.ts +61 -5
- package/dist/command.js +42 -1
- package/dist/command.js.map +1 -1
- package/dist/config.d.ts +59 -10
- package/dist/config.js +67 -7
- package/dist/config.js.map +1 -1
- package/dist/content.d.ts +65 -0
- package/dist/content.js +99 -0
- package/dist/content.js.map +1 -0
- package/dist/errors.d.ts +21 -3
- package/dist/errors.js +13 -0
- package/dist/errors.js.map +1 -1
- package/dist/expose.d.ts +9 -0
- package/dist/expose.js +52 -0
- package/dist/expose.js.map +1 -0
- package/dist/index.d.ts +18 -7
- package/dist/index.js +8 -2
- package/dist/index.js.map +1 -1
- package/dist/inspect/agent.d.ts +104 -0
- package/dist/inspect/agent.js +125 -0
- package/dist/inspect/agent.js.map +1 -0
- package/dist/inspect/audit.d.ts +46 -0
- package/dist/inspect/audit.js +133 -0
- package/dist/inspect/audit.js.map +1 -0
- package/dist/inspect/compare.d.ts +12 -0
- package/dist/inspect/compare.js +31 -0
- package/dist/inspect/compare.js.map +1 -0
- package/dist/inspect/index.d.ts +17 -0
- package/dist/inspect/index.js +12 -0
- package/dist/inspect/index.js.map +1 -0
- package/dist/inspect/inspector.d.ts +168 -0
- package/dist/inspect/inspector.js +392 -0
- package/dist/inspect/inspector.js.map +1 -0
- package/dist/inspect/profiles.d.ts +95 -0
- package/dist/inspect/profiles.js +235 -0
- package/dist/inspect/profiles.js.map +1 -0
- package/dist/inspect/scenario.d.ts +126 -0
- package/dist/inspect/scenario.js +555 -0
- package/dist/inspect/scenario.js.map +1 -0
- package/dist/inspect/ui-server.d.ts +31 -0
- package/dist/inspect/ui-server.js +378 -0
- package/dist/inspect/ui-server.js.map +1 -0
- package/dist/inspect/ui.html +917 -0
- package/dist/instrumentation.d.ts +13 -2
- package/dist/instrumentation.js +11 -1
- package/dist/instrumentation.js.map +1 -1
- package/dist/invoke.d.ts +36 -5
- package/dist/invoke.js +162 -21
- package/dist/invoke.js.map +1 -1
- package/dist/manifest.d.ts +19 -3
- package/dist/manifest.js +5 -1
- package/dist/manifest.js.map +1 -1
- package/dist/mcp/constants.d.ts +7 -0
- package/dist/mcp/constants.js +7 -0
- package/dist/mcp/constants.js.map +1 -1
- package/dist/mcp/dispatch.d.ts +10 -4
- package/dist/mcp/dispatch.js +24 -7
- package/dist/mcp/dispatch.js.map +1 -1
- package/dist/mcp/help.d.ts +2 -0
- package/dist/mcp/help.js +6 -1
- package/dist/mcp/help.js.map +1 -1
- package/dist/mcp/host.d.ts +14 -0
- package/dist/mcp/host.js +48 -0
- package/dist/mcp/host.js.map +1 -0
- package/dist/mcp/http.d.ts +38 -7
- package/dist/mcp/http.js +199 -23
- package/dist/mcp/http.js.map +1 -1
- package/dist/mcp/index.d.ts +2 -1
- package/dist/mcp/index.js +2 -1
- package/dist/mcp/index.js.map +1 -1
- package/dist/mcp/server.d.ts +12 -1
- package/dist/mcp/server.js +63 -17
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/stdio.d.ts +4 -0
- package/dist/mcp/stdio.js +3 -0
- package/dist/mcp/stdio.js.map +1 -1
- package/dist/mcp/tasks.d.ts +3 -1
- package/dist/mcp/tasks.js +7 -4
- package/dist/mcp/tasks.js.map +1 -1
- package/dist/principal.d.ts +22 -0
- package/dist/principal.js +29 -0
- package/dist/principal.js.map +1 -1
- package/dist/registry.d.ts +5 -0
- package/dist/registry.js +21 -0
- package/dist/registry.js.map +1 -1
- package/dist/remote/client.d.ts +26 -1
- package/dist/remote/client.js +180 -55
- package/dist/remote/client.js.map +1 -1
- package/dist/remote/index.d.ts +1 -1
- package/dist/remote/index.js.map +1 -1
- package/dist/remote/oauth.d.ts +1 -1
- package/dist/remote/oauth.js +1 -1
- package/dist/remote/oauth.js.map +1 -1
- package/dist/route.d.ts +40 -2
- package/dist/route.js +128 -8
- package/dist/route.js.map +1 -1
- package/dist/start.js +9 -4
- package/dist/start.js.map +1 -1
- package/dist/suggest.js +1 -1
- package/dist/suggest.js.map +1 -1
- package/dist/typegen.d.ts +27 -0
- package/dist/typegen.js +62 -0
- package/dist/typegen.js.map +1 -0
- package/dist/version-constant.d.ts +5 -0
- package/dist/version-constant.js +6 -0
- package/dist/version-constant.js.map +1 -0
- package/dist/version.d.ts +1 -2
- package/dist/version.js +1 -2
- package/dist/version.js.map +1 -1
- package/package.json +12 -2
package/dist/principal.d.ts
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
/** 행위자 종류. auth · localPrincipal 이 정한다 */
|
|
2
|
+
export type ActorKind = "human" | "agent" | "service" | "system";
|
|
3
|
+
/** 행위자. 서버 (auth · localPrincipal) 가 정한 값만. 클라이언트 신고 값으로 정하지 않는다 */
|
|
4
|
+
export interface Actor {
|
|
5
|
+
kind: ActorKind;
|
|
6
|
+
/** 행위자 식별자 (사용자 ID · 에이전트 ID). principal.id 와 다를 수 있다 (에이전트가 사용자 계정으로 실행) */
|
|
7
|
+
id?: string;
|
|
8
|
+
}
|
|
1
9
|
/** 요청 주체. 서버가 요청마다 생성 (HTTP · CLI 원격), 로컬 · stdio = kind local */
|
|
2
10
|
export interface Principal {
|
|
3
11
|
/** "local:obov" | "user_123" | "anonymous" */
|
|
@@ -7,6 +15,13 @@ export interface Principal {
|
|
|
7
15
|
scopes: string[];
|
|
8
16
|
/** 검증된 토큰 내용. 토큰 원문은 포함하지 않는다 */
|
|
9
17
|
claims?: Record<string, unknown>;
|
|
18
|
+
/** 행위자 (ctx.caller.actor). 없음 = 미확정. mext 는 추정하지 않는다 */
|
|
19
|
+
actor?: Actor;
|
|
20
|
+
/**
|
|
21
|
+
* 논리 표면 (ctx.caller.surface). kebab-case 이름. 없음 = 어댑터 기본 (cli · mcp · stdio).
|
|
22
|
+
* 같은 POST /mcp 요청을 호출자별로 나눌 때 auth 가 정한다 (예: 웹 앱 세션 = "web")
|
|
23
|
+
*/
|
|
24
|
+
surface?: string;
|
|
10
25
|
}
|
|
11
26
|
/** 인증 없는 주체. scopes 없음 → readOnly 명령어만 */
|
|
12
27
|
export declare const ANONYMOUS: Principal;
|
|
@@ -14,3 +29,10 @@ export declare const ANONYMOUS: Principal;
|
|
|
14
29
|
export declare function localPrincipal(user?: string): Principal;
|
|
15
30
|
/** "*" = 전체 */
|
|
16
31
|
export declare function hasScope(principal: Principal, scope: string): boolean;
|
|
32
|
+
/**
|
|
33
|
+
* principal.actor · surface 형식 문제. 없으면 undefined.
|
|
34
|
+
* id · kind · scopes 는 검사하지 않는다 (auth 결과를 검사하지 않던 기존 동작 유지)
|
|
35
|
+
*/
|
|
36
|
+
export declare function callerFieldsProblem(value: unknown): string | undefined;
|
|
37
|
+
/** callerFieldsProblem 이 있으면 TypeError ("<source>: principal <문제>"). source = "auth" · "localPrincipal" */
|
|
38
|
+
export declare function assertCallerFields(value: unknown, source: string): asserts value is Principal;
|
package/dist/principal.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { userInfo } from "node:os";
|
|
2
|
+
import { isSegment } from "./ids.js";
|
|
3
|
+
const ACTOR_KINDS = ["human", "agent", "service", "system"];
|
|
2
4
|
/** 인증 없는 주체. scopes 없음 → readOnly 명령어만 */
|
|
3
5
|
export const ANONYMOUS = Object.freeze({
|
|
4
6
|
id: "anonymous",
|
|
@@ -13,6 +15,33 @@ export function localPrincipal(user = osUser()) {
|
|
|
13
15
|
export function hasScope(principal, scope) {
|
|
14
16
|
return principal.scopes.includes("*") || principal.scopes.includes(scope);
|
|
15
17
|
}
|
|
18
|
+
/**
|
|
19
|
+
* principal.actor · surface 형식 문제. 없으면 undefined.
|
|
20
|
+
* id · kind · scopes 는 검사하지 않는다 (auth 결과를 검사하지 않던 기존 동작 유지)
|
|
21
|
+
*/
|
|
22
|
+
export function callerFieldsProblem(value) {
|
|
23
|
+
const p = value;
|
|
24
|
+
if (typeof p !== "object" || p === null)
|
|
25
|
+
return "must be an object ({ id, kind, scopes })";
|
|
26
|
+
if (p.surface !== undefined && (typeof p.surface !== "string" || !isSegment(p.surface))) {
|
|
27
|
+
return 'surface must be a kebab-case name (e.g. "web")';
|
|
28
|
+
}
|
|
29
|
+
if (p.actor !== undefined) {
|
|
30
|
+
const a = p.actor;
|
|
31
|
+
if (typeof a !== "object" || a === null || !ACTOR_KINDS.includes(a.kind)) {
|
|
32
|
+
return 'actor.kind must be "human", "agent", "service" or "system"';
|
|
33
|
+
}
|
|
34
|
+
if (a.id !== undefined && typeof a.id !== "string")
|
|
35
|
+
return "actor.id must be a string";
|
|
36
|
+
}
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
/** callerFieldsProblem 이 있으면 TypeError ("<source>: principal <문제>"). source = "auth" · "localPrincipal" */
|
|
40
|
+
export function assertCallerFields(value, source) {
|
|
41
|
+
const problem = callerFieldsProblem(value);
|
|
42
|
+
if (problem !== undefined)
|
|
43
|
+
throw new TypeError(`${source}: principal ${problem}`);
|
|
44
|
+
}
|
|
16
45
|
function osUser() {
|
|
17
46
|
try {
|
|
18
47
|
return userInfo().username;
|
package/dist/principal.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"principal.js","sourceRoot":"","sources":["../src/principal.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAA;
|
|
1
|
+
{"version":3,"file":"principal.js","sourceRoot":"","sources":["../src/principal.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAA;AAClC,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAA;AAYpC,MAAM,WAAW,GAAsB,CAAC,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,QAAQ,CAAC,CAAA;AAoB9E,0CAA0C;AAC1C,MAAM,CAAC,MAAM,SAAS,GAAc,MAAM,CAAC,MAAM,CAAC;IAChD,EAAE,EAAE,WAAW;IACf,IAAI,EAAE,WAAW;IACjB,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,EAAE,CAAwB;CACjD,CAAc,CAAA;AAEf,mFAAmF;AACnF,MAAM,UAAU,cAAc,CAAC,IAAI,GAAW,MAAM,EAAE;IACpD,OAAO,EAAE,EAAE,EAAE,SAAS,IAAI,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,GAAG,CAAC,EAAE,CAAA;AAC9D,CAAC;AAED,eAAe;AACf,MAAM,UAAU,QAAQ,CAAC,SAAoB,EAAE,KAAa;IAC1D,OAAO,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAA;AAC3E,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,mBAAmB,CAAC,KAAc;IAChD,MAAM,CAAC,GAAG,KAAkC,CAAA;IAC5C,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,KAAK,IAAI;QAAE,OAAO,0CAA0C,CAAA;IAC1F,IAAI,CAAC,CAAC,OAAO,KAAK,SAAS,IAAI,CAAC,OAAO,CAAC,CAAC,OAAO,KAAK,QAAQ,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,EAAE,CAAC;QACxF,OAAO,gDAAgD,CAAA;IACzD,CAAC;IACD,IAAI,CAAC,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;QAC1B,MAAM,CAAC,GAAG,CAAC,CAAC,KAA8B,CAAA;QAC1C,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAc,CAAC,EAAE,CAAC;YACnF,OAAO,4DAA4D,CAAA;QACrE,CAAC;QACD,IAAI,CAAC,CAAC,EAAE,KAAK,SAAS,IAAI,OAAO,CAAC,CAAC,EAAE,KAAK,QAAQ;YAAE,OAAO,2BAA2B,CAAA;IACxF,CAAC;IACD,OAAO,SAAS,CAAA;AAClB,CAAC;AAED,2GAA2G;AAC3G,MAAM,UAAU,kBAAkB,CAAC,KAAc,EAAE,MAAc;IAC/D,MAAM,OAAO,GAAG,mBAAmB,CAAC,KAAK,CAAC,CAAA;IAC1C,IAAI,OAAO,KAAK,SAAS;QAAE,MAAM,IAAI,SAAS,CAAC,GAAG,MAAM,eAAe,OAAO,EAAE,CAAC,CAAA;AACnF,CAAC;AAED,SAAS,MAAM;IACb,IAAI,CAAC;QACH,OAAO,QAAQ,EAAE,CAAC,QAAQ,CAAA;IAC5B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,OAAO,CAAC,GAAG,CAAC,QAAQ,IAAI,SAAS,CAAA;IAC9D,CAAC;AACH,CAAC","sourcesContent":["import { userInfo } from \"node:os\"\nimport { isSegment } from \"./ids.ts\"\n\n/** 행위자 종류. auth · localPrincipal 이 정한다 */\nexport type ActorKind = \"human\" | \"agent\" | \"service\" | \"system\"\n\n/** 행위자. 서버 (auth · localPrincipal) 가 정한 값만. 클라이언트 신고 값으로 정하지 않는다 */\nexport interface Actor {\n kind: ActorKind\n /** 행위자 식별자 (사용자 ID · 에이전트 ID). principal.id 와 다를 수 있다 (에이전트가 사용자 계정으로 실행) */\n id?: string\n}\n\nconst ACTOR_KINDS: readonly string[] = [\"human\", \"agent\", \"service\", \"system\"]\n\n/** 요청 주체. 서버가 요청마다 생성 (HTTP · CLI 원격), 로컬 · stdio = kind local */\nexport interface Principal {\n /** \"local:obov\" | \"user_123\" | \"anonymous\" */\n id: string\n kind: \"local\" | \"token\" | \"anonymous\"\n /** \"*\" = 전체 */\n scopes: string[]\n /** 검증된 토큰 내용. 토큰 원문은 포함하지 않는다 */\n claims?: Record<string, unknown>\n /** 행위자 (ctx.caller.actor). 없음 = 미확정. mext 는 추정하지 않는다 */\n actor?: Actor\n /**\n * 논리 표면 (ctx.caller.surface). kebab-case 이름. 없음 = 어댑터 기본 (cli · mcp · stdio).\n * 같은 POST /mcp 요청을 호출자별로 나눌 때 auth 가 정한다 (예: 웹 앱 세션 = \"web\")\n */\n surface?: string\n}\n\n/** 인증 없는 주체. scopes 없음 → readOnly 명령어만 */\nexport const ANONYMOUS: Principal = Object.freeze({\n id: \"anonymous\",\n kind: \"anonymous\",\n scopes: Object.freeze([]) as unknown as string[],\n}) as Principal\n\n/** 로컬 · stdio 실행의 기본 주체: { id: \"local:<OS 사용자>\", kind: \"local\", scopes: [\"*\"] } */\nexport function localPrincipal(user: string = osUser()): Principal {\n return { id: `local:${user}`, kind: \"local\", scopes: [\"*\"] }\n}\n\n/** \"*\" = 전체 */\nexport function hasScope(principal: Principal, scope: string): boolean {\n return principal.scopes.includes(\"*\") || principal.scopes.includes(scope)\n}\n\n/**\n * principal.actor · surface 형식 문제. 없으면 undefined.\n * id · kind · scopes 는 검사하지 않는다 (auth 결과를 검사하지 않던 기존 동작 유지)\n */\nexport function callerFieldsProblem(value: unknown): string | undefined {\n const p = value as Partial<Principal> | null\n if (typeof p !== \"object\" || p === null) return \"must be an object ({ id, kind, scopes })\"\n if (p.surface !== undefined && (typeof p.surface !== \"string\" || !isSegment(p.surface))) {\n return 'surface must be a kebab-case name (e.g. \"web\")'\n }\n if (p.actor !== undefined) {\n const a = p.actor as Partial<Actor> | null\n if (typeof a !== \"object\" || a === null || !ACTOR_KINDS.includes(a.kind as string)) {\n return 'actor.kind must be \"human\", \"agent\", \"service\" or \"system\"'\n }\n if (a.id !== undefined && typeof a.id !== \"string\") return \"actor.id must be a string\"\n }\n return undefined\n}\n\n/** callerFieldsProblem 이 있으면 TypeError (\"<source>: principal <문제>\"). source = \"auth\" · \"localPrincipal\" */\nexport function assertCallerFields(value: unknown, source: string): asserts value is Principal {\n const problem = callerFieldsProblem(value)\n if (problem !== undefined) throw new TypeError(`${source}: principal ${problem}`)\n}\n\nfunction osUser(): string {\n try {\n return userInfo().username\n } catch {\n return process.env.USER ?? process.env.USERNAME ?? \"unknown\"\n }\n}\n"]}
|
package/dist/registry.d.ts
CHANGED
|
@@ -90,6 +90,11 @@ export declare function createLazyRegistry(input: {
|
|
|
90
90
|
commands: readonly LazyCommandInput[];
|
|
91
91
|
namespaces?: readonly RegistryNamespaceInput[];
|
|
92
92
|
}): Registry;
|
|
93
|
+
/**
|
|
94
|
+
* keep 을 통과한 명령어만 남긴 Registry (expose 의 표면별 보기). 별칭 · 네임스페이스를 다시 계산한다:
|
|
95
|
+
* 남은 명령어가 없는 네임스페이스는 없어지고, 남은 네임스페이스의 설명은 유지. 명령어 항목 · 자원 · 프롬프트는 원본과 같은 객체
|
|
96
|
+
*/
|
|
97
|
+
export declare function filterRegistry(registry: Registry, keep: (entry: RegisteredCommand) => boolean): Registry;
|
|
93
98
|
/** 실행 가능한 명령어. eager = entry.command, lazy = entry.load() (모듈 import) */
|
|
94
99
|
export declare function loadCommand(entry: RegisteredCommand): Promise<Command>;
|
|
95
100
|
/** 정식 ID 또는 별칭으로 명령어 찾기 */
|
package/dist/registry.js
CHANGED
|
@@ -134,6 +134,27 @@ export function createLazyRegistry(input) {
|
|
|
134
134
|
}
|
|
135
135
|
return { commands, aliases, namespaces: namespaceTable(namespaceIds, [...commands.keys()], input.namespaces) };
|
|
136
136
|
}
|
|
137
|
+
/**
|
|
138
|
+
* keep 을 통과한 명령어만 남긴 Registry (expose 의 표면별 보기). 별칭 · 네임스페이스를 다시 계산한다:
|
|
139
|
+
* 남은 명령어가 없는 네임스페이스는 없어지고, 남은 네임스페이스의 설명은 유지. 명령어 항목 · 자원 · 프롬프트는 원본과 같은 객체
|
|
140
|
+
*/
|
|
141
|
+
export function filterRegistry(registry, keep) {
|
|
142
|
+
const commands = new Map();
|
|
143
|
+
for (const [id, entry] of registry.commands)
|
|
144
|
+
if (keep(entry))
|
|
145
|
+
commands.set(id, entry);
|
|
146
|
+
const aliases = new Map();
|
|
147
|
+
for (const [alias, id] of registry.aliases)
|
|
148
|
+
if (commands.has(id))
|
|
149
|
+
aliases.set(alias, id);
|
|
150
|
+
const namespaceIds = new Set([""]);
|
|
151
|
+
for (const id of commands.keys()) {
|
|
152
|
+
for (let ns = parentNamespace(id); ns !== ""; ns = parentNamespace(ns))
|
|
153
|
+
namespaceIds.add(ns);
|
|
154
|
+
}
|
|
155
|
+
const descriptions = [...registry.namespaces.values()].flatMap((n) => (n.description === undefined ? [] : [{ id: n.id, description: n.description }]));
|
|
156
|
+
return { ...registry, commands, aliases, namespaces: namespaceTable(namespaceIds, [...commands.keys()], descriptions) };
|
|
157
|
+
}
|
|
137
158
|
/** 실행 가능한 명령어. eager = entry.command, lazy = entry.load() (모듈 import) */
|
|
138
159
|
export function loadCommand(entry) {
|
|
139
160
|
return entry.load ? entry.load() : Promise.resolve(entry.command);
|
package/dist/registry.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"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","sourcesContent":["import * as z from \"zod\"\nimport { isCommand, type Command } from \"./command.ts\"\nimport { MextBuildError, type BuildIssue } from \"./errors.ts\"\nimport { isSegment, parentNamespace, RESERVED_ROOT_SEGMENTS } from \"./ids.ts\"\nimport type { PromptSet } from \"./prompt.ts\"\nimport type { ResourceSet } from \"./resource.ts\"\n\nexport type JsonSchema = Record<string, unknown>\n\nexport interface RegistryCommandInput {\n /** 정식 ID. 점 구분 kebab-case 세그먼트 */\n id: string\n command: Command\n /** commands/ 기준 상대 경로 (posix). 스캔 결과에만 있음 */\n file?: string\n}\n\nexport interface RegistryNamespaceInput {\n /** \"\" = 루트 */\n id: string\n description?: string\n}\n\nexport interface RegistryInput {\n commands: readonly RegistryCommandInput[]\n namespaces?: readonly RegistryNamespaceInput[]\n}\n\nexport interface RegisteredCommand {\n id: string\n /**\n * 메타데이터 (description · 안전성 · scopes · examples · positional · aliases) 의 원본.\n * eager (createRegistry) = 실행 가능한 명령어 그대로. lazy (createLazyRegistry) = manifest 로 만든 메타데이터만\n * (input · execute 는 load() 결과에서)\n */\n command: Command\n /** lazy Registry: 명령어 모듈 import (1회 캐시) → 실행 가능한 명령어. 없음 = command 가 실행 가능. loadCommand 로 호출 */\n load?: () => Promise<Command>\n /** 별칭의 전체 ID (정렬) */\n aliases: readonly string[]\n file: string | undefined\n /** input 의 JSON Schema 2020-12 (io: input) */\n inputSchema: JsonSchema\n /** output 의 JSON Schema 2020-12 (io: output) */\n outputSchema: JsonSchema | undefined\n}\n\nexport interface NamespaceInfo {\n /** \"\" = 루트 */\n id: string\n /** _index.md 내용 */\n description: string | undefined\n /** 직속 명령어 정식 ID (정렬). index.ts 명령어 (네임스페이스와 같은 ID) 는 상위 네임스페이스에 속한다 */\n commands: readonly string[]\n /** 직속 하위 네임스페이스 ID (정렬) */\n namespaces: readonly string[]\n}\n\nexport interface Registry {\n /** 정식 ID → 명령어. ID 순 */\n readonly commands: ReadonlyMap<string, RegisteredCommand>\n /** 별칭 전체 ID → 정식 ID */\n readonly aliases: ReadonlyMap<string, string>\n /** 네임스페이스 ID → 정보. 루트 (\"\") 포함, ID 순 */\n readonly namespaces: ReadonlyMap<string, NamespaceInfo>\n /** MCP 자원 (resources/, §12.3). 없음 = 기능 없음 (capability 미선언). 명령어 Map 과 별개 */\n readonly resources?: ResourceSet\n /** MCP 프롬프트 (prompts/, §12.3). 없음 = 기능 없음 (capability 미선언). 이름 순 */\n readonly prompts?: PromptSet\n}\n\n/** 명령어 목록에서 Registry 생성. ID · 별칭 · 스키마 규칙 위반은 MextBuildError */\nexport function createRegistry(input: RegistryInput): Registry {\n const { registry, issues } = collectRegistry(input)\n if (issues.length > 0) throw new MextBuildError(issues)\n return registry\n}\n\n/**\n * createRegistry 의 수집형 (mext build 의 일괄 검증용). 던지지 않는다.\n * registry = 규칙을 통과한 명령어만, issues = 위반 전부\n */\nexport function collectRegistry(input: RegistryInput): { registry: Registry; issues: BuildIssue[] } {\n const issues: BuildIssue[] = []\n const accepted: RegistryCommandInput[] = []\n const seen = new Map<string, RegistryCommandInput>()\n\n for (const entry of input.commands) {\n const where = { id: entry.id, file: entry.file }\n if (!isCommand(entry.command)) {\n issues.push({ code: \"INVALID_COMMAND\", message: \"not a defineCommand result\", ...where })\n continue\n }\n const segments = entry.id.split(\".\")\n const bad = segments.find((s) => !isSegment(s))\n if (bad !== undefined) {\n issues.push({ code: \"INVALID_SEGMENT\", message: `segment \"${bad}\" is not kebab-case`, ...where })\n continue\n }\n if (RESERVED_ROOT_SEGMENTS.includes(segments[0]!)) {\n issues.push({ code: \"RESERVED_ID\", message: `root segment \"${segments[0]}\" is reserved`, ...where })\n continue\n }\n const prev = seen.get(entry.id)\n if (prev) {\n issues.push({ code: \"DUPLICATE_ID\", message: `duplicate ID (also ${prev.file ?? prev.id})`, ...where })\n continue\n }\n seen.set(entry.id, entry)\n accepted.push(entry)\n }\n\n // 네임스페이스 = 명령어 ID 의 모든 상위 경로\n const namespaceIds = new Set<string>([\"\"])\n for (const { id } of accepted) {\n for (let ns = parentNamespace(id); ns !== \"\"; ns = parentNamespace(ns)) namespaceIds.add(ns)\n }\n\n const aliases = new Map<string, string>()\n const aliasesOf = new Map<string, string[]>()\n for (const entry of accepted) {\n const where = { id: entry.id, file: entry.file }\n const parent = parentNamespace(entry.id)\n const own: string[] = []\n for (const alias of entry.command.aliases) {\n if (!isSegment(alias)) {\n issues.push({ code: \"INVALID_ALIAS\", message: `alias \"${alias}\" is not a kebab-case segment`, ...where })\n continue\n }\n if (parent === \"\") {\n issues.push({ code: \"INVALID_ALIAS\", message: `alias \"${alias}\": aliases are not allowed at the root`, ...where })\n continue\n }\n const full = `${parent}.${alias}`\n const owner = seen.has(full) ? full : aliases.get(full)\n if (owner !== undefined || namespaceIds.has(full)) {\n issues.push({\n code: \"DUPLICATE_ID\",\n message: `alias ${full} duplicates ${owner !== undefined ? owner : \"a namespace\"}`,\n ...where,\n })\n continue\n }\n aliases.set(full, entry.id)\n own.push(full)\n }\n aliasesOf.set(entry.id, own.sort())\n }\n\n const commands = new Map<string, RegisteredCommand>()\n for (const entry of [...accepted].sort((a, b) => compareIds(a.id, b.id))) {\n const where = { id: entry.id, file: entry.file }\n let inputSchema: JsonSchema\n let outputSchema: JsonSchema | undefined\n try {\n inputSchema = toJsonSchema(entry.command.input, \"input\")\n outputSchema = entry.command.output ? toJsonSchema(entry.command.output, \"output\") : undefined\n } catch (error) {\n issues.push({ code: \"SCHEMA_UNREPRESENTABLE\", message: errorMessage(error), ...where })\n continue\n }\n commands.set(entry.id, {\n id: entry.id,\n command: entry.command,\n aliases: aliasesOf.get(entry.id) ?? [],\n file: entry.file,\n inputSchema,\n outputSchema,\n })\n }\n\n const namespaces = namespaceTable(namespaceIds, [...commands.keys()], input.namespaces)\n return { registry: { commands, aliases, namespaces }, issues }\n}\n\n/** lazy Registry 의 명령어: manifest 메타데이터 + 모듈 로더 (규칙 검증은 mext build 가 이미 함) */\nexport interface LazyCommandInput {\n id: string\n /** 메타데이터만 있는 명령어 (input · execute 는 load 결과). RegisteredCommand.command */\n command: Command\n /** 별칭의 전체 ID */\n aliases: readonly string[]\n file?: string\n inputSchema: JsonSchema\n outputSchema?: JsonSchema\n /** 실행 시 1회 호출 (결과 캐시, 실패는 다음 호출에서 재시도) */\n load: () => Promise<Command>\n}\n\n/**\n * manifest (mext build 산출) 로 Registry 생성. 명령어 모듈을 import 하지 않는다 (mext start · handler.js).\n * help · tools/list · UNKNOWN_COMMAND · 권한 검사 = 메타데이터만, 입력 검증 · execute = loadCommand 후\n */\nexport function createLazyRegistry(input: {\n commands: readonly LazyCommandInput[]\n namespaces?: readonly RegistryNamespaceInput[]\n}): Registry {\n const commands = new Map<string, RegisteredCommand>()\n const aliases = new Map<string, string>()\n for (const entry of [...input.commands].sort((a, b) => compareIds(a.id, b.id))) {\n const load = entry.load\n let pending: Promise<Command> | undefined\n commands.set(entry.id, {\n id: entry.id,\n command: entry.command,\n aliases: [...entry.aliases].sort(),\n file: entry.file,\n inputSchema: entry.inputSchema,\n outputSchema: entry.outputSchema,\n load: () =>\n (pending ??= load().catch((error: unknown) => {\n pending = undefined\n throw error\n })),\n })\n for (const alias of entry.aliases) aliases.set(alias, entry.id)\n }\n const namespaceIds = new Set<string>([\"\"])\n for (const id of commands.keys()) {\n for (let ns = parentNamespace(id); ns !== \"\"; ns = parentNamespace(ns)) namespaceIds.add(ns)\n }\n return { commands, aliases, namespaces: namespaceTable(namespaceIds, [...commands.keys()], input.namespaces) }\n}\n\n/** 실행 가능한 명령어. eager = entry.command, lazy = entry.load() (모듈 import) */\nexport function loadCommand(entry: RegisteredCommand): Promise<Command> {\n return entry.load ? entry.load() : Promise.resolve(entry.command)\n}\n\nfunction namespaceTable(\n namespaceIds: ReadonlySet<string>,\n commandIds: readonly string[],\n inputs: readonly RegistryNamespaceInput[] | undefined,\n): Map<string, NamespaceInfo> {\n const descriptions = new Map<string, string | undefined>()\n for (const ns of inputs ?? []) descriptions.set(ns.id, ns.description)\n const namespaces = new Map<string, NamespaceInfo>()\n for (const id of [...namespaceIds].sort(compareIds)) {\n namespaces.set(id, {\n id,\n description: descriptions.get(id),\n commands: commandIds.filter((c) => parentNamespace(c) === id),\n namespaces: [...namespaceIds].filter((n) => n !== \"\" && parentNamespace(n) === id).sort(compareIds),\n })\n }\n return namespaces\n}\n\n/** 정식 ID 또는 별칭으로 명령어 찾기 */\nexport function resolveCommand(registry: Registry, id: string): RegisteredCommand | undefined {\n return registry.commands.get(id) ?? registry.commands.get(registry.aliases.get(id) ?? \"\")\n}\n\nfunction toJsonSchema(schema: z.ZodType, io: \"input\" | \"output\"): JsonSchema {\n return z.toJSONSchema(schema, { target: \"draft-2020-12\", io, unrepresentable: \"throw\" }) as JsonSchema\n}\n\nfunction compareIds(a: string, b: string): number {\n return a < b ? -1 : a > b ? 1 : 0\n}\n\nfunction errorMessage(error: unknown): string {\n return error instanceof Error ? error.message : String(error)\n}\n"]}
|
|
1
|
+
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"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","sourcesContent":["import * as z from \"zod\"\nimport { isCommand, type Command } from \"./command.ts\"\nimport { MextBuildError, type BuildIssue } from \"./errors.ts\"\nimport { isSegment, parentNamespace, RESERVED_ROOT_SEGMENTS } from \"./ids.ts\"\nimport type { PromptSet } from \"./prompt.ts\"\nimport type { ResourceSet } from \"./resource.ts\"\n\nexport type JsonSchema = Record<string, unknown>\n\nexport interface RegistryCommandInput {\n /** 정식 ID. 점 구분 kebab-case 세그먼트 */\n id: string\n command: Command\n /** commands/ 기준 상대 경로 (posix). 스캔 결과에만 있음 */\n file?: string\n}\n\nexport interface RegistryNamespaceInput {\n /** \"\" = 루트 */\n id: string\n description?: string\n}\n\nexport interface RegistryInput {\n commands: readonly RegistryCommandInput[]\n namespaces?: readonly RegistryNamespaceInput[]\n}\n\nexport interface RegisteredCommand {\n id: string\n /**\n * 메타데이터 (description · 안전성 · scopes · examples · positional · aliases) 의 원본.\n * eager (createRegistry) = 실행 가능한 명령어 그대로. lazy (createLazyRegistry) = manifest 로 만든 메타데이터만\n * (input · execute 는 load() 결과에서)\n */\n command: Command\n /** lazy Registry: 명령어 모듈 import (1회 캐시) → 실행 가능한 명령어. 없음 = command 가 실행 가능. loadCommand 로 호출 */\n load?: () => Promise<Command>\n /** 별칭의 전체 ID (정렬) */\n aliases: readonly string[]\n file: string | undefined\n /** input 의 JSON Schema 2020-12 (io: input) */\n inputSchema: JsonSchema\n /** output 의 JSON Schema 2020-12 (io: output) */\n outputSchema: JsonSchema | undefined\n}\n\nexport interface NamespaceInfo {\n /** \"\" = 루트 */\n id: string\n /** _index.md 내용 */\n description: string | undefined\n /** 직속 명령어 정식 ID (정렬). index.ts 명령어 (네임스페이스와 같은 ID) 는 상위 네임스페이스에 속한다 */\n commands: readonly string[]\n /** 직속 하위 네임스페이스 ID (정렬) */\n namespaces: readonly string[]\n}\n\nexport interface Registry {\n /** 정식 ID → 명령어. ID 순 */\n readonly commands: ReadonlyMap<string, RegisteredCommand>\n /** 별칭 전체 ID → 정식 ID */\n readonly aliases: ReadonlyMap<string, string>\n /** 네임스페이스 ID → 정보. 루트 (\"\") 포함, ID 순 */\n readonly namespaces: ReadonlyMap<string, NamespaceInfo>\n /** MCP 자원 (resources/, §12.3). 없음 = 기능 없음 (capability 미선언). 명령어 Map 과 별개 */\n readonly resources?: ResourceSet\n /** MCP 프롬프트 (prompts/, §12.3). 없음 = 기능 없음 (capability 미선언). 이름 순 */\n readonly prompts?: PromptSet\n}\n\n/** 명령어 목록에서 Registry 생성. ID · 별칭 · 스키마 규칙 위반은 MextBuildError */\nexport function createRegistry(input: RegistryInput): Registry {\n const { registry, issues } = collectRegistry(input)\n if (issues.length > 0) throw new MextBuildError(issues)\n return registry\n}\n\n/**\n * createRegistry 의 수집형 (mext build 의 일괄 검증용). 던지지 않는다.\n * registry = 규칙을 통과한 명령어만, issues = 위반 전부\n */\nexport function collectRegistry(input: RegistryInput): { registry: Registry; issues: BuildIssue[] } {\n const issues: BuildIssue[] = []\n const accepted: RegistryCommandInput[] = []\n const seen = new Map<string, RegistryCommandInput>()\n\n for (const entry of input.commands) {\n const where = { id: entry.id, file: entry.file }\n if (!isCommand(entry.command)) {\n issues.push({ code: \"INVALID_COMMAND\", message: \"not a defineCommand result\", ...where })\n continue\n }\n const segments = entry.id.split(\".\")\n const bad = segments.find((s) => !isSegment(s))\n if (bad !== undefined) {\n issues.push({ code: \"INVALID_SEGMENT\", message: `segment \"${bad}\" is not kebab-case`, ...where })\n continue\n }\n if (RESERVED_ROOT_SEGMENTS.includes(segments[0]!)) {\n issues.push({ code: \"RESERVED_ID\", message: `root segment \"${segments[0]}\" is reserved`, ...where })\n continue\n }\n const prev = seen.get(entry.id)\n if (prev) {\n issues.push({ code: \"DUPLICATE_ID\", message: `duplicate ID (also ${prev.file ?? prev.id})`, ...where })\n continue\n }\n seen.set(entry.id, entry)\n accepted.push(entry)\n }\n\n // 네임스페이스 = 명령어 ID 의 모든 상위 경로\n const namespaceIds = new Set<string>([\"\"])\n for (const { id } of accepted) {\n for (let ns = parentNamespace(id); ns !== \"\"; ns = parentNamespace(ns)) namespaceIds.add(ns)\n }\n\n const aliases = new Map<string, string>()\n const aliasesOf = new Map<string, string[]>()\n for (const entry of accepted) {\n const where = { id: entry.id, file: entry.file }\n const parent = parentNamespace(entry.id)\n const own: string[] = []\n for (const alias of entry.command.aliases) {\n if (!isSegment(alias)) {\n issues.push({ code: \"INVALID_ALIAS\", message: `alias \"${alias}\" is not a kebab-case segment`, ...where })\n continue\n }\n if (parent === \"\") {\n issues.push({ code: \"INVALID_ALIAS\", message: `alias \"${alias}\": aliases are not allowed at the root`, ...where })\n continue\n }\n const full = `${parent}.${alias}`\n const owner = seen.has(full) ? full : aliases.get(full)\n if (owner !== undefined || namespaceIds.has(full)) {\n issues.push({\n code: \"DUPLICATE_ID\",\n message: `alias ${full} duplicates ${owner !== undefined ? owner : \"a namespace\"}`,\n ...where,\n })\n continue\n }\n aliases.set(full, entry.id)\n own.push(full)\n }\n aliasesOf.set(entry.id, own.sort())\n }\n\n const commands = new Map<string, RegisteredCommand>()\n for (const entry of [...accepted].sort((a, b) => compareIds(a.id, b.id))) {\n const where = { id: entry.id, file: entry.file }\n let inputSchema: JsonSchema\n let outputSchema: JsonSchema | undefined\n try {\n inputSchema = toJsonSchema(entry.command.input, \"input\")\n outputSchema = entry.command.output ? toJsonSchema(entry.command.output, \"output\") : undefined\n } catch (error) {\n issues.push({ code: \"SCHEMA_UNREPRESENTABLE\", message: errorMessage(error), ...where })\n continue\n }\n commands.set(entry.id, {\n id: entry.id,\n command: entry.command,\n aliases: aliasesOf.get(entry.id) ?? [],\n file: entry.file,\n inputSchema,\n outputSchema,\n })\n }\n\n const namespaces = namespaceTable(namespaceIds, [...commands.keys()], input.namespaces)\n return { registry: { commands, aliases, namespaces }, issues }\n}\n\n/** lazy Registry 의 명령어: manifest 메타데이터 + 모듈 로더 (규칙 검증은 mext build 가 이미 함) */\nexport interface LazyCommandInput {\n id: string\n /** 메타데이터만 있는 명령어 (input · execute 는 load 결과). RegisteredCommand.command */\n command: Command\n /** 별칭의 전체 ID */\n aliases: readonly string[]\n file?: string\n inputSchema: JsonSchema\n outputSchema?: JsonSchema\n /** 실행 시 1회 호출 (결과 캐시, 실패는 다음 호출에서 재시도) */\n load: () => Promise<Command>\n}\n\n/**\n * manifest (mext build 산출) 로 Registry 생성. 명령어 모듈을 import 하지 않는다 (mext start · handler.js).\n * help · tools/list · UNKNOWN_COMMAND · 권한 검사 = 메타데이터만, 입력 검증 · execute = loadCommand 후\n */\nexport function createLazyRegistry(input: {\n commands: readonly LazyCommandInput[]\n namespaces?: readonly RegistryNamespaceInput[]\n}): Registry {\n const commands = new Map<string, RegisteredCommand>()\n const aliases = new Map<string, string>()\n for (const entry of [...input.commands].sort((a, b) => compareIds(a.id, b.id))) {\n const load = entry.load\n let pending: Promise<Command> | undefined\n commands.set(entry.id, {\n id: entry.id,\n command: entry.command,\n aliases: [...entry.aliases].sort(),\n file: entry.file,\n inputSchema: entry.inputSchema,\n outputSchema: entry.outputSchema,\n load: () =>\n (pending ??= load().catch((error: unknown) => {\n pending = undefined\n throw error\n })),\n })\n for (const alias of entry.aliases) aliases.set(alias, entry.id)\n }\n const namespaceIds = new Set<string>([\"\"])\n for (const id of commands.keys()) {\n for (let ns = parentNamespace(id); ns !== \"\"; ns = parentNamespace(ns)) namespaceIds.add(ns)\n }\n return { commands, aliases, namespaces: namespaceTable(namespaceIds, [...commands.keys()], input.namespaces) }\n}\n\n/**\n * keep 을 통과한 명령어만 남긴 Registry (expose 의 표면별 보기). 별칭 · 네임스페이스를 다시 계산한다:\n * 남은 명령어가 없는 네임스페이스는 없어지고, 남은 네임스페이스의 설명은 유지. 명령어 항목 · 자원 · 프롬프트는 원본과 같은 객체\n */\nexport function filterRegistry(registry: Registry, keep: (entry: RegisteredCommand) => boolean): Registry {\n const commands = new Map<string, RegisteredCommand>()\n for (const [id, entry] of registry.commands) if (keep(entry)) commands.set(id, entry)\n const aliases = new Map<string, string>()\n for (const [alias, id] of registry.aliases) if (commands.has(id)) aliases.set(alias, id)\n const namespaceIds = new Set<string>([\"\"])\n for (const id of commands.keys()) {\n for (let ns = parentNamespace(id); ns !== \"\"; ns = parentNamespace(ns)) namespaceIds.add(ns)\n }\n const descriptions = [...registry.namespaces.values()].flatMap((n) => (n.description === undefined ? [] : [{ id: n.id, description: n.description }]))\n return { ...registry, commands, aliases, namespaces: namespaceTable(namespaceIds, [...commands.keys()], descriptions) }\n}\n\n/** 실행 가능한 명령어. eager = entry.command, lazy = entry.load() (모듈 import) */\nexport function loadCommand(entry: RegisteredCommand): Promise<Command> {\n return entry.load ? entry.load() : Promise.resolve(entry.command)\n}\n\nfunction namespaceTable(\n namespaceIds: ReadonlySet<string>,\n commandIds: readonly string[],\n inputs: readonly RegistryNamespaceInput[] | undefined,\n): Map<string, NamespaceInfo> {\n const descriptions = new Map<string, string | undefined>()\n for (const ns of inputs ?? []) descriptions.set(ns.id, ns.description)\n const namespaces = new Map<string, NamespaceInfo>()\n for (const id of [...namespaceIds].sort(compareIds)) {\n namespaces.set(id, {\n id,\n description: descriptions.get(id),\n commands: commandIds.filter((c) => parentNamespace(c) === id),\n namespaces: [...namespaceIds].filter((n) => n !== \"\" && parentNamespace(n) === id).sort(compareIds),\n })\n }\n return namespaces\n}\n\n/** 정식 ID 또는 별칭으로 명령어 찾기 */\nexport function resolveCommand(registry: Registry, id: string): RegisteredCommand | undefined {\n return registry.commands.get(id) ?? registry.commands.get(registry.aliases.get(id) ?? \"\")\n}\n\nfunction toJsonSchema(schema: z.ZodType, io: \"input\" | \"output\"): JsonSchema {\n return z.toJSONSchema(schema, { target: \"draft-2020-12\", io, unrepresentable: \"throw\" }) as JsonSchema\n}\n\nfunction compareIds(a: string, b: string): number {\n return a < b ? -1 : a > b ? 1 : 0\n}\n\nfunction errorMessage(error: unknown): string {\n return error instanceof Error ? error.message : String(error)\n}\n"]}
|
package/dist/remote/client.d.ts
CHANGED
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
* Mcp-Name · Mcp-Param-* 헤더. 응답 = application/json 또는 text/event-stream (progress 알림 → onNotification).
|
|
5
5
|
* MRTR (InputRequiredResult) 재시도 루프는 callTool 의 onInputRequired 가 담당.
|
|
6
6
|
* tasks 확장 (§12.6) 선언: tools/call 이 CreateTaskResult (resultType "task") 를 그대로 돌려주고, 폴링 · 답 · 취소 =
|
|
7
|
-
* getTask · updateTask · cancelTask (Mcp-Name = taskId). 폴링 루프는 client/task.ts
|
|
7
|
+
* getTask · updateTask · cancelTask (Mcp-Name = taskId). 폴링 루프는 client/task.ts.
|
|
8
|
+
* subscriptions/listen (mext.md §12.5) = listen: 응답 SSE 를 ack 뒤에도 계속 읽는다 (타입 client subscribe)
|
|
8
9
|
*/
|
|
9
10
|
export declare const PROTOCOL_VERSION = "2026-07-28";
|
|
10
11
|
export declare const META_KEYS: {
|
|
@@ -61,6 +62,8 @@ export interface RequestOptions {
|
|
|
61
62
|
export interface CallToolOptions extends RequestOptions {
|
|
62
63
|
/** input_required 응답 처리 → 이번 라운드의 inputResponses. 없으면 RemoteInputRequiredError */
|
|
63
64
|
onInputRequired?: (request: InputRequired, round: number) => Promise<InputResponses>;
|
|
65
|
+
/** tools/call 요청 params._meta 에 실을 값 (예: 확인 표시 "dev.mext/approval"). 재시도 라운드에도 같은 값 */
|
|
66
|
+
meta?: Record<string, unknown>;
|
|
64
67
|
}
|
|
65
68
|
/** HTTP 401. challenge = WWW-Authenticate 파싱 결과, data = JSON-RPC error.data (mext 는 UNAUTHENTICATED toJSON) */
|
|
66
69
|
export declare class RemoteUnauthorizedError extends Error {
|
|
@@ -103,9 +106,31 @@ export interface ToolsListResult {
|
|
|
103
106
|
ttlMs?: number;
|
|
104
107
|
cacheScope?: string;
|
|
105
108
|
}
|
|
109
|
+
/** subscriptions/listen 스트림 종료. client = close · signal, server = 완료 result · 스트림 끝, error = 연결 오류 · JSON-RPC 오류 */
|
|
110
|
+
export type ListenEnd = {
|
|
111
|
+
reason: "client";
|
|
112
|
+
} | {
|
|
113
|
+
reason: "server";
|
|
114
|
+
} | {
|
|
115
|
+
reason: "error";
|
|
116
|
+
error: unknown;
|
|
117
|
+
};
|
|
118
|
+
/** ack 를 받은 subscriptions/listen 스트림 */
|
|
119
|
+
export interface RemoteListen {
|
|
120
|
+
/** ack (notifications/subscriptions/acknowledged) 의 notifications. ack 없이 완료 = {} */
|
|
121
|
+
readonly acknowledged: Record<string, unknown>;
|
|
122
|
+
/** 스트림 종료 (reject 하지 않는다) */
|
|
123
|
+
readonly done: Promise<ListenEnd>;
|
|
124
|
+
close(): void;
|
|
125
|
+
}
|
|
106
126
|
export interface RemoteClient {
|
|
107
127
|
readonly url: string;
|
|
108
128
|
request(method: string, params?: Record<string, unknown>, options?: RequestOptions): Promise<Record<string, unknown>>;
|
|
129
|
+
/**
|
|
130
|
+
* subscriptions/listen. ack 까지 기다린 뒤 스트림을 계속 읽는다 (알림 = onNotification).
|
|
131
|
+
* ack 전 실패 = reject (401 · JSON-RPC 오류 · 연결 실패 · ack 전 abort = signal.reason)
|
|
132
|
+
*/
|
|
133
|
+
listen(notifications: Record<string, unknown>, options?: RequestOptions): Promise<RemoteListen>;
|
|
109
134
|
listTools(options?: RequestOptions): Promise<RemoteTool[]>;
|
|
110
135
|
listToolsResult(options?: RequestOptions): Promise<ToolsListResult>;
|
|
111
136
|
callTool(tool: RemoteTool, args: Record<string, unknown>, options?: CallToolOptions): Promise<CallToolResult>;
|
package/dist/remote/client.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import { VERSION } from "../version.js";
|
|
1
|
+
import { VERSION } from "../version-constant.js";
|
|
2
2
|
/**
|
|
3
3
|
* 원격 실행용 최소 MCP 클라이언트 (Streamable HTTP, 프로토콜 2026-07-28, stateless).
|
|
4
4
|
* 요청마다 POST 1회: _meta envelope (버전 · clientInfo · clientCapabilities) + MCP-Protocol-Version · Mcp-Method ·
|
|
5
5
|
* Mcp-Name · Mcp-Param-* 헤더. 응답 = application/json 또는 text/event-stream (progress 알림 → onNotification).
|
|
6
6
|
* MRTR (InputRequiredResult) 재시도 루프는 callTool 의 onInputRequired 가 담당.
|
|
7
7
|
* tasks 확장 (§12.6) 선언: tools/call 이 CreateTaskResult (resultType "task") 를 그대로 돌려주고, 폴링 · 답 · 취소 =
|
|
8
|
-
* getTask · updateTask · cancelTask (Mcp-Name = taskId). 폴링 루프는 client/task.ts
|
|
8
|
+
* getTask · updateTask · cancelTask (Mcp-Name = taskId). 폴링 루프는 client/task.ts.
|
|
9
|
+
* subscriptions/listen (mext.md §12.5) = listen: 응답 SSE 를 ack 뒤에도 계속 읽는다 (타입 client subscribe)
|
|
9
10
|
*/
|
|
10
11
|
export const PROTOCOL_VERSION = "2026-07-28";
|
|
11
12
|
export const META_KEYS = {
|
|
@@ -66,7 +67,8 @@ export function createRemoteClient(options) {
|
|
|
66
67
|
const doFetch = options.fetch ?? fetch;
|
|
67
68
|
const clientInfo = options.clientInfo ?? { name: "mext", version: VERSION };
|
|
68
69
|
let nextId = 1;
|
|
69
|
-
|
|
70
|
+
/** POST 1회. 401 = RemoteUnauthorizedError. 응답 본문은 호출자가 읽는다 */
|
|
71
|
+
const send = async (method, params, opts, extraHeaders) => {
|
|
70
72
|
const id = nextId++;
|
|
71
73
|
const meta = {
|
|
72
74
|
...(isRecord(params._meta) ? params._meta : {}),
|
|
@@ -101,20 +103,114 @@ export function createRemoteClient(options) {
|
|
|
101
103
|
throw error;
|
|
102
104
|
throw new RemoteTransportError(`Cannot reach ${options.url}: ${errorMessage(error)}`, { cause: error });
|
|
103
105
|
}
|
|
104
|
-
const type = res.headers.get("content-type") ?? "";
|
|
105
106
|
if (res.status === 401) {
|
|
106
107
|
const payload = await readJson(res);
|
|
107
108
|
const err = isRecord(payload) && isRecord(payload.error) ? payload.error : {};
|
|
108
109
|
throw new RemoteUnauthorizedError(typeof err.message === "string" ? err.message : "Authentication required.", parseBearerChallenge(res.headers.get("www-authenticate")), err.data);
|
|
109
110
|
}
|
|
110
|
-
|
|
111
|
+
return { res, id };
|
|
112
|
+
};
|
|
113
|
+
const request = async (method, params = {}, opts = {}, extraHeaders = {}) => {
|
|
114
|
+
const { res, id } = await send(method, params, opts, extraHeaders);
|
|
115
|
+
if (isEventStream(res)) {
|
|
111
116
|
return handleMessage(await readSse(res, id, opts), id, res.status);
|
|
112
117
|
}
|
|
118
|
+
return handleMessage(await jsonMessage(res), id, res.status);
|
|
119
|
+
};
|
|
120
|
+
const jsonMessage = async (res) => {
|
|
113
121
|
const payload = await readJson(res);
|
|
114
122
|
if (payload === undefined) {
|
|
115
123
|
throw new RemoteTransportError(`${options.url} returned HTTP ${res.status} without a JSON-RPC response.`);
|
|
116
124
|
}
|
|
117
|
-
return
|
|
125
|
+
return payload;
|
|
126
|
+
};
|
|
127
|
+
const listen = async (notifications, opts = {}) => {
|
|
128
|
+
const { signal } = opts;
|
|
129
|
+
signal?.throwIfAborted();
|
|
130
|
+
const { res, id } = await send("subscriptions/listen", { notifications }, opts, {});
|
|
131
|
+
if (!isEventStream(res)) {
|
|
132
|
+
// JSON 응답 = 오류 (handleMessage 가 던진다) 또는 ack 없는 완료
|
|
133
|
+
handleMessage(await jsonMessage(res), id, res.status);
|
|
134
|
+
return { acknowledged: {}, done: Promise.resolve({ reason: "server" }), close() { } };
|
|
135
|
+
}
|
|
136
|
+
const stream = sseStream(res);
|
|
137
|
+
let closed = false;
|
|
138
|
+
const close = () => {
|
|
139
|
+
if (closed)
|
|
140
|
+
return;
|
|
141
|
+
closed = true;
|
|
142
|
+
void stream.cancel();
|
|
143
|
+
};
|
|
144
|
+
signal?.addEventListener("abort", close, { once: true });
|
|
145
|
+
const notify = (message) => {
|
|
146
|
+
try {
|
|
147
|
+
opts.onNotification?.(message.method, isRecord(message.params) ? message.params : {});
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
// 수신자 예외는 스트림에 영향 없음
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
// ack 까지. 그 전의 완료 result = ack 없는 완료, 오류 = reject
|
|
154
|
+
let acknowledged;
|
|
155
|
+
try {
|
|
156
|
+
while (acknowledged === undefined) {
|
|
157
|
+
const message = await stream.next();
|
|
158
|
+
if (message === undefined)
|
|
159
|
+
throw new RemoteTransportError("Event stream ended before subscriptions/listen was acknowledged.");
|
|
160
|
+
if (message.method === "notifications/subscriptions/acknowledged") {
|
|
161
|
+
const params = isRecord(message.params) ? message.params : {};
|
|
162
|
+
acknowledged = isRecord(params.notifications) ? params.notifications : {};
|
|
163
|
+
}
|
|
164
|
+
else if (isNotification(message))
|
|
165
|
+
notify(message);
|
|
166
|
+
else if (message.id === id) {
|
|
167
|
+
handleMessage(message, id, res.status);
|
|
168
|
+
signal?.removeEventListener("abort", close);
|
|
169
|
+
await stream.cancel();
|
|
170
|
+
return { acknowledged: {}, done: Promise.resolve({ reason: "server" }), close() { } };
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
catch (error) {
|
|
175
|
+
signal?.removeEventListener("abort", close);
|
|
176
|
+
await stream.cancel();
|
|
177
|
+
if (signal?.aborted)
|
|
178
|
+
throw signal.reason;
|
|
179
|
+
throw error;
|
|
180
|
+
}
|
|
181
|
+
const done = (async () => {
|
|
182
|
+
try {
|
|
183
|
+
for (;;) {
|
|
184
|
+
let message;
|
|
185
|
+
try {
|
|
186
|
+
message = await stream.next();
|
|
187
|
+
}
|
|
188
|
+
catch (error) {
|
|
189
|
+
return closed ? { reason: "client" } : { reason: "error", error };
|
|
190
|
+
}
|
|
191
|
+
if (closed)
|
|
192
|
+
return { reason: "client" };
|
|
193
|
+
if (message === undefined)
|
|
194
|
+
return { reason: "server" };
|
|
195
|
+
if (isNotification(message))
|
|
196
|
+
notify(message);
|
|
197
|
+
else if (message.id === id) {
|
|
198
|
+
try {
|
|
199
|
+
handleMessage(message, id, res.status);
|
|
200
|
+
return { reason: "server" };
|
|
201
|
+
}
|
|
202
|
+
catch (error) {
|
|
203
|
+
return { reason: "error", error };
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
finally {
|
|
209
|
+
signal?.removeEventListener("abort", close);
|
|
210
|
+
await stream.cancel();
|
|
211
|
+
}
|
|
212
|
+
})();
|
|
213
|
+
return { acknowledged, done, close };
|
|
118
214
|
};
|
|
119
215
|
const handleMessage = (message, id, status) => {
|
|
120
216
|
if (!isRecord(message))
|
|
@@ -154,8 +250,9 @@ export function createRemoteClient(options) {
|
|
|
154
250
|
const callTool = async (tool, args, opts = {}) => {
|
|
155
251
|
const headers = { "Mcp-Name": tool.name, ...paramHeaders(tool, args) };
|
|
156
252
|
let retry = {};
|
|
253
|
+
const meta = opts.meta ? { _meta: opts.meta } : {};
|
|
157
254
|
for (let round = 0;; round++) {
|
|
158
|
-
const result = await request("tools/call", { name: tool.name, arguments: args, ...retry }, opts, headers);
|
|
255
|
+
const result = await request("tools/call", { name: tool.name, arguments: args, ...retry, ...meta }, opts, headers);
|
|
159
256
|
if (result.resultType !== "input_required")
|
|
160
257
|
return result;
|
|
161
258
|
const required = {};
|
|
@@ -182,6 +279,7 @@ export function createRemoteClient(options) {
|
|
|
182
279
|
return {
|
|
183
280
|
url: options.url,
|
|
184
281
|
request: (method, params, opts) => request(method, params, opts),
|
|
282
|
+
listen,
|
|
185
283
|
listTools: async (opts) => (await listToolsResult(opts)).tools,
|
|
186
284
|
listToolsResult,
|
|
187
285
|
callTool,
|
|
@@ -211,77 +309,104 @@ function paramHeaders(tool, args) {
|
|
|
211
309
|
return out;
|
|
212
310
|
}
|
|
213
311
|
/* SSE */
|
|
312
|
+
function isEventStream(res) {
|
|
313
|
+
return (res.headers.get("content-type") ?? "").includes("text/event-stream");
|
|
314
|
+
}
|
|
315
|
+
function isNotification(message) {
|
|
316
|
+
return typeof message.method === "string" && message.id === undefined;
|
|
317
|
+
}
|
|
214
318
|
/** 응답 스트림에서 id 의 응답 메시지가 올 때까지 읽는다. 알림은 콜백, 응답 후 스트림을 닫는다 */
|
|
215
319
|
async function readSse(res, id, opts) {
|
|
320
|
+
const stream = sseStream(res);
|
|
321
|
+
try {
|
|
322
|
+
for (let message = await stream.next(); message !== undefined; message = await stream.next()) {
|
|
323
|
+
if (isNotification(message)) {
|
|
324
|
+
try {
|
|
325
|
+
opts.onNotification?.(message.method, isRecord(message.params) ? message.params : {});
|
|
326
|
+
}
|
|
327
|
+
catch {
|
|
328
|
+
// 표시 오류는 응답에 영향 없음
|
|
329
|
+
}
|
|
330
|
+
continue;
|
|
331
|
+
}
|
|
332
|
+
if (message.id === id && ("result" in message || "error" in message))
|
|
333
|
+
return message;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
finally {
|
|
337
|
+
await stream.cancel();
|
|
338
|
+
}
|
|
339
|
+
if (opts.signal?.aborted)
|
|
340
|
+
throw opts.signal.reason;
|
|
341
|
+
throw new RemoteTransportError("Event stream ended without a response.");
|
|
342
|
+
}
|
|
343
|
+
function sseStream(res) {
|
|
216
344
|
if (!res.body)
|
|
217
345
|
throw new RemoteTransportError("Empty event stream.");
|
|
218
346
|
const reader = res.body.pipeThrough(new TextDecoderStream()).getReader();
|
|
219
347
|
let buffer = "";
|
|
220
348
|
let data = [];
|
|
349
|
+
let ended = false;
|
|
221
350
|
const dispatch = () => {
|
|
222
351
|
if (data.length === 0)
|
|
223
352
|
return undefined;
|
|
224
353
|
const text = data.join("\n");
|
|
225
354
|
data = [];
|
|
226
|
-
let message;
|
|
227
355
|
try {
|
|
228
|
-
message = JSON.parse(text);
|
|
356
|
+
const message = JSON.parse(text);
|
|
357
|
+
return isRecord(message) ? message : undefined;
|
|
229
358
|
}
|
|
230
359
|
catch {
|
|
231
360
|
return undefined;
|
|
232
361
|
}
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
362
|
+
};
|
|
363
|
+
/** buffer 의 완성된 줄을 처리. 이벤트 경계에서 메시지가 나오면 그 메시지 */
|
|
364
|
+
const drain = () => {
|
|
365
|
+
let nl;
|
|
366
|
+
while ((nl = buffer.search(/\r\n|\r|\n/)) >= 0) {
|
|
367
|
+
const line = buffer.slice(0, nl);
|
|
368
|
+
buffer = buffer.slice(nl + (buffer.startsWith("\r\n", nl) ? 2 : 1));
|
|
369
|
+
if (line === "") {
|
|
370
|
+
const found = dispatch();
|
|
371
|
+
if (found !== undefined)
|
|
372
|
+
return found;
|
|
373
|
+
continue;
|
|
241
374
|
}
|
|
242
|
-
|
|
375
|
+
if (line.startsWith(":"))
|
|
376
|
+
continue;
|
|
377
|
+
const colon = line.indexOf(":");
|
|
378
|
+
const field = colon < 0 ? line : line.slice(0, colon);
|
|
379
|
+
let value = colon < 0 ? "" : line.slice(colon + 1);
|
|
380
|
+
if (value.startsWith(" "))
|
|
381
|
+
value = value.slice(1);
|
|
382
|
+
if (field === "data")
|
|
383
|
+
data.push(value);
|
|
243
384
|
}
|
|
244
|
-
if (message.id === id && ("result" in message || "error" in message))
|
|
245
|
-
return message;
|
|
246
385
|
return undefined;
|
|
247
386
|
};
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
const
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
if (found !== undefined)
|
|
261
|
-
return found;
|
|
262
|
-
continue;
|
|
387
|
+
return {
|
|
388
|
+
async next() {
|
|
389
|
+
for (;;) {
|
|
390
|
+
const found = drain();
|
|
391
|
+
if (found !== undefined)
|
|
392
|
+
return found;
|
|
393
|
+
if (ended)
|
|
394
|
+
return undefined;
|
|
395
|
+
const { done, value } = await reader.read();
|
|
396
|
+
if (done) {
|
|
397
|
+
ended = true;
|
|
398
|
+
return dispatch();
|
|
263
399
|
}
|
|
264
|
-
|
|
265
|
-
continue;
|
|
266
|
-
const colon = line.indexOf(":");
|
|
267
|
-
const field = colon < 0 ? line : line.slice(0, colon);
|
|
268
|
-
let value = colon < 0 ? "" : line.slice(colon + 1);
|
|
269
|
-
if (value.startsWith(" "))
|
|
270
|
-
value = value.slice(1);
|
|
271
|
-
if (field === "data")
|
|
272
|
-
data.push(value);
|
|
400
|
+
buffer += value;
|
|
273
401
|
}
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
}
|
|
282
|
-
if (opts.signal?.aborted)
|
|
283
|
-
throw opts.signal.reason;
|
|
284
|
-
throw new RemoteTransportError("Event stream ended without a response.");
|
|
402
|
+
},
|
|
403
|
+
async cancel() {
|
|
404
|
+
ended = true;
|
|
405
|
+
buffer = "";
|
|
406
|
+
data = [];
|
|
407
|
+
await reader.cancel().catch(() => { });
|
|
408
|
+
},
|
|
409
|
+
};
|
|
285
410
|
}
|
|
286
411
|
async function readJson(res) {
|
|
287
412
|
const text = await res.text().catch(() => "");
|