mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-05-06 20:42:43 +08:00
Merge branch 'v2' of https://github.com/jxxghp/MoviePilot into v2
This commit is contained in:
@@ -2,17 +2,32 @@
|
||||
通过HTTP API暴露MoviePilot的智能体工具功能
|
||||
"""
|
||||
|
||||
from typing import List, Any, Dict, Annotated
|
||||
import uuid
|
||||
from typing import List, Any, Dict, Annotated, Optional, Union
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from fastapi import APIRouter, Depends, HTTPException, Request, Header
|
||||
from fastapi.responses import JSONResponse, Response
|
||||
|
||||
from app import schemas
|
||||
from app.agent.tools.manager import MoviePilotToolsManager
|
||||
from app.core.security import verify_apikey
|
||||
from app.log import logger
|
||||
|
||||
# 导入版本号
|
||||
try:
|
||||
from version import APP_VERSION
|
||||
except ImportError:
|
||||
APP_VERSION = "unknown"
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
# MCP 协议版本
|
||||
MCP_PROTOCOL_VERSIONS = ["2025-11-25", "2025-06-18", "2024-11-05"]
|
||||
MCP_PROTOCOL_VERSION = MCP_PROTOCOL_VERSIONS[0] # 默认使用最新版本
|
||||
|
||||
# 全局会话管理器
|
||||
_sessions: Dict[str, Dict[str, Any]] = {}
|
||||
|
||||
# 全局工具管理器实例(单例模式,按用户ID缓存)
|
||||
_tools_managers: Dict[str, MoviePilotToolsManager] = {}
|
||||
|
||||
@@ -39,6 +54,302 @@ def get_tools_manager(user_id: str = "mcp_user", session_id: str = "mcp_session"
|
||||
return _tools_managers[cache_key]
|
||||
|
||||
|
||||
def get_session(session_id: Optional[str]) -> Optional[Dict[str, Any]]:
|
||||
"""获取会话"""
|
||||
if not session_id:
|
||||
return None
|
||||
return _sessions.get(session_id)
|
||||
|
||||
|
||||
def create_session(user_id: str) -> Dict[str, Any]:
|
||||
"""创建新会话"""
|
||||
session_id = str(uuid.uuid4())
|
||||
session = {
|
||||
"id": session_id,
|
||||
"user_id": user_id,
|
||||
"initialized": False,
|
||||
"protocol_version": None,
|
||||
"capabilities": {}
|
||||
}
|
||||
_sessions[session_id] = session
|
||||
return session
|
||||
|
||||
|
||||
def delete_session(session_id: str):
|
||||
"""删除会话"""
|
||||
if session_id in _sessions:
|
||||
del _sessions[session_id]
|
||||
# 同时清理工具管理器缓存
|
||||
cache_key = f"{_sessions.get(session_id, {}).get('user_id', 'mcp_user')}_{session_id}"
|
||||
if cache_key in _tools_managers:
|
||||
del _tools_managers[cache_key]
|
||||
|
||||
|
||||
def create_jsonrpc_response(request_id: Union[str, int, None], result: Any) -> Dict[str, Any]:
|
||||
"""创建 JSON-RPC 成功响应"""
|
||||
response = {
|
||||
"jsonrpc": "2.0",
|
||||
"id": request_id,
|
||||
"result": result
|
||||
}
|
||||
return response
|
||||
|
||||
|
||||
def create_jsonrpc_error(request_id: Union[str, int, None], code: int, message: str, data: Any = None) -> Dict[str, Any]:
|
||||
"""创建 JSON-RPC 错误响应"""
|
||||
error = {
|
||||
"jsonrpc": "2.0",
|
||||
"id": request_id,
|
||||
"error": {
|
||||
"code": code,
|
||||
"message": message
|
||||
}
|
||||
}
|
||||
if data is not None:
|
||||
error["error"]["data"] = data
|
||||
return error
|
||||
|
||||
|
||||
# ==================== MCP JSON-RPC 端点 ====================
|
||||
|
||||
@router.post("", summary="MCP JSON-RPC 端点")
|
||||
async def mcp_jsonrpc(
|
||||
request: Request,
|
||||
mcp_session_id: Optional[str] = Header(None, alias="MCP-Session-Id"),
|
||||
mcp_protocol_version: Optional[str] = Header(None, alias="MCP-Protocol-Version"),
|
||||
_: Annotated[str, Depends(verify_apikey)] = None
|
||||
) -> JSONResponse:
|
||||
"""
|
||||
MCP 标准 JSON-RPC 2.0 端点
|
||||
|
||||
处理所有 MCP 协议消息(初始化、工具列表、工具调用等)
|
||||
"""
|
||||
try:
|
||||
body = await request.json()
|
||||
except Exception as e:
|
||||
logger.error(f"解析请求体失败: {e}")
|
||||
return JSONResponse(
|
||||
status_code=400,
|
||||
content=create_jsonrpc_error(None, -32700, "Parse error", str(e))
|
||||
)
|
||||
|
||||
# 验证 JSON-RPC 格式
|
||||
if not isinstance(body, dict) or body.get("jsonrpc") != "2.0":
|
||||
return JSONResponse(
|
||||
status_code=400,
|
||||
content=create_jsonrpc_error(body.get("id"), -32600, "Invalid Request")
|
||||
)
|
||||
|
||||
method = body.get("method")
|
||||
params = body.get("params", {})
|
||||
request_id = body.get("id")
|
||||
|
||||
# 如果有 id,则为请求;没有 id 则为通知
|
||||
is_notification = request_id is None
|
||||
|
||||
try:
|
||||
# 处理初始化请求
|
||||
if method == "initialize":
|
||||
result = await handle_initialize(params, mcp_session_id)
|
||||
response = create_jsonrpc_response(request_id, result["result"])
|
||||
# 如果创建了新会话,在响应头中返回
|
||||
if "session_id" in result:
|
||||
headers = {"MCP-Session-Id": result["session_id"]}
|
||||
return JSONResponse(content=response, headers=headers)
|
||||
return JSONResponse(content=response)
|
||||
|
||||
# 处理已初始化通知
|
||||
elif method == "notifications/initialized":
|
||||
if is_notification:
|
||||
session = get_session(mcp_session_id)
|
||||
if session:
|
||||
session["initialized"] = True
|
||||
# 通知不需要响应
|
||||
return Response(status_code=202)
|
||||
else:
|
||||
return JSONResponse(
|
||||
content=create_jsonrpc_error(request_id, -32600, "initialized must be a notification")
|
||||
)
|
||||
|
||||
# 验证会话(除了 initialize 和 ping)
|
||||
if method not in ["initialize", "ping"]:
|
||||
session = get_session(mcp_session_id)
|
||||
if not session:
|
||||
return JSONResponse(
|
||||
status_code=404,
|
||||
content=create_jsonrpc_error(request_id, -32002, "Session not found")
|
||||
)
|
||||
if not session.get("initialized") and method != "notifications/initialized":
|
||||
return JSONResponse(
|
||||
content=create_jsonrpc_error(request_id, -32003, "Not initialized")
|
||||
)
|
||||
|
||||
# 处理工具列表请求
|
||||
if method == "tools/list":
|
||||
result = await handle_tools_list(mcp_session_id)
|
||||
return JSONResponse(content=create_jsonrpc_response(request_id, result))
|
||||
|
||||
# 处理工具调用请求
|
||||
elif method == "tools/call":
|
||||
result = await handle_tools_call(params, mcp_session_id)
|
||||
return JSONResponse(content=create_jsonrpc_response(request_id, result))
|
||||
|
||||
# 处理 ping 请求
|
||||
elif method == "ping":
|
||||
return JSONResponse(content=create_jsonrpc_response(request_id, {}))
|
||||
|
||||
# 未知方法
|
||||
else:
|
||||
return JSONResponse(
|
||||
content=create_jsonrpc_error(request_id, -32601, f"Method not found: {method}")
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"处理 MCP 请求失败: {e}", exc_info=True)
|
||||
return JSONResponse(
|
||||
status_code=500,
|
||||
content=create_jsonrpc_error(request_id, -32603, "Internal error", str(e))
|
||||
)
|
||||
|
||||
|
||||
async def handle_initialize(params: Dict[str, Any], session_id: Optional[str]) -> Dict[str, Any]:
|
||||
"""处理初始化请求"""
|
||||
protocol_version = params.get("protocolVersion")
|
||||
client_info = params.get("clientInfo", {})
|
||||
client_capabilities = params.get("capabilities", {})
|
||||
|
||||
logger.info(f"MCP 初始化请求: 客户端={client_info.get('name')}, 协议版本={protocol_version}")
|
||||
|
||||
# 如果没有提供会话ID,创建新会话
|
||||
new_session = None
|
||||
if not session_id:
|
||||
new_session = create_session(user_id="mcp_user")
|
||||
session_id = new_session["id"]
|
||||
|
||||
session = get_session(session_id) or new_session
|
||||
if not session:
|
||||
raise ValueError("Failed to create session")
|
||||
|
||||
# 版本协商:选择客户端和服务器都支持的版本
|
||||
negotiated_version = MCP_PROTOCOL_VERSION
|
||||
if protocol_version in MCP_PROTOCOL_VERSIONS:
|
||||
# 客户端版本在支持列表中,使用客户端版本
|
||||
negotiated_version = protocol_version
|
||||
logger.info(f"使用客户端协议版本: {negotiated_version}")
|
||||
else:
|
||||
# 客户端版本不支持,使用服务器默认版本
|
||||
logger.warning(f"协议版本不匹配: 客户端={protocol_version}, 使用服务器版本={negotiated_version}")
|
||||
|
||||
session["protocol_version"] = negotiated_version
|
||||
session["capabilities"] = client_capabilities
|
||||
|
||||
result = {
|
||||
"result": {
|
||||
"protocolVersion": negotiated_version,
|
||||
"capabilities": {
|
||||
"tools": {
|
||||
"listChanged": False # 暂不支持工具列表变更通知
|
||||
},
|
||||
"logging": {}
|
||||
},
|
||||
"serverInfo": {
|
||||
"name": "MoviePilot",
|
||||
"version": APP_VERSION,
|
||||
"description": "MoviePilot MCP Server - 电影自动化管理工具",
|
||||
},
|
||||
"instructions": "MoviePilot MCP 服务器,提供媒体管理、订阅、下载等工具。使用 tools/list 查看所有可用工具。"
|
||||
}
|
||||
}
|
||||
|
||||
# 如果是新创建的会话,返回会话ID
|
||||
if new_session:
|
||||
result["session_id"] = session_id
|
||||
|
||||
return result
|
||||
|
||||
|
||||
async def handle_tools_list(session_id: Optional[str]) -> Dict[str, Any]:
|
||||
"""处理工具列表请求"""
|
||||
session = get_session(session_id)
|
||||
user_id = session.get("user_id", "mcp_user") if session else "mcp_user"
|
||||
|
||||
manager = get_tools_manager(user_id=user_id, session_id=session_id or "default")
|
||||
tools = manager.list_tools()
|
||||
|
||||
# 转换为 MCP 工具格式
|
||||
mcp_tools = []
|
||||
for tool in tools:
|
||||
mcp_tool = {
|
||||
"name": tool.name,
|
||||
"description": tool.description,
|
||||
"inputSchema": tool.input_schema
|
||||
}
|
||||
mcp_tools.append(mcp_tool)
|
||||
|
||||
return {
|
||||
"tools": mcp_tools
|
||||
}
|
||||
|
||||
|
||||
async def handle_tools_call(params: Dict[str, Any], session_id: Optional[str]) -> Dict[str, Any]:
|
||||
"""处理工具调用请求"""
|
||||
tool_name = params.get("name")
|
||||
arguments = params.get("arguments", {})
|
||||
|
||||
if not tool_name:
|
||||
raise ValueError("Missing tool name")
|
||||
|
||||
session = get_session(session_id)
|
||||
user_id = session.get("user_id", "mcp_user") if session else "mcp_user"
|
||||
|
||||
manager = get_tools_manager(user_id=user_id, session_id=session_id or "default")
|
||||
|
||||
try:
|
||||
result_text = await manager.call_tool(tool_name, arguments)
|
||||
|
||||
return {
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": result_text
|
||||
}
|
||||
]
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error(f"工具调用失败: {tool_name}, 错误: {e}", exc_info=True)
|
||||
return {
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": f"错误: {str(e)}"
|
||||
}
|
||||
],
|
||||
"isError": True
|
||||
}
|
||||
|
||||
|
||||
@router.delete("", summary="终止 MCP 会话")
|
||||
async def delete_mcp_session(
|
||||
mcp_session_id: Optional[str] = Header(None, alias="MCP-Session-Id"),
|
||||
_: Annotated[str, Depends(verify_apikey)] = None
|
||||
) -> JSONResponse:
|
||||
"""
|
||||
终止 MCP 会话(可选实现)
|
||||
|
||||
客户端可以主动调用此接口终止会话
|
||||
"""
|
||||
if not mcp_session_id:
|
||||
return JSONResponse(
|
||||
status_code=400,
|
||||
content={"detail": "Missing MCP-Session-Id header"}
|
||||
)
|
||||
|
||||
delete_session(mcp_session_id)
|
||||
return Response(status_code=204)
|
||||
|
||||
|
||||
# ==================== 兼容的 RESTful API 端点 ====================
|
||||
|
||||
@router.get("/tools", summary="列出所有可用工具", response_model=List[Dict[str, Any]])
|
||||
async def list_tools(
|
||||
_: Annotated[str, Depends(verify_apikey)]
|
||||
@@ -72,7 +383,7 @@ async def list_tools(
|
||||
@router.post("/tools/call", summary="调用工具", response_model=schemas.ToolCallResponse)
|
||||
async def call_tool(
|
||||
request: schemas.ToolCallRequest,
|
||||
|
||||
_: Annotated[str, Depends(verify_apikey)] = None
|
||||
) -> Any:
|
||||
"""
|
||||
调用指定的工具
|
||||
|
||||
@@ -4,7 +4,7 @@ from typing import Annotated, Callable, Any, Dict, Optional
|
||||
|
||||
import aiofiles
|
||||
from anyio import Path as AsyncPath
|
||||
from fastapi import APIRouter, Depends, HTTPException, Path, Request, Response
|
||||
from fastapi import APIRouter, Body, Depends, HTTPException, Path, Request, Response
|
||||
from fastapi.responses import PlainTextResponse
|
||||
from fastapi.routing import APIRoute
|
||||
|
||||
@@ -128,9 +128,12 @@ async def get_cookie(
|
||||
@cookie_router.post("/get/{uuid}")
|
||||
async def post_cookie(
|
||||
uuid: Annotated[str, Path(min_length=5, pattern="^[a-zA-Z0-9]+$")],
|
||||
request: schemas.CookiePassword):
|
||||
request: Optional[schemas.CookiePassword] = Body(None)):
|
||||
"""
|
||||
POST 下载加密数据
|
||||
"""
|
||||
data = await load_encrypt_data(uuid)
|
||||
return get_decrypted_cookie_data(uuid, request.password, data["encrypted"])
|
||||
if request is not None:
|
||||
return get_decrypted_cookie_data(uuid, request.password, data["encrypted"])
|
||||
else:
|
||||
return data
|
||||
|
||||
@@ -407,6 +407,8 @@ class ConfigModel(BaseModel):
|
||||
# ==================== Docker配置 ====================
|
||||
# Docker Client API地址
|
||||
DOCKER_CLIENT_API: Optional[str] = "tcp://127.0.0.1:38379"
|
||||
# Playwright浏览器类型,chromium/firefox
|
||||
PLAYWRIGHT_BROWSER_TYPE: str = "chromium"
|
||||
|
||||
# ==================== AI智能体配置 ====================
|
||||
# AI智能体开关
|
||||
|
||||
@@ -10,7 +10,7 @@ from app.utils.http import RequestUtils, cookie_parse
|
||||
|
||||
|
||||
class PlaywrightHelper:
|
||||
def __init__(self, browser_type="chromium"):
|
||||
def __init__(self, browser_type=settings.PLAYWRIGHT_BROWSER_TYPE):
|
||||
self.browser_type = browser_type
|
||||
|
||||
@staticmethod
|
||||
|
||||
@@ -94,6 +94,7 @@ COPY --from=prepare_venv --chmod=777 ${VENV_PATH} ${VENV_PATH}
|
||||
|
||||
# playwright 环境
|
||||
RUN playwright install-deps chromium \
|
||||
&& playwright install-deps firefox \
|
||||
&& apt-get autoremove -y \
|
||||
&& apt-get clean \
|
||||
&& rm -rf \
|
||||
|
||||
@@ -231,9 +231,9 @@ chown moviepilot:moviepilot /etc/hosts /tmp
|
||||
|
||||
# 下载浏览器内核
|
||||
if [[ "$HTTPS_PROXY" =~ ^https?:// ]] || [[ "$HTTPS_PROXY" =~ ^https?:// ]] || [[ "$PROXY_HOST" =~ ^https?:// ]]; then
|
||||
HTTPS_PROXY="${HTTPS_PROXY:-${https_proxy:-$PROXY_HOST}}" gosu moviepilot:moviepilot playwright install chromium
|
||||
HTTPS_PROXY="${HTTPS_PROXY:-${https_proxy:-$PROXY_HOST}}" gosu moviepilot:moviepilot playwright install ${PLAYWRIGHT_BROWSER_TYPE:-chromium}
|
||||
else
|
||||
gosu moviepilot:moviepilot playwright install chromium
|
||||
gosu moviepilot:moviepilot playwright install ${PLAYWRIGHT_BROWSER_TYPE:-chromium}
|
||||
fi
|
||||
|
||||
# 证书管理
|
||||
|
||||
206
docs/mcp-api.md
206
docs/mcp-api.md
@@ -1,9 +1,75 @@
|
||||
# MoviePilot 工具API文档
|
||||
# MoviePilot MCP (Model Context Protocol) API 文档
|
||||
|
||||
MoviePilot的智能体工具已通过HTTP API暴露,可以通过RESTful API调用所有工具。
|
||||
MoviePilot 实现了标准的 **Model Context Protocol (MCP)**,允许 AI 智能体(如 Claude, GPT 等)直接调用 MoviePilot 的功能进行媒体管理、搜索、订阅和下载。
|
||||
|
||||
## API端点
|
||||
## 1. 基础信息
|
||||
|
||||
* **基础路径**: `/api/v1/mcp`
|
||||
* **协议版本**: `2025-11-25, 2025-06-18, 2024-11-05`
|
||||
* **传输协议**: HTTP (JSON-RPC 2.0)
|
||||
* **认证方式**:
|
||||
* Header: `X-API-KEY: <你的API_KEY>`
|
||||
* Query: `?apikey=<你的API_KEY>`
|
||||
|
||||
## 2. 标准 MCP 协议 (JSON-RPC 2.0)
|
||||
|
||||
### 端点
|
||||
**POST** `/api/v1/mcp`
|
||||
|
||||
## 3. 会话管理
|
||||
|
||||
* **会话维持**: 在标准 MCP 流程中,通过 HTTP Header `MCP-Session-Id` 识别会话。
|
||||
* **主动终止**:
|
||||
**DELETE** `/api/v1/mcp` (携带 `MCP-Session-Id` Header)
|
||||
|
||||
---
|
||||
|
||||
## 4. 客户端配置示例
|
||||
|
||||
### Claude Desktop (Anthropic)
|
||||
|
||||
在Claude Desktop的配置文件中添加MoviePilot的MCP服务器配置:
|
||||
|
||||
**macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
|
||||
**Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
|
||||
|
||||
使用请求头方式:
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"moviepilot": {
|
||||
"url": "http://localhost:3001/api/v1/mcp",
|
||||
"headers": {
|
||||
"X-API-KEY": "your_api_key_here"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
或使用查询参数方式:
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"moviepilot": {
|
||||
"url": "http://localhost:3001/api/v1/mcp?apikey=your_api_key_here"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 5. 错误码说明
|
||||
|
||||
| 错误码 | 消息 | 说明 |
|
||||
| :--- | :--- | :--- |
|
||||
| -32700 | Parse error | JSON 格式错误 |
|
||||
| -32600 | Invalid Request | 无效的 JSON-RPC 请求 |
|
||||
| -32601 | Method not found | 方法不存在 |
|
||||
| -32002 | Session not found | 会话不存在或已过期 |
|
||||
| -32003 | Not initialized | 会话未完成初始化流程 |
|
||||
| -32603 | Internal error | 服务器内部错误 |
|
||||
|
||||
## 6. RESTful API
|
||||
所有工具相关的API端点都在 `/api/v1/mcp` 路径下(保持向后兼容)。
|
||||
|
||||
### 1. 列出所有工具
|
||||
@@ -138,141 +204,9 @@ MoviePilot的智能体工具已通过HTTP API暴露,可以通过RESTful API调
|
||||
}
|
||||
```
|
||||
|
||||
## MCP客户端配置
|
||||
|
||||
MoviePilot的MCP工具可以通过HTTP协议在支持MCP的客户端中使用。以下是常见MCP客户端的配置方法:
|
||||
|
||||
### Claude Desktop (Anthropic)
|
||||
|
||||
在Claude Desktop的配置文件中添加MoviePilot的MCP服务器配置:
|
||||
|
||||
**macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
|
||||
**Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"moviepilot": {
|
||||
"command": "npx",
|
||||
"args": [
|
||||
"-y",
|
||||
"@modelcontextprotocol/server-http",
|
||||
"http://localhost:3001/api/v1/mcp"
|
||||
],
|
||||
"env": {
|
||||
"X-API-KEY": "your_api_key_here"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**注意**: 如果MCP HTTP服务器不支持环境变量传递API Key,可以使用查询参数方式:
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"moviepilot": {
|
||||
"command": "npx",
|
||||
"args": [
|
||||
"-y",
|
||||
"@modelcontextprotocol/server-http",
|
||||
"http://localhost:3001/api/v1/mcp?apikey=your_api_key_here"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 其他支持MCP的聊天客户端
|
||||
|
||||
对于其他支持MCP协议的聊天客户端(如其他AI聊天助手、对话机器人等),通常可以通过配置文件或设置界面添加HTTP协议的MCP服务器。配置格式可能因客户端而异,但通常需要以下信息:
|
||||
|
||||
**配置参数**:
|
||||
1. **服务器类型**: HTTP
|
||||
2. **服务器地址**: `http://your-moviepilot-host:3001/api/v1/mcp`
|
||||
3. **认证方式**:
|
||||
- 在HTTP请求头中添加 `X-API-KEY: <your_api_key>`
|
||||
- 或在URL查询参数中添加 `apikey=<your_api_key>`
|
||||
|
||||
**示例配置**(通用格式):
|
||||
|
||||
使用请求头方式:
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"moviepilot": {
|
||||
"url": "http://localhost:3001/api/v1/mcp",
|
||||
"headers": {
|
||||
"X-API-KEY": "your_api_key_here"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
或使用查询参数方式:
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"moviepilot": {
|
||||
"url": "http://localhost:3001/api/v1/mcp?apikey=your_api_key_here"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**支持的端点**:
|
||||
- `GET /tools` - 列出所有工具
|
||||
- `POST /tools/call` - 调用工具
|
||||
- `GET /tools/{tool_name}` - 获取工具详情
|
||||
- `GET /tools/{tool_name}/schema` - 获取工具参数Schema
|
||||
|
||||
配置完成后,您就可以在聊天对话中使用MoviePilot的各种工具,例如:
|
||||
- 添加媒体订阅
|
||||
- 查询下载历史
|
||||
- 搜索媒体资源
|
||||
- 管理媒体服务器
|
||||
- 等等...
|
||||
|
||||
### 获取API Key
|
||||
|
||||
API Key可以在MoviePilot的系统设置中生成和查看。请妥善保管您的API Key,不要泄露给他人。
|
||||
|
||||
## 认证
|
||||
|
||||
所有MCP API端点都需要认证。**仅支持API Key认证方式**:
|
||||
|
||||
- **请求头方式**: 在请求头中添加 `X-API-KEY: <api_key>`
|
||||
- **查询参数方式**: 在URL查询参数中添加 `apikey=<api_key>`
|
||||
|
||||
**获取API Key**: 在MoviePilot系统设置中生成和查看API Key。请妥善保管您的API Key,不要泄露给他人。
|
||||
|
||||
## 错误处理
|
||||
|
||||
API会返回标准的HTTP状态码:
|
||||
|
||||
- `200 OK`: 请求成功
|
||||
- `400 Bad Request`: 请求参数错误
|
||||
- `401 Unauthorized`: 未认证或API Key无效
|
||||
- `404 Not Found`: 工具不存在
|
||||
- `500 Internal Server Error`: 服务器内部错误
|
||||
|
||||
错误响应格式:
|
||||
```json
|
||||
{
|
||||
"detail": "错误描述信息"
|
||||
}
|
||||
```
|
||||
|
||||
## 架构说明
|
||||
|
||||
工具API通过FastAPI端点暴露,使用HTTP协议与客户端通信。所有工具共享相同的实现,确保功能一致性。
|
||||
|
||||
## 注意事项
|
||||
## 7. 注意事项
|
||||
|
||||
1. **用户上下文**: API调用会使用当前认证用户的ID作为工具执行的用户上下文
|
||||
2. **会话隔离**: 每个API请求使用独立的会话ID
|
||||
3. **参数验证**: 工具参数会根据JSON Schema进行验证
|
||||
4. **错误日志**: 所有工具调用错误都会记录到MoviePilot日志系统
|
||||
|
||||
|
||||
Reference in New Issue
Block a user