visual-image-search-mcp

visual-image-search-mcp

An MCP server that searches the web for images, creates a numbered contact sheet for visual selection, and downloads the chosen images with provenance metadata. Works without an API key.

Category
访问服务器

README

visual-image-search-mcp

An MCP server that searches the web for images, composites the results into one numbered contact sheet the model can actually look at, and downloads the ones it picks.

Zero-config: it works with no API key.

A nine-cell contact sheet of vintage film cameras, each cell numbered

Why a contact sheet

A normal image-search tool returns thirty URLs and some alt-text, and the model chooses blind — it is picking by filename. This one fetches the thumbnails, tiles them into a single image with numbered cells, and returns that. The model scans it the way you scan a Google Images page and says "download 2 and 7".

One image instead of thirty. Cheaper in context, and the choice is made on what the pictures look like.

Full image URLs never enter the conversation. They stay server-side, keyed by ids like s3:4.

What "choosing blind" actually looks like

Here is a real search — manifest best girl band ever, count: 16 — as a text-based tool would hand it over. Nine of the sixteen results, verbatim:

id size source title
s1:1 2560x2560 manifestgirlband.com Manifest — Altı Kız. Bir Enerji.
s1:3 466x466 manifestgirlband.com Manifest — Six Girls. One Motion.
s1:7 1245x698 manifestgirlband.com Manifest — Six Girls. One Motion.
s1:8 1245x859 manifestgirlband.com Manifest — Altı Kız. Bir Enerji.
s1:9 370x370 manifestgirlband.com Manifest — Six Girls. One Motion.
s1:10 1245x698 manifestgirlband.com Manifest — Altı Kız. Bir Enerji.
s1:11 1245x698 manifestgirlband.com Manifest — Six Girls. One Motion.
s1:15 1245x698 manifestgirlband.com Manifest — Altı Kız. Bir Enerji.
s1:16 405x506 manifestgirlband.com Manifest — Six Girls. One Motion.

Same site, two titles alternating, and the same 1245x698 four separate times. There is no signal here. A model asked to "pick a good group photo" is choosing at random, and it cannot tell you otherwise — it will pick one and sound confident.

Now the same sixteen results as a sheet:

A sixteen-cell contact sheet, four by four, of search results for a girl band

The differences the text could not carry are the only ones that matter:

  • 7, 10 and 15 are video thumbnails with titles burned into the pixels — BİR GÜN VLOG, OFFICIAL DANCE VIDEO, SNAP VLOG. All three are 1245x698, the YouTube thumbnail aspect. Indistinguishable in the table; obvious in the sheet.
  • 8 is a backlit silhouette. You cannot see a single face. Its metadata is the best-looking row of the nine.
  • 3 and 9 are small artwork tiles with heavy typography over them, not photographs.
  • 1 and 16 are actual press photos — which is what was asked for.

That is the whole argument. Not that the sheet is prettier, but that nine of these rows are the same row until you look at them.

Grid sizes

count picks the layout; the canvas stays within 1092x1092 either way.

count grid cell
1–4 2x2 546px
5–9 3x3 364px
10–16 4x4 273px

Rows that would be empty are dropped, so a 5-result search is 3x2 and not 3x3 with a blank band. Smaller counts mean bigger cells and more detail per image; 16 is for scanning breadth. When a cell is too small to judge, image_inspect returns that one image on its own at full size — which is how you catch things like site chrome baked into an "original", where the sheet only hints at it.

Install

{
  "mcpServers": {
    "visual-image-search": {
      "command": "npx",
      "args": ["-y", "visual-image-search-mcp"]
    }
  }
}

Or from the CLI:

claude mcp add visual-image-search -- npx -y visual-image-search-mcp

To run the current main instead of the published version — or before the first npm release — install straight from the repository:

claude mcp add visual-image-search -- npx -y github:enescanguven/visual-image-search-mcp

That path compiles on install, so it needs a working toolchain; the npm package ships prebuilt.

Requires Node 20 or newer.

Tools

image_search

Searches, builds the sheet, returns it.

param type default notes
query string — required
count 1–16 9 ≤4 gives a 2x2 sheet, ≤9 a 3x3, ≤16 a 4x4
page int 1 more results for the same query
safe off/moderate/strict moderate
license any/commercial/share-alike any anything but any routes to Openverse
min_width int — drops narrower results
orientation any/square/landscape/portrait any pushed into the provider query where supported
provider string auto force one provider

Returns the sheet as an image, plus structuredContent with one entry per cell: id, title, dimensions, source page, licence. Cells whose thumbnail failed to load are drawn as a placeholder marked failed — the numbering never shifts to close the gap, so an id always points at the picture you saw in that cell.

image_inspect

{ id: "s3:4" } — returns that one image on its own at up to 1092px, for checking detail before committing to a download.

image_download

{ ids: ["s3:2", "s3:7"], dir?, prefix?, max_bytes? }

Writes each image under the output root, alongside a <filename>.json sidecar holding the source page, licence, attribution, original query and provider — so downstream use has provenance without the model having to remember it.

If a host refuses the original (hotlink protection is common), the thumbnail is saved instead and the result is flagged degraded: true.

Providers

The chain is tried in order and moves on when one errors or comes back empty. The response always names the provider that actually served the results.

provider key notes
duckduckgo none default. Best coverage.
openverse none proper per-image licence data. Used for every license filter.
wikimedia none small index, unambiguous licensing, never blocks you.
brave BRAVE_API_KEY independent index, generous free tier
google_cse GOOGLE_CSE_KEY + GOOGLE_CSE_CX 100 free queries/day
serper SERPER_API_KEY real Google Images results, paid

Default chain: duckduckgo → wikimedia. With a license filter: openverse → wikimedia. Any configured keyed provider goes to the front.

The honest part

The keyless providers scrape public endpoints that were never meant for us. DuckDuckGo image search in particular is a two-step browser handshake — fetch a page, pull a vqd token out of the HTML, pass it to an internal JSON endpoint. There is no contract here:

  • It will break. When DuckDuckGo changes that HTML, the token extraction fails. Errors name which of the two steps broke so you are not guessing.
  • It is rate-limited by IP. Search a lot and you get throttled. The server sends at most one search per second by default.
  • Openverse's anonymous tier is small, and caps page_size at 20.

If you need this to be reliable, set BRAVE_API_KEY or GOOGLE_CSE_KEY + GOOGLE_CSE_CX. Those are real APIs with terms of service and they do not break on a markup change.

The keyed provider parsers were written against published response shapes rather than captured traffic — there was no key on hand to record fixtures with. They are conservative about missing fields, but if you are the first to run one with a real key, check the field mapping.

Configuration

All optional.

variable default purpose
IMAGE_MCP_OUTPUT_DIR ./downloaded-images root that every download must land inside
IMAGE_MCP_SEARCH_INTERVAL_MS 1000 minimum gap between outbound searches
IMAGE_MCP_RESPECT_ROBOTS unset set to 1 to check robots.txt before each download
BRAVE_API_KEY — enables brave
GOOGLE_CSE_KEY, GOOGLE_CSE_CX — enables google_cse
SERPER_API_KEY — enables serper

Download safety

Downloads write remote bytes to your disk, so nothing the remote side says is trusted:

  • Paths. dir resolves inside the output root. Absolute paths and ../ are refused, not sanitised.
  • SSRF. Hostnames are resolved and checked against private, loopback and link-local ranges — including cloud metadata at 169.254.169.254 and IPv4-mapped IPv6 — before a byte is sent, and again on every redirect hop. Redirects cap at 5.
  • Size. max_bytes (default 20MB) is enforced against the stream, not just Content-Length, so a lying header aborts mid-download.
  • Type. The format is decided by magic bytes. An HTML error page served as image/jpeg is rejected rather than saved as a .jpg. SVG is deliberately unsupported — it is a script-bearing document and rendering an untrusted one to read its dimensions is not worth the risk.
  • Filenames are slugs derived from the title, falling back to the id. A remote value never decides the path.
  • robots.txt is not consulted by default. Set IMAGE_MCP_RESPECT_ROBOTS=1 to turn the check on; it then fails open when the file is absent or unreachable. The reasoning is in the next section.

One caveat worth stating plainly: the SSRF check validates the hostname as it resolves at check time. A host that re-binds to a private address between the check and the connection could still get through. Closing that needs connection-level IP pinning, which is not exposed here.

Development

npm install
npm test            # offline: parser fixtures, compositor, safety
npm run test:live   # hits the network; exercises all three keyless providers end to end
npm run preview "red panda" 9 sheet.jpg   # render a sheet to look at
npm run inspector   # manual pass with @modelcontextprotocol/inspector

Provider parsers are tested offline against captured responses in test/fixtures/. When a provider breaks, re-capture its fixture and the diff shows you what changed.

Notes on the implementation

A few decisions that differ from what you might expect:

  • Index badges are drawn as vector rectangles, not text. sharp renders SVG text through fontconfig, which resolves to nothing on a host with no fonts installed. Badges that fail to draw make the whole sheet useless, so they do not depend on a font being present. Captions do use real text, and are simply omitted when no font is available.
  • The sheet is JPEG, not PNG. Measured on a real 9-cell sheet: 1994KB as PNG against 229KB as JPEG q85, with badge edges indistinguishable at 2x zoom.
  • Thumbnails are never cropped. fit: contain on a dark ground — cropping changes what the picture is of, and the model would be judging a composition we invented.
  • robots.txt is off by default. Earlier versions enforced it on downloads. Two things were wrong with that. The Robots Exclusion Protocol governs automated crawlers, and this is not one: someone looked at a contact sheet, picked cell 7, and asked for that file — the same act as "save image as", which consults no robots.txt either. And the enforcement was incoherent, running on one of three outbound paths: not on thumbnail fetches, and not on the search itself, where DuckDuckGo's Disallow: /*? covers the exact /i.js endpoint this server is built on. It is still available behind IMAGE_MCP_RESPECT_ROBOTS=1, and the parser it uses was fixed at the same time — it had been treating blank and comment lines as group separators, which discarded every rule after the first comment. On DuckDuckGo's own file that was 5 of 7 rules, so hosts were being checked against a fragment of what they published.

License

MIT

推荐服务器

Baidu Map

Baidu Map

百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。

官方
精选
JavaScript
Playwright MCP Server

Playwright MCP Server

一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。

官方
精选
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。

官方
精选
本地
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。

官方
精选
本地
TypeScript
VeyraX

VeyraX

一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。

官方
精选
本地
graphlit-mcp-server

graphlit-mcp-server

模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。

官方
精选
TypeScript
Kagi MCP Server

Kagi MCP Server

一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。

官方
精选
Python
e2b-mcp-server

e2b-mcp-server

使用 MCP 通过 e2b 运行代码。

官方
精选
Neon MCP Server

Neon MCP Server

用于与 Neon 管理 API 和数据库交互的 MCP 服务器

官方
精选
Exa MCP Server

Exa MCP Server

模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。

官方
精选