Skip to main content
Glama

shutter-mcp

CI MIT License Python 3.11+

shutter-mcp demo: an agent answering a cull question with the cull_report tool

Point Claude, or any MCP client, at a folder of RAW and JPEG files. It can scan the library, break down which cameras and lenses actually got used, find duplicates, flag the shots that are probably blurry, or write a full cull report. It never gets a tool that can touch a file: read-only means read-only, all the way down.

Why this exists: culling tools that touch your shoots ask for a subscription or your client's files on somebody else's cloud. This asks for neither. It reads the metadata and pixels already sitting on your disk and answers in text, not files, and it is deliberately the read-only first step of a bigger local-first project (see Roadmap; the write-capable half is shutter-cull-mcp).

Tools

Tool

Description

Read/write

scan_library

Recursively count files under a root by extension, size, and mtime range

read-only

library_stats

Aggregate EXIF stats across a library: camera, lens, focal length, ISO, year-month

read-only

read_exif

Flattened EXIF fields for one file

read-only

find_duplicates

Exact (sha256) or visual (perceptual hash) duplicate groups

read-only

blur_scores

Laplacian-variance blur ranking for jpg files

read-only

cull_report

Markdown report combining all of the above

read-only

Every tool is read-only. None of them write, move, rename, or delete a file.

Related MCP server: photo-vlm-mcp

Install

Requires Python 3.11+.

With uv (recommended):

uv tool install shutter-mcp

With pipx:

pipx install shutter-mcp

From source, for development:

git clone https://github.com/keivanmalhani/shutter-mcp.git
cd shutter-mcp
uv venv
uv pip install -e ".[dev]"

Usage

shutter-mcp takes one or more --root directories at launch. Every tool call is validated against this allowlist, so the server can only ever see the folders you explicitly grant it.

shutter-mcp --root /path/to/photos --root /path/to/scans

Claude Desktop

Add to your claude_desktop_config.json:

{
  "mcpServers": {
    "shutter-mcp": {
      "command": "shutter-mcp",
      "args": ["--root", "/Users/you/Pictures/2026-shoot"]
    }
  }
}

Claude Code

claude mcp add shutter-mcp -- shutter-mcp --root /Users/you/Pictures/2026-shoot

Security model

  • Read-only. No tool writes, moves, renames, or deletes user files. cull_report returns markdown text; it never writes a file.

  • Root allowlist. The server only accepts --root directories at launch. Every path argument a tool receives is resolved (Path.resolve, symlinks included) and rejected if it falls outside every allowed root, even if a symlink inside the root points elsewhere.

  • No network calls at runtime. Everything happens on your filesystem.

  • No image bytes returned. Tools return paths and metadata only, never base64 or raw pixel data.

  • No telemetry, no analytics.

Format support

Capability

Extensions

EXIF read (read_exif, library_stats)

jpg, jpeg, tif, tiff, png, dng, arw, raf, nef, cr2, cr3 (best effort on RAW)

Pixel decode (blur_scores, visual mode of find_duplicates)

jpg, jpeg, png, tiff

RAW pixel decode (blur scoring and visual dedup on ARW/RAF/etc.) is out of scope for this MVP; EXIF reading works today on both. See Roadmap.

Development

uv pip install -e ".[dev]"
uv run pytest

Tests generate their own fixture images with Pillow at run time; no binary fixtures are committed to the repo.

Roadmap

  • RAW pixel decode (ARW, RAF, and friends) via rawpy, for blur scoring and visual dedup

  • XMP rating/flag writeback so cull decisions show up in Lightroom Classic

  • Integration as the agent interface for the local-first photo culling engine

  • Publish to PyPI and the MCP registry

Family

shutter-cull is the photo engine and shutter-select the video one. shutter-cull-mcp is the side that does write, with human confirmation and undo. This is the read-only one, and that guarantee is structural rather than a promise in the docs.

License

MIT, see LICENSE.

Available Tools

6 tools
blur_scoresA
Read-onlyIdempotent

Score jpg files under a directory for blur using Laplacian variance.

jpg/jpeg only in the MVP. Lower variance means less high-frequency detail, i.e. more likely blurry. Results are sorted ascending (blurriest first); the bottom decile is flagged likely_blurry.

Args: params (BlurScoresInput): Validated input containing: - root (str): Directory of jpg files to score, must be inside an allowed --root - limit (int): Max files to score, default 500, 1-5000

Returns: dict: { "schema_version": 1, "root": str, "files_considered": int, "files_scored": int, "sampled": bool, "sample_note": str | None, "skipped": int, "results": [ {"path": str, "laplacian_variance": float, "likely_blurry": bool}, ... ], # sorted ascending by laplacian_variance } On error: {"schema_version": 1, "error": str}

Examples: - Use when: "Which jpgs in this folder are probably out of focus?" - Don't use when: the library has RAW files only (RAW pixel decode is out of scope for MVP)

NameRequiredDescriptionDefault
paramsYes

TDQS

A4.6/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already cover readOnly/idempotent/non-destructive, so the description is free to add value elsewhere — and it does: results sorted ascending, bottom decile flagged likely_blurry, sampling when the file count exceeds limit, and the error envelope. It stops short of saying how the flag threshold is computed or whether the walk is recursive, which leaves a little ambiguity about scope of the directory traversal.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded core sentence, then method, then args/returns/examples in a predictable structure. The Args section largely restates the schema and the Returns block is long, but since no output schema exists the return dump earns its place.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given two parameters, no output schema, and read-only annotations, the description supplies everything else an agent needs: return keys including sampled/sample_note/skipped, sort order, the likely_blurry semantics, and the error shape. Nothing material is left for the caller to guess.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is reported at 0%, so the description must compensate, and it does: it explains that root must live inside an allowed --root and that limit defaults to 500 with a 1–5000 range. It doesn't explain what 'allowed --root' concretely means or how the containment check fails, which is the one gap.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb (score), resource (jpg files under a directory) and the method (Laplacian variance), which nothing in the sibling list (scan_library, library_stats, read_exif, find_duplicates, cull_report) duplicates. It also scopes the MVP to jpg/jpeg only, so an agent knows immediately this is the blur-quality pass rather than a general library scan.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The Examples block gives an explicit 'Use when' trigger (which jpgs in this folder are probably out of focus?) and an explicit 'Don't use when' exclusion (RAW-only library, since RAW decode is out of MVP scope). That is precisely the when/when-not guidance an agent needs to route between this and scan_library or read_exif.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

cull_reportA
Read-onlyIdempotent

Build a markdown cull report combining scan, EXIF stats, dupes, and blur.

Runs scan_library, library_stats, find_duplicates (exact mode), and blur_scores under the hood, and combines them into one markdown report: library summary, top EXIF stats, exact duplicate groups, and the blurriest 20 jpg files. Returns markdown text only; never writes a file.

Args: params (CullReportInput): Validated input containing: - root (str): Directory to report on, must be inside an allowed --root

Returns: str: Markdown-formatted report, or "Error: " on failure.

Examples: - Use when: "Give me a summary I can read before I start culling this shoot." - Don't use when: you need machine-readable output (use the individual tools instead)

NameRequiredDescriptionDefault
paramsYes

Output Schema

NameRequiredDescription
resultYes

TDQS

A4.6/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnly/idempotent/non-destructive/openWorld=false, and the description goes further: it discloses the composite execution path (which four tools run under the hood), that the output is markdown text only, that it never writes a file, and that failures surface as 'Error: <message>'. That is exactly the added context the annotation baseline doesn't cover.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The core purpose and the composed-tool list are front-loaded, and the when/when-not pair is efficient. The Args/Returns sections are mild boilerplate that restate the schema, but they cost little and the failure-mode line earns its place.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists, so return shape needs no prose, yet the description still names the report sections (library summary, top EXIF stats, duplicate groups, blurriest 20 jpgs) and the error contract. For a single-parameter, side-effect-free aggregation tool, an agent has everything needed to call it correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

One parameter, and the description's 'root: Directory to report on, must be inside an allowed --root' largely mirrors the schema's own text ('must resolve inside an allowed --root'). It reinforces the sandbox constraint but adds no syntax, path-format, or resolution detail beyond the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and artifact ('build a markdown cull report') and enumerates exactly which inputs it combines (scan, EXIF stats, dupes, blur). It also names the sibling tools it orchestrates, so an agent can immediately tell it apart from scan_library, library_stats, find_duplicates, and blur_scores, which produce the component data.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The Examples block gives an explicit when-to-use ('summary I can read before I start culling this shoot') and an explicit when-not ('you need machine-readable output — use the individual tools instead'), naming the alternative. Nothing about selection is left to inference.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

find_duplicatesA
Read-onlyIdempotent

Find duplicate files under a directory, exact or visually similar.

'exact' mode groups byte-identical files (any file type) by sha256 hash. 'visual' mode groups jpg/png images whose perceptual hash (phash) is within a hamming distance of 5, catching near-duplicate bursts even when bytes differ. Visual mode internally samples up to 3000 newest files for performance on very large libraries. Returns at most 200 duplicate groups; truncated indicates whether more groups exist beyond that cap.

Args: params (FindDuplicatesInput): Validated input containing: - root (str): Directory to scan, must be inside an allowed --root - mode ("exact" | "visual"): Duplicate detection mode, default "exact"

Returns: dict: { "schema_version": 1, "root": str, "mode": str, "files_considered": int, "duplicate_group_count": int, "truncated": bool, "groups": [{"count": int, "files": [str, ...]}, ...], # visual mode additionally includes: files_hashed, sampled, sample_note, truncation_note # exact mode additionally includes: skipped, total_duplicate_groups_found } On error: {"schema_version": 1, "error": str}

Examples: - Use when: "Find exact duplicate files I can safely delete one copy of." - Use when: "Find near-duplicate frames from burst shooting." - Don't use when: you want blur ranking, not duplication (use blur_scores)

NameRequiredDescriptionDefault
paramsYes

TDQS

A4.9/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnly/idempotent/non-destructive, so the description focuses on what annotations cannot express: the 3000-file sampling cap in visual mode, the hamming-distance threshold, and the 200-group output cap with a 'truncated' flag. These are exactly the performance and truncation caveats an agent needs before trusting results on a large library.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the core statement and mode semantics, then structured Args/Returns/Examples blocks. The Args section largely restates the input schema, which is redundant, but the Returns documentation earns its space since no output schema exists.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

No output schema exists, and the description fully specifies the return shape including mode-specific extra fields (files_hashed/sampled vs. skipped/total_duplicate_groups_found) and the error envelope. Nothing an agent needs to interpret a response or call correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

With schema description coverage reported at 0%, the description carries the full burden and does: it explains root as a directory that must resolve inside an allowed --root, and mode's two values with their algorithms, defaults, and file-type scope (any type vs. jpg/png). This meaningfully exceeds the bare enum.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('Find duplicate files under a directory') and immediately names the two detection modes with their defining algorithms (sha256 byte-identity vs. phash hamming distance <= 5). It also explicitly distinguishes itself from the blur-ranking sibling by name, so an agent can route without opening either schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Provides concrete 'Use when' scenarios (safe-to-delete exact copies, near-duplicate burst frames) and an explicit 'Don't use when' that names the alternative tool (blur_scores). When-to-use, when-not, and the alternative are all present.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

library_statsA
Read-onlyIdempotent

Aggregate EXIF metadata across a library: cameras, lenses, ISO, focal length, dates.

Scans EXIF-readable files (jpg, jpeg, tif, tiff, png, dng, arw, raf, nef, cr2, cr3) under root. If more files are found than sample_limit, the newest files by modification time are sampled and this is noted in the result. Files with no readable EXIF are counted in skipped_no_exif.

Args: params (LibraryStatsInput): Validated input containing: - root (str): Directory to aggregate over, must be inside an allowed --root - sample_limit (int): Max files to inspect, default 2000, 1-50000

Returns: dict: { "schema_version": 1, "root": str, "files_considered": int, "files_sampled": int, "sampled": bool, "sample_note": str | None, "skipped_no_exif": int, "counts_by_camera_model": {str: int, ...}, "counts_by_lens": {str: int, ...}, "counts_by_focal_length_bucket": {str: int, ...}, "counts_by_iso_bucket": {str: int, ...}, "counts_by_year_month": {"YYYY-MM": int, ...}, } On error: {"schema_version": 1, "error": str}

Examples: - Use when: "What cameras and lenses did I use most this year?" - Use when: "Break down this shoot by ISO." - Don't use when: you need per-file EXIF (use read_exif)

NameRequiredDescriptionDefault
paramsYes

TDQS

A5/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare read-only, idempotent, non-destructive, and closed-world behavior. The description adds important behavioral context beyond those annotations: it documents sampling by newest modification time when sample_limit is exceeded, counts skipped files with no readable EXIF, enforces that root must be inside an allowed --root, and specifies the error return shape.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is front-loaded with purpose and scanning behavior, then uses structured Args, Returns, and Examples sections. Despite its length, every section adds actionable information, and the return schema detail is warranted because no output schema is present.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The tool has no output schema, but the description provides a detailed return structure, including counts, sampling indicators, and error shape. Combined with explicit parameter documentation and usage examples, it gives an agent everything needed to invoke the tool correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is reported as 0%, so the description carries the full burden for parameter semantics. Its Args section fully documents both nested parameters: root as an allowed-root-constrained directory and sample_limit as a bounded max-file count with default 2000 and 1-50000 range, including the newest-mtime sampling behavior.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific verb and resource: aggregate EXIF metadata across a library for cameras, lenses, ISO, focal length, and dates. It clearly distinguishes this from per-file EXIF access by explicitly directing users to read_exif for that need.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The Examples section gives explicit when-to-use cases and a when-not-to-use case that names the sibling alternative read_exif. This leaves little ambiguity about when library_stats is the right choice.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

read_exifA
Read-onlyIdempotent

Read flattened EXIF fields for a single image file.

Best effort across jpg, jpeg, tif, tiff, png, dng, arw, raf, nef, cr2, cr3. A file with no EXIF data, or an unsupported extension, is not an error: fields comes back empty with a note explaining why.

Args: params (ReadExifInput): Validated input containing: - path (str): Path to one image file, must be inside an allowed --root

Returns: dict: { "schema_version": 1, "path": str, "fields": { "camera_make": str, "camera_model": str, "lens_model": str, "focal_length": str, "focal_length_35mm_equiv": str, "iso": str, "exposure_time": str, "f_number": str, "exposure_program": str, "date_time_original": str, "date_time": str, "width": str, "height": str, "orientation": str, "software": str, "gps_latitude": float, "gps_longitude": float, }, # only keys actually present in the file are included "note": str | None, # e.g. "no EXIF data found" } On error: {"schema_version": 1, "error": str}

Examples: - Use when: "What lens and settings were used for this specific frame?" - Don't use when: you want aggregates across many files (use library_stats)

NameRequiredDescriptionDefault
paramsYes

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnly/idempotent/non-destructive, but the description adds behavior the annotations cannot: best-effort support across a named format list, the crucial non-error degradation path (empty fields plus a note for missing EXIF or unsupported extensions), and the --root sandbox constraint on path. It even documents the error return shape, so failure modes are predictable.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with purpose and graceful-degradation behavior, which is the right ordering. The Args block largely restates the schema and the Returns block is verbose, but since no output schema exists, enumerating the field keys earns its space.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With no output schema and only one input parameter, the description carries the return contract and does so fully: schema_version, path, present-only field keys, GPS as floats, the optional note, and the distinct error envelope. Nothing an agent needs in order to call this correctly or interpret the result is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is reported at 0% (the wrapper `params` object carries no description), so the description must compensate, and it does: it explains that `path` points to exactly one image file and must resolve inside an allowed --root. The nested schema property already says nearly the same thing, so the added value is real but partly duplicative.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ("Read flattened EXIF fields") and bounds the scope to "a single image file," which separates it from the aggregate sibling library_stats. An agent can pick this over the other five siblings without opening any schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Provides an explicit "Use when" phrasing ("What lens and settings were used for this specific frame?") and an explicit "Don't use when" that names the alternative (library_stats) for cross-file aggregates. The routing decision is fully specified rather than inferred.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

scan_libraryA
Read-onlyIdempotent

Recursively scan a directory and summarize what is in it.

Walks root recursively (symlinks are not followed) and reports file counts by extension, total bytes, and the earliest/latest file modification time. Stops after 50000 files and reports truncation if the library is larger than that. Does not read file contents or EXIF; use library_stats for EXIF aggregates.

Args: params (ScanLibraryInput): Validated input containing: - root (str): Directory to scan, must be inside an allowed --root

Returns: dict: { "schema_version": 1, "root": str, "total_files": int, "total_bytes": int, "counts_by_extension": {".jpg": int, ...}, "earliest_mtime": str | None, # ISO 8601 UTC "latest_mtime": str | None, # ISO 8601 UTC "truncated": bool, "truncation_note": str | None, } On error: {"schema_version": 1, "error": str}

Examples: - Use when: "How many files are in my 2026 shoot folder and how big is it?" - Don't use when: you need EXIF camera/lens breakdowns (use library_stats)

NameRequiredDescriptionDefault
paramsYes

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnly/idempotent/non-destructive, so the bar is lower, but the description still adds real substance: symlinks are not followed, the walk halts at 50000 files, truncation is reported in the result, and errors return a schema_version/error dict. That is exactly the kind of behavior an agent cannot infer from structured fields.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with a one-line summary, then behavior, then args/returns/examples in a scannable order. It is somewhat long, but with no output schema the Returns block earns its space; the redundancy of mentioning library_stats twice is the only real waste.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With no output schema and 0% schema description coverage, the description carries the full burden and does so: it documents every returned key including the truncation fields and the error shape. An agent can call this correctly and interpret the result without further information.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

There is only one parameter and the description restates its constraint (must be inside an allowed --root), which is the operative safety boundary for the call. The schema's own field description already carries this, so the description adds reinforcement rather than new meaning; a 4 rather than a 5 for a single trivially-shaped string param.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The first sentence gives a specific verb and resource ("Recursively scan a directory and summarize"), and the body adds scope boundaries: walks recursively, does not follow symlinks, does not read contents or EXIF. It explicitly names the sibling for the EXIF case, so it is distinguishable from library_stats without opening either schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The Examples block states both when to use ("How many files are in my 2026 shoot folder and how big is it?") and when not to use, naming library_stats as the alternative for EXIF aggregates. The body also routes to library_stats a second time, reinforcing the exclusion.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 6 tool updatesv0.1.0
    • First observedblur_scores
    • First observedcull_report
    • First observedfind_duplicates
    • First observedlibrary_stats
    • First observedread_exif
    • First observedscan_library

TDQS

A4.6/5.0

Scored across 6 tools

Disambiguation5/5

Each tool targets a clearly distinct level of analysis: whole-library scanning, EXIF aggregation, single-file EXIF, duplicate detection, blur scoring, and a combined report. Descriptions explicitly cross-reference each other with 'use X instead' notes, leaving no realistic chance of misselection.

Naming Consistency4/5

All names use consistent snake_case, which is the important part. Most are verb_noun or clear noun_phrase (scan_library, read_exif, find_duplicates), though library_stats, blur_scores, and cull_report are noun-first, a minor stylistic deviation.

Tool Count5/5

Six tools is well scoped for a photo-library analysis server. Each tool earns its place, and the composite cull_report cleanly wraps the primitives without duplicating their interfaces.

Completeness4/5

The analysis surface is strong: scanning, metadata aggregation, per-file inspection, deduplication, blur detection, and a report. Minor gaps remain for a culling workflow — no way to flag, move, or delete rejected files, and blur scoring is jpg-only — but these are acknowledged limitations rather than dead ends for the stated read-only purpose.

Maintenance

ActivitySlowing
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • F
    license
    A
    quality
    D
    maintenance
    An MCP server for Immich self-hosted photo management that provides AI-accessible tools for browsing, searching, organizing, and managing photo libraries with duplicate detection and safe deletion workflows.
    43
    1
    -
  • A
    license
    Not graded
    quality
    A
    maintenance
    Local image manager MCP server for Windows, enabling AI assistants to search, preview, tag, favorite, and sync local images while keeping originals read-only.
    MIT
  • A
    license
    B
    quality
    C
    maintenance
    An MCP server that lets LLMs read and edit image metadata (EXIF, GPS, XMP, ICC, IPTC, and more) entirely offline. Supports files, URLs, base64, and includes a browser-based UI for analysis, GPS extraction, and metadata stripping.
    13
    BSD 2-Clause "Simplified"