Files
archived-MoviePilot/docs/rules/09-external-response.md

175 lines
5.2 KiB
Markdown

# 09 — External APIs, Protocols, and Responses
## HTTP Client Conventions
**Rule:** All outbound HTTP requests must go through `RequestUtils` from `app/utils/http.py`. Do not use `requests`, `httpx`, or `aiohttp` directly.
`RequestUtils` handles:
- Proxy configuration (from `settings.PROXY_*`)
- Timeouts
- SSL verification settings
- User-Agent headers
- Retry logic
```python
from app.utils.http import RequestUtils
res = RequestUtils(
ua=settings.USER_AGENT,
proxies=settings.PROXY,
timeout=30,
).get_res(url="https://api.example.com/data")
if res and res.status_code == 200:
data = res.json()
```
---
## Response Format — REST API
All REST API responses use Pydantic schema models from `app/schemas/`. Do not return raw `dict` objects from endpoints.
### Standard Response Patterns
```python
# Success with data
from app.schemas.response import Response
return Response(success=True, message="", data=result)
# Success without data
return Response(success=True, message="操作成功")
# Error
return Response(success=False, message="错误原因描述")
```
### List Responses
For paginated lists, follow the pattern of existing endpoint files. Check `app/api/endpoints/` for examples matching the resource domain.
### Error Responses (Endpoint Layer Only)
In endpoints, raise `HTTPException` for request-level errors:
```python
from fastapi import HTTPException
raise HTTPException(status_code=404, detail="Resource not found")
raise HTTPException(status_code=403, detail="Permission denied")
```
Do not raise `HTTPException` in chain or module code. Chains and modules return `None` or domain-level error objects on failure; the endpoint translates that into an HTTP response.
---
## Error Handling by Layer
| Layer | On external API failure |
|---|---|
| Module | Log the error, return `None` or `(False, "error message")` tuple |
| Chain | Log the error, return `None` or an appropriate domain object with failure indication |
| Endpoint | Translate `None` or failure result into a `Response(success=False, ...)` or `HTTPException` |
```python
# Module layer
def test(self) -> Optional[Tuple[bool, str]]:
"""测试模块连通性"""
try:
ok = self.client.ping()
return (True, "连接成功") if ok else (False, "连接失败")
except Exception as err:
logger.error(f"测试连通性失败:{str(err)}")
return (False, str(err))
```
---
## MCP Protocol
MoviePilot exposes an MCP (Model Context Protocol) interface for AI agent integration.
- **Transport:** HTTP, JSON-RPC 2.0
- **Base path:** `/api/v1/mcp`
- **Protocol versions supported:** `2025-11-25`, `2025-06-18`, `2024-11-05`
### Authentication
```
Header: X-API-KEY: <api_key>
Query: ?apikey=<api_key>
```
### Supported Methods
| Method | Description |
|---|---|
| `initialize` | Initialize session, negotiate protocol version and capabilities |
| `notifications/initialized` | Client confirmation of initialization |
| `tools/list` | List all available tools |
| `tools/call` | Invoke a specific tool |
| `ping` | Connection liveness check |
### Error Codes
| Code | Message | Meaning |
|---|---|---|
| -32700 | Parse error | Malformed JSON |
| -32600 | Invalid Request | Invalid JSON-RPC request structure |
| -32601 | Method not found | Unknown method |
| -32602 | Invalid params | Parameter validation failure |
| -32002 | Session not found | Session does not exist or has expired |
| -32003 | Not initialized | Session has not completed initialization |
| -32603 | Internal error | Server-side error |
### Tool Response Format
MCP tools return structured content. Errors must use the JSON-RPC error object format, not HTTP status codes.
---
## Notification and Messaging
Internal notifications use the `Notification` schema and the event system:
```python
from app.schemas import Notification
from app.schemas.types import NotificationType, MessageChannel
from app.core.event import eventmanager
from app.schemas.types import EventType
eventmanager.send_event(
EventType.NoticeMessage,
{
"channel": MessageChannel.Telegram,
"type": NotificationType.Download,
"title": "下载成功",
"text": f"{media_name} 已添加到下载队列",
"image": poster_url,
}
)
```
Do not call message channel modules directly from chain code. Use the event bus to decouple senders from channels.
---
## Media Metadata API Conventions
When calling TMDB, TheTVDB, Douban, or Bangumi via the module layer:
- Always check the module return for `None` before using the result — modules return `None` when the backend is not configured or the request fails.
- Cache responses using `FileCache` / `AsyncFileCache` where the result is stable and repeated requests would be expensive.
- Return domain objects (`MediaInfo`, `TmdbEpisode`, `MediaPerson`, etc.) from modules, never raw API response dicts.
---
## Webhook Handling
Webhook payloads arrive at `app/api/endpoints/webhook.py` and are dispatched via `eventmanager.send_event(EventType.WebhookMessage, ...)`. Processing logic lives in the chain layer (`app/chain/webhook.py`).
Do not add webhook-specific business logic directly in the endpoint. The endpoint parses the payload and fires the event; the chain handles the response.
*Last Updated: 2026-05-25*