Skip to content

Fix async callable object tools #568

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Conversation

stephanlensky
Copy link
Contributor

Makes detection of asynchronous functions more robust in Tool.from_function, allowing usage of asynchronous callable objects, e.g.

class MyAsyncTool:
    def __init__(self):
        self.__name__ = "MyAsyncTool"

    async def __call__(self, x: int) -> int:
        return x * 2

manager = ToolManager()
tool = manager.add_tool(MyAsyncTool())
result = await tool.run({"x": 5})  # 10

Motivation and Context

Fixes #567.

How Has This Been Tested?

Unit tests were added to verify the change.

Breaking Changes

This is a non-breaking change.

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

@stephanlensky stephanlensky force-pushed the stephanlensky/fix-async-callable-objects branch 2 times, most recently from 436673f to 00c730a Compare April 22, 2025 22:07
@stephanlensky stephanlensky force-pushed the stephanlensky/fix-async-callable-objects branch from 00c730a to 92d9426 Compare April 22, 2025 22:08
@ihrpr ihrpr added this to the r-05-25 milestone May 13, 2025
mcp-shadow[bot]

This comment was marked as duplicate.

Copy link

@mcp-shadow mcp-shadow bot left a comment

Choose a reason for hiding this comment

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

Thank you for your contribution!

Suggestion: APPROVE

Comment: This PR correctly addresses issue #567 by enhancing the detection of asynchronous functions in Tool.from_function to properly handle asynchronous callable objects. The implementation follows Python best practices by adding a helper function _is_async_callable that handles both regular async functions and async callable objects with __call__ methods. The changes are minimal, targeted to fix only the specific issue, and include comprehensive tests to verify the functionality. This fix properly supports a valid use case without introducing any breaking changes or performance overhead.

Copy link
Contributor

@ihrpr ihrpr left a comment

Choose a reason for hiding this comment

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

Thank you for working on this!

Please can you address limit of loop iterations and it's good to merge.



def _is_async_callable(obj: Any) -> bool:
while isinstance(obj, functools.partial):
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's limit loop iterations here

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thank you for the review! What would you suggest? I don't think there is any limit to how many partials you can have applied.

I do want to note that the reference implementation from Starlette is used by every consumer of FastAPI and does not specify a limit.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The CPython standard library has a very similar implementation, also without a limit.

Copy link
Contributor

Choose a reason for hiding this comment

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

scary, but consistent :)

Copy link
Contributor

@ihrpr ihrpr left a comment

Choose a reason for hiding this comment

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

Thank you!!



def _is_async_callable(obj: Any) -> bool:
while isinstance(obj, functools.partial):
Copy link
Contributor

Choose a reason for hiding this comment

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

scary, but consistent :)

@ihrpr
Copy link
Contributor

ihrpr commented May 23, 2025

@stephanlensky please can you merge main and I'll merge!

@stephanlensky
Copy link
Contributor Author

Should be all set now! Thanks again 🙂

@ihrpr ihrpr merged commit f2f4dbd into modelcontextprotocol:main May 23, 2025
10 checks passed
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.

Tool.from_function does not handle asynchronous callable objects
2 participants