Mikshi MCP Server

A Model Context Protocol (MCP) server that exposes the Mikshi video platform as a set of agent-callable tools. Point any MCP-compatible client — Claude Desktop, Claude Code, Cursor, Windsurf, VS Code, or your own agent — at it, and your assistant can ingest, analyse, search, and chat with video content using natural language.

It runs as a lightweight Streamable HTTP service and surfaces a curated set of Mikshi capabilities as tools, so the model talks to your Mikshi backend without you writing any glue code.


Table of contents


What you can do with it

Once connected, an agent can perform the full video-intelligence lifecycle through conversation:

CapabilityWhat it enables
IngestImport videos from YouTube directly into your library.
ManageList, inspect, and delete videos in your library with rich filtering and sorting.
ProcessRun the analyse and search-process pipelines that make a video understandable and searchable.
OrganizeGroup processed videos into named collections for scoped operations.
SearchRun natural-language semantic search across videos or whole collections and get back ranked, timestamped clips.
VisualizeProject every segment of a selection into a 2-D map for clustering / exploration UIs.
ChatAsk free-form questions about a specific video and get grounded, RAG-based answers with multi-turn memory.
EmbedGenerate text embeddings or retrieve stored per-segment video embeddings for your own downstream ML.

How it works

┌──────────────┐  Streamable HTTP   ┌──────────────────────┐   HTTPS    ┌──────────────────┐
│  MCP client  │ ─────────────────▶ │  mikshi-mcp          │ ─────────▶ │  Mikshi backend  │
│ (Claude, …)  │ ◀───────────────── │  http://host:8000/mcp│ ◀───────── │   (REST API)     │
└──────────────┘  X-Mikshi-Api-Key  └──────────────────────┘  JSON/SSE  └──────────────────┘
  • The server runs as a long-lived HTTP service, listening at http://<MCP_HOST>:<MCP_PORT>/mcp (default http://0.0.0.0:8000/mcp) and speaking MCP's Streamable HTTP transport.
  • MCP clients connect to that URL and send the Mikshi API key per request via the X-Mikshi-Api-Key header (or Authorization: Bearer <key>). The key is never stored server-side, so one running server can serve multiple clients/users, each with their own key.
  • The server forwards each tool call to the Mikshi backend and returns the result as plain JSON. Large vectors are summarized by default to keep responses compact.
  • It is stateless — each call resolves the caller's key, opens a short-lived client, performs the request, and closes it.
  • For a single-user local setup, it can also run over stdio (MCP_TRANSPORT=stdio).

Run the server

With uv

From this folder:

bash
uv sync
uv run --env-file .env mikshi-mcp
# → serving on http://0.0.0.0:8000/mcp

uv sync resolves and installs dependencies; uv run starts the HTTP server. No separate build step is required.

With Docker

A multi-stage Dockerfile and docker-compose.yml are included. The compose build context is the repo root so the build can reach the sibling ../python-sdk:

bash
docker compose up --build
# → server reachable at http://localhost:9105/mcp  (compose maps host 9105 → container 8000)

Provide configuration through the .env file (compose loads it via env_file). Adjust the published port in docker-compose.yml if 9105 is taken.


Configuration

Environment variables

Copy .env.example to .env and adjust:

env
# API key is normally sent per request by the client via X-Mikshi-Api-Key.
# Uncomment only to set a server-wide fallback key:
# MIKSHI_API_KEY=sk_your_key_here
MIKSHI_BASE_URL=https://vision.mikshi.ai/mcp
MIKSHI_TIMEOUT=30

MCP_HOST=0.0.0.0
MCP_PORT=8000
MCP_TRANSPORT=streamable-http
VariableDefaultDescription
MIKSHI_BASE_URLhttps://vision.mikshi.ai/mcpBase URL of the Mikshi backend. Point this at your hosted environment in production (e.g. https://api.mikshi.com).
MIKSHI_TIMEOUT30Per-request HTTP timeout in seconds. Raise it for large operations on slow links.
MCP_HOST0.0.0.0Interface the MCP server binds to.
MCP_PORT8000Port the MCP server listens on (endpoint is /mcp).
MCP_TRANSPORTstreamable-httpTransport to serve. Set to stdio for a local single-user, subprocess-style setup.
MIKSHI_API_KEY(unset)Optional server-wide fallback key, used only when a request doesn't carry one. Normally you leave this unset and let each client send its own key.

Authentication

The Mikshi API key is supplied by the client, per request — it is not baked into the server:

  • Preferred: X-Mikshi-Api-Key: sk_...
  • Also accepted: Authorization: Bearer sk_...

This keeps keys out of the server image/environment and lets different clients use different keys against the same server. If no header is present, the server falls back to the optional MIKSHI_API_KEY env var; if neither is set, the call returns a clear "no API key" error. The key is never echoed back — sdk_info reports only whether one was received and from where (request-header vs env).

Generate a key (sk_...) from your Mikshi dashboard / developer settings. Keep .env out of source control (it's already covered by .gitignore).


Connecting a client

Clients connect to the server's URL (http://<host>:<port>/mcp) and attach the API key as a header. Field names differ slightly per client; the shapes below cover the common ones. Examples assume the server is local on the default port (http://localhost:8000/mcp); use 9105 instead if you launched via Docker compose.

Claude Code

bash
claude mcp add --transport http mikshi http://localhost:8000/mcp \
  --header "X-Mikshi-Api-Key: sk_your_key_here"

Run /mcp inside Claude Code to confirm it connected. (Add --scope project to share the config with your team via .mcp.json.)

Cursor

Create .cursor/mcp.json in your project (or the global ~/.cursor/mcp.json):

json
{
  "mcpServers": {
    "mikshi": {
      "url": "http://localhost:8000/mcp",
      "headers": {
        "X-Mikshi-Api-Key": "sk_your_key_here"
      }
    }
  }
}

Open Settings → MCP to verify the server shows a green status, then enable its tools.

VS Code (Copilot / MCP)

Add an .vscode/mcp.json to your workspace:

json
{
  "servers": {
    "mikshi": {
      "type": "http",
      "url": "http://localhost:8000/mcp",
      "headers": {
        "X-Mikshi-Api-Key": "sk_your_key_here"
      }
    }
  }
}

Open the Chat view in Agent mode and select the mikshi tools.

Windsurf

Edit ~/.codeium/windsurf/mcp_config.json:

json
{
  "mcpServers": {
    "mikshi": {
      "serverUrl": "http://localhost:8000/mcp",
      "headers": {
        "X-Mikshi-Api-Key": "sk_your_key_here"
      }
    }
  }
}

Reload Windsurf and refresh the MCP server list in the Cascade panel.

Claude Desktop

Claude Desktop connects to remote/HTTP MCP servers through its Connectors UI (Settings → Connectors → add a custom connector pointing at http://localhost:8000/mcp). For config-file setups that only support stdio, bridge to the HTTP endpoint with mcp-remote:

json
{
  "mcpServers": {
    "mikshi": {
      "command": "npx",
      "args": [
        "mcp-remote",
        "http://localhost:8000/mcp",
        "--header",
        "X-Mikshi-Api-Key: sk_your_key_here"
      ]
    }
  }
}

Restart Claude Desktop; the tools appear under the 🔌 / tools menu in a new conversation.

Generic MCP client

Any client that supports the Streamable HTTP transport works:

  • URL: http://<host>:<port>/mcp
  • Header: X-Mikshi-Api-Key: sk_... (or Authorization: Bearer sk_...)

For a quick interactive check, point the MCP Inspector at the same URL and header. To run the server in single-user stdio mode instead, set MCP_TRANSPORT=stdio and launch your client against the mikshi-mcp command.


Capabilities & tool reference

All tools return JSON. Parameters marked optional may be omitted; defaults are shown. IDs are UUID strings unless noted.

1. Server & diagnostics

sdk_info

Report the server's effective configuration without leaking secrets. Use this first to confirm the server is reachable and that your key is being received.

  • Parameters: none
  • Returns: sdk_version, base_url, timeout, api_key_configured (boolean), api_key_source (request-header / env / none)
jsonc
// →
{ "sdk_version": "0.1.0", "base_url": "https://vision.mikshi.ai/mcp", "timeout": 30.0, "api_key_configured": true, "api_key_source": "request-header" }

2. Video library management

list_videos

List videos owned by your API key, with filtering and pagination.

  • Parameters (all optional):
    • status — one of uploaded, analysed, search_processed, processed. processed requires both pipelines complete.
    • skip — offset for pagination
    • limit — page size (default 20)
    • sort_by, sort_order — e.g. created_at / desc
    • not_in_collection — when true, only videos not yet in any collection
  • Returns: a list of video rows, each including video_id, name, status, asset paths (video_path, thumbnail_path, hls_playlist_path), per-pipeline statuses (analyse_status, search_process_status, transcode/thumbnail/HLS), and created_at.

get_video

Fetch full details and metadata for one video.

  • Parameters: video_id
  • Returns: video record including video_metadata (duration, codecs, etc. when available) and all asset paths.

delete_video

Permanently delete a video and every derived asset (thumbnails, HLS, embeddings).

  • Parameters: video_id
  • Returns: { "status": "deleted", "video_id": "..." }
  • ⚠️ Irreversible. There is no soft-delete or undo.

3. Ingestion (uploads & imports)

import_youtube_video

Import a video directly from a YouTube URL — the backend fetches and ingests it.

  • Parameters: url (string), name (optional string)
  • Returns: { "video_id": "..." } (HTTP 202 — ingestion is asynchronous; poll get_upload_status)
  • Failure modes: raises a structured error for known YouTube conditions — private, unavailable, age-restricted, geo-blocked, copyright-blocked, too-long, or auth-required videos. The underlying code (e.g. YOUTUBE_VIDEO_PRIVATE) is included so an agent can explain why.

get_upload_status

Poll the ingestion/processing status of an uploaded or imported video.

  • Parameters: video_id
  • Returns: per-pipeline statuses (thumbnail_generation_status, transcoding_status, hls_generation_status) plus a convenience derived_status rolling them into one of pending / in_progress / completed / failed. Poll derived_status in a loop until it is terminal.

Direct binary file uploads are not exposed as MCP tools, since an LLM host can't stream large file bodies safely. For agent-driven ingestion, use YouTube import, then drive processing/search through these tools.

4. Processing pipelines

A raw video isn't useful until it's processed. Two independent pipelines apply:

  • Analyse — content understanding (the basis for ask_video).
  • Search-process — segments and embeds the video so it becomes searchable and its embeddings retrievable.

A video is fully processed only when both finish. A video must be fully processed before it can be added to a collection.

initiate_analyse

  • Parameters: video_id
  • Returns: { "video_id": "...", "status": "..." } (enqueued)

get_analyse_status

  • Parameters: video_id
  • Returns: video_id, status (e.g. done), optional percentage and error. Note: analyse reports success as "done".

initiate_search_process

  • Parameters: video_id
  • Returns: { "video_id": "...", "status": "..." } (enqueued)

get_search_process_status

  • Parameters: video_id
  • Returns: video_id, status (e.g. success), optional percentage and error. Note: search-process reports success as "success".

Pipeline status vocabularies differ per pipeline. Treat done/success/completed as done, failed/error as failed, and keep polling otherwise.

5. Collections

Collections group fully-processed videos so search and other operations can be scoped to a set.

list_collections

  • Parameters: page (default 1), page_size (default 10, max 100)
  • Returns: data (collections, each with collection_id, name, description, video_count, up to four signed thumbnails), plus page, page_size, total.

create_collection

  • Parameters: name (string), description (optional)
  • Returns: the created collection.

update_collection

  • Parameters: collection_id, name (optional), description (optional). At least one of name/description must be provided.
  • Returns: the updated collection.

delete_collection

  • Parameters: collection_id
  • Returns: { "status": "deleted", "collection_id": "..." }
  • Deletes only the collection — the member videos are not deleted.

list_collection_videos

  • Parameters: collection_id, page (default 1), page_size (default 10), sort_by (default created_at), sort_order (default desc)
  • Returns: paginated videos in the collection (data, page, page_size, total).

add_video_to_collection

  • Parameters: collection_id, video_id
  • Returns: { "status": "added", ... }
  • Constraint: the video must be fully processed (both analyse and search-process done). Adding a video that's already a member returns a conflict.

remove_video_from_collection

  • Parameters: collection_id, video_id
  • Returns: { "status": "removed", ... }
  • Removes only the link; the video record is untouched and can be re-added later.

6. Semantic search & visualization

search_videos

Natural-language semantic search returning ranked, timestamped clips.

  • Parameters:
    • query (string) — the natural-language query
    • video_ids (optional list) — restrict to specific videos
    • collection_id (optional) — restrict to a collection
    • top_k (default 5) — number of hits to return
    • Provide at least one of video_ids or collection_id.
  • Returns: a ranked list of hits, each with rank, start/end (HH:MM:SS timestamps), video_id, and signed thumbnail_url / hls_playlist_url / video_url for playback.

visualize_videos

Project every segment of a selection into a 2-D map (UMAP) — ideal for clustering and exploration UIs.

  • Parameters: video_ids (optional list) and/or collection_id (provide at least one)
  • Returns: videos (per-video metadata + color + playback URLs) and points (one per segment: segment_id, video_id, x, y, start/end, thumbnail_url).

7. Conversational Q&A (chat)

ask_video

Ask a free-form question about one video and get a grounded, retrieval-augmented answer.

  • Parameters:
    • video_id — the video to ask about
    • question (string)
    • top_k (optional) — how many segments to retrieve as context
    • history (optional) — prior turns for multi-turn context, as [{"role": "user", "content": "..."}, {"role": "assistant", "content": "..."}, ...]
  • Returns: { "answer": "..." }
  • Stateless: the server keeps no memory between calls. To hold a conversation, resend prior turns in history each time.

The MCP tool returns the complete answer in one response, which is the right shape for tool-calling agents.

8. Embeddings

embed_text

Generate an embedding vector for an arbitrary string.

  • Parameters: text (1–4096 characters), include_vector (default false)
  • Returns: status, embedding_count, dimension. When include_vector is true, the full vector(s) are included under data. By default the (large) vector is omitted to keep the response compact.

get_video_embeddings

Retrieve the stored per-segment embedding vectors for a processed video.

  • Parameters: video_id, include_vectors (default false)
  • Returns: id, status (ready when available), segment_count, and data — one entry per segment with its start/end, embedding_option/scope, and dimension. Raw vectors are omitted unless include_vectors is true.
  • Requires: the video's search-process pipeline must be finished, otherwise this returns a conflict — wait until search_process_status is done and retry.

End-to-end workflows

Make a YouTube video searchable, then query it

  1. import_youtube_video(url) → grab video_id
  2. Poll get_upload_status(video_id) until derived_status is completed
  3. initiate_analyse(video_id) and initiate_search_process(video_id)
  4. Poll get_analyse_status / get_search_process_status until terminal
  5. search_videos(query="the moment they discuss pricing", video_ids=[video_id])
  6. ask_video(video_id, "Summarize the pricing discussion")

Build a curated, searchable collection

  1. create_collection("Q2 Customer Calls")collection_id
  2. For each fully-processed video: add_video_to_collection(collection_id, video_id)
  3. search_videos(query="objections about onboarding", collection_id=collection_id, top_k=10)
  4. visualize_videos(collection_id=collection_id) to render a segment map in your UI

Status & lifecycle reference

A video moves through these states:

uploaded ──▶ (analyse pipeline)        ──▶ analysed ─┐
         └─▶ (search-process pipeline) ──▶ search_processed ─┴──▶ processed
  • uploaded — ingested; transcode/thumbnail/HLS done, but not yet understood or searchable.
  • analysed — analyse pipeline complete (enables ask_video).
  • search_processed — search pipeline complete (enables search + embeddings retrieval).
  • processedboth pipelines complete (required for collection membership).

Pipeline status vocabularies (don't string-compare blindly):

PipelineSuccess valueFailure valuesTerminal?
Upload (derived_status)completedfailedalso pending / in_progress
Analysedonefailed / errorelse keep polling
Search-processsuccessfailed / errorelse keep polling

Error handling

Every failed call is returned to the model as a structured error containing the exception type, HTTP status_code, the raw response_body, and a human-readable message. Common cases:

HTTPMeaningTypical cause
400Bad requestInvalid parameters; for YouTube imports, a specific YOUTUBE_* code.
401Authentication failedMissing/invalid API key.
403Permission deniedKey lacks access to the resource.
404Not foundUnknown video_id / collection_id.
409ConflictVideo already in collection, or pipeline not finished yet.
413Payload too largeUpload exceeds the size limit.
422Validation errorMalformed body.
429Rate limitedIncludes Retry-After when available.
5xxServer / upstream errorBackend or dependency failure.

If no API key is provided (no X-Mikshi-Api-Key header and no MIKSHI_API_KEY fallback), the call returns a clear "no API key" error before any backend request — check sdk_info to confirm where (if anywhere) your key is being resolved from.


Security notes

  • The API key is supplied per request via the X-Mikshi-Api-Key header (or Authorization: Bearer), forwarded only to your configured MIKSHI_BASE_URL, and never returned by any tool.
  • Prefer not to set MIKSHI_API_KEY in the server environment unless you intend a shared fallback key for every caller.
  • Run the server behind TLS (a reverse proxy) when exposed beyond localhost, and point MIKSHI_BASE_URL at an HTTPS endpoint in production.
  • Keep .env out of version control (already in .gitignore).
  • delete_video is permanent and irreversible; agents should confirm with the user before calling it.

Troubleshooting

SymptomFix
Client can't connect / connection refusedConfirm the server is running and reachable at http://<host>:<port>/mcp. With Docker compose the host port is 9105, not 8000.
401 on every callMissing or wrong API key. Confirm the client sends X-Mikshi-Api-Key (or Authorization: Bearer). Check sdk_infoapi_key_source.
"No API key" errorNeither the request header nor the MIKSHI_API_KEY fallback was set.
Tool calls reach a backend that's downMIKSHI_BASE_URL is wrong or the backend isn't running. Verify with sdk_info, then raise MIKSHI_TIMEOUT if the backend is just slow.
Want a local subprocess instead of HTTPSet MCP_TRANSPORT=stdio and launch your client against the mikshi-mcp command.
add_video_to_collection returns 409The video isn't fully processed yet, or it's already a member. Confirm both pipelines are done.
get_video_embeddings returns 409Search-process pipeline hasn't finished. Wait until search_process_status is done.

Tool index (quick reference)

sdk_info · list_videos · get_video · delete_video · get_upload_status · import_youtube_video · initiate_analyse · get_analyse_status · initiate_search_process · get_search_process_status · list_collections · create_collection · update_collection · delete_collection · list_collection_videos · add_video_to_collection · remove_video_from_collection · search_videos · visualize_videos · ask_video · embed_text · get_video_embeddings