Don't block the event loop on sync resource and prompt functions (#2380)
This commit is contained in:
parent
e6235d1667
commit
3ce0f76e6e
3 changed files with 107 additions and 13 deletions
|
|
@ -2,10 +2,12 @@
|
|||
|
||||
from __future__ import annotations
|
||||
|
||||
import functools
|
||||
import inspect
|
||||
from collections.abc import Awaitable, Callable, Sequence
|
||||
from typing import TYPE_CHECKING, Any, Literal
|
||||
|
||||
import anyio.to_thread
|
||||
import pydantic_core
|
||||
from pydantic import BaseModel, Field, TypeAdapter, validate_call
|
||||
|
||||
|
|
@ -155,10 +157,10 @@ class Prompt(BaseModel):
|
|||
# Add context to arguments if needed
|
||||
call_args = inject_context(self.fn, arguments or {}, context, self.context_kwarg)
|
||||
|
||||
# Call function and check if result is a coroutine
|
||||
result = self.fn(**call_args)
|
||||
if inspect.iscoroutine(result):
|
||||
result = await result
|
||||
if inspect.iscoroutinefunction(self.fn):
|
||||
result = await self.fn(**call_args)
|
||||
else:
|
||||
result = await anyio.to_thread.run_sync(functools.partial(self.fn, **call_args))
|
||||
|
||||
# Validate messages
|
||||
if not isinstance(result, list | tuple):
|
||||
|
|
|
|||
|
|
@ -2,12 +2,14 @@
|
|||
|
||||
from __future__ import annotations
|
||||
|
||||
import functools
|
||||
import inspect
|
||||
import re
|
||||
from collections.abc import Callable
|
||||
from typing import TYPE_CHECKING, Any
|
||||
from urllib.parse import unquote
|
||||
|
||||
import anyio.to_thread
|
||||
from pydantic import BaseModel, Field, validate_call
|
||||
|
||||
from mcp.server.mcpserver.resources.types import FunctionResource, Resource
|
||||
|
|
@ -110,10 +112,10 @@ class ResourceTemplate(BaseModel):
|
|||
# Add context to params if needed
|
||||
params = inject_context(self.fn, params, context, self.context_kwarg)
|
||||
|
||||
# Call function and check if result is a coroutine
|
||||
result = self.fn(**params)
|
||||
if inspect.iscoroutine(result):
|
||||
result = await result
|
||||
if inspect.iscoroutinefunction(self.fn):
|
||||
result = await self.fn(**params)
|
||||
else:
|
||||
result = await anyio.to_thread.run_sync(functools.partial(self.fn, **params))
|
||||
|
||||
return FunctionResource(
|
||||
uri=uri, # type: ignore
|
||||
|
|
|
|||
|
|
@ -55,11 +55,10 @@ class FunctionResource(Resource):
|
|||
async def read(self) -> str | bytes:
|
||||
"""Read the resource by calling the wrapped function."""
|
||||
try:
|
||||
# Call the function first to see if it returns a coroutine
|
||||
result = self.fn()
|
||||
# If it's a coroutine, await it
|
||||
if inspect.iscoroutine(result):
|
||||
result = await result
|
||||
if inspect.iscoroutinefunction(self.fn):
|
||||
result = await self.fn()
|
||||
else:
|
||||
result = await anyio.to_thread.run_sync(self.fn)
|
||||
|
||||
if isinstance(result, Resource): # pragma: no cover
|
||||
return await result.read()
|
||||
|
|
|
|||
Loading…
Reference in a new issue