Add test_credentials.py for Penpot API credential verification and project listing
- Introduced a new script, `test_credentials.py`, to verify Penpot API credentials and list associated projects. - The script loads environment variables, checks for required credentials, and attempts to authenticate with the Penpot API. - Added functionality to fetch and display project details and files, including error handling for authentication and project retrieval. - Updated `PenpotAPI` class to include a User-Agent header and improved error handling during profile retrieval. - Minor adjustments in import order across various modules for consistency.
This commit is contained in:
@@ -31,7 +31,8 @@ class PenpotAPI:
|
||||
# based on the required content type (JSON vs Transit+JSON)
|
||||
self.session.headers.update({
|
||||
"Accept": "application/json, application/transit+json",
|
||||
"Content-Type": "application/json"
|
||||
"Content-Type": "application/json",
|
||||
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
|
||||
})
|
||||
|
||||
def set_access_token(self, token: str):
|
||||
@@ -64,7 +65,12 @@ class PenpotAPI:
|
||||
token = self.login_for_export(email, password)
|
||||
self.set_access_token(token)
|
||||
# Get profile ID after login
|
||||
self.get_profile()
|
||||
try:
|
||||
self.get_profile()
|
||||
except Exception as e:
|
||||
if self.debug:
|
||||
print(f"\nWarning: Could not get profile (may be blocked by Cloudflare): {e}")
|
||||
# Continue without profile_id - most operations don't need it
|
||||
return token
|
||||
|
||||
def get_profile(self) -> Dict[str, Any]:
|
||||
@@ -138,7 +144,8 @@ class PenpotAPI:
|
||||
|
||||
# Set headers
|
||||
headers = {
|
||||
"Content-Type": "application/transit+json"
|
||||
"Content-Type": "application/transit+json",
|
||||
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
|
||||
}
|
||||
|
||||
response = login_session.post(url, json=payload, headers=headers)
|
||||
@@ -171,7 +178,7 @@ class PenpotAPI:
|
||||
# If we reached here, we couldn't find the token
|
||||
raise ValueError("Auth token not found in response cookies or JSON body")
|
||||
|
||||
def _make_authenticated_request(self, method: str, url: str, **kwargs) -> requests.Response:
|
||||
def _make_authenticated_request(self, method: str, url: str, retry_auth: bool = True, **kwargs) -> requests.Response:
|
||||
"""
|
||||
Make an authenticated request, handling re-auth if needed.
|
||||
|
||||
@@ -269,7 +276,11 @@ class PenpotAPI:
|
||||
|
||||
except requests.HTTPError as e:
|
||||
# Handle authentication errors
|
||||
if e.response.status_code in (401, 403) and self.email and self.password:
|
||||
if e.response.status_code in (401, 403) and self.email and self.password and retry_auth:
|
||||
# Special case: don't retry auth for get-profile to avoid infinite loops
|
||||
if url.endswith('/get-profile'):
|
||||
raise
|
||||
|
||||
if self.debug:
|
||||
print("\nAuthentication failed. Trying to re-login...")
|
||||
|
||||
@@ -280,7 +291,7 @@ class PenpotAPI:
|
||||
headers['Authorization'] = f"Token {self.access_token}"
|
||||
combined_headers = {**self.session.headers, **headers}
|
||||
|
||||
# Retry the request with the new token
|
||||
# Retry the request with the new token (but don't retry auth again)
|
||||
response = getattr(self.session, method)(url, headers=combined_headers, **kwargs)
|
||||
response.raise_for_status()
|
||||
return response
|
||||
@@ -500,7 +511,8 @@ class PenpotAPI:
|
||||
|
||||
headers = {
|
||||
"Content-Type": "application/transit+json",
|
||||
"Accept": "application/transit+json"
|
||||
"Accept": "application/transit+json",
|
||||
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
|
||||
}
|
||||
|
||||
# Make the request
|
||||
@@ -557,7 +569,8 @@ class PenpotAPI:
|
||||
}
|
||||
headers = {
|
||||
"Content-Type": "application/transit+json",
|
||||
"Accept": "*/*"
|
||||
"Accept": "*/*",
|
||||
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
|
||||
}
|
||||
if self.debug:
|
||||
print(f"\nFetching export resource: {url}")
|
||||
|
||||
@@ -5,13 +5,14 @@ This module defines the MCP server with resources and tools for interacting with
|
||||
the Penpot design platform.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import argparse
|
||||
import sys
|
||||
from typing import List, Optional, Dict
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
from mcp.server.fastmcp import FastMCP, Image
|
||||
|
||||
from penpot_mcp.api.penpot_api import PenpotAPI
|
||||
|
||||
@@ -6,7 +6,7 @@ a tree representation, which can be displayed or exported.
|
||||
"""
|
||||
|
||||
import re
|
||||
from typing import Any, Dict, Optional, Union, List
|
||||
from typing import Any, Dict, List, Optional, Union
|
||||
|
||||
from anytree import Node, RenderTree
|
||||
from anytree.exporter import DotExporter
|
||||
|
||||
@@ -3,7 +3,8 @@ Cache utilities for Penpot MCP server.
|
||||
"""
|
||||
|
||||
import time
|
||||
from typing import Optional, Dict, Any
|
||||
from typing import Any, Dict, Optional
|
||||
|
||||
|
||||
class MemoryCache:
|
||||
"""In-memory cache implementation with TTL support."""
|
||||
|
||||
@@ -2,9 +2,10 @@
|
||||
|
||||
import io
|
||||
import json
|
||||
import socketserver
|
||||
import threading
|
||||
from http.server import BaseHTTPRequestHandler, HTTPServer
|
||||
import socketserver
|
||||
|
||||
|
||||
class InMemoryImageHandler(BaseHTTPRequestHandler):
|
||||
"""HTTP request handler for serving images stored in memory."""
|
||||
|
||||
Reference in New Issue
Block a user