Claude controlling iMessage (I scrubbed phone numbers and replaced with names for privacy)
The result
Model Context Protocol with iMessage
I wrote an MCP server to let Claude control my iMessages and I'll walk through the process in this post
for anyone who wants to build own of their own.
Short explanation of MCP
Instead of fiddling with countless custom integrations, MCP provides a single, open standard that AI systems can speak. In short:
MCP servers expose data sources (like iMessage) in a consistent format.
AI assistants connect to these servers to retrieve and update data.
Here is the overall structure of my MCP server, which we will implement gradually:
Our MCP Entry Point: index.ts
Our index.ts file implements the main server. It:
Creates a new MCP server with the name "imessage".
Defines which “tools” or capabilities it provides:
get-recent-chat-messages
send-imessage
Wires everything up so that requests from the AI get routed to the appropriate function.
This is pretty standard setting for any MCP server. Next we need to implement the tools. We'll break this
part down into two sections that require distinct techniques:
iMessage sending
iMessage reading
Implementing iMessage Sending
One of the few ways I've found to send iMessages programmatically is to use AppleScript. So for this implementation,
we'll need to write an Applescript script and run it in a terminal shell.
Let's set up a utility to run AppleScript:
Then next we'll write a function specifically for sending iMessages with AppleScript:
I'm not an AppleScript expert, but I pieced together this AppleScript by looking at StackOverflow posts.
It seems Apple somewhat abandoned the scripting language, so I wouldn't invest too much time learning it.
But that's it for the iMessage sending side! It requires a little bit of finessing since there's no first-party API,
but it's still generally easy to set up.
Implementing iMessage Reading
Reading iMessage data is trickier than sending because it requires a few hacks and knowledge of Apple's internals.
Apple stores MacOS iMessage data in a local SQLite database, often found in ~/Library/Messages/chat.db. The three main caveats here are:
The sql file has some OS level protections that need to be disabled. For this to work, you'll need to go to System Preferences > Security & Privacy > "Allow applications to access all user files" > Enable for any applications running the code we write. (Do so at your own risk!)
Ever since MacOS Ventura, message content is encoded, so we need to implement some decoding logic to get the actual message text.
The sqlite integers stored in chat.db are larger than the max JavaScript integer, so we need to be careful when reading the data.
Modeling the iMessage Database with Drizzle (optional)
I used drizzle-orm to model the sqlite database, but it's not required. I typically use Prisma ORM, but due to the sqlite integer size issue mentioned above,
Prisma breaks when reading large integers from the database, and to my knowledge there's no easy fix.
Anyhow, here are my Drizzle models:
Querying the iMessage Database
Now we can write a function that queries the iMessage database using Drizzle:
Notice the call to getContentFromIMessage below. This is where we need to decode the message content. Prior to MacOS Ventura,
the content was stored in plain text on the text field, but with Ventura, the content is now encoded in the attributedBody field.
Load the MCP server into Claude
And now our iMessage MCP server is ready! The last thing to do is register it with Claude.
On your Mac, there's a file ~/Library/Application Support/Claude/claude_desktop_config.json. Open it (or create it if it doesn't exist) and add the following entry:
Now the next time you open Claude, it should have access to your iMessage MCP server!
To view the full source code, check it out on GitHub.
And feel free to connect with me on Twitter if you want to chat.