Lesson 10 - Step 4 of 6

Calling Methods

Complete the StdioClient class with all remaining methods.

In this step, we'll add all the remaining methods to complete our StdioClient class. We'll add them sequentially - just keep appending to the class.

File Structure

Your stdio_client.py currently has the StdioClient class ending with supports_resource_subscriptions(). We'll add all new methods right after that, still inside the class.

Step 1: Add the Generic Call Method

Instead of writing separate code for each MCP method, we create a generic call method that handles request/response logic.

Edit: stdio_client.py

Add this method inside the StdioClient class, after supports_resource_subscriptions:

python stdio_client.py - Append to StdioClient class
    def call(self, method: str, params: Optional[dict] = None) -> Any:
        """
        Call an MCP method and return the result.

        Args:
            method: The MCP method name (e.g., "tools/list")
            params: Optional parameters for the method

        Returns:
            The result from the server response

        Raises:
            RuntimeError: If not connected or initialized
        """
        if not self.initialized:
            raise RuntimeError("Client not initialized. Call initialize() first.")

        # Send request and get response
        response = self.send_request(method, params)

        # Check for error response
        if "error" in response:
            error = response["error"]
            raise RuntimeError(f"MCP Error {error.get('code')}: {error.get('message')}")

        # Return the result
        return response.get("result")

Step 2: Add Method Wrappers

With a generic call method, we can create convenient wrappers for specific MCP methods.

Edit: stdio_client.py

Continue appending to the StdioClient class:

python stdio_client.py - Append to StdioClient class
    def call_tool(self, name: str, arguments: Optional[dict] = None) -> Any:
        """
        Call a tool on the server.

        Args:
            name: The tool name
            arguments: Arguments to pass to the tool

        Returns:
            The tool's result content
        """
        result = self.call("tools/call", {
            "name": name,
            "arguments": arguments or {}
        })
        return result.get("content", [])

    def read_resource(self, uri: str) -> Any:
        """
        Read a resource from the server.

        Args:
            uri: The resource URI

        Returns:
            The resource contents
        """
        result = self.call("resources/read", {"uri": uri})
        return result.get("contents", [])

    def get_prompt(self, name: str, arguments: Optional[dict] = None) -> Any:
        """
        Get a prompt from the server.

        Args:
            name: The prompt name
            arguments: Arguments for the prompt

        Returns:
            The prompt messages
        """
        result = self.call("prompts/get", {
            "name": name,
            "arguments": arguments or {}
        })
        return result.get("messages", [])

Step 3: Add List Methods

Finally, add methods to list available tools, resources, and prompts. These return simple dictionaries for now - we'll add proper dataclasses in the next step.

Edit: stdio_client.py

Continue appending to the StdioClient class:

python stdio_client.py - Append to StdioClient class
    def list_tools(self) -> List[dict]:
        """
        List all tools available on the server.

        Returns:
            List of tool dictionaries
        """
        if not self.has_tools():
            return []
        result = self.call("tools/list")
        return result.get("tools", [])

    def list_resources(self) -> List[dict]:
        """
        List all resources available on the server.

        Returns:
            List of resource dictionaries
        """
        if not self.has_resources():
            return []
        result = self.call("resources/list")
        return result.get("resources", [])

    def list_prompts(self) -> List[dict]:
        """
        List all prompts available on the server.

        Returns:
            List of prompt dictionaries
        """
        if not self.has_prompts():
            return []
        result = self.call("prompts/list")
        return result.get("prompts", [])

Class Complete!

Your StdioClient class is now complete with all the methods needed to interact with an MCP server.

Current File Structure

Your stdio_client.py should now look like this:

imports...

class StdioClient:
    __init__, connect, disconnect
    send_request, send_notification
    __enter__, __exit__
    initialize
    has_tools, has_resources, has_prompts
    supports_resource_subscriptions
    call                    # Added in Step 1
    call_tool, read_resource, get_prompt  # Added in Step 2
    list_tools, list_resources, list_prompts  # Added in Step 3

# End of file

Error Handling

When using these methods, handle errors appropriately:

python
try:
    result = client.call_tool("add_numbers", {"a": 5, "b": 3})
    print(f"Result: {result}")
except RuntimeError as e:
    if "MCP Error -32601" in str(e):
        print("Method not found - server doesn't support this")
    elif "MCP Error -32602" in str(e):
        print("Invalid parameters")
    else:
        print(f"Error: {e}")
Standard JSON-RPC Error Codes

-32700: Parse error
-32600: Invalid request
-32601: Method not found
-32602: Invalid params
-32603: Internal error