Lesson 5 - Step 4 of 6

The tools/call Method

Learn how to receive tool calls and validate arguments.

Executing Tools

After a client discovers your tools via tools/list, it can execute them using tools/call. This is where your tool logic actually runs.

text
Client                              Server
  |                                    |
  |--- tools/call ------------------>  |
  |    name: "add"                     |
  |    arguments: {a: 5, b: 3}         |
  |                                    |
  |                       [executes]   |
  |                                    |
  |<-- result: 8 --------------------  |
  |                                    |

The tools/call Request

The request includes the tool name and arguments:

json
{
    "jsonrpc": "2.0",
    "id": 3,
    "method": "tools/call",
    "params": {
        "name": "add",
        "arguments": {
            "a": 5,
            "b": 3
        }
    }
}

Request Parameters

Field Type Description
name string The name of the tool to execute
arguments object Arguments matching the tool's inputSchema

Implementing tools/call

Here's a complete implementation that handles tool calls:

python
"""
Tool execution handlers for MCP server.
"""

# Tool implementations - the actual logic
def execute_add(arguments):
    """Add two numbers."""
    a = arguments.get("a")
    b = arguments.get("b")
    return a + b


def execute_subtract(arguments):
    """Subtract two numbers."""
    a = arguments.get("a")
    b = arguments.get("b")
    return a - b


def execute_multiply(arguments):
    """Multiply two numbers."""
    a = arguments.get("a")
    b = arguments.get("b")
    return a * b


# Map tool names to their implementations
TOOL_HANDLERS = {
    "add": execute_add,
    "subtract": execute_subtract,
    "multiply": execute_multiply,
}


def handle_tools_call(request_id, params):
    """
    Handle the tools/call method.

    Args:
        request_id: The JSON-RPC request ID
        params: Request parameters with 'name' and 'arguments'

    Returns:
        JSON-RPC response with tool result or error
    """
    tool_name = params.get("name", "")
    arguments = params.get("arguments", {})

    # Check if tool exists
    if tool_name not in TOOL_HANDLERS:
        return {
            "jsonrpc": "2.0",
            "id": request_id,
            "error": {
                "code": -32602,
                "message": f"Unknown tool: {tool_name}"
            }
        }

    try:
        # Execute the tool
        handler = TOOL_HANDLERS[tool_name]
        result = handler(arguments)

        # Return success response
        return {
            "jsonrpc": "2.0",
            "id": request_id,
            "result": {
                "content": [
                    {
                        "type": "text",
                        "text": str(result)
                    }
                ]
            }
        }

    except Exception as e:
        # Return error in tool result (not JSON-RPC error)
        return {
            "jsonrpc": "2.0",
            "id": request_id,
            "result": {
                "content": [
                    {
                        "type": "text",
                        "text": f"Error: {str(e)}"
                    }
                ],
                "isError": True
            }
        }

Validating Arguments

It's good practice to validate arguments before executing tools. Here's how to add validation:

python
def validate_arguments(tool_name, arguments):
    """
    Validate arguments against the tool's schema.

    Returns:
        tuple: (is_valid, error_message)
    """
    # Find the tool definition
    tool = None
    for t in TOOLS:
        if t["name"] == tool_name:
            tool = t
            break

    if not tool:
        return False, f"Unknown tool: {tool_name}"

    schema = tool.get("inputSchema", {})
    required = schema.get("required", [])
    properties = schema.get("properties", {})

    # Check required fields
    for field in required:
        if field not in arguments:
            return False, f"Missing required argument: {field}"

    # Check types
    for field, value in arguments.items():
        if field in properties:
            expected_type = properties[field].get("type")
            if expected_type == "number" and not isinstance(value, (int, float)):
                return False, f"Argument '{field}' must be a number"
            if expected_type == "string" and not isinstance(value, str):
                return False, f"Argument '{field}' must be a string"
            if expected_type == "boolean" and not isinstance(value, bool):
                return False, f"Argument '{field}' must be a boolean"

    return True, None


def handle_tools_call_with_validation(request_id, params):
    """Handle tools/call with argument validation."""
    tool_name = params.get("name", "")
    arguments = params.get("arguments", {})

    # Validate arguments
    is_valid, error = validate_arguments(tool_name, arguments)
    if not is_valid:
        return {
            "jsonrpc": "2.0",
            "id": request_id,
            "error": {
                "code": -32602,
                "message": error
            }
        }

    # ... rest of execution logic ...
Use jsonschema Library

For production servers, consider using the jsonschema library for robust validation: pip install jsonschema

Error Handling

There are two types of errors to handle:

Error Type When to Use How to Return
JSON-RPC Error Invalid request (unknown tool, bad params) Use error field in response
Tool Error Tool executed but failed (API error, etc.) Use isError: true in result
json
// JSON-RPC Error (invalid request)
{
    "jsonrpc": "2.0",
    "id": 3,
    "error": {
        "code": -32602,
        "message": "Unknown tool: nonexistent"
    }
}

// Tool Error (execution failed)
{
    "jsonrpc": "2.0",
    "id": 3,
    "result": {
        "content": [
            {
                "type": "text",
                "text": "Error: Division by zero"
            }
        ],
        "isError": true
    }
}

Testing tools/call

Test your implementation with curl:

bash
# Call the add tool
curl -X POST http://localhost:8080 \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
        "name": "add",
        "arguments": {"a": 5, "b": 3}
    }
  }'

# Expected response:
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "content": [
      {"type": "text", "text": "8"}
    ]
  }
}