Image Search and Download (DuckDuckGo) MCP for Claude Desktop
image-downloader-mcp
A local stdio-based MCP server for Claude Desktop that can search for images and download selected images without Chrome automation or paid API keys.
This project was built for video editing workflows, especially YouTube Shorts, TikTok, Instagram Reels, documentaries, sports edits, story videos, and any project where you need timestamped images or B-roll assets.
The main use case is simple:
- Write or generate a video script with timestamps.
- Ask Claude Desktop to use this MCP server.
- Claude searches for relevant images.
- Claude downloads images into your local folder.
- Each downloaded image is renamed according to the timestamp and scene description.
- You drag the files directly into your video editor.
Example script:
0:00 - Lionel Messi walks onto the field
0:05 - Argentina fans celebrate in the stadium
0:10 - Close-up of a football boot kicking the ball
0:15 - Trophy celebration scene
Expected downloaded files:
00-00_lionel-messi-walks-onto-field.jpg
00-05_argentina-fans-celebrate-stadium.jpg
00-10_football-boot-kicking-ball.jpg
00-15_trophy-celebration-scene.jpg
Why this project exists
Claude Desktop can use MCP servers to call local tools, but many existing image search MCP options rely on browser automation, Chrome, paid API keys, or services that are not ideal for local creative workflows.
This project focuses on a practical local setup:
- No Chrome automation
- No paid API key
- Works through Claude Desktop local MCP
- Uses Node.js and TypeScript
- Exposes proper MCP tools over stdio
- Saves files locally
- Supports timestamp-based video editing workflows
- Handles safe filenames and duplicate filenames
- Validates downloaded files before saving them
Features
- Local MCP server for Claude Desktop
- Stdio transport
- Node.js and TypeScript
- Official MCP TypeScript SDK
- Zod input validation
- No paid API key required
- Image search using a public DuckDuckGo image search method
- Single image download tool
- Batch image download tool
- Timeline-based image search and download tool
- Custom filename support
- Timestamp-based filename generation
- Safe filename sanitization
- Duplicate filename handling with
_2,_3, etc. - Preserves correct file extension based on detected image type
- Content-Type validation
- File signature validation
- Maximum file size protection
- Request timeout protection
- Windows-friendly paths
- Cross-platform-friendly code where possible
- Logs to stderr only so stdout does not interfere with MCP protocol messages
MCP tools included
This server exposes five tools:
search_imagesdownload_imagedownload_imagesdownload_images_for_timelineget_download_folder
Tool: search_images
Searches for image results.
Input
{
"query": "Argentina fans celebrating in stadium photo",
"maxResults": 10,
"safeSearch": "moderate"
}
Fields
| Field | Type | Required | Description |
| ------------ | ------ | -------: | ----------------------------------------- |
| query | string | Yes | Image search query |
| maxResults | number | No | Number of results to return, from 1 to 50 |
| safeSearch | string | No | strict, moderate, or off |
Output
Returns image results with fields such as:
{
"query": "Argentina fans celebrating in stadium photo",
"provider": "duckduckgo",
"count": 10,
"results": [
{
"title": "Example title",
"imageUrl": "https://example.com/image.jpg",
"thumbnailUrl": "https://example.com/thumb.jpg",
"sourceUrl": "https://example.com/article",
"sourceDomain": "example.com",
"width": 1200,
"height": 800
}
]
}
Tool: download_image
Downloads one image URL into the configured download folder.
Input
{
"imageUrl": "https://example.com/image.jpg",
"fileName": "00-05_argentina-fans-celebrate-stadium",
"sourceUrl": "https://example.com/source-page"
}
Fields
| Field | Type | Required | Description |
| ----------- | ------ | -------: | --------------------------------------------- |
| imageUrl | string | Yes | Direct image URL |
| fileName | string | No | Custom filename without needing the extension |
| sourceUrl | string | No | Source page URL for attribution/reference |
Output
{
"success": true,
"imageUrl": "https://example.com/image.jpg",
"sourceUrl": "https://example.com/source-page",
"filePath": "C:\\<PATH>\\MCP\\ImageDownloads\\00-05_argentina-fans-celebrate-stadium.jpg",
"fileName": "00-05_argentina-fans-celebrate-stadium.jpg",
"contentType": "image/jpeg",
"bytes": 245120
}
Tool: download_images
Downloads multiple images.
Input
{
"images": [
{
"imageUrl": "https://example.com/image-one.jpg",
"fileName": "scene-one",
"sourceUrl": "https://example.com/source-one"
},
{
"imageUrl": "https://example.com/image-two.png",
"fileName": "scene-two",
"sourceUrl": "https://example.com/source-two"
}
]
}
Output
{
"downloaded": 2,
"failed": 0,
"results": [
{
"success": true,
"imageUrl": "https://example.com/image-one.jpg",
"sourceUrl": "https://example.com/source-one",
"filePath": "C:\\<PATH>\\MCP\\ImageDownloads\\scene-one.jpg",
"fileName": "scene-one.jpg",
"contentType": "image/jpeg",
"bytes": 245120
}
]
}
Tool: download_images_for_timeline
This is the main YouTube workflow tool.
Given a list of timestamped scenes, the tool searches for relevant images and downloads the requested number of images for each timestamp.
Input
{
"items": [
{
"timestamp": "0:00",
"scene": "Lionel Messi walks onto the field",
"searchQuery": "Lionel Messi walking onto football field photo",
"count": 1
},
{
"timestamp": "0:05",
"scene": "Argentina fans celebrate in the stadium",
"searchQuery": "Argentina football fans celebrating stadium photo",
"count": 1
}
],
"safeSearch": "moderate"
}
Fields
| Field | Type | Required | Description |
| ------------- | ------ | -------: | ---------------------------------------------------------- |
| items | array | Yes | Timeline items |
| timestamp | string | Yes | Timestamp such as 0:05, 00:05, or 01:02:15 |
| scene | string | Yes | Scene description used for filename generation |
| searchQuery | string | No | Specific image search query. If omitted, the scene is used |
| count | number | No | Number of images to download for that timestamp |
| safeSearch | string | No | strict, moderate, or off |
Output
{
"downloaded": 2,
"failed": 0,
"results": [
{
"success": true,
"timestamp": "0:00",
"scene": "Lionel Messi walks onto the field",
"imageUrl": "https://example.com/image.jpg",
"sourceUrl": "https://example.com/source-page",
"filePath": "C:\\<PATH>\\MCP\\ImageDownloads\\00-00_lionel-messi-walks-onto-field.jpg",
"fileName": "00-00_lionel-messi-walks-onto-field.jpg",
"contentType": "image/jpeg",
"bytes": 245120
}
]
}
Tool: get_download_folder
Returns the active download folder and basic runtime limits.
Input
{}
Output
{
"downloadFolder": "C:\\<PATH>\\MCP\\ImageDownloads",
"exists": true,
"maxFileSizeBytes": 10485760,
"timeoutMs": 15000
}
Filename behavior
The project generates clean, sortable filenames for video editing.
Timestamp conversion
0:05 -> 00-05
00:05 -> 00-05
1:23 -> 01-23
01:02:15 -> 01-02-15
Scene conversion
Lionel Messi walks onto the field
becomes:
lionel-messi-walks-onto-field
Final filename
00-00_lionel-messi-walks-onto-field.jpg
Duplicate handling
If a file already exists:
00-05_argentina-fans-celebrate-stadium.jpg
the next file becomes:
00-05_argentina-fans-celebrate-stadium_2.jpg
then:
00-05_argentina-fans-celebrate-stadium_3.jpg
Image validation
Before saving a file, the server checks:
- The URL uses
httporhttps - The server response is successful
- The response content type looks like an image or a safe binary response
- The downloaded file signature is a supported image type
- The file does not exceed the configured max size
- The request does not exceed the configured timeout
Supported image types include:
- JPEG
- PNG
- WebP
- GIF
- AVIF
- BMP
- TIFF
Requirements
- Windows, macOS, or Linux
- Node.js 20 or newer
- npm
- Claude Desktop
The project was originally designed for Windows with this folder structure:
C:\<PATH>\MCP\image-downloader-mcp
C:\<PATH>\MCP\ImageDownloads
However, the code can be configured to use another folder through environment variables.
Windows setup
Create folders:
New-Item -ItemType Directory -Force "C:\<PATH>\MCP"
New-Item -ItemType Directory -Force "C:\<PATH>\MCP\ImageDownloads"
New-Item -ItemType Directory -Force "C:\<PATH>\MCP\image-downloader-mcp"
Clone or place the project here:
cd C:\<PATH>\MCP\image-downloader-mcp
Install dependencies:
npm.cmd install
Build:
npm.cmd run build
Confirm the build exists:
Test-Path "C:\<PATH>\MCP\image-downloader-mcp\build\index.js"
Expected:
True
Why use npm.cmd on Windows PowerShell?
On some Windows systems, PowerShell blocks npm.ps1 because script execution is disabled.
If this happens:
npm.ps1 cannot be loaded because running scripts is disabled on this system
use:
npm.cmd install
npm.cmd run build
Alternatively, you can allow local scripts for the current Windows user:
Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSigned
Then reopen PowerShell.
Manual local test
Run:
node "C:\<PATH>\MCP\image-downloader-mcp\build\index.js"
Expected behavior:
- The process starts.
- It stays running.
- It logs only to stderr.
- It does not print normal logs to stdout.
Stop it with:
Ctrl + C
Claude Desktop configuration
Open Claude Desktop config:
notepad "$env:APPDATA\Claude\claude_desktop_config.json"
Example config:
{
"mcpServers": {
"image-downloader-mcp": {
"command": "C:\\Program Files\\nodejs\\node.exe",
"args": [
"C:\\<PATH>\\MCP\\image-downloader-mcp\\build\\index.js"
],
"env": {
"IMAGE_DOWNLOAD_DIR": "C:\\<PATH>\\MCP\\ImageDownloads",
"HTTP_TIMEOUT_MS": "15000",
"MAX_FILE_SIZE_BYTES": "10485760",
"DDG_REGION": "us-en",
"USER_AGENT": "image-downloader-mcp/1.0.0"
}
}
}
}
If you already have other MCP servers, add only the image-downloader-mcp block inside your existing mcpServers object.
Validate the JSON:
node -e "const fs=require('fs'),path=require('path'); JSON.parse(fs.readFileSync(path.join(process.env.APPDATA,'Claude','claude_desktop_config.json'),'utf8')); console.log('valid JSON')"
Expected:
valid JSON
Then fully restart Claude Desktop:
- Close the Claude Desktop window.
- Quit Claude from the system tray.
- Reopen Claude Desktop.
- Go to Settings.
- Open Developer.
- Check Local MCP servers.
- Confirm
image-downloader-mcpshows as running.
Test in Claude Desktop
Ask Claude:
Use the image-downloader MCP to tell me the active download folder.
Then test the timeline workflow:
Use the image-downloader MCP to find and download images for this YouTube video script. Download one image per timestamp. Rename each downloaded image according to the timestamp and scene description so I can drag them directly into my video editor.
0:00 - Lionel Messi walks onto the field
0:05 - Argentina fans celebrate in the stadium
0:10 - Close-up of a football boot kicking the ball
0:15 - Trophy celebration scene
Then check:
Get-ChildItem "C:\<PATH>\MCP\ImageDownloads"
Expected filenames:
00-00_lionel-messi-walks-onto-field.jpg
00-05_argentina-fans-celebrate-stadium.jpg
00-10_close-up-of-a-football-boot-kicking-the-ball.jpg
00-15_trophy-celebration-scene.jpg
Environment variables
You can configure the server through Claude Desktop config.
| Variable | Default | Description |
| --------------------- | -------------------------------------- | ------------------------------------ |
| IMAGE_DOWNLOAD_DIR | C:\<PATH>\MCP\ImageDownloads on Windows | Folder where images are saved |
| HTTP_TIMEOUT_MS | 15000 | Timeout for search/download requests |
| MAX_FILE_SIZE_BYTES | 10485760 | Maximum allowed image size |
| DDG_REGION | us-en | DuckDuckGo region |
| USER_AGENT | image-downloader-mcp/1.0.0 | User agent for outbound requests |
.env.example
IMAGE_DOWNLOAD_DIR=C:\<PATH>\MCP\ImageDownloads
HTTP_TIMEOUT_MS=15000
MAX_FILE_SIZE_BYTES=10485760
DDG_REGION=us-en
USER_AGENT=image-downloader-mcp/1.0.0
Troubleshooting
Claude does not show the MCP server
Check that the build exists:
Test-Path "C:\<PATH>\MCP\image-downloader-mcp\build\index.js"
Validate the Claude config:
node -e "const fs=require('fs'),path=require('path'); JSON.parse(fs.readFileSync(path.join(process.env.APPDATA,'Claude','claude_desktop_config.json'),'utf8')); console.log('valid JSON')"
Fully quit and restart Claude Desktop.
Check logs:
notepad "$env:APPDATA\Claude\logs\mcp.log"
notepad "$env:APPDATA\Claude\logs\mcp-server-image-downloader-mcp.log"
npm.ps1 cannot be loaded
Use:
npm.cmd install
npm.cmd run build
tsc is not recognized
This usually means dependencies were not installed.
Run:
npm.cmd install
npm.cmd run build
Search fails
Possible reasons:
- DuckDuckGo changed its public image search behavior.
- Network request timed out.
- SafeSearch setting filtered the results.
- The query was too vague.
Try a more specific query, for example:
Argentina football fans celebrating stadium photo
instead of:
fans
Download fails
Possible reasons:
- The image host blocks direct downloads.
- The URL returns HTML instead of an image.
- The image is larger than the configured max size.
- The image format is unsupported.
- The request timed out.
The timeline tool tries multiple search results before marking a scene as failed.
Safety notes
This tool downloads public web images to a local folder. It does not bypass paywalls, authentication, DRM, private accounts, or access controls.
The server includes several safety checks:
- URL validation
- Timeout protection
- Maximum file size protection
- Content-Type validation
- File signature validation
- Safe filename handling
- Duplicate filename handling
However, users are still responsible for reviewing downloaded files and checking whether they have rights to use the images.
Copyright and legal disclaimer
This project helps search for and download images from public web sources. It does not grant any license to use those images.
You are responsible for checking:
- Copyright status
- License terms
- Attribution requirements
- Commercial-use restrictions
- Personality/publicity rights
- Platform rules for YouTube, TikTok, Instagram, or other services
For commercial or monetized videos, prefer:
- Images you own
- Licensed stock images
- Public domain images
- Creative Commons images with compatible terms
- Official press/media kits where usage is allowed
This project does not provide legal advice.
Known limitations
- DuckDuckGo image search is used through public web behavior, not a guaranteed paid API.
- Search behavior may change if DuckDuckGo changes its image result format.
- The server cannot verify copyright or image license status.
- Some websites block direct image downloads.
- Some images may be low quality or unrelated.
- SafeSearch is dependent on the upstream search provider.
Recommended project structure
image-downloader-mcp/
├─ src/
│ └─ index.ts
├─ scripts/
│ └─ fix-bin-mode.mjs
├─ build/
│ └─ index.js
├─ package.json
├─ package-lock.json
├─ tsconfig.json
├─ .env.example
├─ .gitignore
├─ LICENSE
└─ README.md
Do not commit:
node_modules/
build/
.env
*.log
Commit:
src/
scripts/
package.json
package-lock.json
tsconfig.json
.env.example
.gitignore
LICENSE
README.md
Suggested GitHub publishing checklist
Before publishing:
- Confirm there are no secrets in the repo.
- Confirm
.envis ignored. - Confirm
node_modulesis ignored. - Confirm
buildis ignored unless you intentionally want to publish compiled files. - Run
npm.cmd install. - Run
npm.cmd run build. - Run
npm audit. - Test in Claude Desktop.
- Test
search_images. - Test
download_image. - Test
download_images_for_timeline. - Confirm the download folder works.
- Confirm duplicate filenames are handled correctly.
- Add repository metadata to
package.json. - Add screenshots or a short demo GIF if desired.
- Add clear copyright and usage disclaimers.
License
MIT License.
See LICENSE for details.