Files
assistant/mcp_servers/shared/mall_client.py
wangliang c4e97cf312 feat: 添加物流查询功能和完善 token 传递
- 添加 get_logistics 工具查询 Mall API /mall/api/order/parcel
- 修复 Cookie token 传递到 MCP 的问题
- 增强 LLM 客户端超时处理和日志
- 移除 MALL_API_TOKEN,使用用户登录 token
- 更新测试页面使用 setUser 设置用户属性
- 增强 webhook 调试日志
2026-01-16 18:36:17 +08:00

183 lines
5.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
Mall API Client for MCP Servers
用于调用商城 API包括订单查询等接口
"""
from typing import Any, Optional
import httpx
from pydantic_settings import BaseSettings
from pydantic import ConfigDict
class MallSettings(BaseSettings):
"""Mall API configuration"""
mall_api_url: Optional[str] = None
mall_api_token: Optional[str] = None
mall_tenant_id: str = "2"
mall_currency_code: str = "EUR"
mall_language_id: str = "1"
mall_source: str = "us.qa1.gaia888.com"
model_config = ConfigDict(
env_file=".env",
env_file_encoding="utf-8",
extra="ignore"
)
settings = MallSettings()
class MallClient:
"""Async client for Mall API"""
def __init__(
self,
api_url: Optional[str] = None,
api_token: Optional[str] = None,
tenant_id: Optional[str] = None,
currency_code: Optional[str] = None,
language_id: Optional[str] = None,
source: Optional[str] = None
):
self.api_url = (api_url or settings.mall_api_url or "").rstrip("/")
self.api_token = api_token or settings.mall_api_token or ""
self.tenant_id = tenant_id or settings.mall_tenant_id
self.currency_code = currency_code or settings.mall_currency_code
self.language_id = language_id or settings.mall_language_id
self.source = source or settings.mall_source
self._client: Optional[httpx.AsyncClient] = None
async def _get_client(self) -> httpx.AsyncClient:
"""Get or create HTTP client with default headers"""
if self._client is None:
self._client = httpx.AsyncClient(
base_url=self.api_url,
headers={
"Authorization": f"Bearer {self.api_token}",
"Content-Type": "application/json",
"Accept": "application/json",
"Device-Type": "pc",
"tenant-Id": self.tenant_id,
"currency-code": self.currency_code,
"language-id": self.language_id,
"source": self.source,
"origin": "https://www.qa1.gaia888.com",
"referer": "https://www.qa1.gaia888.com/",
},
timeout=30.0
)
return self._client
async def close(self):
"""Close HTTP client"""
if self._client:
await self._client.aclose()
self._client = None
async def request(
self,
method: str,
endpoint: str,
params: Optional[dict[str, Any]] = None,
json: Optional[dict[str, Any]] = None,
headers: Optional[dict[str, str]] = None
) -> dict[str, Any]:
"""Make API request and handle response
Args:
method: HTTP method
endpoint: API endpoint (e.g., "/mall/api/order/show")
params: Query parameters
json: JSON body
headers: Additional headers
Returns:
Response data
"""
client = await self._get_client()
# Merge additional headers
request_headers = {}
if headers:
request_headers.update(headers)
response = await client.request(
method=method,
url=endpoint,
params=params,
json=json,
headers=request_headers
)
response.raise_for_status()
data = response.json()
# Mall API 返回格式: {"code": 200, "msg": "success", "result": {...}}
# 检查 API 错误
if data.get("code") != 200:
raise Exception(f"API Error [{data.get('code')}]: {data.get('msg') or data.get('message')}")
# 返回 result 字段或整个 data
return data.get("result", data)
async def get(
self,
endpoint: str,
params: Optional[dict[str, Any]] = None,
**kwargs: Any
) -> dict[str, Any]:
"""GET request"""
return await self.request("GET", endpoint, params=params, **kwargs)
async def post(
self,
endpoint: str,
json: Optional[dict[str, Any]] = None,
**kwargs: Any
) -> dict[str, Any]:
"""POST request"""
return await self.request("POST", endpoint, json=json, **kwargs)
# ============ Order APIs ============
async def get_order_by_id(
self,
order_id: str
) -> dict[str, Any]:
"""Query order by order ID
根据订单号查询订单详情
Args:
order_id: 订单号 (e.g., "202071324")
Returns:
订单详情,包含订单号、状态、商品信息、金额、物流信息等
Order details including order ID, status, items, amount, logistics info, etc.
Example:
>>> client = MallClient()
>>> order = await client.get_order_by_id("202071324")
>>> print(order["order_id"])
"""
try:
result = await self.get(
"/mall/api/order/show",
params={"orderId": order_id}
)
return result
except Exception as e:
raise Exception(f"查询订单失败 (Query order failed): {str(e)}")
# Global Mall client instance
mall_client: Optional[MallClient] = None
def get_mall_client() -> MallClient:
"""Get or create global Mall client instance"""
global mall_client
if mall_client is None:
mall_client = MallClient()
return mall_client