Koskamo

Lunara MCP

An MCP server for automated subtitle production that accepts audio or video uploads and returns written-language subtitle tracks plus translations with preserved ASR text and timings. It supports tools for uploading media, starting jobs, polling status, and downloading finished SRT files or cue data.

Endpoint and access

No sign-in to connect and list tools. Individual tools may still need a vendor account, credentials, or permissions; check the documented prerequisites.

Lunara MCP logo, by LunaraCommunity listingStreamable HTTPNo sign-inEndpoint checked 2026-11-01Published by Lunara
You need:create a key at https://lunaraai.cc/settings
MCP endpoint (Streamable HTTP)
https://lunaraai.cc/mcp
Vendor documentation

What you can do with it

  • Upload audio or video files for subtitle generation
  • Start subtitle pipelines and poll job status
  • Download finished SRT files or individual cues
  • Retrieve organization glossaries for consistency

Source and maintenance

Community listingRegistry 1.0.1
Maintenance checked

Protocol and capabilities

Server
lunara 1.0.0
MCP protocol
2025-06-18
Tools
20 advertised
Resources
Not advertised
Resource templates
Not advertised
Prompts
Not advertised
Server-advertised capabilities
{
  "tools": {
    "listChanged": false
  }
}

Server-provided instructions

Untrusted server output shown for inspection. It is not Koskamo guidance and is excluded from knowledge search.

Lunara turns one audio or video file into two subtitle tracks — a written-language track and a translation — with the speech-recognition text and timings preserved exactly. DOING THE WORK: call lunara_subtitle_from_url for an existing URL, or lunara_upload_video then lunara_finish_upload for a local file, then poll lunara_get_job every 15–30 s until status is "completed" and srtReady is true, then lunara_get_srt. Ten tools are free; the ones that produce subtitles cost minutes from the account balance. Call lunara_get_queue before submitting several jobs — a saturated worker refuses with a wait time rather than queueing. TEACHING A PERSON: if the question is 「how do I use this」, 「what can it do」, 「how do I get started」 or 「what are the limits」, call lunara_guide and relay its text. It is written for a human reader; your own summary of the tools is not a substitute. ERRORS: a tool failure comes back as result.isError=true with a readable reason. Read that reason; a 401 and a saturated worker are different problems with different fixes.translated track, with ASR text and timings preserved exactly. Start with lunara_start_subtitle_job for a URL or lunara_upload_video for a local file, then poll lunara_get_job every 15–30 s until status is "completed", then call lunara_get_srt. Free tools: lunara_get_job, lunara_get_srt, lunara_list_cues, lunara_readability, lunara_get_coverage, lunara_qa_contract, lunara_probe_media, lunara_get_glossary.

Tools

20 advertised

lunara_list_tools
Advertised by the server during the last successful anonymous check.
Advertised read-only

Return every subtitle tool Lunara exposes, with the minutes each one costs. Call this first when you are unsure which tool fits.

lunara_list_tools input schema
{
  "type": "object",
  "properties": {},
  "additionalProperties": false
}
lunara_list_tools annotations
{
  "title": "List Lunara tools",
  "readOnlyHint": true,
  "openWorldHint": false,
  "idempotentHint": true,
  "destructiveHint": false,
  "lunara/costMinutes": 0
}
lunara_get_job
Advertised by the server during the last successful anonymous check.
Advertised read-only

Return the state of a subtitle job: status, progress, whether the SRT is ready, and how many cues it has. Poll this after lunara_upload_video.

lunara_get_job input schema
{
  "type": "object",
  "required": [
    "jobId"
  ],
  "properties": {
    "jobId": {
      "type": "string",
      "description": "The job id returned by lunara_upload_video."
    }
  },
  "additionalProperties": false
}
lunara_get_job annotations
{
  "title": "Get subtitle job",
  "readOnlyHint": true,
  "openWorldHint": false,
  "idempotentHint": true,
  "destructiveHint": false,
  "lunara/costMinutes": 0
}
lunara_get_srt
Advertised by the server during the last successful anonymous check.
Advertised read-only

Return the subtitle file for a finished job. One line per cue. Use track="secondary" for the translated track (English by default).

lunara_get_srt input schema
{
  "type": "object",
  "required": [
    "jobId"
  ],
  "properties": {
    "jobId": {
      "type": "string",
      "description": "The job id."
    },
    "track": {
      "enum": [
        "primary",
        "secondary"
      ],
      "type": "string",
      "description": "primary = the written-language track; secondary = the translation."
    }
  },
  "additionalProperties": false
}
lunara_get_srt annotations
{
  "title": "Download SRT",
  "readOnlyHint": true,
  "openWorldHint": false,
  "idempotentHint": true,
  "destructiveHint": false,
  "lunara/costMinutes": 0
}
lunara_list_cues
Advertised by the server during the last successful anonymous check.
Advertised read-only

Return subtitle cues for a job as timestamps plus text, so you can inspect or reference individual lines before proposing edits.

lunara_list_cues input schema
{
  "type": "object",
  "required": [
    "jobId"
  ],
  "properties": {
    "jobId": {
      "type": "string",
      "description": "The job id."
    },
    "limit": {
      "type": "integer",
      "maximum": 500,
      "minimum": 1,
      "description": "How many cues to return. Default 100."
    },
    "offset": {
      "type": "integer",
      "minimum": 0,
      "description": "Skip this many cues. Default 0."
    }
  },
  "additionalProperties": false
}
lunara_list_cues annotations
{
  "title": "List cues",
  "readOnlyHint": true,
  "openWorldHint": false,
  "idempotentHint": true,
  "destructiveHint": false,
  "lunara/costMinutes": 0
}
lunara_start_subtitle_job
Advertised by the server during the last successful anonymous check.
Open-world

Run the full eight-layer subtitle pipeline on an audio or video URL you can already host. Produces a written-language track and a translated track. Prefer lunara_upload_video when the file is local.

lunara_start_subtitle_job input schema
{
  "type": "object",
  "required": [
    "audioUrl"
  ],
  "properties": {
    "audioUrl": {
      "type": "string",
      "description": "A URL Lunara can download (public or pre-signed)."
    },
    "spokenLang": {
      "type": "string",
      "description": "What the speaker is speaking, e.g. yue-Hant-HK. Defaults to primaryLang."
    },
    "primaryLang": {
      "enum": [
        "zh-Hant",
        "yue-Hant",
        "zh-CN",
        "en",
        "ja",
        "ko"
      ],
      "type": "string",
      "description": "Language of the written track. Default yue-Hant."
    },
    "secondaryLang": {
      "enum": [
        "zh-Hant",
        "yue-Hant",
        "zh-CN",
        "en",
        "ja",
        "ko"
      ],
      "type": "string",
      "description": "Language of the translated track. Default en."
    }
  },
  "additionalProperties": false
}
lunara_start_subtitle_job annotations
{
  "title": "Start a subtitle job from a URL",
  "readOnlyHint": false,
  "openWorldHint": true,
  "idempotentHint": false,
  "destructiveHint": false,
  "lunara/costMinutes": 5
}
lunara_subtitle_from_url
Advertised by the server during the last successful anonymous check.
Open-world

Start a persisted subtitle job from a URL you already host — no upload needed. Returns a job id; poll lunara_get_job every 15–30 s until status is "completed", then call lunara_get_srt. Prefer this over lunara_upload_video whenever the media is reachable by URL.

lunara_subtitle_from_url input schema
{
  "type": "object",
  "required": [
    "audioUrl"
  ],
  "properties": {
    "audioUrl": {
      "type": "string",
      "description": "Direct HTTP(S) URL to the audio or video file. Must be downloadable by our server."
    },
    "filename": {
      "type": "string",
      "description": "Display name for the result. Defaults to the URL basename."
    },
    "spokenLang": {
      "type": "string",
      "description": "What the speaker is speaking, e.g. yue-Hant-HK. Defaults to auto-detect."
    },
    "primaryLang": {
      "enum": [
        "zh-Hant",
        "yue-Hant",
        "zh-CN",
        "en",
        "ja",
        "ko"
      ],
      "type": "string",
      "description": "Language of the written track. Default yue-Hant."
    },
    "secondaryLang": {
      "enum": [
        "zh-Hant",
        "yue-Hant",
        "zh-CN",
        "en",
        "ja",
        "ko"
      ],
      "type": "string",
      "description": "Language of the translated track. Default en."
    }
  },
  "additionalProperties": false
}
lunara_subtitle_from_url annotations
{
  "title": "Subtitles from a URL",
  "readOnlyHint": false,
  "openWorldHint": true,
  "idempotentHint": false,
  "destructiveHint": false,
  "lunara/costMinutes": 5
}
lunara_upload_video
Advertised by the server during the last successful anonymous check.

Reserve an upload slot and return the object key plus a signed URL per part. After uploading the bytes call lunara_finish_upload. Use this for a file you hold locally.

lunara_upload_video input schema
{
  "type": "object",
  "required": [
    "filename",
    "mime",
    "sizeBytes"
  ],
  "properties": {
    "mime": {
      "type": "string",
      "description": "MIME type, e.g. audio/mpeg, video/mp4."
    },
    "filename": {
      "type": "string",
      "description": "Original filename, used for the download name."
    },
    "projectId": {
      "type": "string",
      "description": "Project to upload into. Omit to let Lunara reuse the most recent project for this organisation."
    },
    "sizeBytes": {
      "type": "integer",
      "minimum": 1,
      "description": "Exact file size in bytes."
    },
    "primaryLang": {
      "enum": [
        "zh-Hant",
        "yue-Hant",
        "zh-CN",
        "en",
        "ja",
        "ko"
      ],
      "type": "string",
      "description": "Written-track language. Default yue-Hant."
    },
    "secondaryLang": {
      "enum": [
        "zh-Hant",
        "yue-Hant",
        "zh-CN",
        "en",
        "ja",
        "ko"
      ],
      "type": "string",
      "description": "Translated-track language. Default en."
    },
    "clientOperationId": {
      "type": "string",
      "description": "Idempotency key. Reuse the same value to retry without creating a second upload."
    }
  },
  "additionalProperties": false
}
lunara_upload_video annotations
{
  "title": "Upload a video for subtitling",
  "readOnlyHint": false,
  "openWorldHint": false,
  "idempotentHint": false,
  "destructiveHint": false,
  "lunara/costMinutes": 5
}
lunara_finish_upload
Advertised by the server during the last successful anonymous check.

Tell Lunara the bytes are uploaded so the pipeline can start. Returns the job id to poll with lunara_get_job.

lunara_finish_upload input schema
{
  "type": "object",
  "required": [
    "videoId",
    "uploadId",
    "key",
    "durationMs",
    "parts"
  ],
  "properties": {
    "key": {
      "type": "string",
      "description": "The object key."
    },
    "parts": {
      "type": "array",
      "items": {
        "type": "object",
        "required": [
          "PartNumber",
          "ETag"
        ],
        "properties": {
          "ETag": {
            "type": "string"
          },
          "PartNumber": {
            "type": "integer",
            "minimum": 1
          }
        },
        "additionalProperties": false
      },
      "description": "Uploaded parts, each { PartNumber, ETag }."
    },
    "videoId": {
      "type": "string",
      "description": "The video id from lunara_upload_video."
    },
    "uploadId": {
      "type": "string",
      "description": "The multipart upload id."
    },
    "durationMs": {
      "type": "integer",
      "minimum": 1,
      "description": "Media duration in milliseconds."
    }
  },
  "additionalProperties": false
}
lunara_finish_upload annotations
{
  "title": "Finish an upload",
  "readOnlyHint": false,
  "openWorldHint": false,
  "idempotentHint": true,
  "destructiveHint": false,
  "lunara/costMinutes": 0
}
lunara_get_glossary
Advertised by the server during the last successful anonymous check.
Advertised read-only

Return the proper nouns Lunara has learned for this org, so names are spelled consistently across tracks.

lunara_get_glossary input schema
{
  "type": "object",
  "properties": {
    "limit": {
      "type": "integer",
      "maximum": 500,
      "minimum": 1,
      "description": "Default 100."
    }
  },
  "additionalProperties": false
}
lunara_get_glossary annotations
{
  "title": "Get the project glossary",
  "readOnlyHint": true,
  "openWorldHint": false,
  "idempotentHint": true,
  "destructiveHint": false,
  "lunara/costMinutes": 0
}
lunara_transcribe
Advertised by the server during the last successful anonymous check.
Open-world

Run speech recognition only — no rewrite, no translation. Returns timed segments. Use this when you want the transcript itself, or when you plan to pick a later stage yourself.

lunara_transcribe input schema
{
  "type": "object",
  "required": [
    "audioUrl"
  ],
  "properties": {
    "audioUrl": {
      "type": "string",
      "description": "Direct HTTP(S) URL to the audio or video file."
    },
    "spokenLang": {
      "type": "string",
      "description": "What the speaker is speaking, e.g. yue-Hant-HK. Defaults to auto-detect."
    }
  },
  "additionalProperties": false
}
lunara_transcribe annotations
{
  "title": "Transcribe audio",
  "readOnlyHint": false,
  "openWorldHint": true,
  "idempotentHint": true,
  "destructiveHint": false,
  "lunara/costMinutes": 3
}
lunara_translate
Advertised by the server during the last successful anonymous check.

Translate the text of an existing job’s cues into another language. Every cue boundary and timestamp is preserved; only the text changes. Use this instead of translating yourself so the timing contract holds.

lunara_translate input schema
{
  "type": "object",
  "required": [
    "jobId",
    "targetLang"
  ],
  "properties": {
    "jobId": {
      "type": "string",
      "description": "The job whose cues to translate."
    },
    "track": {
      "enum": [
        "primary",
        "secondary"
      ],
      "type": "string",
      "description": "Which track to read. Default primary."
    },
    "targetLang": {
      "enum": [
        "zh-Hant",
        "en",
        "ja",
        "ko"
      ],
      "type": "string",
      "description": "Target language."
    }
  },
  "additionalProperties": false
}
lunara_translate annotations
{
  "title": "Translate a cue list",
  "readOnlyHint": false,
  "openWorldHint": false,
  "idempotentHint": true,
  "destructiveHint": false,
  "lunara/costMinutes": 3
}
lunara_rewrite
Advertised by the server during the last successful anonymous check.

Turn spoken Cantonese cue text into written Chinese. Every cue boundary and timestamp is preserved. Deliberate mispronunciations are never corrected, and proper nouns are protected.

lunara_rewrite input schema
{
  "type": "object",
  "required": [
    "jobId"
  ],
  "properties": {
    "jobId": {
      "type": "string",
      "description": "The job whose cues to rewrite."
    },
    "track": {
      "enum": [
        "primary",
        "secondary"
      ],
      "type": "string",
      "description": "Which track to read. Default primary."
    }
  },
  "additionalProperties": false
}
lunara_rewrite annotations
{
  "title": "Rewrite spoken → written",
  "readOnlyHint": false,
  "openWorldHint": false,
  "idempotentHint": true,
  "destructiveHint": false,
  "lunara/costMinutes": 3
}
lunara_segment
Advertised by the server during the last successful anonymous check.

Approximate split, not audio-grounded segmentation: cue text is divided at clause punctuation and each piece receives a share of the original span proportional to its character count. The words are not changed. Timings are estimated, so treat them as a reading aid rather than a re-alignment — use lunara_start_subtitle_job if you need timing derived from the audio.

lunara_segment input schema
{
  "type": "object",
  "required": [
    "jobId"
  ],
  "properties": {
    "jobId": {
      "type": "string",
      "description": "The job whose cues to re-cut."
    },
    "track": {
      "enum": [
        "primary",
        "secondary"
      ],
      "type": "string",
      "description": "Which track to read. Default primary."
    },
    "maxCueMs": {
      "type": "integer",
      "maximum": 15000,
      "minimum": 500,
      "description": "Duration ceiling per cue in ms. Default 7000."
    }
  },
  "additionalProperties": false
}
lunara_segment annotations
{
  "title": "Split cues at punctuation",
  "readOnlyHint": false,
  "openWorldHint": false,
  "idempotentHint": true,
  "destructiveHint": false,
  "lunara/costMinutes": 1
}
lunara_glossary_extract
Advertised by the server during the last successful anonymous check.
Advertised read-only

Find candidate proper nouns in a job’s cues, ranked by frequency, with the first cue they appear in. Suggestions for a glossary, not corrections — nothing is rewritten.

lunara_glossary_extract input schema
{
  "type": "object",
  "required": [
    "jobId"
  ],
  "properties": {
    "jobId": {
      "type": "string",
      "description": "The job whose cues to scan."
    },
    "limit": {
      "type": "integer",
      "maximum": 200,
      "minimum": 1,
      "description": "Maximum terms to return. Default 50."
    }
  },
  "additionalProperties": false
}
lunara_glossary_extract annotations
{
  "title": "Extract proper nouns",
  "readOnlyHint": true,
  "openWorldHint": false,
  "idempotentHint": true,
  "destructiveHint": false,
  "lunara/costMinutes": 1
}
lunara_guide
Advertised by the server during the last successful anonymous check.
Advertised read-only

Returns a step-by-step guide written to be read by a human, not by you. Call this when someone asks "how do I use this", "what can it do", "how do I get started", or when you need to explain the workflow rather than execute it. Pass journey="first_subtitle" for the end-to-end path, "api_key" for credential setup, "languages" for what is supported, or "limits" for what it cannot do. Answer the person with this text rather than paraphrasing from memory.

lunara_guide input schema
{
  "type": "object",
  "required": [],
  "properties": {
    "journey": {
      "enum": [
        "first_subtitle",
        "api_key",
        "languages",
        "limits",
        "edit_subtitles",
        "agent"
      ],
      "type": "string",
      "description": "Which journey to explain. first_subtitle: upload to finished file. api_key: getting a credential. languages: what it does and does not handle. limits: honest boundaries. edit_subtitles: fixing a line after the fact. agent: putting this on an AI agent rather than doing it by hand."
    }
  },
  "additionalProperties": false
}
lunara_guide annotations
{
  "title": "How to use Lunara — a guide you can relay to a person",
  "readOnlyHint": true,
  "openWorldHint": false,
  "idempotentHint": true,
  "destructiveHint": false,
  "lunara/costMinutes": 0
}
lunara_get_queue
Advertised by the server during the last successful anonymous check.
Advertised read-only

How many transcription jobs are running and whether a new one can be accepted. Call this before lunara_start_subtitle_job or lunara_subtitle_from_url if you are submitting several jobs — when saturated, wait the retryAfterSeconds rather than retrying immediately.

lunara_get_queue input schema
{
  "type": "object",
  "required": [],
  "properties": {},
  "additionalProperties": false
}
lunara_get_queue annotations
{
  "title": "Pipeline queue depth",
  "readOnlyHint": true,
  "openWorldHint": false,
  "idempotentHint": true,
  "destructiveHint": false,
  "lunara/costMinutes": 0
}
lunara_qa_contract
Advertised by the server during the last successful anonymous check.
Advertised read-only

Run the contract gate over both tracks: ASR text and timings byte-exact, cue length bounds, no empty cues. Returns pass/fail per rule. Free, because refusing a broken track should never cost the caller money.

lunara_qa_contract input schema
{
  "type": "object",
  "required": [
    "jobId"
  ],
  "properties": {
    "jobId": {
      "type": "string",
      "description": "The job to validate."
    },
    "track": {
      "enum": [
        "primary",
        "secondary",
        "both"
      ],
      "type": "string",
      "description": "Which track to check. Default both."
    }
  },
  "additionalProperties": false
}
lunara_qa_contract annotations
{
  "title": "Check the subtitle contract",
  "readOnlyHint": true,
  "openWorldHint": false,
  "idempotentHint": true,
  "destructiveHint": false,
  "lunara/costMinutes": 0
}
lunara_probe_media
Advertised by the server during the last successful anonymous check.
Advertised read-onlyOpen-world

Return duration, dimensions, codecs and container for a URL, plus whether the audio is mono 16 kHz already. Call this before choosing an ASR engine.

lunara_probe_media input schema
{
  "type": "object",
  "required": [
    "mediaUrl"
  ],
  "properties": {
    "mediaUrl": {
      "type": "string",
      "description": "URL to probe."
    }
  },
  "additionalProperties": false
}
lunara_probe_media annotations
{
  "title": "Probe media",
  "readOnlyHint": true,
  "openWorldHint": true,
  "idempotentHint": true,
  "destructiveHint": false,
  "lunara/costMinutes": 0
}
lunara_get_coverage
Advertised by the server during the last successful anonymous check.
Advertised read-only

Report how much of the source timeline the subtitles cover, and list suspicious gaps longer than a second. Use before shipping a long video.

lunara_get_coverage input schema
{
  "type": "object",
  "required": [
    "jobId"
  ],
  "properties": {
    "jobId": {
      "type": "string",
      "description": "The job to inspect."
    }
  },
  "additionalProperties": false
}
lunara_get_coverage annotations
{
  "title": "Delivery coverage",
  "readOnlyHint": true,
  "openWorldHint": false,
  "idempotentHint": true,
  "destructiveHint": false,
  "lunara/costMinutes": 0
}
lunara_readability
Advertised by the server during the last successful anonymous check.
Advertised read-only

Per-cue reading speed, displayed length and characters-per-second, with the cues that exceed comfortable limits. Mirrors the metrics shown in the editor.

lunara_readability input schema
{
  "type": "object",
  "required": [
    "jobId"
  ],
  "properties": {
    "jobId": {
      "type": "string",
      "description": "The job to measure."
    },
    "track": {
      "enum": [
        "primary",
        "secondary"
      ],
      "type": "string",
      "description": "Default primary."
    }
  },
  "additionalProperties": false
}
lunara_readability annotations
{
  "title": "Readability report",
  "readOnlyHint": true,
  "openWorldHint": false,
  "idempotentHint": true,
  "destructiveHint": false,
  "lunara/costMinutes": 0
}

Prompts

Not advertised

No prompt definitions available.

Resources

Not advertised

No resource definitions available.

30-day availability

Aggregated from read-only remote checks; this is not a tool-execution or security guarantee.

Successful checks
1 of 1 (100%)
Average latency
873 ms
95th percentile latency
873 ms

Maintenance history

Public check and source-refresh events. Editorial and ownership records remain private.

  • · probe · passed · 873 ms · metadata changed · 20 tool(s)

Connect from your client

Each snippet registers the same endpoint; pick the client you use.

This server is operated by the named third party. Listing and endpoint checks are not an endorsement or a security audit. Review the operator's permissions, terms, and data handling before connecting. How listings are checked and corrected.

Claude Code

Run this once in a terminal. It registers the remote server. Use --scope project to write .mcp.json for the repo.
Claude Code configuration
claude mcp add --transport http lunara-mcp https://lunaraai.cc/mcp

Codex

Run this once. Codex stores MCP servers in ~/.codex/config.toml (or .codex/config.toml in a trusted project).
Codex configuration
codex mcp add lunara-mcp --url https://lunaraai.cc/mcp

Cursor

Merge this server into .cursor/mcp.json in your project, then enable lunara-mcp in your MCP settings.
Cursor configuration
{
  "mcpServers": {
    "lunara-mcp": {
      "url": "https://lunaraai.cc/mcp"
    }
  }
}

Cline

Merge this into cline_mcp_settings.json (Cline → MCP Servers → Configure).
Cline configuration
{
  "mcpServers": {
    "lunara-mcp": {
      "type": "streamableHttp",
      "url": "https://lunaraai.cc/mcp",
      "disabled": false
    }
  }
}

VS Code

Merge this server into .vscode/mcp.json. Use MCP: List Servers to start lunara-mcp and inspect its tools.
VS Code configuration
{
  "servers": {
    "lunara-mcp": {
      "type": "http",
      "url": "https://lunaraai.cc/mcp"
    }
  }
}

Gemini CLI

Run this once. It writes .gemini/settings.json in the current project; pass --scope user for ~/.gemini/settings.json.
Gemini CLI configuration
gemini mcp add --transport http lunara-mcp https://lunaraai.cc/mcp

Claude Desktop

Merge this into claude_desktop_config.json (macOS: ~/Library/Application Support/Claude/, Windows: %APPDATA%\Claude\), then restart Claude Desktop.
Claude Desktop configuration
{
  "mcpServers": {
    "lunara-mcp": {
      "url": "https://lunaraai.cc/mcp"
    }
  }
}

Windsurf

Merge this into ~/.codeium/windsurf/mcp_config.json, then refresh MCP in Cascade. Windsurf remote servers use serverUrl, not url.
Windsurf configuration
{
  "mcpServers": {
    "lunara-mcp": {
      "serverUrl": "https://lunaraai.cc/mcp"
    }
  }
}

Let your agent find its tools

Find servers like Lunara MCP from your agent. Add Koskamo once to search the index for endpoints, access requirements, and client setup. Public discovery needs no key.