capture-one-mcp
This server lets you read and control a running Capture One Pro session or catalog on macOS over MCP, covering navigation, metadata, adjustments, layers, output, and a non-destructive candidate editing workflow.
Health & context: check Capture One status, open documents, frontmost document, busy state, and write-tier enablement.
Document & collection navigation: list open sessions/catalogs, get document settings, list/focus collections, list variants with filtering (rating, color tags, pick, filename, extension) and pagination.
Variant & metadata inspection: get full variant details including EXIF, adjustments, crop, metadata, keywords, layers, and output files; list keywords, styles, recipes, adjustment properties, layer properties, and processing queue status.
Selection & view state: change the selected variants to stage sets for the user or subsequent selection-scoped calls.
Editing (when write access is enabled): set ratings/color tags/pick flags, edit IPTC metadata, apply/remove keywords, adjust any of 83 adjustment properties on variants or individual layers, create/configure layers, edit/copy masks, set luma ranges, apply styles to layers, and create AI people masks.
Output & processing: configure recipes, process variants to disk (with queue polling), export originals, import images, and batch rename originals.
Candidate editing workflow: clone variants into editable candidates, render preview JPEGs, compare variants for readable differences, then keep either the source or candidate.
Safety & control: document binding prevents editing the wrong document, destructive operations require extra permission, and all editing tools report exactly which variants changed or failed.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@capture-one-mcp show me the layers and masks on the currently selected image"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
capture-one-mcp
An MCP server that drives Capture One Pro on macOS through its AppleScript/JXA scripting interface — sessions and catalogs, collections, variants, adjustments, layers, recipes and output.
Status: complete through Phase 5 — 43 tools. Reading and navigation, ratings and metadata, the full adjustment surface, layers and masks including AI people masking, and output: recipes, processing with queue polling, import and batch rename, plus a non-destructive candidate/preview/compare workflow. Tethered capture is deliberately out of scope — see docs/TOOL-SURFACE.md.
Findings from verifying against the live app: adjustments · layers and masks · output, import and rename
Target
Built against the dictionary of the installed app:
App | Capture One 16.8.5.30 |
Bundle id |
|
Dictionary | |
Digest |
Regenerate the digest against whatever build is installed:
python3 scripts/gen-dictionary.py > docs/DICTIONARY.mdRelated MCP server: Image Processor MCP Server
Install
npm install && npm run buildRegister it with an MCP client, e.g. Claude Code:
claude mcp add capture-one -- node /absolute/path/to/capture-one-mcp/dist/index.jsVerify it end to end against whatever Capture One has open:
npm test # build + mocked regression tests; never controls Capture One
npm run verify # typecheck + validate every embedded JXA block parses
npm run verify:live # resolve every generated property name against the running app
npm run smoke # drive the built server over stdio as a real MCP clientverify:live needs Capture One running with a document open. It exists because
a wrong JXA property name fails as "Can't convert types.", which this
codebase's defensive wrappers turn into a silent null — see
docs/OUTPUT.md.
Tools
These tools are always available; none of them modifies image data.
Tool | What it does |
| Running state, whether Apple Events are actually getting through, version/tier, open document, and |
| Open sessions and catalogs, and which is frontmost |
| Paths, session folders, naming tokens, counters, queue depth |
| Albums, projects, groups, smart albums, folders, favorites |
| Focus a collection in the browser |
| Find images — filter by rating, color tag, pick, filename, extension; paginated. |
| Full detail for one variant: EXIF, adjustments, crop, metadata, keywords, layers, outputs |
| Output recipes and their full settings |
| The document's import destination and naming, read without importing |
| Available style and preset names |
| Keywords in the current document |
| The 83 adjustment properties with types and measured ranges |
| Layers on variants, with luma-range settings and all registered scalar adjustments |
| The 40 writable recipe properties |
| Depth of the processing queue |
| Files previously written, and whether they still exist |
| Readable differences between a source variant and a candidate clone |
| Change the Capture One selection (view state only) |
co_open_document is in the write tier rather than the read tier: opening an
older document can trigger an irreversible format migration, and it changes
which document later variant ids resolve against. With document binding on (see
Binding to a document), an unbound auto server binds
to the document it opens — never to whatever was frontmost before — and a bound
server (auto or a fixed id) refuses to open any other document, with
WRONG_DOCUMENT.
co_get_variant reads all registered scalar adjustments on the variant and its
layers; co_list_layers reads those same settings on each layer. Adjustment keys
remain camelCase (for example, levelMidtoneRgb); map these through
co_list_adjustments to the snake_case parameters accepted by the write tools.
Unavailable values are null, with details in the adjacent adjustmentReadErrors
object. Check these errors before treating a snapshot as complete, and never
restore null values. These reads cover the scalar registry, not masks or structured
adjustments such as curves/color-editor settings; they cannot establish full style
or image equivalence. Reads are sequential, so avoid editing during a snapshot.
Editing tools, hidden unless CAPTURE_ONE_MCP_ALLOW_WRITE=1:
Tool | What it does |
| Rating (0–5), color tag, pick flag |
| IPTC fields — headline, description, creator, copyright, rights, title, instructions, city/state/country |
| Apply an existing keyword (see limitation below) |
| Remove a keyword from variants (unverified — see below) |
| Any of the 83 adjustment properties, range-validated and read back |
| The same, applied to a single layer |
| Create adjustment layers |
| Name, enabled, opacity |
| Invert, fill, feather, refine — not verifiable, see below |
| Copy a mask between layers |
| Layer mask luma range |
| Apply a named style to a layer |
| AI people masking |
| Recipe settings, validated and read back |
| Render to disk, polling the queue and reporting the files written |
| Copy originals out |
| Import image files (paths validated; |
| Clone variants into editable candidates, preserving existing edits |
| Render one variant to a bounded sRGB JPEG returned as an MCP image; optionally also saved to |
| Open a session or catalog by path |
co_process_variants checks the effective recipe before submitting work. Recipes
with existing_files: overwrite require CAPTURE_ONE_MCP_ALLOW_DESTRUCTIVE=1
as well, regardless of destination: custom paths and folder aliases can also
reach originals. add suffix and skip recipes need ordinary write access only.
Unreadable or unknown existing-files behavior is refused. When the recipe is
omitted, the current recipe is resolved and explicitly submitted so another
enabled recipe cannot bypass the check.
Requiring CAPTURE_ONE_MCP_ALLOW_DESTRUCTIVE=1 as well:
Tool | What it does |
| Clear, fill or rasterize a mask — not undoable |
| Delete a layer (never the background layer) |
| Rename original files on disk — not undoable |
Editing tools never apply to the whole document implicitly: each requires either
explicit variant_ids or use_current_selection: true, reports every id it
could not resolve rather than silently doing less, and returns exactly what it
changed.
Each call touches at most CAPTURE_ONE_MCP_MAX_TARGETS variants (default 500),
counting the current selection too; a larger request is refused before anything
is written. Tools that write variant by variant (co_set_flags,
co_set_metadata, co_remove_keyword, co_adjust_variants, co_create_layer,
co_create_people_mask) return status: "complete" or, when one variant throws
partway, status: "incomplete" with the entries already written, the failed
variant's error, and unattemptedIds — rather than an error that loses the
record of what changed.
Keywords cannot be created via AppleScript
Capture One 16.8.5 exposes no way to create a keyword. The keyword class
declares no make responder, all three of its properties are read-only, and
apply keyword documents its parameter as "an existing keyword object". Every
construction form was tried against the live app — make at the document, at a
variant, at a keywords element, with properties and with data — and all fail
with "You cannot set or change this property of this object" or "AppleEvent
handler failed". Keywords must be added in the Capture One UI, or imported into
a keyword library from a file.
Consequently co_remove_keyword is implemented but has not been verified
against a real keyword — the standing test session has none and none can be
created, so its blast radius (whether deleting a variant's keyword also removes
it from the document) is unconfirmed. The tool reports
documentKeywordSurvives so a caller can check.
Candidate editing workflow
Non-destructive trial edits: clone a variant, edit the clone, look at it, and keep whichever one you prefer. Nothing is overwritten and nothing is deleted.
Step | Tool |
Clone the originals |
|
Edit the candidates |
|
Look at the result |
|
See what changed |
|
Choose | Keep the candidate id, or go back to the source id. Both variants remain. |
All three require the expected document_id and re-check it, so they stay safe
even with document binding off. co_create_candidates is additive and not
idempotent: an incomplete result must be inspected before any retry, or you get
duplicate copies. co_render_preview needs write access — it creates a private
temporary recipe and submits processing — though it never changes image
adjustments.
Comparison is deliberately conservative. It covers the scalar adjustment registry, crop, lens correction, flags, metadata, engine, processing mode, style names and keywords; it cannot see masks, curves, luma ranges or colour-editor structures, and layers are paired by position because they have no id. No differences does not mean identical edits or identical appearance, and the tool says so in its own result.
See the workflow guide, candidate details and preview limits. The workflow passed automated and live tests on Capture One Pro 16.8.5 using five copied RAWs — validation evidence and limits.
Testing
All live testing runs against one disposable session that you nominate, and
client sessions and catalogs are never touched. The harnesses will not choose a
document on their own: set CAPTURE_ONE_MCP_TESTBED_DOCUMENT to the document id
of a session you are willing to have modified, and both scripts/testbed.mjs and
scripts/verify-adjustments.mjs refuse to run against anything else — including
refusing outright when the variable is unset.
export CAPTURE_ONE_MCP_TESTBED_DOCUMENT="/path/to/a scratch session"The reference figures below come from a 138-variant session (69 RAW + 69 JPEG).
Because Phase 2 writes to real variant state, scripts/testbed.mjs records and
restores it. It talks to Capture One directly rather than through the MCP
server, so it keeps working when the server is read-only and cannot be broken by
the code under test, and it is hard-locked to that one session path — it
refuses to run against any other document rather than trusting the caller.
node scripts/testbed.mjs snapshot # ratings, tags, picks, IPTC, keywords, selection,
# all 83 adjustments, layers, and all 9 recipes
node scripts/testbed.mjs diff # what has changed since
node scripts/testbed.mjs restore # put it all back, then re-verifySnapshots land in .testbed/ (gitignored — it contains real image metadata).
Configuration
Variable | Default | Effect |
| unset | Enables the editing tools. Read-only until set. |
| unset | Additionally enables irreversible verbs. Requires the above. |
|
| Cap on variants returned in one call |
|
| Cap on variants one editing call may touch, via |
|
| Fallback Apple Event budget for calls that set none |
| unset |
|
Binding to a document
Variant ids are small per-document integers (641, 3480), and nothing in the
scripting interface ties an id to the document it came from. If the frontmost
document changes between two calls — clicking another Capture One window is
enough — an id read from one document can resolve to a completely different
variant in another, and a write lands there silently.
Three modes:
| Behaviour |
unset | No check. Only safe when exactly one document is ever open. |
| Recommended. Binds to the first document the server actually touches, then refuses any other for the life of the process. |
a document id | Binds to that specific document, whatever is open. |
auto needs no path up front and follows whatever you are working on, while
still converting "silently edited the wrong catalog" into a refusal. The binding
lasts for the life of the process, so reconnect the server when you switch to a
different session or catalog — in Claude Code, /mcp → reconnect.
A document id is the document's own folder path, e.g.
/Volumes/Photos/a session folder. co_status reports the current
binding and warns when it does not match the front document. Note that
co_status itself does not bind — a health check should not claim the server —
and neither do co_list_documents and co_list_styles, which are about the app
rather than one document. co_open_document binds to the document it opened.
Tools above their permitted risk level are not registered at all, so a blocked tool is invisible to the model rather than present-and-failing.
Requirements
macOS with Capture One installed and running
Node 20+
Automation permission: the process hosting this server must be allowed to control Capture One (System Settings → Privacy & Security → Automation)
Some verbs are gated by Capture One's licence tier — the dictionary marks
process as PRO only and autocrop as Cultural Heritage only.
Notes on the Apple Event layer
Two behaviours worth knowing, both verified against the installed app rather than assumed:
Application("Capture One").version()and.running()are answered by JXA from the app bundle without sending an Apple Event. They stay responsive even when the event pipe is completely blocked, so they are useless as a liveness check.app versionis served by Capture One's own handler and is the honest probe — that is whatco_statususes.A pending macOS automation-consent dialog blocks every Apple Event on the machine until someone clicks it. Nothing fails fast; calls just sit until the Apple Event timeout (
-1712).src/jxa/bridge.tsimposes its own wall-clock budget and translates this into an actionable message.Errors carry a code:
NOT_RUNNING,NOT_AUTHORIZED,TIMEOUT,NO_DOCUMENT,WRONG_DOCUMENT,BUSYorSCRIPT_ERROR(co_statusadditionally reportsEVENTS_BLOCKEDwhen the app runs but its probe times out). osascript failures are classified only by the error number ending the error line (-1712,-1743,-600/-609), after the server's own markers, so a collection or path that merely contains such a number is not misclassified. Any failure of a state-changing call — not only a timeout — says it may have partially applied.One Apple Event at a time. The bridge runs a single osascript child at a time, in FIFO order, across the whole process, so concurrent tool calls queue rather than race inside Capture One. A call's timeout starts when its child is spawned, not while it waits its turn.
BUSYafter a state-changing timeout. When a call that changes state (or a read inside an operation that already submitted work) times out, Capture One may still be carrying it out. The server then refuses every further state-changing call with errorBUSY; reads still work.co_statusprobes the app with a fixed 8-secondapp versioncall and, if it answers, clearsbusyand reports what it cleared inbusyCleared. That only proves the app responds again — inspect the affected variants, queue or output files before retrying the call that timed out.
Notes on the variant model
Verified against a real 138-variant session (69 NEF + 69 JPG), not assumed:
variant.namehas no file extension and is not unique. A RAW+JPEG pair produces two variants both namedDSC_1950. Extension is what makes a row identifiable, soco_list_variantsalways returns it. An early version filtered on a suffix ofnameand matched nothing at all.variant.positionis the position within a clone stack, not an index in the collection -- it is1for every unstacked variant. Exposed aspositionInStackso it cannot be mistaken for a browser index.document.pathis the folder containing the document, not the document itself.co_get_documentalso returnsdocumentPathandfolder.image.dimensionsis a point record{x, y}, not a string; it stringifies to[object Object]if treated as one. Returned as{width, height}.
Why bulk reads
Each property access is one Apple Event, so the access pattern dominates everything. Measured on the 138-variant session:
Access pattern | Cost |
| 24ms for all 138 |
fetch images then | 2313ms for the same data |
per-variant hydration | ~215ms each |
So the filter pass reads whole-specifier properties, chaining through
relationships where needed, and stays flat regardless of collection size.
Page hydration uses the same trick while the scope is modest and falls back to
per-variant reads past BULK_HYDRATE_MAX (2000) or when the scope is a plain
array (scope: "selection").
End-to-end result on that session:
Call | Time |
all 138 rows, no extra fields | 280ms |
20 rows + EXIF | 585ms (was 4295ms) |
all 138 rows + EXIF, crop, adjustments, metadata | 3968ms |
Layout
src/index.ts stdio entry point
src/server.ts tool registration + risk gating
src/config.ts risk levels and the env-var gate
src/jxa/bridge.ts osascript transport, timeouts, error translation
src/jxa/health.ts liveness probe that separates "not running" from "blocked"
src/co/ domain layer:
documents, collections, variants — reading and navigation
edit, adjust, layers, process — writing
candidates, preview, comparison — candidate workflow
*-properties.ts, *-ranges.ts — GENERATED registries
scripts/ gen-dictionary.py, gen-adjustments.py, gen-recipes.py,
gen-adjustment-ranges.mjs — generators
check-jxa.mjs, verify-property-names.mjs, smoke.mjs
— guards and smoke test
testbed.mjs, verify-adjustments.mjs — live test harnesses
docs/ DICTIONARY.md (generated digest), ADJUSTMENTS.md, LAYERS.md,
OUTPUT.md, CANDIDATES.md, PREVIEWS.md,
EDIT-PREVIEW-WORKFLOW.md, WORKFLOW-VALIDATION.md,
TOOL-SURFACE.md (historical design doc)
reference/ vendored .sdef from the installed app
CLAUDE.md guidance for agents working on this repo (AGENTS.md points to it)Licence
MIT
Available Tools
18 toolsco_compare_variantsARead-onlyIdempotent
Compare readable settings of source and candidate variants from the same image in the expected document. Reports differences and unavailable fields, plus IDs for keeping the candidate or returning to the source. Mask contents, curves and luma ranges are outside comparison coverage.
Inputs: document_id (required), source_variant_id (required), candidate_variant_id (required).
co_get_documentARead-onlyIdempotent
Detailed settings for the current document: kind (session or catalog), paths, session folders, naming/token formats, counters, current collection and recipe, and processing queue depth.
Inputs: no named parameters.
co_get_recipeARead-onlyIdempotent
Full settings for one output recipe: file format and quality, scaling, output sharpening, destination and naming, metadata inclusion, crop handling and watermark.
Inputs: name (required).
co_get_variantARead-onlyIdempotent
Everything scriptable about one variant: source image and EXIF, all adjustment values, crop, lens correction, IPTC metadata, keywords, layers, and previously written output files.
Inputs: id (required).
co_list_adjustmentsARead-onlyIdempotent
List every writable adjustment property with its type, valid range and any quirks. Ranges were recovered by probing the live app — the scripting dictionary declares none. Call this before co_adjust_variants to get exact parameter names and limits.
Inputs: filter.
co_list_collectionsARead-onlyIdempotent
List the collections in the current document: albums, projects, groups, smart albums, catalog folders and session favorites.
Inputs: include_counts.
co_list_documentsARead-onlyIdempotent
List the Capture One sessions and catalogs that are currently open, and which one is frontmost.
Inputs: no named parameters.
co_list_keywordsBRead-onlyIdempotent
Keywords defined in the current document.
Inputs: limit.
co_list_layersARead-onlyIdempotent
List the layers on variants, with kind, enabled state, opacity and luma-range settings. Layers have no id and are addressed by index; index 0 is always the background layer.
Inputs: variant_ids, use_current_selection.
co_list_recipe_propertiesARead-onlyIdempotent
List every writable recipe property with its type and allowed values, for use with co_configure_recipe.
Inputs: no named parameters.
co_list_recipesARead-onlyIdempotent
List the output recipes in the current document, with format and enabled state.
Inputs: no named parameters.
co_list_stylesARead-onlyIdempotent
Names of the styles and presets available in Capture One. The scripting interface exposes names only, not their contents.
Inputs: no named parameters.
co_list_variantsARead-onlyIdempotent
List variants (the editable instances of images) with filtering and pagination. This is the main way to find images. Filter by rating, color tag, pick flag, filename or extension; request extra field groups only when needed, since those cost a round trip per variant.
Inputs: pick, limit, scope, fields, +7 more.
co_process_queue_statusARead-onlyIdempotent
Current depth of the processing queue, and the jobs still pending.
Inputs: no named parameters.
co_select_variantsADestructiveIdempotent
Change which variants are selected in Capture One. Selection is view state — no image data, metadata or library structure is modified. Useful to stage a set for the user to look at, or to set up a subsequent selection-scoped call.
Inputs: variant_ids (required), mode.
co_set_current_collectionADestructiveIdempotent
Focus a collection in the Capture One browser by name or id. Changes only what is displayed; no image data is touched.
Inputs: name_or_id (required).
co_statusARead-onlyIdempotent
Health and context for Capture One: whether it is running, whether Apple Events are actually getting through, the reported app version, the open document, and which write tiers are enabled. Call this first when anything else times out.
Inputs: no named parameters.
co_variant_output_filesCRead-onlyIdempotent
Files previously written for variants, from Capture One's output history, including whether each file still exists on disk.
Inputs: variant_ids, use_current_selection.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 18 tools
Each tool targets a distinct aspect of Capture One—documents, collections, variants, recipes, styles, keywords, adjustments, layers, selection, processing, and output files. Even the list/get pairs are clearly separated by verb, and no two tools overlap in purpose. The only slight overlap is between co_list_documents and co_status, but they serve different functions (listing all open documents vs. health/status).
All tools share the 'co_' prefix, and most follow a verb_noun pattern (list_*, get_*, set_*, select_*, compare_*). However, three tools—co_status, co_variant_output_files, and co_process_queue_status—are noun phrases without a verb, breaking the consistency slightly. The pattern is still readable and predictable overall.
With 18 tools, the count is on the higher end of the reasonable range (16-25) but not excessive for a complex application like Capture One. It covers a broad range of features without being bloated, and each tool serves a clear purpose within the domain.
The tool set has significant gaps: it references co_configure_recipe and co_adjust_variants but these tools are not present, leaving no way to actually modify recipes or adjustments. Additionally, there are no tools for creating or modifying collections, layers, or applying edits. This incomplete surface will cause agent failures when trying to perform full workflows.
Maintenance
Related MCP Connectors
Screenshots, PDFs, image editing, QR codes, email validation and more through one hosted MCP server.
Hosted MCP server for AI photo editing: nothing to install, no desktop photo editor needed.
MCP Server for Slima - AI Writing IDE for Novel Authors with AI Beta Reader.
The MCP server for Azure DevOps, bringing the power of Azure DevOps directly to your agents.
Related MCP Servers
- AlicenseAqualityDmaintenanceA professional MCP server that lets AI assistants directly control Adobe Photoshop for document creation, layer management, filters, adjustments, shapes, text, and design intelligence like palette generation and font pairing.322MIT
- FlicenseNot gradedqualityDmaintenanceA powerful Model Context Protocol (MCP) server for image processing, designed to empower AI models with advanced image manipulation capabilities.1-
- AlicenseNot gradedqualityCmaintenanceA full-featured secure MCP server for local file system operations, with built-in image processing, OCR and media tools.Apache 2.0
- AlicenseNot gradedqualityDmaintenanceMCP server that enables AI to control Final Cut Pro on macOS, including reading libraries/projects, importing FCPXML, and creating title or storyboard projects.MIT