EnriVision
# EnriVision
EnriVision is a **Model Context Protocol (MCP)** server over `stdio` that uploads local media to **EnriProxy** and returns **server-side extraction + model analysis**.
This is useful for media types that many MCP clients cannot read reliably (videos, audio, scanned PDFs, HEIC/AVIF, large files), while keeping the MCP server itself lightweight.
## What this project is
- An MCP server process your MCP host launches (OpenCode, Claude Code, Codex, etc.)
- A thin client for EnriProxy (resumable upload + structured output)
## Requirements
- Node.js `>= 24`
- A reachable EnriProxy server with these endpoints enabled:
- `POST /v1/uploads`
- `HEAD /v1/uploads/:id`
- `PATCH /v1/uploads/:id`
- `DELETE /v1/uploads/:id` (best-effort cleanup of orphaned upload sessions)
- `POST /v1/vision/analyze`
- `POST /v1/vision/segments` (cursor continuation for long analyses)
- `GET /v1/account/models` (fail-open vision-capability probe before upload)
- An EnriProxy API key (configured on the EnriProxy side)
## Install
```powershell
# Global install
npm install -g @bedolla/enrivision
# Or run without installing
npx -y @bedolla/enrivision@latest --help
```
## Build
```powershell
npm install
npm run typecheck
npm run build
```
## Usage
### 1) Configure your MCP host
EnriVision runs as an MCP server over `stdio`. Your MCP host is responsible for launching the process.
Example: global install
```jsonc
{
"EnriVision": {
"type": "stdio",
"command": "enrivision",
"args": [],
"env": {
"ENRIPROXY_URL": "http://127.0.0.1:8787",
"ENRIPROXY_API_KEY": "YOUR_ENRIPROXY_API_KEY",
"ENRIVISION_DEFAULT_LANGUAGE": "es"
}
}
}
```
Example: no install (always uses whatever npm currently tags as `latest`)
```jsonc
{
"EnriVision": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@bedolla/enrivision@latest"],
"env": {
"ENRIPROXY_URL": "http://127.0.0.1:8787",
"ENRIPROXY_API_KEY": "YOUR_ENRIPROXY_API_KEY",
"ENRIVISION_DEFAULT_LANGUAGE": "es"
}
}
}
```
<details>
<summary>Use a local dev checkout</summary>
```jsonc
{
"EnriVision": {
"type": "stdio",
"command": "node",
"args": ["C:\\Users\\Administrator\\Projects\\EnriVision\\dist\\index.js"],
"env": {
"ENRIPROXY_URL": "http://127.0.0.1:8787",
"ENRIPROXY_API_KEY": "YOUR_ENRIPROXY_API_KEY",
"ENRIVISION_DEFAULT_LANGUAGE": "es"
}
}
}
```
</details>
## Configuration
EnriVision is configured via environment variables:
- `ENRIPROXY_URL` (`string`, optional, default: `http://127.0.0.1:8787`)
- `ENRIPROXY_API_KEY` (`string`, required)
- `ENRIVISION_TIMEOUT_MS` (`string`, optional, default: `1800000`)
- Parsed as an integer (milliseconds). This is the operator cap: the per-call analyze timeout is `min(operator, mode budget)` with `single` = 10 min (one pass, fast/cheap), `multipass`/`auto` = 20 min (per-segment/batch map + reduce; `auto` may escalate to multipass server-side). Uploads are performed in chunks; per-chunk timeouts honor `min(operator, derived 30s..300s)` floored at 30 s (an operator budget below 30 s never forces tighter single-chunk budgets).
- `ENRIVISION_DEFAULT_LANGUAGE` (`string`, optional)
- Default language to send when the tool call does not provide `language`.
- `ENRIVISION_DENY_SYMLINKS` (`string`, optional)
- Set to `1` to reject symlinked `path`/`paths` inputs. Strict mode opens with `O_NOFOLLOW` (POSIX) and compares the `dev:ino` handle identity from `fstat`. On Windows (`win32`) `O_NOFOLLOW` is `0` (advisory only), so strict mode there rests solely on the `lstat`-vs-`fstat` comparison with a small swap window: prefer POSIX hosts when symlink races are in scope.
- `ENRIVISION_MODEL` (`string`, optional)
- Model id for server-side dispatch affinity; omit for auto-dispatch.
- `ENRIVISION_QUIET` (`string`, optional)
- Set to `1` to silence upload/retry progress lines on stderr.
## Analysis budgets
The client analyze timeout is `min(ENRIVISION_TIMEOUT_MS, mode budget)`:
- `single` → 10 min (mirrors EnriCode and the EnriProxy single-pass stage budget).
- `multipass` → 20 min (mirrors EnriCode and the server multipass wall-clock budget).
- `auto` (default) → 20 min: the server picks the mode and may escalate to multipass, so the client cannot assume the short budget. If unsure, omit tuning (`auto`).
## Error shape
Tool failures return MCP `isError` with Spanish-first bilingual text (ES first, EN second) plus machine-readable `structuredContent: { code, retryable, httpStatus? }` reusing the EnriCode vocabulary:
- `ENRICODE_ERR_TOOL_INPUT_INVALID` — argument/tuning errors (including proxy 400/422). Never retry unchanged (`retryable: false`).
- `ENRICODE_ERR_TOOL_EXECUTION_FAILED` — server/transport failures. `retryable` is true for 408/429/5xx, false otherwise.
- `ENRICODE_ERR_TOOL_EXECUTION_TIMEOUT` — expired upload/analyze budgets (`retryable: true`; retry with a smaller scope).
- `ENRICODE_ERR_TOOL_EXECUTION_ABORTED` — caller-cancelled (`retryable: false`).
`httpStatus` is present only when the failure carries a proxy HTTP status.
## MCP tools
EnriVision exposes this MCP tool:
- `analyze_media`
<details>
<summary>Tool inputs (option-by-option)</summary>
General notes:
- The tool accepts a single JSON object as its input (the MCP `arguments`).
- At least one of `path`, `paths`, or `cursor` is required. When `paths` carries at least one valid entry, `path` is ignored (explicit ignore-path contract: sending both is allowed, `path` is silently ignored — prefer oneOf semantics and send only one). A `cursor` (from a truncated response) reads the next window of the list without uploading or analyzing anything.
- Paths must be absolute on the machine running the MCP server, or http(s) URLs. URLs are downloaded to a temporary directory on the MCP host (up to 64 MiB each; localhost and private-network destinations are blocked) and deleted after analysis. A solitary URL above 64 MiB escalates to EnriProxy's server-side `source_url` ingestion (resumable download with extra hops); local files use resumable upload up to 4 GiB.
- EnriVision does not accept per-call `server_url`/`api_key` overrides (these are configured via env vars).
### `analyze_media`
Inputs:
- `path` (`string`, optional): absolute local file path, or one http(s) URL to download and analyze (up to 64 MiB; a solitary larger URL escalates to server-side `source_url` ingestion).
- `paths` (`string[]`, optional): absolute local image paths or http(s) image URLs (useful for UI screenshot sets).
- `context` (`string`, optional): high-level hint (examples: `ui`, `diagram`, `chart`, `error`, `code`, `meeting`, `tutorial`, `photo`).
- `question` (`string`, optional): what you want to extract/answer.
- `language` (`string`, optional): preferred response language (ISO 639-1; e.g., `es`, `en`). If omitted, uses `ENRIVISION_DEFAULT_LANGUAGE` when set.
- `analysis_mode` (`string`, optional): `auto` | `single` | `multipass`.
- `max_frames` (`number`, optional): single-pass video frames (`1..20`).
- `model` (`string`, optional): model id for server-side dispatch affinity (max 128 chars; env `ENRIVISION_MODEL`; omit for auto-dispatch).
- `region` (`object`, optional): relative `[0,1]` zoom box `{x, y, width, height}` for one image (native-resolution reading of small text); single images only.
- `transcribe` (`boolean`, optional): enable/disable transcription (videos). Has no effect on images/documents (declared in `warnings`, ignored).
- `transcription_language` (`string`, optional): whisper hint (`auto`, `es`, `en`, ...).
Continuation:
- `cursor` (`string`, optional): opaque cursor from a truncated response (`segment_summaries_cursor` or `transcription_segments_cursor`); reads the next window without re-analyzing.
- `offset` (`integer`, optional): continuation start index (defaults to the response `next_offset`).
- `limit` (`integer`, optional): continuation window length (`1..100`; defaults to the server window size).
Video targeting:
- `video.clip_start_seconds` (`number`, optional)
- `video.clip_duration_seconds` (`number`, optional)
- `video.clip_end_seconds` (`number`, optional; end = start + duration, wins over `clip_duration_seconds`)
Multipass tuning (advanced; used only for `analysis_mode: multipass`):
- `video.segment_seconds` (`number`, optional)
- `video.max_segments` (`number`, optional)
- `video.max_frames_per_segment` (`number`, optional)
- `document.max_pages_total` (`number`, optional)
- `document.pages_per_batch` (`number`, optional)
- `document.max_images_per_batch` (`number`, optional)
- `document.scanned_text_threshold_chars` (`number`, optional)
- `audio.timestamps` (`boolean`, optional)
- `audio.segment_seconds` (`number`, optional)
- `audio.max_segments` (`number`, optional)
- `images.max_images_total` (`number`, optional)
- `images.images_per_batch` (`number`, optional)
- `images.max_dimension` (`number`, optional)
Output:
- `analysis` (`string`): model-produced analysis.
- `media_type` (`string`): detected media type (`video`, `audio`, `image`, `document`, `image_set`).
- `extraction` (`object`): safe metadata summary (internal routing details are stripped).
Example `arguments` object:
```jsonc
{
"path": "C:\\path\\to\\video.mp4",
"question": "What are the key steps demonstrated?",
"analysis_mode": "auto",
"transcribe": true,
"language": "es"
}
```
</details>
<details>
<summary>Claude Code CLI Read(...) compatibility (reference)</summary>
Many MCP clients include a built-in `Read(...)` tool that can ingest local files and attach them to the model request.
This is convenient, but the set of supported formats is limited and can change across client versions.
If the file you need to analyze is not reliably supported by your client (for example `.avif`, `.heic`, `.svg`, videos,
audio, or Office documents), prefer EnriVision MCP so the client can upload bytes and EnriProxy can do extraction reliably.
</details>
<details>
<summary>Supported media types (by extension)</summary>
EnriProxy determines media type using content-type and extension allow-lists.
Videos:
- `.mp4`, `.mov`, `.avi`, `.mkv`, `.webm`, `.m4v`, `.wmv`, `.flv`, `.3gp`, `.3g2`, `.ts`, `.mts`, `.m2ts`, `.mpeg`, `.mpg`, `.gif`
Audio:
- `.mp3`, `.mp1`, `.mp2`, `.mpa`, `.mpga`, `.wav`, `.aiff`, `.aif`, `.aifc`, `.caf`, `.flac`, `.m4a`, `.m4b`, `.m4r`, `.aac`, `.ogg`, `.oga`, `.wma`, `.opus`, `.weba`, `.mka`
Images:
- `.png`, `.apng`, `.jpg`, `.jpeg`, `.gif`, `.webp`, `.avif`, `.heic`, `.heif`, `.tiff`, `.tif`, `.bmp`, `.svg`, `.ico`
Documents:
- `.pdf`, `.docx`, `.pptx`, `.xlsx`
</details>
TDQS
Scored across 1 tool
Only one tool exists, so there is zero risk of an agent selecting the wrong tool for a task. The single analyze_media tool unambiguously covers any media-analysis request, with no overlapping purposes to confuse.
analyze_media follows the standard verb_noun convention used across well-designed MCP servers. While a single tool offers little evidence of a broader pattern, the name is self-consistent, descriptive, and aligned with common conventions.
A single tool sits at the thin end of the calibration range, where 1-2 tools feels sparse. This is partially redeemed by the tool being a deliberately consolidated mega-tool that absorbs image, video, audio, and document analysis into one entry, but that pushes substantial complexity into a single schema.
For the server's stated purpose — media analysis — coverage is remarkably complete: static and animated images, video with shared audio timeline, transcription, PDFs, Office documents, multi-image sets, video clipping, multipass budgets, and continuation cursors for long outputs. An agent can fully accomplish the server's job with this one tool and no obvious dead ends.