From 3aa23fa61cedcf7c355cff50dc84cfdf22c4e2cc Mon Sep 17 00:00:00 2001 From: Akhil Vamshi Konam Date: Thu, 23 Apr 2026 00:03:01 +0530 Subject: [PATCH] [PAI-1383] fix: mcp error status codes and add structured exception logging (#6866) * chore: improve MCP error logging and update status codes to 400 for connection and OAuth failures * fix: improve MCP connection and decryption error logging by including stack traces and sanitizing error messages --- .../api/plane/silo/services/mcp_connection.py | 14 ++++++----- apps/api/plane/silo/utils/mcp.py | 4 ++-- apps/api/plane/silo/views/app/mcp.py | 24 ++++++++++--------- 3 files changed, 23 insertions(+), 19 deletions(-) diff --git a/apps/api/plane/silo/services/mcp_connection.py b/apps/api/plane/silo/services/mcp_connection.py index 5d6ee3f670..725d44888a 100644 --- a/apps/api/plane/silo/services/mcp_connection.py +++ b/apps/api/plane/silo/services/mcp_connection.py @@ -74,8 +74,8 @@ async def _probe_mcp_async(url, headers): err_msg = _mcp_error_from_exc(exc) if err_msg: return False, {"error": err_msg} - logger.warning("Streamable HTTP probe failed for %s: %s: %s", url, type(exc).__name__, exc) - return False, {"error": f"Connection failed: {type(exc).__name__}"} + logger.warning("Streamable HTTP probe failed for %s", url, exc_info=True) + return False, {"error": "Connection failed."} def test_mcp_connection(url, headers=None): @@ -84,9 +84,9 @@ def test_mcp_connection(url, headers=None): """ try: return async_to_sync(_probe_mcp_async)(url, headers) - except Exception as e: - logger.exception("Unexpected error testing MCP connection to %s", url) - return False, {"error": f"Unexpected error during connection test: {e}"} + except Exception: + logger.warning("Unexpected error testing MCP connection to %s", url, exc_info=True) + return False, {"error": "Unexpected error during connection test."} # --------------------------------------------------------------------------- @@ -228,6 +228,7 @@ def discover_oauth_metadata(mcp_url): prm_data = prm_resp.json() break except Exception: + logger.debug("PRM fetch failed for %s", prm_url, exc_info=True) continue if not prm_data: @@ -253,6 +254,7 @@ def discover_oauth_metadata(mcp_url): as_data = as_resp.json() break except Exception: + logger.debug("AS metadata fetch failed for %s", as_url, exc_info=True) continue if not as_data: @@ -568,7 +570,7 @@ def decrypt_auth_config(auth_config): try: config[key] = _decrypt_value(config[key]) except Exception: - logger.warning("Failed to decrypt auth_config key '%s'; value may be unencrypted", key) + logger.warning("Failed to decrypt auth_config key '%s'; value may be unencrypted", key, exc_info=True) return config diff --git a/apps/api/plane/silo/utils/mcp.py b/apps/api/plane/silo/utils/mcp.py index 26bd4ea0be..3f8f3c558f 100644 --- a/apps/api/plane/silo/utils/mcp.py +++ b/apps/api/plane/silo/utils/mcp.py @@ -111,7 +111,7 @@ def refresh_token_if_expired(credentials, decrypted_config, mcp_url, app_label): credentials.auth_config = encrypt_auth_config(decrypted_config) credentials.save(update_fields=["auth_config"]) logger.info("Refreshed OAuth token for MCP app %s", app_label) - except ValueError as e: - logger.warning("OAuth token refresh failed for MCP app %s: %s", app_label, e) + except ValueError: + logger.warning("OAuth token refresh failed for MCP app %s", app_label, exc_info=True) return decrypted_config diff --git a/apps/api/plane/silo/views/app/mcp.py b/apps/api/plane/silo/views/app/mcp.py index 9e5dbf4ff3..878cfe0b8a 100644 --- a/apps/api/plane/silo/views/app/mcp.py +++ b/apps/api/plane/silo/views/app/mcp.py @@ -279,7 +279,7 @@ class MCPApplicationConnectAPIView(BaseAPIView): if not success: return Response( {"error": data.get("error", "Could not connect to MCP server.")}, - status=status.HTTP_502_BAD_GATEWAY, + status=status.HTTP_400_BAD_REQUEST, ) save_credentials_and_activate(mcp_app, workspace_id, user_id, auth_config={}) @@ -310,7 +310,7 @@ class MCPApplicationConnectAPIView(BaseAPIView): if not success: return Response( {"error": data.get("error", "Could not connect to MCP server.")}, - status=status.HTTP_502_BAD_GATEWAY, + status=status.HTTP_400_BAD_REQUEST, ) save_credentials_and_activate(mcp_app, workspace_id, user_id, credentials.auth_config) @@ -335,10 +335,11 @@ class MCPApplicationOAuthConnectAPIView(BaseAPIView): try: metadata = discover_oauth_metadata(mcp_app.url) - except ValueError as e: + except ValueError: + logger.warning("OAuth metadata discovery failed for MCP app %s", pk, exc_info=True) return Response( - {"error": "OAuth discovery failed.", "detail": str(e)}, - status=status.HTTP_502_BAD_GATEWAY, + {"error": "OAuth discovery failed."}, + status=status.HTTP_400_BAD_REQUEST, ) callback_url = f"{settings.API_URL}/silo/mcp-applications/oauth/callback/" @@ -346,7 +347,7 @@ class MCPApplicationOAuthConnectAPIView(BaseAPIView): if not metadata.get("registration_endpoint"): return Response( {"error": "Server does not support dynamic client registration."}, - status=status.HTTP_502_BAD_GATEWAY, + status=status.HTTP_400_BAD_REQUEST, ) try: @@ -355,10 +356,11 @@ class MCPApplicationOAuthConnectAPIView(BaseAPIView): redirect_uri=callback_url, client_name="Plane", ) - except ValueError as e: + except ValueError: + logger.warning("OAuth DCR failed for MCP app %s", pk, exc_info=True) return Response( - {"error": "Dynamic client registration failed.", "detail": str(e)}, - status=status.HTTP_502_BAD_GATEWAY, + {"error": "Dynamic client registration failed."}, + status=status.HTTP_400_BAD_REQUEST, ) dcr_client_id = dcr_result["client_id"] @@ -471,8 +473,8 @@ class MCPApplicationOAuthCallbackAPIView(BaseAPIView): "token_endpoint_auth_method", "client_secret_post" ), ) - except ValueError as e: - logger.error("OAuth token exchange failed for MCP app %s: %s", mcp_app_id, e) + except ValueError: + logger.error("OAuth token exchange failed for MCP app %s", mcp_app_id, exc_info=True) return HttpResponseRedirect( f"{connectors_url}&status=error&reason=token_exchange_failed" )