# qencode-mcp Model Context Protocol (MCP) server for the [Qencode Transcoding API](https://docs.qencode.com/api-reference/transcoding). Connect any MCP-compatible AI client — Claude, Cursor, ChatGPT, Grok, Gemini — to your Qencode account and let it submit, monitor, and reason about transcoding jobs on your behalf. ## Quick example Once your client is connected (see [Connect a client](#connect-a-client)), ask your agent in plain English: > Transcode `https://example.com/input.mp4` as an HLS ladder at 1080/720/540/360. Put it in my R2 bucket `videos/demo/`. The agent picks the `hls_abr` recipe, fills in the per-rendition encoding params, submits via `start_encode2_raw`, and polls until the job is done. ## Prerequisites - A **Qencode portal account** with at least one project — sign in at the portal for your environment: (production) or (QA). You select the project during the OAuth consent step. - An **MCP-compatible client** (Claude, Cursor, ChatGPT, Grok, Gemini, or any custom client). There are no API keys to copy into client config — authentication is browser-based OAuth. ## How it works The connector uses standard OAuth 2.1 — no API keys in client config. On first use, your client opens a browser, you sign in to your Qencode portal account, pick a project, and approve the requested scopes. The client stores the token; subsequent calls are silent until the token expires. **Scopes the client should request at authorize time** (published via Protected Resource Metadata): | Scope | Purpose | | ----- | ------- | | `openid` | OIDC identity | | `profile` | Display name | | `email` | Account email | | `offline_access` | Refresh token | | `transcoding:read` | `get_job_status`, `list_jobs`, docs tools | | `transcoding:write` | `transcode_video`, `start_encode2_raw` | The RS enforces `transcoding:read` and `transcoding:write` on access tokens at the transport layer. Your Qencode API keys never leave the portal. The MCP server derives a short-lived session token per request via an internal portal endpoint. ## Environments The same connector is deployed in two environments. Each has its own domains, accounts, projects, and credentials — sign in to the portal that matches the endpoint you connect to. | Role | Production | QA (testing) | | --- | --- | --- | | MCP endpoint (connect here) | `https://mcp.qencode.com/mcp` | `https://mcp-qa.qencode.com/mcp` | | Portal (sign in / projects) | `https://portal.qencode.com` | `https://portal-qa.qencode.com` | | Authorization server | `https://auth.qencode.com` | `https://auth-qa.qencode.com` | | Qencode API | `https://api.qencode.com` | `https://api-qa.qencode.com` | The instructions below use the **production** endpoint. To test against QA, swap in the QA URL and sign in at the QA portal. ## Connect a client **Endpoint:** `https://mcp.qencode.com/mcp` — same for every client below. Sign in to your Qencode account when the browser opens and approve access. > **QA (internal testing):** use `https://mcp-qa.qencode.com/mcp` and sign in at the QA portal instead. | Client | Where to add it | MCP URL / config | | --- | --- | --- | | **Claude** (chat) | Message box → **+** → Connectors → Add connector | `https://mcp.qencode.com/mcp` | | **Claude Code** | Terminal | `claude mcp add --transport http qencode https://mcp.qencode.com/mcp` | | **ChatGPT** | Apps → search **Qencode** → Connect; or Developer Mode → Build app | Connector URL: `https://mcp.qencode.com/mcp` | | **Gemini** | `~/.gemini/settings.json` → `mcpServers` | `"httpUrl": "https://mcp.qencode.com/mcp"` — then `/mcp auth qencode` in the CLI | | **Cursor** | Settings → Tools & MCP → New MCP Server (or `~/.cursor/mcp.json`) | `"url": "https://mcp.qencode.com/mcp"` — restart Cursor after saving | **Cursor** (`mcp.json`): ```json { "mcpServers": { "qencode": { "url": "https://mcp.qencode.com/mcp" } } } ``` **Gemini** (`settings.json`): ```json { "mcpServers": { "qencode": { "httpUrl": "https://mcp.qencode.com/mcp", "timeout": 30000, "trust": false } } } ``` Tip: sign in to [portal.qencode.com](https://portal.qencode.com) in your browser before connecting — OAuth goes smoother. ## What the connector exposes ### Tools **Transcoding & jobs** | Tool | Description | | ---- | ----------- | | `transcode_video` | Submit a job from a source URL to one or more outputs. Convenience wrapper — auto-injects `encoder_version: 2` (or `1` for VMAF) when omitted. | | `start_encode2_raw` | Escape hatch — submit a job with the full `query` JSON exactly as the [Qencode API](https://docs.qencode.com/api-reference/transcoding/#start_encode2___query__attributes--format__attributes) expects. | | `get_job_status` | One-shot status snapshot by `task_token`. | | `get_job_status_detailed` | Full, authoritative job status, including per-rendition progress and output details. | | `list_jobs` | Inline jobs card for the `task_tokens` of the current conversation — status badges, filters, expandable rows, output URLs. The card refreshes itself while any job is still running. | | `fetch_job_result` | Read the contents of a result file produced by an analysis job (transcript, VMAF report, metadata, categorization). | | `wait_for_job` | Deprecated. Returns immediately and points at `list_jobs`; kept so older conversations do not hit "tool not found". | | `search_qencode_docs` | Search the built-in knowledge base of recipes and reference docs. | | `fetch_qencode_doc` | Fetch the full content of a knowledge-base resource by `qencode://` URI (tool-based counterpart to `resources/read`). | **Playback** | Tool | Description | | ---- | ----------- | | `open_player` | Render a playable result inline — progressive MP4/WebM or an HLS/DASH manifest. The server applies the calling host's sandbox policy, so which source origins are allowed depends on the client. | | `refresh_jobs` | The jobs card's own silent poll. Not meant to be called directly by an agent; it backs the card's auto-refresh and Refresh button. | **Media Storage** Bucket management and ingest for Qencode Media Storage. These ride the same OAuth grant as the transcoding tools — no extra scope and no re-consent. | Tool | Description | | ---- | ----------- | | `list_buckets` | List the Media Storage buckets available to the account. | | `create_bucket` | Create a new bucket. Called only on an explicit request — not to satisfy a missing `destination`. | | `list_objects` | Browse the contents of a bucket. | | `get_download_url` | Return a time-limited download URL for an existing object. | | `download_url_to_bucket` | Server-side copy of a public URL into a bucket (ingest, no transcoding). | ### Resources The server ships a knowledge base of recipes and reference docs, exposed as MCP resources so the agent can fetch only what it needs. Notable URIs: - `qencode://docs/best-practices` — composition defaults the agent applies automatically - `qencode://docs/storage` — destination compatibility matrix (Qencode S3, R2, AWS S3, Azure, B2, FTP/SFTP) - `qencode://docs/error-codes` — error code → cause → fix - `qencode://docs/gotchas` — non-obvious API quirks - `qencode://schema/digest` — full attribute reference for `start_encode2` - `qencode://recipe/` — one per feature flow: `ai_detection`, `ai_upscaling`, `audio_outputs`, `callbacks`, `clip_trim`, `codec_av1`, `codec_lcevc`, `drm_aes128`, `drm_buydrm`, `drm_doverunner`, `drm_expressplay`, `drm_fairplay_ezdrm`, `drm_forensic_watermark`, `drm_playready_ezdrm`, `drm_widevine_ezdrm`, `hdr_to_sdr`, `hls_abr`, `incremental_abr`, `mp4_ladder`, `per_title_encoding`, `refresh_abr_playlist`, `reliability`, `repack`, `rotate_deinterlace`, `smart_crop`, `smart_thumbnail`, `speech_to_text`, `stitching`, `subtitles`, `thumbnails`, `video_intelligence`, `video_metadata`, `vmaf_quality`, `vr_360`, `vr_mode`, `watermark_logo`, `waveform` Use `search_qencode_docs` to discover the right recipe URI for a goal. ### Prompts (slash commands) In clients that surface MCP prompts, **40** one-shot templates are available. Each tells the agent to read the matching `qencode://recipe/...` resource and submit via `start_encode2_raw`. **ABR / packaging:** `encode_hls_abr`, `encode_dash_abr`, `encode_mp4_ladder`, `encode_incremental_rung`, `encode_refreshing_playlist`, `remux_repack` **Codecs / quality:** `encode_av1`, `encode_lcevc`, `tune_per_title`, `check_vmaf`, `ai_upscale_video`, `convert_hdr_to_sdr` **Audio / images / text:** `extract_audio`, `generate_thumbnails`, `generate_smart_thumbnails`, `generate_waveform`, `transcribe`, `add_subtitles` **Editing / framing:** `trim_clip`, `rotate_video`, `deinterlace_video`, `enable_smart_crop`, `add_watermark`, `add_html_overlay` **Immersive:** `enable_vr_mode`, `inject_360_metadata` **Analysis:** `get_video_metadata`, `analyze_video`, `detect_ai_generated` **Probe / stitch:** `stitch_videos` **Production hooks:** `enable_callbacks`, `enable_reliability` **DRM:** `encode_aes128_hls`, `encode_widevine_ezdrm`, `encode_playready_ezdrm`, `encode_fairplay_ezdrm`, `encode_drm_buydrm`, `encode_drm_expressplay`, `encode_drm_doverunner`, `encode_forensic_watermark` ### Source URL rules `transcode_video` and `start_encode2_raw` accept `source` values with schemes `https://`, `http://`, `s3://`, or `tus:`. FTP/SFTP and private/metadata URLs are rejected at the tool boundary (SSRF defence). See `docs/security/THREAT_MODEL.md` for limitations. ## Security Authentication is OAuth 2.1 only — there is no static-API-key mode. Your Qencode API keys never leave the portal; the server derives a fresh, short-lived session token per request via an internal portal endpoint. Source URLs are validated at the tool boundary (SSRF defence — see [Source URL rules](#source-url-rules)). Full threat model and adversarial test coverage: [`docs/security/THREAT_MODEL.md`](docs/security/THREAT_MODEL.md). ## Development ```bash uv venv && uv pip install -e ".[dev]" NO_NETWORK=1 pytest -q # offline L1 + L2 + L5 (~900 tests) pytest -m protocol # MCP wire conformance only pytest -m unit # per-tool logic (FakeQencode) pytest -m security # OWASP MCP Top 10 adversarial suite ``` Protocol tests run fully offline (mocked Authorization Server, portal, and Qencode API). **CI** is the Jenkins job `mcp_automated_tests` (`Jenkinsfile.manual`): manual checkboxes for any layer, nightly L3 cron, weekly L4 cron. ### Supported MCP protocol versions Clients negotiate a version at `initialize`. This server targets **[MCP 2025-11-25](https://modelcontextprotocol.io/specification/2025-11-25)** as the primary version. CI also runs conformance tests against **2025-06-18** because JSON-RPC batching behavior differs between earlier revisions. We do not claim support for **2025-03-26** or older wire semantics beyond what the underlying SDK negotiates. | Version | Support | Notes | | ----------- | -------------- | ------------------------------------------ | | 2025-11-25 | Primary | Streamable HTTP, resumable SSE where used | | 2025-06-18 | CI matrix | Regression guard for mid-2025 clients | | 2025-03-26 | Not targeted | Batching semantics differ from 2025-06-18 | ### Directory listings (Glama) The connector users install is the hosted endpoint above. For [Glama Servers](https://glama.ai/mcp/servers/Qencode-Corp/mcp) / awesome-mcp-servers scoring only, this repo also ships `qencode-mcp-inspect` (stdio, dummy env, `tools/call` refused). That is not a supported client transport. See [`docs/glama-release.md`](docs/glama-release.md). ### More docs - Local server / env vars: [`docs/local-development.md`](docs/local-development.md) - Test layers (L1–L5): [`docs/testing.md`](docs/testing.md) and [`tests/README.md`](tests/README.md) - L3 against live QA / PROD: [`tests/integration/README.md`](tests/integration/README.md) - L4 agent evals: [`evals/README.md`](evals/README.md) - Pre-release gate: [`docs/release-checklist.md`](docs/release-checklist.md) ## Versioning policy The connector follows [SemVer](https://semver.org/) applied to the **MCP surface** — tools, prompts, resources, OAuth scopes, and supported protocol versions. Qencode HTTP API changes are out of scope (they are the API's own concern, not the connector's). - **MAJOR** — a breaking surface change: a tool/prompt/resource is removed or renamed, a previously optional argument becomes required, an OAuth scope is added or tightened in a way that forces re-consent, or a supported MCP protocol version is dropped. - **MINOR** — a backward-compatible addition: a new tool/prompt/resource, a new optional argument, or a newly supported protocol version. - **PATCH** — no change to the surface shape: tool/prompt description rewordings, knowledge-base/doc updates, and bug fixes. Surface changes are guarded by snapshot tests under [`tests/protocol/`](tests/protocol). When you change the surface, regenerate the snapshots (`python scripts/regen_tools_snapshot.py`) and bump the version in the **same** PR: `pyproject.toml`, `src/qencode_mcp/__init__.py`, `server.json`, and a new `CHANGELOG.md` entry must all agree. ## Links - Changelog: [`CHANGELOG.md`](CHANGELOG.md) - OAuth 2.1 authorization-server spec: [`docs/oauth-spec.md`](docs/oauth-spec.md) - Qencode portal: (production) · (QA) - Qencode API reference: