imt mcp starts the Indicium Mail TUI as an MCP (Model Context Protocol) server. AI agents (Claude, GPT-4, etc.) can use it as a tool provider to read and manage email programmatically.
The server communicates over stdin/stdout using newline-delimited JSON-RPC 2.0, following the MCP 2024-11-05 specification. No network port is opened; the agent spawns the process and talks to it directly.
imtinstalled and at least one account configured (imt add-account)- An MCP-compatible AI agent (Claude Desktop, any MCP client)
Add to ~/.config/claude/claude_desktop_config.json (macOS: ~/Library/Application Support/Claude/claude_desktop_config.json):
{
"mcpServers": {
"indicium-mail": {
"command": "imt",
"args": ["mcp"],
"env": {}
}
}
}Restart Claude Desktop. The mail tools will appear automatically.
{
"mcpServers": {
"indicium-mail": {
"command": "imt",
"args": ["--db", "/path/to/imt.sqlite3", "mcp"]
}
}
}echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","clientInfo":{"name":"test","version":"1.0"}}}' | imt mcpThe server implements MCP 2024-11-05 over JSON-RPC 2.0.
- Client sends
initialize - Server responds with capabilities (
toolssupport) - Client sends
initializednotification - Client can now call
tools/listandtools/call
→ {"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","clientInfo":{"name":"claude","version":"1"}}}
← {"jsonrpc":"2.0","id":1,"result":{"protocolVersion":"2024-11-05","capabilities":{"tools":{}},"serverInfo":{"name":"imt-mcp","version":"0.3.0"}}}
→ {"jsonrpc":"2.0","method":"initialized","params":{}}
→ {"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}
← {"jsonrpc":"2.0","id":2,"result":{"tools":[...]}}
→ {"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"list_accounts","arguments":{}}}
← {"jsonrpc":"2.0","id":3,"result":{"content":[{"type":"text","text":"[...]"}],"isError":false}}List all configured mail accounts.
Parameters: none
Returns: JSON array of accounts with id, display_name, email, imap_host, imap_port, smtp_host, smtp_port, auth_type.
List folders for an account.
Parameters:
| Name | Type | Required | Description |
|---|---|---|---|
account_id |
string (UUID) | yes | Account ID from list_accounts |
Returns: JSON array with id, name, path, role, message_count, unread_count.
List messages in a folder.
Parameters:
| Name | Type | Required | Description |
|---|---|---|---|
account_id |
string (UUID) | yes | Account ID |
folder_id |
string (UUID) | yes | Folder ID from list_folders |
limit |
integer | no | Max results (default 50) |
offset |
integer | no | Pagination offset (default 0) |
Returns: JSON array with id, subject, from, date, snippet, flags, size, has_body.
Fetch the full body of a message. Automatically downloads from IMAP if not yet cached.
Parameters:
| Name | Type | Required | Description |
|---|---|---|---|
message_id |
string (UUID) | yes | Message ID from list_messages |
Returns: JSON object with id, subject, from, to, cc, date, flags, body_text, body_html, snippet.
Errors: if the IMAP body fetch returns no content (server unavailable, message moved, transient failure), the tool returns body fetch returned no content rather than empty body_text/body_html.
Full-text search across all messages using SQLite FTS5.
Parameters:
| Name | Type | Required | Description |
|---|---|---|---|
query |
string | yes | Search terms |
account_id |
string (UUID) | no | Restrict to one account |
limit |
integer | no | Max results (default 25) |
Returns: JSON array of matching messages (same shape as list_messages).
Send a new email.
Parameters:
| Name | Type | Required | Description |
|---|---|---|---|
account_id |
string (UUID) | yes | Sending account |
to |
string | yes | Recipient(s), comma-separated |
subject |
string | yes | Subject line |
body |
string | yes | Plain-text body |
cc |
string | no | CC recipients, comma-separated |
bcc |
string | no | BCC recipients, comma-separated |
attachments |
array | no | Local file paths to attach. Each item is a path string, or an object {path, filename}. The file must exist and be readable; MIME type is inferred from the extension. |
Returns: "Message sent successfully" (with attachment count when any) or error.
Reply to a message.
Parameters:
| Name | Type | Required | Description |
|---|---|---|---|
message_id |
string (UUID) | yes | Message to reply to |
body |
string | yes | Reply body |
reply_all |
boolean | no | Reply to all recipients (default false) |
attachments |
array | no | Local file paths to attach to the reply. Each item is a path string, or an object {path, filename}. The file must exist and be readable; MIME type is inferred from the extension. |
Returns: "Reply sent successfully" (with attachment count when any) or error.
Mark a message as read or unread.
Parameters:
| Name | Type | Required | Description |
|---|---|---|---|
message_id |
string (UUID) | yes | Target message |
read |
boolean | no | true = mark read, false = mark unread (default true) |
Returns: "Marked as read" / "Marked as unread" or error.
Toggle the starred/flagged state of a message.
Parameters:
| Name | Type | Required | Description |
|---|---|---|---|
message_id |
string (UUID) | yes | Target message |
Returns: "Message flagged" / "Flag removed" or error.
Move a message to another folder.
Parameters:
| Name | Type | Required | Description |
|---|---|---|---|
message_id |
string (UUID) | yes | Message to move |
folder_id |
string (UUID) | yes | Destination folder |
Returns: "Message moved" or error.
Move a message to Trash. Returns an error if no Trash folder is configured for the account.
Parameters:
| Name | Type | Required | Description |
|---|---|---|---|
message_id |
string (UUID) | yes | Message to delete |
Returns: "Message moved to Trash" on success.
Errors: "No Trash folder configured - cannot delete. Move to a folder instead." when the account has no folder with the Trash role. Use move_message to move to a specific folder of your choice.
Agent: list_accounts
→ [{ "id": "abc-123", "email": "you@gmail.com", ... }]
Agent: list_folders account_id=abc-123
→ [{ "id": "fold-1", "name": "INBOX", "role": "inbox", "unread_count": 3 }, ...]
Agent: list_messages account_id=abc-123 folder_id=fold-1 limit=10
→ [{ "id": "msg-1", "subject": "Hello", "from": "Alice <alice@example.com>", ... }]
Agent: read_message message_id=msg-1
→ { "subject": "Hello", "body_text": "Hi there!\n\nAlice" }
Agent: reply message_id=msg-1 body="Hi Alice, thanks for reaching out!"
→ "Reply sent successfully"
AI Agent (Claude, etc.)
│ stdin/stdout JSON-RPC 2.0
▼
imt mcp (imt-mcp crate)
│
├── Read tools → SQLite DB (imt-store) — zero network I/O
│ AccountRepo / FolderRepo / MessageRepo / SearchRepo
│
└── Write tools → SyncEngine (imt-sync) → IMAP / SMTP (imt-net)
send, reply, move, set_flag, fetch_body
Read operations (list, search) query the local SQLite cache directly - they are fast and work offline. read_message will fetch the body from IMAP the first time if it is not cached, then cache it locally.
Write operations go through the SyncEngine which manages IMAP/SMTP connections with TLS, OAuth2 token refresh, and retry logic.
Credentials are never sent to the AI agent. The imt mcp process loads them from the local secrets store (~/.local/share/indicium-mail-tui/secrets/) exactly as the TUI does. The agent only sees message content.
The transport caps each JSON-RPC line at 4 MB to bound memory against a malformed or hostile client. Larger requests are rejected with an error.
Set RUST_LOG=imt_mcp=debug for verbose protocol logging.