@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 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 the final `io.modelcontextprotocol/tasks` extension from
44
- SEP-2663: server-directed task creation, `tasks/get`, `tasks/update`, and
45
- `tasks/cancel`. There is intentionally no `tasks/list`; task handles are bound
46
- to an authorization key and checked on every request.
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-06-18";
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 request = isRecord(message.params) ? {
156
- message: typeof message.params.message === "string" ? message.params.message : "",
157
- requestedSchema: isRecord(message.params.requestedSchema) ? message.params.requestedSchema : {}
158
- } : { message: "", requestedSchema: {} };
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 { callTool, initialize, listResources, listTools, ping, readResource };
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
- return task;
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 DEFAULT_PROTOCOLS = ["2025-06-18", "2025-03-26", "2024-11-05"];
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 clientCanElicit = (params) => {
734
- if (!isRecord(params) || !isRecord(params.capabilities))
735
- return false;
736
- return isRecord(params.capabilities.elicitation);
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
- capabilities.extensions = { "io.modelcontextprotocol/tasks": {} };
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: negotiateProtocol(supported, params),
840
+ protocolVersion,
755
841
  serverInfo: config.serverInfo
756
842
  });
757
843
  if (!config.elicitation?.enabled || !context.sessions)
758
844
  return response;
759
- const sessionId = await context.sessions.create(clientCanElicit(params));
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
- if (tasks !== undefined && await tasks.shouldCreate({ args, caller, name })) {
935
- if (!supportsTasks(params)) {
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
- return rpcResult(id, { resultType: "complete" });
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" && isRecord(result?.content) ? { action: "accept", content: result.content } : action === "decline" ? { action: "decline" } : { action: "cancel" };
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, context);
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/update")
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, { canElicit: session.canElicit, lastSeen: Date.now() });
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 { canElicit: session.canElicit };
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, $3::timestamptz, $4::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 : { canElicit: row.can_elicit };
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
  };
@@ -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>;
@@ -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>;
@@ -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";
@@ -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 —
@@ -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
  };
@@ -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 McpElicitationRequest = {
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
- /** Final SEP-2663 `io.modelcontextprotocol/tasks` extension support. */
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.9.0"
61
+ "version": "0.10.1"
62
62
  }