imessage-mcp-send 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,65 @@
1
+ Metadata-Version: 2.3
2
+ Name: imessage-mcp-send
3
+ Version: 0.1.0
4
+ Summary: Optional add-on for imessage-mcp: exposes a send_message tool so MCP clients can also send iMessages.
5
+ Keywords: mcp,imessage,macos,claude,llm,send
6
+ Author: moritzhwnr
7
+ Author-email: moritzhwnr <moritz.hawener@gmail.com>
8
+ License: MIT
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: Operating System :: MacOS :: MacOS X
12
+ Classifier: Programming Language :: Python :: 3.13
13
+ Classifier: Topic :: Communications :: Chat
14
+ Requires-Dist: imessage-mcp>=0.1.0
15
+ Requires-Dist: typer>=0.25.1
16
+ Requires-Python: >=3.13
17
+ Project-URL: Homepage, https://github.com/yourname/imessage-mcp
18
+ Project-URL: Repository, https://github.com/yourname/imessage-mcp
19
+ Project-URL: Issues, https://github.com/yourname/imessage-mcp/issues
20
+ Description-Content-Type: text/markdown
21
+
22
+ # imessage-mcp-send
23
+
24
+ Optional add-on for [`imessage-mcp`](https://pypi.org/project/imessage-mcp/) that adds a `send_message` MCP tool. With this installed, your MCP client can also **send** iMessages, not just read them.
25
+
26
+ **Sending is irreversible.** Install only if you understand and accept that — and only with an MCP client that surfaces tool calls to you for confirmation before executing them.
27
+
28
+ ## Install
29
+
30
+ ```bash
31
+ uv tool install imessage-mcp-send
32
+ ```
33
+
34
+ This pulls in `imessage-mcp` as a dependency, so you don't need to install it separately.
35
+
36
+ ## Quickstart
37
+
38
+ ```bash
39
+ imessage-mcp-send setup # opens the macOS Full Disk Access pane
40
+ imessage-mcp-send serve # MCP server on http://127.0.0.1:8765/mcp
41
+ # exposes list_chats, read_messages, search_messages, send_message
42
+ ```
43
+
44
+ On first send, macOS will prompt for **Automation** permission for whatever app is running the CLI (Terminal, iTerm, etc.) to control **Messages**. Click Allow.
45
+
46
+ ## With the broker ([`imessage-bridge`](https://pypi.org/project/imessage-bridge/))
47
+
48
+ Install both:
49
+
50
+ ```bash
51
+ uv tool install imessage-mcp-send
52
+ uv tool install imessage-bridge
53
+ ```
54
+
55
+ `imessage-bridge serve --public` auto-detects this package and exposes send via your broker URL too.
56
+
57
+ ## The new tool
58
+
59
+ | Tool | Arguments | Purpose |
60
+ |---|---|---|
61
+ | `send_message` | `text`, `recipient?`, `chat_id?` | Send to a phone/email **or** reply to a 1:1 chat. Group chats not supported. |
62
+
63
+ ## License
64
+
65
+ MIT
@@ -0,0 +1,44 @@
1
+ # imessage-mcp-send
2
+
3
+ Optional add-on for [`imessage-mcp`](https://pypi.org/project/imessage-mcp/) that adds a `send_message` MCP tool. With this installed, your MCP client can also **send** iMessages, not just read them.
4
+
5
+ **Sending is irreversible.** Install only if you understand and accept that — and only with an MCP client that surfaces tool calls to you for confirmation before executing them.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ uv tool install imessage-mcp-send
11
+ ```
12
+
13
+ This pulls in `imessage-mcp` as a dependency, so you don't need to install it separately.
14
+
15
+ ## Quickstart
16
+
17
+ ```bash
18
+ imessage-mcp-send setup # opens the macOS Full Disk Access pane
19
+ imessage-mcp-send serve # MCP server on http://127.0.0.1:8765/mcp
20
+ # exposes list_chats, read_messages, search_messages, send_message
21
+ ```
22
+
23
+ On first send, macOS will prompt for **Automation** permission for whatever app is running the CLI (Terminal, iTerm, etc.) to control **Messages**. Click Allow.
24
+
25
+ ## With the broker ([`imessage-bridge`](https://pypi.org/project/imessage-bridge/))
26
+
27
+ Install both:
28
+
29
+ ```bash
30
+ uv tool install imessage-mcp-send
31
+ uv tool install imessage-bridge
32
+ ```
33
+
34
+ `imessage-bridge serve --public` auto-detects this package and exposes send via your broker URL too.
35
+
36
+ ## The new tool
37
+
38
+ | Tool | Arguments | Purpose |
39
+ |---|---|---|
40
+ | `send_message` | `text`, `recipient?`, `chat_id?` | Send to a phone/email **or** reply to a 1:1 chat. Group chats not supported. |
41
+
42
+ ## License
43
+
44
+ MIT
@@ -0,0 +1,34 @@
1
+ [project]
2
+ name = "imessage-mcp-send"
3
+ version = "0.1.0"
4
+ description = "Optional add-on for imessage-mcp: exposes a send_message tool so MCP clients can also send iMessages."
5
+ readme = "README.md"
6
+ license = { text = "MIT" }
7
+ authors = [
8
+ { name = "moritzhwnr", email = "moritz.hawener@gmail.com" },
9
+ ]
10
+ requires-python = ">=3.13"
11
+ dependencies = [
12
+ "imessage-mcp>=0.1.0",
13
+ "typer>=0.25.1",
14
+ ]
15
+ keywords = ["mcp", "imessage", "macos", "claude", "llm", "send"]
16
+ classifiers = [
17
+ "Development Status :: 4 - Beta",
18
+ "Intended Audience :: Developers",
19
+ "Operating System :: MacOS :: MacOS X",
20
+ "Programming Language :: Python :: 3.13",
21
+ "Topic :: Communications :: Chat",
22
+ ]
23
+
24
+ [project.urls]
25
+ Homepage = "https://github.com/yourname/imessage-mcp"
26
+ Repository = "https://github.com/yourname/imessage-mcp"
27
+ Issues = "https://github.com/yourname/imessage-mcp/issues"
28
+
29
+ [project.scripts]
30
+ imessage-mcp-send = "imessage_mcp_send.cli:main"
31
+
32
+ [build-system]
33
+ requires = ["uv_build>=0.11.2,<0.12.0"]
34
+ build-backend = "uv_build"
@@ -0,0 +1,10 @@
1
+ """imessage-mcp-send — adds a send_message tool to the imessage-mcp server.
2
+
3
+ Importing this package has a SIDE EFFECT: it registers `send_message` on
4
+ the shared FastMCP instance (`imessage_mcp.server.mcp`). Both binaries —
5
+ `imessage-mcp-send` (this package) and `imessage-bridge` (when installed
6
+ alongside) — pick up the tool automatically by importing `tools`.
7
+ """
8
+
9
+ # Side-effect import: registers send_message on the shared mcp instance.
10
+ from imessage_mcp_send import tools as tools # noqa: F401
@@ -0,0 +1,22 @@
1
+ """Binary `imessage-mcp-send` — same Typer app as `imessage-mcp`, but with
2
+ the `send_message` tool registered on the shared FastMCP instance.
3
+
4
+ We don't add new Typer commands here. The capability the user installs this
5
+ package for is exposed via the MCP protocol, not the CLI. `serve` from
6
+ imessage-mcp picks up the additional tool automatically because we import
7
+ `imessage_mcp_send` (which triggers `tools` registration) before `app()`
8
+ runs.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ # Side-effect import: registers send_message on imessage_mcp.server.mcp.
14
+ import imessage_mcp_send # noqa: F401
15
+ from imessage_mcp.cli import app
16
+
17
+ # Reflect the upgraded capability in --help.
18
+ app.info.help = "MCP server exposing iMessage (read AND send, local-only)."
19
+
20
+
21
+ def main() -> None:
22
+ app()
@@ -0,0 +1,93 @@
1
+ """The send_message MCP tool. Side-effect: registers on import.
2
+
3
+ Sending is irreversible. The MCP client (Claude Desktop, etc.) is expected
4
+ to surface tool calls to the user for confirmation — we don't add our own
5
+ confirmation here because there's no human in the MCP request loop. If
6
+ your client doesn't gate tool use, that's a client issue, not a tool one.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import subprocess
12
+
13
+ # Importing the singleton `mcp` from imessage-mcp's server lets the
14
+ # @mcp.tool() decorator below register on the SAME instance the read-only
15
+ # server already uses. When the server starts, all 4 tools are exposed.
16
+ from imessage_mcp._imessage import open_db
17
+ from imessage_mcp.server import mcp
18
+
19
+
20
+ def _send_via_applescript(recipient: str, text: str) -> str:
21
+ """Run AppleScript to ask Messages.app to send. Returns a result string."""
22
+ # Pass recipient + text as positional args (not interpolated into the
23
+ # script source) so a maliciously-crafted message body can't break out
24
+ # of string literals and inject AppleScript.
25
+ script = """
26
+ on run argv
27
+ set theBuddy to item 1 of argv
28
+ set theText to item 2 of argv
29
+ tell application "Messages"
30
+ set targetService to 1st service whose service type = iMessage
31
+ set theBuddy to buddy theBuddy of targetService
32
+ send theText to theBuddy
33
+ end tell
34
+ end run
35
+ """
36
+ result = subprocess.run(
37
+ ["osascript", "-e", script, recipient, text],
38
+ capture_output=True,
39
+ text=True,
40
+ )
41
+ if result.returncode != 0:
42
+ return f"ERROR: send failed — {result.stderr.strip() or 'unknown'}"
43
+ return f"sent to {recipient}"
44
+
45
+
46
+ @mcp.tool()
47
+ def send_message(
48
+ text: str,
49
+ recipient: str | None = None,
50
+ chat_id: int | None = None,
51
+ ) -> str:
52
+ """Send an iMessage via Messages.app.
53
+
54
+ IRREVERSIBLE side effect. Provide EXACTLY ONE of `recipient` or `chat_id`.
55
+ Group chats are not currently supported.
56
+
57
+ Args:
58
+ text: The message body to send.
59
+ recipient: E.164 phone number (e.g. "+491234567890") or iMessage email.
60
+ chat_id: A chat ID from list_chats. Must be a 1:1 chat.
61
+
62
+ Returns: A status string starting with "sent" on success or "ERROR" on failure.
63
+ """
64
+ if (recipient is None) == (chat_id is None):
65
+ return "ERROR: provide exactly one of `recipient` or `chat_id`."
66
+
67
+ if chat_id is not None:
68
+ # Resolve a chat_id to a single handle. AppleScript can technically
69
+ # address group chats via `text chat id`, but it's unreliable on
70
+ # modern macOS — better to refuse than half-work.
71
+ try:
72
+ conn = open_db()
73
+ except FileNotFoundError as e:
74
+ return f"ERROR: {e}"
75
+ handles = conn.execute(
76
+ """
77
+ SELECT h.id FROM chat_handle_join chj
78
+ JOIN handle h ON h.ROWID = chj.handle_id
79
+ WHERE chj.chat_id = ?
80
+ """,
81
+ (chat_id,),
82
+ ).fetchall()
83
+ if not handles:
84
+ return f"ERROR: no handles found for chat {chat_id}"
85
+ if len(handles) > 1:
86
+ return (
87
+ f"ERROR: chat {chat_id} is a group ({len(handles)} participants); "
88
+ "group sending is not supported."
89
+ )
90
+ recipient = handles[0][0]
91
+
92
+ assert recipient is not None # guaranteed by the XOR check above
93
+ return _send_via_applescript(recipient, text)