@obov/mext 0.3.1 → 0.4.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.js +67 -4
- package/dist/build.js.map +1 -1
- package/dist/cli/ask-url.d.ts +16 -0
- package/dist/cli/ask-url.js +27 -0
- package/dist/cli/ask-url.js.map +1 -0
- package/dist/cli/build.js +7 -1
- package/dist/cli/build.js.map +1 -1
- package/dist/cli/dev-server.d.ts +7 -1
- package/dist/cli/dev-server.js +15 -2
- package/dist/cli/dev-server.js.map +1 -1
- package/dist/cli/dev.js +22 -7
- package/dist/cli/dev.js.map +1 -1
- package/dist/cli/login.d.ts +5 -0
- package/dist/cli/login.js +11 -7
- package/dist/cli/login.js.map +1 -1
- package/dist/cli/logout.js +4 -6
- package/dist/cli/logout.js.map +1 -1
- package/dist/cli/output.d.ts +1 -1
- package/dist/cli/output.js.map +1 -1
- package/dist/cli/run.js +7 -3
- package/dist/cli/run.js.map +1 -1
- package/dist/client/cache.d.ts +14 -4
- package/dist/client/cache.js +10 -3
- package/dist/client/cache.js.map +1 -1
- package/dist/client/main.js +11 -10
- package/dist/client/main.js.map +1 -1
- package/dist/client/manifest.d.ts +13 -1
- package/dist/client/manifest.js +43 -0
- package/dist/client/manifest.js.map +1 -1
- package/dist/client/runner.d.ts +3 -1
- package/dist/client/runner.js +80 -19
- package/dist/client/runner.js.map +1 -1
- package/dist/client/task.d.ts +47 -0
- package/dist/client/task.js +126 -0
- package/dist/client/task.js.map +1 -0
- package/dist/command.d.ts +30 -3
- package/dist/command.js +16 -0
- package/dist/command.js.map +1 -1
- package/dist/config.d.ts +40 -6
- package/dist/config.js +57 -8
- package/dist/config.js.map +1 -1
- package/dist/errors.d.ts +3 -1
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +17 -5
- package/dist/index.js +9 -2
- package/dist/index.js.map +1 -1
- package/dist/instrumentation.d.ts +14 -2
- package/dist/instrumentation.js +12 -4
- package/dist/instrumentation.js.map +1 -1
- package/dist/invoke.d.ts +38 -0
- package/dist/invoke.js +72 -3
- package/dist/invoke.js.map +1 -1
- package/dist/local-bin.js +5 -3
- package/dist/local-bin.js.map +1 -1
- package/dist/manifest.d.ts +48 -3
- package/dist/manifest.js +14 -3
- package/dist/manifest.js.map +1 -1
- package/dist/mcp/constants.d.ts +4 -0
- package/dist/mcp/constants.js +4 -0
- package/dist/mcp/constants.js.map +1 -1
- package/dist/mcp/dispatch.d.ts +21 -7
- package/dist/mcp/dispatch.js +42 -7
- package/dist/mcp/dispatch.js.map +1 -1
- package/dist/mcp/http.d.ts +23 -5
- package/dist/mcp/http.js +64 -9
- package/dist/mcp/http.js.map +1 -1
- package/dist/mcp/index.d.ts +14 -6
- package/dist/mcp/index.js +7 -3
- package/dist/mcp/index.js.map +1 -1
- package/dist/mcp/mrtr.d.ts +40 -8
- package/dist/mcp/mrtr.js +32 -11
- package/dist/mcp/mrtr.js.map +1 -1
- package/dist/mcp/prompts.d.ts +25 -0
- package/dist/mcp/prompts.js +164 -0
- package/dist/mcp/prompts.js.map +1 -0
- package/dist/mcp/resources.d.ts +48 -0
- package/dist/mcp/resources.js +142 -0
- package/dist/mcp/resources.js.map +1 -0
- package/dist/mcp/server.d.ts +30 -5
- package/dist/mcp/server.js +143 -16
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/stdio.d.ts +5 -0
- package/dist/mcp/stdio.js +16 -3
- package/dist/mcp/stdio.js.map +1 -1
- package/dist/mcp/subscriptions.d.ts +61 -0
- package/dist/mcp/subscriptions.js +188 -0
- package/dist/mcp/subscriptions.js.map +1 -0
- package/dist/mcp/tasks.d.ts +127 -0
- package/dist/mcp/tasks.js +448 -0
- package/dist/mcp/tasks.js.map +1 -0
- package/dist/module-files.d.ts +24 -0
- package/dist/module-files.js +97 -0
- package/dist/module-files.js.map +1 -0
- package/dist/prompt.d.ts +133 -0
- package/dist/prompt.js +222 -0
- package/dist/prompt.js.map +1 -0
- package/dist/registry.d.ts +6 -0
- package/dist/registry.js.map +1 -1
- package/dist/remote/client.d.ts +17 -1
- package/dist/remote/client.js +18 -2
- package/dist/remote/client.js.map +1 -1
- package/dist/resource.d.ts +169 -0
- package/dist/resource.js +301 -0
- package/dist/resource.js.map +1 -0
- package/dist/route.d.ts +71 -0
- package/dist/route.js +280 -0
- package/dist/route.js.map +1 -0
- package/dist/start.d.ts +14 -3
- package/dist/start.js +76 -11
- package/dist/start.js.map +1 -1
- package/dist/state.d.ts +38 -0
- package/dist/state.js +98 -0
- package/dist/state.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { assertResourceUri } from "../invoke.js";
|
|
2
|
+
import { hasScope } from "../principal.js";
|
|
3
|
+
import { matchResource, resourceEntries } from "../resource.js";
|
|
4
|
+
import { STATE_KEY_PREFIX } from "../state.js";
|
|
5
|
+
/**
|
|
6
|
+
* subscriptions/listen (§12.5).
|
|
7
|
+
* D5: SDK 2.4.0 은 listen 을 transport 진입점에서 처리한다 (HTTP createMcpHandler 의 listen router · stdio serveStdio 의
|
|
8
|
+
* StdioListenRouter: ack 먼저 · subscription id 표시 · 15초 keepalive · notifications/cancelled · 종료 result).
|
|
9
|
+
* SDK 의 ack 범위 = 서버 capability 만 → mext 는 요청을 SDK 에 넘기기 전에 notifications 를 허용 부분으로 줄이고 (narrowListenRequest),
|
|
10
|
+
* 갱신 이벤트를 공급한다 (HTTP = SDK ServerEventBus, stdio = 연결 인스턴스의 notifications/resources/updated)
|
|
11
|
+
*/
|
|
12
|
+
/** bus 채널. 메시지 = JSON { uri } */
|
|
13
|
+
export const RESOURCE_UPDATED_CHANNEL = `${STATE_KEY_PREFIX}resource-updated`;
|
|
14
|
+
/** capability resources.subscribe 조건 (§12.5): state 설정 + subscribe: true 자원 */
|
|
15
|
+
export function subscribable(options) {
|
|
16
|
+
return options.state !== undefined && options.resources !== undefined && resourceEntries(options.resources).some((e) => e.subscribe);
|
|
17
|
+
}
|
|
18
|
+
/** 구독 가능한 URI: state 설정 + 매칭 자원 (고정 우선 · 템플릿, read 와 같은 매칭) 이 subscribe: true + scopes 보유 */
|
|
19
|
+
export function subscriptionPermitted(options, principal, uri) {
|
|
20
|
+
if (options.state === undefined || options.resources === undefined)
|
|
21
|
+
return false;
|
|
22
|
+
const entry = matchResource(options.resources, uri)?.entry;
|
|
23
|
+
return entry !== undefined && entry.subscribe && entry.scopes.every((s) => hasScope(principal, s));
|
|
24
|
+
}
|
|
25
|
+
const LIST_CHANGED_KEYS = ["toolsListChanged", "promptsListChanged", "resourcesListChanged"];
|
|
26
|
+
/**
|
|
27
|
+
* subscriptions/listen 요청이면 params.notifications 를 허용 부분으로 바꾼 사본, 아니면 undefined.
|
|
28
|
+
* listChanged 3종 = 제외 (목록은 빌드 시 고정), resourceSubscriptions = subscriptionPermitted 통과분 (중복 제거, 요청 순서).
|
|
29
|
+
* notifications 형식이 틀리면 그대로 (SDK 가 -32602)
|
|
30
|
+
*/
|
|
31
|
+
export function narrowListenRequest(body, options, principal) {
|
|
32
|
+
if (!isRecord(body) || body.method !== "subscriptions/listen" || !isRecord(body.params))
|
|
33
|
+
return undefined;
|
|
34
|
+
const filter = body.params.notifications;
|
|
35
|
+
if (!isRecord(filter))
|
|
36
|
+
return undefined;
|
|
37
|
+
const uris = filter.resourceSubscriptions;
|
|
38
|
+
if (uris !== undefined && !(Array.isArray(uris) && uris.every((u) => typeof u === "string")))
|
|
39
|
+
return undefined;
|
|
40
|
+
if (LIST_CHANGED_KEYS.some((k) => filter[k] !== undefined && typeof filter[k] !== "boolean"))
|
|
41
|
+
return undefined;
|
|
42
|
+
const permitted = [...new Set((uris ?? []))].filter((uri) => subscriptionPermitted(options, principal, uri));
|
|
43
|
+
return {
|
|
44
|
+
...body,
|
|
45
|
+
params: { ...body.params, notifications: permitted.length > 0 ? { resourceSubscriptions: permitted } : {} },
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
const hubs = new WeakMap();
|
|
49
|
+
/** state 별 갱신 허브 (같은 state = 같은 허브. HTTP · route · stdio 가 공유). state 없음 = undefined */
|
|
50
|
+
export function resourceUpdates(state) {
|
|
51
|
+
if (state === undefined)
|
|
52
|
+
return undefined;
|
|
53
|
+
let hub = hubs.get(state);
|
|
54
|
+
if (!hub)
|
|
55
|
+
hubs.set(state, (hub = createHub(state)));
|
|
56
|
+
return hub;
|
|
57
|
+
}
|
|
58
|
+
/** ctx.resourceUpdated 구현. state 없음 = no-op. uri 가 문자열 아님 = TypeError (핸들러 버그) */
|
|
59
|
+
export function resourceUpdatedFn(state) {
|
|
60
|
+
const hub = resourceUpdates(state);
|
|
61
|
+
return async (uri) => {
|
|
62
|
+
assertResourceUri(uri);
|
|
63
|
+
await hub?.publish(uri);
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
function createHub(state) {
|
|
67
|
+
const listeners = new Set();
|
|
68
|
+
const bus = state.bus;
|
|
69
|
+
let unsubscribe;
|
|
70
|
+
const deliver = (uri) => {
|
|
71
|
+
for (const listener of [...listeners]) {
|
|
72
|
+
try {
|
|
73
|
+
listener(uri);
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
// 수신자 예외는 발행자 · 다른 수신자에 전파하지 않는다
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
const onMessage = (message) => {
|
|
81
|
+
let uri;
|
|
82
|
+
try {
|
|
83
|
+
uri = JSON.parse(message)?.uri;
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
if (typeof uri === "string")
|
|
89
|
+
deliver(uri);
|
|
90
|
+
};
|
|
91
|
+
return {
|
|
92
|
+
async publish(uri) {
|
|
93
|
+
if (!bus)
|
|
94
|
+
return deliver(uri);
|
|
95
|
+
try {
|
|
96
|
+
await bus.publish(RESOURCE_UPDATED_CHANNEL, JSON.stringify({ uri }));
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
process.stderr.write(`mext: resourceUpdated ${uri}: bus publish failed: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
listen(listener, onerror) {
|
|
103
|
+
listeners.add(listener);
|
|
104
|
+
if (bus && unsubscribe === undefined) {
|
|
105
|
+
unsubscribe = Promise.resolve()
|
|
106
|
+
.then(() => bus.subscribe(RESOURCE_UPDATED_CHANNEL, onMessage))
|
|
107
|
+
.catch((error) => {
|
|
108
|
+
unsubscribe = undefined;
|
|
109
|
+
onerror?.(error instanceof Error ? error : new Error(String(error)));
|
|
110
|
+
return undefined;
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
return () => {
|
|
114
|
+
if (!listeners.delete(listener) || listeners.size > 0 || unsubscribe === undefined)
|
|
115
|
+
return;
|
|
116
|
+
const pending = unsubscribe;
|
|
117
|
+
unsubscribe = undefined;
|
|
118
|
+
void pending.then((off) => off?.()).catch((error) => onerror?.(error instanceof Error ? error : new Error(String(error))));
|
|
119
|
+
};
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* McpServer 에 resources.subscribe capability (조건 충족 · 2026-07-28 만) 선언 + (stdio) 갱신 전달.
|
|
125
|
+
* installResourceHandlers 뒤 · transport 연결 전에 호출
|
|
126
|
+
*/
|
|
127
|
+
export function installSubscriptions(server, options) {
|
|
128
|
+
if (options.era !== "modern" || !subscribable(options))
|
|
129
|
+
return;
|
|
130
|
+
const inner = server.server;
|
|
131
|
+
inner.registerCapabilities({ resources: { subscribe: true, listChanged: false } });
|
|
132
|
+
if (!options.deliver)
|
|
133
|
+
return;
|
|
134
|
+
const unlisten = resourceUpdates(options.state).listen((uri) => {
|
|
135
|
+
void inner.sendResourceUpdated({ uri }).catch(() => { });
|
|
136
|
+
}, options.onerror);
|
|
137
|
+
const previous = inner.onclose;
|
|
138
|
+
inner.onclose = () => {
|
|
139
|
+
unlisten();
|
|
140
|
+
previous?.();
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
/** HTTP: 허브 → SDK listen router 의 bus. 반환 = 해제. 조건 미충족 = 연결 없음 */
|
|
144
|
+
export function bridgeResourceUpdates(bus, options) {
|
|
145
|
+
if (!subscribable(options))
|
|
146
|
+
return () => { };
|
|
147
|
+
return resourceUpdates(options.state).listen((uri) => bus.publish({ kind: "resource_updated", uri }), options.onerror);
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* 응답 본문을 감싸 소비자가 끝까지 읽었을 때 resolve 하는 done (서버 종료 시 완료 result 전달 대기).
|
|
151
|
+
* highWaterMark 0 = 소비자가 읽을 때만 원본을 읽는다
|
|
152
|
+
*/
|
|
153
|
+
export function trackBody(response) {
|
|
154
|
+
const body = response.body;
|
|
155
|
+
if (body === null)
|
|
156
|
+
return { response, done: Promise.resolve() };
|
|
157
|
+
const reader = body.getReader();
|
|
158
|
+
let finish;
|
|
159
|
+
const done = new Promise((resolve) => {
|
|
160
|
+
finish = resolve;
|
|
161
|
+
});
|
|
162
|
+
const tracked = new ReadableStream({
|
|
163
|
+
async pull(controller) {
|
|
164
|
+
try {
|
|
165
|
+
const chunk = await reader.read();
|
|
166
|
+
if (chunk.done) {
|
|
167
|
+
controller.close();
|
|
168
|
+
finish();
|
|
169
|
+
}
|
|
170
|
+
else
|
|
171
|
+
controller.enqueue(chunk.value);
|
|
172
|
+
}
|
|
173
|
+
catch (error) {
|
|
174
|
+
controller.error(error);
|
|
175
|
+
finish();
|
|
176
|
+
}
|
|
177
|
+
},
|
|
178
|
+
cancel(reason) {
|
|
179
|
+
finish();
|
|
180
|
+
return reader.cancel(reason);
|
|
181
|
+
},
|
|
182
|
+
}, { highWaterMark: 0 });
|
|
183
|
+
return { response: new Response(tracked, { status: response.status, statusText: response.statusText, headers: response.headers }), done };
|
|
184
|
+
}
|
|
185
|
+
function isRecord(value) {
|
|
186
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
187
|
+
}
|
|
188
|
+
//# sourceMappingURL=subscriptions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"subscriptions.js","sourceRoot":"","sources":["../../src/mcp/subscriptions.ts"],"names":[],"mappings":"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","sourcesContent":["import type { McpServer, ServerEventBus } from \"@modelcontextprotocol/server\"\nimport { assertResourceUri } from \"../invoke.ts\"\nimport { hasScope, type Principal } from \"../principal.ts\"\nimport { matchResource, resourceEntries, type ResourceSet } from \"../resource.ts\"\nimport { STATE_KEY_PREFIX, type MextState } from \"../state.ts\"\n\n/**\n * subscriptions/listen (§12.5).\n * D5: SDK 2.4.0 은 listen 을 transport 진입점에서 처리한다 (HTTP createMcpHandler 의 listen router · stdio serveStdio 의\n * StdioListenRouter: ack 먼저 · subscription id 표시 · 15초 keepalive · notifications/cancelled · 종료 result).\n * SDK 의 ack 범위 = 서버 capability 만 → mext 는 요청을 SDK 에 넘기기 전에 notifications 를 허용 부분으로 줄이고 (narrowListenRequest),\n * 갱신 이벤트를 공급한다 (HTTP = SDK ServerEventBus, stdio = 연결 인스턴스의 notifications/resources/updated)\n */\n\n/** bus 채널. 메시지 = JSON { uri } */\nexport const RESOURCE_UPDATED_CHANNEL = `${STATE_KEY_PREFIX}resource-updated`\n\nexport interface SubscriptionOptions {\n state?: MextState | undefined\n resources?: ResourceSet | undefined\n}\n\n/** capability resources.subscribe 조건 (§12.5): state 설정 + subscribe: true 자원 */\nexport function subscribable(options: SubscriptionOptions): boolean {\n return options.state !== undefined && options.resources !== undefined && resourceEntries(options.resources).some((e) => e.subscribe)\n}\n\n/** 구독 가능한 URI: state 설정 + 매칭 자원 (고정 우선 · 템플릿, read 와 같은 매칭) 이 subscribe: true + scopes 보유 */\nexport function subscriptionPermitted(options: SubscriptionOptions, principal: Principal, uri: string): boolean {\n if (options.state === undefined || options.resources === undefined) return false\n const entry = matchResource(options.resources, uri)?.entry\n return entry !== undefined && entry.subscribe && entry.scopes.every((s) => hasScope(principal, s))\n}\n\nconst LIST_CHANGED_KEYS = [\"toolsListChanged\", \"promptsListChanged\", \"resourcesListChanged\"] as const\n\n/**\n * subscriptions/listen 요청이면 params.notifications 를 허용 부분으로 바꾼 사본, 아니면 undefined.\n * listChanged 3종 = 제외 (목록은 빌드 시 고정), resourceSubscriptions = subscriptionPermitted 통과분 (중복 제거, 요청 순서).\n * notifications 형식이 틀리면 그대로 (SDK 가 -32602)\n */\nexport function narrowListenRequest(body: unknown, options: SubscriptionOptions, principal: Principal): unknown {\n if (!isRecord(body) || body.method !== \"subscriptions/listen\" || !isRecord(body.params)) return undefined\n const filter = body.params.notifications\n if (!isRecord(filter)) return undefined\n const uris = filter.resourceSubscriptions\n if (uris !== undefined && !(Array.isArray(uris) && uris.every((u) => typeof u === \"string\"))) return undefined\n if (LIST_CHANGED_KEYS.some((k) => filter[k] !== undefined && typeof filter[k] !== \"boolean\")) return undefined\n const permitted = [...new Set((uris ?? []) as string[])].filter((uri) => subscriptionPermitted(options, principal, uri))\n return {\n ...body,\n params: { ...body.params, notifications: permitted.length > 0 ? { resourceSubscriptions: permitted } : {} },\n }\n}\n\n/* ---------------------------------------------------------------- 갱신 전달 (ctx.resourceUpdated) */\n\nexport interface ResourceUpdates {\n /** 갱신 발행. bus 있음 = 채널로 모든 인스턴스, 없음 = 같은 프로세스 리스너. bus 오류 = stderr 1줄 (거부하지 않음) */\n publish(uri: string): Promise<void>\n /** 이 프로세스의 수신. 반환 = 해제. bus 구독 실패 = onerror */\n listen(listener: (uri: string) => void, onerror?: (error: Error) => void): () => void\n}\n\nconst hubs = new WeakMap<MextState, ResourceUpdates>()\n\n/** state 별 갱신 허브 (같은 state = 같은 허브. HTTP · route · stdio 가 공유). state 없음 = undefined */\nexport function resourceUpdates(state: MextState | undefined): ResourceUpdates | undefined {\n if (state === undefined) return undefined\n let hub = hubs.get(state)\n if (!hub) hubs.set(state, (hub = createHub(state)))\n return hub\n}\n\n/** ctx.resourceUpdated 구현. state 없음 = no-op. uri 가 문자열 아님 = TypeError (핸들러 버그) */\nexport function resourceUpdatedFn(state: MextState | undefined): (uri: string) => Promise<void> {\n const hub = resourceUpdates(state)\n return async (uri) => {\n assertResourceUri(uri)\n await hub?.publish(uri)\n }\n}\n\nfunction createHub(state: MextState): ResourceUpdates {\n const listeners = new Set<(uri: string) => void>()\n const bus = state.bus\n let unsubscribe: Promise<(() => Promise<void>) | undefined> | undefined\n\n const deliver = (uri: string): void => {\n for (const listener of [...listeners]) {\n try {\n listener(uri)\n } catch {\n // 수신자 예외는 발행자 · 다른 수신자에 전파하지 않는다\n }\n }\n }\n const onMessage = (message: string): void => {\n let uri: unknown\n try {\n uri = (JSON.parse(message) as { uri?: unknown } | null)?.uri\n } catch {\n return\n }\n if (typeof uri === \"string\") deliver(uri)\n }\n\n return {\n async publish(uri) {\n if (!bus) return deliver(uri)\n try {\n await bus.publish(RESOURCE_UPDATED_CHANNEL, JSON.stringify({ uri }))\n } catch (error) {\n process.stderr.write(`mext: resourceUpdated ${uri}: bus publish failed: ${error instanceof Error ? error.message : String(error)}\\n`)\n }\n },\n listen(listener, onerror) {\n listeners.add(listener)\n if (bus && unsubscribe === undefined) {\n unsubscribe = Promise.resolve()\n .then(() => bus.subscribe(RESOURCE_UPDATED_CHANNEL, onMessage))\n .catch((error: unknown) => {\n unsubscribe = undefined\n onerror?.(error instanceof Error ? error : new Error(String(error)))\n return undefined\n })\n }\n return () => {\n if (!listeners.delete(listener) || listeners.size > 0 || unsubscribe === undefined) return\n const pending = unsubscribe\n unsubscribe = undefined\n void pending.then((off) => off?.()).catch((error: unknown) => onerror?.(error instanceof Error ? error : new Error(String(error))))\n }\n },\n }\n}\n\n/* ---------------------------------------------------------------- 서버 인스턴스 · 진입점 연결 */\n\nexport interface InstallSubscriptionOptions extends SubscriptionOptions {\n /** 2025 레거시 = resources/subscribe 미구현이므로 선언하지 않는다 */\n era: \"modern\" | \"legacy\"\n /** stdio = 연결 인스턴스가 갱신을 notifications/resources/updated 로 보낸다 (SDK StdioListenRouter 가 구독별로 표시) */\n deliver: boolean\n onerror?: (error: Error) => void\n}\n\n/**\n * McpServer 에 resources.subscribe capability (조건 충족 · 2026-07-28 만) 선언 + (stdio) 갱신 전달.\n * installResourceHandlers 뒤 · transport 연결 전에 호출\n */\nexport function installSubscriptions(server: McpServer, options: InstallSubscriptionOptions): void {\n if (options.era !== \"modern\" || !subscribable(options)) return\n const inner = server.server\n inner.registerCapabilities({ resources: { subscribe: true, listChanged: false } })\n if (!options.deliver) return\n const unlisten = resourceUpdates(options.state)!.listen((uri) => {\n void inner.sendResourceUpdated({ uri }).catch(() => {})\n }, options.onerror)\n const previous = inner.onclose\n inner.onclose = () => {\n unlisten()\n previous?.()\n }\n}\n\n/** HTTP: 허브 → SDK listen router 의 bus. 반환 = 해제. 조건 미충족 = 연결 없음 */\nexport function bridgeResourceUpdates(\n bus: ServerEventBus,\n options: SubscriptionOptions & { onerror?: (error: Error) => void },\n): () => void {\n if (!subscribable(options)) return () => {}\n return resourceUpdates(options.state)!.listen((uri) => bus.publish({ kind: \"resource_updated\", uri }), options.onerror)\n}\n\n/**\n * 응답 본문을 감싸 소비자가 끝까지 읽었을 때 resolve 하는 done (서버 종료 시 완료 result 전달 대기).\n * highWaterMark 0 = 소비자가 읽을 때만 원본을 읽는다\n */\nexport function trackBody(response: Response): { response: Response; done: Promise<void> } {\n const body = response.body\n if (body === null) return { response, done: Promise.resolve() }\n const reader = body.getReader()\n let finish!: () => void\n const done = new Promise<void>((resolve) => {\n finish = resolve\n })\n const tracked = new ReadableStream<Uint8Array>(\n {\n async pull(controller) {\n try {\n const chunk = await reader.read()\n if (chunk.done) {\n controller.close()\n finish()\n } else controller.enqueue(chunk.value)\n } catch (error) {\n controller.error(error)\n finish()\n }\n },\n cancel(reason) {\n finish()\n return reader.cancel(reason)\n },\n },\n { highWaterMark: 0 },\n )\n return { response: new Response(tracked, { status: response.status, statusText: response.statusText, headers: response.headers }), done }\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value)\n}\n"]}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { type McpServer } from "@modelcontextprotocol/server";
|
|
2
|
+
import type { DetachedExecution } from "../invoke.ts";
|
|
3
|
+
import type { Principal } from "../principal.ts";
|
|
4
|
+
import type { RegisteredCommand, Registry } from "../registry.ts";
|
|
5
|
+
import { type MextState } from "../state.ts";
|
|
6
|
+
import { type ToolCallResult } from "./dispatch.ts";
|
|
7
|
+
import { type ElicitationModes, type ElicitationRequest } from "./mrtr.ts";
|
|
8
|
+
/**
|
|
9
|
+
* MCP tasks 확장 (io.modelcontextprotocol/tasks, §12.6). 서버 측: task 생성 (invoke detach) · tasks/get · update · cancel.
|
|
10
|
+
*
|
|
11
|
+
* D5: SDK 2.4.0 은 tasks 런타임이 없다 (task 메서드 = 2025-11-25 어휘, typed API 미제공). 단 3-arg setRequestHandler
|
|
12
|
+
* (custom params schema) 로 tasks/get · update · cancel 을 등록할 수 있고 (era gate 통과), Mcp-Name = params.taskId 헤더 검증은
|
|
13
|
+
* SDK 가 한다. tools/call 결과 resultType "task" 는 SDK 가 그대로 통과시킨다. 그래서 mext 가 이 파일에서 직접 구현한다.
|
|
14
|
+
*
|
|
15
|
+
* D2: store 키 (값 = JSON, 접두 "mext:", 키마다 writer 하나)
|
|
16
|
+
* mext:task:<taskId> TaskRecord. writer = 실행 프로세스 (생성 · 상태 · heartbeat · 결과)
|
|
17
|
+
* mext:task:<taskId>:input:<ask-N> 답 (ElicitResult). writer = tasks/update 를 받은 프로세스. 이미 있으면 무시
|
|
18
|
+
* mext:task:<taskId>:cancel 취소 기록 ({}). writer = tasks/cancel 을 받은 프로세스
|
|
19
|
+
* bus 채널 mext:task:<taskId> = 답 · 취소 도착 알림 ({"type":"input"|"cancel"}). 내용은 store 가 원본, 메시지는 깨우기만.
|
|
20
|
+
* bus 없음 = 실행 프로세스가 storePollMs (1초) 마다 store 폴링. 같은 프로세스의 task = 직접 깨움.
|
|
21
|
+
* store ttl: 실행 중 = ttlMs + lostAfterMs (heartbeat 마다 연장, 유실 후 failed 를 ttlMs 동안 조회), 종료 = ttlMs.
|
|
22
|
+
*
|
|
23
|
+
* R4: ctx.ask 의미 차이. MRTR (동기) = 라운드마다 핸들러 재실행 (ask 이전 코드 반복), task = 재실행 없이 대기
|
|
24
|
+
* (ask 이전 코드 1회). 키 (ask-0 · ask-1 …) · elicitation 형식 · 답 해석 (askAnswer) · 미선언 mode = INPUT_UNAVAILABLE 은 같다.
|
|
25
|
+
* 두 경로 모두에서 맞게 동작하려면 MRTR 규칙 (부작용은 ask 뒤) 을 따른다.
|
|
26
|
+
*/
|
|
27
|
+
export declare const TASKS_EXTENSION = "io.modelcontextprotocol/tasks";
|
|
28
|
+
export interface TaskTiming {
|
|
29
|
+
/** CreateTaskResult · tasks/get 의 pollIntervalMs (클라이언트 폴링 권장 간격) */
|
|
30
|
+
pollIntervalMs: number;
|
|
31
|
+
/** 실행 프로세스의 heartbeat (레코드 갱신) 간격 */
|
|
32
|
+
heartbeatMs: number;
|
|
33
|
+
/** heartbeat 이 이보다 오래 없으면 tasks/get = failed (TASK_LOST) */
|
|
34
|
+
lostAfterMs: number;
|
|
35
|
+
/** bus 없음: 실행 프로세스의 답 · 취소 store 폴링 간격 */
|
|
36
|
+
storePollMs: number;
|
|
37
|
+
/** close (SIGTERM · SIGINT) 시 실행 중 task 정리 상한 */
|
|
38
|
+
shutdownMs: number;
|
|
39
|
+
}
|
|
40
|
+
export declare const TASK_TIMING: Readonly<TaskTiming>;
|
|
41
|
+
/** tasks/get failed 의 error.data.code (실행 프로세스 유실) */
|
|
42
|
+
export declare const TASK_LOST = "TASK_LOST";
|
|
43
|
+
export type TaskStatus = "working" | "input_required" | "completed" | "failed" | "cancelled";
|
|
44
|
+
/** tools/call 의 task 응답 (resultType "task") */
|
|
45
|
+
export interface CreateTaskResult {
|
|
46
|
+
resultType: "task";
|
|
47
|
+
taskId: string;
|
|
48
|
+
status: TaskStatus;
|
|
49
|
+
statusMessage?: string;
|
|
50
|
+
createdAt: string;
|
|
51
|
+
lastUpdatedAt: string;
|
|
52
|
+
ttlMs: number;
|
|
53
|
+
pollIntervalMs: number;
|
|
54
|
+
_meta?: Record<string, unknown>;
|
|
55
|
+
}
|
|
56
|
+
/** tasks/get 결과 (resultType 은 SDK 가 "complete" 로 채운다) */
|
|
57
|
+
export interface TaskView {
|
|
58
|
+
taskId: string;
|
|
59
|
+
status: TaskStatus;
|
|
60
|
+
statusMessage?: string;
|
|
61
|
+
createdAt: string;
|
|
62
|
+
lastUpdatedAt: string;
|
|
63
|
+
ttlMs: number;
|
|
64
|
+
pollIntervalMs: number;
|
|
65
|
+
/** input_required */
|
|
66
|
+
inputRequests?: Record<string, ElicitationRequest>;
|
|
67
|
+
/** completed = 같은 요청의 동기 tools/call 결과 */
|
|
68
|
+
result?: TaskResult;
|
|
69
|
+
/** failed (TASK_LOST) */
|
|
70
|
+
error?: TaskError;
|
|
71
|
+
}
|
|
72
|
+
type TaskResult = ToolCallResult & {
|
|
73
|
+
resultType: "complete";
|
|
74
|
+
_meta?: Record<string, unknown>;
|
|
75
|
+
};
|
|
76
|
+
interface TaskError {
|
|
77
|
+
code: number;
|
|
78
|
+
message: string;
|
|
79
|
+
data?: Record<string, unknown>;
|
|
80
|
+
}
|
|
81
|
+
export interface TaskRuntimeOptions {
|
|
82
|
+
state: MextState;
|
|
83
|
+
timing?: Partial<TaskTiming>;
|
|
84
|
+
/** 완료 결과 _meta (동기 결과와 같은 manifestHash) */
|
|
85
|
+
meta?: Record<string, unknown>;
|
|
86
|
+
/** store · bus 예외 보고. 기본 stderr 1줄 */
|
|
87
|
+
onerror?: (error: unknown) => void;
|
|
88
|
+
}
|
|
89
|
+
/** 요청 하나의 task 조건 */
|
|
90
|
+
export interface TaskRequestOptions {
|
|
91
|
+
principal: Principal;
|
|
92
|
+
/** 생성 요청이 선언한 elicitation mode (ctx.ask 가능 여부, MRTR 과 같은 규칙) */
|
|
93
|
+
modes: ElicitationModes;
|
|
94
|
+
/** handler.js: 응답 후 실행 유지 (플랫폼 waitUntil) */
|
|
95
|
+
waitUntil?: (promise: Promise<unknown>) => void;
|
|
96
|
+
}
|
|
97
|
+
export interface TaskRequest {
|
|
98
|
+
/** invoke 의 detach. task 명령어가 아니면 undefined (동기) */
|
|
99
|
+
detach(entry: RegisteredCommand): Promise<DetachedExecution | undefined>;
|
|
100
|
+
/** 생성 중 store · bus 예외 (있으면 요청 = -32603) */
|
|
101
|
+
readonly failure: unknown;
|
|
102
|
+
}
|
|
103
|
+
export interface TaskRuntime {
|
|
104
|
+
readonly timing: TaskTiming;
|
|
105
|
+
request(options: TaskRequestOptions): TaskRequest;
|
|
106
|
+
get(taskId: string, principal: Principal): Promise<TaskView>;
|
|
107
|
+
update(taskId: string, principal: Principal, inputResponses: Record<string, unknown>): Promise<void>;
|
|
108
|
+
cancel(taskId: string, principal: Principal): Promise<void>;
|
|
109
|
+
/** 실행 중 task 취소 → cancelled 기록 (상한 shutdownMs). 이후 새 task 없음 (동기 실행) */
|
|
110
|
+
close(): Promise<void>;
|
|
111
|
+
}
|
|
112
|
+
/** registry 에 task 명령어가 있는지 (선언 조건) */
|
|
113
|
+
export declare function hasTaskCommands(registry: Registry): boolean;
|
|
114
|
+
/** 요청 _meta 의 clientCapabilities 에 tasks 확장 선언 */
|
|
115
|
+
export declare function declaresTasks(clientCapabilities: unknown): boolean;
|
|
116
|
+
/**
|
|
117
|
+
* HTTP: task 가 될 수 있는 tools/call 인지 (응답 = application/json, §12.6). 확장 선언 + 대상 명령어가 task 명령어.
|
|
118
|
+
* 대상 = dispatcher 의 arguments.command (별칭 포함), 그 외 = 도구 이름 (command 노출)
|
|
119
|
+
*/
|
|
120
|
+
export declare function requestsTask(registry: Registry, body: unknown): boolean;
|
|
121
|
+
export declare function createTaskRuntime(options: TaskRuntimeOptions): TaskRuntime;
|
|
122
|
+
/**
|
|
123
|
+
* McpServer 에 tasks/get · tasks/update · tasks/cancel 등록 (D5: 3-arg setRequestHandler, custom params schema).
|
|
124
|
+
* declare = server/discover capabilities.extensions 에 확장 선언 (생성 가능할 때만). transport 연결 전에 호출
|
|
125
|
+
*/
|
|
126
|
+
export declare function installTaskHandlers(server: McpServer, runtime: TaskRuntime, principal: Principal, declare: boolean): void;
|
|
127
|
+
export {};
|