Gemini-Video-MCP
Allows describing video content using Google's Gemini model, including extracting timestamps and analysis of visual and audio tracks.
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@Gemini-Video-MCPDescribe the video at ~/Downloads/sample.mp4"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
English | 简体中文
Gemini-Video-MCP
An MCP server that hands local video files to Gemini's native video understanding and returns a detailed, timestamped description of what's happening on screen and in the audio track. The video pipeline was extracted and cleaned up from a larger Discord/QQ bot project (MoFox-Bot) where it had already been battle-tested.
Gemini is really good at describing video — good enough that it tends to get dramatic and lyrical
about it. The default prompt in this server deliberately leaves that flourish in (it doesn't ask
for dry, objective summaries) and asks Gemini to write the description in Chinese. If you want a
flatter tone or a different output language, pass your own prompt.
Tools at a glance
describe_video— the main tool: local video → text description.Supports common containers:
mp4 / mov / webm / avi / mkv / flv / wmv / mpeg / mpg / m4v / 3gp / 3gpp;Also handles
.gifnatively (sent as rawimage/gif, so Gemini perceives the full animation instead of a single extracted frame);Small videos (≤14MB) go inline in a single request; larger ones automatically go through the Files API (upload → wait until ready → describe → delete the remote copy when done), up to a 2GB per-file limit;
low_resolutionis a cost-saving switch;personalets Gemini narrate in character;hintlets you feed in what a human already thinks the video is about, useful for abstract or meme-y content (the model is instructed to still describe what it actually sees, not just agree with you);promptfully overrides the built-in template.
describe_video_url— downloads a video from a direct link and runs it through the same pipeline (downloaded to a temp folder, deleted right after).Same parameters as
describe_video, justpathbecomesurl;Only works with direct video file links (something that resolves straight to a video file, ending in
.mp4/.mov/.webm/etc.). Platform watch pages (YouTube, TikTok, Bilibili, etc.) are not direct links and won't resolve — that would need something like yt-dlp, which is out of scope for now;500MB per-file cap; 15s connect / 300s total timeout; non-video pages, non-2xx responses, and oversized files all get a readable error.
view_media— returns a picture (either an image file, or one frame pulled from a video) as actual image content, so the calling model (Claude, etc.) can look at it directly — as opposed todescribe_video, which has Gemini watch the video and write text about it.Images (
png/jpg/jpeg/webp/gif): returned as-is, downscaled (never upscaled) if the longer side exceedsmax_dimension(default 1024, adjustable 16–4096). GIFs return only their first frame (usedescribe_videoif you want the full animation understood);Videos: pass
timestamp(in seconds) to grab that frame, or omit it to grab the frame at the midpoint; requires ffmpeg on the host machine (a clear error is returned if it's missing).
estimate_cost— a small helper that estimates roughly how many input tokens a video will cost before you send it (usesffprobefor real duration when available, otherwise estimates from file size).get_upload_url— a small helper that returns the upload endpoint URL so a claude.ai sandbox can push a chat-uploaded file to this server before describing it (see "HTTP mode" below). Under local stdio mode there is no upload endpoint, so calling this just returns "not needed — you're on the same machine, pass the local path directly."
Related MCP server: youtube-gemini-mcp
Installation
You'll need uv for dependency management, Python ≥3.11 (uv will
resolve this automatically from pyproject.toml), and ffmpeg / ffprobe on your PATH (used by
view_media for frame extraction/scaling and by estimate_cost for reading duration — describe_video
and describe_video_url don't need them; missing ffmpeg only breaks the first two, with a clear error
message).
cd /path/to/Gemini_Video_MCP
uv venv
uv pip install -e .Configure the API key
Copy .env.example to .env and fill in your key (free to get at
Google AI Studio):
GEMINI_API_KEY=your-key-here
GEMINI_MODEL=gemini-3.5-flashYou can also skip .env entirely and inject the key at registration time with -e GEMINI_API_KEY=...
(see below). If both are present, the environment variable wins; .env is only a fallback.
Environment variables
Variable | Required? | Default | Notes |
| Yes | — | Your Gemini API key |
| No |
| Model identifier |
| No |
| API base URL; only change this if you're proxying |
| No |
| One of |
| Only for | — | The sole access lock for HTTP mode; server refuses to start without a real value |
| No | none (falls back to | Public URL after tunneling; used by |
(All values above are placeholders — fill in your own and never commit .env; it's already in
.gitignore.)
Running locally (for debugging)
uv run python main.py # starts in stdio mode (normally launched by your Claude client, no need to run this by hand)Registering with Claude Code
Run this from inside the project directory (passing the key via -e):
claude mcp add gemini-video -e GEMINI_API_KEY=your-key-here -- uv run --directory /path/to/Gemini_Video_MCP python main.pyIf you've already set up .env, you can drop the -e:
claude mcp add gemini-video -- uv run --directory /path/to/Gemini_Video_MCP python main.pyVerify it registered:
claude mcp listRegistering with Claude Desktop
Edit Claude Desktop's config file (Windows: %APPDATA%\Claude\claude_desktop_config.json) and add
this under mcpServers:
{
"mcpServers": {
"gemini-video": {
"command": "uv",
"args": [
"--directory",
"D:/path/to/Gemini_Video_MCP",
"run",
"python",
"main.py"
],
"env": {
"GEMINI_API_KEY": "your-key-here"
}
}
}
}Claude Desktop can't hand a chat-uploaded video straight to an MCP tool. To have Claude read a video from your machine, just type out the video's full file path in the conversation (e.g.
D:/videos/cat.mp4).
HTTP mode (for claude.ai / mobile remote use)
The default stdio mode can only be launched by a Claude client running on the same machine. If you want claude.ai's web app or mobile app to use this video tool too, you need HTTP mode: the server opens a port locally, you expose that port to the internet, and then you add it in claude.ai as a Custom Connector.
The short version: HTTP mode serves on local port 8768; a tunnel (e.g. Cloudflare Tunnel) exposes that port to the internet; the secret baked into the URL path is the only lock on the door — without it, nobody gets in.
Step 1: set an access secret
The secret gets embedded in the URL path (/mcp/<secret>), and it's the only thing standing between
this port and the public internet, so it needs to be long and random.
Generate a random secret (run this from a terminal in the project directory):
uv run python -c "import secrets; print(secrets.token_urlsafe(32))"Paste the output into this line in your
.env(copy.env.examplefirst if you don't have one yet):GEMINI_MCP_HTTP_SECRET=paste-the-generated-string-hereNever share this secret or screenshot it — leaking it is the same as handing someone the front door key. If it's left blank or still a placeholder, the server will refuse to start with
--http(this is intentional, so it never goes online unprotected).
Step 2: start the HTTP server
Double-click start_http.bat in the project directory (or run uv run python main.py --http in a
terminal). Once it's up, the local address is:
http://localhost:8768/mcp/<your-secret>Keep that terminal window open — closing it stops the server.
Step 3: expose it to the internet with a tunnel (Cloudflare Tunnel recommended)
If you already have a tunnel set up for other local services, you can reuse it — just point a new
public hostname at http://localhost:8768. Using Cloudflare Tunnel as an example:
Open the Cloudflare Zero Trust dashboard → Networks → Tunnels → select your tunnel → Configure.
On the Public Hostname tab, click Add a public hostname:
Subdomain: pick something like
gemini-video; Domain: your own domain (the final address will begemini-video.yourdomain.com).Service → Type:
HTTP, URL:http://localhost:8768(note: http, localhost, port 8768).
Save and wait a minute or two for DNS to propagate.
Just want to try it once, without touching the dashboard? Run a throwaway command (the address changes every time, so it's not for long-term use):
cloudflared tunnel --url http://localhost:8768It prints a
https://random-name.trycloudflare.comaddress you can use once and discard.(Alternative: if you'd rather use Tailscale instead of Cloudflare, the equivalent is
tailscale funnel 8768, which gives you ahttps://<machine-name>.<tailnet>.ts.netpublic address — append/mcp/<secret>the same way.)
Step 4: add it as a Custom Connector in claude.ai
Open claude.ai → avatar → Settings → Connectors.
Click Add custom connector.
Name: anything you like, e.g.
Gemini Video.Remote MCP server URL: your public address plus the secret path:
https://gemini-video.yourdomain.com/mcp/<your-secret>(swap in whatever domain you set up in Step 3, and the secret you generated in Step 1.)
Save. Claude should connect and list all five tools —
describe_video/describe_video_url/view_media/estimate_cost/get_upload_url— which means it worked.
From then on you can ask Claude to describe videos right from claude.ai. Note that in remote mode, Claude reads file paths on the server machine, not files on your phone.
Custom Connectors typically require a paid Claude plan (Pro/Max/etc.); the exact menu wording may vary by app version.
Letting the claude.ai sandbox push files to your machine (upload endpoint)
In remote mode, describe_video still only reads paths on the server machine — files uploaded in
a claude.ai chat or downloaded inside its sandbox can't reach your machine on their own. That's what
the --http mode's extra upload endpoint is for:
POST http://localhost:8768/upload/<your-secret>It reuses the same GEMINI_MCP_HTTP_SECRET (a wrong secret in the path just 404s). Have claude.ai's
sandbox run a snippet that POSTs the file it has as multipart form data (swap in your tunnel domain
and secret):
import requests
# The sandbox already has a file (chat-uploaded, or downloaded/generated by the sandbox itself), e.g. /tmp/clip.mp4
resp = requests.post(
"https://gemini-video.yourdomain.com/upload/<secret>",
files={"file": open("/tmp/clip.mp4", "rb")},
)
print(resp.json())
# -> {"saved_path": "/path/to/Gemini_Video_MCP/temp_media/20260714_..._clip.mp4", "size_mb": 3.2, "hint": "pass saved_path to describe_video"}Once you have saved_path, just have Claude call describe_video (or view_media) with it.
You don't need to explain any of this to Claude by hand: the server ships a
get_upload_urltool plus server-level instructions, so Claude in claude.ai will automatically callget_upload_urlwhen it sees a chat-uploaded file, POST it from the sandbox, and use the returnedsaved_path. You just upload a video and say "take a look at this." Prerequisites:GEMINI_MCP_PUBLIC_BASE_URL(your public domain) is set in.env, and the claude.ai sandbox has outbound internet access. Note thatget_upload_url's response contains the full secret (that's what lets the sandbox POST), so it will show up in that conversation — if that bothers you, discard that chat afterward or rotate the secret periodically.
Constraints: 500MB per file; filenames are sanitized (to prevent path traversal) and prefixed with a
timestamp (to prevent overwrites); the caller can never choose a storage path — everything lands in
the server's own temp_media/ directory (auto-pruned, oldest first, once the directory exceeds 2GB
total). The stdio/local mode does not expose this endpoint.
Example usage (in conversation)
"Describe the video at
D:/videos/monkey.mp4for me" → triggersdescribe_video."Narrate
D:/videos/dance.mp4like a snarky high schooler" → Claude passes apersona."This video is people playing five-in-a-row with eggs, describe
D:/videos/eggs.mp4" → Claude puts your description intohintso the model can make sense of something abstract or meme-y."Describe this direct video link:
https://example.com/clip.mp4" → triggersdescribe_video_url(downloads, then describes)."Show me what
D:/videos/monkey.mp4looks like at the 8-second mark" → triggersview_media(pulls the frame at 8s for Claude to actually look at)."Take a look at this picture,
D:/pics/meme.png" → triggersview_media(feeds the image straight to Claude)."About how many tokens would it cost to describe
D:/videos/long.mp4?" → triggersestimate_cost.Want to save money on a long video? Have Claude set
low_resolution=True.
Cost notes
Gemini charges input tokens by video duration:
Mode | Rate | 1-minute video | Notes |
Standard (default) | ~300 tokens/sec | ~18k input tokens | More visual detail |
Low-res ( | ~100 tokens/sec | ~6k input tokens | Cheaper for long videos, coarser detail |
These figures are input tokens only; output tokens are billed separately depending on how long the
description ends up being. Run estimate_cost before sending a large video if you want to know what
you're in for.
The thinking-token gotcha (already mitigated)
Gemini 3.5's "thinking" tokens also count against maxOutputTokens. If the output budget is too small,
or the thinking level too high, thinking can crowd out the visible output entirely and truncate the
description.
This server defaults to thinking_level=high (empirically, more thinking produces noticeably better
prose) and correspondingly defaults max_output_tokens to 8192 to leave room for it; if truncation is
detected, a note is appended to the result. To save money, set GEMINI_THINKING_LEVEL=minimal in
.env (thinking tokens are billed as output tokens). If the model doesn't support the configured
thinking level, the server automatically retries once at low.
Known limitations
.mkvisn't on Gemini's officially supported list; this server sends it asvideo/x-matroskaon a best-effort basis. If it gets rejected, convert tomp4first.Files API cap is 2GB per file; trim or compress anything larger.
Official video duration limits: roughly 1 hour at standard resolution, roughly 3 hours at low resolution (
low_resolution=True); split up anything longer.If you swap
GEMINI_MODELfor a Gemini 2.5-series model: 2.5 usesthinkingBudgetinstead ofthinkingLevel, and thethinkingLevelthis server sends by default may get rejected with a 400. The default 3.5-flash doesn't have this issue.estimate_costfalls back to a rough size-based duration estimate whenffprobeisn't available, which can be noticeably off (the result says so).view_mediadepends on ffmpeg on the host machine (frame extraction/scaling; tested against ffmpeg 8.0); a clear error is returned if it's missing. GIFs only return their first frame — usedescribe_videoto have the full animation understood.describe_video_urlonly supports direct video file links, not platform watch pages (YouTube/TikTok/Bilibili/etc.); 500MB per file, 15s connect / 300s total timeout. When called remotely from claude.ai, download time stacks on top of the tunnel's own proxy timeout (e.g. ~100s for Cloudflare), so large files are more likely to get cut off — for large files, prefer the upload endpoint to push the file to the server first, then describe it via the local path.The upload endpoint (
POST /upload/<secret>) only exists in--httpmode; 500MB per file, 2GB total fortemp_media/(oldest files pruned first once that's exceeded).Transport: stdio (local) by default;
--httpis an optional remote mode (see "HTTP mode" above).The ~100-second Cloudflare proxy timeout (524) in HTTP mode: Cloudflare's proxy cuts off any single request that goes unanswered for about 100 seconds, returning a 524. Small videos going inline are usually fine (15–60s); large videos routed through the Files API can take several minutes and are easily cut off when called remotely from claude.ai. For large files, local stdio mode (Claude Code / Desktop) is still the way to go — that path doesn't have this timeout at all.
License
MIT — do whatever you want with it, no warranty implied.
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Latest Blog Posts
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/SolenmeChiara/Gemini_Video_MCP'
If you have feedback or need assistance with the MCP directory API, please join our Discord server