callTool method

Future<CallToolResult> callTool(
  1. String name,
  2. Map<String, dynamic> toolArguments, {
  3. Map<String, dynamic>? meta,
  4. RequestOptions? options,
})

Call a tool on the server

Every request method takes an optional meta, sent as the request _meta. It carries application context the server reads per request (e.g. a forwarding host relaying its caller's identity); use namespaced keys (prefix/name). Reserved io.modelcontextprotocol/* keys the client writes on the stateless path always win.

Implementation

Future<CallToolResult> callTool(
  String name,
  Map<String, dynamic> toolArguments, {
  Map<String, dynamic>? meta,
  RequestOptions? options,
}) async {
  if (!_initialized) {
    throw McpError('Client is not initialized');
  }

  if (!_statelessMode && _serverCapabilities?.tools != true) {
    throw McpError('Server does not support tools');
  }

  // Create a clean params map with properly typed values
  final Map<String, dynamic> params = _withMeta({
    'name': name,
    'arguments': Map<String, dynamic>.from(toolArguments),
  }, meta);

  if (_mirrorsParamHeaders) {
    if (!_toolParamHeaders.containsKey(name)) await listTools();
    try {
      return await _callToolOnce(name, params, options);
    } on McpError catch (e) {
      if (e.code != McpProtocol.errorHeaderMismatch) rethrow;
      // The tool's annotations may have changed: read them again, retry.
      _toolParamHeaders.remove(name);
      await listTools();
      return _callToolOnce(name, params, options);
    }
  }

  // 2025-11-25: a tool that runs only as a task is always called as one.
  if (!_statelessMode && _serverToolTasks && _toolTaskSupport[name] == 'required') {
    final task = await callToolAsTask(name, toolArguments, meta: meta, options: options);
    final result = CallToolResult.fromJson(await taskResult(task.taskId, options: options));
    _checkToolOutput(name, result);
    return result;
  }

  return _callToolOnce(name, params, options);
}