@absolutejs/mcp 0.9.0 → 0.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -5
- package/dist/index.js +268 -45
- package/dist/src/client.d.ts +17 -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 +26 -2
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -9,7 +9,9 @@ Serve a remote [Model Context Protocol](https://modelcontextprotocol.io) endpoin
|
|
|
9
9
|
**which** tools to expose and **how** to authorize a request into a caller; the
|
|
10
10
|
package owns the JSON-RPC protocol, protocol-version negotiation, RFC 9728
|
|
11
11
|
discovery metadata, and the `401` challenge that lets a client find your
|
|
12
|
-
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.
|
|
13
15
|
|
|
14
16
|
## Agent action enforcement
|
|
15
17
|
|
|
@@ -40,10 +42,11 @@ mcpServer<Caller>({
|
|
|
40
42
|
|
|
41
43
|
## Durable Tasks
|
|
42
44
|
|
|
43
|
-
The package implements
|
|
44
|
-
|
|
45
|
-
`tasks/
|
|
46
|
-
|
|
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.
|
|
47
50
|
|
|
48
51
|
```ts
|
|
49
52
|
tasks: {
|
|
@@ -52,8 +55,20 @@ tasks: {
|
|
|
52
55
|
store: createMemoryMcpTaskStore(), // use a durable shared store in production
|
|
53
56
|
ttlMs: 60 * 60 * 1000,
|
|
54
57
|
}
|
|
58
|
+
|
|
59
|
+
tools: () => ({
|
|
60
|
+
long_running_report: {
|
|
61
|
+
taskSupport: "optional", // "required" and "forbidden" are also supported
|
|
62
|
+
// normal tool definition…
|
|
63
|
+
},
|
|
64
|
+
})
|
|
55
65
|
```
|
|
56
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
|
+
|
|
57
72
|
For multi-instance production deployments, use
|
|
58
73
|
`createPostgresMcpTaskStore()` and `createPostgresMcpSessionStore()` after
|
|
59
74
|
applying `mcpPostgresSchemaSql()`. Task updates and cancellation protect
|
|
@@ -228,6 +243,13 @@ dismissed it), or `unsupported` (this client can't ask anyone — check
|
|
|
228
243
|
`canElicit` and take another path). Never fabricate an answer for the user; the
|
|
229
244
|
spec also forbids eliciting **sensitive information**.
|
|
230
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
|
+
|
|
231
253
|
**The trade-off, stated plainly.** Elicitation is the one MCP feature a
|
|
232
254
|
stateless server cannot do: the question goes out on the SSE stream of an
|
|
233
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"
|
|
@@ -269,7 +276,8 @@ var createMcpClient = (options) => {
|
|
|
269
276
|
description: typeof tool.description === "string" ? tool.description : undefined,
|
|
270
277
|
inputSchema: isRecord(tool.inputSchema) ? tool.inputSchema : undefined,
|
|
271
278
|
name: typeof tool.name === "string" ? tool.name : "",
|
|
272
|
-
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
|
|
273
281
|
})));
|
|
274
282
|
const next = isRecord(result) && typeof result.nextCursor === "string" ? result.nextCursor : undefined;
|
|
275
283
|
if (next === undefined)
|
|
@@ -285,6 +293,44 @@ var createMcpClient = (options) => {
|
|
|
285
293
|
}
|
|
286
294
|
return { content: [], isError: false };
|
|
287
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
|
+
};
|
|
288
334
|
const listResources = async () => {
|
|
289
335
|
const collected = [];
|
|
290
336
|
let cursor;
|
|
@@ -304,7 +350,19 @@ var createMcpClient = (options) => {
|
|
|
304
350
|
const ping = async () => {
|
|
305
351
|
await rpc("ping");
|
|
306
352
|
};
|
|
307
|
-
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
|
+
};
|
|
308
366
|
};
|
|
309
367
|
// src/oauth.ts
|
|
310
368
|
var splitChallenges = (value) => {
|
|
@@ -658,6 +716,7 @@ var createMemoryMcpTaskStore = () => {
|
|
|
658
716
|
const task = tasks.get(taskId);
|
|
659
717
|
return task === undefined ? null : clone(task);
|
|
660
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),
|
|
661
720
|
save: (task) => {
|
|
662
721
|
tasks.set(task.taskId, clone(task));
|
|
663
722
|
},
|
|
@@ -679,11 +738,22 @@ var createMemoryMcpTaskStore = () => {
|
|
|
679
738
|
};
|
|
680
739
|
};
|
|
681
740
|
var publicMcpTask = ({ authorizationKey, ...task }) => {
|
|
682
|
-
|
|
741
|
+
const { error, inputRequests, pollIntervalMs, result, ttlMs, ...rest } = task;
|
|
742
|
+
return {
|
|
743
|
+
...rest,
|
|
744
|
+
...pollIntervalMs === undefined ? {} : { pollInterval: pollIntervalMs },
|
|
745
|
+
ttl: ttlMs
|
|
746
|
+
};
|
|
683
747
|
};
|
|
684
748
|
|
|
685
749
|
// src/dispatch.ts
|
|
686
|
-
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
|
+
];
|
|
687
757
|
var DEFAULT_RESOURCE_MIME = "text/markdown";
|
|
688
758
|
var DEFAULT_LIST_PAGE_SIZE = 50;
|
|
689
759
|
var decodeCursor = (params) => {
|
|
@@ -730,18 +800,34 @@ var normalizeResult = (value) => {
|
|
|
730
800
|
return { content: value, isError: false };
|
|
731
801
|
return { isError: false, ...value };
|
|
732
802
|
};
|
|
733
|
-
var
|
|
734
|
-
if (!isRecord(params) || !isRecord(params.capabilities))
|
|
735
|
-
return false;
|
|
736
|
-
|
|
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
|
+
};
|
|
737
814
|
};
|
|
738
815
|
var initialize = async (config, id, params, context) => {
|
|
739
816
|
const supported = config.supportedProtocols ?? DEFAULT_PROTOCOLS;
|
|
817
|
+
const protocolVersion = negotiateProtocol(supported, params);
|
|
740
818
|
const capabilities = {
|
|
741
819
|
tools: { listChanged: false }
|
|
742
820
|
};
|
|
743
821
|
if (config.tasks !== undefined) {
|
|
744
|
-
|
|
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
|
+
}
|
|
745
831
|
}
|
|
746
832
|
if (config.prompts)
|
|
747
833
|
capabilities.prompts = { listChanged: false };
|
|
@@ -751,16 +837,17 @@ var initialize = async (config, id, params, context) => {
|
|
|
751
837
|
const response = rpcResult(id, {
|
|
752
838
|
capabilities,
|
|
753
839
|
...config.instructions === undefined ? {} : { instructions: config.instructions },
|
|
754
|
-
protocolVersion
|
|
840
|
+
protocolVersion,
|
|
755
841
|
serverInfo: config.serverInfo
|
|
756
842
|
});
|
|
757
843
|
if (!config.elicitation?.enabled || !context.sessions)
|
|
758
844
|
return response;
|
|
759
|
-
const
|
|
845
|
+
const elicitation = clientElicitation(params);
|
|
846
|
+
const sessionId = await context.sessions.create(elicitation.form || elicitation.url, elicitation.url);
|
|
760
847
|
response.headers.set("Mcp-Session-Id", sessionId);
|
|
761
848
|
return response;
|
|
762
849
|
};
|
|
763
|
-
var toolsList = async (config, caller, scopes, id, params) => {
|
|
850
|
+
var toolsList = async (config, caller, scopes, id, params, protocolVersion) => {
|
|
764
851
|
const tools = await config.tools({ caller, meta: {} });
|
|
765
852
|
const visible = Object.entries(tools).filter(([, tool]) => scopeAllows(tool, scopes) && agencyAllows(config, tool, scopes)).map(([name, tool]) => ({
|
|
766
853
|
annotations: tool.annotations,
|
|
@@ -768,7 +855,8 @@ var toolsList = async (config, caller, scopes, id, params) => {
|
|
|
768
855
|
description: tool.description,
|
|
769
856
|
inputSchema: tool.inputSchema,
|
|
770
857
|
name,
|
|
771
|
-
...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 } } : {}
|
|
772
860
|
}));
|
|
773
861
|
const { items, nextCursor } = paginate(visible, decodeCursor(params), config.listPageSize ?? DEFAULT_LIST_PAGE_SIZE);
|
|
774
862
|
return rpcResult(id, {
|
|
@@ -779,6 +867,7 @@ var toolsList = async (config, caller, scopes, id, params) => {
|
|
|
779
867
|
var errorResult = (id, text) => rpcResult(id, { content: [{ text, type: "text" }], isError: true });
|
|
780
868
|
var noElicit = {
|
|
781
869
|
canElicit: false,
|
|
870
|
+
canElicitUrl: false,
|
|
782
871
|
elicit: () => Promise.resolve({ action: "unsupported" })
|
|
783
872
|
};
|
|
784
873
|
var SSE_HEADERS = {
|
|
@@ -871,7 +960,7 @@ var runTool = async (config, caller, scopes, id, name, args, meta, tool, context
|
|
|
871
960
|
await config.onCall({ args, caller, meta, name, ok });
|
|
872
961
|
return payload;
|
|
873
962
|
};
|
|
874
|
-
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) => {
|
|
875
964
|
const encoder = new TextEncoder;
|
|
876
965
|
const body = new ReadableStream({
|
|
877
966
|
async start(controller) {
|
|
@@ -887,9 +976,24 @@ var toolsCallStreaming = (config, caller, scopes, id, name, args, meta, tool, se
|
|
|
887
976
|
};
|
|
888
977
|
const context = {
|
|
889
978
|
canElicit,
|
|
979
|
+
canElicitUrl,
|
|
890
980
|
elicit: async (request) => {
|
|
891
981
|
if (!canElicit)
|
|
892
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
|
+
}
|
|
893
997
|
const pending = sessions.startElicit(request);
|
|
894
998
|
send({
|
|
895
999
|
id: pending.id,
|
|
@@ -931,8 +1035,22 @@ var toolsCall = async (config, caller, scopes, id, params, context) => {
|
|
|
931
1035
|
return rpcError(id, JSONRPC_INVALID_PARAMS, `Unknown tool: ${name}`);
|
|
932
1036
|
}
|
|
933
1037
|
const tasks = config.tasks;
|
|
934
|
-
|
|
935
|
-
|
|
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)) {
|
|
936
1054
|
return rpcError(id, JSONRPC_MISSING_REQUIRED_CLIENT_CAPABILITY, "Missing required client capability", {
|
|
937
1055
|
requiredCapabilities: {
|
|
938
1056
|
extensions: { "io.modelcontextprotocol/tasks": {} }
|
|
@@ -940,6 +1058,7 @@ var toolsCall = async (config, caller, scopes, id, params, context) => {
|
|
|
940
1058
|
});
|
|
941
1059
|
}
|
|
942
1060
|
const createdAt = new Date().toISOString();
|
|
1061
|
+
const requestedTtl = requestedTaskParams !== undefined && typeof requestedTaskParams.ttl === "number" && requestedTaskParams.ttl >= 0 ? requestedTaskParams.ttl : undefined;
|
|
943
1062
|
const task = {
|
|
944
1063
|
authorizationKey: await tasks.authorizationKey(caller),
|
|
945
1064
|
createdAt,
|
|
@@ -947,7 +1066,7 @@ var toolsCall = async (config, caller, scopes, id, params, context) => {
|
|
|
947
1066
|
pollIntervalMs: tasks.pollIntervalMs,
|
|
948
1067
|
status: "working",
|
|
949
1068
|
taskId: crypto.randomUUID(),
|
|
950
|
-
ttlMs: tasks.ttlMs ?? null
|
|
1069
|
+
ttlMs: tasks.ttlMs ?? requestedTtl ?? null
|
|
951
1070
|
};
|
|
952
1071
|
await tasks.store.save(task);
|
|
953
1072
|
setTimeout(() => {
|
|
@@ -955,7 +1074,7 @@ var toolsCall = async (config, caller, scopes, id, params, context) => {
|
|
|
955
1074
|
const result = isRecord(payload2) && isRecord(payload2.result) ? payload2.result : { content: [], isError: true };
|
|
956
1075
|
await tasks.store.update(task.taskId, {
|
|
957
1076
|
result,
|
|
958
|
-
status: "completed"
|
|
1077
|
+
status: result.isError === true ? "failed" : "completed"
|
|
959
1078
|
});
|
|
960
1079
|
}).catch(async (error) => {
|
|
961
1080
|
await tasks.store.update(task.taskId, {
|
|
@@ -967,12 +1086,12 @@ var toolsCall = async (config, caller, scopes, id, params, context) => {
|
|
|
967
1086
|
});
|
|
968
1087
|
});
|
|
969
1088
|
}, 0);
|
|
970
|
-
return rpcResult(id, { ...publicMcpTask(task), resultType: "task" });
|
|
1089
|
+
return nativeTasks ? rpcResult(id, { task: publicMcpTask(task) }) : rpcResult(id, { ...publicMcpTask(task), resultType: "task" });
|
|
971
1090
|
}
|
|
972
1091
|
const sessions = context.sessions;
|
|
973
1092
|
const session = sessions ? await sessions.get(context.sessionId ?? null) : null;
|
|
974
1093
|
if (tool.mayElicit === true && config.elicitation?.enabled === true && sessions && session) {
|
|
975
|
-
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);
|
|
976
1095
|
}
|
|
977
1096
|
const payload = await runTool(config, caller, scopes, id, name, args, meta, tool, noElicit);
|
|
978
1097
|
return new Response(JSON.stringify(payload), {
|
|
@@ -997,11 +1116,70 @@ var authorizedTask = async (config, caller, params) => {
|
|
|
997
1116
|
const authorizationKey = await config.tasks.authorizationKey(caller);
|
|
998
1117
|
return task.authorizationKey === authorizationKey ? task : null;
|
|
999
1118
|
};
|
|
1000
|
-
var tasksGet = async (config, caller, id, params) => {
|
|
1119
|
+
var tasksGet = async (config, caller, id, params, native) => {
|
|
1001
1120
|
const task = await authorizedTask(config, caller, params);
|
|
1002
1121
|
if (task === null)
|
|
1003
1122
|
return rpcError(id, JSONRPC_INVALID_PARAMS, "Unknown task");
|
|
1004
|
-
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
|
+
});
|
|
1005
1183
|
};
|
|
1006
1184
|
var tasksUpdate = async (config, caller, id, params) => {
|
|
1007
1185
|
const task = await authorizedTask(config, caller, params);
|
|
@@ -1015,12 +1193,16 @@ var tasksUpdate = async (config, caller, id, params) => {
|
|
|
1015
1193
|
});
|
|
1016
1194
|
return rpcResult(id, { resultType: "complete" });
|
|
1017
1195
|
};
|
|
1018
|
-
var tasksCancel = async (config, caller, id, params) => {
|
|
1196
|
+
var tasksCancel = async (config, caller, id, params, native) => {
|
|
1019
1197
|
const task = await authorizedTask(config, caller, params);
|
|
1020
1198
|
if (task === null)
|
|
1021
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
|
+
}
|
|
1022
1203
|
await config.tasks?.store.cancel(task.taskId);
|
|
1023
|
-
|
|
1204
|
+
const cancelled = await config.tasks?.store.get(task.taskId);
|
|
1205
|
+
return rpcResult(id, native && cancelled !== null && cancelled !== undefined ? publicMcpTask(cancelled) : { resultType: "complete" });
|
|
1024
1206
|
};
|
|
1025
1207
|
var promptsList = (config, id, params) => {
|
|
1026
1208
|
const definitions = config.prompts?.definitions ?? {};
|
|
@@ -1091,7 +1273,10 @@ var elicitAnswer = async (message, context) => {
|
|
|
1091
1273
|
return notificationAck();
|
|
1092
1274
|
const result = isRecord(message.result) ? message.result : null;
|
|
1093
1275
|
const action = result?.action;
|
|
1094
|
-
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" };
|
|
1095
1280
|
await context.sessions.resolveElicit({
|
|
1096
1281
|
requestId,
|
|
1097
1282
|
result: answer,
|
|
@@ -1108,6 +1293,7 @@ var dispatchMcp = async (config, caller, scopes, message, context = {}) => {
|
|
|
1108
1293
|
if (!("method" in message))
|
|
1109
1294
|
return elicitAnswer(message, context);
|
|
1110
1295
|
const id = idOf(message);
|
|
1296
|
+
const protocolVersion = context.protocolVersion ?? "2025-06-18";
|
|
1111
1297
|
const method = typeof message.method === "string" ? message.method : "";
|
|
1112
1298
|
const { params } = message;
|
|
1113
1299
|
if (method === "initialize") {
|
|
@@ -1124,17 +1310,24 @@ var dispatchMcp = async (config, caller, scopes, message, context = {}) => {
|
|
|
1124
1310
|
if (method === "ping")
|
|
1125
1311
|
return rpcResult(id, {});
|
|
1126
1312
|
if (method === "tools/list") {
|
|
1127
|
-
return toolsList(config, caller, scopes, id, params);
|
|
1313
|
+
return toolsList(config, caller, scopes, id, params, protocolVersion);
|
|
1128
1314
|
}
|
|
1129
1315
|
if (method === "tools/call") {
|
|
1130
|
-
return toolsCall(config, caller, scopes, id, params,
|
|
1316
|
+
return toolsCall(config, caller, scopes, id, params, {
|
|
1317
|
+
...context,
|
|
1318
|
+
protocolVersion
|
|
1319
|
+
});
|
|
1131
1320
|
}
|
|
1132
1321
|
if (method === "tasks/get")
|
|
1133
|
-
return tasksGet(config, caller, id, params);
|
|
1134
|
-
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)
|
|
1135
1328
|
return tasksUpdate(config, caller, id, params);
|
|
1136
1329
|
if (method === "tasks/cancel")
|
|
1137
|
-
return tasksCancel(config, caller, id, params);
|
|
1330
|
+
return tasksCancel(config, caller, id, params, protocolVersion === MCP_LATEST_PROTOCOL_VERSION);
|
|
1138
1331
|
if (method === "prompts/list")
|
|
1139
1332
|
return promptsList(config, id, params);
|
|
1140
1333
|
if (method === "prompts/get")
|
|
@@ -1263,7 +1456,11 @@ var createMemoryStore = (ttlMs) => {
|
|
|
1263
1456
|
create: (session) => {
|
|
1264
1457
|
sweep();
|
|
1265
1458
|
const id = crypto.randomUUID();
|
|
1266
|
-
sessions.set(id, {
|
|
1459
|
+
sessions.set(id, {
|
|
1460
|
+
canElicit: session.canElicit,
|
|
1461
|
+
canElicitUrl: session.canElicitUrl ?? false,
|
|
1462
|
+
lastSeen: Date.now()
|
|
1463
|
+
});
|
|
1267
1464
|
return id;
|
|
1268
1465
|
},
|
|
1269
1466
|
drop: (id) => {
|
|
@@ -1274,7 +1471,10 @@ var createMemoryStore = (ttlMs) => {
|
|
|
1274
1471
|
if (!session)
|
|
1275
1472
|
return null;
|
|
1276
1473
|
session.lastSeen = Date.now();
|
|
1277
|
-
return {
|
|
1474
|
+
return {
|
|
1475
|
+
canElicit: session.canElicit,
|
|
1476
|
+
canElicitUrl: session.canElicitUrl
|
|
1477
|
+
};
|
|
1278
1478
|
}
|
|
1279
1479
|
};
|
|
1280
1480
|
};
|
|
@@ -1309,9 +1509,9 @@ var createSessionRegistry = (options) => {
|
|
|
1309
1509
|
});
|
|
1310
1510
|
pending.clear();
|
|
1311
1511
|
},
|
|
1312
|
-
create: async (canElicit) => {
|
|
1512
|
+
create: async (canElicit, canElicitUrl = false) => {
|
|
1313
1513
|
await ready;
|
|
1314
|
-
return await store.create({ canElicit });
|
|
1514
|
+
return await store.create({ canElicit, canElicitUrl });
|
|
1315
1515
|
},
|
|
1316
1516
|
drop: async (id) => {
|
|
1317
1517
|
await ready;
|
|
@@ -1390,12 +1590,27 @@ var runMcpPost = async (config, request, body) => {
|
|
|
1390
1590
|
if (Array.isArray(body)) {
|
|
1391
1591
|
return rpcError(null, JSONRPC_INVALID_REQUEST, "Batching is not supported");
|
|
1392
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
|
+
}
|
|
1393
1606
|
const sessions = registryFor(config);
|
|
1394
1607
|
const sessionId = request.headers.get("mcp-session-id");
|
|
1395
1608
|
if (sessions && sessionId && !await sessions.get(sessionId)) {
|
|
1396
1609
|
return new Response(null, { status: HTTP_NOT_FOUND });
|
|
1397
1610
|
}
|
|
1398
1611
|
return dispatchMcp(config, auth.caller, auth.scopes ?? [], body, {
|
|
1612
|
+
protocolVersion: protocolVersion ?? MCP_LATEST_PROTOCOL_VERSION,
|
|
1613
|
+
requestSignal: request.signal,
|
|
1399
1614
|
sessionId,
|
|
1400
1615
|
sessions
|
|
1401
1616
|
}).catch(() => rpcError(null, JSONRPC_INVALID_REQUEST, "Internal error"));
|
|
@@ -1472,10 +1687,12 @@ CREATE INDEX IF NOT EXISTS tasks_expiry_idx ON ${ns}.tasks (expires_at) WHERE ex
|
|
|
1472
1687
|
CREATE TABLE IF NOT EXISTS ${ns}.sessions (
|
|
1473
1688
|
session_id text PRIMARY KEY,
|
|
1474
1689
|
can_elicit boolean NOT NULL,
|
|
1690
|
+
can_elicit_url boolean NOT NULL DEFAULT false,
|
|
1475
1691
|
created_at timestamptz NOT NULL DEFAULT now(),
|
|
1476
1692
|
last_seen_at timestamptz NOT NULL DEFAULT now(),
|
|
1477
1693
|
expires_at timestamptz NOT NULL
|
|
1478
1694
|
);
|
|
1695
|
+
ALTER TABLE ${ns}.sessions ADD COLUMN IF NOT EXISTS can_elicit_url boolean NOT NULL DEFAULT false;
|
|
1479
1696
|
CREATE INDEX IF NOT EXISTS sessions_expiry_idx ON ${ns}.sessions (expires_at);`;
|
|
1480
1697
|
};
|
|
1481
1698
|
var createPostgresMcpTaskStore = ({
|
|
@@ -1494,6 +1711,7 @@ var createPostgresMcpTaskStore = ({
|
|
|
1494
1711
|
]);
|
|
1495
1712
|
},
|
|
1496
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),
|
|
1497
1715
|
save: async (task) => {
|
|
1498
1716
|
const expiresAt = task.ttlMs === null ? null : new Date(new Date(task.createdAt).getTime() + task.ttlMs).toISOString();
|
|
1499
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`, [
|
|
@@ -1524,12 +1742,13 @@ var createPostgresMcpSessionStore = ({
|
|
|
1524
1742
|
}) => {
|
|
1525
1743
|
const ns = namespaceOf(namespace);
|
|
1526
1744
|
return {
|
|
1527
|
-
create: async ({ canElicit }) => {
|
|
1745
|
+
create: async ({ canElicit, canElicitUrl }) => {
|
|
1528
1746
|
const id = crypto.randomUUID();
|
|
1529
1747
|
const current = now();
|
|
1530
|
-
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)`, [
|
|
1531
1749
|
id,
|
|
1532
1750
|
canElicit,
|
|
1751
|
+
canElicitUrl ?? false,
|
|
1533
1752
|
current.toISOString(),
|
|
1534
1753
|
new Date(current.getTime() + ttlMs).toISOString()
|
|
1535
1754
|
]);
|
|
@@ -1542,13 +1761,16 @@ var createPostgresMcpSessionStore = ({
|
|
|
1542
1761
|
},
|
|
1543
1762
|
get: async (id) => {
|
|
1544
1763
|
const current = now();
|
|
1545
|
-
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`, [
|
|
1546
1765
|
id,
|
|
1547
1766
|
current.toISOString(),
|
|
1548
1767
|
new Date(current.getTime() + ttlMs).toISOString()
|
|
1549
1768
|
]);
|
|
1550
1769
|
const row = result.rows[0];
|
|
1551
|
-
return row === undefined ? null : {
|
|
1770
|
+
return row === undefined ? null : {
|
|
1771
|
+
canElicit: row.can_elicit,
|
|
1772
|
+
canElicitUrl: row.can_elicit_url
|
|
1773
|
+
};
|
|
1552
1774
|
}
|
|
1553
1775
|
};
|
|
1554
1776
|
};
|
|
@@ -1573,5 +1795,6 @@ export {
|
|
|
1573
1795
|
createMcpClient,
|
|
1574
1796
|
createMcpAuthorizationRequest,
|
|
1575
1797
|
McpClientError,
|
|
1798
|
+
MCP_LATEST_PROTOCOL_VERSION,
|
|
1576
1799
|
FEEDBACK_INSTRUCTIONS
|
|
1577
1800
|
};
|
package/dist/src/client.d.ts
CHANGED
|
@@ -39,6 +39,16 @@ export type McpRemoteTool = {
|
|
|
39
39
|
inputSchema?: Record<string, unknown>;
|
|
40
40
|
name: string;
|
|
41
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;
|
|
42
52
|
};
|
|
43
53
|
export type McpInitializeResult = {
|
|
44
54
|
capabilities?: Record<string, unknown>;
|
|
@@ -52,7 +62,14 @@ export type McpInitializeResult = {
|
|
|
52
62
|
};
|
|
53
63
|
export type McpClient = {
|
|
54
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>;
|
|
55
71
|
initialize: () => Promise<McpInitializeResult>;
|
|
72
|
+
listTasks: () => Promise<McpRemoteTask[]>;
|
|
56
73
|
listResources: () => Promise<unknown[]>;
|
|
57
74
|
listTools: () => Promise<McpRemoteTool[]>;
|
|
58
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. */
|
|
@@ -126,6 +139,9 @@ export type McpTool = {
|
|
|
126
139
|
mayElicit?: boolean;
|
|
127
140
|
/** JSON Schema for `structuredContent`, advertised on `tools/list`. */
|
|
128
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";
|
|
129
145
|
/** If set, the tool is only listed and callable when the caller's scopes
|
|
130
146
|
* include this. Tools without a scope are always available. Fails closed:
|
|
131
147
|
* a scoped tool is hidden when the caller's scopes are unknown. */
|
|
@@ -198,6 +214,11 @@ export type McpTask = {
|
|
|
198
214
|
export type McpTaskStore = {
|
|
199
215
|
cancel: (taskId: string) => Promise<void> | void;
|
|
200
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[];
|
|
201
222
|
save: (task: McpTask) => Promise<void> | void;
|
|
202
223
|
update: (taskId: string, update: Partial<Omit<McpTask, "authorizationKey" | "createdAt" | "taskId">>) => Promise<McpTask | null> | McpTask | null;
|
|
203
224
|
};
|
|
@@ -209,6 +230,8 @@ export type McpTasksOptions<Caller> = {
|
|
|
209
230
|
task: McpTask;
|
|
210
231
|
}) => Promise<void> | void;
|
|
211
232
|
pollIntervalMs?: number;
|
|
233
|
+
/** Maximum tasks returned by one tasks/list page (default 50, max 100). */
|
|
234
|
+
listPageSize?: number;
|
|
212
235
|
shouldCreate: (context: {
|
|
213
236
|
args: unknown;
|
|
214
237
|
caller: Caller;
|
|
@@ -298,7 +321,8 @@ export type McpServerConfig<Caller> = {
|
|
|
298
321
|
/** Protocol versions this endpoint accepts; the first is the preferred one.
|
|
299
322
|
* Defaults to the versions this package knows. */
|
|
300
323
|
supportedProtocols?: string[];
|
|
301
|
-
/**
|
|
324
|
+
/** MCP 2025-11-25 native tasks plus legacy SEP-2663 compatibility for
|
|
325
|
+
* older negotiated protocol versions. */
|
|
302
326
|
tasks?: McpTasksOptions<Caller>;
|
|
303
327
|
/** Build the tool registry for this caller. Called once per request. */
|
|
304
328
|
tools: (ctx: McpToolContext<Caller>) => McpToolRegistry | Promise<McpToolRegistry>;
|
package/package.json
CHANGED
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"name": "@absolutejs/mcp",
|
|
40
40
|
"repository": {
|
|
41
41
|
"type": "git",
|
|
42
|
-
"url": "https://github.com/absolutejs/mcp.git"
|
|
42
|
+
"url": "git+https://github.com/absolutejs/mcp.git"
|
|
43
43
|
},
|
|
44
44
|
"homepage": "https://github.com/absolutejs/mcp",
|
|
45
45
|
"bugs": {
|
|
@@ -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.1"
|
|
62
62
|
}
|