11import { mergeCapabilities , Protocol , type ProtocolOptions , type RequestOptions } from '../shared/protocol.js' ;
22import type { Transport } from '../shared/transport.js' ;
3- import { takeResult } from '../shared/responseMessage.js' ;
43
54import {
65 type CallToolRequest ,
@@ -203,6 +202,7 @@ export class Client<
203202 private _jsonSchemaValidator : jsonSchemaValidator ;
204203 private _cachedToolOutputValidators : Map < string , JsonSchemaValidator < unknown > > = new Map ( ) ;
205204 private _cachedKnownTaskTools : Set < string > = new Set ( ) ;
205+ private _cachedRequiredTaskTools : Set < string > = new Set ( ) ;
206206 private _experimental ?: { tasks : ExperimentalClientTasks < RequestT , NotificationT , ResultT > } ;
207207
208208 /**
@@ -645,13 +645,57 @@ export class Client<
645645 *
646646 * For task-based execution with streaming behavior, use client.experimental.tasks.callToolStream() instead.
647647 */
648- async callTool < T extends typeof CallToolResultSchema | typeof CompatibilityCallToolResultSchema > (
648+ async callTool (
649649 params : CallToolRequest [ 'params' ] ,
650- resultSchema : T = CallToolResultSchema as T ,
650+ resultSchema : typeof CallToolResultSchema | typeof CompatibilityCallToolResultSchema = CallToolResultSchema ,
651651 options ?: RequestOptions
652- ) : Promise < SchemaOutput < T > > {
653- // Use experimental.tasks.callToolStream for implementation (temporary dependency)
654- return await takeResult ( this . experimental . tasks . callToolStream < T > ( params , resultSchema , options ) ) ;
652+ ) {
653+ // Guard: required-task tools need experimental API
654+ if ( this . isToolTaskRequired ( params . name ) ) {
655+ throw new McpError (
656+ ErrorCode . InvalidRequest ,
657+ `Tool "${ params . name } " requires task-based execution. Use client.experimental.tasks.callToolStream() instead.`
658+ ) ;
659+ }
660+
661+ const result = await this . request ( { method : 'tools/call' , params } , resultSchema , options ) ;
662+
663+ // Check if the tool has an outputSchema
664+ const validator = this . getToolOutputValidator ( params . name ) ;
665+ if ( validator ) {
666+ // If tool has outputSchema, it MUST return structuredContent (unless it's an error)
667+ if ( ! result . structuredContent && ! result . isError ) {
668+ throw new McpError (
669+ ErrorCode . InvalidRequest ,
670+ `Tool ${ params . name } has an output schema but did not return structured content`
671+ ) ;
672+ }
673+
674+ // Only validate structured content if present (not when there's an error)
675+ if ( result . structuredContent ) {
676+ try {
677+ // Validate the structured content against the schema
678+ const validationResult = validator ( result . structuredContent ) ;
679+
680+ if ( ! validationResult . valid ) {
681+ throw new McpError (
682+ ErrorCode . InvalidParams ,
683+ `Structured content does not match the tool's output schema: ${ validationResult . errorMessage } `
684+ ) ;
685+ }
686+ } catch ( error ) {
687+ if ( error instanceof McpError ) {
688+ throw error ;
689+ }
690+ throw new McpError (
691+ ErrorCode . InvalidParams ,
692+ `Failed to validate structured content: ${ error instanceof Error ? error . message : String ( error ) } `
693+ ) ;
694+ }
695+ }
696+ }
697+
698+ return result ;
655699 }
656700
657701 private isToolTask ( toolName : string ) : boolean {
@@ -662,13 +706,22 @@ export class Client<
662706 return this . _cachedKnownTaskTools . has ( toolName ) ;
663707 }
664708
709+ /**
710+ * Check if a tool requires task-based execution.
711+ * Unlike isToolTask which includes 'optional' tools, this only checks for 'required'.
712+ */
713+ private isToolTaskRequired ( toolName : string ) : boolean {
714+ return this . _cachedRequiredTaskTools . has ( toolName ) ;
715+ }
716+
665717 /**
666718 * Cache validators for tool output schemas.
667719 * Called after listTools() to pre-compile validators for better performance.
668720 */
669721 private cacheToolMetadata ( tools : Tool [ ] ) : void {
670722 this . _cachedToolOutputValidators . clear ( ) ;
671723 this . _cachedKnownTaskTools . clear ( ) ;
724+ this . _cachedRequiredTaskTools . clear ( ) ;
672725
673726 for ( const tool of tools ) {
674727 // If the tool has an outputSchema, create and cache the validator
@@ -682,6 +735,9 @@ export class Client<
682735 if ( taskSupport === 'required' || taskSupport === 'optional' ) {
683736 this . _cachedKnownTaskTools . add ( tool . name ) ;
684737 }
738+ if ( taskSupport === 'required' ) {
739+ this . _cachedRequiredTaskTools . add ( tool . name ) ;
740+ }
685741 }
686742 }
687743
0 commit comments