imsg-mcp 1.0.0

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.
package/package.json ADDED
@@ -0,0 +1,135 @@
1
+ {
2
+ "name": "imsg-mcp",
3
+ "version": "1.0.0",
4
+ "description": "MCP server, CLI, and TUI for reading and sending iMessage / SMS on macOS — lets AI agents text humans, fetch conversation history, and stream-export entire chats. Includes a self-healing watchdog and an optional Rust acceleration module.",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "bin": {
8
+ "imsg": "./dist/cli.js"
9
+ },
10
+ "files": [
11
+ "dist",
12
+ "native/imsg-native.darwin-arm64.node",
13
+ "native/imsg-native.darwin-x64.node",
14
+ "native/index.js",
15
+ "native/index.d.ts",
16
+ "README.md",
17
+ "LICENSE",
18
+ "CHANGELOG.md",
19
+ "skills"
20
+ ],
21
+ "keywords": [
22
+ "mcp",
23
+ "model-context-protocol",
24
+ "imessage",
25
+ "messages",
26
+ "sms",
27
+ "macos",
28
+ "ai-agent",
29
+ "ai",
30
+ "anthropic",
31
+ "claude",
32
+ "applescript",
33
+ "tui",
34
+ "terminal-ui",
35
+ "cli",
36
+ "chat-export"
37
+ ],
38
+ "author": "George G",
39
+ "repository": {
40
+ "type": "git",
41
+ "url": "https://github.com/george43g/imsg-mcp.git"
42
+ },
43
+ "homepage": "https://github.com/george43g/imsg-mcp#readme",
44
+ "bugs": {
45
+ "url": "https://github.com/george43g/imsg-mcp/issues"
46
+ },
47
+ "license": "MIT",
48
+ "dependencies": {
49
+ "@inkjs/ui": "^2.0.0",
50
+ "@modelcontextprotocol/sdk": "^1.0.0",
51
+ "better-sqlite3": "^12.6.2",
52
+ "bplist-parser": "^0.3.2",
53
+ "commander": "^14.0.3",
54
+ "fullscreen-ink": "^0.1.0",
55
+ "imsg-mcp": "link:",
56
+ "ink": "^7.0.1",
57
+ "react": "^19.2.5",
58
+ "zod": "^3.23.0",
59
+ "zod-to-json-schema": "^3.25.2"
60
+ },
61
+ "devDependencies": {
62
+ "@biomejs/biome": "^2.0.0",
63
+ "@napi-rs/cli": "^3.6.2",
64
+ "@semantic-release/changelog": "^6.0.3",
65
+ "@semantic-release/commit-analyzer": "^13.0.1",
66
+ "@semantic-release/git": "^10.0.1",
67
+ "@semantic-release/github": "^12.0.6",
68
+ "@semantic-release/npm": "^13.1.5",
69
+ "@semantic-release/release-notes-generator": "^14.1.0",
70
+ "@types/better-sqlite3": "^7.6.13",
71
+ "@types/node": "^20.0.0",
72
+ "@types/react": "^19.2.14",
73
+ "ink-testing-library": "^4.0.0",
74
+ "nodemon": "^3.1.14",
75
+ "semantic-release": "^25.0.3",
76
+ "tsx": "^4.19.0",
77
+ "typescript": "^5.5.0",
78
+ "vite": "^7.3.1",
79
+ "vitest": "^2.0.0"
80
+ },
81
+ "engines": {
82
+ "node": ">=24",
83
+ "npm": "not in use"
84
+ },
85
+ "volta": {
86
+ "node": "24.13.0"
87
+ },
88
+ "scripts": {
89
+ "build": "pnpm build:ts && pnpm build:native:optional",
90
+ "build:ts": "vite build",
91
+ "build:native": "cd native && napi build --platform --release",
92
+ "build:native:debug": "cd native && napi build --platform",
93
+ "build:native:optional": "cd native && (napi build --platform --release || (echo '⚠️ Rust native build skipped (rustc not installed). TS fallback will be used.' && exit 0))",
94
+ "dev": "vite build --watch",
95
+ "mcp": "node --env-file=.env --env-file-if-exists=.env.local dist/cli.js mcp",
96
+ "mcp:ai": "node --env-file=.env --env-file-if-exists=.env.ai dist/cli.js mcp",
97
+ "mcp:test": "node --env-file=.env.test dist/cli.js mcp",
98
+ "mcp:test:no-native": "IMSG_DISABLE_NATIVE=1 node --env-file=.env.test dist/cli.js mcp",
99
+ "mcp:no-native": "IMSG_DISABLE_NATIVE=1 node --env-file=.env --env-file-if-exists=.env.local dist/cli.js mcp",
100
+ "dev:mcp": "tsx src/cli.ts mcp",
101
+ "dev:mcp:watch": "nodemon --ext ts --exec 'tsx src/cli.ts mcp'",
102
+ "start": "node --env-file=.env --env-file-if-exists=.env.local dist/cli.js",
103
+ "debug": "node --env-file=.env --env-file-if-exists=.env.local dist/cli.js cli",
104
+ "cli": "node --env-file=.env --env-file-if-exists=.env.local dist/cli.js cli",
105
+ "cli:test": "node --env-file=.env.test dist/cli.js cli",
106
+ "tui": "node --env-file=.env --env-file-if-exists=.env.local dist/cli.js tui",
107
+ "tui:test": "node --env-file=.env.test dist/cli.js tui",
108
+ "tui:no-native": "IMSG_DISABLE_NATIVE=1 node --env-file=.env --env-file-if-exists=.env.local dist/cli.js tui",
109
+ "tui:test:no-native": "IMSG_DISABLE_NATIVE=1 node --env-file=.env.test dist/cli.js tui",
110
+ "doctor": "node --env-file=.env --env-file-if-exists=.env.local dist/cli.js doctor",
111
+ "test": "vitest run",
112
+ "test:native": "vitest run --mode development",
113
+ "test:no-native": "IMSG_DISABLE_NATIVE=1 vitest run",
114
+ "test:watch": "vitest",
115
+ "test:rust": "cd native && cargo test --release",
116
+ "verify": "pnpm lint && pnpm typecheck && pnpm test && pnpm build",
117
+ "lint": "biome check .",
118
+ "typecheck": "tsc --noEmit",
119
+ "fixtures": "tsx scripts/generate-fixtures.ts",
120
+ "fixtures:fresh": "rm -rf fixtures && pnpm fixtures",
121
+ "fixtures:stress": "tsx scripts/generate-fixtures.ts --stress --out fixtures-stress",
122
+ "stress:tui": "tsx scripts/stress-tui.ts",
123
+ "screenshots": "fc-list | grep -qi 'jetbrains mono' || { echo 'Install: brew install --cask font-jetbrains-mono'; exit 1; }; for f in scripts/screenshots/*.tape; do vhs \"$f\"; done",
124
+ "screenshots:check": "pnpm screenshots && git diff --exit-code docs/screenshots/",
125
+ "screenshots:native": "for f in scripts/screenshots/native/*.sh; do echo \"→ $f\"; bash \"$f\" || echo \" (script failed; continuing)\"; done",
126
+ "hook:install": "git config core.hooksPath .githooks && echo '✓ pre-push hook installed (regenerates screenshots before push on macOS)'",
127
+ "pack:mcpb": "pnpm build && rm -rf release/mcpb && mkdir -p release/mcpb && cp -R dist dxt-manifest.json release/mcpb/ && (cd release/mcpb && zip -r ../imsg-mcp.mcpb . -x '*.DS_Store') && echo 'Built release/imsg-mcp.mcpb'",
128
+ "fixtures:if-missing": "node -e \"if(!require('fs').existsSync('fixtures/chat.db')){require('child_process').execSync('pnpm fixtures', {stdio:'inherit'})}\"",
129
+ "rebuild": "pnpm rebuild better-sqlite3",
130
+ "native:build": "cd native && napi build --platform --release",
131
+ "native:build:debug": "cd native && napi build --platform",
132
+ "stress": "tsx scripts/stress-mcp.ts .env.test",
133
+ "stress:live": "tsx scripts/stress-mcp.ts .env.local"
134
+ }
135
+ }
@@ -0,0 +1,168 @@
1
+ # imsg-mcp
2
+
3
+ Companion skill for agents using or developing the `imsg-mcp` iMessage MCP server.
4
+
5
+ ## When to use
6
+
7
+ - When an agent needs to text the human for clarification or approval.
8
+ - When an agent should wait for an iMessage reply before continuing.
9
+ - When an agent needs to inspect recent conversations or unread messages.
10
+ - When developing/debugging the imsg-mcp codebase itself.
11
+
12
+ ## Quick start
13
+
14
+ 1. Run `imsg doctor` on a new machine to check permissions.
15
+ 2. Use `list_conversations` to identify the correct thread (returns `threadSlug`).
16
+ 3. Use `send_message` with `threadSlug` for existing threads, especially groups.
17
+ 4. Use `wait_for_reply` after sending when the workflow depends on a human answer.
18
+
19
+ ## Local machine requirements
20
+
21
+ - macOS only for live reads and sends.
22
+ - Full Disk Access required to read `~/Library/Messages/chat.db` and Address Book.
23
+ - Messages.app must be open to send messages.
24
+ - Node >= 24 (see `package.json` / Volta).
25
+
26
+ ## Git LFS (required in cloud / CI / fresh clones)
27
+
28
+ Large binaries (`*.db`, `*.abcddb`) are stored with Git LFS. A normal clone may leave pointer stubs.
29
+
30
+ ```bash
31
+ git lfs install # once per machine
32
+ git lfs pull # before pnpm install / tests / running against env-data/
33
+ ```
34
+
35
+ ## Thread slugs
36
+
37
+ Stable, human-readable IDs for conversations: `{name}~{service}~{4-hex}` (e.g. `alice~imsg~a3f2`).
38
+
39
+ - `list_conversations` returns `threadSlug` per row
40
+ - `send_message` accepts `threadSlug` or `recipient`
41
+ - `wait_for_reply` accepts `threadSlug` or `chatIdentifier`
42
+ - See `src/thread-slug.ts`, `src/slug-store.ts`
43
+
44
+ ## Binaries
45
+
46
+ | Binary | Purpose |
47
+ |--------|---------|
48
+ | `imsg mcp` | MCP stdio server (used by AI hosts like Claude, Cursor) |
49
+ | `imsg cli` | Interactive CLI with REPL |
50
+ | `imsg tui` | Full-screen TUI (Ink/React) |
51
+
52
+ ## TUI (`imsg`)
53
+
54
+ Vim-style keybindings: `j/k` move, `gg/G` top/bottom, `Ctrl-d/u` half-page, `{/}` group-jump, `Enter` message details, `o` open attachment, `y` copy slug, `d` toggle dev stats, `Tab` switch panes, `/` filter, `c` compose, `q` quit.
55
+
56
+ ## Native Rust module (optional)
57
+
58
+ `native/` contains a Rust acceleration module (`napi-rs` + `rusqlite` + `rayon`). Build with `pnpm native:build`. Falls back to TypeScript automatically if not built. The dev stats panel (`d` key in TUI) shows which engine is active.
59
+
60
+ ## Tool limits
61
+
62
+ `limit: 0` = unlimited (bounded only by the per-tool timeout). No upper cap.
63
+ Defaults: 20 for most tools, 100 for `get_unread_messages`.
64
+
65
+ ## Self-healing watchdog
66
+
67
+ `src/watchdog.ts` runs three monitors that self-kill (so the host respawns) on:
68
+ - Event-loop p99 lag > 10s
69
+ - RSS > 1GB or monotonic heap growth across 10 × 60s samples
70
+ - Uptime > 24h with no activity in the last hour
71
+
72
+ All thresholds are env-overridable (`IMSG_EVENT_LOOP_KILL_MS`, `IMSG_MAX_RSS_MB`, etc.).
73
+
74
+ ## MCP cancellation
75
+
76
+ The server honors `notifications/cancelled`. Long-running handlers (`wait_for_reply`) abort cleanly when the host cancels.
77
+
78
+ ## MCP pagination & export
79
+
80
+ - `get_messages` returns a footer with `oldestMessageId`. Pass it as `beforeMessageId` to paginate. Hard cap 5000/call.
81
+ - `export_messages` streams a chat to a file (markdown/csv/json/ndjson) — use this instead of huge `get_messages` calls.
82
+
83
+ ## TUI date jump + selection + export
84
+
85
+ - `:` open date-jump (e.g. `1 year ago`, `2024-01-15`).
86
+ - `V` enter visual select; `e` export selection; `y` copy text.
87
+
88
+ ## Bounded memory
89
+
90
+ When loaded messages exceed `IMSG_TUI_MSG_HARD_CAP` (default 5000), middle is evicted; first 200 (anchor) + window around cursor stay. Gap markers show evicted regions.
91
+
92
+ ## TUI lazy loading + cache
93
+
94
+ - 200 conversations load initially; another 100 lazy-load when within 20 of the end.
95
+ - Older messages lazy-load when scrolling within 10 of the top of a thread.
96
+ - Per-chat cache holds messages for fast re-entry. Tunable: `IMSG_TUI_CACHE_TTL_MS`, `IMSG_TUI_CACHE_STALE_MS`, `IMSG_TUI_CACHE_MEM_PRESSURE_MB`.
97
+
98
+ ## Debugging & Logs
99
+
100
+ ### MCP tool: `health_check`
101
+
102
+ Returns vital signs (uptime, heap, RSS, event-loop p99, tool calls, engine) in milliseconds — works even when the DB is wedged.
103
+
104
+ ### MCP tool: `get_logs`
105
+
106
+ ```
107
+ get_logs({ tail: 50, source: "all" })
108
+ ```
109
+
110
+ - `source: "memory"` — in-process buffer (default)
111
+ - `source: "file"` — NDJSON log from `$TMPDIR/imsg-mcp/`
112
+ - `source: "all"` — both
113
+
114
+ ### Log files
115
+
116
+ Full NDJSON logs are written to `$TMPDIR/imsg-mcp/imsg-mcp-{PID}-{date}.ndjson`. These persist across restarts and contain:
117
+
118
+ - `level: "perf"` + `dur_ms` — performance spans for every DB query
119
+ - `level: "info"`, `msg: "heartbeat"` — periodic memory/uptime (every 60s)
120
+ - `msg: "startup"` — process start marker (with PID, node version)
121
+ - `msg: "shutdown"` — graceful exit marker (with uptime, reason)
122
+
123
+ **Crash detection:** If a log file has no `"shutdown"` entry, the process crashed or hung.
124
+
125
+ ### Process lifecycle
126
+
127
+ - `src/shutdown.ts` — central cleanup registry, signal handlers (SIGINT, SIGTERM, SIGHUP, SIGQUIT)
128
+ - Orphan detection: parent PID watchdog (detects reparenting to launchd) + stdin EOF detection (MCP host died)
129
+ - After TUI crashes, check for orphans: `ps aux | grep imsg`
130
+
131
+ ## Environment variables (Vite / Vitest)
132
+
133
+ Precedence: `.env` → `.env.local` → `.env.[mode]` → `.env.[mode].local`
134
+
135
+ | File | Role |
136
+ |------|------|
137
+ | `.env` | Baseline: `VITE_ENV=development` |
138
+ | `.env.local` | Machine paths (`VITE_IMSG_DB_PATH`, etc.) |
139
+ | `.env.test` | `VITE_ENV=ai` + `env-data/` paths for `pnpm test` |
140
+
141
+ - `pnpm test` — Vitest mode `test` (loads `.env.test`)
142
+ - `pnpm test:native` — mode `development` (Mac paths from `.env.local`)
143
+ - Vitest always mocks AppleScript sends (`VITEST=true`)
144
+
145
+ ## Code map
146
+
147
+ | Area | Location |
148
+ |------|----------|
149
+ | MCP tools / Zod | `src/index.ts` |
150
+ | SQLite messages | `src/imessage-db.ts` |
151
+ | Blob parsing | `src/attributed-body-text.ts`, `src/parsers/typedstream-parser.ts` |
152
+ | Contacts | `src/contacts-db.ts` |
153
+ | Slugs | `src/thread-slug.ts`, `src/slug-store.ts` |
154
+ | Send / mock | `src/applescript.ts`, `src/mock-send-db.ts` |
155
+ | TUI | `src/tui/` (Ink/React) |
156
+ | Native module | `native/` (Rust + napi-rs) |
157
+ | Shutdown | `src/shutdown.ts` |
158
+ | Logging | `src/logger.ts` |
159
+ | Config | `src/config.ts` |
160
+ | Types | `src/types.ts` |
161
+ | DB schema | `docs/IMESSAGE_DB_SCHEMA.md` |
162
+
163
+ ## Security / guardrails
164
+
165
+ - Treat iMessage data as sensitive local user data.
166
+ - Never publish private message content.
167
+ - Confirm before sending messages on behalf of the user.
168
+ - See `AGENTS.md` for thread isolation and MCP guardrails.