Skip to content

fix(mcp): handle sync OR async fastmcp Context.get_state/set_state#87

Merged
zzstoatzz merged 1 commit into
mainfrom
fix/version-agnostic-state
May 29, 2026
Merged

fix(mcp): handle sync OR async fastmcp Context.get_state/set_state#87
zzstoatzz merged 1 commit into
mainfrom
fix/version-agnostic-state

Conversation

@zzstoatzz

Copy link
Copy Markdown
Owner

Follow-up to #86.

#86 fixed the fastmcp-3 async get_state/set_state bug locally, but FastMCP Cloud (the hosted pdsx-by-zzstoatzz.fastmcp.app deploy) pins a fastmcp where those methods are still sync — so await ctx.get_state(...) flipped the failure mode to:

object NoneType can't be used in 'await' expression

i.e. every tool was still broken on the deployed server, just with a different error. The installed fastmcp version doesn't govern the hosted runtime, so the fix can't assume either convention.

fix

  • add _maybe_await(value) — awaits only if inspect.isawaitable(value), else returns as-is
  • route every ctx.get_state(...) (client.py) and ctx.set_state(...) (middleware.py) through it

tests

  • regression tests now parametrize over _FakeAsyncContext (3.x) and _FakeSyncContext (2.x / hosted). The sync variants reproduce the cloud await None error — verified they fail on the bare-await code and pass with _maybe_await.
  • full suite: 166 passed; prek (ruff / ty / mdxify) clean and idempotent.

🤖 Generated with Claude Code

#86 awaited get_state/set_state unconditionally, which fixed local fastmcp
3.x but broke on FastMCP Cloud: the hosted runtime pins a fastmcp where
those are still sync, so `await ctx.get_state(...)` raised
`object NoneType can't be used in 'await' expression` on every tool call.

the installed fastmcp version doesn't govern the hosted runtime, so detect
the calling convention instead of assuming it:

- add `_maybe_await()` — awaits the return value only if it's awaitable
- route every `ctx.get_state(...)` (client.py) and `ctx.set_state(...)`
  (middleware.py) through it
- regression tests now parametrize over `_FakeAsyncContext` (3.x) AND
  `_FakeSyncContext` (2.x / hosted); the sync variants fail on the bare-await
  code and pass with `_maybe_await` (verified by reverting)

full suite: 166 passed; prek (ruff/ty/mdxify) clean and idempotent.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@zzstoatzz
zzstoatzz merged commit 0e95088 into main May 29, 2026
3 checks passed
@zzstoatzz
zzstoatzz deleted the fix/version-agnostic-state branch May 29, 2026 05:55
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.

1 participant