Skip to main content
Glama
appstore_connect_mcp_client.py5.22 kB
""" App Store Connect MCP 客户端 - 重构为中转站模式 """ import os import time from typing import Dict, Any, Optional import jwt import requests from .models import AppStoreConnectConfig from ..mcp_client_interface import IMCPClient class AppStoreConnectMCPClient(IMCPClient): """App Store Connect MCP 客户端 - 作为中转站协调各个处理器""" def __init__(self): """初始化客户端和各个处理器""" self.config = None # 调用父类的初始化方法来自动发现处理器 super().__init__() # ============================================================================= # 配置和基础服务方法 - 供handler调用 # ============================================================================= def set_config(self, config: AppStoreConnectConfig) -> None: """设置配置""" self.config = config def load_config_from_env(self) -> Optional[AppStoreConnectConfig]: """从环境变量加载App Store Connect配置""" key_id = os.getenv('APPSTORE_KEY_ID') issuer_id = os.getenv('APPSTORE_ISSUER_ID') vendor_number = os.getenv('APPSTORE_VENDOR_NUMBER') # 支持多种私钥配置方式 private_key = ( os.getenv('APPSTORE_PRIVATE_KEY') or self._load_private_key_from_file() ) # 处理私钥格式 - 将 \n 转换为真正的换行符 if private_key and '\\n' in private_key: private_key = private_key.replace('\\n', '\n') # 检查所有必填字段 if not all([key_id, issuer_id, private_key, vendor_number]): return None try: return AppStoreConnectConfig( key_id=key_id, issuer_id=issuer_id, private_key=private_key, vendor_number=vendor_number ) except ValueError as e: print(f"配置验证失败: {str(e)}") return None @classmethod def _load_private_key_from_file(cls) -> Optional[str]: """从文件加载私钥""" key_path = os.getenv('APPSTORE_PRIVATE_KEY_PATH') if key_path and os.path.exists(key_path): try: with open(key_path, 'r') as f: return f.read() except Exception as e: print(f"读取私钥文件失败: {str(e)}") pass return None def generate_jwt_token(self) -> str: """生成JWT认证令牌""" if not self.config: self.config = self.load_config_from_env() header = { "alg": "ES256", "kid": self.config.key_id, "typ": "JWT" } payload = { "iss": self.config.issuer_id, "iat": int(time.time()), "exp": int(time.time()) + 20 * 60, # 20分钟过期 "aud": "appstoreconnect-v1" } return jwt.encode(payload, self.config.private_key, algorithm="ES256", headers=header) def make_api_request(self, endpoint: str, method: str = "GET", data: Optional[Dict] = None) -> Dict[str, Any]: """发送API请求""" if not self.config: self.config = self.load_config_from_env() if not self.config: raise ValueError("未找到App Store Connect配置,请先配置环境变量") token = self.generate_jwt_token() headers = { "Authorization": f"Bearer {token}", "Content-Type": "application/json" } url = f"https://api.appstoreconnect.apple.com/v1/{endpoint}" try: if method == "GET": # 对于GET请求,将data作为查询参数 response = requests.get(url, headers=headers, params=data) elif method == "POST": response = requests.post(url, headers=headers, json=data) elif method == "PATCH": response = requests.patch(url, headers=headers, json=data) elif method == "DELETE": response = requests.delete(url, headers=headers, json=data) else: raise ValueError(f"不支持的HTTP方法: {method}") response.raise_for_status() # 打印响应信息用于调试 print(f"响应状态码: {response.status_code}") print(f"响应头: {dict(response.headers)}") print(f"响应内容类型: {response.headers.get('content-type', 'unknown')}") print(f"响应内容长度: {len(response.content)} bytes") # 检查响应内容类型 content_type = response.headers.get('content-type', '').lower() if 'application/json' in content_type: return response.json() elif 'application/a-gzip' in content_type or 'application/gzip' in content_type: return {"raw_content": response.content, "content_type": content_type} except requests.exceptions.RequestException as e: raise Exception(f"API请求失败: {str(e)}") except ValueError as e: raise Exception(f"API请求失败: {str(e)}")

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/peroperogames/pero-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server