From 5fa2f4258a0eba46d202d4a32cf87b959820d46f Mon Sep 17 00:00:00 2001 From: Amanuel Tilahun Bogale Date: Mon, 6 Apr 2026 20:46:08 -0400 Subject: [PATCH] fix: serialize Pydantic AnyUrl fields when persisting MCP OAuth state OAuth client information and token responses from the MCP SDK contain Pydantic AnyUrl fields (client_uri, redirect_uris, etc.). The previous model_dump() call returned a dict with these AnyUrl objects still as their native Python type, which then crashed json.dumps with: TypeError: Object of type AnyUrl is not JSON serializable This caused any OAuth-based MCP server (e.g. alphaxiv) to fail registration with an "OAuth flow error" traceback during startup. Adding mode="json" tells Pydantic to serialize all fields to JSON-compatible primitives (AnyUrl -> str, datetime -> ISO string, etc.) before returning the dict, so the standard json.dumps can handle it. Three call sites fixed: - HermesTokenStorage.set_tokens - HermesTokenStorage.set_client_info - build_oauth_auth pre-registration write --- tools/mcp_oauth.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/mcp_oauth.py b/tools/mcp_oauth.py index 7910c3cdc..a0ec9dc0e 100644 --- a/tools/mcp_oauth.py +++ b/tools/mcp_oauth.py @@ -233,7 +233,7 @@ class HermesTokenStorage: return None async def set_tokens(self, tokens: "OAuthToken") -> None: - payload = tokens.model_dump(exclude_none=True) + payload = tokens.model_dump(mode="json", exclude_none=True) # Persist an absolute ``expires_at`` so a process restart can # reconstruct the correct remaining TTL. Without this the MCP SDK's # ``_initialize`` reloads a relative ``expires_in`` which has no @@ -265,7 +265,7 @@ class HermesTokenStorage: return None async def set_client_info(self, client_info: "OAuthClientInformationFull") -> None: - _write_json(self._client_info_path(), client_info.model_dump(exclude_none=True)) + _write_json(self._client_info_path(), client_info.model_dump(mode="json", exclude_none=True)) logger.debug("OAuth client info saved for %s", self._server_name) # -- cleanup ----------------------------------------------------------- @@ -508,7 +508,7 @@ def _maybe_preregister_client( info_dict["scope"] = cfg["scope"] client_info = OAuthClientInformationFull.model_validate(info_dict) - _write_json(storage._client_info_path(), client_info.model_dump(exclude_none=True)) + _write_json(storage._client_info_path(), client_info.model_dump(mode="json", exclude_none=True)) logger.debug("Pre-registered client_id=%s for '%s'", client_id, storage._server_name)