client.js 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  1. /**
  2. * Experimental client task features for MCP SDK.
  3. * WARNING: These APIs are experimental and may change without notice.
  4. *
  5. * @experimental
  6. */
  7. import { CallToolResultSchema, McpError, ErrorCode } from '../../types.js';
  8. /**
  9. * Experimental task features for MCP clients.
  10. *
  11. * Access via `client.experimental.tasks`:
  12. * ```typescript
  13. * const stream = client.experimental.tasks.callToolStream({ name: 'tool', arguments: {} });
  14. * const task = await client.experimental.tasks.getTask(taskId);
  15. * ```
  16. *
  17. * @experimental
  18. */
  19. export class ExperimentalClientTasks {
  20. constructor(_client) {
  21. this._client = _client;
  22. }
  23. /**
  24. * Calls a tool and returns an AsyncGenerator that yields response messages.
  25. * The generator is guaranteed to end with either a 'result' or 'error' message.
  26. *
  27. * This method provides streaming access to tool execution, allowing you to
  28. * observe intermediate task status updates for long-running tool calls.
  29. * Automatically validates structured output if the tool has an outputSchema.
  30. *
  31. * @example
  32. * ```typescript
  33. * const stream = client.experimental.tasks.callToolStream({ name: 'myTool', arguments: {} });
  34. * for await (const message of stream) {
  35. * switch (message.type) {
  36. * case 'taskCreated':
  37. * console.log('Tool execution started:', message.task.taskId);
  38. * break;
  39. * case 'taskStatus':
  40. * console.log('Tool status:', message.task.status);
  41. * break;
  42. * case 'result':
  43. * console.log('Tool result:', message.result);
  44. * break;
  45. * case 'error':
  46. * console.error('Tool error:', message.error);
  47. * break;
  48. * }
  49. * }
  50. * ```
  51. *
  52. * @param params - Tool call parameters (name and arguments)
  53. * @param resultSchema - Zod schema for validating the result (defaults to CallToolResultSchema)
  54. * @param options - Optional request options (timeout, signal, task creation params, etc.)
  55. * @returns AsyncGenerator that yields ResponseMessage objects
  56. *
  57. * @experimental
  58. */
  59. async *callToolStream(params, resultSchema = CallToolResultSchema, options) {
  60. // Access Client's internal methods
  61. const clientInternal = this._client;
  62. // Add task creation parameters if server supports it and not explicitly provided
  63. const optionsWithTask = {
  64. ...options,
  65. // We check if the tool is known to be a task during auto-configuration, but assume
  66. // the caller knows what they're doing if they pass this explicitly
  67. task: options?.task ?? (clientInternal.isToolTask(params.name) ? {} : undefined)
  68. };
  69. const stream = clientInternal.requestStream({ method: 'tools/call', params }, resultSchema, optionsWithTask);
  70. // Get the validator for this tool (if it has an output schema)
  71. const validator = clientInternal.getToolOutputValidator(params.name);
  72. // Iterate through the stream and validate the final result if needed
  73. for await (const message of stream) {
  74. // If this is a result message and the tool has an output schema, validate it
  75. if (message.type === 'result' && validator) {
  76. const result = message.result;
  77. // If tool has outputSchema, it MUST return structuredContent (unless it's an error)
  78. if (!result.structuredContent && !result.isError) {
  79. yield {
  80. type: 'error',
  81. error: new McpError(ErrorCode.InvalidRequest, `Tool ${params.name} has an output schema but did not return structured content`)
  82. };
  83. return;
  84. }
  85. // Only validate structured content if present (not when there's an error)
  86. if (result.structuredContent) {
  87. try {
  88. // Validate the structured content against the schema
  89. const validationResult = validator(result.structuredContent);
  90. if (!validationResult.valid) {
  91. yield {
  92. type: 'error',
  93. error: new McpError(ErrorCode.InvalidParams, `Structured content does not match the tool's output schema: ${validationResult.errorMessage}`)
  94. };
  95. return;
  96. }
  97. }
  98. catch (error) {
  99. if (error instanceof McpError) {
  100. yield { type: 'error', error };
  101. return;
  102. }
  103. yield {
  104. type: 'error',
  105. error: new McpError(ErrorCode.InvalidParams, `Failed to validate structured content: ${error instanceof Error ? error.message : String(error)}`)
  106. };
  107. return;
  108. }
  109. }
  110. }
  111. // Yield the message (either validated result or any other message type)
  112. yield message;
  113. }
  114. }
  115. /**
  116. * Gets the current status of a task.
  117. *
  118. * @param taskId - The task identifier
  119. * @param options - Optional request options
  120. * @returns The task status
  121. *
  122. * @experimental
  123. */
  124. async getTask(taskId, options) {
  125. return this._client.getTask({ taskId }, options);
  126. }
  127. /**
  128. * Retrieves the result of a completed task.
  129. *
  130. * @param taskId - The task identifier
  131. * @param resultSchema - Zod schema for validating the result
  132. * @param options - Optional request options
  133. * @returns The task result
  134. *
  135. * @experimental
  136. */
  137. async getTaskResult(taskId, resultSchema, options) {
  138. // Delegate to the client's underlying Protocol method
  139. return this._client.getTaskResult({ taskId }, resultSchema, options);
  140. }
  141. /**
  142. * Lists tasks with optional pagination.
  143. *
  144. * @param cursor - Optional pagination cursor
  145. * @param options - Optional request options
  146. * @returns List of tasks with optional next cursor
  147. *
  148. * @experimental
  149. */
  150. async listTasks(cursor, options) {
  151. // Delegate to the client's underlying Protocol method
  152. return this._client.listTasks(cursor ? { cursor } : undefined, options);
  153. }
  154. /**
  155. * Cancels a running task.
  156. *
  157. * @param taskId - The task identifier
  158. * @param options - Optional request options
  159. *
  160. * @experimental
  161. */
  162. async cancelTask(taskId, options) {
  163. // Delegate to the client's underlying Protocol method
  164. return this._client.cancelTask({ taskId }, options);
  165. }
  166. /**
  167. * Sends a request and returns an AsyncGenerator that yields response messages.
  168. * The generator is guaranteed to end with either a 'result' or 'error' message.
  169. *
  170. * This method provides streaming access to request processing, allowing you to
  171. * observe intermediate task status updates for task-augmented requests.
  172. *
  173. * @param request - The request to send
  174. * @param resultSchema - Zod schema for validating the result
  175. * @param options - Optional request options (timeout, signal, task creation params, etc.)
  176. * @returns AsyncGenerator that yields ResponseMessage objects
  177. *
  178. * @experimental
  179. */
  180. requestStream(request, resultSchema, options) {
  181. return this._client.requestStream(request, resultSchema, options);
  182. }
  183. }
  184. //# sourceMappingURL=client.js.map