Local Image Search MCP
Give your AI coding agent the ability to search through all your local images. Privacy-first, 100% local MCP server for macOS. Uses MLX CLIP for embeddings, Daft for batch processing, and Lance for vector storage.
https://github.com/user-attachments/assets/41e167f0-bb73-4310-8c1c-4be07af21cc1
Features
- 100% local - Images and embeddings never leave your machine
- MCP Server - Works with Claude Code and Claude Desktop
- Natural language search - Find images by describing them
- Fast - 260+ images/second on Apple Silicon via MLX
Requirements
- macOS with Apple Silicon (M1/M2/M3/M4)
- uv (for
uvxcommand)
Quick Start
Claude Code
Option 1: CLI
claude mcp add local-image-search -- uvx local-image-search
Option 2: Manual - add to ~/.claude.json:
{
"mcpServers": {
"local-image-search": {
"command": "uvx",
"args": ["local-image-search"]
}
}
}
Claude Desktop
Add to ~/Library/Application Support/Claude/claude_desktop_config.json:
{
"mcpServers": {
"local-image-search": {
"command": "uvx",
"args": ["local-image-search"]
}
}
}
Restart Claude after setup. The first run downloads the model (~600MB) and embeds your images, which may take a few minutes. After that, it only processes new or changed files. By default, it scans your home directory (~) and skips common system folders. See Configuration Logic for details.
Custom Configuration
Scan a specific folder:
{
"args": ["local-image-search", "~/Pictures"]
}
Custom excludes:
{
"args": ["local-image-search"],
"env": {
"EXCLUDE_DIRS": "Downloads,Desktop,Movies"
}
}
Faster refresh:
{
"env": {
"REFRESH_INTERVAL": "30"
}
}
Configuration Logic
| Options | Root | Excludes |
|---|---|---|
| None | ~ (home) |
Default excludes |
| Root only | Custom root | None |
| Excludes only | ~ (home) |
Custom excludes |
| Root + Excludes | Custom root | Custom excludes |
Default excludes: Library, .Trash, .cache, Cache, node_modules, .git, .venv, venv
MCP Tools
search_images(query, limit)- Search for images matching a text descriptionget_status()- Check if the service is ready (model loaded, embeddings synced)
Development Setup
# Clone the repo
git clone https://github.com/Eventual-Inc/local-image-search.git
cd local-image-search
# Install dependencies
uv sync
# Download and convert CLIP model (~600MB, first time only)
cd clip && uv run python convert.py && cd ..
CLI Usage
Embed images from a directory
uv run python embed.py ~/Pictures # embed all images
uv run python embed.py ~/Pictures --dry-run # count and estimate time
uv run python embed.py . --no-recursive # current dir only
Embeddings are cached in embeddings.lance/. Re-running skips unchanged files.
Supported formats
| Format | Extensions | Tested |
|---|---|---|
| JPEG | .jpg, .jpeg |
Created and embedded |
| PNG | .png |
Created and embedded |
| GIF | .gif |
Created and embedded |
| WebP | .webp |
Created and embedded |
| BMP | .bmp |
Created and embedded |
| TIFF | .tiff, .tif |
Created and embedded |
| HEIC/HEIF | .heic, .heif |
Real iPhone photo + converted PNG |
Corrupted or unreadable images get zero vectors (won't match searches).
Search
Start the server (loads model once):
uv run python server.py
Search via CLI:
uv run python search.py "sunset" # list results
uv run python search.py "people" -n 10 # show 10 results
Or via API:
curl -X POST http://127.0.0.1:8000/search \
-H "Content-Type: application/json" \
-d '{"query": "yellow mouse", "limit": 5}'
Demo scripts
uv run python simple_image_search.py # basic in-memory search (2 images)
uv run python daft_image_search.py # batch processing demo
Project Structure
local-image-search/
├── clip/ # MLX CLIP implementation (from ml-explore/mlx-examples)
│ ├── model.py # CLIP model architecture
│ ├── clip.py # Model loading and inference
│ ├── convert.py # HuggingFace to MLX converter
│ ├── image_processor.py # Image preprocessing
│ ├── tokenizer.py # Text tokenization
│ ├── mlx_model/ # Converted model weights (generated)
│ └── LICENSE # MIT License (Apple Inc.)
├── data/
│ └── pokemon/ # Pokemon artwork (1025 images)
├── embeddings.lance/ # Lance DB storage (generated)
├── mcp_server.py # MCP server entry point
├── server.py # FastAPI server for local API
├── search.py # CLI search tool
├── core.py # Shared utilities (EmbedImages, find_images, etc.)
├── embed.py # CLI tool to sync embeddings from a directory
├── test_embed.py # Tests for embed.py
├── simple_image_search.py # Basic in-memory search demo
├── daft_image_search.py # Daft-based batch processing demo
├── benchmark.py # Benchmark script
├── plot_benchmark.py # Generate benchmark plot
├── benchmark_results.csv # Raw benchmark data (10 runs)
├── benchmark_plot.png # Benchmark visualization
├── pyproject.toml # Project dependencies
└── uv.lock # Dependency lockfile
Benchmarks
Embedding time for the Pokemon dataset (1025 images) on M4 Max, averaged over 10 runs.
Run benchmarks yourself:
uv run python benchmark.py # Run one iteration, appends to CSV
uv run python benchmark.py 100 # Benchmark with specific number of images
uv run python plot_benchmark.py # Generate plot from CSV
Real-world performance (M4 Max, home directory)
| Metric | Value |
|---|---|
| Images found | 11,843 |
| Scan time | ~26s |
| Embed time | ~39s |
| Total time | ~65s |
| Embed speed | 260 img/s |
| Re-run (cached) | ~31s (scan only) |
Data Attribution
Pokemon Artwork
- Source: PokeAPI/sprites
- License: Repository is CC0 1.0 Universal
- Copyright: All Pokemon images are Copyright The Pokemon Company
CLIP Implementation
- Source: ml-explore/mlx-examples
- License: MIT License (Apple Inc.)
Metadata
Release files for local-image-search 0.2.4
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| local_image_search-0.2.4.tar.gz | 22.1 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| local_image_search-0.2.4-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 45.3 kB
Release files / local_image_search-0.2.4.tar.gz
| Download URL | local_image_search-0.2.4.tar.gz |
|---|---|
| Size | 22.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
9c0efb287060a7d15fb72ffd16638af3012acb5c8e974c929d6b383ea82ad506
|
|
BLAKE2b-256 checksum How to use checksums |
4bbf9cc8d7c0fa5efbad7559ca43326865ceaede9843ce4c3cf99badcc7102c7
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.9.24 {"installer":{"name":"uv","version":"0.9.24","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / local_image_search-0.2.4-py3-none-any.whl
| Download URL | local_image_search-0.2.4-py3-none-any.whl |
|---|---|
| Size | 23.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
842edd82ed9c10474a18a1c299713e63c44b53fb1019e04c71e07de219c9cff2
|
|
BLAKE2b-256 checksum How to use checksums |
11d3ee1cb0feacaaa2fa90403a75cb7e06da63a6161fabe463c65472602d36c8
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.9.24 {"installer":{"name":"uv","version":"0.9.24","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Security for local-image-search 0.2.4
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.