@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
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import type * as z from "zod";
|
|
2
|
+
import type { Command, InputSchema } from "../command.ts";
|
|
3
|
+
import type { ContentBlock, ResultValue } from "../content.ts";
|
|
4
|
+
import type { ToolExposure } from "../mcp/dispatch.ts";
|
|
5
|
+
import type { InputRequestObject } from "../remote/client.ts";
|
|
6
|
+
import type { CallError } from "./typed.ts";
|
|
7
|
+
/**
|
|
8
|
+
* 타입 client (@obov/mext/client, specs/client.md) 의 타입. 런타임 코드 없음.
|
|
9
|
+
* 이 파일의 import 는 전부 타입 import: 브라우저 번들에 Zod · 명령어 코드가 들어가지 않는다
|
|
10
|
+
*/
|
|
11
|
+
export type JsonSchema = Record<string, unknown>;
|
|
12
|
+
/** 명령어 하나의 입력 · 출력 타입 */
|
|
13
|
+
export interface CommandShape {
|
|
14
|
+
input: unknown;
|
|
15
|
+
output: unknown;
|
|
16
|
+
}
|
|
17
|
+
/** 타입 인자 없는 createClient() 의 명령어 표: ID = string, 입력 = 객체, 반환 = unknown */
|
|
18
|
+
export type AnyCommands = Record<string, {
|
|
19
|
+
input: Record<string, unknown>;
|
|
20
|
+
output: unknown;
|
|
21
|
+
}>;
|
|
22
|
+
/** 명령어 표 제약. interface (생성 파일의 Commands) 도 통과한다 (index signature 를 요구하지 않는다) */
|
|
23
|
+
export type CommandMap<C> = {
|
|
24
|
+
[K in keyof C]: CommandShape;
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* defineCommand 결과의 타입 → { input, output } (specs/client.md §3).
|
|
28
|
+
* input = z.input<input 스키마>. output = output 스키마가 있으면 z.output, 없으면 execute 반환 타입. 둘 다 JSON 으로 전송된 모양.
|
|
29
|
+
* 결과 콘텐츠 (mext.md §4) = withContent 의 값 타입, 콘텐츠만 = null (콘텐츠는 CallOptions.onContent)
|
|
30
|
+
*/
|
|
31
|
+
export type CommandTypes<T> = T extends Command<infer I extends InputSchema, infer R, infer O extends z.ZodType> ? {
|
|
32
|
+
input: z.input<I>;
|
|
33
|
+
output: Jsonify<z.ZodType extends O ? ResultValue<Awaited<R>> : z.output<O>>;
|
|
34
|
+
} : never;
|
|
35
|
+
export type InputOf<C extends CommandMap<C>, K extends keyof C> = C[K]["input"];
|
|
36
|
+
export type OutputOf<C extends CommandMap<C>, K extends keyof C> = C[K]["output"];
|
|
37
|
+
type IsAny<T> = 0 extends 1 & T ? true : false;
|
|
38
|
+
type JsonPrimitive = string | number | boolean | null;
|
|
39
|
+
/** JSON.stringify 가 속성을 빼는 값 (함수 · symbol) */
|
|
40
|
+
type Dropped = ((...args: never[]) => unknown) | symbol;
|
|
41
|
+
/**
|
|
42
|
+
* JSON 으로 전송된 값의 타입: Date (toJSON) → string · undefined · void 반환 → null · undefined 속성 → 선택 ·
|
|
43
|
+
* 함수 · symbol 속성 제거 · Map · Set → 빈 객체 · bigint → never (직렬화할 수 없다). any · unknown 은 그대로
|
|
44
|
+
*/
|
|
45
|
+
export type Jsonify<T> = IsAny<T> extends true ? T : unknown extends T ? unknown : T extends JsonPrimitive ? T : T extends undefined | void ? null : T extends bigint ? never : T extends {
|
|
46
|
+
toJSON(): infer J;
|
|
47
|
+
} ? Jsonify<J> : T extends Dropped ? never : T extends ReadonlyMap<unknown, unknown> | ReadonlySet<unknown> ? Record<string, never> : T extends readonly unknown[] ? {
|
|
48
|
+
-readonly [K in keyof T]: Jsonify<T[K]>;
|
|
49
|
+
} : T extends object ? JsonifyObject<T> : never;
|
|
50
|
+
type JsonKey<T, K extends keyof T> = K extends string | number ? (Exclude<T[K], undefined> extends Dropped ? never : K) : never;
|
|
51
|
+
type JsonifyObject<T> = Simplify<{
|
|
52
|
+
[K in keyof T as undefined extends T[K] ? never : JsonKey<T, K>]: Jsonify<T[K]>;
|
|
53
|
+
} & {
|
|
54
|
+
[K in keyof T as undefined extends T[K] ? JsonKey<T, K> : never]?: Jsonify<Exclude<T[K], undefined>>;
|
|
55
|
+
}>;
|
|
56
|
+
type Simplify<T> = {
|
|
57
|
+
[K in keyof T]: T[K];
|
|
58
|
+
} & {};
|
|
59
|
+
export interface ClientOptions {
|
|
60
|
+
/** MCP 엔드포인트 (http(s)://host/mcp). 브라우저 = 상대 경로 ("/mcp") 가능 */
|
|
61
|
+
url: string;
|
|
62
|
+
/** Authorization: Bearer <값>. 요청마다 호출 (함수). client 는 저장 · 갱신하지 않는다 */
|
|
63
|
+
token?: string | (() => string | undefined | Promise<string | undefined>);
|
|
64
|
+
/** 추가 요청 헤더. 요청마다 호출 */
|
|
65
|
+
headers?: Record<string, string> | (() => Record<string, string> | undefined | Promise<Record<string, string> | undefined>);
|
|
66
|
+
/** 자가 신고 clientInfo (ctx.caller.client). 기본 { name: "mext-client", version: mext 버전 상수 } */
|
|
67
|
+
clientInfo?: {
|
|
68
|
+
name: string;
|
|
69
|
+
version: string;
|
|
70
|
+
};
|
|
71
|
+
/** 서버 도구 노출 방식. 지정하면 server/discover 왕복을 생략한다 */
|
|
72
|
+
tools?: ToolExposure;
|
|
73
|
+
/** 기본 = 전역 fetch */
|
|
74
|
+
fetch?: typeof fetch;
|
|
75
|
+
}
|
|
76
|
+
export interface RequestOptions {
|
|
77
|
+
signal?: AbortSignal;
|
|
78
|
+
}
|
|
79
|
+
export interface ProgressUpdate {
|
|
80
|
+
progress?: number;
|
|
81
|
+
total?: number;
|
|
82
|
+
message?: string;
|
|
83
|
+
}
|
|
84
|
+
/** 서버의 입력 요청 하나 (ctx.ask · requiresConfirmation, MRTR · task input_required) */
|
|
85
|
+
export interface InputRequestInfo {
|
|
86
|
+
/** 요청 키 (응답을 묶는 값) */
|
|
87
|
+
key: string;
|
|
88
|
+
/** confirm = 예 · 아니오, form = schema 에 맞는 값, url = URL 열기 동의 */
|
|
89
|
+
kind: "confirm" | "form" | "url";
|
|
90
|
+
message: string;
|
|
91
|
+
/** confirm · form: 요청한 값의 JSON Schema */
|
|
92
|
+
schema?: JsonSchema;
|
|
93
|
+
/** url: 사용자가 열 URL */
|
|
94
|
+
url?: string;
|
|
95
|
+
/** 호출한 명령어 ID (client.task(...).wait = "") */
|
|
96
|
+
command: string;
|
|
97
|
+
/** 서버가 보낸 요청 그대로 */
|
|
98
|
+
raw: InputRequestObject;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* 입력 요청의 답. true = 승인 (confirm) · URL 열기 동의 (url) · 빈 값으로 수락 (form). false = 거절.
|
|
102
|
+
* 값을 보낼 때 = { action: "accept", content }
|
|
103
|
+
*/
|
|
104
|
+
export type InputAnswer = boolean | {
|
|
105
|
+
action: "accept";
|
|
106
|
+
content?: Record<string, unknown>;
|
|
107
|
+
} | {
|
|
108
|
+
action: "decline" | "cancel";
|
|
109
|
+
};
|
|
110
|
+
export interface TaskStatus {
|
|
111
|
+
taskId: string;
|
|
112
|
+
/** working · input_required · completed · failed · cancelled */
|
|
113
|
+
status: string;
|
|
114
|
+
statusMessage?: string;
|
|
115
|
+
}
|
|
116
|
+
export interface CallOptions extends RequestOptions {
|
|
117
|
+
/** ctx.progress (SSE) · task statusMessage. 있으면 progressToken 을 싣는다 */
|
|
118
|
+
onProgress?: (update: ProgressUpdate) => void;
|
|
119
|
+
/** 입력 요청 처리. 없으면 입력 요청 = CallError INPUT_UNAVAILABLE */
|
|
120
|
+
onInput?: (request: InputRequestInfo) => InputAnswer | Promise<InputAnswer>;
|
|
121
|
+
/**
|
|
122
|
+
* 사람이 이 호출을 확인했다는 호출 코드의 선언 (specs/client.md §2.2 확인). 기본 false.
|
|
123
|
+
* 요청 _meta["dev.mext/approval"] = { confirmed: true } + confirm 입력 요청에 묻지 않고 승인
|
|
124
|
+
*/
|
|
125
|
+
confirmed?: boolean;
|
|
126
|
+
/** task 로 실행될 때 (mext.md §12.6): 생성 · 상태 변화마다 */
|
|
127
|
+
onTask?: (task: TaskStatus) => void;
|
|
128
|
+
/** 결과 콘텐츠 (mext.md §4): 값 외 MCP 블록 (image · audio · resource_link). 있을 때 1회, call 이 값을 돌려주기 전 */
|
|
129
|
+
onContent?: (content: ContentBlock[]) => void;
|
|
130
|
+
}
|
|
131
|
+
export interface NamespaceSummary {
|
|
132
|
+
id: string;
|
|
133
|
+
/** _index.md 첫 줄 */
|
|
134
|
+
description?: string;
|
|
135
|
+
}
|
|
136
|
+
export interface CommandSummary {
|
|
137
|
+
id: string;
|
|
138
|
+
description: string;
|
|
139
|
+
readOnly: boolean;
|
|
140
|
+
destructive: boolean;
|
|
141
|
+
requiresConfirmation: boolean;
|
|
142
|
+
/** 승인 필요 (mext.md §4 승인). 서버가 주면 그대로 */
|
|
143
|
+
needsApproval?: true | "conditional";
|
|
144
|
+
/** split 노출에서 이 명령어를 호출할 도구 (query · mutate) */
|
|
145
|
+
tool?: string;
|
|
146
|
+
/** 서버가 준 그 밖의 필드 (client 보다 새 서버) */
|
|
147
|
+
[key: string]: unknown;
|
|
148
|
+
}
|
|
149
|
+
export interface CommandInfo extends CommandSummary {
|
|
150
|
+
aliases: string[];
|
|
151
|
+
scopes: string[];
|
|
152
|
+
positional: string[];
|
|
153
|
+
examples: unknown[];
|
|
154
|
+
/** JSON Schema 2020-12 */
|
|
155
|
+
inputSchema: JsonSchema;
|
|
156
|
+
outputSchema?: JsonSchema;
|
|
157
|
+
task?: {
|
|
158
|
+
ttlMs: number;
|
|
159
|
+
};
|
|
160
|
+
/** 이 명령어 호출이 돌려줄 수 있는 구조화 오류 code */
|
|
161
|
+
errors: string[];
|
|
162
|
+
}
|
|
163
|
+
export interface CommandList {
|
|
164
|
+
/** commands/_index.md */
|
|
165
|
+
description?: string;
|
|
166
|
+
/** 루트 제외, ID 순 */
|
|
167
|
+
namespaces: NamespaceSummary[];
|
|
168
|
+
/** ID 순 */
|
|
169
|
+
commands: CommandSummary[];
|
|
170
|
+
}
|
|
171
|
+
export interface ServerInfo {
|
|
172
|
+
/** 도구 노출 방식 */
|
|
173
|
+
tools: ToolExposure;
|
|
174
|
+
/** 이 호출자에게 보이는 명령어 기준 manifestHash (mext.md §5) */
|
|
175
|
+
manifestHash?: string;
|
|
176
|
+
/** serverInfo.name · version */
|
|
177
|
+
name?: string;
|
|
178
|
+
version?: string;
|
|
179
|
+
instructions?: string;
|
|
180
|
+
/** tasks 확장 선언 여부 (이 호출자 기준) */
|
|
181
|
+
tasks: boolean;
|
|
182
|
+
}
|
|
183
|
+
export interface TaskHandle {
|
|
184
|
+
readonly taskId: string;
|
|
185
|
+
/** tasks/get 1회 */
|
|
186
|
+
get(options?: RequestOptions): Promise<TaskStatus>;
|
|
187
|
+
/** 종료까지 폴링. 반환 · 오류 = call 과 같다 (입력 요청 · 진행률 · 취소 포함) */
|
|
188
|
+
wait(options?: CallOptions): Promise<unknown>;
|
|
189
|
+
/** tasks/cancel */
|
|
190
|
+
cancel(options?: RequestOptions): Promise<void>;
|
|
191
|
+
}
|
|
192
|
+
/** notifications/resources/updated 1건 */
|
|
193
|
+
export interface ResourceUpdate {
|
|
194
|
+
uri: string;
|
|
195
|
+
}
|
|
196
|
+
export interface SubscribeOptions extends RequestOptions {
|
|
197
|
+
/** 갱신 알림마다. close · abort 뒤에는 부르지 않는다. 예외는 스트림에 영향 없음 */
|
|
198
|
+
onUpdate?: (update: ResourceUpdate) => void;
|
|
199
|
+
}
|
|
200
|
+
/** 스트림 종료. client = close() · signal abort, server = 서버 완료 · 스트림 종료 (재시작 · 배포), error = ack 뒤 연결 오류 */
|
|
201
|
+
export type SubscriptionEnd = {
|
|
202
|
+
reason: "client";
|
|
203
|
+
} | {
|
|
204
|
+
reason: "server";
|
|
205
|
+
} | {
|
|
206
|
+
reason: "error";
|
|
207
|
+
error: CallError;
|
|
208
|
+
};
|
|
209
|
+
export interface Subscription {
|
|
210
|
+
/** 서버가 받아들인 URI (ack). subscribe 자원이 아니거나 scopes 가 없는 URI 는 빠진다 */
|
|
211
|
+
readonly resources: string[];
|
|
212
|
+
/** 스트림이 끝나면 resolve (reject 하지 않는다). 자동 재연결 없음 */
|
|
213
|
+
readonly closed: Promise<SubscriptionEnd>;
|
|
214
|
+
close(): void;
|
|
215
|
+
}
|
|
216
|
+
/** 입력이 전부 선택 필드면 입력 인자 생략 가능 */
|
|
217
|
+
export type CallArgs<I> = {} extends I ? [input?: I, options?: CallOptions] : [input: I, options?: CallOptions];
|
|
218
|
+
export type SafeResult<T, E> = {
|
|
219
|
+
ok: true;
|
|
220
|
+
value: T;
|
|
221
|
+
} | {
|
|
222
|
+
ok: false;
|
|
223
|
+
error: E;
|
|
224
|
+
};
|
|
225
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/client/types.ts"],"names":[],"mappings":"","sourcesContent":["import type * as z from \"zod\"\nimport type { Command, InputSchema } from \"../command.ts\"\nimport type { ContentBlock, ResultValue } from \"../content.ts\"\nimport type { ToolExposure } from \"../mcp/dispatch.ts\"\nimport type { InputRequestObject } from \"../remote/client.ts\"\nimport type { CallError } from \"./typed.ts\"\n\n/**\n * 타입 client (@obov/mext/client, specs/client.md) 의 타입. 런타임 코드 없음.\n * 이 파일의 import 는 전부 타입 import: 브라우저 번들에 Zod · 명령어 코드가 들어가지 않는다\n */\n\nexport type JsonSchema = Record<string, unknown>\n\n/* 명령어 ID → 입력 · 출력 타입 */\n\n/** 명령어 하나의 입력 · 출력 타입 */\nexport interface CommandShape {\n input: unknown\n output: unknown\n}\n\n/** 타입 인자 없는 createClient() 의 명령어 표: ID = string, 입력 = 객체, 반환 = unknown */\nexport type AnyCommands = Record<string, { input: Record<string, unknown>; output: unknown }>\n\n/** 명령어 표 제약. interface (생성 파일의 Commands) 도 통과한다 (index signature 를 요구하지 않는다) */\nexport type CommandMap<C> = { [K in keyof C]: CommandShape }\n\n/**\n * defineCommand 결과의 타입 → { input, output } (specs/client.md §3).\n * input = z.input<input 스키마>. output = output 스키마가 있으면 z.output, 없으면 execute 반환 타입. 둘 다 JSON 으로 전송된 모양.\n * 결과 콘텐츠 (mext.md §4) = withContent 의 값 타입, 콘텐츠만 = null (콘텐츠는 CallOptions.onContent)\n */\nexport type CommandTypes<T> =\n T extends Command<infer I extends InputSchema, infer R, infer O extends z.ZodType>\n ? { input: z.input<I>; output: Jsonify<z.ZodType extends O ? ResultValue<Awaited<R>> : z.output<O>> }\n : never\n\nexport type InputOf<C extends CommandMap<C>, K extends keyof C> = C[K][\"input\"]\nexport type OutputOf<C extends CommandMap<C>, K extends keyof C> = C[K][\"output\"]\n\ntype IsAny<T> = 0 extends 1 & T ? true : false\ntype JsonPrimitive = string | number | boolean | null\n/** JSON.stringify 가 속성을 빼는 값 (함수 · symbol) */\ntype Dropped = ((...args: never[]) => unknown) | symbol\n\n/**\n * JSON 으로 전송된 값의 타입: Date (toJSON) → string · undefined · void 반환 → null · undefined 속성 → 선택 ·\n * 함수 · symbol 속성 제거 · Map · Set → 빈 객체 · bigint → never (직렬화할 수 없다). any · unknown 은 그대로\n */\nexport type Jsonify<T> =\n IsAny<T> extends true\n ? T\n : unknown extends T\n ? unknown\n : T extends JsonPrimitive\n ? T\n : T extends undefined | void\n ? null\n : T extends bigint\n ? never\n : T extends { toJSON(): infer J }\n ? Jsonify<J>\n : T extends Dropped\n ? never\n : T extends ReadonlyMap<unknown, unknown> | ReadonlySet<unknown>\n ? Record<string, never>\n : T extends readonly unknown[]\n ? { -readonly [K in keyof T]: Jsonify<T[K]> }\n : T extends object\n ? JsonifyObject<T>\n : never\n\ntype JsonKey<T, K extends keyof T> = K extends string | number ? (Exclude<T[K], undefined> extends Dropped ? never : K) : never\n\ntype JsonifyObject<T> = Simplify<\n { [K in keyof T as undefined extends T[K] ? never : JsonKey<T, K>]: Jsonify<T[K]> } & {\n [K in keyof T as undefined extends T[K] ? JsonKey<T, K> : never]?: Jsonify<Exclude<T[K], undefined>>\n }\n>\n\ntype Simplify<T> = { [K in keyof T]: T[K] } & {}\n\n/* 옵션 */\n\nexport interface ClientOptions {\n /** MCP 엔드포인트 (http(s)://host/mcp). 브라우저 = 상대 경로 (\"/mcp\") 가능 */\n url: string\n /** Authorization: Bearer <값>. 요청마다 호출 (함수). client 는 저장 · 갱신하지 않는다 */\n token?: string | (() => string | undefined | Promise<string | undefined>)\n /** 추가 요청 헤더. 요청마다 호출 */\n headers?: Record<string, string> | (() => Record<string, string> | undefined | Promise<Record<string, string> | undefined>)\n /** 자가 신고 clientInfo (ctx.caller.client). 기본 { name: \"mext-client\", version: mext 버전 상수 } */\n clientInfo?: { name: string; version: string }\n /** 서버 도구 노출 방식. 지정하면 server/discover 왕복을 생략한다 */\n tools?: ToolExposure\n /** 기본 = 전역 fetch */\n fetch?: typeof fetch\n}\n\nexport interface RequestOptions {\n signal?: AbortSignal\n}\n\nexport interface ProgressUpdate {\n progress?: number\n total?: number\n message?: string\n}\n\n/** 서버의 입력 요청 하나 (ctx.ask · requiresConfirmation, MRTR · task input_required) */\nexport interface InputRequestInfo {\n /** 요청 키 (응답을 묶는 값) */\n key: string\n /** confirm = 예 · 아니오, form = schema 에 맞는 값, url = URL 열기 동의 */\n kind: \"confirm\" | \"form\" | \"url\"\n message: string\n /** confirm · form: 요청한 값의 JSON Schema */\n schema?: JsonSchema\n /** url: 사용자가 열 URL */\n url?: string\n /** 호출한 명령어 ID (client.task(...).wait = \"\") */\n command: string\n /** 서버가 보낸 요청 그대로 */\n raw: InputRequestObject\n}\n\n/**\n * 입력 요청의 답. true = 승인 (confirm) · URL 열기 동의 (url) · 빈 값으로 수락 (form). false = 거절.\n * 값을 보낼 때 = { action: \"accept\", content }\n */\nexport type InputAnswer = boolean | { action: \"accept\"; content?: Record<string, unknown> } | { action: \"decline\" | \"cancel\" }\n\nexport interface TaskStatus {\n taskId: string\n /** working · input_required · completed · failed · cancelled */\n status: string\n statusMessage?: string\n}\n\nexport interface CallOptions extends RequestOptions {\n /** ctx.progress (SSE) · task statusMessage. 있으면 progressToken 을 싣는다 */\n onProgress?: (update: ProgressUpdate) => void\n /** 입력 요청 처리. 없으면 입력 요청 = CallError INPUT_UNAVAILABLE */\n onInput?: (request: InputRequestInfo) => InputAnswer | Promise<InputAnswer>\n /**\n * 사람이 이 호출을 확인했다는 호출 코드의 선언 (specs/client.md §2.2 확인). 기본 false.\n * 요청 _meta[\"dev.mext/approval\"] = { confirmed: true } + confirm 입력 요청에 묻지 않고 승인\n */\n confirmed?: boolean\n /** task 로 실행될 때 (mext.md §12.6): 생성 · 상태 변화마다 */\n onTask?: (task: TaskStatus) => void\n /** 결과 콘텐츠 (mext.md §4): 값 외 MCP 블록 (image · audio · resource_link). 있을 때 1회, call 이 값을 돌려주기 전 */\n onContent?: (content: ContentBlock[]) => void\n}\n\n/* 목록 · 스키마 */\n\nexport interface NamespaceSummary {\n id: string\n /** _index.md 첫 줄 */\n description?: string\n}\n\nexport interface CommandSummary {\n id: string\n description: string\n readOnly: boolean\n destructive: boolean\n requiresConfirmation: boolean\n /** 승인 필요 (mext.md §4 승인). 서버가 주면 그대로 */\n needsApproval?: true | \"conditional\"\n /** split 노출에서 이 명령어를 호출할 도구 (query · mutate) */\n tool?: string\n /** 서버가 준 그 밖의 필드 (client 보다 새 서버) */\n [key: string]: unknown\n}\n\nexport interface CommandInfo extends CommandSummary {\n aliases: string[]\n scopes: string[]\n positional: string[]\n examples: unknown[]\n /** JSON Schema 2020-12 */\n inputSchema: JsonSchema\n outputSchema?: JsonSchema\n task?: { ttlMs: number }\n /** 이 명령어 호출이 돌려줄 수 있는 구조화 오류 code */\n errors: string[]\n}\n\nexport interface CommandList {\n /** commands/_index.md */\n description?: string\n /** 루트 제외, ID 순 */\n namespaces: NamespaceSummary[]\n /** ID 순 */\n commands: CommandSummary[]\n}\n\nexport interface ServerInfo {\n /** 도구 노출 방식 */\n tools: ToolExposure\n /** 이 호출자에게 보이는 명령어 기준 manifestHash (mext.md §5) */\n manifestHash?: string\n /** serverInfo.name · version */\n name?: string\n version?: string\n instructions?: string\n /** tasks 확장 선언 여부 (이 호출자 기준) */\n tasks: boolean\n}\n\n/* client */\n\nexport interface TaskHandle {\n readonly taskId: string\n /** tasks/get 1회 */\n get(options?: RequestOptions): Promise<TaskStatus>\n /** 종료까지 폴링. 반환 · 오류 = call 과 같다 (입력 요청 · 진행률 · 취소 포함) */\n wait(options?: CallOptions): Promise<unknown>\n /** tasks/cancel */\n cancel(options?: RequestOptions): Promise<void>\n}\n\n/* 자원 갱신 구독 (specs/client.md §2.4) */\n\n/** notifications/resources/updated 1건 */\nexport interface ResourceUpdate {\n uri: string\n}\n\nexport interface SubscribeOptions extends RequestOptions {\n /** 갱신 알림마다. close · abort 뒤에는 부르지 않는다. 예외는 스트림에 영향 없음 */\n onUpdate?: (update: ResourceUpdate) => void\n}\n\n/** 스트림 종료. client = close() · signal abort, server = 서버 완료 · 스트림 종료 (재시작 · 배포), error = ack 뒤 연결 오류 */\nexport type SubscriptionEnd = { reason: \"client\" } | { reason: \"server\" } | { reason: \"error\"; error: CallError }\n\nexport interface Subscription {\n /** 서버가 받아들인 URI (ack). subscribe 자원이 아니거나 scopes 가 없는 URI 는 빠진다 */\n readonly resources: string[]\n /** 스트림이 끝나면 resolve (reject 하지 않는다). 자동 재연결 없음 */\n readonly closed: Promise<SubscriptionEnd>\n close(): void\n}\n\n/** 입력이 전부 선택 필드면 입력 인자 생략 가능 */\nexport type CallArgs<I> = {} extends I ? [input?: I, options?: CallOptions] : [input: I, options?: CallOptions]\n\nexport type SafeResult<T, E> = { ok: true; value: T } | { ok: false; error: E }\n"]}
|
package/dist/command.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import type * as z from "zod";
|
|
2
|
+
import { type AuditTarget } from "./audit.ts";
|
|
3
|
+
import { type Caller, type Exposure } from "./caller.ts";
|
|
2
4
|
import type { Principal } from "./principal.ts";
|
|
3
5
|
/**
|
|
4
6
|
* 도입 프로젝트가 declaration merging 으로 채우는 ctx.app 타입.
|
|
@@ -36,6 +38,11 @@ export interface Ask {
|
|
|
36
38
|
}
|
|
37
39
|
export interface CommandContext {
|
|
38
40
|
principal: Principal;
|
|
41
|
+
/**
|
|
42
|
+
* 호출자 맥락 (spec §4): 표면 (surface · transport) · 행위자 (actor) · client.
|
|
43
|
+
* surface · actor = 서버가 정한 값, client = 자가 신고 값 (판정 근거로 쓰지 않는다)
|
|
44
|
+
*/
|
|
45
|
+
caller: Caller;
|
|
39
46
|
signal: AbortSignal;
|
|
40
47
|
progress(update: ProgressUpdate): void;
|
|
41
48
|
ask: Ask;
|
|
@@ -48,16 +55,38 @@ export interface CommandContext {
|
|
|
48
55
|
}
|
|
49
56
|
export type InputSchema = z.ZodObject;
|
|
50
57
|
type InputKey<I extends InputSchema> = keyof z.input<I> & string;
|
|
51
|
-
|
|
58
|
+
/**
|
|
59
|
+
* 명령어의 감사 옵션 (spec §11.1). config.audit.sink 가 있을 때만 쓰인다.
|
|
60
|
+
* result = 핸들러 반환값 (output 파싱 후). 실패 · 거부 = undefined.
|
|
61
|
+
* 타입이 unknown 인 이유: 반환 타입으로 두면 audit 을 execute 앞에 쓸 때 반환 타입 추론이 unknown 으로 굳는다
|
|
62
|
+
*/
|
|
63
|
+
export interface CommandAudit<I extends InputSchema = InputSchema> {
|
|
64
|
+
/** 대상. type · id 외 값 (버전 해시 등) 을 자유롭게 싣는다. undefined = 대상 없음 */
|
|
65
|
+
target?: (input: z.output<I>, result: unknown) => AuditTarget | undefined;
|
|
66
|
+
/** 사람이 읽는 한 줄 */
|
|
67
|
+
summary?: string | ((input: z.output<I>, result: unknown) => string | undefined);
|
|
68
|
+
/** 입력 기록. 함수 = 가린 값 (실행 전에 계산), false = 기록하지 않는다. 기본 = 검증된 입력 그대로 */
|
|
69
|
+
input?: boolean | ((input: z.output<I>) => unknown);
|
|
70
|
+
}
|
|
71
|
+
export interface CommandDefinition<I extends InputSchema, R, O extends z.ZodType = z.ZodType> {
|
|
52
72
|
/** 필수. help · MCP 설명의 원본 */
|
|
53
73
|
description: string;
|
|
54
74
|
/** 필수. 최상위 z.object */
|
|
55
75
|
input: I;
|
|
56
76
|
/** 선택. MCP outputSchema 의 원본 (tools: "command"). 있으면 모든 어댑터가 반환값을 검증 (실패 = EXECUTION_ERROR) */
|
|
57
|
-
output?:
|
|
77
|
+
output?: O;
|
|
58
78
|
readOnly?: boolean;
|
|
59
79
|
destructive?: boolean;
|
|
80
|
+
/** needsApproval: true 와 같다 (이전 이름). 둘 중 하나라도 필요하면 필요 */
|
|
60
81
|
requiresConfirmation?: boolean;
|
|
82
|
+
/**
|
|
83
|
+
* 사람의 승인이 필요한 호출 (spec §4 승인). true = 항상. 조건식 = false 를 돌려줄 때만 승인 불필요
|
|
84
|
+
* (그 외 반환값 · 예외 = 승인 필요, 예외는 onCommandError 보고). 입력 검증 · authorize 뒤, execute 전에 판정한다.
|
|
85
|
+
* 승인이 필요하면 표면의 확인 방식 (mext.config.ts approval.surfaces, 기본 ask) 으로 확인한다:
|
|
86
|
+
* ask = ctx.ask (CLI 프롬프트 · --yes, MCP MRTR), client = 요청 _meta 의 확인 표시 + human 행위자.
|
|
87
|
+
* 서버가 agent 로 정한 행위자는 승인할 수 없다 (APPROVAL_REQUIRED). MRTR 재시도마다 다시 판정되므로 부작용 없는 판정만
|
|
88
|
+
*/
|
|
89
|
+
needsApproval?: boolean | ((input: z.output<I>, ctx: CommandContext) => boolean | Promise<boolean>);
|
|
61
90
|
/** 실행에 필요한 scopes. 전부 보유해야 실행 */
|
|
62
91
|
scopes?: readonly string[];
|
|
63
92
|
examples?: readonly z.input<I>[];
|
|
@@ -72,6 +101,21 @@ export interface CommandDefinition<I extends InputSchema, R> {
|
|
|
72
101
|
task?: boolean | {
|
|
73
102
|
ttlMs: number;
|
|
74
103
|
};
|
|
104
|
+
/** 감사 이벤트의 대상 · 요약 · 입력 가림 (spec §11.1) */
|
|
105
|
+
audit?: CommandAudit<I>;
|
|
106
|
+
/**
|
|
107
|
+
* 표면별 노출 (spec §4). 기본 = 모든 표면. 숨긴 표면에서는 목록 (help · tools/list) 에 없고 호출 = UNKNOWN_COMMAND.
|
|
108
|
+
* 판정 기준 = 요청 주체의 표면 (ctx.caller.surface: principal.surface, 없으면 어댑터 기본 cli · mcp · stdio)
|
|
109
|
+
*/
|
|
110
|
+
expose?: Exposure;
|
|
111
|
+
/**
|
|
112
|
+
* 실행 가드 (spec §4). 입력 검증 뒤 · 확인 (requiresConfirmation) 과 execute 전에 호출한다. scopes 검사는 그 전에 그대로.
|
|
113
|
+
* true 만 통과. 그 외 반환값 (false · undefined · 반환 없음) · 예외 = PERMISSION_DENIED (reason "authorize").
|
|
114
|
+
* 반환 타입이 boolean 이라 반환 없는 가드 · `return user?.isAdmin` (boolean | undefined) 은 컴파일 오류다.
|
|
115
|
+
* CommandError 를 던지면 그 message 가 거부 message. 그 외 예외 = 거부 + onCommandError 보고.
|
|
116
|
+
* ctx = execute 와 같은 ctx (ctx.caller 의 표면 · 행위자). MRTR 재시도마다 다시 호출되므로 부작용 없는 판정만
|
|
117
|
+
*/
|
|
118
|
+
authorize?(input: z.output<I>, ctx: CommandContext): boolean | Promise<boolean>;
|
|
75
119
|
execute(input: z.output<I>, ctx: CommandContext): R | Promise<R>;
|
|
76
120
|
}
|
|
77
121
|
/** task 명령어의 기본 ttlMs (1시간) */
|
|
@@ -81,22 +125,34 @@ export interface TaskOptions {
|
|
|
81
125
|
ttlMs: number;
|
|
82
126
|
}
|
|
83
127
|
export declare const COMMAND: unique symbol;
|
|
84
|
-
export interface Command<I extends InputSchema = InputSchema, R = unknown> {
|
|
128
|
+
export interface Command<I extends InputSchema = InputSchema, R = unknown, O extends z.ZodType = z.ZodType> {
|
|
85
129
|
readonly [COMMAND]: true;
|
|
86
130
|
readonly description: string;
|
|
87
131
|
readonly input: I;
|
|
88
|
-
readonly output:
|
|
132
|
+
readonly output: O | undefined;
|
|
89
133
|
readonly readOnly: boolean;
|
|
90
134
|
readonly destructive: boolean;
|
|
135
|
+
/** 항상 승인 필요 (requiresConfirmation: true · needsApproval: true) */
|
|
91
136
|
readonly requiresConfirmation: boolean;
|
|
137
|
+
/**
|
|
138
|
+
* 승인 조건식 (needsApproval 이 함수일 때만. 항상 필요 = requiresConfirmation). manifest · help = needsApproval: "conditional".
|
|
139
|
+
* lazy Registry 의 메타데이터 명령어 = 호출할 수 없는 자리 표시 (모듈 로드 후의 명령어에서 부른다)
|
|
140
|
+
*/
|
|
141
|
+
needsApproval?(input: z.output<I>, ctx: CommandContext): boolean | Promise<boolean>;
|
|
92
142
|
readonly scopes: readonly string[];
|
|
93
143
|
readonly examples: readonly z.input<I>[];
|
|
94
144
|
readonly positional: readonly string[];
|
|
95
145
|
readonly aliases: readonly string[];
|
|
96
146
|
/** task 명령어 (§12.6). 없음 = 항상 동기 */
|
|
97
147
|
readonly task?: TaskOptions;
|
|
148
|
+
/** 감사 옵션 (§11.1). 없음 = 기본 (검증된 입력 그대로, 대상 · 요약 없음) */
|
|
149
|
+
readonly audit?: Readonly<CommandAudit<I>>;
|
|
150
|
+
/** 표면별 노출 (§4). 없음 = 모든 표면 */
|
|
151
|
+
readonly expose?: Exposure;
|
|
152
|
+
/** 실행 가드 (§4). 없음 = 가드 없음. lazy Registry 의 메타데이터 명령어에는 없다 (모듈 로드 후의 명령어에서 읽는다) */
|
|
153
|
+
authorize?(input: z.output<I>, ctx: CommandContext): boolean | Promise<boolean>;
|
|
98
154
|
execute(input: z.output<I>, ctx: CommandContext): R | Promise<R>;
|
|
99
155
|
}
|
|
100
|
-
export declare function defineCommand<I extends InputSchema, R>(definition: CommandDefinition<I, R>): Command<I, R>;
|
|
156
|
+
export declare function defineCommand<I extends InputSchema, R, O extends z.ZodType = z.ZodType>(definition: CommandDefinition<I, R, O>): Command<I, R, O>;
|
|
101
157
|
export declare function isCommand(value: unknown): value is Command;
|
|
102
158
|
export {};
|
package/dist/command.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { commandAuditProblem } from "./audit.js";
|
|
2
|
+
import { exposeProblem } from "./caller.js";
|
|
1
3
|
/** task 명령어의 기본 ttlMs (1시간) */
|
|
2
4
|
export const DEFAULT_TASK_TTL_MS = 3_600_000;
|
|
3
5
|
export const COMMAND = Symbol.for("@obov/mext/command");
|
|
@@ -19,6 +21,29 @@ export function defineCommand(definition) {
|
|
|
19
21
|
}
|
|
20
22
|
}
|
|
21
23
|
const task = taskOptions(d.task);
|
|
24
|
+
if (d.audit !== undefined) {
|
|
25
|
+
const problem = commandAuditProblem(d.audit);
|
|
26
|
+
if (problem !== undefined)
|
|
27
|
+
throw new TypeError(`defineCommand: ${problem}`);
|
|
28
|
+
}
|
|
29
|
+
const audit = d.audit === undefined ? undefined : Object.freeze({ ...d.audit });
|
|
30
|
+
if (d.expose !== undefined) {
|
|
31
|
+
const problem = exposeProblem(d.expose);
|
|
32
|
+
if (problem !== undefined)
|
|
33
|
+
throw new TypeError(`defineCommand: ${problem}`);
|
|
34
|
+
}
|
|
35
|
+
// 빈 객체 = 규칙 없음 (없는 것과 같다)
|
|
36
|
+
const expose = d.expose === undefined || Object.keys(d.expose).length === 0 ? undefined : Object.freeze({ ...d.expose });
|
|
37
|
+
if (d.authorize !== undefined && typeof d.authorize !== "function") {
|
|
38
|
+
throw new TypeError("defineCommand: authorize must be a function");
|
|
39
|
+
}
|
|
40
|
+
const authorize = d.authorize;
|
|
41
|
+
if (d.needsApproval !== undefined && typeof d.needsApproval !== "boolean" && typeof d.needsApproval !== "function") {
|
|
42
|
+
throw new TypeError("defineCommand: needsApproval must be a boolean or a function");
|
|
43
|
+
}
|
|
44
|
+
// 항상 필요 = requiresConfirmation (정규형). 조건식은 항상 필요가 아닐 때만 남긴다
|
|
45
|
+
const alwaysApproval = d.requiresConfirmation === true || d.needsApproval === true;
|
|
46
|
+
const approvalCondition = !alwaysApproval && typeof d.needsApproval === "function" ? d.needsApproval : undefined;
|
|
22
47
|
const execute = d.execute;
|
|
23
48
|
return Object.freeze({
|
|
24
49
|
[COMMAND]: true,
|
|
@@ -27,12 +52,28 @@ export function defineCommand(definition) {
|
|
|
27
52
|
output: d.output,
|
|
28
53
|
readOnly: d.readOnly === true,
|
|
29
54
|
destructive: d.destructive === true,
|
|
30
|
-
requiresConfirmation:
|
|
55
|
+
requiresConfirmation: alwaysApproval,
|
|
56
|
+
...(approvalCondition
|
|
57
|
+
? {
|
|
58
|
+
needsApproval(input, ctx) {
|
|
59
|
+
return approvalCondition.call(definition, input, ctx);
|
|
60
|
+
},
|
|
61
|
+
}
|
|
62
|
+
: {}),
|
|
31
63
|
scopes: Object.freeze([...(d.scopes ?? [])]),
|
|
32
64
|
examples: Object.freeze([...(d.examples ?? [])]),
|
|
33
65
|
positional: Object.freeze([...(d.positional ?? [])]),
|
|
34
66
|
aliases: Object.freeze([...(d.aliases ?? [])]),
|
|
35
67
|
...(task ? { task } : {}),
|
|
68
|
+
...(audit ? { audit } : {}),
|
|
69
|
+
...(expose ? { expose } : {}),
|
|
70
|
+
...(authorize
|
|
71
|
+
? {
|
|
72
|
+
authorize(input, ctx) {
|
|
73
|
+
return authorize.call(definition, input, ctx);
|
|
74
|
+
},
|
|
75
|
+
}
|
|
76
|
+
: {}),
|
|
36
77
|
execute(input, ctx) {
|
|
37
78
|
return execute.call(definition, input, ctx);
|
|
38
79
|
},
|
package/dist/command.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"command.js","sourceRoot":"","sources":["../src/command.ts"],"names":[],"mappings":"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","sourcesContent":["import type * as z from \"zod\"\nimport type { Principal } from \"./principal.ts\"\n\n/**\n * 도입 프로젝트가 declaration merging 으로 채우는 ctx.app 타입.\n *\n * declare module \"@obov/mext\" { interface MextApp { db: Db } }\n */\nexport interface MextApp {}\n\nexport interface ProgressUpdate {\n progress: number\n total?: number\n message?: string\n}\n\nexport interface ConfirmRequest {\n kind: \"confirm\"\n message: string\n}\n\nexport interface InputRequest<S extends z.ZodObject = z.ZodObject> {\n kind: \"input\"\n message: string\n schema: S\n}\n\n/** URL 모드 (spec §12.4). 제3자 인가 · API 키 · 결제처럼 클라이언트를 거치면 안 되는 입력. url = http(s) 절대 URL */\nexport interface UrlRequest {\n kind: \"url\"\n message: string\n url: string\n}\n\nexport type AskRequest = ConfirmRequest | InputRequest | UrlRequest\n\n/** ctx.ask. confirm = boolean, input = schema 로 검증된 값, url = boolean (accept = URL 열기 동의. 완료 의미 아님) */\nexport interface Ask {\n (request: ConfirmRequest): Promise<boolean>\n (request: UrlRequest): Promise<boolean>\n <S extends z.ZodObject>(request: InputRequest<S>): Promise<z.output<S>>\n}\n\nexport interface CommandContext {\n principal: Principal\n signal: AbortSignal\n progress(update: ProgressUpdate): void\n ask: Ask\n app: MextApp\n /**\n * 자원 갱신 알림 (§12.5) → 구독 중인 subscriptions/listen 스트림에 notifications/resources/updated.\n * state 없음 · 구독 없음 · 로컬 CLI 실행 = no-op. bus 발행 실패 = stderr 1줄 (거부하지 않음)\n */\n resourceUpdated(uri: string): Promise<void>\n}\n\nexport type InputSchema = z.ZodObject\n\ntype InputKey<I extends InputSchema> = keyof z.input<I> & string\n\nexport interface CommandDefinition<I extends InputSchema, R> {\n /** 필수. help · MCP 설명의 원본 */\n description: string\n /** 필수. 최상위 z.object */\n input: I\n /** 선택. MCP outputSchema 의 원본 (tools: \"command\"). 있으면 모든 어댑터가 반환값을 검증 (실패 = EXECUTION_ERROR) */\n output?: z.ZodType\n readOnly?: boolean\n destructive?: boolean\n requiresConfirmation?: boolean\n /** 실행에 필요한 scopes. 전부 보유해야 실행 */\n scopes?: readonly string[]\n examples?: readonly z.input<I>[]\n /** CLI positional 인자로 받을 input 키 순서 */\n positional?: readonly InputKey<I>[]\n /** 같은 네임스페이스 안 kebab-case 세그먼트. commands/users/get.ts 의 \"fetch\" → users.fetch */\n aliases?: readonly string[]\n /**\n * 장시간 명령어. 조건 (state.store · 클라이언트 선언) 이 맞으면 MCP tasks 확장으로 실행 (§12.6).\n * true = 기본 ttlMs (DEFAULT_TASK_TTL_MS). ttlMs = 종료 후 tasks/get 으로 조회할 수 있는 시간\n */\n task?: boolean | { ttlMs: number }\n execute(input: z.output<I>, ctx: CommandContext): R | Promise<R>\n}\n\n/** task 명령어의 기본 ttlMs (1시간) */\nexport const DEFAULT_TASK_TTL_MS = 3_600_000\n\n/** defineCommand 의 task 정규형 */\nexport interface TaskOptions {\n ttlMs: number\n}\n\nexport const COMMAND: unique symbol = Symbol.for(\"@obov/mext/command\") as never\n\nexport interface Command<I extends InputSchema = InputSchema, R = unknown> {\n readonly [COMMAND]: true\n readonly description: string\n readonly input: I\n readonly output: z.ZodType | undefined\n readonly readOnly: boolean\n readonly destructive: boolean\n readonly requiresConfirmation: boolean\n readonly scopes: readonly string[]\n readonly examples: readonly z.input<I>[]\n readonly positional: readonly string[]\n readonly aliases: readonly string[]\n /** task 명령어 (§12.6). 없음 = 항상 동기 */\n readonly task?: TaskOptions\n execute(input: z.output<I>, ctx: CommandContext): R | Promise<R>\n}\n\nexport function defineCommand<I extends InputSchema, R>(definition: CommandDefinition<I, R>): Command<I, R> {\n const d = definition as Partial<CommandDefinition<I, R>>\n if (typeof d.description !== \"string\" || d.description.trim() === \"\") {\n throw new TypeError(\"defineCommand: description must be a non-empty string\")\n }\n if (!isObjectSchema(d.input)) {\n throw new TypeError(\"defineCommand: input must be a top-level z.object schema\")\n }\n if (typeof d.execute !== \"function\") {\n throw new TypeError(\"defineCommand: execute must be a function\")\n }\n const shapeKeys = Object.keys(d.input.shape)\n for (const key of d.positional ?? []) {\n if (!shapeKeys.includes(key)) {\n throw new TypeError(`defineCommand: positional \"${key}\" is not an input key`)\n }\n }\n const task = taskOptions(d.task)\n const execute = d.execute\n return Object.freeze({\n [COMMAND]: true as const,\n description: d.description,\n input: d.input,\n output: d.output,\n readOnly: d.readOnly === true,\n destructive: d.destructive === true,\n requiresConfirmation: d.requiresConfirmation === true,\n scopes: Object.freeze([...(d.scopes ?? [])]),\n examples: Object.freeze([...(d.examples ?? [])]),\n positional: Object.freeze([...(d.positional ?? [])]),\n aliases: Object.freeze([...(d.aliases ?? [])]),\n ...(task ? { task } : {}),\n execute(input: z.output<I>, ctx: CommandContext) {\n return execute.call(definition, input, ctx)\n },\n })\n}\n\n/** task: true | { ttlMs } → TaskOptions. false · undefined = 없음, 그 외 = TypeError */\nfunction taskOptions(value: unknown): TaskOptions | undefined {\n if (value === undefined || value === false) return undefined\n if (value === true) return Object.freeze({ ttlMs: DEFAULT_TASK_TTL_MS })\n const ttlMs = (value as { ttlMs?: unknown } | null)?.ttlMs\n if (typeof value !== \"object\" || !Number.isSafeInteger(ttlMs) || (ttlMs as number) <= 0) {\n throw new TypeError(\"defineCommand: task must be true or { ttlMs: <positive integer> }\")\n }\n return Object.freeze({ ttlMs: ttlMs as number })\n}\n\nexport function isCommand(value: unknown): value is Command {\n return typeof value === \"object\" && value !== null && (value as Record<symbol, unknown>)[COMMAND] === true\n}\n\nfunction isObjectSchema(value: unknown): value is InputSchema {\n const def = (value as { _zod?: { def?: { type?: unknown } } } | undefined)?._zod?.def\n return def?.type === \"object\" && typeof (value as { safeParseAsync?: unknown }).safeParseAsync === \"function\"\n}\n"]}
|
|
1
|
+
{"version":3,"file":"command.js","sourceRoot":"","sources":["../src/command.ts"],"names":[],"mappings":"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","sourcesContent":["import type * as z from \"zod\"\nimport { commandAuditProblem, type AuditTarget } from \"./audit.ts\"\nimport { exposeProblem, type Caller, type Exposure } from \"./caller.ts\"\nimport type { Principal } from \"./principal.ts\"\n\n/**\n * 도입 프로젝트가 declaration merging 으로 채우는 ctx.app 타입.\n *\n * declare module \"@obov/mext\" { interface MextApp { db: Db } }\n */\nexport interface MextApp {}\n\nexport interface ProgressUpdate {\n progress: number\n total?: number\n message?: string\n}\n\nexport interface ConfirmRequest {\n kind: \"confirm\"\n message: string\n}\n\nexport interface InputRequest<S extends z.ZodObject = z.ZodObject> {\n kind: \"input\"\n message: string\n schema: S\n}\n\n/** URL 모드 (spec §12.4). 제3자 인가 · API 키 · 결제처럼 클라이언트를 거치면 안 되는 입력. url = http(s) 절대 URL */\nexport interface UrlRequest {\n kind: \"url\"\n message: string\n url: string\n}\n\nexport type AskRequest = ConfirmRequest | InputRequest | UrlRequest\n\n/** ctx.ask. confirm = boolean, input = schema 로 검증된 값, url = boolean (accept = URL 열기 동의. 완료 의미 아님) */\nexport interface Ask {\n (request: ConfirmRequest): Promise<boolean>\n (request: UrlRequest): Promise<boolean>\n <S extends z.ZodObject>(request: InputRequest<S>): Promise<z.output<S>>\n}\n\nexport interface CommandContext {\n principal: Principal\n /**\n * 호출자 맥락 (spec §4): 표면 (surface · transport) · 행위자 (actor) · client.\n * surface · actor = 서버가 정한 값, client = 자가 신고 값 (판정 근거로 쓰지 않는다)\n */\n caller: Caller\n signal: AbortSignal\n progress(update: ProgressUpdate): void\n ask: Ask\n app: MextApp\n /**\n * 자원 갱신 알림 (§12.5) → 구독 중인 subscriptions/listen 스트림에 notifications/resources/updated.\n * state 없음 · 구독 없음 · 로컬 CLI 실행 = no-op. bus 발행 실패 = stderr 1줄 (거부하지 않음)\n */\n resourceUpdated(uri: string): Promise<void>\n}\n\nexport type InputSchema = z.ZodObject\n\ntype InputKey<I extends InputSchema> = keyof z.input<I> & string\n\n/**\n * 명령어의 감사 옵션 (spec §11.1). config.audit.sink 가 있을 때만 쓰인다.\n * result = 핸들러 반환값 (output 파싱 후). 실패 · 거부 = undefined.\n * 타입이 unknown 인 이유: 반환 타입으로 두면 audit 을 execute 앞에 쓸 때 반환 타입 추론이 unknown 으로 굳는다\n */\nexport interface CommandAudit<I extends InputSchema = InputSchema> {\n /** 대상. type · id 외 값 (버전 해시 등) 을 자유롭게 싣는다. undefined = 대상 없음 */\n target?: (input: z.output<I>, result: unknown) => AuditTarget | undefined\n /** 사람이 읽는 한 줄 */\n summary?: string | ((input: z.output<I>, result: unknown) => string | undefined)\n /** 입력 기록. 함수 = 가린 값 (실행 전에 계산), false = 기록하지 않는다. 기본 = 검증된 입력 그대로 */\n input?: boolean | ((input: z.output<I>) => unknown)\n}\n\nexport interface CommandDefinition<I extends InputSchema, R, O extends z.ZodType = z.ZodType> {\n /** 필수. help · MCP 설명의 원본 */\n description: string\n /** 필수. 최상위 z.object */\n input: I\n /** 선택. MCP outputSchema 의 원본 (tools: \"command\"). 있으면 모든 어댑터가 반환값을 검증 (실패 = EXECUTION_ERROR) */\n output?: O\n readOnly?: boolean\n destructive?: boolean\n /** needsApproval: true 와 같다 (이전 이름). 둘 중 하나라도 필요하면 필요 */\n requiresConfirmation?: boolean\n /**\n * 사람의 승인이 필요한 호출 (spec §4 승인). true = 항상. 조건식 = false 를 돌려줄 때만 승인 불필요\n * (그 외 반환값 · 예외 = 승인 필요, 예외는 onCommandError 보고). 입력 검증 · authorize 뒤, execute 전에 판정한다.\n * 승인이 필요하면 표면의 확인 방식 (mext.config.ts approval.surfaces, 기본 ask) 으로 확인한다:\n * ask = ctx.ask (CLI 프롬프트 · --yes, MCP MRTR), client = 요청 _meta 의 확인 표시 + human 행위자.\n * 서버가 agent 로 정한 행위자는 승인할 수 없다 (APPROVAL_REQUIRED). MRTR 재시도마다 다시 판정되므로 부작용 없는 판정만\n */\n needsApproval?: boolean | ((input: z.output<I>, ctx: CommandContext) => boolean | Promise<boolean>)\n /** 실행에 필요한 scopes. 전부 보유해야 실행 */\n scopes?: readonly string[]\n examples?: readonly z.input<I>[]\n /** CLI positional 인자로 받을 input 키 순서 */\n positional?: readonly InputKey<I>[]\n /** 같은 네임스페이스 안 kebab-case 세그먼트. commands/users/get.ts 의 \"fetch\" → users.fetch */\n aliases?: readonly string[]\n /**\n * 장시간 명령어. 조건 (state.store · 클라이언트 선언) 이 맞으면 MCP tasks 확장으로 실행 (§12.6).\n * true = 기본 ttlMs (DEFAULT_TASK_TTL_MS). ttlMs = 종료 후 tasks/get 으로 조회할 수 있는 시간\n */\n task?: boolean | { ttlMs: number }\n /** 감사 이벤트의 대상 · 요약 · 입력 가림 (spec §11.1) */\n audit?: CommandAudit<I>\n /**\n * 표면별 노출 (spec §4). 기본 = 모든 표면. 숨긴 표면에서는 목록 (help · tools/list) 에 없고 호출 = UNKNOWN_COMMAND.\n * 판정 기준 = 요청 주체의 표면 (ctx.caller.surface: principal.surface, 없으면 어댑터 기본 cli · mcp · stdio)\n */\n expose?: Exposure\n /**\n * 실행 가드 (spec §4). 입력 검증 뒤 · 확인 (requiresConfirmation) 과 execute 전에 호출한다. scopes 검사는 그 전에 그대로.\n * true 만 통과. 그 외 반환값 (false · undefined · 반환 없음) · 예외 = PERMISSION_DENIED (reason \"authorize\").\n * 반환 타입이 boolean 이라 반환 없는 가드 · `return user?.isAdmin` (boolean | undefined) 은 컴파일 오류다.\n * CommandError 를 던지면 그 message 가 거부 message. 그 외 예외 = 거부 + onCommandError 보고.\n * ctx = execute 와 같은 ctx (ctx.caller 의 표면 · 행위자). MRTR 재시도마다 다시 호출되므로 부작용 없는 판정만\n */\n authorize?(input: z.output<I>, ctx: CommandContext): boolean | Promise<boolean>\n execute(input: z.output<I>, ctx: CommandContext): R | Promise<R>\n}\n\n/** task 명령어의 기본 ttlMs (1시간) */\nexport const DEFAULT_TASK_TTL_MS = 3_600_000\n\n/** defineCommand 의 task 정규형 */\nexport interface TaskOptions {\n ttlMs: number\n}\n\nexport const COMMAND: unique symbol = Symbol.for(\"@obov/mext/command\") as never\n\nexport interface Command<I extends InputSchema = InputSchema, R = unknown, O extends z.ZodType = z.ZodType> {\n readonly [COMMAND]: true\n readonly description: string\n readonly input: I\n readonly output: O | undefined\n readonly readOnly: boolean\n readonly destructive: boolean\n /** 항상 승인 필요 (requiresConfirmation: true · needsApproval: true) */\n readonly requiresConfirmation: boolean\n /**\n * 승인 조건식 (needsApproval 이 함수일 때만. 항상 필요 = requiresConfirmation). manifest · help = needsApproval: \"conditional\".\n * lazy Registry 의 메타데이터 명령어 = 호출할 수 없는 자리 표시 (모듈 로드 후의 명령어에서 부른다)\n */\n needsApproval?(input: z.output<I>, ctx: CommandContext): boolean | Promise<boolean>\n readonly scopes: readonly string[]\n readonly examples: readonly z.input<I>[]\n readonly positional: readonly string[]\n readonly aliases: readonly string[]\n /** task 명령어 (§12.6). 없음 = 항상 동기 */\n readonly task?: TaskOptions\n /** 감사 옵션 (§11.1). 없음 = 기본 (검증된 입력 그대로, 대상 · 요약 없음) */\n readonly audit?: Readonly<CommandAudit<I>>\n /** 표면별 노출 (§4). 없음 = 모든 표면 */\n readonly expose?: Exposure\n /** 실행 가드 (§4). 없음 = 가드 없음. lazy Registry 의 메타데이터 명령어에는 없다 (모듈 로드 후의 명령어에서 읽는다) */\n authorize?(input: z.output<I>, ctx: CommandContext): boolean | Promise<boolean>\n execute(input: z.output<I>, ctx: CommandContext): R | Promise<R>\n}\n\nexport function defineCommand<I extends InputSchema, R, O extends z.ZodType = z.ZodType>(\n definition: CommandDefinition<I, R, O>,\n): Command<I, R, O> {\n const d = definition as Partial<CommandDefinition<I, R, O>>\n if (typeof d.description !== \"string\" || d.description.trim() === \"\") {\n throw new TypeError(\"defineCommand: description must be a non-empty string\")\n }\n if (!isObjectSchema(d.input)) {\n throw new TypeError(\"defineCommand: input must be a top-level z.object schema\")\n }\n if (typeof d.execute !== \"function\") {\n throw new TypeError(\"defineCommand: execute must be a function\")\n }\n const shapeKeys = Object.keys(d.input.shape)\n for (const key of d.positional ?? []) {\n if (!shapeKeys.includes(key)) {\n throw new TypeError(`defineCommand: positional \"${key}\" is not an input key`)\n }\n }\n const task = taskOptions(d.task)\n if (d.audit !== undefined) {\n const problem = commandAuditProblem(d.audit)\n if (problem !== undefined) throw new TypeError(`defineCommand: ${problem}`)\n }\n const audit = d.audit === undefined ? undefined : Object.freeze({ ...d.audit })\n if (d.expose !== undefined) {\n const problem = exposeProblem(d.expose)\n if (problem !== undefined) throw new TypeError(`defineCommand: ${problem}`)\n }\n // 빈 객체 = 규칙 없음 (없는 것과 같다)\n const expose = d.expose === undefined || Object.keys(d.expose).length === 0 ? undefined : (Object.freeze({ ...d.expose }) as Exposure)\n if (d.authorize !== undefined && typeof d.authorize !== \"function\") {\n throw new TypeError(\"defineCommand: authorize must be a function\")\n }\n const authorize = d.authorize\n if (d.needsApproval !== undefined && typeof d.needsApproval !== \"boolean\" && typeof d.needsApproval !== \"function\") {\n throw new TypeError(\"defineCommand: needsApproval must be a boolean or a function\")\n }\n // 항상 필요 = requiresConfirmation (정규형). 조건식은 항상 필요가 아닐 때만 남긴다\n const alwaysApproval = d.requiresConfirmation === true || d.needsApproval === true\n const approvalCondition = !alwaysApproval && typeof d.needsApproval === \"function\" ? d.needsApproval : undefined\n const execute = d.execute\n return Object.freeze({\n [COMMAND]: true as const,\n description: d.description,\n input: d.input,\n output: d.output,\n readOnly: d.readOnly === true,\n destructive: d.destructive === true,\n requiresConfirmation: alwaysApproval,\n ...(approvalCondition\n ? {\n needsApproval(input: z.output<I>, ctx: CommandContext) {\n return approvalCondition.call(definition, input, ctx)\n },\n }\n : {}),\n scopes: Object.freeze([...(d.scopes ?? [])]),\n examples: Object.freeze([...(d.examples ?? [])]),\n positional: Object.freeze([...(d.positional ?? [])]),\n aliases: Object.freeze([...(d.aliases ?? [])]),\n ...(task ? { task } : {}),\n ...(audit ? { audit } : {}),\n ...(expose ? { expose } : {}),\n ...(authorize\n ? {\n authorize(input: z.output<I>, ctx: CommandContext) {\n return authorize.call(definition, input, ctx)\n },\n }\n : {}),\n execute(input: z.output<I>, ctx: CommandContext) {\n return execute.call(definition, input, ctx)\n },\n })\n}\n\n/** task: true | { ttlMs } → TaskOptions. false · undefined = 없음, 그 외 = TypeError */\nfunction taskOptions(value: unknown): TaskOptions | undefined {\n if (value === undefined || value === false) return undefined\n if (value === true) return Object.freeze({ ttlMs: DEFAULT_TASK_TTL_MS })\n const ttlMs = (value as { ttlMs?: unknown } | null)?.ttlMs\n if (typeof value !== \"object\" || !Number.isSafeInteger(ttlMs) || (ttlMs as number) <= 0) {\n throw new TypeError(\"defineCommand: task must be true or { ttlMs: <positive integer> }\")\n }\n return Object.freeze({ ttlMs: ttlMs as number })\n}\n\nexport function isCommand(value: unknown): value is Command {\n return typeof value === \"object\" && value !== null && (value as Record<symbol, unknown>)[COMMAND] === true\n}\n\nfunction isObjectSchema(value: unknown): value is InputSchema {\n const def = (value as { _zod?: { def?: { type?: unknown } } } | undefined)?._zod?.def\n return def?.type === \"object\" && typeof (value as { safeParseAsync?: unknown }).safeParseAsync === \"function\"\n}\n"]}
|
package/dist/config.d.ts
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
import { type ApprovalConfig } from "./approval.ts";
|
|
2
|
+
import { type AuditConfig } from "./audit.ts";
|
|
1
3
|
import type { CliErrorFormatter } from "./cli/output.ts";
|
|
2
4
|
import type { MextApp } from "./command.ts";
|
|
3
|
-
import type { OAuthResourceOptions } from "./mcp/http.ts";
|
|
5
|
+
import type { McpAuthRequest, OAuthResourceOptions } from "./mcp/http.ts";
|
|
4
6
|
import { type Principal } from "./principal.ts";
|
|
7
|
+
import { type RouteOptions } from "./route.ts";
|
|
5
8
|
import { type MextState } from "./state.ts";
|
|
6
9
|
/** mext.config.ts 의 default export */
|
|
7
10
|
export interface MextConfig {
|
|
@@ -11,23 +14,38 @@ export interface MextConfig {
|
|
|
11
14
|
resourcesDir?: string;
|
|
12
15
|
/** MCP 프롬프트 폴더 (§12.3). 설정 파일 위치 (cwd) 기준. 기본 "prompts". 폴더 없음 = 프롬프트 없음 */
|
|
13
16
|
promptsDir?: string;
|
|
17
|
+
/**
|
|
18
|
+
* 명령어 타입 생성 파일 (specs/client.md §4). true = ./mext.gen.ts, 문자열 = 그 경로 (프로젝트 루트 기준).
|
|
19
|
+
* 있으면 mext dev · mext build 가 생성한다. 없음 · false = 만들지 않는다 (mext types 는 설정 없이도 동작)
|
|
20
|
+
*/
|
|
21
|
+
types?: boolean | string;
|
|
14
22
|
/** ctx.app 팩토리. 실행 프로세스당 1회 */
|
|
15
23
|
app?: () => MextApp | Promise<MextApp>;
|
|
16
|
-
/**
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
24
|
+
/**
|
|
25
|
+
* HTTP 전용. 요청마다 주체 생성. 잘못된 토큰 = unauthenticated() throw.
|
|
26
|
+
* req = { authorization, headers, surface, client } (McpAuthRequest). client 는 자가 신고 값 (판정 근거 아님).
|
|
27
|
+
* 반환 Principal 의 actor · surface 가 ctx.caller 의 행위자 · 논리 표면을 정한다 (spec §4)
|
|
28
|
+
*/
|
|
29
|
+
auth?: (req: McpAuthRequest) => Principal | Promise<Principal>;
|
|
20
30
|
/** MCP 노출 방식. 기본 "execute". command = 명령어별 도구 (§12.2) */
|
|
21
31
|
tools?: "execute" | "split" | "command";
|
|
32
|
+
/**
|
|
33
|
+
* HTTP 서버. maxBodyBytes = POST /mcp 본문 상한 (기본 4 MiB). routes/ 는 상한 없음 (route 의 export maxBodyBytes = true 가 이 값을 쓴다)
|
|
34
|
+
*/
|
|
22
35
|
server?: {
|
|
23
36
|
host?: string;
|
|
24
37
|
port?: number;
|
|
25
38
|
allowedOrigins?: string[];
|
|
39
|
+
allowedHosts?: string[];
|
|
40
|
+
maxBodyBytes?: number;
|
|
26
41
|
};
|
|
27
42
|
/** HTTP 전용. OAuth 보호 자원 메타데이터 (인가 서버 위치). mext login · MCP 클라이언트의 discovery */
|
|
28
43
|
oauth?: OAuthResourceOptions;
|
|
29
|
-
/**
|
|
30
|
-
|
|
44
|
+
/**
|
|
45
|
+
* 로컬 실행 (mext run · 로컬 bin · stdio) 주체. 기본 localPrincipal().
|
|
46
|
+
* 함수 인자 surface = 어댑터 기본 표면 (mext run · 로컬 bin = "cli", stdio = "stdio"): 표면별로 actor 를 다르게 정할 수 있다
|
|
47
|
+
*/
|
|
48
|
+
localPrincipal?: Principal | ((req: LocalPrincipalRequest) => Principal | Promise<Principal>);
|
|
31
49
|
/** 고객 CLI (mext build → .mext/cli/). 없으면 고객 CLI 를 만들지 않는다 */
|
|
32
50
|
cli?: CliConfig;
|
|
33
51
|
/** MCP 서버 선언 (serverInfo.name · instructions · capabilities) · stdio 서버 발신 알림 (§6) */
|
|
@@ -44,6 +62,26 @@ export interface MextConfig {
|
|
|
44
62
|
state?: MextState;
|
|
45
63
|
/** HTTP 경로 폴더 (§12.4). 설정 파일 위치 (cwd) 기준. 기본 "routes". 폴더 없음 = route 없음 */
|
|
46
64
|
routesDir?: string;
|
|
65
|
+
/**
|
|
66
|
+
* 모든 route 의 auth · origin · maxBodyBytes 기본값 (§12.4). route 모듈의 export 가 있으면 그 값 (false 포함).
|
|
67
|
+
* 없음 = route 마다 export 로 켠다 (기본 = 인증 · Origin 검사 · 본문 상한 없음)
|
|
68
|
+
*/
|
|
69
|
+
routes?: RouteOptions;
|
|
70
|
+
/**
|
|
71
|
+
* 감사 (§11.1). sink = 변경 명령어 호출 1건마다 AuditEvent 1건 (모든 어댑터). 없으면 감사 없음.
|
|
72
|
+
* mext 는 저장소에 의존하지 않는다: 저장 · 조회는 sink 가 한다
|
|
73
|
+
*/
|
|
74
|
+
audit?: AuditConfig;
|
|
75
|
+
/**
|
|
76
|
+
* 승인의 표면별 확인 방식 (§4 승인). 없으면 모든 표면 ask (ctx.ask 확인 = requiresConfirmation 의 기존 동작).
|
|
77
|
+
* surfaces: { web: "client" } = web 표면은 클라이언트가 확인받고 요청에 확인 표시를 싣는다
|
|
78
|
+
*/
|
|
79
|
+
approval?: ApprovalConfig;
|
|
80
|
+
}
|
|
81
|
+
/** localPrincipal 함수의 인자 */
|
|
82
|
+
export interface LocalPrincipalRequest {
|
|
83
|
+
/** 어댑터 기본 표면. mext run · 로컬 bin = "cli", stdio (mext dev --stdio · mext start --stdio · <bin> mcp serve) = "stdio" */
|
|
84
|
+
surface: "cli" | "stdio";
|
|
47
85
|
}
|
|
48
86
|
/** mext.config.ts 의 mcp (§6 서버 선언 · 서버 발신 알림) */
|
|
49
87
|
export interface McpConfig {
|
|
@@ -124,20 +162,31 @@ export declare function resolveResourcesDir(cwd: string, config: MextConfig): st
|
|
|
124
162
|
export declare function resolvePromptsDir(cwd: string, config: MextConfig): string;
|
|
125
163
|
/** routes/ 절대 경로 */
|
|
126
164
|
export declare function resolveRoutesDir(cwd: string, config: MextConfig): string;
|
|
127
|
-
/**
|
|
128
|
-
|
|
165
|
+
/**
|
|
166
|
+
* 로컬 실행 주체: config.localPrincipal (값 · 함수) 또는 localPrincipal().
|
|
167
|
+
* surface = 어댑터 기본 표면 (함수 인자). 함수 결과의 actor · surface 형식 오류 = TypeError
|
|
168
|
+
*/
|
|
169
|
+
export declare function resolveLocalPrincipal(config: MextConfig, surface?: LocalPrincipalRequest["surface"]): Promise<Principal>;
|
|
129
170
|
/**
|
|
130
171
|
* 서버 비밀: config.secret > MEXT_SECRET > undefined (프로세스별 임의 키).
|
|
131
172
|
* 환경변수 값이 짧으면 TypeError (config.secret 은 설정 로드 시 검사)
|
|
132
173
|
*/
|
|
133
174
|
export declare function resolveSecret(config: MextConfig, env?: Record<string, string | undefined>): string | undefined;
|
|
134
|
-
/** 서버 선언 (모든 transport): config.mcp 의 name · instructions · capabilities + secret (requestStateKey) + state. onConnect 는 stdio 만 따로 */
|
|
175
|
+
/** 서버 선언 (모든 transport): config.mcp 의 name · instructions · capabilities + secret (requestStateKey) + state + audit. onConnect 는 stdio 만 따로 */
|
|
135
176
|
export declare function mcpDeclarations(config: MextConfig, env?: Record<string, string | undefined>): {
|
|
136
177
|
name?: string;
|
|
137
178
|
instructions?: string;
|
|
138
179
|
capabilities?: McpCapabilities;
|
|
139
180
|
requestStateKey?: string;
|
|
140
181
|
state?: MextState;
|
|
182
|
+
audit?: AuditConfig;
|
|
183
|
+
approval?: ApprovalConfig;
|
|
184
|
+
};
|
|
185
|
+
/** HTTP 처리기의 server 옵션 (host · port 제외): allowedOrigins · allowedHosts · maxBodyBytes */
|
|
186
|
+
export declare function httpServerOptions(config: MextConfig): {
|
|
187
|
+
allowedOrigins?: string[];
|
|
188
|
+
allowedHosts?: string[];
|
|
189
|
+
maxBodyBytes?: number;
|
|
141
190
|
};
|
|
142
191
|
/** stdio 옵션 = 선언 + onConnect */
|
|
143
192
|
export declare function mcpStdioDeclarations(config: MextConfig, env?: Record<string, string | undefined>): ReturnType<typeof mcpDeclarations> & {
|