darktable MCP 服务器:用 AI Agent 通过 stdio 驱动 darktable 原始开发引擎的完整指南
【免费下载链接】darktabledarktable is an open source photography workflow application and raw developer项目地址: https://gitcode.com/GitHub_Trending/da/darktable
导读
本文介绍 darktable 仓库中的darktable-mcp——一个无头(headless)的 Model Context Protocol 服务器,它将 darktable 的 RAW 开发引擎以 JSON-RPC 2.0 协议暴露给 AI Agent。读完本文,你将掌握它的构建与运行方式、--read-only等安全选项的语义、与 Claude Code / Claude Desktop 的接入方法,以及render、image_stats、import_images、export_images等 18 个工具的完整参数与底层实现原理,可以直接把 "渲染这张 RAW 并给我看"、"测量 agx 参数对暗部的影响" 这类自然语言指令交给 Agent 执行。
darktable-mcp是darktable-cli的兄弟命令行程序:它直接链接libdarktable,因此拥有真实的模块自省(introspection)、进程内像素管道(pixelpipe)以及原生的 styles/history 支持——不需要手写 XMP 或注入 SQL。
定位与架构概览
darktable-mcp是一个标准 stdio MCP 服务器,基于 JSON-RPC 2.0 通信。它不是一个 GUI 插件,而是一个独立的后台工作进程,专门让 LLM/AI Agent 以结构化方式使用 darktable 的暗房能力。从源码结构看,它由四层组成(详见 src/mcp/):
src/mcp/ main.c process lifecycle: dt_init boot, --core passthrough, stdio loop mcp_jsonrpc.c/.h transport: framing, JSON-RPC dispatch, error objects mcp_tools.c/.h tool registry + JSON <-> C marshalling dt_bridge.c/.h the ONLY unit that calls libdarktable其中 dt_bridge.c 是唯一调用libdarktable的单元,将所有库 API 调用隔离在其中,因此当 darktable 模块参数版本升级等 API 变化发生时,影响范围被限制在这个文件内。参数始终通过自省接口(get_p/get_introspection_linear)寻址,绝不使用固定的字节偏移,从而保证服务器跨模块版本保持正确(见 dt_bridge.c 附近的设计注释)。
构建
darktable-mcp在默认构建中就会被编译。CMake 选项为USE_MCP,默认ON,可通过-DUSE_MCP=OFF或./build.sh --disable-mcp关闭。它只依赖json-glib-1.0,这本来就是 darktable 的既有依赖,因此无需新增任何第三方库(见 src/mcp/CMakeLists.txt,其中通过pkg_check_modules(JSON_GLIB REQUIRED json-glib-1.0)检查依赖,并链接lib_darktable)。
# 作为普通构建的一部分 ./build.sh # 或者只构建这一个目标 cmake -B build . cmake --build build --target darktable-mcp -j二进制产物位于build/bin/darktable-mcp。
运行
darktable-mcp [--read-only] [--core <darktable core options...>]参数解析规则:--read-only与--core
--read-only是服务器自己的标志,必须放在--core之前。--core之后的所有内容都会被原样转发给dt_init,所以 darktable 的核心选项在这里与darktable/darktable-cli完全一致:--configdir、--cachedir、--library、--conf key=value、-d <domain>等全部可用。
从 main.c 的源码可以看到这一逻辑:main()扫描--core的位置,将其后的参数作为 core 参数数组;同时检测--read-only是否被误放在--core之后(此时会报错退出,因为dt_init不认识这个选项)。此外,main()还会执行一个关键动作:保留真正的 stdout 专用于 JSON-RPC——darktable 自身有时会向 stdout 打印日志,因此服务端用dup2(STDERR_FILENO, STDOUT_FILENO)把 darktable 的日志重定向到 stderr,从而保证 stdout 上只有干净的协议流量(见 main.c)。
默认注入:两种模式
当既不给--library也不给--configdir时,服务器会同时注入两个默认值,因为这两者都意味着"不碰任何东西":
--library :memory:—— 一个一次性(throwaway)目录库,散装文件按需临时导入;--conf write_sidecar_files=never—— 不会在你的文件旁边写出任何.xmp。
从源码看,这个判断基于"两个标志都未出现"(adhoc),且只有当write_sidecar_files=前缀的--conf也未出现时才注入后者(main.c)。
只要命名了其中任何一个,两个默认值都不会被注入。指定--configdir就会使用该目录自己的library.db——这和 darktable 其他任何地方的行为一致,因此把服务器指向一个配置目录,得到的是那个目录的目录库,而不是一个空库。此时你等于选择了持久化:你自己的write_sidecar_files偏好会生效,编辑会写进 sidecar。
--read-only的精确语义
凡是携带stack的渲染都会修改图片(见下文 Develop 部分)。--read-only会拒绝所有会改动目录库的工具——stack渲染、reset_history、apply_style、save_style、import_style、set_rating、set_color_label——同时保留只读操作和普通渲染不受影响:
darktable-mcp --read-only --core --library /path/to/library.db这让你可以把 Agent 指向一个真实目录库而不允许它修改。内存库虽然也能阻止持久化,但代价是失去对你真正想处理的目录库的访问。
--read-only背后还有一些精细的补偿逻辑。darktable 中渲染并非完全没有副作用:当管道第一次运行在 history 为空的图片上时,darktable 会写出plugins/darkroom/workflow自动应用的模块并同步 sidecar。--read-only会把这一点"拿回来"——在渲染期间抑制.xmp,并在请求结束后清除自动应用的 history,让图片在请求结束时恢复为空 history 和它开始时携带的标志(源码中由dt_mcp_pristine_t与_pristine_hold/_pristine_release实现,见 dt_bridge.c)。
它不会撤销两件事,因为这两者都不是编辑:读取 RAW 会填充图片行上的缓存传感器列(raw_black、raw_maximum);加载一张已经有 history 的图片在读取时也会重写这些行及其哈希。
按input.path渲染散装文件仍然可行,因为那次导入是临时性的,在请求返回前会被撤销(见下文 Input 部分),对目录库的净效果为零。唯一残留的是data.db中共享的darktable|format|<ext>标签定义——它不指向任何图片,darktable 自己也从不删除这类行(GUI 中删除图片同样会留下它,对应dt_image_remove(),见 src/common/image.c)。import_images会被拒绝,因为它是用于持久化的。
目录库模式
- 临时(ad-hoc,默认
:memory:)——一次性目录库。仅在既未给--library也未给--configdir时使用。 - 真实目录库——
--core --library /path/to/library.db,或直接用--core --configdir /path/to/dir使用该目录下的library.db。此时目录库类工具能看到已存在的图片、它们的编辑和已保存的样式。
注意:darktable 会在
library.db/data.db上持有 PID 锁,所以目录库模式运行时,GUI 不能正持有那个目录库(或者对副本操作)。
接入 Claude
darktable-mcp是标准 stdio MCP 服务器。建议给它一个专用的 config/cache 目录:服务器会锁住它打开的任何目录库,而拥有自己的目录意味着那永远不会是 GUI 正在持有的那个,两者可以同时运行。下面的例子还给了服务器一个属于自己的目录库——把图片导入进去,目录库工具就能看到它们:
mkdir -p ~/.config/darktable-mcpClaude Code
claude mcp add darktable -- \ /path/to/build/bin/darktable-mcp \ --core --configdir ~/.config/darktable-mcp \ --cachedir ~/.config/darktable-mcp/cache默认作用域是 local(当前项目);加-s user对所有项目生效,或-s project写入共享的.mcp.json。用claude mcp list(或会话内/mcp)检查,用claude mcp remove darktable移除。
Claude Desktop
把同一个服务器加到claude_desktop_config.json(Settings → Developer → Edit Config)并重启应用:
{ "mcpServers": { "darktable": { "command": "/path/to/build/bin/darktable-mcp", "args": ["--core", "--configdir", "/home/you/.config/darktable-mcp", "--cachedir", "/home/you/.config/darktable-mcp/cache"] } } }然后直接用自然语言提问,例如"Using darktable, render this raw and show it"或"measure image_stats with agx target_black at 0.008 vs 0.0008"。要操作真实目录库时,把参数换成--core --library /path/to/library.db(该目录库对应的 GUI 必须已关闭)。
协议
标准 MCP 握手走 stdio,既接受默认的每行一个 JSON 对象(newline-delimited JSON),也接受Content-Length:帧(LSP 风格)。支持的方法:initialize、notifications/initialized、tools/list、tools/call、ping、shutdown。
从 mcp_jsonrpc.c 可以看到协议版本常量:SERVER_NAME为darktable-mcp、SERVER_VERSION为0.1.0、PROTOCOL_VERSION为2025-06-18,同时定义了标准 JSON-RPC 错误码(-32700解析错误、-32600无效请求、-32601方法未找到、-32602无效参数)。帧检测逻辑在读取循环中完成:一旦收到以Content-Length:开头的行,后续消息就切换到帧模式(mcp_jsonrpc.c)。
一次典型的握手:
// → request {"jsonrpc":"2.0","id":1,"method":"initialize","params":{}} // ← reply {"jsonrpc":"2.0","id":1,"result":{"protocolVersion":"2025-06-18", "capabilities":{"tools":{"listChanged":false}}, "serverInfo":{"name":"darktable-mcp","version":"0.1.0"}}}工具总览
服务器通过tools/list暴露 18 个工具,分为五组。所有工具的参数 schema 与描述文本都存放在数据目录的 data/mcp_tools.json 中(安装后位于share/darktable/mcp_tools.json),启动时通过dt_loc_get_datadir()加载(见 main.c)。
自省(Introspection)
| 工具 | 输入 | 输出 |
|---|---|---|
list_modules | – | [{operation, version, have_introspection, doc_url}] |
module_schema | {operation} | 各字段的{name, type, offset, min, max, default}(枚举值会列出)+doc_url |
decode_params | {operation, blob_hex} | {operation, version, fields:{…}}—— 从十六进制op_paramsblob 解出命名值 |
encode_params | {operation, fields:{…}} | {operation, blob_hex}—— 先填充默认值,再应用 fields(枚举接受符号名) |
doc_url是该模块在 darktable 用户手册中的页面链接,可用于获取每个参数含义的文字说明。
从源码看,list_modules遍历darktable.iop链表并调用每个模块的version()与have_introspection标志(dt_bridge.c);module_schema则通过get_introspection()/get_introspection_linear()遍历标量字段,输出类型名(float/double/int/uint/int8/uint8/short/ushort/bool/enum)、字节偏移、范围与默认值(dt_bridge.c)。
编码与校验的关键细节:encode_params中,数值超出字段自省范围时是拒绝而不是截断(clamp)——源码注释明确解释,静默修正的值渲染出来可能"看起来没问题",却会让调用方误以为自己发的数字被采用了。枚举字段既可以传符号名也可以传整数值(dt_bridge.c)。字段范围来自模块源码中的范围注释;没有范围注释的字段取该类型的完整范围(见 src/common/introspection.h 附近),所以实际只拒绝模块真正声明为越界的值。
输入(Input)
render、image_stats和export_images既可以接受imgid也可以接受path,两者含义完全不同:
input | 行为 |
|---|---|
{imgid} | 常规路径。编辑会持久化,XMP sidecar 会随之更新。 |
{path}不在目录库中 | 临时(scratch):文件被导入以便 darktable 开发它,随后图片行再次被删除——包括 sidecar 复制出的 darktable 文件以及所在 film roll。不保留 history、sidecar 或任何东西。磁盘上的文件永远不会被触碰。 |
{path}已在目录库中 | 被拒绝,并指出对应的imgid供你决定。 |
为什么路径必须先变成图片?因为 darktable 无法开发一个裸文件——history、sidecar 和模块默认值都绑定到目录库中的一行记录。所以路径总是要先导入成一张图片。Scratch 导入就是"在不每次让目录库多一行记录的情况下查看文件"的实现方式(源码实现见 dt_bridge.c 的_resolve_input,以及 dt_bridge.c 的_drop_scratch——它按基线删除本次导入创建的行、清掉module_order残留(该表没有外键,不会随图片自动删除)、并在 roll 为空时移除 film roll,同时通过_xmp_mute临时把write_sidecar_files设为never,避免导入同步 sidecar)。
推荐实践:优先使用import_images导入后再按imgid操作。这是 darktable 的正常工作流,是唯一保留编辑的路径,也能让 Agent 的改动在你的目录库中可见。Path 输入是用来"看一眼"文件,不是用来"处理"文件。
开发(Develop)
| 工具 | 输入 | 输出 |
|---|---|---|
render | {input:{path\|imgid}, width?, height?, stack?, disable_tone_mappers?, history_end?} | MCP image content(base64 PNG) |
image_stats | 同render | 每通道{min, max, mean, p1, p50, p99, clip_lo, clip_hi} |
一个stack是{operation, params:{…} \| blob_hex, multi_priority?, enabled?}的数组,叠加在图片的基础管道之上。disable_tone_mappers:true会关闭 darktable 自动应用的任何一个 tone mapper(即plugins/darkroom/workflow所选的那个),让你在 stack 里加的 tone mapper 独占 tone 曲线——并且和 stack 一样,这个开关会写入图片的 history 和 sidecar,比请求活得更久。
stack 条目可以携带before或after命名另一个模块,把它放到管道中的那个位置——这是相对定位,而不是裸的iop_order(一个没人能合理选择的晦涩数字)。get_history会报告每个模块的iop_order,方便你先读取当前排列;注意 history 条目的num是编辑发生的顺序,这是另一回事。源码中,before/after移动会先经过dt_ioppr_check_can_move_before/after_iop()的管道规则校验,与 GUI 中 src/develop/imageop.c 附近的移动限制一致,防止写出一条非法的自定义顺序(dt_bridge.c)。
stack 会被写入图片。对于imgid,它成为图片的 history,XMP sidecar 在库的偏好允许时随之更新——没有一次性副本,所以一次渲染也是提交一次编辑的方式。history_end选择在叠加 stack 之前保留多少现有 history。当你不想这样时,请用--read-only运行,或使用内存库。
history_end在render和image_stats上需要小心:带 stack 时它会重写图片的 history,永久丢弃history_end之后的条目,因为基础状态在写 stack 之前会被截断。不带 stack 时它只选择渲染什么、不改变任何东西——在export_images中它永远只做这件事。从 dt_bridge.c 可以看到,带 stack 时实现先把 history 截断到history_end并重置为-1,然后dt_dev_pop_history_items_ext处理其余部分。
在 scratch 渲染上,stack 仍然塑造你拿到的像素,但没有任何东西能活过这次请求——这正是image_stats可以在不留任何痕迹的情况下用于比较参数的原因。
渲染的底层实现:render并不走dt_imageio_preview(那个辅助函数通过一个仅 GUI 可用的 cairo 包装构建 surface,无 GUI 时会崩溃),而是通过一个内存导出格式模块 + 纯 cairo 渲染成 RGB24 surface,再用cairo_surface_write_to_png_stream编码为 PNG(dt_bridge.c)。image_stats则对渲染结果建立每通道 256 级直方图,计算 min/max/mean、p1/p50/p99 百分位以及 0 和 255 处的裁剪计数(dt_bridge.c)。
width/height 是边界框而非目标尺寸:图片按宽高比适配进width × height内,较小的一边决定结果;省略或传 0 表示该维不限,此时由另一维单独约束但不会放大超过图片自身尺寸;两维都给出则允许放大。render两维都不给时默认1024x1024,image_stats默认512x512(见 data/mcp_tools.json 中对应 schema 描述)。
配置(Configuration)
| 工具 | 输入 | 输出 |
|---|---|---|
list_conf | {prefix?} | 已声明设置[{key, value, type, default, min?, max?, values?}] |
get_conf | {key} | 单个设置,同样的结构 |
配置工具在设计上是只读的。有些设置会改变render和export_images的输出——plugins/darkroom/workflow决定自动应用哪些模块,write_sidecar_files决定编辑是否到达.xmp——所以能读取它们就能解释一些否则看起来很怪的结果。没有set_conf:会话中途改设置会静默地使之前收集的所有结果失效,且没有任何记录说明发生过这件事。请改用--conf key=value重启,让改动成为一次可见的会话边界。源码中list_conf只遍历darktable.conf->x_confgen中已声明的键(darktablerc里还有窗口几何等运行时涂鸦,对调用方没有意义),并用与偏好对话框相同的方式解析枚举的[a][b][c]存储格式(dt_bridge.c)。
目录库(Library / Catalog)
| 工具 | 输入 | 输出 |
|---|---|---|
import_images | {paths[]?, folder?, recursive?} | {images:[{path, status, imgid?, error?}], imported, already, failed} |
list_images | {limit?, film_roll?, folder?, rating?, color?, rejected?} | [{imgid, path, rating, color_labels?, rejected?}] |
list_film_rolls | – | [{film_roll, name, folder, images}] |
get_metadata | {imgid} | 相机与镜头、EXIF(ISO、快门、光圈、焦距、拍摄时间),以及传感器raw黑电平与白点 |
get_history | {imgid} | 按模块解码的编辑栈[{num, operation, version, enabled, iop_order, multi_priority, fields}] |
reset_history | {imgid} | {ok:true}—— 把图片的编辑清回导入状态 |
set_rating | {imgids[], rating?, reject?} | {ok:true} |
set_color_label | {imgids[], color, toggle?} | {ok:true} |
list_styles | {filter?, limit?} | {total, styles:[{name, description}]} |
apply_style | {name, imgid \| imgids[], overwrite?} | {ok:true} |
save_style | {name, description?, imgid} | {ok:true} |
import_style | {path} | {ok:true}(.dtstyle→ styles 数据库) |
export_images | {input \| imgids[], out_path?, out_dir?, format?, quality?, width?, height?, upscale?, high_quality?, …} | {ok, exported, paths[]}—— 写文件并返回落点 |
导入。import_images是文件进入目录库的方式:传paths,或传folder(配合recursive遍历子目录)来导入整组拍摄。重复导入是幂等的——已在库中的图片以already状态返回并带有它已有的imgid,绝不会产生重复。遍历目录时只保留dt_supported_image()能识别的文件,sidecar 和杂散文件被静默跳过;而显式指定的路径总是会被尝试,失败时会返回原因(dt_bridge.c)。
浏览。film roll 是 darktable 中"一个导入的文件夹"的单位,也是摄影师对一组拍摄的称呼,所以list_film_rolls是 Agent 发现过滤依据的方式:film_roll精确匹配一个,folder是子串匹配、可以覆盖包含多个 roll 的父目录。rating是最小值,color和rejected进一步收窄。注意list_images的 SQL 会把 rating 存在flags的低 3 位、DT_IMAGE_REJECTED(8) 单独存放,因此被拒图片仍保留它原有的星级;颜色名必须严格是red, yellow, green, blue, purple之一,否则直接报错而不是返回空列表(dt_bridge.c)。
筛选(Culling)。set_rating和set_color_label接受列表,因为一张一张地评星标色是处理整组拍摄最慢的方式。拒绝(reject)是与星级并列的标志而非星级数值,所以传reject:true而不是数字,被拒的帧保留它原有的星。两个工具都是幂等的:darktable 的 GUI 会在重复评分时把评分关掉——这是键盘操作习惯,没有任何 API 调用方需要它——所以桥接层把这类图片从待处理列表中剔除,保证重复调用无副作用(dt_bridge.c)。
样式(Styles)。一个目录库可能存有数百个样式,完整列表可长达几十 KB,所以list_styles接受filter子串(同时匹配名称和描述,与 darktable 自己的样式列表一致)和limit。total总是统计所有匹配数,因此列表被截断也能一眼看出。
批量(Batch)。apply_style和export_images既接受单数imgid/out_path,也接受复数imgids/out_dir;两者都给时列表优先。批量导出的文件名取自源文件,与darktable-cli指向目录时的行为一致。不同 roll 之间、RAW 与同名 JPEG 之间都可能重名,冲突由 darktable 自己的冲突设置plugins/imageio/storage/disk/overwrite决定:默认取一个空闲的_01名字而不是覆盖已有文件。该设置只决定"导出前磁盘上已存在的文件"怎么办;同一批次内两个源解析到同一目标时总是各取独立名字(否则一张图覆盖另一张的输出就丢图了)。目标目录不存在时会自动创建。批次在第一个写不动的图片处停止,错误信息同时点名已经写在磁盘上的文件和从未尝试过的图片,重试既不会重复也不会跳过。
导出到哪里。命名out_path(单图)或out_dir(多图),或者都不命名让 darktable 决定——与它自己的导出模块完全一致:plugins/imageio/storage/disk/file_directory,默认值是$(FILE_FOLDER)/darktable_exported/$(FILE_NAME)——每个源文件旁边的darktable_exported/文件夹,绝不是服务器的当前工作目录(调用方无从知道那是哪)。回复会列出解析出的paths,所以即使让 darktable 自己选,Agent 也会知道文件去了哪。从源码看,未指定目标时的路径生成通过dt_variables_expand_path展开变量,并应用冲突策略_resolve_conflict(dt_bridge.c)。
跳过目标。把plugins/imageio/storage/disk/overwrite设为 3,已有文件就被保持原样,那张图什么都不写。回复总是携带skipped字段,非零时在skipped_paths中点名这些目标:一次什么都没写成的导出,否则看起来和成功导出毫无区别。只有本来就存在的文件才会被跳过,绝不会跳过同一次调用中另一张图刚刚占用的名字。你自己命名的out_path永远不受该策略约束。
导出不带编辑。与render不同,export_images不接受stack或disable_tone_mappers,请求里带任何一个都会被拒绝:两者都会提交到图片的 history,接受它们就等于写一个 JPEG 的同时悄悄改动它来源的图片。请先编辑(带 stack 的render,或apply_style),再导出。history_end是安全的、可以保留,因为它只选择应用多少现有 history,不改变任何东西。导出从未开发过的图片仍会物化 darktable 自动应用的 history,与render完全一样(见下文"注意事项");--read-only会把它拿回来。省略width/height时按全分辨率导出,与 darktable 自己的导出一致。
格式。format选择输出模块——jpeg、png、tiff、webp、jxl、avif、exr、pfm、ppm、j2k,以你构建中可用的为准(jpg和tif作为别名接受)。省略时由out_path的扩展名决定;再无其他依据时,使用 darktable 自己的导出格式设置plugins/lighttable/export/format_name(出厂为jpeg)。quality适用于有损格式;格式提供的其他一切来自它自己的plugins/imageio/format/…设置,可用list_conf读取。upscale允许导出大于源图,high_quality选择更慢但重采样质量更高的路径。源码中_write_export通过dt_imageio_export_with_flags驱动真实格式模块,quality 临时写入模块自己的 conf 键并在取参后恢复(dt_bridge.c)。
get_metadata的raw块由rawprepare填充,所以只有图片至少走过一次管道才会出现——需要黑电平和白点就先渲染一次。
完整示例
测量 RAW 上某个agx参数的暗部下限效果,同时关闭默认 tone mapper:
{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{ "name":"image_stats", "arguments":{ "input":{"path":"/photos/DSCF0001.RAF"}, "width":512,"height":512, "disable_tone_mappers":true, "stack":[{"operation":"agx", "params":{"curve_target_display_black_ratio":0.0008}, "enabled":true}]}}}结果的 text 是 JSON,形如{"width":512,"height":341,"channels":{"g":{"p1":6,"p50":71,…},…}}。
把这个例子的disable_tone_mappers去掉、把stack里换成target_black: 0.008,再对比两次的p1/clip_lo,就能定量描述参数对暗部阴影的影响——这正是image_stats设计要解决的"无痕参数比较"场景。
自定义工具描述与 Schema
每个工具的表现层——name、description、inputSchema——都存放在 darktable 数据目录的 data/mcp_tools.json 中(安装到share/darktable/mcp_tools.json,与noiseprofiles.json等同目录),启动时通过dt_loc_get_datadir()加载。只有工具行为(handler)被编译进二进制,按name与元数据条目匹配。
因此你可以改写某个 description(用来引导模型选择哪个工具)或收紧inputSchema,然后重启服务器——无需重新编译。真正新增工具仍需要在 src/mcp/mcp_tools.c 里写一个 C handler。一个name没有对应 handler 的 JSON 条目会被忽略并在 stderr 上给出警告。
注意事项与限制
- 无头渲染走内存导出格式模块 + 纯 cairo,而非
dt_imageio_preview(那个辅助函数通过仅 GUI 可用的 cairo 包装构建 surface,无 GUI 时会崩溃)。 - 首次渲染一个 RAW 要跑 demosaic 加完整管道,可能需要几秒;给客户端留充足的超时。
- 版本升级:
decode_params目前要求 blob 与模块当前的参数大小一致。把旧版本 blob 先喂给dt_iop_legacy_params是计划中的补充。 imgid输入的编辑会持久化。一个stack会被提交到图片的 history,所以探索变体的 Agent 会把最后一个变体留在图上,且没有撤销。reset_history可以清空一张图;--read-only或内存库可以阻止写入。Scratch 渲染(input.path)按设计什么都不保留。- 首次渲染会物化自动应用的 history。任何
render、image_stats或export_images之后,一张空 history 的图片会带着十几个条目回来(请求中没有stack时也一样):darktable 第一次在该图上运行管道时会写出plugins/darkroom/workflow自动应用的模块并同步 sidecar。darktable-cli的行为完全相同,所以这是 darktable 自己的默认渲染被记录下来,而不是服务器做的编辑。需要让图片保持原样就用--read-only:它会压住.xmp并在请求返回前再次清掉那段 history。 - 请求中途崩溃可能遗留一个 scratch 行,因为清理在请求结束时才运行。它会以一张意外出现的图片显示在目录库里。
- 不包含:驱动一个在运行/打开的 darktable GUI(这是一个后台 worker)。
相关源码参考
- 服务器入口与生命周期:src/mcp/main.c
- 唯一的 libdarktable 桥接层(导入、渲染、统计、目录库、导出):src/mcp/dt_bridge.c、src/mcp/dt_bridge.h
- JSON-RPC 传输层:src/mcp/mcp_jsonrpc.c、src/mcp/mcp_jsonrpc.h
- 工具注册与 JSON↔C 编组:src/mcp/mcp_tools.c、src/mcp/mcp_tools.h
- 工具描述与 Schema 数据:data/mcp_tools.json
- 构建集成与依赖声明:src/mcp/CMakeLists.txt
- 相关底层实现:图片删除行语义 src/common/image.c、管道移动限制 src/develop/imageop.c、自省字段范围约定 src/common/introspection.h
【免费下载链接】darktabledarktable is an open source photography workflow application and raw developer项目地址: https://gitcode.com/GitHub_Trending/da/darktable
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考