@obov/mext 0.3.1 → 0.5.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.js +2 -1
- package/dist/build-cli.js.map +1 -1
- 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 +9 -2
- 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/install.d.ts +1 -1
- package/dist/mcp/install.js +3 -2
- package/dist/mcp/install.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 +41 -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/runtime.d.ts +10 -0
- package/dist/runtime.js +11 -0
- package/dist/runtime.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 +3 -2
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { ProtocolError, ProtocolErrorCode } from "@modelcontextprotocol/server";
|
|
2
|
+
import { invalidArgument } from "../invoke.js";
|
|
3
|
+
import { hasScope } from "../principal.js";
|
|
4
|
+
import { PERMISSION_DENIED_RPC_CODE, runHandler, runResourceHandler } from "./resources.js";
|
|
5
|
+
/**
|
|
6
|
+
* MCP prompts · completion (§12.3): prompts/list · prompts/get · completion/complete (ref/prompt · ref/resource).
|
|
7
|
+
* SDK McpServer.registerPrompt 는 쓰지 않는다 (인자 completion = completable() 스키마 래퍼, ctx 없음 · 목록 미정렬 ·
|
|
8
|
+
* capability listChanged 기본 true · ref/resource completion = SDK 에 등록한 템플릿만). 저수준 Server.setRequestHandler 로 직접 처리한다
|
|
9
|
+
*/
|
|
10
|
+
/** completion/complete 결과 최대 값 수 (MCP 사양) */
|
|
11
|
+
export const COMPLETION_MAX_VALUES = 100;
|
|
12
|
+
/** server 에 prompts capability { listChanged: false } 선언 + prompts/list · get (프롬프트 없음 = 아무것도 안 함). transport 연결 전에 호출 */
|
|
13
|
+
export function installPromptHandlers(server, options) {
|
|
14
|
+
if (options.prompts.size === 0)
|
|
15
|
+
return;
|
|
16
|
+
const inner = server.server;
|
|
17
|
+
inner.registerCapabilities({ prompts: { listChanged: false } });
|
|
18
|
+
// 목록 = 주체 무관 (tools/list 와 같은 public 캐시). scopes 는 get · completion 에서 검사
|
|
19
|
+
inner.setRequestHandler("prompts/list", () => ({
|
|
20
|
+
prompts: [...options.prompts.values()].map((entry) => ({
|
|
21
|
+
name: entry.name,
|
|
22
|
+
...(entry.title !== undefined ? { title: entry.title } : {}),
|
|
23
|
+
description: entry.description,
|
|
24
|
+
...(entry.arguments.length > 0 ? { arguments: entry.arguments.map((a) => ({ ...a })) } : {}),
|
|
25
|
+
})),
|
|
26
|
+
}));
|
|
27
|
+
inner.setRequestHandler("prompts/get", async (request, ctx) => {
|
|
28
|
+
const entry = findPrompt(options.prompts, request.params.name);
|
|
29
|
+
authorize(entry, options.principal);
|
|
30
|
+
const raw = request.params.arguments ?? {};
|
|
31
|
+
// 모듈 로드 · 인자 검증 = render span 밖 (INVALID_ARGUMENT = -32602, 보고 없음)
|
|
32
|
+
const prompt = await entry.load();
|
|
33
|
+
let args = {};
|
|
34
|
+
if (prompt.args) {
|
|
35
|
+
const parsed = await prompt.args.safeParseAsync(raw);
|
|
36
|
+
if (!parsed.success) {
|
|
37
|
+
const error = invalidArgument(entry.name, raw, parsed.error.issues, entry.argsSchema ?? {}, []);
|
|
38
|
+
const { command: _command, examples: _examples, ...body } = error.toJSON();
|
|
39
|
+
throw new ProtocolError(ProtocolErrorCode.InvalidParams, error.message, { ...body, prompt: entry.name });
|
|
40
|
+
}
|
|
41
|
+
args = parsed.data;
|
|
42
|
+
}
|
|
43
|
+
const messages = await runPromptHandler(options, entry, ctx, async (pctx) => toMessages(entry.name, await prompt.render(args, pctx)));
|
|
44
|
+
return { description: entry.description, messages };
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* server 에 completions capability + completion/complete. complete 를 가진 프롬프트 · 자원 템플릿이 없으면 아무것도 안 함 (§12 표).
|
|
49
|
+
* 결과 = 앞 100개 + total · hasMore. 없는 인자 · complete 없는 인자 = 빈 결과
|
|
50
|
+
*/
|
|
51
|
+
export function installCompletionHandler(server, options) {
|
|
52
|
+
if (!hasCompletion(options))
|
|
53
|
+
return;
|
|
54
|
+
const inner = server.server;
|
|
55
|
+
inner.registerCapabilities({ completions: {} });
|
|
56
|
+
inner.setRequestHandler("completion/complete", async (request, ctx) => {
|
|
57
|
+
const { ref, argument } = request.params;
|
|
58
|
+
const others = request.params.context?.arguments ?? {};
|
|
59
|
+
if (ref.type === "ref/prompt") {
|
|
60
|
+
if (!options.prompts)
|
|
61
|
+
throw new ProtocolError(ProtocolErrorCode.InvalidParams, `Prompt ${ref.name} not found`, { name: ref.name });
|
|
62
|
+
const entry = findPrompt(options.prompts, ref.name);
|
|
63
|
+
authorize(entry, options.principal);
|
|
64
|
+
if (!entry.complete.includes(argument.name))
|
|
65
|
+
return EMPTY_COMPLETION;
|
|
66
|
+
return runPromptHandler(options, entry, ctx, async (pctx) => {
|
|
67
|
+
const prompt = await entry.load();
|
|
68
|
+
return completionResult(entry.name, await prompt.complete[argument.name](argument.value, { ...pctx, arguments: { ...others } }));
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
const uri = ref.uri;
|
|
72
|
+
const entry = options.resources?.templates.find((t) => t.uri === uri);
|
|
73
|
+
if (!entry) {
|
|
74
|
+
if (options.resources?.fixed.has(uri))
|
|
75
|
+
return EMPTY_COMPLETION;
|
|
76
|
+
throw new ProtocolError(ProtocolErrorCode.InvalidParams, `Resource template ${uri} not found`, { uri });
|
|
77
|
+
}
|
|
78
|
+
authorizeResource(entry, options.principal);
|
|
79
|
+
if (!entry.complete.includes(argument.name))
|
|
80
|
+
return EMPTY_COMPLETION;
|
|
81
|
+
return runResourceHandler(options, entry, ctx, async (rctx) => {
|
|
82
|
+
const resource = await entry.load();
|
|
83
|
+
return completionResult(entry.uri, await resource.complete[argument.name](argument.value, { ...rctx, arguments: { ...others } }));
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
function hasCompletion(options) {
|
|
88
|
+
for (const entry of options.prompts?.values() ?? [])
|
|
89
|
+
if (entry.complete.length > 0)
|
|
90
|
+
return true;
|
|
91
|
+
return (options.resources?.templates ?? []).some((t) => t.complete.length > 0);
|
|
92
|
+
}
|
|
93
|
+
const EMPTY_COMPLETION = { completion: { values: [], total: 0, hasMore: false } };
|
|
94
|
+
/** complete 반환 → 결과 (앞 100개 + total · hasMore). 문자열 배열 아님 = TypeError (핸들러 오류) */
|
|
95
|
+
function completionResult(owner, values) {
|
|
96
|
+
if (!Array.isArray(values) || values.some((v) => typeof v !== "string")) {
|
|
97
|
+
throw new TypeError(`${owner}: complete must return an array of strings`);
|
|
98
|
+
}
|
|
99
|
+
return {
|
|
100
|
+
completion: {
|
|
101
|
+
values: values.slice(0, COMPLETION_MAX_VALUES),
|
|
102
|
+
total: values.length,
|
|
103
|
+
hasMore: values.length > COMPLETION_MAX_VALUES,
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
/** 프롬프트 핸들러 (render · complete) 1회: `mext.prompt <이름>` span + 오류 변환 */
|
|
108
|
+
function runPromptHandler(options, entry, ctx, fn) {
|
|
109
|
+
const target = { span: `mext.prompt ${entry.name}`, attributes: { "mext.prompt.name": entry.name }, name: entry.name, info: { prompt: entry.name } };
|
|
110
|
+
return runHandler(options, target, ctx, fn);
|
|
111
|
+
}
|
|
112
|
+
function findPrompt(prompts, name) {
|
|
113
|
+
const entry = prompts.get(name);
|
|
114
|
+
if (!entry)
|
|
115
|
+
throw new ProtocolError(ProtocolErrorCode.InvalidParams, `Prompt ${name} not found`, { name });
|
|
116
|
+
return entry;
|
|
117
|
+
}
|
|
118
|
+
/** scopes 부족 = -32001 + PERMISSION_DENIED 본문 (자원과 같은 형식, prompt = 이름) */
|
|
119
|
+
function authorize(entry, principal) {
|
|
120
|
+
const missing = entry.scopes.filter((s) => !hasScope(principal, s));
|
|
121
|
+
if (missing.length === 0)
|
|
122
|
+
return;
|
|
123
|
+
const message = `${entry.name} requires scopes: ${missing.join(", ")}.`;
|
|
124
|
+
throw new ProtocolError(PERMISSION_DENIED_RPC_CODE, message, {
|
|
125
|
+
code: "PERMISSION_DENIED",
|
|
126
|
+
message,
|
|
127
|
+
prompt: entry.name,
|
|
128
|
+
reason: "scope",
|
|
129
|
+
required_scopes: [...entry.scopes],
|
|
130
|
+
held_scopes: [...principal.scopes],
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
function authorizeResource(entry, principal) {
|
|
134
|
+
const missing = entry.scopes.filter((s) => !hasScope(principal, s));
|
|
135
|
+
if (missing.length === 0)
|
|
136
|
+
return;
|
|
137
|
+
const message = `${entry.uri} requires scopes: ${missing.join(", ")}.`;
|
|
138
|
+
throw new ProtocolError(PERMISSION_DENIED_RPC_CODE, message, {
|
|
139
|
+
code: "PERMISSION_DENIED",
|
|
140
|
+
message,
|
|
141
|
+
resource: entry.uri,
|
|
142
|
+
reason: "scope",
|
|
143
|
+
required_scopes: [...entry.scopes],
|
|
144
|
+
held_scopes: [...principal.scopes],
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
/** render 반환: string = user 텍스트 메시지 1개, 배열 = 그대로 (role · content 형식 검사). 그 외 = TypeError (핸들러 오류) */
|
|
148
|
+
function toMessages(name, value) {
|
|
149
|
+
if (typeof value === "string")
|
|
150
|
+
return [{ role: "user", content: { type: "text", text: value } }];
|
|
151
|
+
if (Array.isArray(value) && value.every(isMessage))
|
|
152
|
+
return value;
|
|
153
|
+
throw new TypeError(`${name}: render must return a string or an array of { role, content } messages`);
|
|
154
|
+
}
|
|
155
|
+
function isMessage(value) {
|
|
156
|
+
const m = value;
|
|
157
|
+
return (typeof m === "object" &&
|
|
158
|
+
m !== null &&
|
|
159
|
+
(m.role === "user" || m.role === "assistant") &&
|
|
160
|
+
typeof m.content === "object" &&
|
|
161
|
+
m.content !== null &&
|
|
162
|
+
typeof m.content.type === "string");
|
|
163
|
+
}
|
|
164
|
+
//# sourceMappingURL=prompts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prompts.js","sourceRoot":"","sources":["../../src/mcp/prompts.ts"],"names":[],"mappings":"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","sourcesContent":["import { ProtocolError, ProtocolErrorCode, type McpServer, type ServerContext } from \"@modelcontextprotocol/server\"\nimport { invalidArgument } from \"../invoke.ts\"\nimport { hasScope, type Principal } from \"../principal.ts\"\nimport type { PromptMessage, PromptSet, RegisteredPrompt } from \"../prompt.ts\"\nimport type { RegisteredResource, ResourceSet } from \"../resource.ts\"\nimport { PERMISSION_DENIED_RPC_CODE, runHandler, runResourceHandler, type HandlerRunOptions } from \"./resources.ts\"\n\n/**\n * MCP prompts · completion (§12.3): prompts/list · prompts/get · completion/complete (ref/prompt · ref/resource).\n * SDK McpServer.registerPrompt 는 쓰지 않는다 (인자 completion = completable() 스키마 래퍼, ctx 없음 · 목록 미정렬 ·\n * capability listChanged 기본 true · ref/resource completion = SDK 에 등록한 템플릿만). 저수준 Server.setRequestHandler 로 직접 처리한다\n */\n\n/** completion/complete 결과 최대 값 수 (MCP 사양) */\nexport const COMPLETION_MAX_VALUES = 100\n\nexport interface PromptHandlerOptions extends HandlerRunOptions {\n prompts: PromptSet\n}\n\nexport interface CompletionHandlerOptions extends HandlerRunOptions {\n prompts?: PromptSet\n resources?: ResourceSet\n}\n\n/** server 에 prompts capability { listChanged: false } 선언 + prompts/list · get (프롬프트 없음 = 아무것도 안 함). transport 연결 전에 호출 */\nexport function installPromptHandlers(server: McpServer, options: PromptHandlerOptions): void {\n if (options.prompts.size === 0) return\n const inner = server.server\n inner.registerCapabilities({ prompts: { listChanged: false } })\n // 목록 = 주체 무관 (tools/list 와 같은 public 캐시). scopes 는 get · completion 에서 검사\n inner.setRequestHandler(\"prompts/list\", () => ({\n prompts: [...options.prompts.values()].map((entry) => ({\n name: entry.name,\n ...(entry.title !== undefined ? { title: entry.title } : {}),\n description: entry.description,\n ...(entry.arguments.length > 0 ? { arguments: entry.arguments.map((a) => ({ ...a })) } : {}),\n })),\n }))\n inner.setRequestHandler(\"prompts/get\", async (request, ctx) => {\n const entry = findPrompt(options.prompts, request.params.name)\n authorize(entry, options.principal)\n const raw = request.params.arguments ?? {}\n // 모듈 로드 · 인자 검증 = render span 밖 (INVALID_ARGUMENT = -32602, 보고 없음)\n const prompt = await entry.load()\n let args: Record<string, unknown> = {}\n if (prompt.args) {\n const parsed = await prompt.args.safeParseAsync(raw)\n if (!parsed.success) {\n const error = invalidArgument(entry.name, raw, parsed.error.issues, entry.argsSchema ?? {}, [])\n const { command: _command, examples: _examples, ...body } = error.toJSON()\n throw new ProtocolError(ProtocolErrorCode.InvalidParams, error.message, { ...body, prompt: entry.name })\n }\n args = parsed.data\n }\n const messages = await runPromptHandler(options, entry, ctx, async (pctx) => toMessages(entry.name, await prompt.render(args, pctx)))\n return { description: entry.description, messages }\n })\n}\n\n/**\n * server 에 completions capability + completion/complete. complete 를 가진 프롬프트 · 자원 템플릿이 없으면 아무것도 안 함 (§12 표).\n * 결과 = 앞 100개 + total · hasMore. 없는 인자 · complete 없는 인자 = 빈 결과\n */\nexport function installCompletionHandler(server: McpServer, options: CompletionHandlerOptions): void {\n if (!hasCompletion(options)) return\n const inner = server.server\n inner.registerCapabilities({ completions: {} })\n inner.setRequestHandler(\"completion/complete\", async (request, ctx) => {\n const { ref, argument } = request.params\n const others = request.params.context?.arguments ?? {}\n if (ref.type === \"ref/prompt\") {\n if (!options.prompts) throw new ProtocolError(ProtocolErrorCode.InvalidParams, `Prompt ${ref.name} not found`, { name: ref.name })\n const entry = findPrompt(options.prompts, ref.name)\n authorize(entry, options.principal)\n if (!entry.complete.includes(argument.name)) return EMPTY_COMPLETION\n return runPromptHandler(options, entry, ctx, async (pctx) => {\n const prompt = await entry.load()\n return completionResult(entry.name, await prompt.complete[argument.name]!(argument.value, { ...pctx, arguments: { ...others } }))\n })\n }\n const uri = ref.uri\n const entry = options.resources?.templates.find((t) => t.uri === uri)\n if (!entry) {\n if (options.resources?.fixed.has(uri)) return EMPTY_COMPLETION\n throw new ProtocolError(ProtocolErrorCode.InvalidParams, `Resource template ${uri} not found`, { uri })\n }\n authorizeResource(entry, options.principal)\n if (!entry.complete.includes(argument.name)) return EMPTY_COMPLETION\n return runResourceHandler(options, entry, ctx, async (rctx) => {\n const resource = await entry.load()\n return completionResult(entry.uri, await resource.complete[argument.name]!(argument.value, { ...rctx, arguments: { ...others } }))\n })\n })\n}\n\nfunction hasCompletion(options: CompletionHandlerOptions): boolean {\n for (const entry of options.prompts?.values() ?? []) if (entry.complete.length > 0) return true\n return (options.resources?.templates ?? []).some((t) => t.complete.length > 0)\n}\n\nconst EMPTY_COMPLETION = { completion: { values: [], total: 0, hasMore: false } }\n\n/** complete 반환 → 결과 (앞 100개 + total · hasMore). 문자열 배열 아님 = TypeError (핸들러 오류) */\nfunction completionResult(owner: string, values: unknown): { completion: { values: string[]; total: number; hasMore: boolean } } {\n if (!Array.isArray(values) || values.some((v) => typeof v !== \"string\")) {\n throw new TypeError(`${owner}: complete must return an array of strings`)\n }\n return {\n completion: {\n values: values.slice(0, COMPLETION_MAX_VALUES) as string[],\n total: values.length,\n hasMore: values.length > COMPLETION_MAX_VALUES,\n },\n }\n}\n\n/** 프롬프트 핸들러 (render · complete) 1회: `mext.prompt <이름>` span + 오류 변환 */\nfunction runPromptHandler<T>(\n options: HandlerRunOptions,\n entry: RegisteredPrompt,\n ctx: ServerContext,\n fn: Parameters<typeof runHandler<T>>[3],\n): Promise<T> {\n const target = { span: `mext.prompt ${entry.name}`, attributes: { \"mext.prompt.name\": entry.name }, name: entry.name, info: { prompt: entry.name } }\n return runHandler(options, target, ctx, fn)\n}\n\nfunction findPrompt(prompts: PromptSet, name: string): RegisteredPrompt {\n const entry = prompts.get(name)\n if (!entry) throw new ProtocolError(ProtocolErrorCode.InvalidParams, `Prompt ${name} not found`, { name })\n return entry\n}\n\n/** scopes 부족 = -32001 + PERMISSION_DENIED 본문 (자원과 같은 형식, prompt = 이름) */\nfunction authorize(entry: RegisteredPrompt, principal: Principal): void {\n const missing = entry.scopes.filter((s) => !hasScope(principal, s))\n if (missing.length === 0) return\n const message = `${entry.name} requires scopes: ${missing.join(\", \")}.`\n throw new ProtocolError(PERMISSION_DENIED_RPC_CODE, message, {\n code: \"PERMISSION_DENIED\",\n message,\n prompt: entry.name,\n reason: \"scope\",\n required_scopes: [...entry.scopes],\n held_scopes: [...principal.scopes],\n })\n}\n\nfunction authorizeResource(entry: RegisteredResource, principal: Principal): void {\n const missing = entry.scopes.filter((s) => !hasScope(principal, s))\n if (missing.length === 0) return\n const message = `${entry.uri} requires scopes: ${missing.join(\", \")}.`\n throw new ProtocolError(PERMISSION_DENIED_RPC_CODE, message, {\n code: \"PERMISSION_DENIED\",\n message,\n resource: entry.uri,\n reason: \"scope\",\n required_scopes: [...entry.scopes],\n held_scopes: [...principal.scopes],\n })\n}\n\n/** render 반환: string = user 텍스트 메시지 1개, 배열 = 그대로 (role · content 형식 검사). 그 외 = TypeError (핸들러 오류) */\nfunction toMessages(name: string, value: unknown): PromptMessage[] {\n if (typeof value === \"string\") return [{ role: \"user\", content: { type: \"text\", text: value } }]\n if (Array.isArray(value) && value.every(isMessage)) return value as PromptMessage[]\n throw new TypeError(`${name}: render must return a string or an array of { role, content } messages`)\n}\n\nfunction isMessage(value: unknown): boolean {\n const m = value as Partial<PromptMessage> | null\n return (\n typeof m === \"object\" &&\n m !== null &&\n (m.role === \"user\" || m.role === \"assistant\") &&\n typeof m.content === \"object\" &&\n m.content !== null &&\n typeof m.content.type === \"string\"\n )\n}\n"]}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { type McpServer, type ServerContext } from "@modelcontextprotocol/server";
|
|
2
|
+
import type { MextApp } from "../command.ts";
|
|
3
|
+
import { type Instrumentation, type Transport } from "../instrumentation.ts";
|
|
4
|
+
import { type Principal } from "../principal.ts";
|
|
5
|
+
import { type RegisteredResource, type ResourceContext, type ResourceSet } from "../resource.ts";
|
|
6
|
+
/**
|
|
7
|
+
* MCP resources (§12.3): resources/list · resources/templates/list · resources/read.
|
|
8
|
+
* R1: SDK McpServer.registerResource 는 쓰지 않는다 (템플릿 매칭 = 등록 순, URI = new URL 정규화, 목록 = 주체 무관 · 미정렬,
|
|
9
|
+
* capability listChanged 기본 true). 저수준 Server.setRequestHandler 로 직접 처리한다
|
|
10
|
+
*/
|
|
11
|
+
/** resources/read 캐시 힌트. 내용은 주체 · 시점에 따라 다르다 */
|
|
12
|
+
export declare const RESOURCE_READ_CACHE: {
|
|
13
|
+
readonly ttlMs: 0;
|
|
14
|
+
readonly cacheScope: "private";
|
|
15
|
+
};
|
|
16
|
+
/** scopes 부족 (명령어 UNAUTHENTICATED 와 같은 -32001, data = PERMISSION_DENIED 본문) */
|
|
17
|
+
export declare const PERMISSION_DENIED_RPC_CODE = -32001;
|
|
18
|
+
/** MextError · CommandError (data = 오류 본문) */
|
|
19
|
+
export declare const DECLARED_ERROR_RPC_CODE = -32000;
|
|
20
|
+
export interface ResourceHandlerOptions {
|
|
21
|
+
resources: ResourceSet;
|
|
22
|
+
principal: Principal;
|
|
23
|
+
/** ctx.app (memoized) */
|
|
24
|
+
app: () => Promise<MextApp>;
|
|
25
|
+
instrumentation?: Instrumentation;
|
|
26
|
+
transport?: Transport;
|
|
27
|
+
/** span 의 부모 trace context (params._meta) */
|
|
28
|
+
traceContext?: (ctx: ServerContext) => Record<string, string> | undefined;
|
|
29
|
+
}
|
|
30
|
+
/** server 에 resources capability { subscribe: false, listChanged: false } 선언 + 핸들러 3개 (자원 없음 = 아무것도 안 함). transport 연결 전에 호출 */
|
|
31
|
+
export declare function installResourceHandlers(server: McpServer, options: ResourceHandlerOptions): void;
|
|
32
|
+
/** 자원 핸들러 (read · list · complete) 1회: `mext.resource <uri 템플릿>` span + 오류 변환 (runHandler) */
|
|
33
|
+
export declare function runResourceHandler<T>(options: HandlerRunOptions, entry: RegisteredResource, ctx: ServerContext, fn: (rctx: ResourceContext) => Promise<T>): Promise<T>;
|
|
34
|
+
/** runHandler 의 옵션 (자원 · 프롬프트 핸들러 공통) */
|
|
35
|
+
export type HandlerRunOptions = Omit<ResourceHandlerOptions, "resources">;
|
|
36
|
+
/**
|
|
37
|
+
* MCP 핸들러 (자원 · 프롬프트, §12.3) 1회: span + 오류 변환.
|
|
38
|
+
* MextError · CommandError = -32000 + data 본문, 그 외 = onCommandError 보고 후 -32603. 취소 · ProtocolError = 그대로 전파
|
|
39
|
+
*/
|
|
40
|
+
export declare function runHandler<T>(options: HandlerRunOptions, target: {
|
|
41
|
+
span: string;
|
|
42
|
+
attributes: Record<string, string>;
|
|
43
|
+
name: string;
|
|
44
|
+
info: {
|
|
45
|
+
resource?: string;
|
|
46
|
+
prompt?: string;
|
|
47
|
+
};
|
|
48
|
+
}, ctx: ServerContext, fn: (rctx: ResourceContext) => Promise<T>): Promise<T>;
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { ProtocolError, ProtocolErrorCode, ResourceNotFoundError } from "@modelcontextprotocol/server";
|
|
2
|
+
import { isDeclaredError } from "../errors.js";
|
|
3
|
+
import { observeSpan, reportCommandError } from "../instrumentation.js";
|
|
4
|
+
import { hasScope } from "../principal.js";
|
|
5
|
+
import { matchResource } from "../resource.js";
|
|
6
|
+
/**
|
|
7
|
+
* MCP resources (§12.3): resources/list · resources/templates/list · resources/read.
|
|
8
|
+
* R1: SDK McpServer.registerResource 는 쓰지 않는다 (템플릿 매칭 = 등록 순, URI = new URL 정규화, 목록 = 주체 무관 · 미정렬,
|
|
9
|
+
* capability listChanged 기본 true). 저수준 Server.setRequestHandler 로 직접 처리한다
|
|
10
|
+
*/
|
|
11
|
+
/** resources/read 캐시 힌트. 내용은 주체 · 시점에 따라 다르다 */
|
|
12
|
+
export const RESOURCE_READ_CACHE = { ttlMs: 0, cacheScope: "private" };
|
|
13
|
+
/** scopes 부족 (명령어 UNAUTHENTICATED 와 같은 -32001, data = PERMISSION_DENIED 본문) */
|
|
14
|
+
export const PERMISSION_DENIED_RPC_CODE = -32_001;
|
|
15
|
+
/** MextError · CommandError (data = 오류 본문) */
|
|
16
|
+
export const DECLARED_ERROR_RPC_CODE = -32_000;
|
|
17
|
+
/** server 에 resources capability { subscribe: false, listChanged: false } 선언 + 핸들러 3개 (자원 없음 = 아무것도 안 함). transport 연결 전에 호출 */
|
|
18
|
+
export function installResourceHandlers(server, options) {
|
|
19
|
+
if (options.resources.fixed.size === 0 && options.resources.templates.length === 0)
|
|
20
|
+
return;
|
|
21
|
+
const inner = server.server;
|
|
22
|
+
inner.registerCapabilities({ resources: { subscribe: false, listChanged: false } });
|
|
23
|
+
inner.setRequestHandler("resources/list", async (_request, ctx) => ({ resources: await listResources(options, ctx) }));
|
|
24
|
+
inner.setRequestHandler("resources/templates/list", () => ({
|
|
25
|
+
resourceTemplates: options.resources.templates
|
|
26
|
+
.filter((entry) => permitted(entry, options.principal))
|
|
27
|
+
.map((entry) => ({ uriTemplate: entry.uri, name: entry.uri, ...describe(entry) }))
|
|
28
|
+
.sort((a, b) => compare(a.uriTemplate, b.uriTemplate)),
|
|
29
|
+
}));
|
|
30
|
+
inner.setRequestHandler("resources/read", async (request, ctx) => ({
|
|
31
|
+
contents: [await readResource(options, request.params.uri, ctx)],
|
|
32
|
+
}));
|
|
33
|
+
}
|
|
34
|
+
/** 고정 URI 자원 + 템플릿 list 결과. scopes 없는 자원 제외, uri 순 */
|
|
35
|
+
async function listResources(options, ctx) {
|
|
36
|
+
const items = [];
|
|
37
|
+
for (const entry of options.resources.fixed.values()) {
|
|
38
|
+
if (permitted(entry, options.principal))
|
|
39
|
+
items.push({ uri: entry.uri, name: entry.uri, ...describe(entry) });
|
|
40
|
+
}
|
|
41
|
+
for (const entry of options.resources.templates) {
|
|
42
|
+
if (!entry.list || !permitted(entry, options.principal))
|
|
43
|
+
continue;
|
|
44
|
+
const listed = await runResourceHandler(options, entry, ctx, async (rctx) => {
|
|
45
|
+
const resource = await entry.load();
|
|
46
|
+
const result = await resource.list(rctx);
|
|
47
|
+
if (!Array.isArray(result))
|
|
48
|
+
throw new TypeError(`${entry.uri}: list must return an array`);
|
|
49
|
+
return result;
|
|
50
|
+
});
|
|
51
|
+
for (const item of listed) {
|
|
52
|
+
if (typeof item?.uri !== "string")
|
|
53
|
+
throw new ProtocolError(ProtocolErrorCode.InternalError, `${entry.uri}: list item without a uri`);
|
|
54
|
+
items.push({ ...describe(entry), ...item, name: typeof item.name === "string" ? item.name : item.uri });
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return items.sort((a, b) => compare(a.uri, b.uri));
|
|
58
|
+
}
|
|
59
|
+
async function readResource(options, uri, ctx) {
|
|
60
|
+
const matched = matchResource(options.resources, uri);
|
|
61
|
+
if (!matched)
|
|
62
|
+
throw new ResourceNotFoundError(uri);
|
|
63
|
+
const { entry, variables } = matched;
|
|
64
|
+
const missing = entry.scopes.filter((s) => !hasScope(options.principal, s));
|
|
65
|
+
if (missing.length > 0) {
|
|
66
|
+
const message = `${uri} requires scopes: ${missing.join(", ")}.`;
|
|
67
|
+
throw new ProtocolError(PERMISSION_DENIED_RPC_CODE, message, {
|
|
68
|
+
code: "PERMISSION_DENIED",
|
|
69
|
+
message,
|
|
70
|
+
resource: uri,
|
|
71
|
+
reason: "scope",
|
|
72
|
+
required_scopes: [...entry.scopes],
|
|
73
|
+
held_scopes: [...options.principal.scopes],
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
return runResourceHandler(options, entry, ctx, async (rctx) => {
|
|
77
|
+
const resource = await entry.load();
|
|
78
|
+
return contents(uri, entry, await resource.read(variables, rctx));
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
/** D3: contents[].uri = 요청 URI 그대로. string = text · Uint8Array = blob (base64) · 그 외 = JSON text */
|
|
82
|
+
function contents(uri, entry, value) {
|
|
83
|
+
if (typeof value === "string")
|
|
84
|
+
return { uri, ...(entry.mimeType !== undefined ? { mimeType: entry.mimeType } : {}), text: value };
|
|
85
|
+
if (value instanceof Uint8Array) {
|
|
86
|
+
return { uri, ...(entry.mimeType !== undefined ? { mimeType: entry.mimeType } : {}), blob: Buffer.from(value).toString("base64") };
|
|
87
|
+
}
|
|
88
|
+
const text = JSON.stringify(value === undefined ? null : value, null, 2);
|
|
89
|
+
return { uri, mimeType: entry.mimeType ?? "application/json", text };
|
|
90
|
+
}
|
|
91
|
+
/** 자원 핸들러 (read · list · complete) 1회: `mext.resource <uri 템플릿>` span + 오류 변환 (runHandler) */
|
|
92
|
+
export function runResourceHandler(options, entry, ctx, fn) {
|
|
93
|
+
const target = { span: `mext.resource ${entry.uri}`, attributes: { "mext.resource.uri": entry.uri }, name: entry.uri, info: { resource: entry.uri } };
|
|
94
|
+
return runHandler(options, target, ctx, fn);
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* MCP 핸들러 (자원 · 프롬프트, §12.3) 1회: span + 오류 변환.
|
|
98
|
+
* MextError · CommandError = -32000 + data 본문, 그 외 = onCommandError 보고 후 -32603. 취소 · ProtocolError = 그대로 전파
|
|
99
|
+
*/
|
|
100
|
+
export async function runHandler(options, target, ctx, fn) {
|
|
101
|
+
const signal = ctx.mcpReq.signal;
|
|
102
|
+
const traceContext = options.traceContext?.(ctx);
|
|
103
|
+
const observe = {
|
|
104
|
+
principal: options.principal,
|
|
105
|
+
...(options.instrumentation ? { instrumentation: options.instrumentation } : {}),
|
|
106
|
+
...(options.transport ? { transport: options.transport } : {}),
|
|
107
|
+
...(traceContext ? { traceContext } : {}),
|
|
108
|
+
};
|
|
109
|
+
try {
|
|
110
|
+
return await observeSpan(target.span, target.attributes, observe, () => signal, async () => {
|
|
111
|
+
const rctx = { principal: options.principal, signal, app: await options.app() };
|
|
112
|
+
try {
|
|
113
|
+
return await fn(rctx);
|
|
114
|
+
}
|
|
115
|
+
catch (error) {
|
|
116
|
+
await reportCommandError(error, target.name, observe, signal, target.info);
|
|
117
|
+
throw error;
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
catch (error) {
|
|
122
|
+
if (signal.aborted || error instanceof ProtocolError)
|
|
123
|
+
throw error;
|
|
124
|
+
if (isDeclaredError(error))
|
|
125
|
+
throw new ProtocolError(DECLARED_ERROR_RPC_CODE, error.message, error.toJSON());
|
|
126
|
+
throw new ProtocolError(ProtocolErrorCode.InternalError, error instanceof Error ? error.message : String(error));
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
function describe(entry) {
|
|
130
|
+
return {
|
|
131
|
+
...(entry.title !== undefined ? { title: entry.title } : {}),
|
|
132
|
+
description: entry.description,
|
|
133
|
+
...(entry.mimeType !== undefined ? { mimeType: entry.mimeType } : {}),
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
function permitted(entry, principal) {
|
|
137
|
+
return entry.scopes.every((s) => hasScope(principal, s));
|
|
138
|
+
}
|
|
139
|
+
function compare(a, b) {
|
|
140
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
141
|
+
}
|
|
142
|
+
//# sourceMappingURL=resources.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resources.js","sourceRoot":"","sources":["../../src/mcp/resources.ts"],"names":[],"mappings":"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","sourcesContent":["import { ProtocolError, ProtocolErrorCode, ResourceNotFoundError, type McpServer, type ServerContext } from \"@modelcontextprotocol/server\"\nimport type { MextApp } from \"../command.ts\"\nimport { isDeclaredError } from \"../errors.ts\"\nimport { observeSpan, reportCommandError, type Instrumentation, type Transport } from \"../instrumentation.ts\"\nimport { hasScope, type Principal } from \"../principal.ts\"\nimport { matchResource, type RegisteredResource, type ResourceContext, type ResourceListItem, type ResourceSet } from \"../resource.ts\"\n\n/**\n * MCP resources (§12.3): resources/list · resources/templates/list · resources/read.\n * R1: SDK McpServer.registerResource 는 쓰지 않는다 (템플릿 매칭 = 등록 순, URI = new URL 정규화, 목록 = 주체 무관 · 미정렬,\n * capability listChanged 기본 true). 저수준 Server.setRequestHandler 로 직접 처리한다\n */\n\n/** resources/read 캐시 힌트. 내용은 주체 · 시점에 따라 다르다 */\nexport const RESOURCE_READ_CACHE = { ttlMs: 0, cacheScope: \"private\" } as const\n\n/** scopes 부족 (명령어 UNAUTHENTICATED 와 같은 -32001, data = PERMISSION_DENIED 본문) */\nexport const PERMISSION_DENIED_RPC_CODE = -32_001\n/** MextError · CommandError (data = 오류 본문) */\nexport const DECLARED_ERROR_RPC_CODE = -32_000\n\nexport interface ResourceHandlerOptions {\n resources: ResourceSet\n principal: Principal\n /** ctx.app (memoized) */\n app: () => Promise<MextApp>\n instrumentation?: Instrumentation\n transport?: Transport\n /** span 의 부모 trace context (params._meta) */\n traceContext?: (ctx: ServerContext) => Record<string, string> | undefined\n}\n\n/** server 에 resources capability { subscribe: false, listChanged: false } 선언 + 핸들러 3개 (자원 없음 = 아무것도 안 함). transport 연결 전에 호출 */\nexport function installResourceHandlers(server: McpServer, options: ResourceHandlerOptions): void {\n if (options.resources.fixed.size === 0 && options.resources.templates.length === 0) return\n const inner = server.server\n inner.registerCapabilities({ resources: { subscribe: false, listChanged: false } })\n inner.setRequestHandler(\"resources/list\", async (_request, ctx) => ({ resources: await listResources(options, ctx) }))\n inner.setRequestHandler(\"resources/templates/list\", () => ({\n resourceTemplates: options.resources.templates\n .filter((entry) => permitted(entry, options.principal))\n .map((entry) => ({ uriTemplate: entry.uri, name: entry.uri, ...describe(entry) }))\n .sort((a, b) => compare(a.uriTemplate, b.uriTemplate)),\n }))\n inner.setRequestHandler(\"resources/read\", async (request, ctx) => ({\n contents: [await readResource(options, request.params.uri, ctx)],\n }))\n}\n\n/** 고정 URI 자원 + 템플릿 list 결과. scopes 없는 자원 제외, uri 순 */\nasync function listResources(options: ResourceHandlerOptions, ctx: ServerContext): Promise<ResourceListItem[]> {\n const items: ResourceListItem[] = []\n for (const entry of options.resources.fixed.values()) {\n if (permitted(entry, options.principal)) items.push({ uri: entry.uri, name: entry.uri, ...describe(entry) })\n }\n for (const entry of options.resources.templates) {\n if (!entry.list || !permitted(entry, options.principal)) continue\n const listed = await runResourceHandler(options, entry, ctx, async (rctx) => {\n const resource = await entry.load()\n const result = await resource.list!(rctx)\n if (!Array.isArray(result)) throw new TypeError(`${entry.uri}: list must return an array`)\n return result as readonly ResourceListItem[]\n })\n for (const item of listed) {\n if (typeof item?.uri !== \"string\") throw new ProtocolError(ProtocolErrorCode.InternalError, `${entry.uri}: list item without a uri`)\n items.push({ ...describe(entry), ...item, name: typeof item.name === \"string\" ? item.name : item.uri })\n }\n }\n return items.sort((a, b) => compare(a.uri, b.uri))\n}\n\ntype ResourceContents = { uri: string; mimeType?: string } & ({ text: string } | { blob: string })\n\nasync function readResource(options: ResourceHandlerOptions, uri: string, ctx: ServerContext): Promise<ResourceContents> {\n const matched = matchResource(options.resources, uri)\n if (!matched) throw new ResourceNotFoundError(uri)\n const { entry, variables } = matched\n const missing = entry.scopes.filter((s) => !hasScope(options.principal, s))\n if (missing.length > 0) {\n const message = `${uri} requires scopes: ${missing.join(\", \")}.`\n throw new ProtocolError(PERMISSION_DENIED_RPC_CODE, message, {\n code: \"PERMISSION_DENIED\",\n message,\n resource: uri,\n reason: \"scope\",\n required_scopes: [...entry.scopes],\n held_scopes: [...options.principal.scopes],\n })\n }\n return runResourceHandler(options, entry, ctx, async (rctx) => {\n const resource = await entry.load()\n return contents(uri, entry, await resource.read(variables, rctx))\n })\n}\n\n/** D3: contents[].uri = 요청 URI 그대로. string = text · Uint8Array = blob (base64) · 그 외 = JSON text */\nfunction contents(uri: string, entry: RegisteredResource, value: unknown): ResourceContents {\n if (typeof value === \"string\") return { uri, ...(entry.mimeType !== undefined ? { mimeType: entry.mimeType } : {}), text: value }\n if (value instanceof Uint8Array) {\n return { uri, ...(entry.mimeType !== undefined ? { mimeType: entry.mimeType } : {}), blob: Buffer.from(value).toString(\"base64\") }\n }\n const text = JSON.stringify(value === undefined ? null : value, null, 2)\n return { uri, mimeType: entry.mimeType ?? \"application/json\", text }\n}\n\n/** 자원 핸들러 (read · list · complete) 1회: `mext.resource <uri 템플릿>` span + 오류 변환 (runHandler) */\nexport function runResourceHandler<T>(\n options: HandlerRunOptions,\n entry: RegisteredResource,\n ctx: ServerContext,\n fn: (rctx: ResourceContext) => Promise<T>,\n): Promise<T> {\n const target = { span: `mext.resource ${entry.uri}`, attributes: { \"mext.resource.uri\": entry.uri }, name: entry.uri, info: { resource: entry.uri } }\n return runHandler(options, target, ctx, fn)\n}\n\n/** runHandler 의 옵션 (자원 · 프롬프트 핸들러 공통) */\nexport type HandlerRunOptions = Omit<ResourceHandlerOptions, \"resources\">\n\n/**\n * MCP 핸들러 (자원 · 프롬프트, §12.3) 1회: span + 오류 변환.\n * MextError · CommandError = -32000 + data 본문, 그 외 = onCommandError 보고 후 -32603. 취소 · ProtocolError = 그대로 전파\n */\nexport async function runHandler<T>(\n options: HandlerRunOptions,\n target: { span: string; attributes: Record<string, string>; name: string; info: { resource?: string; prompt?: string } },\n ctx: ServerContext,\n fn: (rctx: ResourceContext) => Promise<T>,\n): Promise<T> {\n const signal = ctx.mcpReq.signal\n const traceContext = options.traceContext?.(ctx)\n const observe = {\n principal: options.principal,\n ...(options.instrumentation ? { instrumentation: options.instrumentation } : {}),\n ...(options.transport ? { transport: options.transport } : {}),\n ...(traceContext ? { traceContext } : {}),\n }\n try {\n return await observeSpan(target.span, target.attributes, observe, () => signal, async () => {\n const rctx: ResourceContext = { principal: options.principal, signal, app: await options.app() }\n try {\n return await fn(rctx)\n } catch (error) {\n await reportCommandError(error, target.name, observe, signal, target.info)\n throw error\n }\n })\n } catch (error) {\n if (signal.aborted || error instanceof ProtocolError) throw error\n if (isDeclaredError(error)) throw new ProtocolError(DECLARED_ERROR_RPC_CODE, error.message, error.toJSON())\n throw new ProtocolError(ProtocolErrorCode.InternalError, error instanceof Error ? error.message : String(error))\n }\n}\n\nfunction describe(entry: RegisteredResource): { title?: string; description: string; mimeType?: string } {\n return {\n ...(entry.title !== undefined ? { title: entry.title } : {}),\n description: entry.description,\n ...(entry.mimeType !== undefined ? { mimeType: entry.mimeType } : {}),\n }\n}\n\nfunction permitted(entry: RegisteredResource, principal: Principal): boolean {\n return entry.scopes.every((s) => hasScope(principal, s))\n}\n\nfunction compare(a: string, b: string): number {\n return a < b ? -1 : a > b ? 1 : 0\n}\n"]}
|
package/dist/mcp/server.d.ts
CHANGED
|
@@ -5,11 +5,18 @@ import type { Instrumentation, Transport } from "../instrumentation.ts";
|
|
|
5
5
|
import { type Principal } from "../principal.ts";
|
|
6
6
|
import type { Registry } from "../registry.ts";
|
|
7
7
|
import { type ToolExposure } from "./dispatch.ts";
|
|
8
|
+
import type { MextState } from "../state.ts";
|
|
9
|
+
import { type TaskTiming } from "./tasks.ts";
|
|
8
10
|
/** tools/list · server/discover 캐시 힌트. 도구 정의는 principal 과 무관하게 고정 → public */
|
|
9
11
|
export declare const TOOLS_LIST_CACHE: {
|
|
10
12
|
readonly ttlMs: 300000;
|
|
11
13
|
readonly cacheScope: "public";
|
|
12
14
|
};
|
|
15
|
+
/** resources/list · templates/list 캐시 힌트. ttlMs = tools/list, 결과가 scopes 로 주체별 필터 → private */
|
|
16
|
+
export declare const RESOURCE_LIST_CACHE: {
|
|
17
|
+
readonly ttlMs: 300000;
|
|
18
|
+
readonly cacheScope: "private";
|
|
19
|
+
};
|
|
13
20
|
/**
|
|
14
21
|
* progressToken 있는 요청의 주기 progress 간격 (ms). 마지막 알림 후 이 시간 동안 알림이 없으면 보낸다.
|
|
15
22
|
* TS SDK 클라이언트 기본 타임아웃 60초 · 프록시 유휴 타임아웃 (보통 60초) 보다 충분히 짧게
|
|
@@ -19,7 +26,7 @@ export interface McpCoreOptions {
|
|
|
19
26
|
registry: Registry;
|
|
20
27
|
/** ctx.app 생성. 처음 필요할 때 한 번 호출 (실패하면 다음 요청에서 재시도) */
|
|
21
28
|
app?: () => MextApp | Promise<MextApp>;
|
|
22
|
-
/** 기본 execute */
|
|
29
|
+
/** 기본 execute. command = 명령어별 도구 (§12.2) */
|
|
23
30
|
tools?: ToolExposure;
|
|
24
31
|
/** serverInfo.name. 기본 "mext" */
|
|
25
32
|
name?: string;
|
|
@@ -50,14 +57,32 @@ export interface McpCoreOptions {
|
|
|
50
57
|
* mext start = manifest.json 의 manifestHash (같은 함수로 계산한 값)
|
|
51
58
|
*/
|
|
52
59
|
manifestHash?: string;
|
|
60
|
+
/** 주입 state (§12.1). 자원 구독 (resources.subscribe · ctx.resourceUpdated, §12.5). 없음 = stateless */
|
|
61
|
+
state?: MextState;
|
|
62
|
+
/** 응답에 영향 없는 오류 보고 (state.bus 구독 실패) */
|
|
63
|
+
onerror?: (error: Error) => void;
|
|
64
|
+
/** task 시간 간격 (테스트). 기본 TASK_TIMING */
|
|
65
|
+
taskTiming?: Partial<TaskTiming>;
|
|
66
|
+
/**
|
|
67
|
+
* task 실행 유지 수단. process (기본) = 이 프로세스가 응답 후 계속 실행 (node 서버 · stdio),
|
|
68
|
+
* waitUntil = 요청이 waitUntil 을 넘긴 경우만 task (handler.js, §10). 없으면 확장 미선언 · 동기 실행
|
|
69
|
+
*/
|
|
70
|
+
taskExecution?: "process" | "waitUntil";
|
|
53
71
|
}
|
|
72
|
+
/** createMcpServerFactory 결과. close = 실행 중 task 취소 · cancelled 기록 (상한 5초, §12.6) */
|
|
73
|
+
export type MextServerFactory = McpServerFactory & {
|
|
74
|
+
close(): Promise<void>;
|
|
75
|
+
};
|
|
54
76
|
/**
|
|
55
77
|
* Registry → MCP 서버 factory (SDK McpServerFactory). 요청 (HTTP) · 연결 (stdio) 마다 새 McpServer.
|
|
56
78
|
* HTTP 의 createMcpHandler 와 stdio 의 serveStdio 가 같은 factory 를 쓴다.
|
|
57
79
|
*/
|
|
58
|
-
export declare function createMcpServerFactory(options: McpCoreOptions):
|
|
59
|
-
/** registry 명령어
|
|
80
|
+
export declare function createMcpServerFactory(options: McpCoreOptions): MextServerFactory;
|
|
81
|
+
/** registry 명령어 메타데이터 · 자원 uri · 프롬프트 이름 · 스키마의 manifestHash (빌드 manifest 와 같은 계산) */
|
|
60
82
|
export declare function registryManifestHash(registry: Registry): string;
|
|
61
|
-
/**
|
|
62
|
-
|
|
83
|
+
/**
|
|
84
|
+
* HTTP transport: 검증된 principal → SDK authInfo (토큰 원문은 담지 않는다).
|
|
85
|
+
* waitUntil = handler.js 요청의 응답 후 실행 유지 수단 (tasks, §10) 을 같은 경로로 factory 에 전달
|
|
86
|
+
*/
|
|
87
|
+
export declare function principalAuthInfo(principal: Principal, waitUntil?: (promise: Promise<unknown>) => void): AuthInfo;
|
|
63
88
|
export declare function principalFromAuthInfo(authInfo: AuthInfo | undefined): Principal | undefined;
|