@absolutejs/mcp 0.8.0 → 0.10.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/README.md +31 -5
- package/dist/index.js +270 -45
- package/dist/manifest.js +8 -0
- package/dist/src/client.d.ts +18 -0
- package/dist/src/dispatch.d.ts +3 -0
- package/dist/src/index.d.ts +3 -3
- package/dist/src/sessions.d.ts +2 -1
- package/dist/src/tasks.d.ts +2 -5
- package/dist/src/types.d.ts +29 -2
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
# @absolutejs/mcp
|
|
2
2
|
|
|
3
|
+
MCP tool discovery preserves the OpenID AuthZEN COAZ `coaz` marker and
|
|
4
|
+
`x-coaz-mapping` JSON Schema extension end to end. Use `@absolutejs/policy` to
|
|
5
|
+
validate and evaluate the mapping before dispatching an authorized tool call.
|
|
6
|
+
|
|
3
7
|
Serve a remote [Model Context Protocol](https://modelcontextprotocol.io) endpoint
|
|
4
8
|
— streamable HTTP, stateless — from a tool/prompt/resource registry. You supply
|
|
5
9
|
**which** tools to expose and **how** to authorize a request into a caller; the
|
|
6
10
|
package owns the JSON-RPC protocol, protocol-version negotiation, RFC 9728
|
|
7
11
|
discovery metadata, and the `401` challenge that lets a client find your
|
|
8
|
-
authorization server.
|
|
12
|
+
authorization server. The default negotiated revision is the current finalized
|
|
13
|
+
`2025-11-25` specification; older finalized revisions remain available when
|
|
14
|
+
explicitly requested.
|
|
9
15
|
|
|
10
16
|
## Agent action enforcement
|
|
11
17
|
|
|
@@ -36,10 +42,11 @@ mcpServer<Caller>({
|
|
|
36
42
|
|
|
37
43
|
## Durable Tasks
|
|
38
44
|
|
|
39
|
-
The package implements
|
|
40
|
-
|
|
41
|
-
`tasks/
|
|
42
|
-
|
|
45
|
+
The package implements native MCP `2025-11-25` task augmentation:
|
|
46
|
+
`execution.taskSupport`, client-requested task creation, `tasks/get`,
|
|
47
|
+
`tasks/result`, authorization-bound `tasks/list`, and terminal-safe
|
|
48
|
+
`tasks/cancel`. It also retains the older `io.modelcontextprotocol/tasks`
|
|
49
|
+
SEP-2663 wire shape only when an older protocol revision is negotiated.
|
|
43
50
|
|
|
44
51
|
```ts
|
|
45
52
|
tasks: {
|
|
@@ -48,8 +55,20 @@ tasks: {
|
|
|
48
55
|
store: createMemoryMcpTaskStore(), // use a durable shared store in production
|
|
49
56
|
ttlMs: 60 * 60 * 1000,
|
|
50
57
|
}
|
|
58
|
+
|
|
59
|
+
tools: () => ({
|
|
60
|
+
long_running_report: {
|
|
61
|
+
taskSupport: "optional", // "required" and "forbidden" are also supported
|
|
62
|
+
// normal tool definition…
|
|
63
|
+
},
|
|
64
|
+
})
|
|
51
65
|
```
|
|
52
66
|
|
|
67
|
+
Clients can use `callToolAsTask`, then `getTask`, `listTasks`, `cancelTask`, and
|
|
68
|
+
`getTaskResult`. Task status never exposes the stored result or authorization
|
|
69
|
+
key; the final result is returned only by `tasks/result` with required
|
|
70
|
+
`io.modelcontextprotocol/related-task` metadata.
|
|
71
|
+
|
|
53
72
|
For multi-instance production deployments, use
|
|
54
73
|
`createPostgresMcpTaskStore()` and `createPostgresMcpSessionStore()` after
|
|
55
74
|
applying `mcpPostgresSchemaSql()`. Task updates and cancellation protect
|
|
@@ -224,6 +243,13 @@ dismissed it), or `unsupported` (this client can't ask anyone — check
|
|
|
224
243
|
`canElicit` and take another path). Never fabricate an answer for the user; the
|
|
225
244
|
spec also forbids eliciting **sensitive information**.
|
|
226
245
|
|
|
246
|
+
For credentials, third-party OAuth, or payment flows, use `mode: "url"` with a
|
|
247
|
+
unique `elicitationId` and HTTPS URL. The client advertises form and URL modes
|
|
248
|
+
separately, never prefetches the URL, and returns only the user's consent—not
|
|
249
|
+
credentials or page contents. Tool handlers can check `canElicitUrl` before
|
|
250
|
+
starting the flow. The server rejects non-HTTPS URLs except localhost
|
|
251
|
+
development URLs and rejects URLs containing embedded credentials.
|
|
252
|
+
|
|
227
253
|
**The trade-off, stated plainly.** Elicitation is the one MCP feature a
|
|
228
254
|
stateless server cannot do: the question goes out on the SSE stream of an
|
|
229
255
|
in-flight `tools/call`, and the client answers on a _separate_ HTTP POST. Two
|
package/dist/index.js
CHANGED
|
@@ -46,7 +46,7 @@ var verifyBearer = async (config) => {
|
|
|
46
46
|
};
|
|
47
47
|
// src/client.ts
|
|
48
48
|
var DEFAULT_TIMEOUT_MS = 30000;
|
|
49
|
-
var DEFAULT_PROTOCOL = "2025-
|
|
49
|
+
var DEFAULT_PROTOCOL = "2025-11-25";
|
|
50
50
|
|
|
51
51
|
class McpClientError extends Error {
|
|
52
52
|
code;
|
|
@@ -152,12 +152,19 @@ var createMcpClient = (options) => {
|
|
|
152
152
|
const answerServer = async (message) => {
|
|
153
153
|
if (message.method !== "elicitation/create")
|
|
154
154
|
return;
|
|
155
|
-
const
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
155
|
+
const params = isRecord(message.params) ? message.params : {};
|
|
156
|
+
const request = params.mode === "url" && typeof params.elicitationId === "string" && typeof params.url === "string" ? {
|
|
157
|
+
elicitationId: params.elicitationId,
|
|
158
|
+
message: typeof params.message === "string" ? params.message : "",
|
|
159
|
+
mode: "url",
|
|
160
|
+
url: params.url
|
|
161
|
+
} : {
|
|
162
|
+
message: typeof params.message === "string" ? params.message : "",
|
|
163
|
+
mode: "form",
|
|
164
|
+
requestedSchema: isRecord(params.requestedSchema) ? params.requestedSchema : {}
|
|
165
|
+
};
|
|
159
166
|
const result = options.onElicit ? await options.onElicit(request) : { action: "decline" };
|
|
160
|
-
await respond(message.id, result);
|
|
167
|
+
await respond(message.id, request.mode === "url" && result.action === "accept" ? { action: "accept" } : result);
|
|
161
168
|
};
|
|
162
169
|
const rpc = async (method, params) => {
|
|
163
170
|
const controller = new AbortController;
|
|
@@ -243,7 +250,7 @@ var createMcpClient = (options) => {
|
|
|
243
250
|
};
|
|
244
251
|
const initialize = async () => {
|
|
245
252
|
const result = await rpc("initialize", {
|
|
246
|
-
capabilities: options.onElicit ? { elicitation: {} } : {},
|
|
253
|
+
capabilities: options.onElicit ? { elicitation: { form: {}, url: {} } } : {},
|
|
247
254
|
clientInfo: options.clientInfo ?? {
|
|
248
255
|
name: "@absolutejs/mcp",
|
|
249
256
|
version: "0"
|
|
@@ -265,10 +272,12 @@ var createMcpClient = (options) => {
|
|
|
265
272
|
const tools = isRecord(result) && Array.isArray(result.tools) ? result.tools : [];
|
|
266
273
|
collected.push(...tools.filter(isRecord).map((tool) => ({
|
|
267
274
|
annotations: isRecord(tool.annotations) ? tool.annotations : undefined,
|
|
275
|
+
coaz: typeof tool.coaz === "boolean" ? tool.coaz : undefined,
|
|
268
276
|
description: typeof tool.description === "string" ? tool.description : undefined,
|
|
269
277
|
inputSchema: isRecord(tool.inputSchema) ? tool.inputSchema : undefined,
|
|
270
278
|
name: typeof tool.name === "string" ? tool.name : "",
|
|
271
|
-
outputSchema: isRecord(tool.outputSchema) ? tool.outputSchema : undefined
|
|
279
|
+
outputSchema: isRecord(tool.outputSchema) ? tool.outputSchema : undefined,
|
|
280
|
+
taskSupport: isRecord(tool.execution) && (tool.execution.taskSupport === "forbidden" || tool.execution.taskSupport === "optional" || tool.execution.taskSupport === "required") ? tool.execution.taskSupport : undefined
|
|
272
281
|
})));
|
|
273
282
|
const next = isRecord(result) && typeof result.nextCursor === "string" ? result.nextCursor : undefined;
|
|
274
283
|
if (next === undefined)
|
|
@@ -284,6 +293,44 @@ var createMcpClient = (options) => {
|
|
|
284
293
|
}
|
|
285
294
|
return { content: [], isError: false };
|
|
286
295
|
};
|
|
296
|
+
const taskFrom = (value) => {
|
|
297
|
+
if (!isRecord(value) || typeof value.taskId !== "string") {
|
|
298
|
+
throw new McpClientError("Malformed MCP task response");
|
|
299
|
+
}
|
|
300
|
+
return value;
|
|
301
|
+
};
|
|
302
|
+
const callToolAsTask = async (name, args, options2 = {}) => {
|
|
303
|
+
const result = await rpc("tools/call", {
|
|
304
|
+
arguments: args ?? {},
|
|
305
|
+
name,
|
|
306
|
+
task: options2.ttl === undefined ? {} : { ttl: options2.ttl }
|
|
307
|
+
});
|
|
308
|
+
return taskFrom(isRecord(result) ? result.task : undefined);
|
|
309
|
+
};
|
|
310
|
+
const getTask = async (taskId) => taskFrom(await rpc("tasks/get", { taskId }));
|
|
311
|
+
const cancelTask = async (taskId) => taskFrom(await rpc("tasks/cancel", { taskId }));
|
|
312
|
+
const getTaskResult = async (taskId) => {
|
|
313
|
+
const result = await rpc("tasks/result", { taskId });
|
|
314
|
+
if (!isRecord(result) || !Array.isArray(result.content)) {
|
|
315
|
+
throw new McpClientError("Malformed MCP task result");
|
|
316
|
+
}
|
|
317
|
+
return result;
|
|
318
|
+
};
|
|
319
|
+
const listTasks = async () => {
|
|
320
|
+
const collected = [];
|
|
321
|
+
let cursor;
|
|
322
|
+
for (let page = 0;page < MAX_LIST_PAGES; page += 1) {
|
|
323
|
+
const result = await rpc("tasks/list", cursor === undefined ? undefined : { cursor });
|
|
324
|
+
if (isRecord(result) && Array.isArray(result.tasks)) {
|
|
325
|
+
collected.push(...result.tasks.map(taskFrom));
|
|
326
|
+
}
|
|
327
|
+
const next = isRecord(result) && typeof result.nextCursor === "string" ? result.nextCursor : undefined;
|
|
328
|
+
if (next === undefined)
|
|
329
|
+
break;
|
|
330
|
+
cursor = next;
|
|
331
|
+
}
|
|
332
|
+
return collected;
|
|
333
|
+
};
|
|
287
334
|
const listResources = async () => {
|
|
288
335
|
const collected = [];
|
|
289
336
|
let cursor;
|
|
@@ -303,7 +350,19 @@ var createMcpClient = (options) => {
|
|
|
303
350
|
const ping = async () => {
|
|
304
351
|
await rpc("ping");
|
|
305
352
|
};
|
|
306
|
-
return {
|
|
353
|
+
return {
|
|
354
|
+
callTool,
|
|
355
|
+
callToolAsTask,
|
|
356
|
+
cancelTask,
|
|
357
|
+
getTask,
|
|
358
|
+
getTaskResult,
|
|
359
|
+
initialize,
|
|
360
|
+
listResources,
|
|
361
|
+
listTasks,
|
|
362
|
+
listTools,
|
|
363
|
+
ping,
|
|
364
|
+
readResource
|
|
365
|
+
};
|
|
307
366
|
};
|
|
308
367
|
// src/oauth.ts
|
|
309
368
|
var splitChallenges = (value) => {
|
|
@@ -657,6 +716,7 @@ var createMemoryMcpTaskStore = () => {
|
|
|
657
716
|
const task = tasks.get(taskId);
|
|
658
717
|
return task === undefined ? null : clone(task);
|
|
659
718
|
},
|
|
719
|
+
list: (authorizationKey, { limit, offset }) => [...tasks.values()].filter((task) => task.authorizationKey === authorizationKey).sort((left, right) => right.lastUpdatedAt.localeCompare(left.lastUpdatedAt)).slice(offset, offset + limit).map(clone),
|
|
660
720
|
save: (task) => {
|
|
661
721
|
tasks.set(task.taskId, clone(task));
|
|
662
722
|
},
|
|
@@ -678,11 +738,22 @@ var createMemoryMcpTaskStore = () => {
|
|
|
678
738
|
};
|
|
679
739
|
};
|
|
680
740
|
var publicMcpTask = ({ authorizationKey, ...task }) => {
|
|
681
|
-
|
|
741
|
+
const { error, inputRequests, pollIntervalMs, result, ttlMs, ...rest } = task;
|
|
742
|
+
return {
|
|
743
|
+
...rest,
|
|
744
|
+
...pollIntervalMs === undefined ? {} : { pollInterval: pollIntervalMs },
|
|
745
|
+
ttl: ttlMs
|
|
746
|
+
};
|
|
682
747
|
};
|
|
683
748
|
|
|
684
749
|
// src/dispatch.ts
|
|
685
|
-
var
|
|
750
|
+
var MCP_LATEST_PROTOCOL_VERSION = "2025-11-25";
|
|
751
|
+
var DEFAULT_PROTOCOLS = [
|
|
752
|
+
MCP_LATEST_PROTOCOL_VERSION,
|
|
753
|
+
"2025-06-18",
|
|
754
|
+
"2025-03-26",
|
|
755
|
+
"2024-11-05"
|
|
756
|
+
];
|
|
686
757
|
var DEFAULT_RESOURCE_MIME = "text/markdown";
|
|
687
758
|
var DEFAULT_LIST_PAGE_SIZE = 50;
|
|
688
759
|
var decodeCursor = (params) => {
|
|
@@ -729,18 +800,34 @@ var normalizeResult = (value) => {
|
|
|
729
800
|
return { content: value, isError: false };
|
|
730
801
|
return { isError: false, ...value };
|
|
731
802
|
};
|
|
732
|
-
var
|
|
733
|
-
if (!isRecord(params) || !isRecord(params.capabilities))
|
|
734
|
-
return false;
|
|
735
|
-
|
|
803
|
+
var clientElicitation = (params) => {
|
|
804
|
+
if (!isRecord(params) || !isRecord(params.capabilities)) {
|
|
805
|
+
return { form: false, url: false };
|
|
806
|
+
}
|
|
807
|
+
const elicitation = params.capabilities.elicitation;
|
|
808
|
+
if (!isRecord(elicitation))
|
|
809
|
+
return { form: false, url: false };
|
|
810
|
+
return {
|
|
811
|
+
form: Object.keys(elicitation).length === 0 || isRecord(elicitation.form),
|
|
812
|
+
url: isRecord(elicitation.url)
|
|
813
|
+
};
|
|
736
814
|
};
|
|
737
815
|
var initialize = async (config, id, params, context) => {
|
|
738
816
|
const supported = config.supportedProtocols ?? DEFAULT_PROTOCOLS;
|
|
817
|
+
const protocolVersion = negotiateProtocol(supported, params);
|
|
739
818
|
const capabilities = {
|
|
740
819
|
tools: { listChanged: false }
|
|
741
820
|
};
|
|
742
821
|
if (config.tasks !== undefined) {
|
|
743
|
-
|
|
822
|
+
if (protocolVersion === MCP_LATEST_PROTOCOL_VERSION) {
|
|
823
|
+
capabilities.tasks = {
|
|
824
|
+
cancel: {},
|
|
825
|
+
list: {},
|
|
826
|
+
requests: { tools: { call: {} } }
|
|
827
|
+
};
|
|
828
|
+
} else {
|
|
829
|
+
capabilities.extensions = { "io.modelcontextprotocol/tasks": {} };
|
|
830
|
+
}
|
|
744
831
|
}
|
|
745
832
|
if (config.prompts)
|
|
746
833
|
capabilities.prompts = { listChanged: false };
|
|
@@ -750,23 +837,26 @@ var initialize = async (config, id, params, context) => {
|
|
|
750
837
|
const response = rpcResult(id, {
|
|
751
838
|
capabilities,
|
|
752
839
|
...config.instructions === undefined ? {} : { instructions: config.instructions },
|
|
753
|
-
protocolVersion
|
|
840
|
+
protocolVersion,
|
|
754
841
|
serverInfo: config.serverInfo
|
|
755
842
|
});
|
|
756
843
|
if (!config.elicitation?.enabled || !context.sessions)
|
|
757
844
|
return response;
|
|
758
|
-
const
|
|
845
|
+
const elicitation = clientElicitation(params);
|
|
846
|
+
const sessionId = await context.sessions.create(elicitation.form || elicitation.url, elicitation.url);
|
|
759
847
|
response.headers.set("Mcp-Session-Id", sessionId);
|
|
760
848
|
return response;
|
|
761
849
|
};
|
|
762
|
-
var toolsList = async (config, caller, scopes, id, params) => {
|
|
850
|
+
var toolsList = async (config, caller, scopes, id, params, protocolVersion) => {
|
|
763
851
|
const tools = await config.tools({ caller, meta: {} });
|
|
764
852
|
const visible = Object.entries(tools).filter(([, tool]) => scopeAllows(tool, scopes) && agencyAllows(config, tool, scopes)).map(([name, tool]) => ({
|
|
765
853
|
annotations: tool.annotations,
|
|
854
|
+
...tool.coaz === undefined ? {} : { coaz: tool.coaz },
|
|
766
855
|
description: tool.description,
|
|
767
856
|
inputSchema: tool.inputSchema,
|
|
768
857
|
name,
|
|
769
|
-
...tool.outputSchema === undefined ? {} : { outputSchema: tool.outputSchema }
|
|
858
|
+
...tool.outputSchema === undefined ? {} : { outputSchema: tool.outputSchema },
|
|
859
|
+
...protocolVersion === MCP_LATEST_PROTOCOL_VERSION && tool.taskSupport !== undefined ? { execution: { taskSupport: tool.taskSupport } } : {}
|
|
770
860
|
}));
|
|
771
861
|
const { items, nextCursor } = paginate(visible, decodeCursor(params), config.listPageSize ?? DEFAULT_LIST_PAGE_SIZE);
|
|
772
862
|
return rpcResult(id, {
|
|
@@ -777,6 +867,7 @@ var toolsList = async (config, caller, scopes, id, params) => {
|
|
|
777
867
|
var errorResult = (id, text) => rpcResult(id, { content: [{ text, type: "text" }], isError: true });
|
|
778
868
|
var noElicit = {
|
|
779
869
|
canElicit: false,
|
|
870
|
+
canElicitUrl: false,
|
|
780
871
|
elicit: () => Promise.resolve({ action: "unsupported" })
|
|
781
872
|
};
|
|
782
873
|
var SSE_HEADERS = {
|
|
@@ -869,7 +960,7 @@ var runTool = async (config, caller, scopes, id, name, args, meta, tool, context
|
|
|
869
960
|
await config.onCall({ args, caller, meta, name, ok });
|
|
870
961
|
return payload;
|
|
871
962
|
};
|
|
872
|
-
var toolsCallStreaming = (config, caller, scopes, id, name, args, meta, tool, sessions, canElicit) => {
|
|
963
|
+
var toolsCallStreaming = (config, caller, scopes, id, name, args, meta, tool, sessions, canElicit, canElicitUrl) => {
|
|
873
964
|
const encoder = new TextEncoder;
|
|
874
965
|
const body = new ReadableStream({
|
|
875
966
|
async start(controller) {
|
|
@@ -885,9 +976,24 @@ var toolsCallStreaming = (config, caller, scopes, id, name, args, meta, tool, se
|
|
|
885
976
|
};
|
|
886
977
|
const context = {
|
|
887
978
|
canElicit,
|
|
979
|
+
canElicitUrl,
|
|
888
980
|
elicit: async (request) => {
|
|
889
981
|
if (!canElicit)
|
|
890
982
|
return { action: "unsupported" };
|
|
983
|
+
if (request.mode === "url") {
|
|
984
|
+
if (!canElicitUrl)
|
|
985
|
+
return { action: "unsupported" };
|
|
986
|
+
let url;
|
|
987
|
+
try {
|
|
988
|
+
url = new URL(request.url);
|
|
989
|
+
} catch {
|
|
990
|
+
throw new Error("URL elicitation requires a valid URL");
|
|
991
|
+
}
|
|
992
|
+
const localDevelopment = url.protocol === "http:" && (url.hostname === "localhost" || url.hostname === "127.0.0.1");
|
|
993
|
+
if (url.protocol !== "https:" && !localDevelopment || url.username !== "" || url.password !== "") {
|
|
994
|
+
throw new Error("URL elicitation requires HTTPS without embedded credentials");
|
|
995
|
+
}
|
|
996
|
+
}
|
|
891
997
|
const pending = sessions.startElicit(request);
|
|
892
998
|
send({
|
|
893
999
|
id: pending.id,
|
|
@@ -929,8 +1035,22 @@ var toolsCall = async (config, caller, scopes, id, params, context) => {
|
|
|
929
1035
|
return rpcError(id, JSONRPC_INVALID_PARAMS, `Unknown tool: ${name}`);
|
|
930
1036
|
}
|
|
931
1037
|
const tasks = config.tasks;
|
|
932
|
-
|
|
933
|
-
|
|
1038
|
+
const nativeTasks = context.protocolVersion === MCP_LATEST_PROTOCOL_VERSION;
|
|
1039
|
+
const requestedTaskParams = isRecord(params.task) ? params.task : undefined;
|
|
1040
|
+
const requestedTask = requestedTaskParams !== undefined;
|
|
1041
|
+
const taskSupport = tool.taskSupport ?? "forbidden";
|
|
1042
|
+
if (nativeTasks && requestedTask && tasks === undefined) {
|
|
1043
|
+
return rpcError(id, JSONRPC_METHOD_NOT_FOUND, "Tasks are not configured");
|
|
1044
|
+
}
|
|
1045
|
+
if (nativeTasks && requestedTask && taskSupport === "forbidden") {
|
|
1046
|
+
return rpcError(id, JSONRPC_METHOD_NOT_FOUND, "Tool does not support task execution");
|
|
1047
|
+
}
|
|
1048
|
+
if (nativeTasks && !requestedTask && taskSupport === "required") {
|
|
1049
|
+
return rpcError(id, JSONRPC_METHOD_NOT_FOUND, "Tool requires task execution");
|
|
1050
|
+
}
|
|
1051
|
+
const shouldCreateTask = tasks !== undefined && (nativeTasks ? requestedTask && taskSupport !== "forbidden" : await tasks.shouldCreate({ args, caller, name }));
|
|
1052
|
+
if (tasks !== undefined && shouldCreateTask) {
|
|
1053
|
+
if (!nativeTasks && !supportsTasks(params)) {
|
|
934
1054
|
return rpcError(id, JSONRPC_MISSING_REQUIRED_CLIENT_CAPABILITY, "Missing required client capability", {
|
|
935
1055
|
requiredCapabilities: {
|
|
936
1056
|
extensions: { "io.modelcontextprotocol/tasks": {} }
|
|
@@ -938,6 +1058,7 @@ var toolsCall = async (config, caller, scopes, id, params, context) => {
|
|
|
938
1058
|
});
|
|
939
1059
|
}
|
|
940
1060
|
const createdAt = new Date().toISOString();
|
|
1061
|
+
const requestedTtl = requestedTaskParams !== undefined && typeof requestedTaskParams.ttl === "number" && requestedTaskParams.ttl >= 0 ? requestedTaskParams.ttl : undefined;
|
|
941
1062
|
const task = {
|
|
942
1063
|
authorizationKey: await tasks.authorizationKey(caller),
|
|
943
1064
|
createdAt,
|
|
@@ -945,7 +1066,7 @@ var toolsCall = async (config, caller, scopes, id, params, context) => {
|
|
|
945
1066
|
pollIntervalMs: tasks.pollIntervalMs,
|
|
946
1067
|
status: "working",
|
|
947
1068
|
taskId: crypto.randomUUID(),
|
|
948
|
-
ttlMs: tasks.ttlMs ?? null
|
|
1069
|
+
ttlMs: tasks.ttlMs ?? requestedTtl ?? null
|
|
949
1070
|
};
|
|
950
1071
|
await tasks.store.save(task);
|
|
951
1072
|
setTimeout(() => {
|
|
@@ -953,7 +1074,7 @@ var toolsCall = async (config, caller, scopes, id, params, context) => {
|
|
|
953
1074
|
const result = isRecord(payload2) && isRecord(payload2.result) ? payload2.result : { content: [], isError: true };
|
|
954
1075
|
await tasks.store.update(task.taskId, {
|
|
955
1076
|
result,
|
|
956
|
-
status: "completed"
|
|
1077
|
+
status: result.isError === true ? "failed" : "completed"
|
|
957
1078
|
});
|
|
958
1079
|
}).catch(async (error) => {
|
|
959
1080
|
await tasks.store.update(task.taskId, {
|
|
@@ -965,12 +1086,12 @@ var toolsCall = async (config, caller, scopes, id, params, context) => {
|
|
|
965
1086
|
});
|
|
966
1087
|
});
|
|
967
1088
|
}, 0);
|
|
968
|
-
return rpcResult(id, { ...publicMcpTask(task), resultType: "task" });
|
|
1089
|
+
return nativeTasks ? rpcResult(id, { task: publicMcpTask(task) }) : rpcResult(id, { ...publicMcpTask(task), resultType: "task" });
|
|
969
1090
|
}
|
|
970
1091
|
const sessions = context.sessions;
|
|
971
1092
|
const session = sessions ? await sessions.get(context.sessionId ?? null) : null;
|
|
972
1093
|
if (tool.mayElicit === true && config.elicitation?.enabled === true && sessions && session) {
|
|
973
|
-
return toolsCallStreaming(config, caller, scopes, id, name, args, meta, tool, sessions, session.canElicit);
|
|
1094
|
+
return toolsCallStreaming(config, caller, scopes, id, name, args, meta, tool, sessions, session.canElicit, session.canElicitUrl ?? false);
|
|
974
1095
|
}
|
|
975
1096
|
const payload = await runTool(config, caller, scopes, id, name, args, meta, tool, noElicit);
|
|
976
1097
|
return new Response(JSON.stringify(payload), {
|
|
@@ -995,11 +1116,70 @@ var authorizedTask = async (config, caller, params) => {
|
|
|
995
1116
|
const authorizationKey = await config.tasks.authorizationKey(caller);
|
|
996
1117
|
return task.authorizationKey === authorizationKey ? task : null;
|
|
997
1118
|
};
|
|
998
|
-
var tasksGet = async (config, caller, id, params) => {
|
|
1119
|
+
var tasksGet = async (config, caller, id, params, native) => {
|
|
999
1120
|
const task = await authorizedTask(config, caller, params);
|
|
1000
1121
|
if (task === null)
|
|
1001
1122
|
return rpcError(id, JSONRPC_INVALID_PARAMS, "Unknown task");
|
|
1002
|
-
return rpcResult(id, { ...publicMcpTask(task), resultType: "complete" });
|
|
1123
|
+
return rpcResult(id, native ? publicMcpTask(task) : { ...publicMcpTask(task), resultType: "complete" });
|
|
1124
|
+
};
|
|
1125
|
+
var tasksResult = async (config, caller, id, params, signal) => {
|
|
1126
|
+
let task = await authorizedTask(config, caller, params);
|
|
1127
|
+
if (task === null)
|
|
1128
|
+
return rpcError(id, JSONRPC_INVALID_PARAMS, "Unknown task");
|
|
1129
|
+
while (task.status === "working" || task.status === "input_required") {
|
|
1130
|
+
if (signal?.aborted) {
|
|
1131
|
+
return rpcError(id, JSONRPC_INTERNAL_ERROR, "Task result request cancelled");
|
|
1132
|
+
}
|
|
1133
|
+
await new Promise((resolve) => setTimeout(resolve, task?.pollIntervalMs ?? 100));
|
|
1134
|
+
task = await authorizedTask(config, caller, params);
|
|
1135
|
+
if (task === null)
|
|
1136
|
+
return rpcError(id, JSONRPC_INVALID_PARAMS, "Unknown task");
|
|
1137
|
+
}
|
|
1138
|
+
if (task.error !== undefined) {
|
|
1139
|
+
return rpcError(id, typeof task.error.code === "number" ? task.error.code : JSONRPC_INTERNAL_ERROR, typeof task.error.message === "string" ? task.error.message : "Task failed");
|
|
1140
|
+
}
|
|
1141
|
+
const result = task.result ?? {
|
|
1142
|
+
content: [],
|
|
1143
|
+
isError: task.status !== "completed"
|
|
1144
|
+
};
|
|
1145
|
+
return rpcResult(id, {
|
|
1146
|
+
...result,
|
|
1147
|
+
_meta: {
|
|
1148
|
+
...isRecord(result._meta) ? result._meta : {},
|
|
1149
|
+
"io.modelcontextprotocol/related-task": { taskId: task.taskId }
|
|
1150
|
+
}
|
|
1151
|
+
});
|
|
1152
|
+
};
|
|
1153
|
+
var tasksList = async (config, caller, id, params) => {
|
|
1154
|
+
if (config.tasks === undefined) {
|
|
1155
|
+
return rpcError(id, JSONRPC_METHOD_NOT_FOUND, "Tasks are not configured");
|
|
1156
|
+
}
|
|
1157
|
+
const authorizationKey = await config.tasks.authorizationKey(caller);
|
|
1158
|
+
let offset = 0;
|
|
1159
|
+
if (isRecord(params) && params.cursor !== undefined) {
|
|
1160
|
+
if (typeof params.cursor !== "string") {
|
|
1161
|
+
return rpcError(id, JSONRPC_INVALID_PARAMS, "Invalid task cursor");
|
|
1162
|
+
}
|
|
1163
|
+
try {
|
|
1164
|
+
offset = Number.parseInt(atob(params.cursor), 10);
|
|
1165
|
+
} catch {
|
|
1166
|
+
offset = -1;
|
|
1167
|
+
}
|
|
1168
|
+
if (!Number.isSafeInteger(offset) || offset < 0) {
|
|
1169
|
+
return rpcError(id, JSONRPC_INVALID_PARAMS, "Invalid task cursor");
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
const pageSize = Math.min(100, Math.max(1, config.tasks.listPageSize ?? 50));
|
|
1173
|
+
const fetched = await config.tasks.store.list(authorizationKey, {
|
|
1174
|
+
limit: pageSize + 1,
|
|
1175
|
+
offset
|
|
1176
|
+
});
|
|
1177
|
+
const items = fetched.slice(0, pageSize);
|
|
1178
|
+
const nextCursor = fetched.length > pageSize ? encodeCursor(offset + pageSize) : undefined;
|
|
1179
|
+
return rpcResult(id, {
|
|
1180
|
+
tasks: items.map(publicMcpTask),
|
|
1181
|
+
...nextCursor === undefined ? {} : { nextCursor }
|
|
1182
|
+
});
|
|
1003
1183
|
};
|
|
1004
1184
|
var tasksUpdate = async (config, caller, id, params) => {
|
|
1005
1185
|
const task = await authorizedTask(config, caller, params);
|
|
@@ -1013,12 +1193,16 @@ var tasksUpdate = async (config, caller, id, params) => {
|
|
|
1013
1193
|
});
|
|
1014
1194
|
return rpcResult(id, { resultType: "complete" });
|
|
1015
1195
|
};
|
|
1016
|
-
var tasksCancel = async (config, caller, id, params) => {
|
|
1196
|
+
var tasksCancel = async (config, caller, id, params, native) => {
|
|
1017
1197
|
const task = await authorizedTask(config, caller, params);
|
|
1018
1198
|
if (task === null)
|
|
1019
1199
|
return rpcError(id, JSONRPC_INVALID_PARAMS, "Unknown task");
|
|
1200
|
+
if (["cancelled", "completed", "failed"].includes(task.status)) {
|
|
1201
|
+
return rpcError(id, JSONRPC_INVALID_PARAMS, "Task is already terminal");
|
|
1202
|
+
}
|
|
1020
1203
|
await config.tasks?.store.cancel(task.taskId);
|
|
1021
|
-
|
|
1204
|
+
const cancelled = await config.tasks?.store.get(task.taskId);
|
|
1205
|
+
return rpcResult(id, native && cancelled !== null && cancelled !== undefined ? publicMcpTask(cancelled) : { resultType: "complete" });
|
|
1022
1206
|
};
|
|
1023
1207
|
var promptsList = (config, id, params) => {
|
|
1024
1208
|
const definitions = config.prompts?.definitions ?? {};
|
|
@@ -1089,7 +1273,10 @@ var elicitAnswer = async (message, context) => {
|
|
|
1089
1273
|
return notificationAck();
|
|
1090
1274
|
const result = isRecord(message.result) ? message.result : null;
|
|
1091
1275
|
const action = result?.action;
|
|
1092
|
-
const answer = action === "accept"
|
|
1276
|
+
const answer = action === "accept" ? {
|
|
1277
|
+
action: "accept",
|
|
1278
|
+
content: isRecord(result?.content) ? result.content : {}
|
|
1279
|
+
} : action === "decline" ? { action: "decline" } : { action: "cancel" };
|
|
1093
1280
|
await context.sessions.resolveElicit({
|
|
1094
1281
|
requestId,
|
|
1095
1282
|
result: answer,
|
|
@@ -1106,6 +1293,7 @@ var dispatchMcp = async (config, caller, scopes, message, context = {}) => {
|
|
|
1106
1293
|
if (!("method" in message))
|
|
1107
1294
|
return elicitAnswer(message, context);
|
|
1108
1295
|
const id = idOf(message);
|
|
1296
|
+
const protocolVersion = context.protocolVersion ?? "2025-06-18";
|
|
1109
1297
|
const method = typeof message.method === "string" ? message.method : "";
|
|
1110
1298
|
const { params } = message;
|
|
1111
1299
|
if (method === "initialize") {
|
|
@@ -1122,17 +1310,24 @@ var dispatchMcp = async (config, caller, scopes, message, context = {}) => {
|
|
|
1122
1310
|
if (method === "ping")
|
|
1123
1311
|
return rpcResult(id, {});
|
|
1124
1312
|
if (method === "tools/list") {
|
|
1125
|
-
return toolsList(config, caller, scopes, id, params);
|
|
1313
|
+
return toolsList(config, caller, scopes, id, params, protocolVersion);
|
|
1126
1314
|
}
|
|
1127
1315
|
if (method === "tools/call") {
|
|
1128
|
-
return toolsCall(config, caller, scopes, id, params,
|
|
1316
|
+
return toolsCall(config, caller, scopes, id, params, {
|
|
1317
|
+
...context,
|
|
1318
|
+
protocolVersion
|
|
1319
|
+
});
|
|
1129
1320
|
}
|
|
1130
1321
|
if (method === "tasks/get")
|
|
1131
|
-
return tasksGet(config, caller, id, params);
|
|
1132
|
-
if (method === "tasks/
|
|
1322
|
+
return tasksGet(config, caller, id, params, protocolVersion === MCP_LATEST_PROTOCOL_VERSION);
|
|
1323
|
+
if (method === "tasks/result" && protocolVersion === MCP_LATEST_PROTOCOL_VERSION)
|
|
1324
|
+
return tasksResult(config, caller, id, params, context.requestSignal);
|
|
1325
|
+
if (method === "tasks/list" && protocolVersion === MCP_LATEST_PROTOCOL_VERSION)
|
|
1326
|
+
return tasksList(config, caller, id, params);
|
|
1327
|
+
if (method === "tasks/update" && protocolVersion !== MCP_LATEST_PROTOCOL_VERSION)
|
|
1133
1328
|
return tasksUpdate(config, caller, id, params);
|
|
1134
1329
|
if (method === "tasks/cancel")
|
|
1135
|
-
return tasksCancel(config, caller, id, params);
|
|
1330
|
+
return tasksCancel(config, caller, id, params, protocolVersion === MCP_LATEST_PROTOCOL_VERSION);
|
|
1136
1331
|
if (method === "prompts/list")
|
|
1137
1332
|
return promptsList(config, id, params);
|
|
1138
1333
|
if (method === "prompts/get")
|
|
@@ -1261,7 +1456,11 @@ var createMemoryStore = (ttlMs) => {
|
|
|
1261
1456
|
create: (session) => {
|
|
1262
1457
|
sweep();
|
|
1263
1458
|
const id = crypto.randomUUID();
|
|
1264
|
-
sessions.set(id, {
|
|
1459
|
+
sessions.set(id, {
|
|
1460
|
+
canElicit: session.canElicit,
|
|
1461
|
+
canElicitUrl: session.canElicitUrl ?? false,
|
|
1462
|
+
lastSeen: Date.now()
|
|
1463
|
+
});
|
|
1265
1464
|
return id;
|
|
1266
1465
|
},
|
|
1267
1466
|
drop: (id) => {
|
|
@@ -1272,7 +1471,10 @@ var createMemoryStore = (ttlMs) => {
|
|
|
1272
1471
|
if (!session)
|
|
1273
1472
|
return null;
|
|
1274
1473
|
session.lastSeen = Date.now();
|
|
1275
|
-
return {
|
|
1474
|
+
return {
|
|
1475
|
+
canElicit: session.canElicit,
|
|
1476
|
+
canElicitUrl: session.canElicitUrl
|
|
1477
|
+
};
|
|
1276
1478
|
}
|
|
1277
1479
|
};
|
|
1278
1480
|
};
|
|
@@ -1307,9 +1509,9 @@ var createSessionRegistry = (options) => {
|
|
|
1307
1509
|
});
|
|
1308
1510
|
pending.clear();
|
|
1309
1511
|
},
|
|
1310
|
-
create: async (canElicit) => {
|
|
1512
|
+
create: async (canElicit, canElicitUrl = false) => {
|
|
1311
1513
|
await ready;
|
|
1312
|
-
return await store.create({ canElicit });
|
|
1514
|
+
return await store.create({ canElicit, canElicitUrl });
|
|
1313
1515
|
},
|
|
1314
1516
|
drop: async (id) => {
|
|
1315
1517
|
await ready;
|
|
@@ -1388,12 +1590,27 @@ var runMcpPost = async (config, request, body) => {
|
|
|
1388
1590
|
if (Array.isArray(body)) {
|
|
1389
1591
|
return rpcError(null, JSONRPC_INVALID_REQUEST, "Batching is not supported");
|
|
1390
1592
|
}
|
|
1593
|
+
const isInitialize = typeof body === "object" && body !== null && "method" in body && body.method === "initialize";
|
|
1594
|
+
const protocolVersion = request.headers.get("mcp-protocol-version");
|
|
1595
|
+
const supportedProtocols = config.supportedProtocols ?? [
|
|
1596
|
+
MCP_LATEST_PROTOCOL_VERSION,
|
|
1597
|
+
"2025-06-18",
|
|
1598
|
+
"2025-03-26",
|
|
1599
|
+
"2024-11-05"
|
|
1600
|
+
];
|
|
1601
|
+
if (!isInitialize && (protocolVersion === null || !supportedProtocols.includes(protocolVersion))) {
|
|
1602
|
+
return new Response("Missing or unsupported MCP-Protocol-Version", {
|
|
1603
|
+
status: 400
|
|
1604
|
+
});
|
|
1605
|
+
}
|
|
1391
1606
|
const sessions = registryFor(config);
|
|
1392
1607
|
const sessionId = request.headers.get("mcp-session-id");
|
|
1393
1608
|
if (sessions && sessionId && !await sessions.get(sessionId)) {
|
|
1394
1609
|
return new Response(null, { status: HTTP_NOT_FOUND });
|
|
1395
1610
|
}
|
|
1396
1611
|
return dispatchMcp(config, auth.caller, auth.scopes ?? [], body, {
|
|
1612
|
+
protocolVersion: protocolVersion ?? MCP_LATEST_PROTOCOL_VERSION,
|
|
1613
|
+
requestSignal: request.signal,
|
|
1397
1614
|
sessionId,
|
|
1398
1615
|
sessions
|
|
1399
1616
|
}).catch(() => rpcError(null, JSONRPC_INVALID_REQUEST, "Internal error"));
|
|
@@ -1470,10 +1687,12 @@ CREATE INDEX IF NOT EXISTS tasks_expiry_idx ON ${ns}.tasks (expires_at) WHERE ex
|
|
|
1470
1687
|
CREATE TABLE IF NOT EXISTS ${ns}.sessions (
|
|
1471
1688
|
session_id text PRIMARY KEY,
|
|
1472
1689
|
can_elicit boolean NOT NULL,
|
|
1690
|
+
can_elicit_url boolean NOT NULL DEFAULT false,
|
|
1473
1691
|
created_at timestamptz NOT NULL DEFAULT now(),
|
|
1474
1692
|
last_seen_at timestamptz NOT NULL DEFAULT now(),
|
|
1475
1693
|
expires_at timestamptz NOT NULL
|
|
1476
1694
|
);
|
|
1695
|
+
ALTER TABLE ${ns}.sessions ADD COLUMN IF NOT EXISTS can_elicit_url boolean NOT NULL DEFAULT false;
|
|
1477
1696
|
CREATE INDEX IF NOT EXISTS sessions_expiry_idx ON ${ns}.sessions (expires_at);`;
|
|
1478
1697
|
};
|
|
1479
1698
|
var createPostgresMcpTaskStore = ({
|
|
@@ -1492,6 +1711,7 @@ var createPostgresMcpTaskStore = ({
|
|
|
1492
1711
|
]);
|
|
1493
1712
|
},
|
|
1494
1713
|
get: async (taskId) => (await client.query(`SELECT data FROM ${ns}.tasks WHERE task_id = $1 AND (expires_at IS NULL OR expires_at > $2::timestamptz)`, [taskId, now().toISOString()])).rows[0]?.data ?? null,
|
|
1714
|
+
list: async (authorizationKey, { limit, offset }) => (await client.query(`SELECT data FROM ${ns}.tasks WHERE authorization_key = $1 AND (expires_at IS NULL OR expires_at > $2::timestamptz) ORDER BY updated_at DESC LIMIT $3 OFFSET $4`, [authorizationKey, now().toISOString(), limit, offset])).rows.map((row) => row.data),
|
|
1495
1715
|
save: async (task) => {
|
|
1496
1716
|
const expiresAt = task.ttlMs === null ? null : new Date(new Date(task.createdAt).getTime() + task.ttlMs).toISOString();
|
|
1497
1717
|
await client.query(`INSERT INTO ${ns}.tasks (task_id, authorization_key, status, created_at, updated_at, expires_at, data) VALUES ($1, $2, $3, $4::timestamptz, $5::timestamptz, $6::timestamptz, $7::jsonb) ON CONFLICT (task_id) DO NOTHING`, [
|
|
@@ -1522,12 +1742,13 @@ var createPostgresMcpSessionStore = ({
|
|
|
1522
1742
|
}) => {
|
|
1523
1743
|
const ns = namespaceOf(namespace);
|
|
1524
1744
|
return {
|
|
1525
|
-
create: async ({ canElicit }) => {
|
|
1745
|
+
create: async ({ canElicit, canElicitUrl }) => {
|
|
1526
1746
|
const id = crypto.randomUUID();
|
|
1527
1747
|
const current = now();
|
|
1528
|
-
await client.query(`INSERT INTO ${ns}.sessions (session_id, can_elicit, created_at, last_seen_at, expires_at) VALUES ($1, $2, $3::timestamptz, $
|
|
1748
|
+
await client.query(`INSERT INTO ${ns}.sessions (session_id, can_elicit, can_elicit_url, created_at, last_seen_at, expires_at) VALUES ($1, $2, $3, $4::timestamptz, $4::timestamptz, $5::timestamptz)`, [
|
|
1529
1749
|
id,
|
|
1530
1750
|
canElicit,
|
|
1751
|
+
canElicitUrl ?? false,
|
|
1531
1752
|
current.toISOString(),
|
|
1532
1753
|
new Date(current.getTime() + ttlMs).toISOString()
|
|
1533
1754
|
]);
|
|
@@ -1540,13 +1761,16 @@ var createPostgresMcpSessionStore = ({
|
|
|
1540
1761
|
},
|
|
1541
1762
|
get: async (id) => {
|
|
1542
1763
|
const current = now();
|
|
1543
|
-
const result = await client.query(`UPDATE ${ns}.sessions SET last_seen_at = $2::timestamptz, expires_at = $3::timestamptz WHERE session_id = $1 AND expires_at > $2::timestamptz RETURNING can_elicit`, [
|
|
1764
|
+
const result = await client.query(`UPDATE ${ns}.sessions SET last_seen_at = $2::timestamptz, expires_at = $3::timestamptz WHERE session_id = $1 AND expires_at > $2::timestamptz RETURNING can_elicit, can_elicit_url`, [
|
|
1544
1765
|
id,
|
|
1545
1766
|
current.toISOString(),
|
|
1546
1767
|
new Date(current.getTime() + ttlMs).toISOString()
|
|
1547
1768
|
]);
|
|
1548
1769
|
const row = result.rows[0];
|
|
1549
|
-
return row === undefined ? null : {
|
|
1770
|
+
return row === undefined ? null : {
|
|
1771
|
+
canElicit: row.can_elicit,
|
|
1772
|
+
canElicitUrl: row.can_elicit_url
|
|
1773
|
+
};
|
|
1550
1774
|
}
|
|
1551
1775
|
};
|
|
1552
1776
|
};
|
|
@@ -1571,5 +1795,6 @@ export {
|
|
|
1571
1795
|
createMcpClient,
|
|
1572
1796
|
createMcpAuthorizationRequest,
|
|
1573
1797
|
McpClientError,
|
|
1798
|
+
MCP_LATEST_PROTOCOL_VERSION,
|
|
1574
1799
|
FEEDBACK_INSTRUCTIONS
|
|
1575
1800
|
};
|
package/dist/manifest.js
CHANGED
|
@@ -5916,6 +5916,14 @@ var serializedTool = Type.Object({
|
|
|
5916
5916
|
});
|
|
5917
5917
|
var manifestSchema = Type.Object({
|
|
5918
5918
|
contract: Type.Union([Type.Literal(1), Type.Literal(2)]),
|
|
5919
|
+
discovery: Type.Optional(Type.Object({
|
|
5920
|
+
audiences: Type.Optional(Type.Array(Type.String({ minLength: 1 }))),
|
|
5921
|
+
certificationUrl: Type.Optional(Type.String()),
|
|
5922
|
+
intents: Type.Optional(Type.Array(Type.String({ minLength: 1 }))),
|
|
5923
|
+
keywords: Type.Optional(Type.Array(Type.String({ minLength: 1 }))),
|
|
5924
|
+
protocols: Type.Optional(Type.Array(Type.String({ minLength: 1 }))),
|
|
5925
|
+
url: Type.Optional(Type.String())
|
|
5926
|
+
})),
|
|
5919
5927
|
identity: Type.Object({
|
|
5920
5928
|
accent: Type.Optional(Type.String({ pattern: "^#[0-9a-fA-F]{3,8}$" })),
|
|
5921
5929
|
category: Type.String({ minLength: 1 }),
|
package/dist/src/client.d.ts
CHANGED
|
@@ -34,10 +34,21 @@ export type McpClientOptions = {
|
|
|
34
34
|
};
|
|
35
35
|
export type McpRemoteTool = {
|
|
36
36
|
annotations?: McpToolAnnotations;
|
|
37
|
+
coaz?: boolean;
|
|
37
38
|
description?: string;
|
|
38
39
|
inputSchema?: Record<string, unknown>;
|
|
39
40
|
name: string;
|
|
40
41
|
outputSchema?: Record<string, unknown>;
|
|
42
|
+
taskSupport?: "forbidden" | "optional" | "required";
|
|
43
|
+
};
|
|
44
|
+
export type McpRemoteTask = {
|
|
45
|
+
createdAt: string;
|
|
46
|
+
lastUpdatedAt: string;
|
|
47
|
+
pollInterval?: number;
|
|
48
|
+
status: "working" | "input_required" | "completed" | "failed" | "cancelled";
|
|
49
|
+
statusMessage?: string;
|
|
50
|
+
taskId: string;
|
|
51
|
+
ttl: number | null;
|
|
41
52
|
};
|
|
42
53
|
export type McpInitializeResult = {
|
|
43
54
|
capabilities?: Record<string, unknown>;
|
|
@@ -51,7 +62,14 @@ export type McpInitializeResult = {
|
|
|
51
62
|
};
|
|
52
63
|
export type McpClient = {
|
|
53
64
|
callTool: (name: string, args?: unknown) => Promise<McpToolResult>;
|
|
65
|
+
callToolAsTask: (name: string, args?: unknown, options?: {
|
|
66
|
+
ttl?: number;
|
|
67
|
+
}) => Promise<McpRemoteTask>;
|
|
68
|
+
cancelTask: (taskId: string) => Promise<McpRemoteTask>;
|
|
69
|
+
getTask: (taskId: string) => Promise<McpRemoteTask>;
|
|
70
|
+
getTaskResult: (taskId: string) => Promise<McpToolResult>;
|
|
54
71
|
initialize: () => Promise<McpInitializeResult>;
|
|
72
|
+
listTasks: () => Promise<McpRemoteTask[]>;
|
|
55
73
|
listResources: () => Promise<unknown[]>;
|
|
56
74
|
listTools: () => Promise<McpRemoteTool[]>;
|
|
57
75
|
ping: () => Promise<void>;
|
package/dist/src/dispatch.d.ts
CHANGED
|
@@ -4,7 +4,10 @@ import type { McpServerConfig } from "./types";
|
|
|
4
4
|
* Only elicitation uses it; without it the dispatcher is exactly as stateless
|
|
5
5
|
* as it was. */
|
|
6
6
|
export type McpDispatchContext = {
|
|
7
|
+
protocolVersion?: string;
|
|
8
|
+
requestSignal?: AbortSignal;
|
|
7
9
|
sessionId?: string | null;
|
|
8
10
|
sessions?: SessionRegistry;
|
|
9
11
|
};
|
|
12
|
+
export declare const MCP_LATEST_PROTOCOL_VERSION: "2025-11-25";
|
|
10
13
|
export declare const dispatchMcp: <Caller>(config: McpServerConfig<Caller>, caller: Caller, scopes: string[], message: unknown, context?: McpDispatchContext) => Promise<Response>;
|
package/dist/src/index.d.ts
CHANGED
|
@@ -31,9 +31,9 @@
|
|
|
31
31
|
* depends on a model.
|
|
32
32
|
*/
|
|
33
33
|
export { verifyBearer, type BearerResult, type BearerVerifier, type VerifiedJwt, type VerifyBearerConfig, } from "./auth";
|
|
34
|
-
export { createMcpClient, McpClientError, type McpClient, type McpClientOptions, type McpInitializeResult, type McpRemoteTool, } from "./client";
|
|
34
|
+
export { createMcpClient, McpClientError, type McpClient, type McpClientOptions, type McpInitializeResult, type McpRemoteTask, type McpRemoteTool, } from "./client";
|
|
35
35
|
export * from "./oauth";
|
|
36
|
-
export { dispatchMcp, type McpDispatchContext } from "./dispatch";
|
|
36
|
+
export { dispatchMcp, MCP_LATEST_PROTOCOL_VERSION, type McpDispatchContext, } from "./dispatch";
|
|
37
37
|
export { FEEDBACK_INSTRUCTIONS, feedbackTools, type McpFeedbackRating, type McpFeedbackReport, type McpFeedbackStore, type McpProblemReport, } from "./feedback";
|
|
38
38
|
export { createMcpHandler } from "./handler";
|
|
39
39
|
export { metadataPathFor, protectedResourceMetadata, type ProtectedResourceMetadata, } from "./metadata";
|
|
@@ -41,4 +41,4 @@ export { mcpServer } from "./server";
|
|
|
41
41
|
export { createSessionRegistry, type SessionRegistry } from "./sessions";
|
|
42
42
|
export { createMemoryMcpTaskStore, publicMcpTask } from "./tasks";
|
|
43
43
|
export { createPostgresMcpSessionStore, createPostgresMcpTaskStore, mcpPostgresSchemaSql, type McpSqlClient, type McpSqlResult, } from "./postgres";
|
|
44
|
-
export type { McpAgencyOptions, McpAudioContent, McpElicitAnswer, McpElicitationRequest, McpElicitBus, McpElicitResult, McpSessionStore, McpAuthResult, McpCallGate, McpCallMeta, McpContent, McpImageContent, McpPromptArgument, McpPromptDefinition, McpPrompts, McpResource, McpResourceLink, McpResources, McpServerConfig, McpServerInfo, McpTextContent, McpTask, McpTaskStatus, McpTaskStore, McpTasksOptions, McpTool, McpToolAnnotations, McpToolCallContext, McpToolContext, McpToolRegistry, McpToolResult, McpToolReturn, } from "./types";
|
|
44
|
+
export type { McpAgencyOptions, McpAudioContent, McpElicitAnswer, McpElicitationRequest, McpElicitBus, McpElicitResult, McpFormElicitationRequest, McpSessionStore, McpAuthResult, McpCallGate, McpCallMeta, McpContent, McpImageContent, McpPromptArgument, McpPromptDefinition, McpPrompts, McpResource, McpResourceLink, McpResources, McpServerConfig, McpServerInfo, McpTextContent, McpTask, McpTaskStatus, McpTaskStore, McpTasksOptions, McpTool, McpToolAnnotations, McpToolCallContext, McpToolContext, McpToolRegistry, McpToolResult, McpToolReturn, McpUrlElicitationRequest, } from "./types";
|
package/dist/src/sessions.d.ts
CHANGED
|
@@ -8,10 +8,11 @@ export declare const createSessionRegistry: (options?: {
|
|
|
8
8
|
}) => {
|
|
9
9
|
ready: Promise<void>;
|
|
10
10
|
close: () => Promise<void>;
|
|
11
|
-
create: (canElicit: boolean) => Promise<string>;
|
|
11
|
+
create: (canElicit: boolean, canElicitUrl?: boolean) => Promise<string>;
|
|
12
12
|
drop: (id: string) => Promise<void>;
|
|
13
13
|
get: (id: string | null) => Promise<{
|
|
14
14
|
canElicit: boolean;
|
|
15
|
+
canElicitUrl?: boolean;
|
|
15
16
|
} | null>;
|
|
16
17
|
/** The client answered. If the call that asked is running HERE, resolve it.
|
|
17
18
|
* If not, put the answer on the bus so the instance that is waiting can —
|
package/dist/src/tasks.d.ts
CHANGED
|
@@ -1,14 +1,11 @@
|
|
|
1
1
|
import type { McpTask, McpTaskStore } from "./types";
|
|
2
2
|
export declare const createMemoryMcpTaskStore: () => McpTaskStore;
|
|
3
3
|
export declare const publicMcpTask: ({ authorizationKey, ...task }: McpTask) => {
|
|
4
|
+
ttl: number | null;
|
|
5
|
+
pollInterval?: number | undefined;
|
|
4
6
|
createdAt: string;
|
|
5
|
-
error?: Record<string, unknown>;
|
|
6
|
-
inputRequests?: Record<string, unknown>;
|
|
7
7
|
lastUpdatedAt: string;
|
|
8
|
-
pollIntervalMs?: number;
|
|
9
|
-
result?: Record<string, unknown>;
|
|
10
8
|
status: import("./types").McpTaskStatus;
|
|
11
9
|
statusMessage?: string;
|
|
12
10
|
taskId: string;
|
|
13
|
-
ttlMs: number | null;
|
|
14
11
|
};
|
package/dist/src/types.d.ts
CHANGED
|
@@ -49,10 +49,18 @@ export type McpToolReturn = McpContent[] | McpToolResult | string;
|
|
|
49
49
|
* arrays are not allowed by the spec.
|
|
50
50
|
*
|
|
51
51
|
* Servers MUST NOT elicit sensitive information (spec, Security). */
|
|
52
|
-
export type
|
|
52
|
+
export type McpFormElicitationRequest = {
|
|
53
53
|
message: string;
|
|
54
|
+
mode?: "form";
|
|
54
55
|
requestedSchema: Record<string, unknown>;
|
|
55
56
|
};
|
|
57
|
+
export type McpUrlElicitationRequest = {
|
|
58
|
+
elicitationId: string;
|
|
59
|
+
message: string;
|
|
60
|
+
mode: "url";
|
|
61
|
+
url: string;
|
|
62
|
+
};
|
|
63
|
+
export type McpElicitationRequest = McpFormElicitationRequest | McpUrlElicitationRequest;
|
|
56
64
|
/** What came back. `unsupported` is ours, not the spec's: it is what you get
|
|
57
65
|
* when the client never declared the elicitation capability, so a tool can
|
|
58
66
|
* fall back instead of pretending the user declined. */
|
|
@@ -78,12 +86,15 @@ export type McpElicitAnswer = {
|
|
|
78
86
|
export type McpSessionStore = {
|
|
79
87
|
create: (session: {
|
|
80
88
|
canElicit: boolean;
|
|
89
|
+
canElicitUrl?: boolean;
|
|
81
90
|
}) => Promise<string> | string;
|
|
82
91
|
drop: (id: string) => Promise<void> | void;
|
|
83
92
|
get: (id: string) => Promise<{
|
|
84
93
|
canElicit: boolean;
|
|
94
|
+
canElicitUrl?: boolean;
|
|
85
95
|
} | null> | {
|
|
86
96
|
canElicit: boolean;
|
|
97
|
+
canElicitUrl?: boolean;
|
|
87
98
|
} | null;
|
|
88
99
|
};
|
|
89
100
|
/** How an answer reaches the instance that asked the question. The tool call
|
|
@@ -102,6 +113,8 @@ export type McpToolCallContext = {
|
|
|
102
113
|
/** True when this client can actually show the user a form. Check it before
|
|
103
114
|
* designing a flow around elicit(). */
|
|
104
115
|
canElicit: boolean;
|
|
116
|
+
/** True only when the client negotiated secure URL-mode elicitation. */
|
|
117
|
+
canElicitUrl: boolean;
|
|
105
118
|
/** Ask the user a question and wait for the answer. Resolves to
|
|
106
119
|
* `{action:"unsupported"}` immediately when the client can't elicit, and to
|
|
107
120
|
* `{action:"cancel"}` if they never answer. */
|
|
@@ -110,6 +123,9 @@ export type McpToolCallContext = {
|
|
|
110
123
|
/** One callable tool. `inputSchema` is a JSON Schema object. */
|
|
111
124
|
export type McpTool = {
|
|
112
125
|
annotations?: McpToolAnnotations;
|
|
126
|
+
/** OpenID AuthZEN COAZ opt-in marker. When true, inputSchema MUST carry an
|
|
127
|
+
* `x-coaz-mapping`; hosts should evaluate it before invoking the handler. */
|
|
128
|
+
coaz?: boolean;
|
|
113
129
|
/** Enforceable semantic effects from manifest contract 2. A tool carrying
|
|
114
130
|
* this is hidden unless the server configures `agency`. */
|
|
115
131
|
authorization?: ToolAuthorization;
|
|
@@ -123,6 +139,9 @@ export type McpTool = {
|
|
|
123
139
|
mayElicit?: boolean;
|
|
124
140
|
/** JSON Schema for `structuredContent`, advertised on `tools/list`. */
|
|
125
141
|
outputSchema?: Record<string, unknown>;
|
|
142
|
+
/** MCP 2025-11-25 task augmentation support for this tool. Omitted means
|
|
143
|
+
* forbidden, as required by the specification. */
|
|
144
|
+
taskSupport?: "forbidden" | "optional" | "required";
|
|
126
145
|
/** If set, the tool is only listed and callable when the caller's scopes
|
|
127
146
|
* include this. Tools without a scope are always available. Fails closed:
|
|
128
147
|
* a scoped tool is hidden when the caller's scopes are unknown. */
|
|
@@ -195,6 +214,11 @@ export type McpTask = {
|
|
|
195
214
|
export type McpTaskStore = {
|
|
196
215
|
cancel: (taskId: string) => Promise<void> | void;
|
|
197
216
|
get: (taskId: string) => Promise<McpTask | null> | McpTask | null;
|
|
217
|
+
/** List only tasks owned by this authorization context, newest first. */
|
|
218
|
+
list: (authorizationKey: string, options: {
|
|
219
|
+
limit: number;
|
|
220
|
+
offset: number;
|
|
221
|
+
}) => Promise<McpTask[]> | McpTask[];
|
|
198
222
|
save: (task: McpTask) => Promise<void> | void;
|
|
199
223
|
update: (taskId: string, update: Partial<Omit<McpTask, "authorizationKey" | "createdAt" | "taskId">>) => Promise<McpTask | null> | McpTask | null;
|
|
200
224
|
};
|
|
@@ -206,6 +230,8 @@ export type McpTasksOptions<Caller> = {
|
|
|
206
230
|
task: McpTask;
|
|
207
231
|
}) => Promise<void> | void;
|
|
208
232
|
pollIntervalMs?: number;
|
|
233
|
+
/** Maximum tasks returned by one tasks/list page (default 50, max 100). */
|
|
234
|
+
listPageSize?: number;
|
|
209
235
|
shouldCreate: (context: {
|
|
210
236
|
args: unknown;
|
|
211
237
|
caller: Caller;
|
|
@@ -295,7 +321,8 @@ export type McpServerConfig<Caller> = {
|
|
|
295
321
|
/** Protocol versions this endpoint accepts; the first is the preferred one.
|
|
296
322
|
* Defaults to the versions this package knows. */
|
|
297
323
|
supportedProtocols?: string[];
|
|
298
|
-
/**
|
|
324
|
+
/** MCP 2025-11-25 native tasks plus legacy SEP-2663 compatibility for
|
|
325
|
+
* older negotiated protocol versions. */
|
|
299
326
|
tasks?: McpTasksOptions<Caller>;
|
|
300
327
|
/** Build the tool registry for this caller. Called once per request. */
|
|
301
328
|
tools: (ctx: McpToolContext<Caller>) => McpToolRegistry | Promise<McpToolRegistry>;
|
package/package.json
CHANGED
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
"elysia": ">=1.1.0"
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"@absolutejs/agency": "^0.
|
|
15
|
-
"@absolutejs/manifest": "^0.
|
|
14
|
+
"@absolutejs/agency": "^0.4.0",
|
|
15
|
+
"@absolutejs/manifest": "^0.3.0",
|
|
16
16
|
"@sinclair/typebox": "^0.34.0"
|
|
17
17
|
},
|
|
18
18
|
"license": "BUSL-1.1",
|
|
@@ -58,5 +58,5 @@
|
|
|
58
58
|
"typecheck": "tsc --noEmit --project tsconfig.json"
|
|
59
59
|
},
|
|
60
60
|
"types": "./dist/src/index.d.ts",
|
|
61
|
-
"version": "0.
|
|
61
|
+
"version": "0.10.0"
|
|
62
62
|
}
|