23 KiB
MoviePilot MCP 接入说明
本文面向通过 MCP 调用 MoviePilot 的外部 Agent 和工具开发者。接入后,客户端通过工具目录发现能力,按 JSON Schema 构造参数,并根据业务结果判断操作是否完成。
**最短调用路径:**配置连接与密钥 → initialize → notifications/initialized → tools/list → tools/call。已有 MCP 客户端通常会自动完成初始化和工具发现,只需配置下表中的连接信息。
订阅下载的提交来源
新产生的订阅下载在订阅活动和日志中区分“首次订阅”“洗版升级”或“全集洗版”。
下载通知的标题和正文按通知模板渲染,不额外拼接来源标签。
该标记来自提交前订阅的已下载事实与最终选集,不改变过滤、优先级、选集和下载策略。
同一整包包含已下载集与缺集时计作一次洗版任务,分别保留 initial_episodes 和
upgrade_episodes;best_version_full 表示此次提交使用的全集洗版模式。
GET /api/v1/subscribe/{id}/activity的counts保留submitted总量,增加initial_submitted、upgrade_submitted、full_upgrade_submitted;最后一项是洗版计数的子集。- 活动详情
details.resources[].subscription_download保存subscription_id、submission_type(initial/upgrade)、best_version_full及两类集数。 活动可从订阅历史继续查看,恢复重试按资源去重,不重复累计成功回执。 GET /api/v1/subscribe/execution/batches及其/{batch_id}详情返回download_counts、downloads和downloads_omitted。分类数量按成功下载任务计数,与搜索任务完成数量独立; 明细最多 100 条,裁剪不影响总量,访问权限沿用整批订阅归属校验。- 下载历史的
note.subscription_download保留同一来源,note.source继续保留原入口信息。 下载处理上下文在持久事件恢复后保留该来源,已冻结的通知保持模板渲染结果。
升级前历史没有来源标记,界面显示“未记录”,不会按当前订阅设置追溯推断。 普通订阅的首次提交与洗版订阅补缺在活动中都标记为首次订阅。
1. 连接与认证
| 配置项 | 值与含义 |
|---|---|
| 服务地址 | https://<MoviePilot 后端地址>/api/v1/mcp;使用实际可达的后端地址,反向代理有前缀时需一并保留 |
| 传输方式 | Streamable HTTP,向同一个地址 POST JSON-RPC 2.0 消息 |
| 认证请求头 | X-API-KEY: <API_TOKEN>,值为该 MoviePilot 实例配置的 API_TOKEN |
| 请求类型 | Content-Type: application/json |
| 接受类型 | 客户端可发送 Accept: application/json, text/event-stream;当前服务端以 JSON 返回调用结果 |
| Schema 模式 | 默认 full;客户端无法处理大型 Schema 时,增加 X-MCP-Schema-Mode: compact,详见参数合同 |
客户端必须能够配置远程 HTTP MCP URL 和自定义请求头。不同客户端的配置文件格式不同,将上述值填入其对应配置项即可。这里的服务地址是 HTTP 入口,不是本地进程启动命令。
认证补充:
- MCP 使用固定 API 密钥,不使用网页登录的 Bearer JWT,也不提供 OAuth 登录流程。
- 无法设置请求头的客户端可使用
.../api/v1/mcp?apikey=<API_TOKEN>。优先使用请求头,避免密钥进入 URL 日志与历史记录。 - 每个请求都需要认证,包括初始化、发现工具和终止会话。密钥校验还要求实例中存在有效的超级管理员账号。
- 这是管理员级集成入口,可以访问站点、配置、数据库及有副作用的操作。只向可信客户端提供密钥,并使用 HTTPS 或受信网络。
- 写操作的用户授权由调用方负责取得;不要假设 MCP 会弹出 MoviePilot 内置 Agent 的确认界面。隐藏工具也不构成权限隔离。
下文的 HTTP 示例使用两个占位环境变量,由接入方在自己的环境中设置。不要把真实密钥写进文档、模型提示词或代码仓库。
MP_MCP_URL='https://moviepilot.example.com/api/v1/mcp'
MP_API_KEY='<该实例的 API_TOKEN>'
2. 完成一次最小调用
2.1 初始化
curl -sS "$MP_MCP_URL" \
-H "X-API-KEY: $MP_API_KEY" \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
--data '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-11-25","capabilities":{},"clientInfo":{"name":"example-agent","version":"1.0.0"}}}'
成功响应位于 result,其中包含 protocolVersion、capabilities、serverInfo 和 instructions。服务端支持 2025-11-25、2025-06-18、2024-11-05;请求版本不在支持列表时返回默认版本 2025-11-25,客户端应检查是否能继续使用该版本。
随后发送初始化完成通知,不携带 id:
{
"jsonrpc": "2.0",
"method": "notifications/initialized",
"params": {}
}
使用与初始化相同的地址和认证头 POST。当前实现返回 HTTP 204,没有 JSON 正文。
2.2 发现工具
curl -sS "$MP_MCP_URL" \
-H "X-API-KEY: $MP_API_KEY" \
-H 'Content-Type: application/json' \
--data '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}'
读取 result.tools。每项包含:
| 字段 | 客户端用途 |
|---|---|
name |
tools/call.params.name 必须使用的精确工具名 |
description |
工具用途、边界与调用提示 |
inputSchema |
本次部署实际提供的 JSON Schema,决定参数名、类型、必填项和可选操作 |
工具目录可能随插件安装、启停或卸载而改变。当前 capabilities.tools.listChanged=false,不会主动推送目录变更通知;重连、插件变更或遇到工具不存在时重新发现。不要将本文中的示例当成完整工具清单。
2.3 调用一个只读工具
下面查询第一页订阅,不创建或修改订阅:
curl -sS "$MP_MCP_URL" \
-H "X-API-KEY: $MP_API_KEY" \
-H 'Content-Type: application/json' \
--data '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"moviepilot_api","arguments":{"operation_id":"subscription.list","query":{"page":1,"count":20}}}}'
MCP 响应的外层结构如下。text 中的内容为示意业务结果:
{
"jsonrpc": "2.0",
"id": 3,
"result": {
"content": [
{
"type": "text",
"text": "{\"collection\":{\"result_count\":0,\"total_count\":0,\"page\":1,\"count\":20},\"success\":true,\"message\":\"\",\"data\":[]}"
}
]
}
}
result 表示协议调用已返回,业务结果在 content 的文本块中。文本可解析为 JSON 时再读取其中的 success、error、data 和执行状态;不要仅凭 HTTP 200 或存在 result 判断业务成功。完整处理规则见结果与恢复。
2.4 传输约定
| 方法 | 行为 |
|---|---|
POST /api/v1/mcp |
每次发送一个 JSON-RPC 对象;不支持批量数组请求 |
JSON-RPC ping |
返回空对象 result: {},可用于检查协议连通性 |
DELETE /api/v1/mcp |
认证后返回 204;当前为无状态模式,无需维护服务端 Session ID |
当前没有供旧式 HTTP+SSE 客户端使用的 GET 事件流或独立消息投递地址,也不提供 resources/list、prompts/list。客户端应选择向同一 URL POST 的 HTTP 传输模式。
3. 获取和使用参数合同
完整模式:按 operation / action 选择分支
默认 tools/list 返回完整 Schema。四个主要入口使用 oneOf 表达不同操作的独立参数合同:
- 根据任务选择工具。
moviepilot_api按properties.operation_id.const选择分支;其余三个入口按properties.action.const选择分支。- 根据该分支的
required、properties、enum、默认值和跨字段规则构造参数。分支中的$ref应在整份 Schema 的$defs中解析。 - 只传该操作声明的字段;JSON 字符串、数字、布尔值、数组、对象和
null必须保持正确类型。 - 若已获得完整参数合同且目标明确,直接调用。无需为每次操作额外执行能力探测,也不要用缺参请求试探写操作。
外部 MCP 客户端不需要访问 MoviePilot 本地文件,也不会自动获得内置 Agent 的 Skill 内容。工具说明中提到的 Skill 可作为补充阅读;接入所需参数以当前部署返回的 Schema 为准。
紧凑模式:为受 Schema 体积限制的客户端使用
在 MCP 请求头中增加:
X-MCP-Schema-Mode: compact
该模式只改变工具发现时的 Schema 展示:超过客户端压缩阈值的大型 Schema 会去掉组合分支和定义表,保留顶层对象、必填字段及 operation/action 枚举。发生投影时会出现 x-moviepilot-schema-projection 标记。
紧凑模式不包含每个操作的完整参数,也不承诺 Schema 一定小于客户端体积限制。 服务端实际参数校验、身份与权限规则不变。调用前通过以下 HTTP 辅助端点取得完整 Schema,或复用已缓存且与当前部署一致的合同:
curl -sS "$MP_MCP_URL/tools/moviepilot_api/schema" \
-H "X-API-KEY: $MP_API_KEY"
这个辅助端点属于 REST,完整 Schema 位于响应的 data。如果客户端侧有 jq,可只保留所需分支及其依赖定义,减少交给模型的内容:
curl -sS "$MP_MCP_URL/tools/moviepilot_api/schema" \
-H "X-API-KEY: $MP_API_KEY" \
| jq --arg op 'media.detail' '.data | .oneOf |= map(select(.properties.operation_id.const == $op))'
上例假设 MP_MCP_URL 不包含查询参数;使用 URL 密钥认证时,需先构造完整路径,再附加 ?apikey=...。不要向 tools/call 提交 Schema 或把 $defs 当成业务参数。
4. 选择工具
| 任务 | 主要入口 | 选择依据 |
|---|---|---|
| 媒体搜索、识别、订阅、站点资源搜索、下载、整理、历史、站点、插件、配置、调度、工作流 | moviepilot_api |
MoviePilot 自身业务流程的首选入口 |
| 下载器任务、文件选择、队列、标签、限速、Tracker、校验 | downloader_operation |
直接操作已配置下载器的原生能力 |
| 媒体库浏览、播放会话、近期入库、扫描和元数据刷新 | mediaserver_operation |
直接操作已配置媒体服务器的原生能力 |
| 数据库表、实时字段、SQL 统计与明确授权的数据修复 | database_operation |
业务 API 无法满足时使用;普通配置修改优先走业务 API |
| 插件或其它运行时扩展能力 | tools/list 中对应工具 |
按返回的精确名称、说明和 Schema 调用 |
不要由此推断任意 REST 路由、第三方 API 或本地命令都能通过 MCP 调用。目录未提供的能力应明确说明不可用。execute_command、文件读写、read_skill 等内部工具不向 MCP 开放。
4.1 moviepilot_api:产品业务操作
工具参数最多使用以下四个顶层字段,具体是否允许或必填由所选分支决定:
| 字段 | 含义 |
|---|---|
operation_id |
精确的白名单操作标识,例如 media.search、subscription.list |
path_params |
资源标识等路径参数 |
query |
筛选、分页等查询参数 |
body |
该操作声明的 JSON 请求体 |
以下示例均为 tools/call 的 params,发送时仍需外包 jsonrpc、id、method。示例标识必须替换为前序查询返回的真实标识。
搜索媒体:
{
"name": "moviepilot_api",
"arguments": {
"operation_id": "media.search",
"query": {"title": "盗梦空间", "media_source": ["tmdb"], "page": 1, "count": 5}
}
}
按来源和媒体标识读取详情:
{
"name": "moviepilot_api",
"arguments": {
"operation_id": "media.detail",
"path_params": {"media_id": "27205"},
"query": {"media_source": "tmdb", "type_name": "电影"}
}
}
构造业务调用时遵守以下规则:
- 不传
url、method、请求头或认证凭据;固定 operation 由服务端映射到业务接口。 - 不将
media_id等业务字段平铺到顶层,也不为无请求体的操作补body: {}。 body使用原生 JSON 对象或数组;只有合同允许时才传null。当前字符串请求体仅用于system.upgrade.dev的固定值"dev"。- 媒体标识必须与返回它的
media_source配对。不同操作的类型字段与枚举可能不同,不要把movie、电影或type、mtype、type_name相互套用。 - 下载、整理、订阅等操作优先复用前序查询返回的媒体上下文、种子上下文、存储项目和精确 ID,不根据标题猜测资源身份或文件路径。
业务操作的完整分类参考见 MoviePilot API Skill 和其 API 分类目录。运行时 Schema 才是目标实例的可调用范围。
4.2 downloader_operation / mediaserver_operation:原生服务操作
下载器示例:
{
"name": "downloader_operation",
"arguments": {
"client": "main-qb",
"action": "tasks.list",
"arguments": {"status": "downloading", "offset": 0, "limit": 20}
}
}
媒体服务器示例:
{
"name": "mediaserver_operation",
"arguments": {
"server": "living-room",
"action": "activity.latest",
"arguments": {"limit": 20}
}
}
注意这里有两层 arguments:外层是 MCP 工具输入,内层是所选 action 的参数。
client/server是 MoviePilot 中已配置实例的精确名称,不是地址、类型或凭据。- 已知实例名称时直接传入;未知时可省略。下载器使用默认或唯一启用实例,媒体服务器使用唯一启用实例。存在歧义时结果会列出有效名称,按用户目标选择后再调用。
- 若需要主动查看实例,使用
action: "instances.list";查看一个动作的能力与参数时使用action: "capabilities.list",内层参数为{"action_name":"<精确动作>"}。 - 动作是否可用取决于实际提供方。下载器任务 ID、媒体服务器项目 ID 与 MoviePilot 业务记录 ID 不可互换。
- 原生服务操作的结果只证明该服务的状态;例如下载器提交成功不等于 MoviePilot 已完成整理或入库。
Emby、Jellyfin、Plex、Navidrome 的目录、条目、搜索、季集和活动查询支持内层可选 query,例如
{"parent":"movies","limit":50,"query":{"fields":["identity","path","year"]}}。
fields 省略或为 null 时保留原内容;空数组只取当前操作必需的信息。输入 schema 从
MediaServerQueryOptions 推导,内容组与服务端能力边界见 媒体查询内容契约。
调用方不能据此假定每种私有协议都支持完整的远端字段裁剪。
4.3 database_operation:直接数据库访问
先发现真实表名和字段,再构造 SQL:
{
"name": "database_operation",
"arguments": {"action": "tables", "arguments": {}}
}
action |
内层 arguments |
|---|---|
tables |
空对象,返回实际表清单 |
schema |
table_name,必须使用表清单中的精确名称 |
query |
sql 与 file 二选一;limit 默认 100,范围 1–200;默认只读,write 默认 false |
write |
sql 与 file 二选一;执行单条写入或结构变更语句,必须已有对应用户授权 |
file 指 MoviePilot 运行环境中的 SQL 文件,不是客户端本地文件;外部接入通常直接传 sql。工具自行读取 MoviePilot 数据库配置,不接受任意连接串或数据库密码。
读取行时使用必要字段和有限窗口;写入前核对目标与条件,完成后只读核验。普通设置修改使用 config.system.*,避免绕过配置校验、事件通知和专用业务流程。更多约束见数据库操作参考。
5. 组合业务调用
以下是调用顺序指引。具体参数仍按各 operation 的 Schema 构造,不将顺序中的操作全部默认执行。
| 用户目标 | 调用路径与完成依据 |
|---|---|
| 查找影片并查看详情 | media.search → 用户目标对应的精确媒体身份 → media.detail |
| 检查库存后订阅 | 确认媒体身份 → library.exists / subscription.find → 按已授权意图调用 subscription.add → 查询订阅核验 |
| 搜索并下载站点资源 | search.torrents 或 search.title → 从结果选择种子上下文 → download.add → 查询下载任务;下载和整理状态另行核验 |
| 搜索已有订阅 | 读取订阅 → subscription.search → 按返回批次标识调用 subscription.execution.get;不要把触发搜索当成下载完成 |
| 修改系统设置 | config.system.list 发现精确键 → config.system.describe 读取合同、当前值与 revision → config.system.update → 重新读取核验 |
| 操作插件能力 | plugin.installed / plugin.capabilities 或当前工具目录 → 明确插件实例 → 对应业务 operation 或动态工具 → 查询结果 |
补充规则:
- 设置更新优先回传
expected_revision。发生冲突时重新读取并比较,不直接覆盖;generic_write_allowed=false时使用返回的专用操作。 - 插件存在分身时,显式选择目标实例或使用已经配置的默认目标。遇到实例歧义、停用目标或插件门禁拒绝时,按错误处理,不猜测另一个实例。
- 参数省略、空字符串与
null的含义由具体操作决定。例如更新订阅时,省略字段通常保留原值,允许清空的字段可使用空值;不要把整份读取结果不加筛选地回写。 search.torrents可能执行多轮站点搜索,工具等待上限为 300 秒;客户端应为此设置足够的超时。分页只限制返回结果,不会缩短搜索过程。
6. 处理结果、分页与失败恢复
分层判断成功
客户端依次检查三层结果:
- **HTTP 与 JSON-RPC:**检查状态码及顶层
error。认证或协议失败时不进入业务结果解析;通知成功的204没有正文。 - **MCP 工具:**检查
result.isError,读取result.content中的文本块。当前工具返回文本,不能假定存在structuredContent。 - **业务与执行状态:**文本为 JSON 时检查
success: false、error、execution_outcome及具体业务状态。即使isError缺失,文本内仍可能是业务失败。
execution_outcome: "pending" 表示已提交但尚未确认最终完成;"unknown" 表示效果未知,先用只读操作核验实际状态。没有该字段时,依据具体业务响应判断,不能自动推断异步任务完成。
分页与截断
- 按选定操作的合同使用分页字段。例如
subscription.list使用page/count,subscription.execution.list使用limit,原生服务列表常用offset/limit。 - 支持兼容分页的列表,两项均省略时仍可能返回全量;显式传
page或count才开启分页,缺失值分别按 1 和 50 处理,count最大 200。 moviepilot_api的collection是附加元数据,不替换data。result_count是本次返回数量,total_count仅在服务端知道精确总数时出现;缺失不等于零。- 只求总数时,对提供精确总数的操作请求最小窗口,例如
page=1,count=1,读取collection.total_count。不要将当前页长度当成总数。 - 工具结果可能被截断。看到
tool_result_truncated: true时,content_preview只是预览;按回执提示缩小查询、分页,或在提供结果引用与读取工具时继续读取。不要因预览不完整而重新执行写操作。
常见失败
| 信号 | 处理方式 |
|---|---|
HTTP 401 |
检查地址对应实例、API_TOKEN、请求头是否被代理保留,以及超级管理员账号是否有效 |
HTTP 503 |
认证等运行时服务可能尚未初始化;先检查实例就绪状态 |
JSON-RPC -32700 |
请求正文不是有效 JSON |
JSON-RPC -32600 |
请求不是 JSON-RPC 2.0 对象,或错误地给 initialized 通知添加了 id |
JSON-RPC -32601 |
方法未实现;检查是否误用旧 SSE、resources 或 prompts 接口 |
JSON-RPC -32602 |
请求参数不符合协议入口要求,按错误详情修正 |
JSON-RPC -32603 |
服务端内部错误,保留请求 ID 与脱敏错误信息排查 |
业务 invalid_input |
按返回的 input_contract 一次修正缺失、位置、类型或未声明字段;不要添加猜测参数 |
| 工具或 operation 不存在 | 重新读取工具目录和完整 Schema,选择当前部署提供的能力 |
| 权限拒绝或配置修订冲突 | 核对身份、授权范围或当前修订,不能通过 SQL 或另一个接口绕过 |
超时、连接中断、pending / unknown |
对有副作用的操作先查实际状态,避免重复下载、订阅、删除或提交任务 |
网关参数校验失败通常携带 input_contract;更外层的解析失败或其它工具不保证提供该字段,必要时回到完整 Schema。不要把全部失败归结为可直接重试的网络问题。
7. HTTP 辅助端点
不使用 MCP SDK 的程序也可以通过这些端点发现和调用同一套工具。所有端点使用相同 API Key 认证,响应遵循普通 REST 包装 {"success": ..., "message": ..., "data": ...},与 /mcp 的 JSON-RPC 包装不同。
| 方法与路径 | 请求或返回内容 |
|---|---|
GET /api/v1/mcp/tools |
data 为工具数组,包含完整 inputSchema;可选 page / count |
GET /api/v1/mcp/tools/{tool_name} |
data 为单个工具的名称、说明与完整 inputSchema |
GET /api/v1/mcp/tools/{tool_name}/schema |
data 为完整输入 Schema,即使 MCP 使用 compact 也不受影响 |
POST /api/v1/mcp/tools/call |
请求字段为 tool_name、arguments;成功分发时 data.result 为工具结果文本 |
REST 调用示例:
{
"tool_name": "moviepilot_api",
"arguments": {"operation_id": "subscription.list", "query": {"page": 1, "count": 20}}
}
注意 REST 使用 tool_name,MCP tools/call.params 使用 name。REST 外层 success: true 同样不代表内层业务成功,仍需解析 data.result。
8. 参考与维护
- 业务 API 操作参考:按领域查阅业务操作与约束。
- 下载器操作参考、媒体服务器操作参考、数据库操作参考:原生服务与 SQL 操作合同。
本文维护连接方式、发现流程、调用结构和结果处理。字段清单、操作全集、提供方差异由运行时 Schema 及对应参考维护;新增业务能力时不应继续向本文追加 REST 路由表、前端展示契约或版本变更记录。