{
  "markdown": "# media-gen-mcp\n\n<p align=\"center\">\n  <a href=\"https://www.npmjs.com/package/media-gen-mcp\"><img src=\"https://img.shields.io/npm/v/media-gen-mcp?label=media-gen-mcp&color=brightgreen\" alt=\"media-gen-mcp\"></a>\n  <a href=\"https://www.npmjs.com/package/@modelcontextprotocol/sdk\"><img src=\"https://img.shields.io/npm/v/@modelcontextprotocol/sdk?label=MCP%20SDK&color=blue\" alt=\"MCP SDK\"></a>\n  <a href=\"https://www.npmjs.com/package/openai\"><img src=\"https://img.shields.io/npm/v/openai?label=OpenAI%20SDK&color=blueviolet\" alt=\"OpenAI SDK\"></a>\n  <a href=\"https://github.com/punkpeye/mcp-proxy\"><img src=\"https://img.shields.io/github/stars/punkpeye/mcp-proxy?label=mcp-proxy&style=social\" alt=\"mcp-proxy\"></a>\n  <a href=\"https://github.com/yjacquin/fast-mcp\"><img src=\"https://img.shields.io/github/stars/yjacquin/fast-mcp?label=fast-mcp&style=social\" alt=\"fast-mcp\"></a>\n  <a href=\"https://github.com/strato-space/media-gen-mcp/blob/main/LICENSE\"><img src=\"https://img.shields.io/github/license/strato-space/media-gen-mcp?color=brightgreen\" alt=\"License\"></a>\n  <a href=\"https://github.com/strato-space/media-gen-mcp/stargazers\"><img src=\"https://img.shields.io/github/stars/strato-space/media-gen-mcp?style=social\" alt=\"GitHub stars\"></a>\n  <a href=\"https://github.com/strato-space/media-gen-mcp/actions\"><img src=\"https://img.shields.io/github/actions/workflow/status/strato-space/media-gen-mcp/main.yml?label=build&logo=github\" alt=\"Build Status\"></a>\n</p>\n\n---\n\n**Media Gen MCP** is a **strict TypeScript** Model Context Protocol (MCP) server for OpenAI Images (`gpt-image-1.5`, `gpt-image-1`), OpenAI Videos (Sora), and Google GenAI Videos (Veo): generate/edit images, create/remix video jobs, and fetch media from URLs or disk with smart `resource_link` vs inline `image` outputs and optional `sharp` processing. Production-focused (full strict typecheck, ESLint + Vitest CI). Works with fast-agent, Claude Desktop, ChatGPT, Cursor, VS Code, Windsurf, and any MCP-compatible client.\n\n**Design principle:** spec-first, type-safe image tooling – strict OpenAI Images API + MCP compliance with fully static TypeScript types and flexible result placements/response formats for different clients.\n\n- **Generate images** from text prompts using OpenAI's `gpt-image-1.5` model (with `gpt-image-1` compatibility and DALL·E support planned in future versions).\n- **Edit images** (inpainting, outpainting, compositing) from 1 up to 16 images at once, with advanced prompt control.\n- **Generate videos** via OpenAI Videos (`sora-2`, `sora-2-pro`) with job create/remix/list/retrieve/delete and asset downloads.\n- **Generate videos** via Google GenAI (Veo) with operation polling and file-first downloads.\n- **Fetch & compress images** from HTTP(S) URLs or local file paths with smart size/quality optimization.\n- **Fetch documents** from HTTP(S) URLs or local file paths and return `resource_link`/`resource` outputs.\n- **Debug MCP output shapes** with a `test-images` tool that mirrors production result placement (`content`, `structuredContent`, `toplevel`).\n- **Integrates with**: [fast-agent](https://github.com/strato-space/fast-agent), [Windsurf](https://windsurf.com), [Claude Desktop](https://www.anthropic.com/claude/desktop), [Cursor](https://cursor.com), [VS Code](https://code.visualstudio.com/), and any MCP-compatible client.\n\n---\n\n## ✨ Features\n\n- **Strict MCP spec support**  \n  Tool outputs are first-class [`CallToolResult`](https://github.com/modelcontextprotocol/spec/blob/main/schema/2025-11-25/schema.json) objects from the latest MCP schema, including:\n  `content` items (`text`, `image`, `resource_link`, `resource`), optional `structuredContent`, optional top-level `files`, and the `isError` flag for failures.\n\n- **Full gpt-image-1.5 and sora-2/sora-2-pro parameters coverage (generate & edit)**  \n  - [`openai-images-generate`](#openai-images-generate) mirrors the OpenAI Images [`create`](https://platform.openai.com/docs/api-reference/images/create) API for `gpt-image-1.5` (and `gpt-image-1`) (background, moderation, size, quality, output_format, output_compression, `n`, `user`, etc.).\n  - [`openai-images-edit`](#openai-images-edit) mirrors the OpenAI Images [`createEdit`](https://platform.openai.com/docs/api-reference/images/createEdit) API for `gpt-image-1.5` (and `gpt-image-1`) (image, mask, `n`, quality, size, `user`).\n\n- **OpenAI Videos (Sora) job tooling (create / remix / list / retrieve / delete / content)**  \n  - [`openai-videos-create`](#openai-videos-create) mirrors [`videos/create`](https://platform.openai.com/docs/api-reference/videos/create) and can optionally wait for completion.\n  - [`openai-videos-remix`](#openai-videos-remix) mirrors [`videos/remix`](https://platform.openai.com/docs/api-reference/videos/remix).\n  - [`openai-videos-list`](#openai-videos-list) mirrors [`videos/list`](https://platform.openai.com/docs/api-reference/videos/list).\n  - [`openai-videos-retrieve`](#openai-videos-retrieve) mirrors [`videos/retrieve`](https://platform.openai.com/docs/api-reference/videos/retrieve).\n  - [`openai-videos-delete`](#openai-videos-delete) mirrors [`videos/delete`](https://platform.openai.com/docs/api-reference/videos/delete).\n  - [`openai-videos-retrieve-content`](#openai-videos-retrieve-content) mirrors [`videos/content`](https://platform.openai.com/docs/api-reference/videos/content) and downloads `video` / `thumbnail` / `spritesheet` assets to disk, returning MCP `resource_link` (default) or embedded `resource` blocks (via `tool_result`).\n\n- **Google GenAI (Veo) operations + downloads (generate / retrieve operation / retrieve content)**  \n  - [`google-videos-generate`](#google-videos-generate) starts a long-running operation (`ai.models.generateVideos`) and can optionally wait for completion and download `.mp4` outputs. [Veo model reference](https://docs.cloud.google.com/vertex-ai/generative-ai/docs/model-reference/veo-video-generation)\n  - [`google-videos-retrieve-operation`](#google-videos-retrieve-operation) polls an existing operation.\n  - [`google-videos-retrieve-content`](#google-videos-retrieve-content) downloads an `.mp4` from a completed operation, returning MCP `resource_link` (default) or embedded `resource` blocks (via `tool_result`).\n\n- **Fetch and process images from URLs or files**  \n  [`fetch-images`](#fetch-images) tool loads images from HTTP(S) URLs or local file paths with optional, user-controlled compression (disabled by default). Supports parallel processing of up to 20 images.\n\n- **Fetch videos from URLs or files**  \n  [`fetch-videos`](#fetch-videos) tool lists local videos or downloads remote video URLs to disk and returns MCP `resource_link` (default) or embedded `resource` blocks (via `tool_result`).\n\n- **Fetch documents from URLs or files**  \n  [`fetch-document`](#fetch-document) tool downloads remote files or reuses local paths and returns MCP `resource_link` (default) or embedded `resource` blocks (via `tool_result`).\n\n- **Mix and edit up to 16 images**  \n  [`openai-images-edit`](#openai-images-edit) accepts `image` as a single string or an array of 1–16 file paths/base64 strings, matching the OpenAI spec for GPT Image models (`gpt-image-1.5`, `gpt-image-1`) image edits.\n\n- **Smart image compression**  \n  Built-in compression using [sharp](https://sharp.pixelplumbing.com/) — iteratively reduces quality and dimensions to fit MCP payload limits while maintaining visual quality.\n\n- **Resource-aware file output with `resource_link`**  \n  - Automatic switch from inline base64 to `file` when the total response size exceeds a safe threshold.\n  - Outputs are written to disk using `output_<time_t>_media-gen__<tool>_<id>.<ext>` filenames (images/documents use a generated UUID; videos use the OpenAI `video_id`) and exposed to MCP clients via `content[]` depending on `tool_result` (`resource_link`/`image` for images, `resource_link`/`resource` for video/document downloads).\n\n- **Built-in test-images tool for MCP client debugging**  \n  [`test-images`](#test-images) reads sample images from a configured directory and returns them using the same result-building logic as production tools. Use `tool_result` and `response_format` parameters to test how different MCP clients handle `content[]` and `structuredContent`.\n\n- **Structured MCP error handling**  \n  All tool errors (validation, OpenAI API failures, I/O) are returned as MCP errors with\n  `isError: true` and `content: [{ type: \"text\", text: <error message> }]`, making failures easy to parse and surface in MCP clients.\n\n---\n\n## 🚀 Installation\n\n```sh\ngit clone https://github.com/strato-space/media-gen-mcp.git\ncd media-gen-mcp\n\nnpm install\nnpm run build\n```\n\nBuild modes:\n\n- `npm run build` – strict TypeScript build with **all strict flags enabled**, including `skipLibCheck: false`. Incremental builds via `.tsbuildinfo` (~2-3s on warm cache).\n- `npm run esbuild` – fast bundling via esbuild (no type checking, useful for rapid iteration).\n\n### Development mode (no build required)\n\nFor development or when TypeScript compilation fails due to memory constraints:\n\n```sh\nnpm run dev  # Uses tsx to run TypeScript directly\n```\n\n### Quality checks\n\n```sh\nnpm run lint        # ESLint with typescript-eslint\nnpm run typecheck   # Strict tsc --noEmit\nnpm run test        # Unit tests (vitest)\nnpm run test:watch  # Watch mode for TDD\nnpm run ci          # lint + typecheck + test\n```\n\n### Unit tests\n\nThe project uses [vitest](https://vitest.dev/) for unit testing. Tests are located in `test/`.\n\n**Covered modules:**\n\n| Module | Tests | Description |\n|--------|-------|-------------|\n| `compression` | 12 | Image format detection, buffer processing, file I/O |\n| `helpers` | 31 | URL/path validation, output resolution, result placement, resource links |\n| `env` | 19 | Configuration parsing, env validation, defaults |\n| `logger` | 10 | Structured logging + truncation safety |\n| `pricing` | 5 | Sora pricing estimate helpers |\n| `schemas` | 69 | Zod schema validation for all tools, type inference |\n| `fetch-images` (integration) | 3 | End-to-end MCP tool call behavior |\n| `fetch-videos` (integration) | 3 | End-to-end MCP tool call behavior |\n\n**Test categories:**\n\n- **compression** — `isCompressionAvailable`, `detectImageFormat`, `processBufferWithCompression`, `readAndProcessImage`\n- **helpers** — `isHttpUrl`, `isAbsolutePath`, `isBase64Image`, `ensureDirectoryWritable`, `resolveOutputPath`, `getResultPlacement`, `buildResourceLinks`\n- **env** — config loading and validation for `MEDIA_GEN_*` / `MEDIA_GEN_MCP_*` settings\n- **logger** — truncation and error formatting behavior\n- **schemas** — validation for `openai-images-*`, `openai-videos-*`, `fetch-images`, `fetch-videos`, `test-images` inputs, boundary testing (prompt length, image count limits, path validation)\n\n```sh\nnpm run test\n# ✓ test/compression.test.ts (12 tests)\n# ✓ test/helpers.test.ts (31 tests)\n# ✓ test/env.test.ts (19 tests)\n# ✓ test/logger.test.ts (10 tests)\n# ✓ test/pricing.test.ts (5 tests)\n# ✓ test/schemas.test.ts (69 tests)\n# ✓ test/fetch-images.integration.test.ts (3 tests)\n# ✓ test/fetch-videos.integration.test.ts (3 tests)\n# Tests: 152 passed\n```\n\n### Run directly via npx (no local clone)\n\nYou can also run the server straight from a remote repo using `npx`:\n\n```sh\nnpx -y github:strato-space/media-gen-mcp --env-file /path/to/media-gen.env\n```\n\nThe `--env-file` argument tells the server which env file to load (e.g. when you keep secrets outside the cloned directory). The file should contain `OPENAI_API_KEY`, optional Azure variables, and any `MEDIA_GEN_MCP_*` settings.\n\n### `secrets.yaml` (optional)\n\nYou can keep API keys (and optional Google Vertex AI settings) in a `secrets.yaml` file (compatible with the fast-agent secrets template):\n\n```yaml\nopenai:\n  api_key: <your-api-key-here>\nanthropic:\n  api_key: <your-api-key-here>\ngoogle:\n  api_key: <your-api-key-here>\n  vertex_ai:\n    enabled: true\n    project_id: your-gcp-project-id\n    location: europe-west4\n```\n\n`media-gen-mcp` loads `secrets.yaml` from the current working directory (or from `--secrets-file /path/to/secrets.yaml`) and applies it to env vars; values in `secrets.yaml` override env, and `<your-api-key-here>` placeholders are ignored.\n\n---\n\n## ⚡ Quick start (fast-agent & Windsurf)\n\n### fast-agent\n\nIn fast-agent, MCP servers are configured in `fastagent.config.yaml` under the `mcp.servers` section (see the [fast-agent docs](https://github.com/strato-space/fast-agent)).\n\nTo add `media-gen-mcp` from GitHub via `npx` as an MCP server:\n\n```yaml\n# fastagent.config.yaml\n\nmcp:\n  servers:\n    # your existing servers (e.g. fetch, filesystem, huggingface, ...)\n    media-gen-mcp:\n      command: \"npx\"\n      args: [\"-y\", \"github:strato-space/media-gen-mcp\", \"--env-file\", \"/path/to/media-gen.env\"]\n```\n\nPut `OPENAI_API_KEY` and other settings into `media-gen.env` (see `.env.sample` in this repo).\n\n### Windsurf\n\nAdd an MCP server that runs `media-gen-mcp` from GitHub via `npx` using the JSON format below (similar to Claude Desktop / VS Code):\n\n```json\n{\n  \"mcpServers\": {\n    \"media-gen-mcp\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"github:strato-space/media-gen-mcp\", \"--env-file\", \"/path/to/media-gen.env\"]\n    }\n  }\n}\n```\n\n---\n\n## 🔑 Configuration\n\nAdd to your MCP client config (fast-agent, Windsurf, Claude Desktop, Cursor, VS Code):\n\n```json\n{\n  \"mcpServers\": {\n    \"media-gen-mcp\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"github:strato-space/media-gen-mcp\"],\n      \"env\": { \"OPENAI_API_KEY\": \"sk-...\" }\n    }\n  }\n}\n```\n\nAlso supports Azure deployments:\n\n```json\n{\n  \"mcpServers\": {\n    \"media-gen-mcp\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"github:strato-space/media-gen-mcp\"],\n      \"env\": {\n        // \"AZURE_OPENAI_API_KEY\": \"sk-...\",\n        // \"AZURE_OPENAI_ENDPOINT\": \"my.endpoint.com\",\n        \"OPENAI_API_VERSION\": \"2024-12-01-preview\"\n      }\n    }\n  }\n}\n```\n\nEnvironment variables:\n\n- Set `OPENAI_API_KEY` (and optionally `AZURE_OPENAI_API_KEY`, `AZURE_OPENAI_ENDPOINT`, `OPENAI_API_VERSION`) in the environment of the process that runs `node dist/index.js` (shell, systemd unit, Docker env, etc.).\n- The server will **optionally** load a local `.env` file from its working directory if present (it does not override already-set environment variables).\n- You can also pass `--env-file /path/to/env` when starting the server (including via `npx`); this file is loaded via `dotenv` before tools run, again without overriding already-set variables.\n\n### Logging and base64 truncation\n\nTo avoid flooding logs with huge image payloads, the built-in logger applies a\nlog-only sanitizer to structured `data` passed to `log.debug/info/warn/error`:\n\n- Truncates configured string fields (e.g. `b64_json`, `base64`, string\n  `data`, `image_url`) to a short preview controlled by\n  `LOG_TRUNCATE_DATA_MAX` (default: 64 characters). The list of keys defaults\n  to `LOG_SANITIZE_KEYS` inside `src/lib/logger.ts` and can be overridden via\n  `MEDIA_GEN_MCP_LOG_SANITIZE_KEYS` (comma-separated list of field names).\n- Sanitization is applied **only** to log serialization; tool results returned\n  to MCP clients are never modified.\n\nControl via environment:\n\n- `MEDIA_GEN_MCP_LOG_SANITIZE_IMAGES` (default: `true`)\n  - `1`, `true`, `yes`, `on` – enable truncation (default behaviour).\n  - `0`, `false`, `no`, `off` – disable truncation and log full payloads.\n\nField list and limits are configured in `src/lib/logger.ts` via\n`LOG_SANITIZE_KEYS` and `LOG_TRUNCATE_DATA_MAX`.\n\n### Security and local file access\n\n- **Allowed directories**: All tools are restricted to paths matching `MEDIA_GEN_DIRS`. If unset, defaults to `/tmp/media-gen-mcp` (or `%TEMP%/media-gen-mcp` on Windows).\n- **Test samples**: `MEDIA_GEN_MCP_TEST_SAMPLE_DIR` adds a directory to the allowlist and enables the `test-images` tool.\n- **Local reads**: `fetch-images` and `fetch-document` accept file paths (absolute or relative). Relative paths are resolved against the first `MEDIA_GEN_DIRS` entry and must still match an allowed pattern.\n- **Remote reads**: HTTP(S) fetches are filtered by `MEDIA_GEN_URLS` patterns. Empty = allow all.\n- **Writes**: `openai-images-generate`, `openai-images-edit`, `fetch-images`, `fetch-videos`, and `fetch-document` write under the first entry of `MEDIA_GEN_DIRS`. `test-images` is read-only and does not create new files.\n\n#### Glob patterns\n\nBoth `MEDIA_GEN_DIRS` and `MEDIA_GEN_URLS` support glob wildcards:\n\n| Pattern | Matches | Example |\n|---------|---------|---------|\n| `*` | Any single segment (no `/`) | `/home/*/media/` matches `/home/user1/media/` |\n| `**` | Any number of segments | `/data/**/images/` matches `/data/a/b/images/` |\n\nURL examples:\n```shell\nMEDIA_GEN_URLS=https://*.cdn.example.com/,https://storage.example.com/**/assets/\n```\n\nPath examples:\n```shell\nMEDIA_GEN_DIRS=/home/*/media-gen/output/,/data/**/images/\n```\n\n⚠️ **Warning**: Trailing wildcards without a delimiter (e.g., `/home/user/*` or `https://cdn.com/**`) expose entire subtrees and trigger a console warning at startup.\n\n#### Recommended mitigations\n\n1. Run under a dedicated OS user with access only to allowed directories.\n2. Keep allowlists minimal. Avoid `*` in home directories or system paths.\n3. Use explicit `MEDIA_GEN_URLS` prefixes for remote fetches.\n4. Monitor allowed directories via OS ACLs or backups.\n\n### Tool Result Parameters: `tool_result` and `response_format`\n\nImage tools (`openai-images-*`, `fetch-images`, `test-images`) support two parameters that control the shape of the MCP tool result:\n\n| Parameter | Values | Default | Description |\n|-----------|--------|---------|-------------|\n| `tool_result` | `resource_link`, `image` | `resource_link` | Controls `content[]` shape |\n| `response_format` | `url`, `path`, `b64_json` | `url` | Controls `structuredContent` shape (OpenAI ImagesResponse format) |\n\nVideo/document download tools (`openai-videos-create` / `openai-videos-remix` when downloading, `openai-videos-retrieve-content`, `google-videos-generate` when downloading, `google-videos-retrieve-content`, `fetch-videos`, `fetch-document`) support:\n\n| Parameter | Values | Default | Description |\n|-----------|--------|---------|-------------|\n| `tool_result` | `resource_link`, `resource` | `resource_link` | Controls `content[]` shape |\n\nGoogle video tools (`google-videos-*`) also support:\n\n| Parameter | Values | Default | Description |\n|-----------|--------|---------|-------------|\n| `response_format` | `url`, `b64_json` | `url` | Controls `structuredContent.response.generatedVideos[].video` shape (`uri` vs `videoBytes`) |\n\n#### `tool_result` — controls `content[]`\n\n- **Images** (`openai-images-*`, `fetch-images`, `test-images`)\n  - **`resource_link`** (default): Emits `ResourceLink` items with `file://` or `https://` URIs\n  - **`image`**: Emits base64 `ImageContent` blocks\n- **Videos** (tools that download video data)\n  - **`resource_link`** (default): Emits `ResourceLink` items with `file://` or `https://` URIs\n  - **`resource`**: Emits `EmbeddedResource` blocks with base64 `resource.blob`\n- **Documents** (`fetch-document`)\n  - **`resource_link`** (default): Emits `ResourceLink` items with `file://` or `https://` URIs\n  - **`resource`**: Emits `EmbeddedResource` blocks with base64 `resource.blob`\n\n#### `response_format` — controls `structuredContent`\n\nFor OpenAI images, `structuredContent` always contains an OpenAI ImagesResponse-style object:\n\n```jsonc\n{\n  \"created\": 1234567890,\n  \"data\": [\n    { \"url\": \"https://...\" } // or { \"path\": \"/abs/path.png\" } / { \"b64_json\": \"...\" } depending on response_format\n  ]\n}\n```\n\n- **`url`** (default): `data[].url` contains file URLs\n- **`path`**: `data[].path` contains local filesystem paths\n- **`b64_json`**: `data[].b64_json` contains base64-encoded image data\n\nFor Google videos, `response_format` controls whether `structuredContent.response.generatedVideos[].video` prefers:\n\n- **`url`** (default): `video.uri` (and strips `video.videoBytes`)\n- **`b64_json`**: `video.videoBytes` (and strips `video.uri`)\n\n#### Backward Compatibility (MCP 5.2.6)\n\nPer MCP spec 5.2.6, a `TextContent` block with serialized JSON (always using URLs in `data[]`) is also included in `content[]` for backward compatibility with clients that don't support `structuredContent`.\n\nExample tool result structure:\n\n```jsonc\n{\n  \"content\": [\n    // ResourceLink or ImageContent based on tool_result\n    { \"type\": \"resource_link\", \"uri\": \"https://...\", \"name\": \"image.png\", \"mimeType\": \"image/png\" },\n    // Serialized JSON for backward compatibility (MCP 5.2.6)\n    { \"type\": \"text\", \"text\": \"{ \\\"created\\\": 1234567890, \\\"data\\\": [{ \\\"url\\\": \\\"https://...\\\" }] }\" }\n  ],\n  \"structuredContent\": {\n    \"created\": 1234567890,\n    \"data\": [{ \"url\": \"https://...\" }]\n  }\n}\n```\n\n**ChatGPT MCP client behavior (chatgpt.com, as of 2025-12-01):**\n\n- ChatGPT currently ignores `content[]` image data in favor of `structuredContent`.\n- For ChatGPT, use `response_format: \"url\"` and configure the first `MEDIA_GEN_MCP_URL_PREFIXES` entry as a public HTTPS prefix (for example `MEDIA_GEN_MCP_URL_PREFIXES=https://media-gen.example.com/media`).\n\nFor Anthropic clients (Claude Desktop, etc.), the default configuration works well.\n\n### Network access via mcp-proxy (SSE)\n\nFor networked SSE access you can front `media-gen-mcp` with [`mcp-proxy`](https://github.com/modelcontextprotocol/servers/tree/main/src/proxy) or its equivalent. This setup has been tested with the TypeScript SSE proxy implementation [`punkpeye/mcp-proxy`](https://github.com/punkpeye/mcp-proxy).\n\nFor example, a one-line command looks like:\n\n```sh\nmcp-proxy --host=0.0.0.0 --port=99 --server=sse --sseEndpoint=/ --shell 'npx -y github:strato-space/media-gen-mcp --env-file /path/to/media-gen.env'\n```\n\nIn production you would typically wire this up via a systemd template unit that loads `PORT`/`SHELL_CMD` from an `EnvironmentFile=` (see `server/mcp/mcp@.service` style setups).\n\n---\n\n## 🛠 Tool signatures\n\n### openai-images-generate\n\nArguments (input schema):\n\n- `prompt` (string, required)\n  - Text prompt describing the desired image.\n  - Max length: 32,000 characters.\n- `background` (\"transparent\" | \"opaque\" | \"auto\", optional)\n  - Background handling mode.\n  - If `background` is `\"transparent\"`, then `output_format` must be `\"png\"` or `\"webp\"`.\n- `model` (\"gpt-image-1.5\" | \"gpt-image-1\", optional, default: \"gpt-image-1.5\")\n- `moderation` (\"auto\" | \"low\", optional)\n  - Content moderation behavior, passed through to the Images API.\n- `n` (integer, optional)\n  - Number of images to generate.\n  - Min: 1, Max: 10.\n- `output_compression` (integer, optional)\n  - Compression level (0–100).\n  - Only applied when `output_format` is `\"jpeg\"` or `\"webp\"`.\n- `output_format` (\"png\" | \"jpeg\" | \"webp\", optional)\n  - Output image format.\n  - If omitted, the server treats output as PNG semantics.\n- `quality` (\"auto\" | \"high\" | \"medium\" | \"low\", default: \"high\")\n- `size` (\"1024x1024\" | \"1536x1024\" | \"1024x1536\" | \"auto\", default: \"1024x1536\")\n- `user` (string, optional)\n  - User identifier forwarded to OpenAI for monitoring.\n- `response_format` (\"url\" | \"path\" | \"b64_json\", default: \"url\")\n  - Response format (aligned with OpenAI Images API):\n    - `\"url\"`: file/URL-based output (resource_link items, `image_url` fields, `data[].url` in `api` placement).\n    - `\"path\"`: local filesystem paths in `data[].path` (for local skill workflows).\n    - `\"b64_json\"`: inline base64 image data (image content, `data[].b64_json` in `api` placement).\n  - `tool_result` (\"resource_link\" | \"image\", default: \"resource_link\")\n    - Controls `content[]` shape:\n      - `\"resource_link\"` emits ResourceLink items (file/URL-based)\n      - `\"image\"` emits base64 ImageContent blocks\n\nBehavior notes:\n\n- The server uses OpenAI `gpt-image-1.5` by default (set `model: \"gpt-image-1\"` for legacy behavior).\n- If the total size of all base64 images would exceed the configured payload\n  threshold (default ~50MB via `MCP_MAX_CONTENT_BYTES`), the server\n  automatically switches the **effective output mode** to file/URL-based and saves\n  images to the first entry of `MEDIA_GEN_DIRS` (default: `/tmp/media-gen-mcp`).\n- Even when you explicitly request `response_format: \"b64_json\"`, the server still writes\n  the files to disk (for static hosting, caching, or later reuse). Exposure of\n  file paths / URLs in the tool result then depends on `MEDIA_GEN_MCP_RESULT_PLACEMENT`\n  and per-call `result_placement` (see section below).\n\nOutput (MCP CallToolResult, when placement includes `\"content\"`):\n\n- When the effective `output` mode is `\"base64\"`:\n  - `content` is an array that may contain:\n    - image items:\n      - `{ type: \"image\", data: <base64 string>, mimeType: <\"image/png\" | \"image/jpeg\" | \"image/webp\"> }`\n    - optional text items with revised prompts returned by the Images API (for models that support it, e.g. DALL·E 3):\n      - `{ type: \"text\", text: <revised_prompt string> }`\n- When the effective `output` mode is `\"file\"`:\n  - `content` contains one `resource_link` item per file, plus the same optional `text` items with revised prompts:\n    - `{ type: \"resource_link\", uri: \"file:///absolute-path-1.png\", name: \"absolute-path-1.png\", mimeType: <image mime> }`\n  - For `gpt-image-1.5` and `gpt-image-1`, an additional `text` line is included with a pricing estimate (based on `structuredContent.usage`), and `structuredContent.pricing` contains the full pricing breakdown.\n\nWhen `result_placement` includes `\"api\"`, `openai-images-generate` instead returns an **OpenAI Images API-like object** without MCP wrappers:\n\n```jsonc\n{\n  \"created\": 1764599500,\n  \"data\": [\n    { \"b64_json\": \"...\" } // or { \"url\": \"https://.../media/file.png\" } when output: \"file\"\n  ],\n  \"background\": \"opaque\",\n  \"output_format\": \"png\",\n  \"size\": \"1024x1024\",\n  \"quality\": \"high\"\n}\n```\n\n### openai-images-edit\n\nArguments (input schema):\n\n- `image` (string or string[], required)\n  - Either a single absolute path to an image file (`.png`, `.jpg`, `.jpeg`, `.webp`),\n    a base64-encoded image string (optionally as a `data:image/...;base64,...` URL),\n    **or an HTTP(S) URL** pointing to a publicly accessible image,\n    **or** an array of 1–16 such strings (for multi-image editing).\n  - When an HTTP(S) URL is provided, the server fetches the image and converts it to base64 before sending to OpenAI.\n- `prompt` (string, required)\n  - Text description of the desired edit.\n  - Max length: 32,000 characters.\n- `mask` (string, optional)\n  - Absolute path, base64 string, or HTTP(S) URL for a mask image (PNG < 4MB, same dimensions\n    as the source image). Transparent areas mark regions to edit.\n- `model` (\"gpt-image-1.5\" | \"gpt-image-1\", optional, default: \"gpt-image-1.5\")\n- `n` (integer, optional)\n  - Number of images to generate.\n  - Min: 1, Max: 10.\n- `quality` (\"auto\" | \"high\" | \"medium\" | \"low\", default: \"high\")\n- `size` (\"1024x1024\" | \"1536x1024\" | \"1024x1536\" | \"auto\", default: \"1024x1536\")\n- `user` (string, optional)\n  - User identifier forwarded to OpenAI for monitoring.\n- `response_format` (\"url\" | \"path\" | \"b64_json\", default: \"url\")\n  - Response format (aligned with OpenAI Images API):\n    - `\"url\"`: file/URL-based output (resource_link items, `image_url` fields, `data[].url` in `api` placement).\n    - `\"path\"`: local filesystem paths in `data[].path` (for local skill workflows).\n    - `\"b64_json\"`: inline base64 image data (image content, `data[].b64_json` in `api` placement).\n- `tool_result` (\"resource_link\" | \"image\", default: \"resource_link\")\n  - Controls `content[]` shape:\n    - `\"resource_link\"` emits ResourceLink items (file/URL-based)\n    - `\"image\"` emits base64 ImageContent blocks\n\nBehavior notes:\n\n- The server accepts `image` and `mask` as absolute paths, base64/data URLs, or HTTP(S) URLs.\n- When an HTTP(S) URL is provided, the server fetches the image and converts it to a base64 data URL before calling OpenAI.\n- For edits, the server always returns PNG semantics (mime type `image/png`)\n  when emitting images.\n\nOutput (MCP CallToolResult):\n\n- When the effective `output` mode is `\"base64\"`:\n  - `content` is an array that may contain:\n    - image items:\n      - `{ type: \"image\", data: <base64 string>, mimeType: \"image/png\" }`\n    - optional text items with revised prompts (when the underlying model returns them):\n      - `{ type: \"text\", text: <revised_prompt string> }`\n- When the effective `output` mode is `\"file\"`:\n  - `content` contains one `resource_link` item per file, plus the same optional `text` items with revised prompts:\n    - `{ type: \"resource_link\", uri: \"file:///absolute-path-1.png\", name: \"absolute-path-1.png\", mimeType: \"image/png\" }`\n  - For `gpt-image-1.5` and `gpt-image-1`, an additional `text` line is included with a pricing estimate (based on `structuredContent.usage`), and `structuredContent.pricing` contains the full pricing breakdown.\n\nWhen `result_placement` includes `\"api\"`, `openai-images-edit` follows the **same raw API format** as `openai-images-generate` (top-level `created`, `data[]`, `background`, `output_format`, `size`, `quality` with `b64_json` for base64 output or `url` for file output).\n\nError handling (both tools):\n\n- On errors inside the tool handler (validation, OpenAI API failures, I/O, etc.), the server returns a CallToolResult marked as an error:\n  - `isError: true`\n  - `content: [{ type: \"text\", text: <error message string> }]`\n- The error message text is taken directly from the underlying exception message, without additional commentary from the server, while full details are logged to the server console.\n\n### openai-videos-create\n\nCreate a video generation job using the OpenAI Videos API (`videos.create`).\n\nArguments (input schema):\n\n- `prompt` (string, required) — text prompt describing the video (max 32K chars).\n- `input_reference` (string, optional) — optional image reference (HTTP(S) URL, base64/data URL, or file path).\n- `input_reference_fit` (\"match\" | \"cover\" | \"contain\" | \"stretch\", default: \"contain\")\n  - How to fit `input_reference` to the requested video `size`:\n    - `match`: require exact dimensions (fails fast on mismatch)\n    - `cover`: resize + center-crop to fill\n    - `contain`: resize + pad/letterbox to fit (default)\n    - `stretch`: resize with distortion\n- `input_reference_background` (\"blur\" | \"black\" | \"white\" | \"#RRGGBB\" | \"#RRGGBBAA\", default: \"blur\")\n  - Padding background used when `input_reference_fit=\"contain\"`.\n- `model` (\"sora-2\" | \"sora-2-pro\", default: \"sora-2-pro\")\n- `seconds` (\"4\" | \"8\" | \"12\", optional)\n- `size` (\"720x1280\" | \"1280x720\" | \"1024x1792\" | \"1792x1024\", optional)\n  - `1024x1792` and `1792x1024` require `sora-2-pro`.\n  - If `input_reference` is omitted and `size` is omitted, the API default is used.\n- `wait_for_completion` (boolean, default: true)\n  - When true, the server polls `openai-videos-retrieve` until `completed` or `failed` (or timeout), then downloads assets.\n- `timeout_ms` (integer, default: 900000)\n- `poll_interval_ms` (integer, default: 2000)\n- `download_variants` (string[], default: [\"video\"])\n  - Allowed values: `\"video\" | \"thumbnail\" | \"spritesheet\"`.\n- `tool_result` (`\"resource_link\"` | `\"resource\"`, default: `\"resource_link\"`)\n  - Controls `content[]` shape for downloaded assets:\n    - `\"resource_link\"` emits ResourceLink items (file/URL-based)\n    - `\"resource\"` emits EmbeddedResource blocks with base64 `resource.blob`\n\nOutput (MCP CallToolResult):\n\n- `structuredContent`: OpenAI `Video` object (job metadata; final state when `wait_for_completion=true`).\n- `content`: includes `resource_link` (default) or embedded `resource` blocks for downloaded assets (when requested) and text blocks with JSON.\n  - Includes a summary JSON block: `{ \"video_id\": \"...\", \"pricing\": { \"currency\": \"USD\", \"model\": \"...\", \"size\": \"...\", \"seconds\": 4, \"price\": 0.1, \"cost\": 0.4 } | null }` (and when waiting: `{ \"video_id\": \"...\", \"assets\": [...], \"pricing\": ... }`).\n\n### openai-videos-remix\n\nCreate a remix job from an existing `video_id` (`videos.remix`).\n\nArguments (input schema):\n\n- `video_id` (string, required)\n- `prompt` (string, required)\n- `wait_for_completion`, `timeout_ms`, `poll_interval_ms`, `download_variants`, `tool_result` — same semantics as `openai-videos-create` (default wait is true).\n\n### openai-videos-list\n\nList video jobs (`videos.list`).\n\nArguments (input schema):\n\n- `after` (string, optional) — cursor (video id) to list after.\n- `limit` (integer, optional)\n- `order` (\"asc\" | \"desc\", optional)\n\nOutput:\n\n- `structuredContent`: OpenAI list response shape `{ data, has_more, last_id }`.\n- `content`: a text block with serialized JSON.\n\n### openai-videos-retrieve\n\nRetrieve job status (`videos.retrieve`).\n\n- `video_id` (string, required)\n\n### openai-videos-delete\n\nDelete a video job (`videos.delete`).\n\n- `video_id` (string, required)\n\n### openai-videos-retrieve-content\n\nRetrieve an asset for a completed job (`videos.downloadContent`, REST `GET /videos/{video_id}/content`), write it under allowed `MEDIA_GEN_DIRS`, and return MCP `resource_link` (default) or embedded `resource` blocks (via `tool_result`).\n\nArguments (input schema):\n\n- `video_id` (string, required)\n- `variant` (\"video\" | \"thumbnail\" | \"spritesheet\", default: \"video\")\n- `tool_result` (`\"resource_link\"` | `\"resource\"`, default: `\"resource_link\"`)\n\nOutput (MCP CallToolResult):\n\n- `structuredContent`: OpenAI `Video` object.\n- `content`: a `resource_link` (or embedded `resource`), a summary JSON block `{ video_id, variant, uri, pricing }`, plus the full video JSON.\n\n### google-videos-generate\n\nCreate a Google video generation operation using the Google GenAI SDK (`@google/genai`) `ai.models.generateVideos`.\n\nArguments (input schema):\n\n- `prompt` (string, optional)\n- `input_reference` (string, optional) — image-to-video input (HTTP(S) URL, base64/data URL, or file path under `MEDIA_GEN_DIRS`)\n- `input_reference_mime_type` (string, optional) — override for `input_reference` MIME type (must be `image/*`)\n- `input_video_reference` (string, optional) — video-extension input (HTTP(S) URL or file path under `MEDIA_GEN_DIRS`; mutually exclusive with `input_reference`)\n- `model` (string, default: `\"veo-3.1-generate-001\"`)\n- `number_of_videos` (integer, default: `1`)\n- `aspect_ratio` (`\"16:9\" | \"9:16\"`, optional)\n- `duration_seconds` (integer, optional)\n  - Veo 2 models: 5–8 seconds (default: 8)\n  - Veo 3 models: 4, 6, or 8 seconds (default: 8)\n  - When using `referenceImages`: 8 seconds\n- `person_generation` (`\"DONT_ALLOW\" | \"ALLOW_ADULT\" | \"ALLOW_ALL\"`, optional)\n- `wait_for_completion` (boolean, default: `true`)\n- `timeout_ms` (integer, default: `900000`)\n- `poll_interval_ms` (integer, default: `10000`)\n- `download_when_done` (boolean, optional; defaults to `true` when waiting)\n- `tool_result` (`\"resource_link\"` | `\"resource\"`, default: `\"resource_link\"`)\n  - Controls `content[]` shape when downloading generated videos.\n- `response_format` (`\"url\"` | `\"b64_json\"`, default: `\"url\"`)\n  - Controls `structuredContent.response.generatedVideos[].video` fields:\n    - `\"url\"` prefers `video.uri` (and strips `video.videoBytes`)\n    - `\"b64_json\"` prefers `video.videoBytes` (and strips `video.uri`)\n\nRequirements:\n\n- Gemini Developer API: set `GEMINI_API_KEY` (or `GOOGLE_API_KEY`), or `google.api_key` in `secrets.yaml`.\n- Vertex AI: set `GOOGLE_GENAI_USE_VERTEXAI=true`, `GOOGLE_CLOUD_PROJECT`, and `GOOGLE_CLOUD_LOCATION` (or `google.vertex_ai.*` in `secrets.yaml`).\n\nOutput:\n\n- `structuredContent`: Google operation object (includes `name`, `done`, and `response.generatedVideos[]` when available).\n- `content`: status text, optional `.mp4` `resource_link` (default) or embedded `resource` blocks (when downloaded), plus JSON text blocks for compatibility.\n\n### google-videos-retrieve-operation\n\nRetrieve/poll an existing Google video operation (`ai.operations.getVideosOperation`).\n\n- `operation_name` (string, required)\n- `response_format` (`\"url\"` | `\"b64_json\"`, default: `\"url\"`)\n\nOutput:\n\n- `structuredContent`: Google operation object.\n- `content`: JSON text blocks with a short summary + the full operation.\n\n### google-videos-retrieve-content\n\nDownload `.mp4` content for a completed operation and return file-first MCP `resource_link` (default) or embedded `resource` blocks (via `tool_result`).\n\n- `operation_name` (string, required)\n- `index` (integer, default: `0`) — selects `response.generatedVideos[index]`\n- `tool_result` (`\"resource_link\"` | `\"resource\"`, default: `\"resource_link\"`)\n- `response_format` (`\"url\"` | `\"b64_json\"`, default: `\"url\"`)\n\nRecommended workflow:\n\n1) Call `google-videos-generate` with `wait_for_completion=true` (default) to get the completed operation and downloads; set to false only if you need the operation id immediately.\n2) Poll `google-videos-retrieve-operation` until `done=true`.\n3) Call `google-videos-retrieve-content` to download an `.mp4` and receive a `resource_link` (or embedded `resource`).\n\n### fetch-images\n\nFetch and process images from URLs or local file paths with optional compression.\n\nArguments (input schema):\n\n- `sources` (string[], optional)\n  - Array of image sources: HTTP(S) URLs or file paths (absolute or relative to the first `MEDIA_GEN_DIRS` entry).\n  - Min: 1, Max: 20 images.\n  - Mutually exclusive with `ids` and `n`.\n- `ids` (string[], optional)\n  - Array of image IDs to fetch by local filename match under the primary `MEDIA_GEN_DIRS[0]` directory.\n  - IDs must be safe (`[A-Za-z0-9_-]` only; no `..`, `*`, `?`, slashes).\n  - Matches filenames containing `_{id}_` or `_{id}.` (supports both single outputs and multi-output suffixes like `_1.png`).\n  - When `ids` is used, `compression` and `file` are not supported (no new files are created).\n  - Mutually exclusive with `sources` and `n`.\n- `n` (integer, optional)\n  - When set, returns the last N image files from the primary `MEDIA_GEN_DIRS[0]` directory.\n  - Files are sorted by modification time (most recently modified first).\n  - Mutually exclusive with `sources` and `ids`.\n- `compression` (object, optional)\n  - `max_size` (integer, optional): Max dimension in pixels. Images larger than this will be resized.\n  - `max_bytes` (integer, optional): Target max file size in bytes. Default: 819200 (800KB).\n  - `quality` (integer, optional): JPEG/WebP quality 1-100. Default: 85.\n  - `format` (\"jpeg\" | \"png\" | \"webp\", optional): Output format. Default: jpeg.\n- `response_format` (\"url\" | \"path\" | \"b64_json\", default: \"url\")\n  - Response format: file/URL-based (`url`), local path (`path`), or inline base64 (`b64_json`).\n- `tool_result` (\"resource_link\" | \"image\", default: \"resource_link\")\n  - Controls `content[]` shape:\n    - `\"resource_link\"` emits ResourceLink items (file/URL-based)\n    - `\"image\"` emits base64 ImageContent blocks\n- `file` (string, optional)\n  - Base path for output files. If multiple images, index suffix is added.\n\nBehavior notes:\n\n- Images are processed in parallel for maximum throughput.\n- Compression is **only** applied when `compression` options are provided.\n- Compression uses [sharp](https://sharp.pixelplumbing.com/) with iterative quality/size reduction when enabled.\n- Partial success: if some sources fail, successful images are still returned with errors listed in the response.\n- When `n` is provided, it is only honored when the `MEDIA_GEN_MCP_ALLOW_FETCH_LAST_N_IMAGES` environment variable is set to `true`. Otherwise, the call fails with a validation error.\n- Sometimes an MCP client (for example, ChargeGPT) may not wait for a response from `media-gen-mcp` due to a timeout. In creative environments where you need to quickly retrieve the latest `openai-images-generate` / `openai-images-edit` outputs, you can use `fetch-images` with the `n` argument. When the `MEDIA_GEN_MCP_ALLOW_FETCH_LAST_N_IMAGES=true` environment variable is set, `fetch-images` will return the last N files from `MEDIA_GEN_DIRS[0]` even if the original generation or edit operation timed out on the MCP client side.\n\n### fetch-videos\n\nFetch videos from HTTP(S) URLs or local file paths.\n\nArguments (input schema):\n\n- `sources` (string[], optional)\n  - Array of video sources: HTTP(S) URLs or file paths (absolute or relative to the first `MEDIA_GEN_DIRS` entry).\n  - Min: 1, Max: 20 videos.\n  - Mutually exclusive with `ids` and `n`.\n- `ids` (string[], optional)\n  - Array of video IDs to fetch by local filename match under the primary `MEDIA_GEN_DIRS[0]` directory.\n  - IDs must be safe (`[A-Za-z0-9_-]` only; no `..`, `*`, `?`, slashes).\n  - Matches filenames containing `_{id}_` or `_{id}.` (supports both single outputs and multi-asset suffixes like `_thumbnail.webp`).\n  - When `ids` is used, `file` is not supported (no downloads; returns existing files).\n  - Mutually exclusive with `sources` and `n`.\n- `n` (integer, optional)\n  - When set, returns the last N video files from the primary `MEDIA_GEN_DIRS[0]` directory.\n  - Files are sorted by modification time (most recently modified first).\n  - Mutually exclusive with `sources` and `ids`.\n- `tool_result` (`\"resource_link\"` | `\"resource\"`, default: `\"resource_link\"`)\n  - Controls `content[]` shape:\n    - `\"resource_link\"` emits ResourceLink items (file/URL-based)\n    - `\"resource\"` emits EmbeddedResource blocks with base64 `resource.blob`\n- `file` (string, optional)\n  - Base path for output files (used when downloading from URLs). If multiple videos are downloaded, an index suffix is added.\n\nOutput:\n\n- `content`: one `resource_link` (default) or embedded `resource` block per resolved video, plus an optional error summary text block.\n- `structuredContent`: `{ data: [{ source, uri, file, mimeType, name, downloaded }], errors?: string[] }`.\n\nBehavior notes:\n\n- URL downloads are only allowed when the URL matches `MEDIA_GEN_URLS` (when set).\n- When `n` is provided, it is only honored when the `MEDIA_GEN_MCP_ALLOW_FETCH_LAST_N_VIDEOS` environment variable is set to `true`. Otherwise, the call fails with a validation error.\n\n### fetch-document\n\nFetch documents from HTTP(S) URLs or local file paths.\n\nArguments (input schema):\n\n- `sources` (string[])\n  - Array of document sources: HTTP(S) URLs or file paths (absolute or relative to the first `MEDIA_GEN_DIRS` entry).\n  - Min: 1, Max: 20 documents.\n- `tool_result` (`\"resource_link\"` | `\"resource\"`, default: `\"resource_link\"`)\n  - Controls `content[]` shape:\n    - `\"resource_link\"` emits ResourceLink items (file/URL-based)\n    - `\"resource\"` emits EmbeddedResource blocks with base64 `resource.blob`\n- `file` (string, optional)\n  - Base path for output files (used when downloading from URLs). If multiple documents are downloaded, an index suffix is added.\n\nOutput:\n\n- `content`: one `resource_link` (default) or embedded `resource` block per resolved document, plus an optional error summary text block.\n- `structuredContent`: `{ data: [{ source, uri, file, mimeType, name, downloaded }], errors?: string[] }`.\n\nBehavior notes:\n\n- URL downloads are only allowed when the URL matches `MEDIA_GEN_URLS` (when set).\n- Local paths are validated against `MEDIA_GEN_DIRS` and can be provided as `file://` URLs.\n- Default filenames use `output_<time_t>_media-gen__fetch-document_<uuid>.<ext>` when `file` is omitted.\n\n### test-images\n\nDebug tool for testing MCP result placement without calling OpenAI API.\n\n**Enabled only when `MEDIA_GEN_MCP_TEST_SAMPLE_DIR` is set**. The tool reads existing images from this directory and does **not** create new files.\n\nArguments (input schema):\n\n- `response_format` (\"url\" | \"path\" | \"b64_json\", default: \"url\")\n- `result_placement` (\"content\" | \"api\" | \"structured\" | \"toplevel\" or array of these, optional)\n  - Override `MEDIA_GEN_MCP_RESULT_PLACEMENT` for this call.\n- `compression` (object, optional)\n  - Same logical tuning knobs as `fetch-images`, but using camelCase keys:\n- `tool_result` (\"resource_link\" | \"image\", default: \"resource_link\")\n  - Controls `content[]` shape:\n    - `\"resource_link\"` emits ResourceLink items (file/URL-based)\n    - `\"image\"` emits base64 ImageContent blocks\n    - `maxSize` (integer, optional): max dimension in pixels.\n    - `maxBytes` (integer, optional): target max file size in bytes.\n    - `quality` (integer, optional): JPEG/WebP quality 1–100.\n    - `format` (\"jpeg\" | \"png\" | \"webp\", optional): output format.\n\nBehavior notes:\n\n- Reads up to 10 images from the sample directory (no sorting — filesystem order).\n- Uses the same result-building logic as `openai-images-generate` and `openai-images-edit` (including `result_placement` overrides).\n- When `output == \"base64\"` and `compression` is provided, sample files are read and compressed **in memory** using `sharp`; original files on disk are never modified.\n- Useful for testing how different MCP clients handle various result structures.\n\n- When `result_placement` includes `\"api\"`, the tool returns a **mock OpenAI Images API-style object**:\n  - Top level: `created`, `data[]`, `background`, `output_format`, `size`, `quality`.\n  - For `response_format: \"b64_json\"` each `data[i]` contains `b64_json`.\n  - For `response_format: \"path\"` each `data[i]` contains `path`.\n  - For `response_format: \"url\"` each `data[i]` contains `url` instead of `b64_json`.\n\n#### Debug CLI helpers for `test-images`\n\nFor local debugging there are two helper scripts that call `test-images` directly:\n\n- `npm run test-images` – uses `debug/debug-call.ts` and prints the validated\n  `CallToolResult` as seen by the MCP SDK client. Usage:\n\n  ```sh\n  npm run test-images -- [placement] [--response_format url|path|b64_json]\n  # examples:\n  # npm run test-images -- structured --response_format b64_json\n  # npm run test-images -- structured --response_format path\n  # npm run test-images -- structured --response_format url\n  ```\n\n- `npm run test-images:raw` – uses `debug/debug-call-raw.ts` and prints the raw\n  JSON-RPC `result` (the underlying `CallToolResult` without extra wrapping). Same\n  CLI flags as above.\n\nBoth scripts truncate large fields for readability:\n\n- `image_url` → first 80 characters, then `...(N chars)`;\n- `b64_json` and `data` (when it is a base64 string) → first 25 characters, then `...(N chars)`.\n\n---\n\n## 🧩 Version policy\n\n### Semantic Versioning (SemVer)\n\nThis package follows **SemVer**: `MAJOR.MINOR.PATCH` (x.y.z).\n\n- `MAJOR` — breaking changes (tool names, input schemas, output shapes).\n- `MINOR` — new tools or backward-compatible additions (new optional params, new fields in responses).\n- `PATCH` — bug fixes and internal refactors with no intentional behavior change.\n\nSince `1.0.0`, this project follows **standard SemVer rules**: breaking changes bump **MAJOR** (npm’s `^1.0.0` allows `1.x`, but not `2.0.0`).\n\n### Dependency policy\n\nThis repository aims to stay **closely aligned with current stable releases**:\n\n- **MCP SDK**: targeting the latest stable `@modelcontextprotocol/sdk` and schema.\n- **OpenAI SDK**: regularly updated to the latest stable `openai` package.\n- **Zod**: using the Zod 4.x line (currently `^4.1.3`). In this project we previously ran on Zod 3.x and, in combination with the MCP TypeScript SDK typings, hit heavy TypeScript errors when passing `.shape` into `inputSchema` — in particular TS2589 (*\"type instantiation is excessively deep and possibly infinite\"*) and TS2322 (*schema shape not assignable to `AnySchema | ZodRawShapeCompat`*). We track the upstream discussion in [modelcontextprotocol/typescript-sdk#494](https://github.com/modelcontextprotocol/typescript-sdk/issues/494) and the related Zod typing work in [colinhacks/zod#5222](https://github.com/colinhacks/zod/pull/5222), and keep the stack on a combination that passes **full strict** compilation reliably.\n- **Tooling stack** (Node.js, TypeScript, etc.): developed and tested against recent LTS / current releases, with a dedicated `tsconfig-strict.json` that enables all strict TypeScript checks (`strict`, `noUnusedLocals`, `noUnusedParameters`, `exactOptionalPropertyTypes`, `noUncheckedIndexedAccess`, `noPropertyAccessFromIndexSignature`, etc.).\n\nYou are welcome to pin or downgrade Node.js, TypeScript, the OpenAI SDK, Zod, or other pieces of the stack if your environment requires it, but please keep in mind:\n\n- we primarily test and tune against the latest stack;\n- issues that only reproduce on older runtimes / SDK versions may be harder for us to investigate and support;\n- upstream compatibility is validated first of all against the latest MCP spec and OpenAI Images API.\n\nThis project is intentionally a bit **futuristic**: it tries to keep up with new capabilities as they appear in MCP and OpenAI tooling (in particular, robust multimodal/image support over MCP and in ChatGPT’s UI). A detailed real‑world bug report and analysis of MCP image rendering in ChatGPT is listed in the **References** section as a case study.\n\nIf you need a long-term-stable stack, pin exact versions in your own fork and validate them carefully in your environment.\n\n---\n\n## 🧩 Typed tool callbacks\n\nAll tool handlers use **strongly typed callback parameters** derived from Zod schemas via `z.input<typeof schema>`:\n\n```typescript\n// Schema definition\nconst openaiImagesGenerateBaseSchema = z.object({\n  prompt: z.string().max(32000),\n  background: z.enum([\"transparent\", \"opaque\", \"auto\"]).optional(),\n  // ... more fields\n});\n\n// Type alias\ntype OpenAIImagesGenerateArgs = z.input<typeof openaiImagesGenerateBaseSchema>;\n\n// Strictly typed callback\nserver.registerTool(\n  \"openai-images-generate\",\n  { inputSchema: openaiImagesGenerateBaseSchema.shape, ... },\n  async (args: OpenAIImagesGenerateArgs, _extra: unknown) => {\n    const validated = openaiImagesGenerateSchema.parse(args);\n    // ... handler logic\n  },\n);\n```\n\nThis pattern provides:\n\n- **Static type safety** — IDE autocomplete and compile-time checks for all input fields.\n- **Runtime validation** — Zod `.parse()` ensures all inputs match the schema before processing.\n- **MCP SDK compatibility** — `inputSchema: schema.shape` provides the JSON Schema for tool registration.\n\nAll tools (`openai-images-*`, `openai-videos-*`, `fetch-images`, `fetch-videos`, `fetch-document`, `test-images`) follow this pattern.\n\n---\n\n## 🧩 Tool annotations\n\nThis MCP server exposes the following tools with annotation hints:\n\n| Tool | `readOnlyHint` | `destructiveHint` | `idempotentHint` | `openWorldHint` |\n|------|----------------|-------------------|------------------|-----------------|\n| **openai-images-generate** | `true` | `false` | `false` | `true` |\n| **openai-images-edit** | `true` | `false` | `false` | `true` |\n| **openai-videos-create** | `true` | `false` | `false` | `true` |\n| **openai-videos-remix** | `true` | `false` | `false` | `true` |\n| **openai-videos-list** | `true` | `false` | `false` | `true` |\n| **openai-videos-retrieve** | `true` | `false` | `false` | `true` |\n| **openai-videos-delete** | `true` | `false` | `false` | `true` |\n| **openai-videos-retrieve-content** | `true` | `false` | `false` | `true` |\n| **fetch-images** | `true` | `false` | `false` | `false` |\n| **fetch-videos** | `true` | `false` | `false` | `false` |\n| **fetch-document** | `true` | `false` | `false` | `false` |\n| **test-images** | `true` | `false` | `false` | `false` |\n\nThese hints help MCP clients understand that these tools:\n- may invoke external APIs or read external resources (open world),\n- do not modify existing project files or user data; they only create new media files (images/videos/documents) in configured output directories,\n- may produce different outputs on each call, even with the same inputs.\n\nBecause `readOnlyHint` is set to `true` for most tools, MCP platforms (including chatgpt.com) can treat this server as logically read-only and usually will not show \"this tool can modify your files\" warnings.\n\n---\n\n## 📁 Project structure\n\n```text\nmedia-gen-mcp/\n├── src/\n│   ├── index.ts              # MCP server entry point\n│   └── lib/\n│       ├── compression.ts    # Image compression (sharp)\n│       ├── env.ts            # Env parsing + allowlists (+ glob support)\n│       ├── helpers.ts        # URL/path validation, result building\n│       ├── logger.ts         # Structured logging + truncation helpers\n│       └── schemas.ts        # Zod schemas for all tools\n├── test/\n│   ├── compression.test.ts             # 12 tests\n│   ├── env.test.ts                     # 19 tests\n│   ├── fetch-images.integration.test.ts# 2 tests\n│   ├── fetch-videos.integration.test.ts# 2 tests\n│   ├── helpers.test.ts                 # 31 tests\n│   ├── logger.test.ts                  # 10 tests\n│   └── schemas.test.ts                 # 64 tests\n├── debug/                    # Local debug helpers (MCP client scripts)\n├── plan/                     # Design notes / plans\n├── dist/                     # Compiled output\n├── tsconfig.json\n├── vitest.config.ts\n├── package.json\n├── CHANGELOG.md\n├── README.md\n└── AGENTS.md\n```\n\n---\n\n## 📝 License\n\nMIT\n\n---\n\n## 🩺 Troubleshooting\n\n- Make sure your `OPENAI_API_KEY` is valid and has image API access.\n- You must have a [verified OpenAI organization](https://platform.openai.com/account/organization). After verifying, it can take 15–20 minutes for image API access to activate.\n- File paths [optional param] must be absolute.\n  - **Unix/macOS/Linux**: Starting with `/` (e.g., `/path/to/image.png`)\n  - **Windows**: Drive letter followed by `:` (e.g., `C:/path/to/image.png` or `C:\\path\\to\\image.png`)\n - For file output, ensure the target directory is writable.\n - If you see errors about file types, check your image file extensions and formats.\n\n---\n\n## 🙏 Inspiration\n\nThis server was originally inspired by\n[SureScaleAI/openai-gpt-image-mcp](https://github.com/SureScaleAI/openai-gpt-image-mcp),\nbut is now a separate implementation focused on **closely tracking the official\nspecifications**:\n\n- **OpenAI Images API alignment** – The arguments for `openai-images-generate`\n  and `openai-images-edit` mirror\n  [`images.create` / `gpt-image-1.5`](https://platform.openai.com/docs/api-reference/images/create):\n  `prompt`, `n`, `size`, `quality`, `background`, `output_format`,\n  `output_compression`, `user`, plus `response_format` (`url` / `b64_json`) with\n  the same semantics as the OpenAI Images API.\n- **MCP Tool Result alignment (image + resource_link)** – With\n  `result_placement = \"content\"`, the server follows the MCP **5.2 Tool Result**\n  section\n  ([5.2.2 Image Content](https://modelcontextprotocol.io/specification/2025-11-25/server/tools#image-content),\n  [5.2.4 Resource Links](https://modelcontextprotocol.io/specification/2025-11-25/server/tools#tool-result))\n  and emits strongly-typed `content[]` items:\n  - `{ \"type\": \"image\", \"data\": \"<base64>\", \"mimeType\": \"image/png\" }` for\n    `response_format = \"b64_json\"`;\n  - `{ \"type\": \"resource_link\", \"uri\": \"file:///...\" | \"https://...\", \"name\": \"...\", \"mimeType\": \"image/...\" }`\n    for file/URL-based output.\n- **Raw OpenAI-style API output** – With `result_placement = \"api\"`, the tool\n  result itself **is** an OpenAI Images-style object:\n  `{ created, data: [...], background, output_format, size, quality, usage? }`,\n  where each `data[]` entry contains either `b64_json` (for\n  `response_format = \"b64_json\"`) or `url` (for `response_format = \"url\"`). No\n  MCP wrapper fields (`content`, `structuredContent`, `files`, `urls`) are\n  added in this mode.\n\nIn short, this library:\n\n- tracks the OpenAI Images API for **arguments and result shape** when\n  `result_placement = \"api\"` with `response_format = \"url\" | \"b64_json\"`, and\n- follows the MCP specification for **tool result content blocks** (`image`,\n  `resource_link`, `text`) when `result_placement = \"content\"`.\n\n### Recommended presets for common clients\n\n- **Default mode / Claude Desktop / strict MCP clients**  \n  For clients that strictly follow the MCP spec, the recommended (and natural)\n  configuration is:\n  - `result_placement = content`\n  - `response_format = b64_json`\n\n  In this mode the server returns:\n  - `content[]` with `type: \"image\"` (base64 image data) and\n    `type: \"resource_link\"` (file/URL links), matching MCP section 5.2 (Image\n    Content and Resource Links). This output works well for **direct\n    integration** with Claude Desktop and any client that fully implements the\n    2025‑11‑25 spec.\n\n- **chatgpt.com Developer Mode**  \n  For running this server as an MCP backend behind ChatGPT Developer Mode, the\n  most practical configuration is the one that most closely matches the OpenAI\n  Images API:\n  - `result_placement = api`\n  - `response_format = url`\n\n  In this mode the tool result matches the `images.create` / `gpt-image-1.5`\n  format (including `data[].url`), which simplifies consumption from backends\n  and libraries that expect the OpenAI schema.\n\n  However, **even with this OpenAI-native shape, the chatgpt.com client does\n  not currently render images**. This behavior is documented in detail in the\n  following report:  \n  <https://github.com/strato-space/report/issues/1>\n---\n\n## ⚠️ Limitations & Large File Handling\n\n- **Configurable payload safeguard:** By default this server uses a ~50MB budget (52,428,800 bytes) for inline `content` to stay within typical MCP client limits. You can override this threshold by setting the `MCP_MAX_CONTENT_BYTES` environment variable to a higher (or lower) value.\n- **Auto-Switch to File Output:** If the total image base64 size exceeds the configured threshold, the tool automatically saves images to disk and returns file path(s) via `resource_link` instead of inline base64. This helps avoid client-side \"payload too large\" errors while still delivering full-resolution images.\n- **Default File Location:** If you do not specify a `file` path, outputs are saved under `MEDIA_GEN_DIRS[0]` (default: `/tmp/media-gen-mcp`) using names like `output_<time_t>_media-gen__<tool>_<id>.<ext>`.\n- **Environment Variables:**\n  - `MEDIA_GEN_DIRS`: Set this to control where outputs are saved. Example: `export MEDIA_GEN_DIRS=/your/desired/dir`. This directory may coincide with your public static directory if you serve files directly from it.\n  - `MEDIA_GEN_MCP_URL_PREFIXES`: Optional comma-separated HTTPS prefixes for public URLs, matched positionally to `MEDIA_GEN_DIRS` entries. When set, the server builds public URLs as `<prefix>/<relative_path_inside_root>` and returns them alongside file paths (for example via `resource_link` URIs and `structuredContent.data[].url` when `response_format: \"url\"`). Example: `export MEDIA_GEN_MCP_URL_PREFIXES=https://media-gen.example.com/media,https://media-gen.example.com/samples`\n  - **Best Practice:** For large or production images, always use file output and ensure your client is configured to handle file paths. Configure `MEDIA_GEN_DIRS` and (optionally) `MEDIA_GEN_MCP_URL_PREFIXES` to serve images via a public web server (e.g., nginx).\n\n---\n\n## 🌐 Serving generated files over HTTPS\n\nIf you want ChatGPT (or any MCP client) to mention publicly accessible URLs alongside file paths:\n\n1. Expose your image directory via HTTPS. For example, on nginx:\n\n   ```nginx\n   server {\n       # listen 443 ssl http2;\n       # server_name <server_name>;\n\n       # ssl_certificate     <path>;\n       # ssl_certificate_key <path>;\n\n       location /media/ {\n           alias /home/username/media-gen-mcp/media/;\n           autoindex off;\n           expires 7d;\n           add_header Cache-Control \"public, immutable\";\n       }\n   }\n   ```\n\n2. Ensure the first entry in `MEDIA_GEN_DIRS` points to the same directory (e.g. `MEDIA_GEN_DIRS=/home/username/media-gen-mcp/media/` or `MEDIA_GEN_DIRS=media/` when running from the project root).\n3. Set `MEDIA_GEN_MCP_URL_PREFIXES=https://media-gen.example.com/media` so the server returns matching HTTPS URLs in top-level `urls`, `resource_link` URIs, and `image_url` fields (for `response_format: \"url\"`).\n\nBoth `openai-images-generate` and `openai-images-edit` now attach `files` + `urls` for **base64** and **file** response modes, allowing clients to reference either the local filesystem path or the public HTTPS link. This is particularly useful while ChatGPT cannot yet render MCP image blo",
  "bytes": 60000,
  "sha": "e6cda03f0e1d1428662582554f406159aabecf514864c03652d0689dc1780f99",
  "repo_slug": "strato-space/media-gen-mcp",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_strato_space_media_gen_mcp_a3e48597/readme"
}