@obov/mext 0.0.0-stage → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/build-cli.d.ts +39 -0
- package/dist/build-cli.js +167 -0
- package/dist/build-cli.js.map +1 -0
- package/dist/build.d.ts +65 -0
- package/dist/build.js +252 -0
- package/dist/build.js.map +1 -0
- package/dist/cli/args.d.ts +107 -0
- package/dist/cli/args.js +621 -0
- package/dist/cli/args.js.map +1 -0
- package/dist/cli/bin.d.ts +2 -0
- package/dist/cli/bin.js +13 -0
- package/dist/cli/bin.js.map +1 -0
- package/dist/cli/build.d.ts +11 -0
- package/dist/cli/build.js +87 -0
- package/dist/cli/build.js.map +1 -0
- package/dist/cli/dev-lock.d.ts +23 -0
- package/dist/cli/dev-lock.js +68 -0
- package/dist/cli/dev-lock.js.map +1 -0
- package/dist/cli/dev-server.d.ts +20 -0
- package/dist/cli/dev-server.js +75 -0
- package/dist/cli/dev-server.js.map +1 -0
- package/dist/cli/dev.d.ts +21 -0
- package/dist/cli/dev.js +565 -0
- package/dist/cli/dev.js.map +1 -0
- package/dist/cli/help.d.ts +31 -0
- package/dist/cli/help.js +238 -0
- package/dist/cli/help.js.map +1 -0
- package/dist/cli/io.d.ts +26 -0
- package/dist/cli/io.js +74 -0
- package/dist/cli/io.js.map +1 -0
- package/dist/cli/login.d.ts +32 -0
- package/dist/cli/login.js +284 -0
- package/dist/cli/login.js.map +1 -0
- package/dist/cli/logout.d.ts +9 -0
- package/dist/cli/logout.js +105 -0
- package/dist/cli/logout.js.map +1 -0
- package/dist/cli/main.d.ts +19 -0
- package/dist/cli/main.js +77 -0
- package/dist/cli/main.js.map +1 -0
- package/dist/cli/mcp.d.ts +16 -0
- package/dist/cli/mcp.js +92 -0
- package/dist/cli/mcp.js.map +1 -0
- package/dist/cli/output.d.ts +78 -0
- package/dist/cli/output.js +229 -0
- package/dist/cli/output.js.map +1 -0
- package/dist/cli/remote.d.ts +17 -0
- package/dist/cli/remote.js +35 -0
- package/dist/cli/remote.js.map +1 -0
- package/dist/cli/run.d.ts +14 -0
- package/dist/cli/run.js +218 -0
- package/dist/cli/run.js.map +1 -0
- package/dist/cli/start.d.ts +8 -0
- package/dist/cli/start.js +37 -0
- package/dist/cli/start.js.map +1 -0
- package/dist/client/cache.d.ts +104 -0
- package/dist/client/cache.js +208 -0
- package/dist/client/cache.js.map +1 -0
- package/dist/client/main.d.ts +31 -0
- package/dist/client/main.js +101 -0
- package/dist/client/main.js.map +1 -0
- package/dist/client/manifest.d.ts +19 -0
- package/dist/client/manifest.js +68 -0
- package/dist/client/manifest.js.map +1 -0
- package/dist/client/runner.d.ts +52 -0
- package/dist/client/runner.js +563 -0
- package/dist/client/runner.js.map +1 -0
- package/dist/command.d.ts +75 -0
- package/dist/command.js +44 -0
- package/dist/command.js.map +1 -0
- package/dist/config.d.ts +111 -0
- package/dist/config.js +197 -0
- package/dist/config.js.map +1 -0
- package/dist/errors.d.ts +74 -0
- package/dist/errors.js +38 -0
- package/dist/errors.js.map +1 -0
- package/dist/ids.d.ts +6 -0
- package/dist/ids.js +12 -0
- package/dist/ids.js.map +1 -0
- package/dist/index.d.ts +46 -0
- package/dist/index.js +27 -0
- package/dist/index.js.map +1 -0
- package/dist/instrumentation.d.ts +97 -0
- package/dist/instrumentation.js +165 -0
- package/dist/instrumentation.js.map +1 -0
- package/dist/invoke.d.ts +64 -0
- package/dist/invoke.js +152 -0
- package/dist/invoke.js.map +1 -0
- package/dist/lookup.d.ts +9 -0
- package/dist/lookup.js +27 -0
- package/dist/lookup.js.map +1 -0
- package/dist/manifest.d.ts +68 -0
- package/dist/manifest.js +77 -0
- package/dist/manifest.js.map +1 -0
- package/dist/mcp/constants.d.ts +9 -0
- package/dist/mcp/constants.js +10 -0
- package/dist/mcp/constants.js.map +1 -0
- package/dist/mcp/dispatch.d.ts +66 -0
- package/dist/mcp/dispatch.js +249 -0
- package/dist/mcp/dispatch.js.map +1 -0
- package/dist/mcp/help.d.ts +57 -0
- package/dist/mcp/help.js +136 -0
- package/dist/mcp/help.js.map +1 -0
- package/dist/mcp/http.d.ts +96 -0
- package/dist/mcp/http.js +368 -0
- package/dist/mcp/http.js.map +1 -0
- package/dist/mcp/index.d.ts +15 -0
- package/dist/mcp/index.js +9 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/install.d.ts +17 -0
- package/dist/mcp/install.js +36 -0
- package/dist/mcp/install.js.map +1 -0
- package/dist/mcp/mrtr.d.ts +82 -0
- package/dist/mcp/mrtr.js +130 -0
- package/dist/mcp/mrtr.js.map +1 -0
- package/dist/mcp/server.d.ts +63 -0
- package/dist/mcp/server.js +210 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/stdio.d.ts +75 -0
- package/dist/mcp/stdio.js +222 -0
- package/dist/mcp/stdio.js.map +1 -0
- package/dist/principal.d.ts +16 -0
- package/dist/principal.js +24 -0
- package/dist/principal.js.map +1 -0
- package/dist/registry.d.ts +90 -0
- package/dist/registry.js +169 -0
- package/dist/registry.js.map +1 -0
- package/dist/remote/browser.d.ts +15 -0
- package/dist/remote/browser.js +47 -0
- package/dist/remote/browser.js.map +1 -0
- package/dist/remote/client.d.ts +113 -0
- package/dist/remote/client.js +312 -0
- package/dist/remote/client.js.map +1 -0
- package/dist/remote/credentials.d.ts +48 -0
- package/dist/remote/credentials.js +196 -0
- package/dist/remote/credentials.js.map +1 -0
- package/dist/remote/index.d.ts +8 -0
- package/dist/remote/index.js +5 -0
- package/dist/remote/index.js.map +1 -0
- package/dist/remote/oauth.d.ts +186 -0
- package/dist/remote/oauth.js +507 -0
- package/dist/remote/oauth.js.map +1 -0
- package/dist/scan.d.ts +34 -0
- package/dist/scan.js +155 -0
- package/dist/scan.js.map +1 -0
- package/dist/start.d.ts +63 -0
- package/dist/start.js +269 -0
- package/dist/start.js.map +1 -0
- package/dist/suggest.d.ts +7 -0
- package/dist/suggest.js +45 -0
- package/dist/suggest.js.map +1 -0
- package/dist/version.d.ts +4 -0
- package/dist/version.js +13 -0
- package/dist/version.js.map +1 -0
- package/package.json +49 -4
- package/README.md +0 -3
package/dist/mcp/http.js
ADDED
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
import { createServer } from "node:http";
|
|
2
|
+
import { Readable } from "node:stream";
|
|
3
|
+
import { createMcpHandler, isLegacyRequest } from "@modelcontextprotocol/server";
|
|
4
|
+
import { isMextError } from "../errors.js";
|
|
5
|
+
import { ANONYMOUS } from "../principal.js";
|
|
6
|
+
import { DEFAULT_HOST, DEFAULT_PORT, MCP_PATH } from "./constants.js";
|
|
7
|
+
import { createMcpServerFactory, principalAuthInfo } from "./server.js";
|
|
8
|
+
/** 요청 본문 상한 (SDK 기본과 같음) */
|
|
9
|
+
export const MAX_BODY_BYTES = 4 * 1024 * 1024;
|
|
10
|
+
/** RFC 9728 well-known 경로 */
|
|
11
|
+
export const PROTECTED_RESOURCE_METADATA_PATH = "/.well-known/oauth-protected-resource";
|
|
12
|
+
/**
|
|
13
|
+
* Streamable HTTP (2026-07-28) 의 transport 무관 처리 (Web Request → Response).
|
|
14
|
+
* 순서: 경로 (404) → Origin (403) → 메서드 (POST 외 405) → 본문 (413) → auth (401) → SDK createMcpHandler.
|
|
15
|
+
* SDK 가 요청별 버전 · 헤더 검증 (-32020) · server/discover · Mcp-Param-Command · 2025 레거시 stateless 를 처리.
|
|
16
|
+
* progressToken 있는 2026-07-28 요청 = SSE 응답 (진행률 알림), 그 외 = JSON.
|
|
17
|
+
*/
|
|
18
|
+
export function createMcpFetchHandler(options) {
|
|
19
|
+
const factory = createMcpServerFactory({
|
|
20
|
+
registry: options.registry,
|
|
21
|
+
...(options.app ? { app: options.app } : {}),
|
|
22
|
+
...(options.tools ? { tools: options.tools } : {}),
|
|
23
|
+
...(options.name ? { name: options.name } : {}),
|
|
24
|
+
...(options.instructions !== undefined ? { instructions: options.instructions } : {}),
|
|
25
|
+
...(options.capabilities ? { capabilities: options.capabilities } : {}),
|
|
26
|
+
...(options.requestStateKey !== undefined ? { requestStateKey: options.requestStateKey } : {}),
|
|
27
|
+
...(options.progressInterval !== undefined ? { progressInterval: options.progressInterval } : {}),
|
|
28
|
+
...(options.instrumentation ? { instrumentation: options.instrumentation } : {}),
|
|
29
|
+
...(options.manifestHash !== undefined ? { manifestHash: options.manifestHash } : {}),
|
|
30
|
+
transport: "http",
|
|
31
|
+
});
|
|
32
|
+
const report = reporter(options.onerror);
|
|
33
|
+
const sdkOptions = options.onerror ? { onerror: report } : {};
|
|
34
|
+
// 진행률 없는 요청 = JSON (auto: 관련 메시지가 없으면 단일 JSON), 진행률 있는 요청 = SSE
|
|
35
|
+
const jsonHandler = createMcpHandler(factory, { ...sdkOptions, responseMode: "auto" });
|
|
36
|
+
const sseHandler = createMcpHandler(factory, { ...sdkOptions, responseMode: "sse" });
|
|
37
|
+
const allowedOrigins = options.server?.allowedOrigins;
|
|
38
|
+
const handle = async (request) => {
|
|
39
|
+
const url = new URL(request.url);
|
|
40
|
+
const oauth = options.oauth;
|
|
41
|
+
if (oauth && (url.pathname === PROTECTED_RESOURCE_METADATA_PATH || url.pathname === protectedResourceMetadataPath())) {
|
|
42
|
+
if (request.method !== "GET")
|
|
43
|
+
return jsonResponse(405, { error: "method_not_allowed" }, { Allow: "GET" });
|
|
44
|
+
return jsonResponse(200, protectedResourceMetadata(oauth, url));
|
|
45
|
+
}
|
|
46
|
+
if (url.pathname !== MCP_PATH)
|
|
47
|
+
return jsonResponse(404, rpcError(-32_000, `Not found. The MCP endpoint is POST ${MCP_PATH}.`));
|
|
48
|
+
const origin = request.headers.get("origin") ?? undefined;
|
|
49
|
+
if (origin !== undefined && origin !== "" && !originAllowed(origin, allowedOrigins)) {
|
|
50
|
+
return jsonResponse(403, rpcError(-32_000, `Forbidden: origin ${origin} is not allowed.`));
|
|
51
|
+
}
|
|
52
|
+
if (request.method !== "POST") {
|
|
53
|
+
return jsonResponse(405, rpcError(-32_000, "Method not allowed."), { Allow: "POST" });
|
|
54
|
+
}
|
|
55
|
+
const body = await readBody(request.body, MAX_BODY_BYTES);
|
|
56
|
+
if (body === "too-large")
|
|
57
|
+
return jsonResponse(413, rpcError(-32_000, "Request body too large."));
|
|
58
|
+
const parsed = parseJson(body);
|
|
59
|
+
const id = requestId(parsed);
|
|
60
|
+
let principal = ANONYMOUS;
|
|
61
|
+
if (options.auth) {
|
|
62
|
+
try {
|
|
63
|
+
principal = await options.auth({ authorization: request.headers.get("authorization") ?? undefined });
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
if (isMextError(error, "UNAUTHENTICATED")) {
|
|
67
|
+
return unauthenticatedResponse(error, id, oauth ? resourceMetadataUrl(oauth, url) : undefined);
|
|
68
|
+
}
|
|
69
|
+
report(error);
|
|
70
|
+
return jsonResponse(500, rpcError(-32_603, "Internal server error", undefined, id));
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
const sdkRequest = new Request(url, {
|
|
74
|
+
method: "POST",
|
|
75
|
+
headers: statelessHeaders(request.headers),
|
|
76
|
+
body,
|
|
77
|
+
signal: request.signal,
|
|
78
|
+
});
|
|
79
|
+
const parsedBody = parsed === NOT_JSON ? undefined : parsed;
|
|
80
|
+
// 2025-11-25 이하 (envelope 없음) = SDK stateless 레거시 경로. 진행률 기반 SSE 전환은 2026-07-28 요청만
|
|
81
|
+
const legacy = await isLegacyRequest(sdkRequest, parsedBody, { maxRequestBodySize: MAX_BODY_BYTES });
|
|
82
|
+
const handler = !legacy && hasProgressToken(parsedBody) ? sseHandler : jsonHandler;
|
|
83
|
+
return handler.fetch(sdkRequest, {
|
|
84
|
+
authInfo: principalAuthInfo(principal),
|
|
85
|
+
...(parsedBody !== undefined ? { parsedBody } : {}),
|
|
86
|
+
});
|
|
87
|
+
};
|
|
88
|
+
return {
|
|
89
|
+
async fetch(request) {
|
|
90
|
+
try {
|
|
91
|
+
return await handle(request);
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
report(error);
|
|
95
|
+
return jsonResponse(500, rpcError(-32_603, "Internal server error"));
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
async close() {
|
|
99
|
+
await Promise.all([jsonHandler.close(), sseHandler.close()]);
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
/** createMcpFetchHandler 의 node:http 어댑터. 응답 · SSE 를 스트리밍, 클라이언트 연결 종료 = request.signal abort */
|
|
104
|
+
export function createMcpHttpHandler(options) {
|
|
105
|
+
const core = createMcpFetchHandler(options);
|
|
106
|
+
const report = reporter(options.onerror);
|
|
107
|
+
const inflight = new Set();
|
|
108
|
+
const listener = ((req, res) => {
|
|
109
|
+
const controller = new AbortController();
|
|
110
|
+
inflight.add(controller);
|
|
111
|
+
res.on("close", () => {
|
|
112
|
+
if (!res.writableFinished)
|
|
113
|
+
controller.abort();
|
|
114
|
+
inflight.delete(controller);
|
|
115
|
+
});
|
|
116
|
+
const hasBody = req.method !== "GET" && req.method !== "HEAD";
|
|
117
|
+
const request = new Request(requestUrl(req), {
|
|
118
|
+
method: req.method ?? "GET",
|
|
119
|
+
headers: nodeHeaders(req),
|
|
120
|
+
...(hasBody ? { body: Readable.toWeb(req), duplex: "half" } : {}),
|
|
121
|
+
signal: controller.signal,
|
|
122
|
+
});
|
|
123
|
+
core
|
|
124
|
+
.fetch(request)
|
|
125
|
+
.then((response) => sendResponse(res, response))
|
|
126
|
+
.catch((error) => {
|
|
127
|
+
report(error);
|
|
128
|
+
if (!res.headersSent && !res.destroyed)
|
|
129
|
+
sendJson(res, 500, rpcError(-32_603, "Internal server error"));
|
|
130
|
+
else
|
|
131
|
+
res.destroy();
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
listener.close = async () => {
|
|
135
|
+
for (const c of inflight)
|
|
136
|
+
c.abort();
|
|
137
|
+
inflight.clear();
|
|
138
|
+
await core.close();
|
|
139
|
+
};
|
|
140
|
+
return listener;
|
|
141
|
+
}
|
|
142
|
+
/** 응답에 영향 없는 오류 보고 (보고 자체의 예외는 삼킨다) */
|
|
143
|
+
function reporter(onerror) {
|
|
144
|
+
return (error) => {
|
|
145
|
+
try {
|
|
146
|
+
onerror?.(error instanceof Error ? error : new Error(String(error)));
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
// 보고는 응답을 바꾸지 않는다
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
/** createMcpHttpHandler + node:http listen. 기본 127.0.0.1:3100 */
|
|
154
|
+
export async function startMcpHttpServer(options) {
|
|
155
|
+
const listener = createMcpHttpHandler(options);
|
|
156
|
+
const server = createServer(listener);
|
|
157
|
+
const host = options.server?.host ?? DEFAULT_HOST;
|
|
158
|
+
const port = options.server?.port ?? DEFAULT_PORT;
|
|
159
|
+
await new Promise((resolve, reject) => {
|
|
160
|
+
server.once("error", reject);
|
|
161
|
+
server.listen(port, host, () => {
|
|
162
|
+
server.off("error", reject);
|
|
163
|
+
resolve();
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
const address = server.address();
|
|
167
|
+
return {
|
|
168
|
+
url: `http://${formatHost(host)}:${address.port}${MCP_PATH}`,
|
|
169
|
+
host,
|
|
170
|
+
port: address.port,
|
|
171
|
+
async close() {
|
|
172
|
+
await listener.close();
|
|
173
|
+
server.closeAllConnections();
|
|
174
|
+
await new Promise((resolve) => server.close(() => resolve()));
|
|
175
|
+
},
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
/* Origin */
|
|
179
|
+
const LOOPBACK_HOSTNAMES = new Set(["localhost", "127.0.0.1", "[::1]"]);
|
|
180
|
+
/** Origin 허용 여부. allowed 미지정 = loopback 만, 지정 = 목록의 origin 과 정확히 일치 ("*" = 전체) */
|
|
181
|
+
export function originAllowed(origin, allowed) {
|
|
182
|
+
if (allowed?.includes("*"))
|
|
183
|
+
return true;
|
|
184
|
+
let url;
|
|
185
|
+
try {
|
|
186
|
+
url = new URL(origin);
|
|
187
|
+
}
|
|
188
|
+
catch {
|
|
189
|
+
return false;
|
|
190
|
+
}
|
|
191
|
+
if (url.protocol !== "http:" && url.protocol !== "https:")
|
|
192
|
+
return false;
|
|
193
|
+
if (allowed === undefined)
|
|
194
|
+
return LOOPBACK_HOSTNAMES.has(url.hostname);
|
|
195
|
+
return allowed.some((entry) => normalizeOrigin(entry) === url.origin);
|
|
196
|
+
}
|
|
197
|
+
function normalizeOrigin(entry) {
|
|
198
|
+
try {
|
|
199
|
+
return new URL(entry).origin;
|
|
200
|
+
}
|
|
201
|
+
catch {
|
|
202
|
+
return undefined;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
/* node:http ↔ fetch */
|
|
206
|
+
/** 무시하는 헤더 (stateless): 세션 · SSE 재개 */
|
|
207
|
+
const DROPPED_HEADERS = new Set(["mcp-session-id", "last-event-id"]);
|
|
208
|
+
function statelessHeaders(source) {
|
|
209
|
+
const headers = new Headers(source);
|
|
210
|
+
for (const name of DROPPED_HEADERS)
|
|
211
|
+
headers.delete(name);
|
|
212
|
+
return headers;
|
|
213
|
+
}
|
|
214
|
+
function nodeHeaders(req) {
|
|
215
|
+
const headers = new Headers();
|
|
216
|
+
for (const [name, value] of Object.entries(req.headers)) {
|
|
217
|
+
if (value === undefined || name.startsWith(":"))
|
|
218
|
+
continue;
|
|
219
|
+
if (Array.isArray(value))
|
|
220
|
+
for (const v of value)
|
|
221
|
+
headers.append(name, v);
|
|
222
|
+
else
|
|
223
|
+
headers.set(name, value);
|
|
224
|
+
}
|
|
225
|
+
return headers;
|
|
226
|
+
}
|
|
227
|
+
function requestUrl(req) {
|
|
228
|
+
try {
|
|
229
|
+
return new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
|
|
230
|
+
}
|
|
231
|
+
catch {
|
|
232
|
+
return new URL(req.url ?? "/", "http://localhost");
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
/** 본문을 limit 바이트까지. 넘으면 스트림 취소 후 "too-large" */
|
|
236
|
+
async function readBody(body, limit) {
|
|
237
|
+
if (body === null)
|
|
238
|
+
return new Uint8Array(0);
|
|
239
|
+
const chunks = [];
|
|
240
|
+
let size = 0;
|
|
241
|
+
const reader = body.getReader();
|
|
242
|
+
for (;;) {
|
|
243
|
+
const { done, value } = await reader.read();
|
|
244
|
+
if (done)
|
|
245
|
+
break;
|
|
246
|
+
size += value.byteLength;
|
|
247
|
+
if (size > limit) {
|
|
248
|
+
await reader.cancel().catch(() => { });
|
|
249
|
+
return "too-large";
|
|
250
|
+
}
|
|
251
|
+
chunks.push(value);
|
|
252
|
+
}
|
|
253
|
+
const out = new Uint8Array(size);
|
|
254
|
+
let offset = 0;
|
|
255
|
+
for (const c of chunks) {
|
|
256
|
+
out.set(c, offset);
|
|
257
|
+
offset += c.byteLength;
|
|
258
|
+
}
|
|
259
|
+
return out;
|
|
260
|
+
}
|
|
261
|
+
async function sendResponse(res, response) {
|
|
262
|
+
if (res.destroyed) {
|
|
263
|
+
await response.body?.cancel().catch(() => { });
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
res.statusCode = response.status;
|
|
267
|
+
response.headers.forEach((value, name) => res.setHeader(name, value));
|
|
268
|
+
if (response.body === null) {
|
|
269
|
+
res.end();
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
const reader = response.body.getReader();
|
|
273
|
+
// 클라이언트가 SSE 스트림을 닫으면 = 취소. SDK 가 transport 를 닫고 핸들러 signal 을 abort, 이후 메시지는 버린다
|
|
274
|
+
const onClose = () => {
|
|
275
|
+
if (!res.writableFinished)
|
|
276
|
+
void reader.cancel().catch(() => { });
|
|
277
|
+
};
|
|
278
|
+
res.on("close", onClose);
|
|
279
|
+
res.flushHeaders();
|
|
280
|
+
try {
|
|
281
|
+
for (;;) {
|
|
282
|
+
const { done, value } = await reader.read();
|
|
283
|
+
if (done || res.destroyed)
|
|
284
|
+
break;
|
|
285
|
+
res.write(value);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
catch {
|
|
289
|
+
// 스트림 취소 · 생산자 오류: 아래에서 응답을 닫는다
|
|
290
|
+
}
|
|
291
|
+
finally {
|
|
292
|
+
res.off("close", onClose);
|
|
293
|
+
}
|
|
294
|
+
if (!res.destroyed)
|
|
295
|
+
res.end();
|
|
296
|
+
}
|
|
297
|
+
function sendJson(res, status, body, headers = {}) {
|
|
298
|
+
const text = JSON.stringify(body);
|
|
299
|
+
res.writeHead(status, { "Content-Type": "application/json", "Content-Length": Buffer.byteLength(text), ...headers });
|
|
300
|
+
res.end(text);
|
|
301
|
+
}
|
|
302
|
+
function jsonResponse(status, body, headers = {}) {
|
|
303
|
+
return new Response(JSON.stringify(body), { status, headers: { "Content-Type": "application/json", ...headers } });
|
|
304
|
+
}
|
|
305
|
+
function unauthenticatedResponse(error, id, resourceMetadata) {
|
|
306
|
+
const params = [];
|
|
307
|
+
if (error.details.reason === "invalid_token")
|
|
308
|
+
params.push('error="invalid_token"');
|
|
309
|
+
if (resourceMetadata !== undefined)
|
|
310
|
+
params.push(`resource_metadata="${resourceMetadata}"`);
|
|
311
|
+
const challenge = params.length > 0 ? `Bearer ${params.join(", ")}` : "Bearer";
|
|
312
|
+
return jsonResponse(401, rpcError(-32_001, error.message, error.toJSON(), id), { "WWW-Authenticate": challenge });
|
|
313
|
+
}
|
|
314
|
+
/* OAuth 보호 자원 메타데이터 (RFC 9728) */
|
|
315
|
+
/** MCP 엔드포인트 경로를 삽입한 well-known 경로 (/.well-known/oauth-protected-resource/mcp) */
|
|
316
|
+
function protectedResourceMetadataPath() {
|
|
317
|
+
return `${PROTECTED_RESOURCE_METADATA_PATH}${MCP_PATH}`;
|
|
318
|
+
}
|
|
319
|
+
function resourceOf(oauth, url) {
|
|
320
|
+
return oauth.resource ?? `${url.origin}${MCP_PATH}`;
|
|
321
|
+
}
|
|
322
|
+
function resourceMetadataUrl(oauth, url) {
|
|
323
|
+
const resource = new URL(resourceOf(oauth, url));
|
|
324
|
+
const path = resource.pathname.replace(/\/$/, "");
|
|
325
|
+
return `${resource.origin}${PROTECTED_RESOURCE_METADATA_PATH}${path}`;
|
|
326
|
+
}
|
|
327
|
+
function protectedResourceMetadata(oauth, url) {
|
|
328
|
+
const doc = {
|
|
329
|
+
resource: resourceOf(oauth, url),
|
|
330
|
+
authorization_servers: [...oauth.authorizationServers],
|
|
331
|
+
bearer_methods_supported: ["header"],
|
|
332
|
+
};
|
|
333
|
+
if (oauth.scopesSupported)
|
|
334
|
+
doc.scopes_supported = [...oauth.scopesSupported];
|
|
335
|
+
return doc;
|
|
336
|
+
}
|
|
337
|
+
function rpcError(code, message, data, id = null) {
|
|
338
|
+
return { jsonrpc: "2.0", id, error: data === undefined ? { code, message } : { code, message, data } };
|
|
339
|
+
}
|
|
340
|
+
const NOT_JSON = Symbol("not-json");
|
|
341
|
+
function parseJson(body) {
|
|
342
|
+
if (body.length === 0)
|
|
343
|
+
return NOT_JSON;
|
|
344
|
+
try {
|
|
345
|
+
return JSON.parse(new TextDecoder().decode(body));
|
|
346
|
+
}
|
|
347
|
+
catch {
|
|
348
|
+
return NOT_JSON;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
function requestId(body) {
|
|
352
|
+
if (body === null || typeof body !== "object" || Array.isArray(body))
|
|
353
|
+
return null;
|
|
354
|
+
const { id, method } = body;
|
|
355
|
+
if (typeof method !== "string")
|
|
356
|
+
return null;
|
|
357
|
+
return typeof id === "string" || typeof id === "number" ? id : null;
|
|
358
|
+
}
|
|
359
|
+
function hasProgressToken(body) {
|
|
360
|
+
if (body === null || typeof body !== "object" || Array.isArray(body))
|
|
361
|
+
return false;
|
|
362
|
+
const meta = body.params?._meta;
|
|
363
|
+
return meta !== null && typeof meta === "object" && meta.progressToken !== undefined;
|
|
364
|
+
}
|
|
365
|
+
function formatHost(host) {
|
|
366
|
+
return host.includes(":") && !host.startsWith("[") ? `[${host}]` : host;
|
|
367
|
+
}
|
|
368
|
+
//# sourceMappingURL=http.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.js","sourceRoot":"","sources":["../../src/mcp/http.ts"],"names":[],"mappings":"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","sourcesContent":["import { createServer, type IncomingMessage, type ServerResponse } from \"node:http\"\nimport type { AddressInfo } from \"node:net\"\nimport { Readable } from \"node:stream\"\nimport { createMcpHandler, isLegacyRequest, type McpHttpHandler } from \"@modelcontextprotocol/server\"\nimport type { MextApp } from \"../command.ts\"\nimport { isMextError, type MextError } from \"../errors.ts\"\nimport { ANONYMOUS, type Principal } from \"../principal.ts\"\nimport type { Registry } from \"../registry.ts\"\nimport { DEFAULT_HOST, DEFAULT_PORT, MCP_PATH } from \"./constants.ts\"\nimport type { Instrumentation } from \"../instrumentation.ts\"\nimport type { ToolExposure } from \"./dispatch.ts\"\nimport { createMcpServerFactory, principalAuthInfo, type McpCoreOptions } from \"./server.ts\"\n\n/** 요청 본문 상한 (SDK 기본과 같음) */\nexport const MAX_BODY_BYTES = 4 * 1024 * 1024\n\nexport interface McpAuthRequest {\n /** Authorization 헤더 원문 */\n authorization: string | undefined\n}\n\nexport interface McpHttpOptions {\n registry: Registry\n app?: () => MextApp | Promise<MextApp>\n /**\n * 요청마다 Authorization → Principal. 없으면 모든 요청 = ANONYMOUS (readOnly 명령어만).\n * 잘못된 토큰은 unauthenticated() 를 던진다 → HTTP 401. anonymous 로 낮추지 않는다\n */\n auth?: (req: McpAuthRequest) => Principal | Promise<Principal>\n tools?: ToolExposure\n server?: {\n /** 기본 127.0.0.1 */\n host?: string\n /** 기본 3100. 0 = 임의 포트 */\n port?: number\n /**\n * Origin 허용 목록 (https://app.example.com 형식, \"*\" = 전체).\n * 미지정 = loopback (localhost · 127.0.0.1 · [::1], 포트 무관) 만. Origin 헤더 없는 요청은 항상 통과\n */\n allowedOrigins?: string[]\n }\n /** serverInfo.name · 안내 문구. 기본 \"mext\" */\n name?: string\n /** initialize · server/discover 결과 instructions */\n instructions?: string\n /** 서버 capabilities 에 병합 (experimental) */\n capabilities?: McpCoreOptions[\"capabilities\"]\n /** 응답에 영향 없는 오류 보고 (auth 예외 · SDK 내부 오류) */\n onerror?: (error: Error) => void\n /** MRTR requestState 서명 키. McpCoreOptions.requestStateKey */\n requestStateKey?: McpCoreOptions[\"requestStateKey\"]\n /** 주기 progress 간격 (ms). McpCoreOptions.progressInterval */\n progressInterval?: number\n /** loadInstrumentation 결과 (onCommandError · span, transport = \"http\") */\n instrumentation?: Instrumentation\n /** _meta[\"dev.mext/manifestHash\"]. McpCoreOptions.manifestHash (기본 = registry 로 계산) */\n manifestHash?: string\n /**\n * OAuth 보호 자원 메타데이터 (RFC 9728). 지정하면 GET /.well-known/oauth-protected-resource/mcp ·\n * /.well-known/oauth-protected-resource 응답, 401 의 WWW-Authenticate 에 resource_metadata.\n * 클라이언트 (mext login · MCP 클라이언트) 가 인가 서버를 찾는 데 쓴다\n */\n oauth?: OAuthResourceOptions\n}\n\nexport interface OAuthResourceOptions {\n /** 인가 서버 issuer URL (1개 이상) */\n authorizationServers: string[]\n scopesSupported?: string[]\n /** 토큰 대상 자원 ID (RFC 8707). 기본 = 요청 Host 기준 http://<host>/mcp (프록시 · TLS 뒤에서는 지정) */\n resource?: string\n}\n\n/** RFC 9728 well-known 경로 */\nexport const PROTECTED_RESOURCE_METADATA_PATH = \"/.well-known/oauth-protected-resource\"\n\nexport type McpHttpListener = ((req: IncomingMessage, res: ServerResponse) => void) & {\n /** 진행 중인 요청 abort · SDK handler 정리 */\n close(): Promise<void>\n}\n\n/** Web 표준 (Request → Response) MCP 처리기. handler.js · 다른 런타임 · node:http 어댑터가 공유 */\nexport interface McpFetchHandler {\n /** 거부하지 않는다 (내부 오류 = 500 JSON-RPC 오류). request.signal abort = 핸들러 ctx.signal abort */\n fetch(request: Request): Promise<Response>\n /** SDK handler 정리 */\n close(): Promise<void>\n}\n\n/**\n * Streamable HTTP (2026-07-28) 의 transport 무관 처리 (Web Request → Response).\n * 순서: 경로 (404) → Origin (403) → 메서드 (POST 외 405) → 본문 (413) → auth (401) → SDK createMcpHandler.\n * SDK 가 요청별 버전 · 헤더 검증 (-32020) · server/discover · Mcp-Param-Command · 2025 레거시 stateless 를 처리.\n * progressToken 있는 2026-07-28 요청 = SSE 응답 (진행률 알림), 그 외 = JSON.\n */\nexport function createMcpFetchHandler(options: McpHttpOptions): McpFetchHandler {\n const factory = createMcpServerFactory({\n registry: options.registry,\n ...(options.app ? { app: options.app } : {}),\n ...(options.tools ? { tools: options.tools } : {}),\n ...(options.name ? { name: options.name } : {}),\n ...(options.instructions !== undefined ? { instructions: options.instructions } : {}),\n ...(options.capabilities ? { capabilities: options.capabilities } : {}),\n ...(options.requestStateKey !== undefined ? { requestStateKey: options.requestStateKey } : {}),\n ...(options.progressInterval !== undefined ? { progressInterval: options.progressInterval } : {}),\n ...(options.instrumentation ? { instrumentation: options.instrumentation } : {}),\n ...(options.manifestHash !== undefined ? { manifestHash: options.manifestHash } : {}),\n transport: \"http\",\n })\n const report = reporter(options.onerror)\n const sdkOptions = options.onerror ? { onerror: report } : {}\n // 진행률 없는 요청 = JSON (auto: 관련 메시지가 없으면 단일 JSON), 진행률 있는 요청 = SSE\n const jsonHandler = createMcpHandler(factory, { ...sdkOptions, responseMode: \"auto\" })\n const sseHandler = createMcpHandler(factory, { ...sdkOptions, responseMode: \"sse\" })\n const allowedOrigins = options.server?.allowedOrigins\n\n const handle = async (request: Request): Promise<Response> => {\n const url = new URL(request.url)\n const oauth = options.oauth\n if (oauth && (url.pathname === PROTECTED_RESOURCE_METADATA_PATH || url.pathname === protectedResourceMetadataPath())) {\n if (request.method !== \"GET\") return jsonResponse(405, { error: \"method_not_allowed\" }, { Allow: \"GET\" })\n return jsonResponse(200, protectedResourceMetadata(oauth, url))\n }\n if (url.pathname !== MCP_PATH) return jsonResponse(404, rpcError(-32_000, `Not found. The MCP endpoint is POST ${MCP_PATH}.`))\n const origin = request.headers.get(\"origin\") ?? undefined\n if (origin !== undefined && origin !== \"\" && !originAllowed(origin, allowedOrigins)) {\n return jsonResponse(403, rpcError(-32_000, `Forbidden: origin ${origin} is not allowed.`))\n }\n if (request.method !== \"POST\") {\n return jsonResponse(405, rpcError(-32_000, \"Method not allowed.\"), { Allow: \"POST\" })\n }\n\n const body = await readBody(request.body, MAX_BODY_BYTES)\n if (body === \"too-large\") return jsonResponse(413, rpcError(-32_000, \"Request body too large.\"))\n const parsed = parseJson(body)\n const id = requestId(parsed)\n\n let principal: Principal = ANONYMOUS\n if (options.auth) {\n try {\n principal = await options.auth({ authorization: request.headers.get(\"authorization\") ?? undefined })\n } catch (error) {\n if (isMextError(error, \"UNAUTHENTICATED\")) {\n return unauthenticatedResponse(error, id, oauth ? resourceMetadataUrl(oauth, url) : undefined)\n }\n report(error)\n return jsonResponse(500, rpcError(-32_603, \"Internal server error\", undefined, id))\n }\n }\n\n const sdkRequest = new Request(url, {\n method: \"POST\",\n headers: statelessHeaders(request.headers),\n body,\n signal: request.signal,\n })\n const parsedBody = parsed === NOT_JSON ? undefined : parsed\n // 2025-11-25 이하 (envelope 없음) = SDK stateless 레거시 경로. 진행률 기반 SSE 전환은 2026-07-28 요청만\n const legacy = await isLegacyRequest(sdkRequest, parsedBody, { maxRequestBodySize: MAX_BODY_BYTES })\n const handler: McpHttpHandler = !legacy && hasProgressToken(parsedBody) ? sseHandler : jsonHandler\n return handler.fetch(sdkRequest, {\n authInfo: principalAuthInfo(principal),\n ...(parsedBody !== undefined ? { parsedBody } : {}),\n })\n }\n\n return {\n async fetch(request) {\n try {\n return await handle(request)\n } catch (error) {\n report(error)\n return jsonResponse(500, rpcError(-32_603, \"Internal server error\"))\n }\n },\n async close() {\n await Promise.all([jsonHandler.close(), sseHandler.close()])\n },\n }\n}\n\n/** createMcpFetchHandler 의 node:http 어댑터. 응답 · SSE 를 스트리밍, 클라이언트 연결 종료 = request.signal abort */\nexport function createMcpHttpHandler(options: McpHttpOptions): McpHttpListener {\n const core = createMcpFetchHandler(options)\n const report = reporter(options.onerror)\n const inflight = new Set<AbortController>()\n\n const listener = ((req: IncomingMessage, res: ServerResponse) => {\n const controller = new AbortController()\n inflight.add(controller)\n res.on(\"close\", () => {\n if (!res.writableFinished) controller.abort()\n inflight.delete(controller)\n })\n const hasBody = req.method !== \"GET\" && req.method !== \"HEAD\"\n const request = new Request(requestUrl(req), {\n method: req.method ?? \"GET\",\n headers: nodeHeaders(req),\n ...(hasBody ? { body: Readable.toWeb(req) as ReadableStream<Uint8Array>, duplex: \"half\" as const } : {}),\n signal: controller.signal,\n })\n core\n .fetch(request)\n .then((response) => sendResponse(res, response))\n .catch((error: unknown) => {\n report(error)\n if (!res.headersSent && !res.destroyed) sendJson(res, 500, rpcError(-32_603, \"Internal server error\"))\n else res.destroy()\n })\n }) as McpHttpListener\n listener.close = async () => {\n for (const c of inflight) c.abort()\n inflight.clear()\n await core.close()\n }\n return listener\n}\n\n/** 응답에 영향 없는 오류 보고 (보고 자체의 예외는 삼킨다) */\nfunction reporter(onerror: ((error: Error) => void) | undefined): (error: unknown) => void {\n return (error) => {\n try {\n onerror?.(error instanceof Error ? error : new Error(String(error)))\n } catch {\n // 보고는 응답을 바꾸지 않는다\n }\n }\n}\n\nexport interface McpHttpServer {\n /** http://<host>:<port>/mcp */\n url: string\n host: string\n port: number\n close(): Promise<void>\n}\n\n/** createMcpHttpHandler + node:http listen. 기본 127.0.0.1:3100 */\nexport async function startMcpHttpServer(options: McpHttpOptions): Promise<McpHttpServer> {\n const listener = createMcpHttpHandler(options)\n const server = createServer(listener)\n const host = options.server?.host ?? DEFAULT_HOST\n const port = options.server?.port ?? DEFAULT_PORT\n await new Promise<void>((resolve, reject) => {\n server.once(\"error\", reject)\n server.listen(port, host, () => {\n server.off(\"error\", reject)\n resolve()\n })\n })\n const address = server.address() as AddressInfo\n return {\n url: `http://${formatHost(host)}:${address.port}${MCP_PATH}`,\n host,\n port: address.port,\n async close() {\n await listener.close()\n server.closeAllConnections()\n await new Promise<void>((resolve) => server.close(() => resolve()))\n },\n }\n}\n\n/* Origin */\n\nconst LOOPBACK_HOSTNAMES = new Set([\"localhost\", \"127.0.0.1\", \"[::1]\"])\n\n/** Origin 허용 여부. allowed 미지정 = loopback 만, 지정 = 목록의 origin 과 정확히 일치 (\"*\" = 전체) */\nexport function originAllowed(origin: string, allowed: readonly string[] | undefined): boolean {\n if (allowed?.includes(\"*\")) return true\n let url: URL\n try {\n url = new URL(origin)\n } catch {\n return false\n }\n if (url.protocol !== \"http:\" && url.protocol !== \"https:\") return false\n if (allowed === undefined) return LOOPBACK_HOSTNAMES.has(url.hostname)\n return allowed.some((entry) => normalizeOrigin(entry) === url.origin)\n}\n\nfunction normalizeOrigin(entry: string): string | undefined {\n try {\n return new URL(entry).origin\n } catch {\n return undefined\n }\n}\n\n/* node:http ↔ fetch */\n\n/** 무시하는 헤더 (stateless): 세션 · SSE 재개 */\nconst DROPPED_HEADERS = new Set([\"mcp-session-id\", \"last-event-id\"])\n\nfunction statelessHeaders(source: Headers): Headers {\n const headers = new Headers(source)\n for (const name of DROPPED_HEADERS) headers.delete(name)\n return headers\n}\n\nfunction nodeHeaders(req: IncomingMessage): Headers {\n const headers = new Headers()\n for (const [name, value] of Object.entries(req.headers)) {\n if (value === undefined || name.startsWith(\":\")) continue\n if (Array.isArray(value)) for (const v of value) headers.append(name, v)\n else headers.set(name, value)\n }\n return headers\n}\n\nfunction requestUrl(req: IncomingMessage): URL {\n try {\n return new URL(req.url ?? \"/\", `http://${req.headers.host ?? \"localhost\"}`)\n } catch {\n return new URL(req.url ?? \"/\", \"http://localhost\")\n }\n}\n\n/** 본문을 limit 바이트까지. 넘으면 스트림 취소 후 \"too-large\" */\nasync function readBody(body: ReadableStream<Uint8Array> | null, limit: number): Promise<Uint8Array<ArrayBuffer> | \"too-large\"> {\n if (body === null) return new Uint8Array(0)\n const chunks: Uint8Array[] = []\n let size = 0\n const reader = body.getReader()\n for (;;) {\n const { done, value } = await reader.read()\n if (done) break\n size += value.byteLength\n if (size > limit) {\n await reader.cancel().catch(() => {})\n return \"too-large\"\n }\n chunks.push(value)\n }\n const out = new Uint8Array(size)\n let offset = 0\n for (const c of chunks) {\n out.set(c, offset)\n offset += c.byteLength\n }\n return out\n}\n\nasync function sendResponse(res: ServerResponse, response: Response): Promise<void> {\n if (res.destroyed) {\n await response.body?.cancel().catch(() => {})\n return\n }\n res.statusCode = response.status\n response.headers.forEach((value, name) => res.setHeader(name, value))\n if (response.body === null) {\n res.end()\n return\n }\n const reader = response.body.getReader()\n // 클라이언트가 SSE 스트림을 닫으면 = 취소. SDK 가 transport 를 닫고 핸들러 signal 을 abort, 이후 메시지는 버린다\n const onClose = () => {\n if (!res.writableFinished) void reader.cancel().catch(() => {})\n }\n res.on(\"close\", onClose)\n res.flushHeaders()\n try {\n for (;;) {\n const { done, value } = await reader.read()\n if (done || res.destroyed) break\n res.write(value)\n }\n } catch {\n // 스트림 취소 · 생산자 오류: 아래에서 응답을 닫는다\n } finally {\n res.off(\"close\", onClose)\n }\n if (!res.destroyed) res.end()\n}\n\nfunction sendJson(res: ServerResponse, status: number, body: unknown, headers: Record<string, string> = {}): void {\n const text = JSON.stringify(body)\n res.writeHead(status, { \"Content-Type\": \"application/json\", \"Content-Length\": Buffer.byteLength(text), ...headers })\n res.end(text)\n}\n\nfunction jsonResponse(status: number, body: unknown, headers: Record<string, string> = {}): Response {\n return new Response(JSON.stringify(body), { status, headers: { \"Content-Type\": \"application/json\", ...headers } })\n}\n\nfunction unauthenticatedResponse(\n error: MextError<\"UNAUTHENTICATED\">,\n id: string | number | null,\n resourceMetadata: string | undefined,\n): Response {\n const params: string[] = []\n if (error.details.reason === \"invalid_token\") params.push('error=\"invalid_token\"')\n if (resourceMetadata !== undefined) params.push(`resource_metadata=\"${resourceMetadata}\"`)\n const challenge = params.length > 0 ? `Bearer ${params.join(\", \")}` : \"Bearer\"\n return jsonResponse(401, rpcError(-32_001, error.message, error.toJSON(), id), { \"WWW-Authenticate\": challenge })\n}\n\n/* OAuth 보호 자원 메타데이터 (RFC 9728) */\n\n/** MCP 엔드포인트 경로를 삽입한 well-known 경로 (/.well-known/oauth-protected-resource/mcp) */\nfunction protectedResourceMetadataPath(): string {\n return `${PROTECTED_RESOURCE_METADATA_PATH}${MCP_PATH}`\n}\n\nfunction resourceOf(oauth: OAuthResourceOptions, url: URL): string {\n return oauth.resource ?? `${url.origin}${MCP_PATH}`\n}\n\nfunction resourceMetadataUrl(oauth: OAuthResourceOptions, url: URL): string {\n const resource = new URL(resourceOf(oauth, url))\n const path = resource.pathname.replace(/\\/$/, \"\")\n return `${resource.origin}${PROTECTED_RESOURCE_METADATA_PATH}${path}`\n}\n\nfunction protectedResourceMetadata(oauth: OAuthResourceOptions, url: URL): Record<string, unknown> {\n const doc: Record<string, unknown> = {\n resource: resourceOf(oauth, url),\n authorization_servers: [...oauth.authorizationServers],\n bearer_methods_supported: [\"header\"],\n }\n if (oauth.scopesSupported) doc.scopes_supported = [...oauth.scopesSupported]\n return doc\n}\n\nfunction rpcError(code: number, message: string, data?: unknown, id: string | number | null = null) {\n return { jsonrpc: \"2.0\", id, error: data === undefined ? { code, message } : { code, message, data } }\n}\n\nconst NOT_JSON = Symbol(\"not-json\")\n\nfunction parseJson(body: Uint8Array): unknown {\n if (body.length === 0) return NOT_JSON\n try {\n return JSON.parse(new TextDecoder().decode(body))\n } catch {\n return NOT_JSON\n }\n}\n\nfunction requestId(body: unknown): string | number | null {\n if (body === null || typeof body !== \"object\" || Array.isArray(body)) return null\n const { id, method } = body as { id?: unknown; method?: unknown }\n if (typeof method !== \"string\") return null\n return typeof id === \"string\" || typeof id === \"number\" ? id : null\n}\n\nfunction hasProgressToken(body: unknown): boolean {\n if (body === null || typeof body !== \"object\" || Array.isArray(body)) return false\n const meta = (body as { params?: { _meta?: { progressToken?: unknown } } }).params?._meta\n return meta !== null && typeof meta === \"object\" && meta.progressToken !== undefined\n}\n\nfunction formatHost(host: string): string {\n return host.includes(\":\") && !host.startsWith(\"[\") ? `[${host}]` : host\n}\n"]}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export { DEFAULT_HOST, DEFAULT_PORT, MANIFEST_HASH_META_KEY, MCP_PATH } from "./constants.ts";
|
|
2
|
+
export { callDispatcher, dispatcherInputSchema, dispatcherTools, errorResult, EXECUTION_ERROR, findDispatcherTool, } from "./dispatch.ts";
|
|
3
|
+
export type { DispatcherAnnotations, DispatcherTool, DispatcherToolName, DispatchOptions, DispatchResult, TextContent, ToolCallResult, ToolExposure, } from "./dispatch.ts";
|
|
4
|
+
export { formatHelp, help, HELP_COMMAND } from "./help.ts";
|
|
5
|
+
export type { CommandHelp, Help, HelpCommandSummary, HelpNamespaceSummary, HelpOptions, NamespaceHelp, RootHelp, } from "./help.ts";
|
|
6
|
+
export { createMcpServerFactory, principalAuthInfo, principalFromAuthInfo, PROGRESS_INTERVAL_MS, registryManifestHash, TOOLS_LIST_CACHE, } from "./server.ts";
|
|
7
|
+
export type { McpCoreOptions } from "./server.ts";
|
|
8
|
+
export { createMcpFetchHandler, createMcpHttpHandler, MAX_BODY_BYTES, originAllowed, PROTECTED_RESOURCE_METADATA_PATH, startMcpHttpServer, } from "./http.ts";
|
|
9
|
+
export type { McpAuthRequest, McpFetchHandler, McpHttpListener, McpHttpOptions, McpHttpServer, OAuthResourceOptions } from "./http.ts";
|
|
10
|
+
export { mcpInstallText } from "./install.ts";
|
|
11
|
+
export type { McpInstallOptions } from "./install.ts";
|
|
12
|
+
export { redirectStdout, serveMcpStdio } from "./stdio.ts";
|
|
13
|
+
export type { McpStdioOptions, McpStdioServer, StdoutRedirect } from "./stdio.ts";
|
|
14
|
+
export { ASK_KEY_PREFIX, CONFIRM_REQUESTED_SCHEMA, createAskSession, createRequestStateSigner, InputRequiredSignal, REQUEST_STATE_TTL_MS, } from "./mrtr.ts";
|
|
15
|
+
export type { AskSession, ElicitationRequest, InputRequiredResult, InputRound, RequestStateBinding, RequestStateSigner, RequestStateSignerOptions, } from "./mrtr.ts";
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { DEFAULT_HOST, DEFAULT_PORT, MANIFEST_HASH_META_KEY, MCP_PATH } from "./constants.js";
|
|
2
|
+
export { callDispatcher, dispatcherInputSchema, dispatcherTools, errorResult, EXECUTION_ERROR, findDispatcherTool, } from "./dispatch.js";
|
|
3
|
+
export { formatHelp, help, HELP_COMMAND } from "./help.js";
|
|
4
|
+
export { createMcpServerFactory, principalAuthInfo, principalFromAuthInfo, PROGRESS_INTERVAL_MS, registryManifestHash, TOOLS_LIST_CACHE, } from "./server.js";
|
|
5
|
+
export { createMcpFetchHandler, createMcpHttpHandler, MAX_BODY_BYTES, originAllowed, PROTECTED_RESOURCE_METADATA_PATH, startMcpHttpServer, } from "./http.js";
|
|
6
|
+
export { mcpInstallText } from "./install.js";
|
|
7
|
+
export { redirectStdout, serveMcpStdio } from "./stdio.js";
|
|
8
|
+
export { ASK_KEY_PREFIX, CONFIRM_REQUESTED_SCHEMA, createAskSession, createRequestStateSigner, InputRequiredSignal, REQUEST_STATE_TTL_MS, } from "./mrtr.js";
|
|
9
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/mcp/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,sBAAsB,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAC7F,OAAO,EACL,cAAc,EACd,qBAAqB,EACrB,eAAe,EACf,WAAW,EACX,eAAe,EACf,kBAAkB,GACnB,MAAM,eAAe,CAAA;AAWtB,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,YAAY,EAAE,MAAM,WAAW,CAAA;AAU1D,OAAO,EACL,sBAAsB,EACtB,iBAAiB,EACjB,qBAAqB,EACrB,oBAAoB,EACpB,oBAAoB,EACpB,gBAAgB,GACjB,MAAM,aAAa,CAAA;AAEpB,OAAO,EACL,qBAAqB,EACrB,oBAAoB,EACpB,cAAc,EACd,aAAa,EACb,gCAAgC,EAChC,kBAAkB,GACnB,MAAM,WAAW,CAAA;AAElB,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAE7C,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAE1D,OAAO,EACL,cAAc,EACd,wBAAwB,EACxB,gBAAgB,EAChB,wBAAwB,EACxB,mBAAmB,EACnB,oBAAoB,GACrB,MAAM,WAAW,CAAA","sourcesContent":["export { DEFAULT_HOST, DEFAULT_PORT, MANIFEST_HASH_META_KEY, MCP_PATH } from \"./constants.ts\"\nexport {\n callDispatcher,\n dispatcherInputSchema,\n dispatcherTools,\n errorResult,\n EXECUTION_ERROR,\n findDispatcherTool,\n} from \"./dispatch.ts\"\nexport type {\n DispatcherAnnotations,\n DispatcherTool,\n DispatcherToolName,\n DispatchOptions,\n DispatchResult,\n TextContent,\n ToolCallResult,\n ToolExposure,\n} from \"./dispatch.ts\"\nexport { formatHelp, help, HELP_COMMAND } from \"./help.ts\"\nexport type {\n CommandHelp,\n Help,\n HelpCommandSummary,\n HelpNamespaceSummary,\n HelpOptions,\n NamespaceHelp,\n RootHelp,\n} from \"./help.ts\"\nexport {\n createMcpServerFactory,\n principalAuthInfo,\n principalFromAuthInfo,\n PROGRESS_INTERVAL_MS,\n registryManifestHash,\n TOOLS_LIST_CACHE,\n} from \"./server.ts\"\nexport type { McpCoreOptions } from \"./server.ts\"\nexport {\n createMcpFetchHandler,\n createMcpHttpHandler,\n MAX_BODY_BYTES,\n originAllowed,\n PROTECTED_RESOURCE_METADATA_PATH,\n startMcpHttpServer,\n} from \"./http.ts\"\nexport type { McpAuthRequest, McpFetchHandler, McpHttpListener, McpHttpOptions, McpHttpServer, OAuthResourceOptions } from \"./http.ts\"\nexport { mcpInstallText } from \"./install.ts\"\nexport type { McpInstallOptions } from \"./install.ts\"\nexport { redirectStdout, serveMcpStdio } from \"./stdio.ts\"\nexport type { McpStdioOptions, McpStdioServer, StdoutRedirect } from \"./stdio.ts\"\nexport {\n ASK_KEY_PREFIX,\n CONFIRM_REQUESTED_SCHEMA,\n createAskSession,\n createRequestStateSigner,\n InputRequiredSignal,\n REQUEST_STATE_TTL_MS,\n} from \"./mrtr.ts\"\nexport type {\n AskSession,\n ElicitationRequest,\n InputRequiredResult,\n InputRound,\n RequestStateBinding,\n RequestStateSigner,\n RequestStateSignerOptions,\n} from \"./mrtr.ts\"\n"]}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export interface McpInstallOptions {
|
|
2
|
+
/** MCP 클라이언트에 등록할 서버 이름. 기본 "mext" */
|
|
3
|
+
name?: string;
|
|
4
|
+
/** Streamable HTTP 엔드포인트. 기본 http://127.0.0.1:3100/mcp */
|
|
5
|
+
url?: string;
|
|
6
|
+
/**
|
|
7
|
+
* stdio 실행 명령. 기본 "npx mext dev --stdio" (운영 = "npx mext start --stdio"). false = stdio 안내 생략 (고객 CLI 는 원격만)
|
|
8
|
+
*/
|
|
9
|
+
stdioCommand?: string | false;
|
|
10
|
+
/** 토큰이 필요한 서버면 Authorization 헤더 예시 포함 */
|
|
11
|
+
auth?: boolean;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* `mext mcp install` · `myapp mcp install` 의 안내 텍스트.
|
|
15
|
+
* MCP 클라이언트 등록 명령만 출력한다. 클라이언트 설정 파일은 쓰지 않는다.
|
|
16
|
+
*/
|
|
17
|
+
export declare function mcpInstallText(options?: McpInstallOptions): string;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { DEFAULT_HOST, DEFAULT_PORT, MCP_PATH } from "./constants.js";
|
|
2
|
+
/**
|
|
3
|
+
* `mext mcp install` · `myapp mcp install` 의 안내 텍스트.
|
|
4
|
+
* MCP 클라이언트 등록 명령만 출력한다. 클라이언트 설정 파일은 쓰지 않는다.
|
|
5
|
+
*/
|
|
6
|
+
export function mcpInstallText(options = {}) {
|
|
7
|
+
const name = options.name ?? "mext";
|
|
8
|
+
const url = options.url ?? `http://${DEFAULT_HOST}:${DEFAULT_PORT}${MCP_PATH}`;
|
|
9
|
+
const stdio = options.stdioCommand === undefined ? "npx mext dev --stdio" : options.stdioCommand;
|
|
10
|
+
const n = shellQuote(name);
|
|
11
|
+
const lines = [
|
|
12
|
+
`Register the "${name}" MCP server with your MCP client.`,
|
|
13
|
+
"",
|
|
14
|
+
"Claude Code (Streamable HTTP):",
|
|
15
|
+
` claude mcp add --transport http ${n} ${shellQuote(url)}`,
|
|
16
|
+
];
|
|
17
|
+
if (options.auth) {
|
|
18
|
+
lines.push(" # with an API token:", ` claude mcp add --transport http ${n} ${shellQuote(url)} --header "Authorization: Bearer <token>"`);
|
|
19
|
+
}
|
|
20
|
+
if (stdio !== false) {
|
|
21
|
+
lines.push("", "Claude Code (stdio, runs commands locally):", ` claude mcp add ${n} -- ${stdio}`);
|
|
22
|
+
if (options.stdioCommand === undefined)
|
|
23
|
+
lines.push(" # production (after mext build):", ` claude mcp add ${n} -- npx mext start --stdio`);
|
|
24
|
+
}
|
|
25
|
+
const config = { type: "http", url };
|
|
26
|
+
if (options.auth)
|
|
27
|
+
config.headers = { Authorization: "Bearer <token>" };
|
|
28
|
+
lines.push("", "Other MCP clients (JSON config):", ...JSON.stringify({ mcpServers: { [name]: config } }, null, 2)
|
|
29
|
+
.split("\n")
|
|
30
|
+
.map((l) => ` ${l}`), "", "This command only prints instructions; it does not modify client configuration files.");
|
|
31
|
+
return lines.join("\n");
|
|
32
|
+
}
|
|
33
|
+
function shellQuote(value) {
|
|
34
|
+
return /^[A-Za-z0-9_./:@%+=,-]+$/.test(value) ? value : `'${value.replaceAll("'", `'\\''`)}'`;
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=install.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"install.js","sourceRoot":"","sources":["../../src/mcp/install.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAerE;;;GAGG;AACH,MAAM,UAAU,cAAc,CAAC,OAAO,GAAsB,EAAE;IAC5D,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,IAAI,MAAM,CAAA;IACnC,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,UAAU,YAAY,IAAI,YAAY,GAAG,QAAQ,EAAE,CAAA;IAC9E,MAAM,KAAK,GAAG,OAAO,CAAC,YAAY,KAAK,SAAS,CAAC,CAAC,CAAC,sBAAsB,CAAC,CAAC,CAAC,OAAO,CAAC,YAAY,CAAA;IAChG,MAAM,CAAC,GAAG,UAAU,CAAC,IAAI,CAAC,CAAA;IAC1B,MAAM,KAAK,GAAG;QACZ,iBAAiB,IAAI,oCAAoC;QACzD,EAAE;QACF,gCAAgC;QAChC,qCAAqC,CAAC,IAAI,UAAU,CAAC,GAAG,CAAC,EAAE;KAC5D,CAAA;IACD,IAAI,OAAO,CAAC,IAAI,EAAE,CAAC;QACjB,KAAK,CAAC,IAAI,CACR,wBAAwB,EACxB,qCAAqC,CAAC,IAAI,UAAU,CAAC,GAAG,CAAC,2CAA2C,CACrG,CAAA;IACH,CAAC;IACD,IAAI,KAAK,KAAK,KAAK,EAAE,CAAC;QACpB,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,6CAA6C,EAAE,oBAAoB,CAAC,OAAO,KAAK,EAAE,CAAC,CAAA;QAClG,IAAI,OAAO,CAAC,YAAY,KAAK,SAAS;YAAE,KAAK,CAAC,IAAI,CAAC,oCAAoC,EAAE,oBAAoB,CAAC,4BAA4B,CAAC,CAAA;IAC7I,CAAC;IACD,MAAM,MAAM,GAA4B,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,CAAA;IAC7D,IAAI,OAAO,CAAC,IAAI;QAAE,MAAM,CAAC,OAAO,GAAG,EAAE,aAAa,EAAE,gBAAgB,EAAE,CAAA;IACtE,KAAK,CAAC,IAAI,CACR,EAAE,EACF,kCAAkC,EAClC,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,UAAU,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC;SAC3D,KAAK,CAAC,IAAI,CAAC;SACX,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,EACvB,EAAE,EACF,uFAAuF,CACxF,CAAA;IACD,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;AACzB,CAAC;AAED,SAAS,UAAU,CAAC,KAAa;IAC/B,OAAO,0BAA0B,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,UAAU,CAAC,GAAG,EAAE,OAAO,CAAC,GAAG,CAAA;AAC/F,CAAC","sourcesContent":["import { DEFAULT_HOST, DEFAULT_PORT, MCP_PATH } from \"./constants.ts\"\n\nexport interface McpInstallOptions {\n /** MCP 클라이언트에 등록할 서버 이름. 기본 \"mext\" */\n name?: string\n /** Streamable HTTP 엔드포인트. 기본 http://127.0.0.1:3100/mcp */\n url?: string\n /**\n * stdio 실행 명령. 기본 \"npx mext dev --stdio\" (운영 = \"npx mext start --stdio\"). false = stdio 안내 생략 (고객 CLI 는 원격만)\n */\n stdioCommand?: string | false\n /** 토큰이 필요한 서버면 Authorization 헤더 예시 포함 */\n auth?: boolean\n}\n\n/**\n * `mext mcp install` · `myapp mcp install` 의 안내 텍스트.\n * MCP 클라이언트 등록 명령만 출력한다. 클라이언트 설정 파일은 쓰지 않는다.\n */\nexport function mcpInstallText(options: McpInstallOptions = {}): string {\n const name = options.name ?? \"mext\"\n const url = options.url ?? `http://${DEFAULT_HOST}:${DEFAULT_PORT}${MCP_PATH}`\n const stdio = options.stdioCommand === undefined ? \"npx mext dev --stdio\" : options.stdioCommand\n const n = shellQuote(name)\n const lines = [\n `Register the \"${name}\" MCP server with your MCP client.`,\n \"\",\n \"Claude Code (Streamable HTTP):\",\n ` claude mcp add --transport http ${n} ${shellQuote(url)}`,\n ]\n if (options.auth) {\n lines.push(\n \" # with an API token:\",\n ` claude mcp add --transport http ${n} ${shellQuote(url)} --header \"Authorization: Bearer <token>\"`,\n )\n }\n if (stdio !== false) {\n lines.push(\"\", \"Claude Code (stdio, runs commands locally):\", ` claude mcp add ${n} -- ${stdio}`)\n if (options.stdioCommand === undefined) lines.push(\" # production (after mext build):\", ` claude mcp add ${n} -- npx mext start --stdio`)\n }\n const config: Record<string, unknown> = { type: \"http\", url }\n if (options.auth) config.headers = { Authorization: \"Bearer <token>\" }\n lines.push(\n \"\",\n \"Other MCP clients (JSON config):\",\n ...JSON.stringify({ mcpServers: { [name]: config } }, null, 2)\n .split(\"\\n\")\n .map((l) => ` ${l}`),\n \"\",\n \"This command only prints instructions; it does not modify client configuration files.\",\n )\n return lines.join(\"\\n\")\n}\n\nfunction shellQuote(value: string): string {\n return /^[A-Za-z0-9_./:@%+=,-]+$/.test(value) ? value : `'${value.replaceAll(\"'\", `'\\\\''`)}'`\n}\n"]}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import type { AskHandler } from "../invoke.ts";
|
|
2
|
+
import type { JsonSchema } from "../registry.ts";
|
|
3
|
+
/**
|
|
4
|
+
* ctx.ask 의 MCP 구현: Multi Round-Trip Requests (MCP 2026-07-28, basic/patterns/mrtr). transport · SDK 무관.
|
|
5
|
+
*
|
|
6
|
+
* stateless: 라운드마다 핸들러를 처음부터 다시 실행한다. ctx.ask 는 호출 순서대로 키 ask-0 · ask-1 · … 를 받고,
|
|
7
|
+
* 답 (재시도의 inputResponses · 이전 라운드의 requestState) 이 있으면 그 값으로 resolve, 없으면 라운드를 멈추고
|
|
8
|
+
* InputRequiredResult (elicitation/create form) 를 돌려준다. 서버 메모리에 대기 상태를 두지 않는다.
|
|
9
|
+
* 그래서 ctx.ask 이전의 핸들러 코드는 라운드마다 다시 실행된다 (부작용은 ask 뒤에 둔다).
|
|
10
|
+
*
|
|
11
|
+
* requestState = 이전 라운드에서 받은 답 (2번째 이후 ask 에만 필요). HMAC-SHA256 서명 · 만료 · 바인딩
|
|
12
|
+
* (principal · 도구 · 명령어 · args 다이제스트). 서명만 (암호화 아님): 클라이언트 자신의 답만 담는다.
|
|
13
|
+
*/
|
|
14
|
+
/** 임베드 elicitation 요청 (InputRequests 의 값) */
|
|
15
|
+
export interface ElicitationRequest {
|
|
16
|
+
method: "elicitation/create";
|
|
17
|
+
params: {
|
|
18
|
+
mode: "form";
|
|
19
|
+
message: string;
|
|
20
|
+
requestedSchema: JsonSchema;
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
/** MCP InputRequiredResult (tools/call 결과). SDK 타입 비의존 */
|
|
24
|
+
export interface InputRequiredResult {
|
|
25
|
+
resultType: "input_required";
|
|
26
|
+
/** 키 = ask-<호출 순서>. 클라이언트는 같은 키로 inputResponses 를 채워 같은 tools/call 을 재시도 */
|
|
27
|
+
inputRequests: Record<string, ElicitationRequest>;
|
|
28
|
+
/** 있으면 재시도에 그대로 echo */
|
|
29
|
+
requestState?: string;
|
|
30
|
+
}
|
|
31
|
+
/** 한 라운드의 MRTR 입력. transport 가 요청에서 채운다 */
|
|
32
|
+
export interface InputRound {
|
|
33
|
+
/** 요청의 clientCapabilities 에 elicitation (form) 선언. false = ctx.ask → INPUT_UNAVAILABLE */
|
|
34
|
+
elicitation: boolean;
|
|
35
|
+
/** 재시도의 inputResponses (bare ElicitResult). 첫 호출 = undefined */
|
|
36
|
+
inputResponses?: Record<string, unknown>;
|
|
37
|
+
/** 재시도의 requestState 원문 */
|
|
38
|
+
requestState?: string;
|
|
39
|
+
}
|
|
40
|
+
export declare const ASK_KEY_PREFIX = "ask-";
|
|
41
|
+
/** confirm 요청의 requestedSchema. 승인 = action accept + content.confirm === true */
|
|
42
|
+
export declare const CONFIRM_REQUESTED_SCHEMA: JsonSchema;
|
|
43
|
+
/** requestState 유효 기간 (사람이 답하는 시간) */
|
|
44
|
+
export declare const REQUEST_STATE_TTL_MS = 600000;
|
|
45
|
+
/** requestState 가 묶이는 호출. 다른 주체 · 도구 · 명령어 · args 에서 제시하면 거부 */
|
|
46
|
+
export interface RequestStateBinding {
|
|
47
|
+
principal: string;
|
|
48
|
+
tool: string;
|
|
49
|
+
command: string;
|
|
50
|
+
args: unknown;
|
|
51
|
+
}
|
|
52
|
+
export interface RequestStateSigner {
|
|
53
|
+
mint(answers: Record<string, unknown>, binding: RequestStateBinding): string;
|
|
54
|
+
/** 검증된 답. 서명 · 만료 · 바인딩 불일치 · 형식 오류 = undefined */
|
|
55
|
+
verify(state: string, binding: RequestStateBinding): Record<string, unknown> | undefined;
|
|
56
|
+
}
|
|
57
|
+
export interface RequestStateSignerOptions {
|
|
58
|
+
/** HMAC 키, 32바이트 이상. 기본 = 프로세스별 임의 키 (여러 인스턴스 배포는 공유 키 필요) */
|
|
59
|
+
key?: string | Uint8Array;
|
|
60
|
+
ttlMs?: number;
|
|
61
|
+
now?: () => number;
|
|
62
|
+
}
|
|
63
|
+
export declare function createRequestStateSigner(options?: RequestStateSignerOptions): RequestStateSigner;
|
|
64
|
+
export interface AskSession {
|
|
65
|
+
/** invoke 의 ask 로 주입 */
|
|
66
|
+
ask: AskHandler;
|
|
67
|
+
/** 답이 없어 이번 라운드를 멈춘 요청 */
|
|
68
|
+
readonly pending: Record<string, ElicitationRequest>;
|
|
69
|
+
/** 이번 라운드에서 사용한 답 (다음 requestState 에 실림) */
|
|
70
|
+
readonly used: Record<string, unknown>;
|
|
71
|
+
/** 대기 요청이 생기면 abort (핸들러 signal 에 연결) */
|
|
72
|
+
readonly signal: AbortSignal;
|
|
73
|
+
}
|
|
74
|
+
/** 대기 요청이 생겨 라운드를 멈출 때 ask 가 던지는 값. 핸들러가 잡아도 결과는 InputRequiredResult */
|
|
75
|
+
export declare class InputRequiredSignal extends Error {
|
|
76
|
+
constructor();
|
|
77
|
+
}
|
|
78
|
+
/** 한 라운드의 ask 세션. answers = 재시도의 inputResponses + 검증된 requestState 의 답 */
|
|
79
|
+
export declare function createAskSession(answers: Record<string, unknown>): AskSession;
|
|
80
|
+
export declare function hasPending(session: AskSession): boolean;
|
|
81
|
+
/** 대기 요청 → InputRequiredResult. 사용한 답이 있으면 서명한 requestState 로 다음 라운드에 넘긴다 */
|
|
82
|
+
export declare function inputRequiredResult(session: AskSession, signer: RequestStateSigner, binding: RequestStateBinding): InputRequiredResult;
|