MCP server exposing the Fotovid media API as first-class agent tools
Strong foundation with 14 well-named tools using consistent verb_noun patterns (watermark_*, trim_*, extract_*, probe_*, get_task). All tools have descriptions (avg 150 chars, within 10-1024 baseline). Input schemas are comprehensive with Zod validation, enums for constrained inputs, and parameter descriptions. However, output schemas are not documented, responses are JSON-stringified but structure is opaque to LLMs. Error handling returns plain text messages without recovery guidance or categorization. No tool annotations (readOnlyHint/destructiveHint) despite clear semantic differences (watermark_* are WRITE, probe_* are READ_ONLY). Async/sync tool pairs are well-designed but lack explicit guidance on when to use each.
Pull the audio track out of a video and return it as an MP3 — a hosted, time-limited, opaque URL; see expires_at.
Submit an audio-extraction job for input too large or long for the sync tool. Returns immediately with a task_id — poll fotovid_get_task for the result, do not wait here.
Check the status of a task submitted by one of the _async tools. While status is starting/processing, outputs/error are absent — wait a couple of seconds and call again. Once status is succeeded, outputs has the result(s); once failed, error explains why (this is normal — not an exception).
Return metadata for a video — width, height, duration, fps, codec, and alpha. Does not produce a file.
Slice an audio file to a start/end window (seconds) and return it as an MP3 — a hosted, time-limited, opaque URL; see expires_at.
Output schemas not documented. Tools return JSON-stringified responses but LLMs cannot infer field structure (e.g., what fields does watermark_video return? Is it {url, expires_at, ...}?). This forces LLMs to guess and parse unstructured text.
No tool annotations (readOnlyHint, destructiveHint, idempotentHint). Async tools return task_id immediately but sync tools block, LLMs cannot distinguish without annotations. Watermark/trim tools modify state but lack destructiveHint.
| Scored | Grade | Overall | Spec posture | Rubric |
|---|---|---|---|---|
| 2026-09-23 | C | 64 | 2026-07-28+ | v2 |
Submit an audio trim job for input too large or long for the sync tool. Returns immediately with a task_id — poll fotovid_get_task for the result, do not wait here.
Cut a frame-accurate clip between two timestamps (seconds). Returns a URL to the trimmed video — hosted, time-limited, opaque; see expires_at.
Submit a video trim job for input too large or long for the sync tool. Returns immediately with a task_id — poll fotovid_get_task for the result, do not wait here.
Capture a frame at a given timestamp as an image thumbnail. Returns a URL — hosted, time-limited, opaque; see expires_at.
Submit a video-thumbnail job for input too large or long for the sync tool. Returns immediately with a task_id — poll fotovid_get_task for the result, do not wait here.
Overlay text or a logo on an image. Returns a URL to the finished image — hosted, time-limited, opaque; see expires_at, and store your own copy.
Submit an image watermark job. Returns immediately with a task_id — poll fotovid_get_task for the result, do not wait here.
Overlay text or a logo on a video (position, opacity, scale, padding). Returns a URL to the finished video — hosted, time-limited, opaque; see expires_at, and store your own copy.
Submit a video watermark job for input too large or long for the sync tool (over ~720p or 15s). Returns immediately with a task_id — poll fotovid_get_task for the result, do not wait here.
Error handling returns plain text ('Fotovid error: {message}') without recovery guidance or error classification. LLMs cannot determine if an error is retryable, user-fixable, or fatal.
Async tool descriptions lack explicit guidance on when to use async vs sync variants. 'Submit a job for input too large' is vague, what size threshold? How long does polling typically take? LLMs need concrete decision criteria.
Parameter constraints (e.g., font_size 8 - 200, scale >0 - 1, quality 1 - 100) are enforced in Zod but not all are reflected in descriptions. LLMs read descriptions, not schema, missing bounds in text descriptions invite invalid inputs.