| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
PR #1909 fixed this for tools by running sync functions via anyio.to_thread.run_sync, but the same blocking pattern existed in FunctionResource.read, ResourceTemplate.create_resource, and Prompt.render. All three called self.fn() directly and checked inspect.iscoroutine(result) afterward, so a blocking sync @mcp.resource or @mcp.prompt handler would still freeze the event loop. This applies the same fix: check inspect.iscoroutinefunction(self.fn) up front and dispatch sync functions to a worker thread. Verified that pydantic.validate_call (used to wrap stored functions in templates and prompts) preserves async-ness, so the check works correctly on the wrapped function. Github-Issue: #1646
Proves the behavioral fix directly: the handler blocks on a threading.Event in a worker thread while the async side awaits an anyio.Event. The handler signals back into the event loop via anyio.from_thread.run_sync, so the async side's await resolves without polling or sleeps. On regression (sync runs inline), anyio.from_thread.run_sync raises RuntimeError immediately since there is no worker-thread context, failing fast rather than waiting out the fail_after timeout.
There was a problem hiding this comment.
I'm not sure those tests are useful. I would prefer to not have tests in this case.
Sorry, something went wrong.
Replaces the regex-based resource template matcher with a linear-time RFC 6570 implementation, adds configurable path-safety validation, and ships a standalone UriTemplate utility usable from the low-level server. Rebased onto main@4caa41f6 (squashed from 47 commits at 8b5ca89). Absorbs mcp-types split (#2973), ResourceNotFoundError (#2920), ResourceError chaining (#2542), resources= ctor param (#2414), is_async_callable (#2389), and sync-handler offloading (#2380). ResourceSecurityError now re-raises as ResourceNotFoundError in ResourceManager.get_resource so security rejections map to -32602 (SEP-2164) and halt template iteration.
Replaces the regex-based resource template matcher with a linear-time RFC 6570 implementation, adds configurable path-safety validation, and ships a standalone UriTemplate utility usable from the low-level server. Rebased onto main@4caa41f6 (squashed from 47 commits at 8b5ca89). Absorbs mcp-types split (#2973), ResourceNotFoundError (#2920), ResourceError chaining (#2542), resources= ctor param (#2414), is_async_callable (#2389), and sync-handler offloading (#2380). ResourceSecurityError now re-raises as ResourceNotFoundError in ResourceManager.get_resource so security rejections map to -32602 (SEP-2164) and halt template iteration.
Replaces the regex-based resource template matcher with a linear-time RFC 6570 implementation, adds configurable path-safety validation, and ships a standalone UriTemplate utility usable from the low-level server. Rebased onto main@4caa41f6 (squashed from 47 commits at 8b5ca89). Absorbs mcp-types split (#2973), ResourceNotFoundError (#2920), ResourceError chaining (#2542), resources= ctor param (#2414), is_async_callable (#2389), and sync-handler offloading (#2380). ResourceSecurityError now re-raises as ResourceNotFoundError in ResourceManager.get_resource so security rejections map to -32602 (SEP-2164) and halt template iteration.
Replaces the regex-based resource template matcher with a linear-time RFC 6570 implementation, adds configurable path-safety validation, and ships a standalone UriTemplate utility usable from the low-level server. Rebased onto main@4caa41f6 (squashed from 47 commits at 8b5ca89). Absorbs mcp-types split (#2973), ResourceNotFoundError (#2920), ResourceError chaining (#2542), resources= ctor param (#2414), is_async_callable (#2389), and sync-handler offloading (#2380). ResourceSecurityError now re-raises as ResourceNotFoundError in ResourceManager.get_resource so security rejections map to -32602 (SEP-2164) and halt template iteration.
| Back | FazBrowse Home | New Git URL |
Extends the #1909 fix to resources and prompts — sync @mcp.resource and @mcp.prompt handlers now run in worker threads instead of blocking the event loop.
Motivation and Context
#1909 fixed tools by routing sync functions through anyio.to_thread.run_sync, but the same blocking pattern existed in three other places:
All three called self.fn(...) directly and checked inspect.iscoroutine(result) afterward. A blocking sync handler (file I/O, HTTP request, CPU-bound work) would freeze the entire event loop.
This applies the same fix: check inspect.iscoroutinefunction(self.fn) up front and dispatch sync functions to a worker thread.
Related: #1646, #1839
How Has This Been Tested?
Added thread-identity regression tests for each of the three call sites. Each test captures threading.get_ident() inside a sync handler and asserts it differs from the event loop's thread.
Verified that pydantic.validate_call (which wraps the stored self.fn in templates and prompts) preserves async-ness — inspect.iscoroutinefunction(validate_call(async_fn)) returns True, so the dispatch check works correctly on the wrapped function.
All 39 tests in the affected test files pass; pyright, ruff, and strict-no-cover are clean.
Breaking Changes
None. Sync handlers that were previously starving the event loop now run concurrently.
Types of changes
Checklist
Additional context
The previous implementation happened to support callable objects with async __call__ (by checking iscoroutine(result) after calling). That edge case is not preserved here — matching the approach taken in #1909, which relies on _is_async_callable being evaluated at registration time for tools. Resources and prompts have no equivalent pre-computed field; if that edge case matters, it's worth a separate discussion.
AI Disclaimer