Add comprehensive CloudFlare error detection and user-friendly error handling

- Add CloudFlareError and PenpotAPIError exception classes to penpot_api.py
- Implement _is_cloudflare_error() method to detect CloudFlare protection blocks
- Add _create_cloudflare_error_message() to provide helpful user instructions
- Update _make_authenticated_request() to catch and handle CloudFlare errors
- Add _handle_api_error() method to MCP server for consistent error formatting
- Update all MCP tool methods to use enhanced error handling
- Provide clear instructions for resolving CloudFlare verification challenges
- Include error_type field for better error categorization in MCP responses

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
chema
2025-06-29 20:04:20 +02:00
parent a5517d6b95
commit d8ed2fac70
2 changed files with 135 additions and 8 deletions

View File

@@ -15,7 +15,7 @@ from typing import Dict, List, Optional
from mcp.server.fastmcp import FastMCP, Image
from penpot_mcp.api.penpot_api import PenpotAPI
from penpot_mcp.api.penpot_api import PenpotAPI, CloudFlareError, PenpotAPIError
from penpot_mcp.tools.penpot_tree import get_object_subtree_with_fields
from penpot_mcp.utils import config
from penpot_mcp.utils.cache import MemoryCache
@@ -91,6 +91,30 @@ Let me know which Penpot design you'd like to convert to code, and I'll guide yo
else:
self._register_resources(resources_only=False)
self._register_tools(include_resource_tools=False)
def _handle_api_error(self, e: Exception) -> dict:
"""Handle API errors and return user-friendly error messages."""
if isinstance(e, CloudFlareError):
return {
"error": "CloudFlare Protection",
"message": str(e),
"error_type": "cloudflare_protection",
"instructions": [
"Open your web browser and navigate to https://design.penpot.app",
"Log in to your Penpot account",
"Complete any CloudFlare human verification challenges if prompted",
"Once verified, try your request again"
]
}
elif isinstance(e, PenpotAPIError):
return {
"error": "Penpot API Error",
"message": str(e),
"error_type": "api_error",
"status_code": getattr(e, 'status_code', None)
}
else:
return {"error": str(e)}
def _register_resources(self, resources_only=False):
"""Register all MCP resources. If resources_only is True, only register server://info as a resource."""
@@ -148,7 +172,7 @@ Let me know which Penpot design you'd like to convert to code, and I'll guide yo
projects = self.api.list_projects()
return {"projects": projects}
except Exception as e:
return {"error": str(e)}
return self._handle_api_error(e)
@self.mcp.tool()
def get_project_files(project_id: str) -> dict:
"""Get all files contained within a specific Penpot project.
@@ -160,7 +184,7 @@ Let me know which Penpot design you'd like to convert to code, and I'll guide yo
files = self.api.get_project_files(project_id)
return {"files": files}
except Exception as e:
return {"error": str(e)}
return self._handle_api_error(e)
def get_cached_file(file_id: str) -> dict:
"""Internal helper to retrieve a file, using cache if available.
@@ -175,7 +199,7 @@ Let me know which Penpot design you'd like to convert to code, and I'll guide yo
self.file_cache.set(file_id, file_data)
return file_data
except Exception as e:
return {"error": str(e)}
return self._handle_api_error(e)
@self.mcp.tool()
def get_file(file_id: str) -> dict:
"""Retrieve a Penpot file by its ID and cache it. Don't use this tool for code generation, use 'get_object_tree' instead.
@@ -188,7 +212,7 @@ Let me know which Penpot design you'd like to convert to code, and I'll guide yo
self.file_cache.set(file_id, file_data)
return file_data
except Exception as e:
return {"error": str(e)}
return self._handle_api_error(e)
@self.mcp.tool()
def export_object(
file_id: str,
@@ -233,7 +257,10 @@ Let me know which Penpot design you'd like to convert to code, and I'll guide yo
return image
except Exception as e:
raise Exception(f"Export failed: {str(e)}")
if isinstance(e, CloudFlareError):
raise Exception(f"CloudFlare Protection: {str(e)}")
else:
raise Exception(f"Export failed: {str(e)}")
finally:
if temp_filename and os.path.exists(temp_filename):
try:
@@ -309,7 +336,7 @@ Let me know which Penpot design you'd like to convert to code, and I'll guide yo
return {"format_error": f"Error formatting as YAML: {str(e)}"}
return final_result
except Exception as e:
return {"error": str(e)}
return self._handle_api_error(e)
@self.mcp.tool()
def search_object(file_id: str, query: str) -> dict:
"""Search for objects within a Penpot file by name.
@@ -339,7 +366,7 @@ Let me know which Penpot design you'd like to convert to code, and I'll guide yo
})
return {'objects': matches}
except Exception as e:
return {"error": str(e)}
return self._handle_api_error(e)
if include_resource_tools:
@self.mcp.tool()
def penpot_schema() -> dict: