Use shared `is_async_callable` instead of `inspect.iscoroutinefunction` by Kludex · Pull Request #2389 · modelcontextprotocol/python-sdk · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions src/mcp/server/mcpserver/prompts/base.py
7 changes: 4 additions & 3 deletions src/mcp/server/mcpserver/resources/templates.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
from __future__ import annotations

import functools
import inspect
import re
from collections.abc import Callable
from typing import TYPE_CHECKING, Any
Expand All @@ -15,6 +14,7 @@
from mcp.server.mcpserver.resources.types import FunctionResource, Resource
from mcp.server.mcpserver.utilities.context_injection import find_context_parameter, inject_context
from mcp.server.mcpserver.utilities.func_metadata import func_metadata
from mcp.shared._callable_inspection import is_async_callable
from mcp.types import Annotations, Icon

if TYPE_CHECKING:
Expand Down Expand Up @@ -112,8 +112,9 @@ async def create_resource(
# Add context to params if needed
params = inject_context(self.fn, params, context, self.context_kwarg)

if inspect.iscoroutinefunction(self.fn):
result = await self.fn(**params)
fn = self.fn
if is_async_callable(fn):
result = await fn(**params)
else:
result = await anyio.to_thread.run_sync(functools.partial(self.fn, **params))

Expand Down
11 changes: 7 additions & 4 deletions src/mcp/server/mcpserver/resources/types.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""Concrete resource implementations."""

import inspect
from __future__ import annotations

import json
from collections.abc import Callable
from pathlib import Path
Expand All @@ -14,6 +15,7 @@
from pydantic import Field, ValidationInfo, validate_call

from mcp.server.mcpserver.resources.base import Resource
from mcp.shared._callable_inspection import is_async_callable
from mcp.types import Annotations, Icon


Expand Down Expand Up @@ -55,8 +57,9 @@ class FunctionResource(Resource):
async def read(self) -> str | bytes:
"""Read the resource by calling the wrapped function."""
try:
if inspect.iscoroutinefunction(self.fn):
result = await self.fn()
fn = self.fn
if is_async_callable(fn):
result = await fn()
else:
result = await anyio.to_thread.run_sync(self.fn)

Expand All @@ -83,7 +86,7 @@ def from_function(
icons: list[Icon] | None = None,
annotations: Annotations | None = None,
meta: dict[str, Any] | None = None,
) -> "FunctionResource":
) -> FunctionResource:
"""Create a FunctionResource from a function."""
func_name = name or fn.__name__
if func_name == "<lambda>": # pragma: no cover
Expand Down
14 changes: 2 additions & 12 deletions src/mcp/server/mcpserver/tools/base.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
from __future__ import annotations

import functools
import inspect
from collections.abc import Callable
from functools import cached_property
from typing import TYPE_CHECKING, Any
Expand All @@ -11,6 +9,7 @@
from mcp.server.mcpserver.exceptions import ToolError
from mcp.server.mcpserver.utilities.context_injection import find_context_parameter
from mcp.server.mcpserver.utilities.func_metadata import FuncMetadata, func_metadata
from mcp.shared._callable_inspection import is_async_callable
from mcp.shared.exceptions import UrlElicitationRequiredError
from mcp.shared.tool_name_validation import validate_and_warn_tool_name
from mcp.types import Icon, ToolAnnotations
Expand Down Expand Up @@ -63,7 +62,7 @@ def from_function(
raise ValueError("You must provide a name for lambda functions")

func_doc = description or fn.__doc__ or ""
is_async = _is_async_callable(fn)
is_async = is_async_callable(fn)

if context_kwarg is None: # pragma: no branch
context_kwarg = find_context_parameter(fn)
Expand Down Expand Up @@ -118,12 +117,3 @@ async def run(
raise
except Exception as e:
raise ToolError(f"Error executing tool {self.name}: {e}") from e


def _is_async_callable(obj: Any) -> bool:
while isinstance(obj, functools.partial): # pragma: lax no cover
obj = obj.func

return inspect.iscoroutinefunction(obj) or (
callable(obj) and inspect.iscoroutinefunction(getattr(obj, "__call__", None))
)
33 changes: 33 additions & 0 deletions src/mcp/shared/_callable_inspection.py
Loading