DEV Community

Cover image for Compress images from your AI editor with an MCP server
Sahadat Husain
Sahadat Husain

Posted on

Compress images from your AI editor with an MCP server

You can compress and convert images from Claude Code, Cursor, VS Code or Codex by adding a remote MCP server to the editor. Then you ask in plain words, and the agent calls a tool. I asked for "this photo as an AVIF under 200 KB, 1600 px wide". One convert_image call took a 9,804,300-byte JPEG to a 196,540-byte AVIF. This article shows the setup, what goes over the wire, and the mistakes that stop a connection.

What does an image MCP server do for you?

An MCP server gives your AI assistant tools it can call. A remote image server runs the encode on its own machines. Your editor sends JSON-RPC; the server fetches or receives the file, encodes it and hands back a download link.

The server here is the one I build: Blinkimg's, at https://api.blinkimg.com/mcp. It speaks Streamable HTTP, the transport in the MCP spec where every client message is a POST to one endpoint. It registers six tools:

Tool What it does Credits
upload_image Returns a one-time upload link (15 minutes) and a ready curl command for a local file Free
compress_image Makes an image smaller from an image_url or an upload_id 1; 2 when target_format changes the format
convert_image Converts to JPEG, PNG, WebP or AVIF; HEIC, JPEG, PNG, WebP and AVIF go in 2
get_job Reads a job: status, sizes, download link Free
analyze_page Scans a public page for image weight Free; 25 scans a day on Free
get_quota Reads your plan, credits left and limits Free

Every account gets 500 free credits a month. The MCP server page lists the setup for each client.

How do you add it to Claude Code?

One command. Leave the header out and Claude Code signs you in with OAuth in the browser:

claude mcp add --transport http blinkimg https://api.blinkimg.com/mcp
Enter fullscreen mode Exit fullscreen mode

To use an API key instead, keep it in an environment variable and put a .mcp.json in the project root:

{
  "mcpServers": {
    "blinkimg": {
      "type": "http",
      "url": "https://api.blinkimg.com/mcp",
      "headers": {
        "Authorization": "Bearer ${BLINKIMG_API_KEY}"
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Cursor reads the same shape from ~/.cursor/mcp.json, with ${env:BLINKIMG_API_KEY} for the variable. The Claude Code MCP docs and the Cursor MCP docs cover the scopes.

Note: A configured Authorization header turns off the OAuth sign-in in Claude Code. If the editor never offers to sign you in, look for a header you forgot. And if Claude Code reports a missing environment variable, BLINKIMG_API_KEY was not set when the editor started. Set it, then restart the editor.

What actually goes over the wire?

Before you trust a tool in your editor, it helps to see the protocol by hand. Here is a tools/list request with no key:

curl -s -i -X POST https://api.blinkimg.com/mcp \
  -H 'content-type: application/json' \
  -H 'accept: application/json, text/event-stream' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
Enter fullscreen mode Exit fullscreen mode

The accept header lists both types because the spec requires it. The server answered (headers trimmed):

HTTP/2 401
content-type: application/json; charset=utf-8
www-authenticate: Bearer realm="blinkimg", resource_metadata="https://api.blinkimg.com/.well-known/oauth-protected-resource/mcp", scope="mcp"

{"error":{"code":"UNAUTHORIZED","message":"The MCP endpoint takes an API key. Send Authorization: Bearer bk_... (or X-API-Key: bk_...). How to get one: https://blinkimg.com/auth.md","requestId":"5411e245-b480-4d8f-993a-bc7bb28b8f31"}}
Enter fullscreen mode Exit fullscreen mode

The resource_metadata address is how an OAuth-capable client finds where to sign in:

curl -s https://api.blinkimg.com/.well-known/oauth-protected-resource/mcp
Enter fullscreen mode Exit fullscreen mode
{"resource":"https://api.blinkimg.com/mcp","authorization_servers":["https://api.blinkimg.com"],"scopes_supported":["mcp"],"bearer_methods_supported":["header"],"resource_name":"Blinkimg","resource_documentation":"https://blinkimg.com/mcp"}
Enter fullscreen mode Exit fullscreen mode

That pair, a 401 with WWW-Authenticate and a metadata document, is all a client needs to start the sign-in. When Claude Code shows "Needs authentication" in claude mcp list, this is the exchange it saw.

What does one tool call return?

I asked the agent to convert the site's 4964 × 2782 sample photo to AVIF, under 200 KB and no wider than 1600 px. The agent made one call:

{
  "name": "convert_image",
  "arguments": {
    "image_url": "https://blinkimg.com/images/index/image-compressor-sample-camera-photo.jpeg",
    "target_format": "avif",
    "target_bytes": 200000,
    "max_width": 1600
  }
}
Enter fullscreen mode Exit fullscreen mode

The result, with the signed URLs cut:

{
  "status": "succeeded",
  "format": "avif",
  "input_format": "jpeg",
  "input_bytes": 9804300,
  "output_bytes": 196540,
  "saved_percent": 98,
  "width": 1600,
  "height": 897,
  "returned_original": false,
  "download_command": "curl -sS --fail -o 'image-compressor-sample-camera-photo.avif' '...'",
  "next": "Download the result from download_url before ..."
}
Enter fullscreen mode Exit fullscreen mode
Bytes KB Size
Source JPEG 9,804,300 9,804.3 4964 × 2782
AVIF result 196,540 196.5 1600 × 897

Three things in that result matter for an agent. output_bytes stays under target_bytes: the cap is a ceiling, and the engine lowers quality, then dimensions, until the file fits. download_command is a ready curl -o line, so an agent in a terminal saves the file without parsing anything. And next tells the agent the deadline in words: the link lasts an hour, the file a day.

The same cap on the site's reference photo, a 1920 × 1280 JPEG of 576.8 KB, lands at 99.7 KB when target_bytes is 100,000.

How do local files get in?

A hosted server cannot read your disk. So upload_image returns a one-time upload link and a curl command. An agent that can run commands (Claude Code, Cursor, Codex) runs the command, the file goes straight to storage, and compress_image takes the upload_id. A chat app without a shell cannot do this step; give it a public image address instead.

A prompt like "Convert every PNG in ./public to AVIF under 100 KB" turns into one upload_image and one convert_image per file.

Which mistakes stop the connection?

Most failures are config, not the server:

  • Gemini CLI: use httpUrl, not url. In Gemini CLI, url means the older SSE transport.
  • VS Code: in .vscode/mcp.json the top-level key is servers, not mcpServers, with "type": "http".
  • Cline: add "type": "streamableHttp", or it falls back to legacy SSE.
  • Codex: bearer_token_env_var takes the variable's name, BLINKIMG_API_KEY, not the key itself.
  • An app opened from the Mac Dock: it does not see variables you export in ~/.zshrc, so the key is empty and you get a 401.

One limit you may meet in a batch: one key runs 2 image_url downloads at once. A third call waits up to 20 seconds, then asks you to retry. A tool waits up to 25 seconds for the engine; a slower job comes back as a job_id for get_job.

If you would rather call the same engine from your own code, the developer API takes the same options over plain HTTP.

How I measured

  • Machine: Apple Silicon Mac (M1 Pro), 2026-10-17.
  • The 401 and the metadata document: curl against the live server, output pasted with Cloudflare and date headers removed.
  • The tool call: Claude Code's MCP client calling convert_image on the live server with my account. 2 credits spent.
  • Source: image-compressor-sample-camera-photo.jpeg, 4964 × 2782, 9,804,300 bytes (the content-length the site served).
  • The 99.7 KB figure comes from the site's measured engine output (measured.ts, sizeTargets.to100kb).
  • Tool list, credits and limits: read from the server's tool definitions and get_quota on the same day.

What I did not test

I ran one photo through one client. I did not test the OAuth sign-in flow in Cursor, VS Code, Gemini CLI or Codex; their config shapes come from each client's docs.


I build Blinkimg, an image compressor and converter with a developer API. I wrote this article with help from an AI assistant (Claude), which ran the code on my machine; every number in it comes from those runs.

Top comments (0)