imessage-index
Extracts iMessage history, resolves identities, segments conversations topically, embeds and indexes them, and serves hybrid full-text + vector search over an MCP server.
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@imessage-indexsearch for messages about the weekend trip"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
imessage-index
A local-first iMessage retrieval system. Extracts your full message history, resolves identities, segments conversations topically, embeds and indexes them, and serves hybrid full-text + vector search over an MCP server — so an AI assistant can actually answer questions about your own correspondence.
Designed to run unattended on a headless Mac, with all derived state on an encrypted volume and nothing leaving the machine unless you explicitly allowlist it.
⚠️ Read this before you clone
This code has never run against a real corpus. 836 tests pass (639 without a database, plus 197 integration tests that need a live PostgreSQL and skip cleanly without one), the CLI works, migrations apply against real PostgreSQL + pgvector — and no message has ever been indexed by it.
It ships deterministic fake model providers. The embedding,
captioning, OCR, transcription, and segmentation providers are
correctly-dimensioned stubs (marked PLACEHOLDER in cli.py) that let
the pipeline run end to end in tests. Run the pipeline as-is and every
stage will report success while your search results are meaningless.
Replacing them is the first real task — see
Replacing the model providers.
Treat this as a thoroughly tested skeleton with a complete design behind it, not a working product. If you want something that works today, this isn't it. If you want a rigorous starting point that has already made and documented the non-obvious decisions, read on.
What is genuinely done
Area | State |
Schema + migrations | Complete; applied against live PostgreSQL 17 + pgvector |
Pipeline stages (snapshot → extract → identity → segment → enrich → embed → sync → export) | Implemented; unit + integration tested |
Hybrid retrieval (BM25 + text vector + multimodal vector, RRF-fused, reranked) | Implemented |
Local MCP surface (stdio) | Implemented |
Public MCP surface (StreamableHTTP + OAuth) | Implemented; never exposed |
Export gate (default-deny, plan/approve/push) | Implemented; transport never wired to a live API |
Eval harness (nDCG@k, recall@k, MRR) | Implemented; metrics verified against hand-computed fixtures |
Real model providers | Not implemented |
Any run against real data | Never happened |
Related MCP server: imessage-rich-search
Architecture in one pass
chat.db ──.backup──▶ snapshot ──▶ extract ──▶ identity resolution
│
segmentation
│
┌──────────────────┴─────────────┐
text bodies attachments
│
┌────────────────────────┼───────────┐
PDF text layer images audio/video
└────────────────────────┼───────────┘
│
embed ──▶ pgvector + FTS5 + multimodal vector
│
┌────────────────────────┴──────────┐
local MCP filtered export
(full corpus) (allowlisted)Four choices that shaped everything else:
Segments, not messages. Retrieval returns topically coherent conversation segments. Message-granular results flood a model's context with "sounds good" and prevent reconstructing what was actually discussed.
Everything becomes text first. OCR output, captions, transcripts, and PDF text layers all embed into one text space, so failures stay debuggable — when something doesn't surface you can read the extracted text and see why. A second multimodal vector runs alongside for visual similarity.
Identity resolution precedes segmentation. Nothing downstream keys on a raw handle. One person legitimately has many numbers, emails and aliases across a decade.
Default deny on export. Nothing reaches an external index unless explicitly allowlisted, and a group thread requires every participant allowlisted.
Requirements
macOS on Apple Silicon. The pipeline depends on macOS-only APIs: Full Disk Access to Messages, the Contacts framework, Apple Vision.
Python 3.12+ and
uv.PostgreSQL 17 + pgvector, as a dedicated instance.
Rust toolchain, to build the extraction shim.
≥ 32 GB unified memory for 8B-class local models; smaller models work with reduced quality.
Getting started
git clone https://github.com/msg43/imessage_mcp.git imessage-index
cd imessage-index && uv synccargo build --release --manifest-path tools/imsg-dump/Cargo.tomluv run pytest # 639 passed, 197 skipped (integration tests need a live DB)Copy config.example.yaml, fill it in, and point the CLI at it:
export IMSG_CONFIG=/path/to/your/config.yamluv run imsg check-permissions && uv run imsg migrateSecrets are never stored in config — they resolve from the macOS
Keychain (keychain:<item>) or the environment (env:<VAR>), and
config validation rejects anything that looks like a literal secret.
Two macOS gotchas that will each cost you an hour. PostgreSQL needs
export LC_ALL=Cor the postmaster dies at startup with "postmaster became multithreaded during startup" — which reads like a corrupt installation and is not. And Full Disk Access cannot be granted over SSH: TCC prompts require a GUI session, and the grant goes to the binary that launches the job, not to Messages.
Then uv run imsg --help. Every stage supports --dry-run.
Replacing the model providers
This is the work between "tests pass" and "it does something." The
interfaces already exist and are correctly dimensioned — implement them
and inject your implementations where cli.py currently constructs the
fakes.
Interface | What it needs |
|
|
|
|
| Topical boundary indices for a window of messages |
| One method each |
The reference design uses local MLX-hosted models throughout, on the reasoning that sending a decade of personal messages to a hosted API is a categorically different decision from indexing them on your own machine — and that as of mid-2026 the leading open text-embedding models top the benchmarks anyway, so there is little quality left to trade for it. Nothing in the code requires that choice; the interfaces are provider-agnostic.
⚠️ The dimensions are load-bearing — see the first item below.
Things that will bite you
Learned the expensive way; written down so you don't have to.
pgvector's index caps are lower than its type limits. The
vectorandhalfvectypes accept up to 16,000 dimensions, but HNSW/IVFFlat indexes cap at 2,000 (vector) and 4,000 (halfvec). A column can be perfectly legal DDL whose index can never be created — the error surfaces atCREATE INDEX, and ignoring it means silently falling back to sequential scan.scripts/lint_ddl.pyexists solely to catch this. An earlier revision of this project specified an unbuildablehalfvec(4096)for exactly this reason.Audience validation and subject validation are not redundant. On the public surface the subject check answers "is this the owner?" and the audience check answers "was this token minted for this system?" A user's OAuth subject is identical across every app they sign into, so subject-checking alone does not stop a token minted for another application being replayed here. Both, or neither works.
Applied migrations are immutable, enforced by hash. Correct a mistake in a later migration; never edit a shipped one.
updated_atis enforced by trigger, not convention. Re-segmentation keys off it, so a writer that forgot to bump it would strand chats out of reprocessing — no error, stale results the only symptom.Filters must overfetch, not post-filter. Post-filtering a fixed top-K silently starves results when filters are selective: you get few or zero hits and it looks like "nothing matched."
Ingest-time and query-time text normalization must match exactly. If they drift, exact-phrase search silently stops working.
Layout
src/imsg/
config/ config surface + validation (enforces the safety rules)
db/ connection, migrations, cluster fingerprint
stages/ snapshot, extract, identity, sync
segment/ backfill/ enrich/ embed/ indexing pipeline
retrieval/ hybrid query flow, RRF fusion, reranking
mcp/ auth boundary, local + public surfaces, tools
export/ default-deny eligibility, plan/approve/push
eval/ metrics, runner, diff
verify/ seed completeness, attachment reconciliation
migrations/ schema, applied in order by a hash-checked runner
tools/imsg-dump/ GPL-3.0 Rust extraction shim (subprocess only)Development
uv run ruff check . && uv run mypy . && uv run pytestIntegration tests run against a live PostgreSQL when one is reachable and skip cleanly when it isn't; the unit suite never needs a database.
Licensing
The core is MIT — see LICENSE. Component licensing and
the reasoning behind the split are in NOTICE.
In short: tools/imsg-dump/ is GPL-3.0 and carries its own
LICENSE. It links the GPL imessage-database crate to parse the
attributedBody typedstream format, which is not optional — since Big
Sur much of a message's text is not in the text column at all, and
readers that only query that column silently return empty strings for
large portions of modern history.
It is invoked strictly across a process boundary — spawned as a subprocess, never linked into the Python code. That boundary is deliberate and load-bearing for the licensing split. Vendor it differently and that is yours to reason about.
What isn't here
Instance configuration, by design: real config values, contact seed
data, allowlists and eval queries live in a separate private overlay
you supply and point at with IMSG_CONFIG. This repo is public-safe by
construction — no real names, hosts or secrets have ever been committed
to it, and config.example.yaml ships placeholders only.
The design record — architecture rationale, full build spec, and the decision log explaining why each choice above was made — is kept private, since it's written against a specific deployment.
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Servers
- Alicense-qualityCmaintenanceA read-only MCP server for macOS that enables users to search through iMessage history and analyze conversation patterns using AI-powered tools. It provides detailed statistics on messaging habits, streaks, and contact analytics while keeping all data private and local.Last updated1151
- AlicenseAqualityCmaintenanceEnables full-text search of macOS iMessages including link preview metadata. Works as an MCP server for Claude Desktop to search your messages locally.Last updated1MIT
- Alicense-qualityCmaintenanceLocal-first WhatsApp and iMessage memory for AI coding agents. It provides MCP tools to search messages, get recent context, summarize relationships, draft replies, and view unreplied threads.Last updatedMIT
- Flicense-qualityDmaintenanceAn MCP server that exposes Apple iMessage as tools for AI assistants, enabling reading, searching, and sending iMessages through natural language.Last updated
Related MCP Connectors
Long-term memory for AI assistants. Hybrid retrieval, query expansion, auto-topics.
Search your AI chat history (ChatGPT, Claude, Codex) from any MCP client. Remote, private, read-only
User-owned memory for AI agents, Copilot, Claude, IDEs, CLIs, and chat apps over remote MCP.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/msg43/imessage_mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server