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
- How it works
- Requirements
- Run the server
- Configuration
- Connecting a client
- Capabilities & tool reference
- End-to-end workflows
- Status & lifecycle reference
- Error handling
- Security notes
- Troubleshooting
What you can do with it
Once connected, an agent can perform the full video-intelligence lifecycle through conversation:
| Capability | What it enables |
|---|---|
| Ingest | Import videos from YouTube directly into your library. |
| Manage | List, inspect, and delete videos in your library with rich filtering and sorting. |
| Process | Run the analyse and search-process pipelines that make a video understandable and searchable. |
| Organize | Group processed videos into named collections for scoped operations. |
| Search | Run natural-language semantic search across videos or whole collections and get back ranked, timestamped clips. |
| Visualize | Project every segment of a selection into a 2-D map for clustering / exploration UIs. |
| Chat | Ask free-form questions about a specific video and get grounded, RAG-based answers with multi-turn memory. |
| Embed | Generate 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(defaulthttp://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-Keyheader (orAuthorization: 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:
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:
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:
# 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
| Variable | Default | Description |
|---|---|---|
MIKSHI_BASE_URL | https://vision.mikshi.ai/mcp | Base URL of the Mikshi backend. Point this at your hosted environment in production (e.g. https://api.mikshi.com). |
MIKSHI_TIMEOUT | 30 | Per-request HTTP timeout in seconds. Raise it for large operations on slow links. |
MCP_HOST | 0.0.0.0 | Interface the MCP server binds to. |
MCP_PORT | 8000 | Port the MCP server listens on (endpoint is /mcp). |
MCP_TRANSPORT | streamable-http | Transport 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.envout 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
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):
{
"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:
{
"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:
{
"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:
{
"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_...(orAuthorization: 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)
// →
{ "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 ofuploaded,analysed,search_processed,processed.processedrequires both pipelines complete.skip— offset for paginationlimit— page size (default20)sort_by,sort_order— e.g.created_at/descnot_in_collection— whentrue, 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), andcreated_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; pollget_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 conveniencederived_statusrolling them into one ofpending/in_progress/completed/failed. Pollderived_statusin 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), optionalpercentageanderror. Note:analysereports 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), optionalpercentageanderror. Note:search-processreports success as"success".
Pipeline status vocabularies differ per pipeline. Treat
done/success/completedas done,failed/erroras 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(default1),page_size(default10, max100) - Returns:
data(collections, each withcollection_id,name,description,video_count, up to four signedthumbnails), pluspage,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(default1),page_size(default10),sort_by(defaultcreated_at),sort_order(defaultdesc) - 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
analyseandsearch-processdone). 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 queryvideo_ids(optional list) — restrict to specific videoscollection_id(optional) — restrict to a collectiontop_k(default5) — number of hits to return- Provide at least one of
video_idsorcollection_id.
- Returns: a ranked list of hits, each with
rank,start/end(HH:MM:SStimestamps),video_id, and signedthumbnail_url/hls_playlist_url/video_urlfor 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/orcollection_id(provide at least one) - Returns:
videos(per-video metadata + color + playback URLs) andpoints(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 aboutquestion(string)top_k(optional) — how many segments to retrieve as contexthistory(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
historyeach 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(defaultfalse) - Returns:
status,embedding_count,dimension. Wheninclude_vectoristrue, the full vector(s) are included underdata. 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(defaultfalse) - Returns:
id,status(readywhen available),segment_count, anddata— one entry per segment with itsstart/end,embedding_option/scope, anddimension. Raw vectors are omitted unlessinclude_vectorsistrue. - Requires: the video's search-process pipeline must be finished, otherwise this returns a conflict — wait until
search_process_statusis done and retry.
End-to-end workflows
Make a YouTube video searchable, then query it
import_youtube_video(url)→ grabvideo_id- Poll
get_upload_status(video_id)untilderived_statusiscompleted initiate_analyse(video_id)andinitiate_search_process(video_id)- Poll
get_analyse_status/get_search_process_statusuntil terminal search_videos(query="the moment they discuss pricing", video_ids=[video_id])ask_video(video_id, "Summarize the pricing discussion")
Build a curated, searchable collection
create_collection("Q2 Customer Calls")→collection_id- For each fully-processed video:
add_video_to_collection(collection_id, video_id) search_videos(query="objections about onboarding", collection_id=collection_id, top_k=10)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 (enablesask_video).search_processed— search pipeline complete (enables search + embeddings retrieval).processed— both pipelines complete (required for collection membership).
Pipeline status vocabularies (don't string-compare blindly):
| Pipeline | Success value | Failure values | Terminal? |
|---|---|---|---|
Upload (derived_status) | completed | failed | also pending / in_progress |
| Analyse | done | failed / error | else keep polling |
| Search-process | success | failed / error | else 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:
| HTTP | Meaning | Typical cause |
|---|---|---|
| 400 | Bad request | Invalid parameters; for YouTube imports, a specific YOUTUBE_* code. |
| 401 | Authentication failed | Missing/invalid API key. |
| 403 | Permission denied | Key lacks access to the resource. |
| 404 | Not found | Unknown video_id / collection_id. |
| 409 | Conflict | Video already in collection, or pipeline not finished yet. |
| 413 | Payload too large | Upload exceeds the size limit. |
| 422 | Validation error | Malformed body. |
| 429 | Rate limited | Includes Retry-After when available. |
| 5xx | Server / upstream error | Backend 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-Keyheader (orAuthorization: Bearer), forwarded only to your configuredMIKSHI_BASE_URL, and never returned by any tool. - Prefer not to set
MIKSHI_API_KEYin 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_URLat an HTTPS endpoint in production. - Keep
.envout of version control (already in.gitignore). delete_videois permanent and irreversible; agents should confirm with the user before calling it.
Troubleshooting
| Symptom | Fix |
|---|---|
| Client can't connect / connection refused | Confirm the server is running and reachable at http://<host>:<port>/mcp. With Docker compose the host port is 9105, not 8000. |
401 on every call | Missing or wrong API key. Confirm the client sends X-Mikshi-Api-Key (or Authorization: Bearer). Check sdk_info → api_key_source. |
| "No API key" error | Neither the request header nor the MIKSHI_API_KEY fallback was set. |
| Tool calls reach a backend that's down | MIKSHI_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 HTTP | Set MCP_TRANSPORT=stdio and launch your client against the mikshi-mcp command. |
add_video_to_collection returns 409 | The video isn't fully processed yet, or it's already a member. Confirm both pipelines are done. |
get_video_embeddings returns 409 | Search-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