Skip to content

Conversation

@tmatthews90
Copy link

@tmatthews90 tmatthews90 commented Jul 10, 2025

This change adds httpx.HTTPStatusError handling to the tool run class to provide detailed error information when returning from the MCP server.

Motivation and Context

The default error message from httpx does not have detailed information on the exception thrown. Our tools use httpx to make API calls and it would be much easier for us to raise_for_status and not have to catch the exception to format a better message to be rasied.

Current tool implementation example

This is currently what we have to do to get a detailed exception message to the mcp tool run handler.

async def list_memories(page: int = 1, limit: int = 10) -> str:
    """Retrieves a list of memories."""
    token = get_token()
    headers = {"Authorization": token}

    api_url = get_api_url()
    memory_collection_id = get_memory_collection_id()

    async with httpx.AsyncClient(timeout=30, verify=False) as client:
        try:
            params = {
                "collection_id": memory_collection_id,
                "page": page,
                "limit": limit,
            }
            response = await client.get(
                f"{api_url}/memory/{memory_collection_id}/memories",
                params=params,
                headers=headers,
            )
            response.raise_for_status()
            return response.json()
        except httpx.HTTPStatusError as e:
            try:
                error_detail = e.response.json()
            except:
                error_detail = e.response.text
            raise Exception(error_detail)

Ideal tool implementation

@mcp.tool()
async def list_memories(page: int = 1, limit: int = 10) -> str:
    """Retrieves a list of memories."""
    token = get_token()
    headers = {"Authorization": token}

    api_url = get_api_url()
    memory_collection_id = get_memory_collection_id()

    async with httpx.AsyncClient(timeout=30, verify=False) as client:
        params = {
            "collection_id": memory_collection_id,
            "page": page,
            "limit": limit,
        }
        response = await client.get(
            f"{api_url}/memory/{memory_collection_id}/memories",
            params=params,
            headers=headers,
        )
        response.raise_for_status()
        return response.json()

Current response format

Error executing tool list_memories: 404 Client Error: Not Found for url: https://httpbin.org/status/404 For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404

Ideal response

Error executing tool list_memories: [404] <JSON response | text from response>

How Has This Been Tested?

Breaking Changes

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Checklist

  • I have read the MCP Documentation
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling
  • I have added or updated documentation as needed

Additional context

@tmatthews90 tmatthews90 marked this pull request as ready for review July 10, 2025 16:40
Copy link
Contributor

@felixweinberger felixweinberger left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @tmatthews90 thank you for contributing to MCP! And apologies for the time it took to get back to you on this.

Having looked at the change and your motivating example, I think this is actually best handled by the application and not by the framework. FastMCP is transport and tool implementation agnostic , others might use requests or aiohttp for example. That's going to depend on the application and should therefore also be handled there in my opinion.

If you wanted to make your application code cleaner, you could for example create a decorator for your own tools that does this repeated error handling for you?

Comment on lines +103 to +108
except httpx.HTTPStatusError as e:
try:
error_detail = e.response.json()
except:
error_detail = e.response.text
raise ToolError(f"Error executing tool {self.name}: [{e.response.status_code}] {error_detail}")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we should be handling httpx specific errors in a framework that's agnostic of the transport - FastMCP might be using stdio after all, so this seems like the wrong place to handle this kind of error.

@felixweinberger
Copy link
Contributor

Closing this for the reasons discussed above - thanks again for the contribution!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants