subtitle-mcp
Server Configuration
Describes the environment variables required to run the server.
| Name | Required | Description | Default |
|---|---|---|---|
| --dir | No | Directory to confine every read and write (the --dir DIR option). Repeatable; relative paths resolve against the first --dir. Without --dir, any path the user account can reach is allowed, so it is strongly recommended to set it. |
Instructions
Guidance the server publishes about itself, which clients place ahead of the tool catalog so the model reads it before choosing anything.
Everyday subtitle work on SRT and WebVTT files (ASS/SSA can be read and converted to SRT/VTT, never written; styling is dropped). Paths are absolute, or relative to the server's allowed folder. Start with subtitle_info to see the format, detected encoding, timing statistics and a problem list. Then: read_cues / search_subtitles to look inside; shift (constant offset, or frames at a frame rate) and resync (two or more anchor points fix offset AND drift; or fps_from/fps_to) to fix timing; fix_common_problems (dry run by default) to repair overlaps, bad durations, ordering, indices, long lines; convert between srt/vtt or to another text encoding; merge_or_split for multi-part files. To translate: chunk_for_translation hands you numbered batches; YOU translate them (the server never calls a translation service) and return everything to apply_translation, which keeps timing and checks that no cue was lost. No tool overwrites a file unless you pass overwrite=true; edits go to a new file next to the original by default.
Capabilities
Features and capabilities supported by this server
Protocol revision2025-11-25
| Capability | Details |
|---|---|
| tools | {
"listChanged": false
} |
| prompts | {
"listChanged": false
} |
| resources | {
"subscribe": false,
"listChanged": false
} |
Tools
Functions exposed to the LLM to take actions
| Name | Description |
|---|---|
| subtitle_infoA | Inspect a subtitle file: format, detected encoding, line endings, cue count, time span, overlaps, gaps, longest line, reading speed (characters per second: min/mean/median/p95/max) and a list of problems with the cue numbers affected. Read-only. Run this first. The encoding is detected (UTF-8/16/32 by byte order mark or structure; Turkish cp1254,
Western cp1252 and Central European iso-8859-2 by letter evidence) and the reason and
confidence are reported. If the text looks garbled, pass the right Args: path: .srt, .vtt, .ass or .ssa file. encoding: Python codec name to force (for example 'cp1254'); default is auto-detect. |
| read_cuesA | Show cues, selected either by number or by time. Cue numbers are 1-based positions in the file (the same numbers every other tool uses). Args: path: subtitle file. start_cue: first cue number (inclusive). Use with end_cue. end_cue: last cue number (inclusive). from_time: show cues that overlap [from_time, to_time). Format 00:01:02,500 or 62.5. to_time: end of the time window (default: end of file). limit: maximum cues to return (default 100, at most 500). encoding: force a text encoding. |
| search_subtitlesA | Find cues whose text contains a phrase (or matches a regular expression), with cue number and timestamps. Matching ignores line breaks inside a cue (they count as a space), so a phrase split over two lines is still found. Markup tags are searched too. Args: path: subtitle file. query: text to find, or a Python regular expression when regex=true. regex: treat query as a regular expression. case_sensitive: default is case-insensitive. limit: maximum matches to show (default 50). encoding: force a text encoding. |
| shiftA | Move cues later (positive) or earlier (negative) by a constant amount, either in milliseconds or in frames at a frame rate (frames=24, fps=29.97 is exact: 29.97 means 30000/1001). Optionally only from a given cue to the end of the file. Fails instead of producing a time before 00:00:00. SRT and WebVTT only; writes a new file. For a subtitle that is early/late by a different amount at the start and the end, use resync instead. Args: path: .srt or .vtt file. offset_ms: milliseconds to add (negative = earlier). Give this OR frames+fps. frames: number of frames to add (negative = earlier); needs fps. fps: frame rate for frames, e.g. 23.976, 24, 25, 29.97. from_cue: 1-based cue number to start shifting from (default: every cue). output: where to write (default: .shifted. next to the input). overwrite: allow replacing an existing output file (default false). encoding: force the input text encoding. output_encoding: encoding of the written file (default utf-8). |
| resyncA | Fix a subtitle that is out of sync by an amount that CHANGES through the film (drift) as well as a constant offset. Give anchor points: a line in the file and the time it should really appear. Two anchors (one near the start, one near the end) define the straight line new = a*old + b exactly; with three or more a least-squares line is fitted. The tool applies it to every start and end time and reports the error left at each anchor. Find anchors with search_subtitles / read_cues: take a distinctive line near the start and one near the end, play the video to find when each is really spoken. Alternatively give fps_from and fps_to (no anchors) when the subtitle was timed for a video at one frame rate and your video runs at another (for example fps_from=25, fps_to=23.976). Args: path: .srt or .vtt file. anchors: list of {"cue": 12, "to": "00:03:10,400"} (cue = cue number, its START is moved) or {"at": "00:03:05,000", "to": "00:03:10,400"} (at = the current wrong time). fps_from: frame rate the subtitle was made for. fps_to: frame rate of the video you have. output: where to write (default: .resynced.). overwrite: allow replacing an existing output file (default false). encoding: force the input text encoding. output_encoding: encoding of the written file (default utf-8). |
| convertA | Convert between SRT and WebVTT, turn an ASS/SSA file into SRT or VTT, or just re-encode a file (same format in and out, for example cp1254 Turkish -> UTF-8). Timing and text are kept. What cannot be carried over is listed in the answer: ASS styling (fonts, colours, positions, effects) is always dropped, WebVTT cue settings and NOTE/STYLE/REGION blocks do not exist in SRT, and blank lines inside a cue are removed because both formats end a cue at a blank line. Args: path: .srt, .vtt, .ass or .ssa file. to_format: 'srt' or 'vtt'. output: where to write (default: .converted.). overwrite: allow replacing an existing output file (default false). encoding: force the input text encoding. output_encoding: encoding of the written file: utf-8 (default), utf-8-sig, utf-16, cp1254, cp1252, iso-8859-9, ... |
| fix_common_problemsA | Repair the usual damage in SRT/WebVTT files. DRY RUN BY DEFAULT: it lists every fix it would make (with cue numbers and before/after times) and writes nothing until you call again with apply=true. Fixes: out-of-order cues are re-sorted; cues with end <= start get a readable duration (never past the next cue); overlapping cues are trimmed to the next cue's start; empty cues and blank lines inside cues are removed; missing/duplicate/wrong SRT index numbers are renumbered; lines longer than max_line_length are re-wrapped (balanced; dialogue dashes keep one speaker per line); the BOM and mixed CR/LF line endings are normalised. With strip_tags=true, HTML/font/WebVTT/ASS tags are removed from the text. Flagged but never changed: reading-speed outliers (more than max_cps characters per second) and cues on screen for over 10 seconds; overlapping cues that start together. Args: path: .srt or .vtt file. apply: false (default) = report only; true = write the repaired file. strip_tags: also remove , , <font ...>, <v ...>, {\an8} etc. (default false). max_line_length: wrap lines longer than this many visible characters (default 42). max_cps: reading-speed flag threshold in characters per second (default 25). output: where to write when apply=true (default: .fixed.). overwrite: allow replacing an existing output file (default false). encoding: force the input text encoding. output_encoding: encoding of the written file (default utf-8). |
| merge_or_splitA | Join two subtitle files or cut one in two. SRT and WebVTT only. mode='merge': the cues of path2 are moved by offset_ms and combined with those of path (result sorted by start time, in the format of the first file). If offset_ms is omitted, the second file is placed right after the last cue of the first (typical for CD1 + CD2 rips). Use offset_ms=0 to overlay two files, such as two languages. mode='split': cut at time Args: path: first (or only) file. mode: 'merge' or 'split'. path2: second file (merge only). offset_ms: milliseconds added to every cue of path2 (merge only; may be negative). at: split time such as 00:45:10,000 (split only). output: merged file / first part (default .merged. or .part1.). output2: second part (split only, default .part2.). overwrite: allow replacing existing output files (default false). encoding: force the input text encoding (applies to both inputs). output_encoding: encoding of the written files (default utf-8). |
| chunk_for_translationA | Step 1 of translating a subtitle. Returns one batch of cues as JSON: each cue has a stable
Rules for the translation: keep every id; keep formatting tags such as and {\an8} exactly; keep line breaks (\n) where they make sense; keep it about as long as the original (subtitles are read against a clock). The response says how many chunks exist; call again with chunk=2, 3, ... until all are done, then send ONE mapping with all ids. Args: path: .srt or .vtt file. max_chars: approximate size of a batch in characters of cue text (default 1500, min 200). chunk: which batch to return, starting at 1. encoding: force the input text encoding. |
| apply_translationA | Step 2 of translating a subtitle: write the translated file. Timing, cue order, WebVTT
identifiers/settings/notes are kept exactly; only the text changes. Every id of the file
must be present in Args: path: the SAME file given to chunk_for_translation. mapping: {"1": "translated text", "2": "..."}; keys are the ids from chunk_for_translation. output: where to write (default: .translated.). overwrite: allow replacing an existing output file (default false). partial: allow a mapping that does not cover every cue. encoding: force the input text encoding. output_encoding: encoding of the written file (default utf-8; use utf-8 for non-Latin target languages). |
Prompts
Interactive templates invoked by user choice
| Name | Description |
|---|---|
No prompts | |
Resources
Contextual data attached and managed by the client
| Name | Description |
|---|---|
No resources | |
TDQS
Scored across 10 tools
Each tool has a clearly distinct purpose: inspection, reading, searching, repair, constant shifting, drift resync, format conversion, merge/split, and the two-step translation workflow. The only potentially overlapping pair, shift and resync, is explicitly differentiated by constant offset versus drift/frame-rate correction, and the descriptions point to each other. search_subtitles and read_cues also target different needs (text matching versus positional/time-based listing).
All names use consistent snake_case, and most are readable verb_noun or verb_object forms such as search_subtitles, fix_common_problems, read_cues, and merge_or_split. However, several tools use single verbs (shift, resync, convert) or a noun-first name (subtitle_info), so the pattern is not uniformly verb_noun.
Ten tools is well within the ideal range for a focused subtitle-processing server. Each tool covers a meaningful, non-redundant operation in the workflow, from inspection and repair through timing adjustment, conversion, merging, and translation.
The surface covers the core subtitle lifecycle: inspect, read, search, repair, shift/resync timing, convert formats, merge/split, and translate text. Minor gaps remain, such as direct cue-level text editing outside the translation flow or explicit cue addition/deletion, but most practical workflows can be handled with the existing tools.