server.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250
  1. "use strict";
  2. /**
  3. * Experimental server task features for MCP SDK.
  4. * WARNING: These APIs are experimental and may change without notice.
  5. *
  6. * @experimental
  7. */
  8. Object.defineProperty(exports, "__esModule", { value: true });
  9. exports.ExperimentalServerTasks = void 0;
  10. const types_js_1 = require("../../types.js");
  11. /**
  12. * Experimental task features for low-level MCP servers.
  13. *
  14. * Access via `server.experimental.tasks`:
  15. * ```typescript
  16. * const stream = server.experimental.tasks.requestStream(request, schema, options);
  17. * ```
  18. *
  19. * For high-level server usage with task-based tools, use `McpServer.experimental.tasks` instead.
  20. *
  21. * @experimental
  22. */
  23. class ExperimentalServerTasks {
  24. constructor(_server) {
  25. this._server = _server;
  26. }
  27. /**
  28. * Sends a request and returns an AsyncGenerator that yields response messages.
  29. * The generator is guaranteed to end with either a 'result' or 'error' message.
  30. *
  31. * This method provides streaming access to request processing, allowing you to
  32. * observe intermediate task status updates for task-augmented requests.
  33. *
  34. * @param request - The request to send
  35. * @param resultSchema - Zod schema for validating the result
  36. * @param options - Optional request options (timeout, signal, task creation params, etc.)
  37. * @returns AsyncGenerator that yields ResponseMessage objects
  38. *
  39. * @experimental
  40. */
  41. requestStream(request, resultSchema, options) {
  42. return this._server.requestStream(request, resultSchema, options);
  43. }
  44. /**
  45. * Sends a sampling request and returns an AsyncGenerator that yields response messages.
  46. * The generator is guaranteed to end with either a 'result' or 'error' message.
  47. *
  48. * For task-augmented requests, yields 'taskCreated' and 'taskStatus' messages
  49. * before the final result.
  50. *
  51. * @example
  52. * ```typescript
  53. * const stream = server.experimental.tasks.createMessageStream({
  54. * messages: [{ role: 'user', content: { type: 'text', text: 'Hello' } }],
  55. * maxTokens: 100
  56. * }, {
  57. * onprogress: (progress) => {
  58. * // Handle streaming tokens via progress notifications
  59. * console.log('Progress:', progress.message);
  60. * }
  61. * });
  62. *
  63. * for await (const message of stream) {
  64. * switch (message.type) {
  65. * case 'taskCreated':
  66. * console.log('Task created:', message.task.taskId);
  67. * break;
  68. * case 'taskStatus':
  69. * console.log('Task status:', message.task.status);
  70. * break;
  71. * case 'result':
  72. * console.log('Final result:', message.result);
  73. * break;
  74. * case 'error':
  75. * console.error('Error:', message.error);
  76. * break;
  77. * }
  78. * }
  79. * ```
  80. *
  81. * @param params - The sampling request parameters
  82. * @param options - Optional request options (timeout, signal, task creation params, onprogress, etc.)
  83. * @returns AsyncGenerator that yields ResponseMessage objects
  84. *
  85. * @experimental
  86. */
  87. createMessageStream(params, options) {
  88. // Access client capabilities via the server
  89. const clientCapabilities = this._server.getClientCapabilities();
  90. // Capability check - only required when tools/toolChoice are provided
  91. if ((params.tools || params.toolChoice) && !clientCapabilities?.sampling?.tools) {
  92. throw new Error('Client does not support sampling tools capability.');
  93. }
  94. // Message structure validation - always validate tool_use/tool_result pairs.
  95. // These may appear even without tools/toolChoice in the current request when
  96. // a previous sampling request returned tool_use and this is a follow-up with results.
  97. if (params.messages.length > 0) {
  98. const lastMessage = params.messages[params.messages.length - 1];
  99. const lastContent = Array.isArray(lastMessage.content) ? lastMessage.content : [lastMessage.content];
  100. const hasToolResults = lastContent.some(c => c.type === 'tool_result');
  101. const previousMessage = params.messages.length > 1 ? params.messages[params.messages.length - 2] : undefined;
  102. const previousContent = previousMessage
  103. ? Array.isArray(previousMessage.content)
  104. ? previousMessage.content
  105. : [previousMessage.content]
  106. : [];
  107. const hasPreviousToolUse = previousContent.some(c => c.type === 'tool_use');
  108. if (hasToolResults) {
  109. if (lastContent.some(c => c.type !== 'tool_result')) {
  110. throw new Error('The last message must contain only tool_result content if any is present');
  111. }
  112. if (!hasPreviousToolUse) {
  113. throw new Error('tool_result blocks are not matching any tool_use from the previous message');
  114. }
  115. }
  116. if (hasPreviousToolUse) {
  117. // Extract tool_use IDs from previous message and tool_result IDs from current message
  118. const toolUseIds = new Set(previousContent.filter(c => c.type === 'tool_use').map(c => c.id));
  119. const toolResultIds = new Set(lastContent.filter(c => c.type === 'tool_result').map(c => c.toolUseId));
  120. if (toolUseIds.size !== toolResultIds.size || ![...toolUseIds].every(id => toolResultIds.has(id))) {
  121. throw new Error('ids of tool_result blocks and tool_use blocks from previous message do not match');
  122. }
  123. }
  124. }
  125. return this.requestStream({
  126. method: 'sampling/createMessage',
  127. params
  128. }, types_js_1.CreateMessageResultSchema, options);
  129. }
  130. /**
  131. * Sends an elicitation request and returns an AsyncGenerator that yields response messages.
  132. * The generator is guaranteed to end with either a 'result' or 'error' message.
  133. *
  134. * For task-augmented requests (especially URL-based elicitation), yields 'taskCreated'
  135. * and 'taskStatus' messages before the final result.
  136. *
  137. * @example
  138. * ```typescript
  139. * const stream = server.experimental.tasks.elicitInputStream({
  140. * mode: 'url',
  141. * message: 'Please authenticate',
  142. * elicitationId: 'auth-123',
  143. * url: 'https://example.com/auth'
  144. * }, {
  145. * task: { ttl: 300000 } // Task-augmented for long-running auth flow
  146. * });
  147. *
  148. * for await (const message of stream) {
  149. * switch (message.type) {
  150. * case 'taskCreated':
  151. * console.log('Task created:', message.task.taskId);
  152. * break;
  153. * case 'taskStatus':
  154. * console.log('Task status:', message.task.status);
  155. * break;
  156. * case 'result':
  157. * console.log('User action:', message.result.action);
  158. * break;
  159. * case 'error':
  160. * console.error('Error:', message.error);
  161. * break;
  162. * }
  163. * }
  164. * ```
  165. *
  166. * @param params - The elicitation request parameters
  167. * @param options - Optional request options (timeout, signal, task creation params, etc.)
  168. * @returns AsyncGenerator that yields ResponseMessage objects
  169. *
  170. * @experimental
  171. */
  172. elicitInputStream(params, options) {
  173. // Access client capabilities via the server
  174. const clientCapabilities = this._server.getClientCapabilities();
  175. const mode = params.mode ?? 'form';
  176. // Capability check based on mode
  177. switch (mode) {
  178. case 'url': {
  179. if (!clientCapabilities?.elicitation?.url) {
  180. throw new Error('Client does not support url elicitation.');
  181. }
  182. break;
  183. }
  184. case 'form': {
  185. if (!clientCapabilities?.elicitation?.form) {
  186. throw new Error('Client does not support form elicitation.');
  187. }
  188. break;
  189. }
  190. }
  191. // Normalize params to ensure mode is set for form mode (defaults to 'form' per spec)
  192. const normalizedParams = mode === 'form' && params.mode === undefined ? { ...params, mode: 'form' } : params;
  193. // Cast to ServerRequest needed because TypeScript can't narrow the union type
  194. // based on the discriminated 'method' field when constructing the object literal
  195. return this.requestStream({
  196. method: 'elicitation/create',
  197. params: normalizedParams
  198. }, types_js_1.ElicitResultSchema, options);
  199. }
  200. /**
  201. * Gets the current status of a task.
  202. *
  203. * @param taskId - The task identifier
  204. * @param options - Optional request options
  205. * @returns The task status
  206. *
  207. * @experimental
  208. */
  209. async getTask(taskId, options) {
  210. return this._server.getTask({ taskId }, options);
  211. }
  212. /**
  213. * Retrieves the result of a completed task.
  214. *
  215. * @param taskId - The task identifier
  216. * @param resultSchema - Zod schema for validating the result
  217. * @param options - Optional request options
  218. * @returns The task result
  219. *
  220. * @experimental
  221. */
  222. async getTaskResult(taskId, resultSchema, options) {
  223. return this._server.getTaskResult({ taskId }, resultSchema, options);
  224. }
  225. /**
  226. * Lists tasks with optional pagination.
  227. *
  228. * @param cursor - Optional pagination cursor
  229. * @param options - Optional request options
  230. * @returns List of tasks with optional next cursor
  231. *
  232. * @experimental
  233. */
  234. async listTasks(cursor, options) {
  235. return this._server.listTasks(cursor ? { cursor } : undefined, options);
  236. }
  237. /**
  238. * Cancels a running task.
  239. *
  240. * @param taskId - The task identifier
  241. * @param options - Optional request options
  242. *
  243. * @experimental
  244. */
  245. async cancelTask(taskId, options) {
  246. return this._server.cancelTask({ taskId }, options);
  247. }
  248. }
  249. exports.ExperimentalServerTasks = ExperimentalServerTasks;
  250. //# sourceMappingURL=server.js.map