From 501afd2361f710621647b8f49fec6e07e231aa1d Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 19 May 2026 07:41:06 +0000 Subject: [PATCH] fix(mcp): provide data arg to RecordNotFoundError; preserve BYOK has_user_credential on decryption failure - delete_user_credential now constructs RecordNotFoundError with the required 'data' kwarg so the proxy delete endpoints continue to receive a typed exception instead of a TypeError-induced 500. - _annotate_user_credential_flags falls back to row-existence when a credential row cannot be decrypted (e.g. after salt-key rotation), so BYOK servers do not regress to has_user_credential=False when the row is in fact stored. Co-authored-by: Yassin Kortam --- litellm/proxy/_experimental/mcp_server/db.py | 13 +++++++++++-- .../mcp_management_endpoints.py | 8 +++++--- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/litellm/proxy/_experimental/mcp_server/db.py b/litellm/proxy/_experimental/mcp_server/db.py index 175a9e4597a..ff4b72002be 100644 --- a/litellm/proxy/_experimental/mcp_server/db.py +++ b/litellm/proxy/_experimental/mcp_server/db.py @@ -680,11 +680,20 @@ async def delete_user_credential( where={"user_id_server_id": {"user_id": user_id, "server_id": server_id}} ) if existing is None: - raise RecordNotFoundError() + raise RecordNotFoundError( + data={"error": {"message": "no BYOK credential row", "meta": {}}} + ) if _decode_user_fields_payload(existing.credential_b64) is not None: # Treat as "no BYOK credential present" so the endpoint reports # has_credential=False without clobbering the user-fields row. - raise RecordNotFoundError() + raise RecordNotFoundError( + data={ + "error": { + "message": "row holds user-fields payload, not a BYOK credential", + "meta": {}, + } + } + ) await prisma_client.db.litellm_mcpusercredentials.delete( where={"user_id_server_id": {"user_id": user_id, "server_id": server_id}} ) diff --git a/litellm/proxy/management_endpoints/mcp_management_endpoints.py b/litellm/proxy/management_endpoints/mcp_management_endpoints.py index 9c1410bbae9..f3c0fecb174 100644 --- a/litellm/proxy/management_endpoints/mcp_management_endpoints.py +++ b/litellm/proxy/management_endpoints/mcp_management_endpoints.py @@ -882,12 +882,14 @@ if MCP_AVAILABLE: # Decrypt once and classify, instead of paying the crypto # cost twice (once for user-fields detection, once for BYOK). decoded = _decode_user_credential(row.credential_b64) - if not decoded: - continue - payload = _parse_user_fields_plaintext(decoded) + payload = _parse_user_fields_plaintext(decoded) if decoded else None if payload is not None: user_fields_by_server[row.server_id] = payload else: + # Either a BYOK credential or an undecryptable row (e.g. + # after a salt-key rotation). Either way, a credential row + # exists for this (user, server), so surface it as present + # instead of silently telling the user to reconnect. byok_set.add(row.server_id) for server in servers: if getattr(server, "is_byok", False):