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 <yassin@berri.ai>
This commit is contained in:
Cursor Agent 2026-05-19 07:41:06 +00:00
parent d9e1ce2894
commit 501afd2361
No known key found for this signature in database
2 changed files with 16 additions and 5 deletions

View file

@ -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}}
)

View file

@ -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):