apple-voice-memos-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/README.md +63 -0
- package/bin/cli.js +352 -0
- package/package.json +34 -0
- package/src/dist/index.d.ts +3 -0
- package/src/dist/index.d.ts.map +1 -0
- package/src/dist/index.js +24 -0
- package/src/dist/index.js.map +1 -0
- package/src/dist/server.d.ts +11 -0
- package/src/dist/server.d.ts.map +1 -0
- package/src/dist/server.js +96 -0
- package/src/dist/server.js.map +1 -0
- package/src/dist/services/transcript-extractor.d.ts +6 -0
- package/src/dist/services/transcript-extractor.d.ts.map +1 -0
- package/src/dist/services/transcript-extractor.js +28 -0
- package/src/dist/services/transcript-extractor.js.map +1 -0
- package/src/dist/services/transcription-service.d.ts +16 -0
- package/src/dist/services/transcription-service.d.ts.map +1 -0
- package/src/dist/services/transcription-service.js +81 -0
- package/src/dist/services/transcription-service.js.map +1 -0
- package/src/dist/services/voice-memo-db.d.ts +19 -0
- package/src/dist/services/voice-memo-db.d.ts.map +1 -0
- package/src/dist/services/voice-memo-db.js +79 -0
- package/src/dist/services/voice-memo-db.js.map +1 -0
- package/src/dist/tools/get-audio.d.ts +34 -0
- package/src/dist/tools/get-audio.d.ts.map +1 -0
- package/src/dist/tools/get-audio.js +68 -0
- package/src/dist/tools/get-audio.js.map +1 -0
- package/src/dist/tools/get-memo.d.ts +26 -0
- package/src/dist/tools/get-memo.d.ts.map +1 -0
- package/src/dist/tools/get-memo.js +40 -0
- package/src/dist/tools/get-memo.js.map +1 -0
- package/src/dist/tools/get-transcript.d.ts +35 -0
- package/src/dist/tools/get-transcript.d.ts.map +1 -0
- package/src/dist/tools/get-transcript.js +63 -0
- package/src/dist/tools/get-transcript.js.map +1 -0
- package/src/dist/tools/list-memos.d.ts +39 -0
- package/src/dist/tools/list-memos.d.ts.map +1 -0
- package/src/dist/tools/list-memos.js +58 -0
- package/src/dist/tools/list-memos.js.map +1 -0
- package/src/dist/tools/transcribe.d.ts +34 -0
- package/src/dist/tools/transcribe.d.ts.map +1 -0
- package/src/dist/tools/transcribe.js +64 -0
- package/src/dist/tools/transcribe.js.map +1 -0
- package/src/dist/types/index.d.ts +56 -0
- package/src/dist/types/index.d.ts.map +1 -0
- package/src/dist/types/index.js +2 -0
- package/src/dist/types/index.js.map +1 -0
- package/src/dist/utils/dates.d.ts +17 -0
- package/src/dist/utils/dates.d.ts.map +1 -0
- package/src/dist/utils/dates.js +57 -0
- package/src/dist/utils/dates.js.map +1 -0
- package/src/dist/utils/mp4-parser.d.ts +6 -0
- package/src/dist/utils/mp4-parser.d.ts.map +1 -0
- package/src/dist/utils/mp4-parser.js +109 -0
- package/src/dist/utils/mp4-parser.js.map +1 -0
- package/src/dist/utils/paths.d.ts +5 -0
- package/src/dist/utils/paths.d.ts.map +1 -0
- package/src/dist/utils/paths.js +55 -0
- package/src/dist/utils/paths.js.map +1 -0
package/README.md
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# apple-voice-memos-mcp
|
|
2
|
+
|
|
3
|
+
MCP server for **Apple Voice Memos** (Memo Vocali) on macOS — with **auto-setup**.
|
|
4
|
+
|
|
5
|
+
## Quick start (one command)
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npx apple-voice-memos-mcp --setup
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Then add to your MCP config (Claude Desktop, pi, Cursor, etc.):
|
|
12
|
+
|
|
13
|
+
```json
|
|
14
|
+
{
|
|
15
|
+
"mcpServers": {
|
|
16
|
+
"apple-voice-memos": {
|
|
17
|
+
"command": "npx",
|
|
18
|
+
"args": ["apple-voice-memos-mcp"]
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Why the setup?
|
|
25
|
+
|
|
26
|
+
macOS protects Voice Memos data (database + audio) with **Full Disk Access** — a privacy
|
|
27
|
+
permission that, unlike AppleScript automation, **cannot be granted programmatically or
|
|
28
|
+
via automatic prompt**. This package minimizes the manual work to:
|
|
29
|
+
|
|
30
|
+
1. `npx apple-voice-memos-mcp --setup` — builds a signed helper app
|
|
31
|
+
(`VoiceMemosMCP.app`), registers a LaunchAgent daemon, and opens the Full Disk Access
|
|
32
|
+
panel for you
|
|
33
|
+
2. Drag **VoiceMemosMCP.app** into the Full Disk Access list (once, ~10 seconds)
|
|
34
|
+
3. Restart your MCP client (or `/reload`)
|
|
35
|
+
|
|
36
|
+
The architecture (MCP client → stdio → local TCP daemon launched via launchd → signed app)
|
|
37
|
+
is what makes the permission stick reliably — plain `node` processes inherit the
|
|
38
|
+
permissions of their parent, which is why direct setups silently fail.
|
|
39
|
+
|
|
40
|
+
## MCP Tools
|
|
41
|
+
|
|
42
|
+
- `list_voice_memos` — list memos with metadata (limit/offset/search)
|
|
43
|
+
- `get_voice_memo` — full metadata for one memo
|
|
44
|
+
- `get_audio` — the audio file (path or base64)
|
|
45
|
+
- `get_transcript` — Apple's embedded transcript, if present
|
|
46
|
+
- `transcribe_memo` — on-demand transcription via Apple Speech Recognition
|
|
47
|
+
|
|
48
|
+
## Status
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
npx apple-voice-memos-mcp --status
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Requirements
|
|
55
|
+
|
|
56
|
+
- macOS 14+ (Voice Memos app opened at least once)
|
|
57
|
+
- Node.js 18+ (26.x tested — uses better-sqlite3 prebuilds, no compilation)
|
|
58
|
+
- Xcode Command Line Tools (for the 1-second launcher compile during setup)
|
|
59
|
+
|
|
60
|
+
## Credits
|
|
61
|
+
|
|
62
|
+
Server implementation forked from [jwulff/apple-voice-memo-mcp](https://github.com/jwulff/apple-voice-memo-mcp),
|
|
63
|
+
with a Node 26 compatibility fix (better-sqlite3 13) and the TCC/LaunchAgent architecture.
|
package/bin/cli.js
ADDED
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* apple-voice-memos-mcp — CLI entry point
|
|
4
|
+
*
|
|
5
|
+
* Uso:
|
|
6
|
+
* apple-voice-memos-mcp → avvia il server MCP in stdio (config npx standard)
|
|
7
|
+
* apple-voice-memos-mcp --setup → setup guidato (app firmata + LaunchAgent + pannello FDA)
|
|
8
|
+
* apple-voice-memos-mcp --status → stato del demone e dei permessi
|
|
9
|
+
*
|
|
10
|
+
* Architettura (risolve TCC/Full Disk Access su macOS):
|
|
11
|
+
* gateway MCP → [stdio] → cli.js (questo file) → [TCP 127.0.0.1] → demone (launchd)
|
|
12
|
+
* Il demone è lanciato da launchd come VoiceMemosMCP.app (firmata) → eredita
|
|
13
|
+
* il Full Disk Access e genera un'istanza del server MCP per connessione.
|
|
14
|
+
*/
|
|
15
|
+
import { spawn, execSync } from "child_process";
|
|
16
|
+
import { existsSync, mkdirSync, writeFileSync, copyFileSync, readFileSync } from "fs";
|
|
17
|
+
import { join, dirname } from "path";
|
|
18
|
+
import { fileURLToPath } from "url";
|
|
19
|
+
import net from "net";
|
|
20
|
+
import os from "os";
|
|
21
|
+
|
|
22
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
23
|
+
const HOME = os.homedir();
|
|
24
|
+
|
|
25
|
+
const APP_DIR = join(HOME, "Library", "apple-voice-memos-mcp");
|
|
26
|
+
const APP_PATH = join(APP_DIR, "VoiceMemosMCP.app");
|
|
27
|
+
const APP_EXEC = join(APP_PATH, "Contents", "MacOS", "VoiceMemosMCP");
|
|
28
|
+
const APP_NODE = join(APP_PATH, "Contents", "MacOS", "noded");
|
|
29
|
+
const APP_LIB_DIR = join(APP_PATH, "Contents", "lib");
|
|
30
|
+
const PLIST_PATH = join(HOME, "Library", "LaunchAgents", "it.otomatik.voicememosmcp.plist");
|
|
31
|
+
const TCP_PORT = 8917;
|
|
32
|
+
const TCP_HOST = "127.0.0.1";
|
|
33
|
+
const SERVER_JS = join(__dirname, "..", "src", "dist", "index.js");
|
|
34
|
+
|
|
35
|
+
const PLIST = (nodePath, daemonJs) => `<?xml version="1.0" encoding="UTF-8"?>
|
|
36
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
37
|
+
<plist version="1.0">
|
|
38
|
+
<dict>
|
|
39
|
+
<key>Label</key>
|
|
40
|
+
<string>it.otomatik.voicememosmcp</string>
|
|
41
|
+
<key>ProgramArguments</key>
|
|
42
|
+
<array>
|
|
43
|
+
<string>${nodePath}</string>
|
|
44
|
+
<string>${daemonJs}</string>
|
|
45
|
+
</array>
|
|
46
|
+
<key>RunAtLoad</key>
|
|
47
|
+
<true/>
|
|
48
|
+
<key>KeepAlive</key>
|
|
49
|
+
<true/>
|
|
50
|
+
<key>StandardErrorPath</key>
|
|
51
|
+
<string>/tmp/apple-voice-memos-mcp-daemon.err</string>
|
|
52
|
+
</dict>
|
|
53
|
+
</plist>
|
|
54
|
+
`;
|
|
55
|
+
|
|
56
|
+
const DAEMON_JS = `#!/usr/bin/env node
|
|
57
|
+
// Demone TCP generato da apple-voice-memos-mcp: per ogni connessione
|
|
58
|
+
// lancia un'istanza stdio del server MCP. Lanciato da launchd → eredita FDA.
|
|
59
|
+
const net = require('net');
|
|
60
|
+
const { spawn } = require('child_process');
|
|
61
|
+
const SERVER_JS = ${JSON.stringify(SERVER_JS)};
|
|
62
|
+
const NODE = ${JSON.stringify(process.execPath)};
|
|
63
|
+
const PORT = ${TCP_PORT};
|
|
64
|
+
|
|
65
|
+
const server = net.createServer((socket) => {
|
|
66
|
+
const child = spawn(NODE, [SERVER_JS], { stdio: ['pipe', 'pipe', 'pipe'] });
|
|
67
|
+
socket.pipe(child.stdin);
|
|
68
|
+
child.stdout.pipe(socket);
|
|
69
|
+
child.stderr.on('data', () => {});
|
|
70
|
+
child.on('exit', () => socket.end());
|
|
71
|
+
socket.on('close', () => { try { child.kill(); } catch (e) {} });
|
|
72
|
+
socket.on('error', () => {});
|
|
73
|
+
});
|
|
74
|
+
server.listen(PORT, '127.0.0.1', () => console.error('apple-voice-memos-mcp daemon on :' + PORT));
|
|
75
|
+
`;
|
|
76
|
+
|
|
77
|
+
function log(msg) {
|
|
78
|
+
process.stderr.write(`[apple-voice-memos-mcp] ${msg}\n`);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function portOpen(port) {
|
|
82
|
+
return new Promise((resolve) => {
|
|
83
|
+
const sock = net.connect(port, TCP_HOST);
|
|
84
|
+
sock.setTimeout(1500);
|
|
85
|
+
sock.on("connect", () => { sock.destroy(); resolve(true); });
|
|
86
|
+
sock.on("error", () => resolve(false));
|
|
87
|
+
sock.on("timeout", () => { sock.destroy(); resolve(false); });
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** Crea (o aggiorna) l'app VoiceMemosMCP.app firmata che avvia il demone */
|
|
92
|
+
function buildApp() {
|
|
93
|
+
// 1. launcher C che esegue node con il demone — compila con cc (presente con Xcode CLT)
|
|
94
|
+
const launcherC = `/tmp/avmm-launcher.c`;
|
|
95
|
+
const launcherBin = `/tmp/avmm-launcher`;
|
|
96
|
+
writeFileSync(
|
|
97
|
+
launcherC,
|
|
98
|
+
`#include <unistd.h>
|
|
99
|
+
#include <stdlib.h>
|
|
100
|
+
#include <libgen.h>
|
|
101
|
+
#include <stdio.h>
|
|
102
|
+
#include <string.h>
|
|
103
|
+
#include <mach-o/dyld.h>
|
|
104
|
+
|
|
105
|
+
int main(int argc, char **argv) {
|
|
106
|
+
char exe_path[4096];
|
|
107
|
+
uint32_t size = sizeof(exe_path);
|
|
108
|
+
if (_NSGetExecutablePath(exe_path, &size) != 0) return 1;
|
|
109
|
+
char *dir = dirname(exe_path); // Contents/MacOS
|
|
110
|
+
|
|
111
|
+
char node_path[4096];
|
|
112
|
+
snprintf(node_path, sizeof(node_path), "%s/noded", dir);
|
|
113
|
+
|
|
114
|
+
char server_path[4096];
|
|
115
|
+
snprintf(server_path, sizeof(server_path), "%s/dist/index.js", dir);
|
|
116
|
+
|
|
117
|
+
char libdir[4096];
|
|
118
|
+
snprintf(libdir, sizeof(libdir), "%s/../lib", dir);
|
|
119
|
+
setenv("DYLD_LIBRARY_PATH", libdir, 1);
|
|
120
|
+
|
|
121
|
+
execl(node_path, "noded", server_path, (char *)NULL);
|
|
122
|
+
perror("execl failed");
|
|
123
|
+
return 1;
|
|
124
|
+
}
|
|
125
|
+
`
|
|
126
|
+
);
|
|
127
|
+
execSync(`cc -o ${launcherBin} ${launcherC}`);
|
|
128
|
+
|
|
129
|
+
// 2. struttura bundle
|
|
130
|
+
mkdirSync(join(APP_PATH, "Contents", "MacOS"), { recursive: true });
|
|
131
|
+
mkdirSync(join(APP_PATH, "Contents", "lib"), { recursive: true });
|
|
132
|
+
|
|
133
|
+
writeFileSync(
|
|
134
|
+
join(APP_PATH, "Contents", "Info.plist"),
|
|
135
|
+
`<?xml version="1.0" encoding="UTF-8"?>
|
|
136
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
137
|
+
<plist version="1.0">
|
|
138
|
+
<dict>
|
|
139
|
+
<key>CFBundleIdentifier</key>
|
|
140
|
+
<string>it.otomatik.voicememosmcp</string>
|
|
141
|
+
<key>CFBundleName</key>
|
|
142
|
+
<string>VoiceMemosMCP</string>
|
|
143
|
+
<key>CFBundleExecutable</key>
|
|
144
|
+
<string>VoiceMemosMCP</string>
|
|
145
|
+
<key>CFBundlePackageType</key>
|
|
146
|
+
<string>APPL</string>
|
|
147
|
+
<key>CFBundleVersion</key>
|
|
148
|
+
<string>1.0.0</string>
|
|
149
|
+
<key>CFBundleShortVersionString</key>
|
|
150
|
+
<string>1.0.0</string>
|
|
151
|
+
<key>LSUIElement</key>
|
|
152
|
+
<true/>
|
|
153
|
+
</dict>
|
|
154
|
+
</plist>
|
|
155
|
+
`
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
copyFileSync(launcherBin, APP_EXEC);
|
|
159
|
+
chmod(APP_EXEC, 0o755);
|
|
160
|
+
|
|
161
|
+
// 3. copia node + libnode DENTRO l'app (binario dedicato per TCC)
|
|
162
|
+
const nodeBin = process.execPath;
|
|
163
|
+
copyFileSync(nodeBin, APP_NODE);
|
|
164
|
+
chmod(APP_NODE, 0o755);
|
|
165
|
+
|
|
166
|
+
// libnode: cerca TUTTE le libnode*.dylib e le copia con il nome originale
|
|
167
|
+
try {
|
|
168
|
+
const realNode = execSync(`readlink -f ${nodeBin}`).toString().trim();
|
|
169
|
+
const nodeDir = dirname(realNode); // .../bin
|
|
170
|
+
mkdirSync(APP_LIB_DIR, { recursive: true });
|
|
171
|
+
try {
|
|
172
|
+
const libDir = join(nodeDir, "..", "lib");
|
|
173
|
+
const libs = execSync(`ls ${libDir}/libnode*.dylib 2>/dev/null`).toString().trim().split("\n").filter(Boolean);
|
|
174
|
+
for (const lib of libs) {
|
|
175
|
+
const dest = join(APP_LIB_DIR, lib.split("/").pop());
|
|
176
|
+
copyFileSync(lib, dest);
|
|
177
|
+
chmod(dest, 0o755);
|
|
178
|
+
}
|
|
179
|
+
} catch (e) { /* node statico o bundled: continua */ }
|
|
180
|
+
|
|
181
|
+
// rpath del nodo copiato → punta a ../lib dentro il bundle
|
|
182
|
+
try {
|
|
183
|
+
execSync(`install_name_tool -add_rpath "@executable_path/../lib" ${APP_NODE} 2>/dev/null || true`);
|
|
184
|
+
} catch (e) { /* rpath già presente */ }
|
|
185
|
+
} catch (e) { /* readlink fallito: node forse statico */ }
|
|
186
|
+
|
|
187
|
+
// 4. copia l'INTERA dist del server dentro Contents/MacOS/dist (entry index.js)
|
|
188
|
+
// I moduli (better-sqlite3, sdk, zod) si risolvono via NODE_PATH verso i node_modules del pacchetto
|
|
189
|
+
const pkgRoot = join(__dirname, "..");
|
|
190
|
+
const distSrc = join(pkgRoot, "src", "dist");
|
|
191
|
+
const distDst = join(APP_PATH, "Contents", "MacOS", "dist");
|
|
192
|
+
mkdirSync(distDst, { recursive: true });
|
|
193
|
+
execSync(`cp -R "${distSrc}/." "${distDst}/"`);
|
|
194
|
+
// ESM risolve i bare imports SOLO dal node_modules relativo: creo un symlink
|
|
195
|
+
// Contents/MacOS/dist/node_modules → node_modules del pacchetto installato
|
|
196
|
+
try { execSync(`rm -f "${join(distDst, 'node_modules')}"`); } catch (e) {}
|
|
197
|
+
execSync(`ln -s "${join(pkgRoot, 'node_modules')}" "${join(distDst, 'node_modules')}"`);
|
|
198
|
+
|
|
199
|
+
// 5. firma
|
|
200
|
+
execSync(`codesign --force --deep --sign - "${APP_PATH}" 2>/dev/null`);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function chmod(p, mode) {
|
|
204
|
+
try { execSync(`chmod ${mode.toString(8)} "${p}"`); } catch (e) {}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/** Registra il LaunchAgent e avvia il demone */
|
|
208
|
+
function installDaemon() {
|
|
209
|
+
writeFileSync(join(APP_DIR, "daemon.js"), DAEMON_JS);
|
|
210
|
+
writeFileSync(PLIST_PATH, PLIST(APP_NODE, join(APP_DIR, "daemon.js")));
|
|
211
|
+
try { execSync(`launchctl unload "${PLIST_PATH}" 2>/dev/null`); } catch (e) {}
|
|
212
|
+
execSync(`launchctl load "${PLIST_PATH}"`);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/** Setup guidato */
|
|
216
|
+
async function setup() {
|
|
217
|
+
log("Setup apple-voice-memos-mcp…");
|
|
218
|
+
log("1/4 · Compilo il launcher e creo l'app VoiceMemosMCP.app…");
|
|
219
|
+
buildApp();
|
|
220
|
+
log(" ✓ " + APP_PATH);
|
|
221
|
+
|
|
222
|
+
log("2/4 · Registro il LaunchAgent…");
|
|
223
|
+
installDaemon();
|
|
224
|
+
log(" ✓ demone TCP su " + TCP_HOST + ":" + TCP_PORT);
|
|
225
|
+
|
|
226
|
+
// attesa che il demone risponda
|
|
227
|
+
const up = await portOpen(TCP_PORT);
|
|
228
|
+
log(up ? " ✓ demone attivo" : " ⚠ demone non risponde (riproverà il gateway)");
|
|
229
|
+
|
|
230
|
+
log("3/4 · Permessi macOS (Full Disk Access)…");
|
|
231
|
+
log("");
|
|
232
|
+
log(" ╔══════════════════════════════════════════════════════════╗");
|
|
233
|
+
log(" ║ AZIONE MANUALE RICHIESTA (una volta sola, 10 secondi) ║");
|
|
234
|
+
log(" ║ ║");
|
|
235
|
+
log(" ║ Nel pannello che sto per aprire: ║");
|
|
236
|
+
log(" ║ 1. clicca \"+\" ║");
|
|
237
|
+
log(" ║ 2. premi Cmd+Shift+G e incolla: ║");
|
|
238
|
+
log(` ║ ${APP_PATH.slice(0, 42)} ║`);
|
|
239
|
+
log(` ║ ${APP_PATH.slice(42)} ║`);
|
|
240
|
+
log(" ║ 3. seleziona VoiceMemosMCP.app e attivala ║");
|
|
241
|
+
log(" ╚══════════════════════════════════════════════════════════╝");
|
|
242
|
+
log("");
|
|
243
|
+
try { execSync(`open "x-apple.systempreferences:com.apple.preference.security?Privacy_AllFiles"`); } catch (e) {}
|
|
244
|
+
log("4/4 · Al termine riavvia il client MCP (o esegui /reload). Fatto! ✅");
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/** Status */
|
|
248
|
+
async function status() {
|
|
249
|
+
const up = await portOpen(TCP_PORT);
|
|
250
|
+
console.log("Demone TCP (" + TCP_HOST + ":" + TCP_PORT + "): " + (up ? "ATTIVO ✅" : "NON ATTIVO ❌"));
|
|
251
|
+
console.log("App: " + (existsSync(APP_PATH) ? "presente ✅" : "assente ❌") + " → " + APP_PATH);
|
|
252
|
+
console.log("LaunchAgent: " + (existsSync(PLIST_PATH) ? "registrato ✅" : "assente ❌"));
|
|
253
|
+
// test lettura DB (se il demone è attivo, chiede la lista)
|
|
254
|
+
if (up) {
|
|
255
|
+
try {
|
|
256
|
+
const res = await callMcp("list_voice_memos", { limit: 1 });
|
|
257
|
+
console.log("Accesso al DB Memo Vocali: OK ✅ (" + res.total + " memo)");
|
|
258
|
+
} catch (e) {
|
|
259
|
+
console.log("Accesso al DB Memo Vocali: NEGATO ❌ → manca Full Disk Access su VoiceMemosMCP.app");
|
|
260
|
+
console.log(" Esegui: apple-voice-memos-mcp --setup");
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function callMcp(tool, args) {
|
|
266
|
+
return new Promise((resolve, reject) => {
|
|
267
|
+
const sock = net.connect(TCP_PORT, TCP_HOST);
|
|
268
|
+
let buf = "";
|
|
269
|
+
sock.on("connect", () => {
|
|
270
|
+
sock.write(JSON.stringify({ jsonrpc: "2.0", id: 1, method: "initialize", params: { protocolVersion: "2024-11-05", capabilities: {}, clientInfo: { name: "status", version: "1" } } }) + "\n");
|
|
271
|
+
setTimeout(() => {
|
|
272
|
+
sock.write(JSON.stringify({ jsonrpc: "2.0", method: "notifications/initialized" }) + "\n");
|
|
273
|
+
sock.write(JSON.stringify({ jsonrpc: "2.0", id: 2, method: "tools/call", params: { name: tool, arguments: args } }) + "\n");
|
|
274
|
+
}, 300);
|
|
275
|
+
});
|
|
276
|
+
sock.on("data", (d) => {
|
|
277
|
+
buf += d.toString();
|
|
278
|
+
const lines = buf.split("\n");
|
|
279
|
+
buf = lines.pop();
|
|
280
|
+
for (const line of lines) {
|
|
281
|
+
try {
|
|
282
|
+
const msg = JSON.parse(line);
|
|
283
|
+
if (msg.id === 2) {
|
|
284
|
+
sock.destroy();
|
|
285
|
+
if (msg.result?.isError) return reject(new Error(msg.result.content?.[0]?.text));
|
|
286
|
+
return resolve(JSON.parse(msg.result.content[0].text));
|
|
287
|
+
}
|
|
288
|
+
} catch (e) {}
|
|
289
|
+
}
|
|
290
|
+
});
|
|
291
|
+
sock.on("error", reject);
|
|
292
|
+
setTimeout(() => { sock.destroy(); reject(new Error("timeout")); }, 8000);
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/** Modalità server stdio per il gateway MCP: bridge verso il demone TCP con auto-bootstrap */
|
|
297
|
+
async function serveStdio() {
|
|
298
|
+
// se il demone non risponde, prova a installarlo/riavviarlo al volo
|
|
299
|
+
let up = await portOpen(TCP_PORT);
|
|
300
|
+
if (!up && existsSync(APP_PATH) && existsSync(PLIST_PATH)) {
|
|
301
|
+
try {
|
|
302
|
+
execSync(`launchctl unload "${PLIST_PATH}" 2>/dev/null; launchctl load "${PLIST_PATH}"`);
|
|
303
|
+
await new Promise((r) => setTimeout(r, 2000));
|
|
304
|
+
up = await portOpen(TCP_PORT);
|
|
305
|
+
} catch (e) {}
|
|
306
|
+
}
|
|
307
|
+
if (!up) {
|
|
308
|
+
process.stderr.write(
|
|
309
|
+
"\n[apple-voice-memos-mcp] Demone non attivo. Esegui il setup una volta sola:\n" +
|
|
310
|
+
" npx apple-voice-memos-mcp --setup\n\n"
|
|
311
|
+
);
|
|
312
|
+
// resta comunque in stdio e prova a servire localmente senza demone (fallirà sui dati ma il client MCP non crasha)
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
if (up) {
|
|
316
|
+
// bridge stdio <-> TCP
|
|
317
|
+
const sock = net.connect(TCP_PORT, TCP_HOST);
|
|
318
|
+
sock.on("error", () => process.exit(1));
|
|
319
|
+
process.stdin.pipe(sock);
|
|
320
|
+
sock.pipe(process.stdout);
|
|
321
|
+
process.stdin.on("end", () => sock.end());
|
|
322
|
+
sock.on("close", () => process.exit(0));
|
|
323
|
+
} else {
|
|
324
|
+
// echo server minimo per non far crashare il gateway
|
|
325
|
+
let b = "";
|
|
326
|
+
process.stdin.on("data", (d) => {
|
|
327
|
+
b += d.toString();
|
|
328
|
+
let i;
|
|
329
|
+
while ((i = b.indexOf("\n")) !== -1) {
|
|
330
|
+
const line = b.slice(0, i); b = b.slice(i + 1);
|
|
331
|
+
try {
|
|
332
|
+
const msg = JSON.parse(line);
|
|
333
|
+
if (msg.id !== undefined) {
|
|
334
|
+
process.stdout.write(JSON.stringify({
|
|
335
|
+
jsonrpc: "2.0", id: msg.id,
|
|
336
|
+
error: { code: -32000, message: "daemon not running — run: npx apple-voice-memos-mcp --setup" }
|
|
337
|
+
}) + "\n");
|
|
338
|
+
}
|
|
339
|
+
} catch (e) {}
|
|
340
|
+
}
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
const arg = process.argv[2];
|
|
346
|
+
if (arg === "--setup" || arg === "-s") {
|
|
347
|
+
setup().catch((e) => { log("ERRORE: " + e.message); process.exit(1); });
|
|
348
|
+
} else if (arg === "--status") {
|
|
349
|
+
status().catch((e) => { log("ERRORE: " + e.message); process.exit(1); });
|
|
350
|
+
} else {
|
|
351
|
+
serveStdio();
|
|
352
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "apple-voice-memos-mcp",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "MCP server for Apple Voice Memos on macOS \u2014 zero-config npx, auto-setup with one-click Full Disk Access",
|
|
5
|
+
"bin": {
|
|
6
|
+
"apple-voice-memos-mcp": "bin/cli.js"
|
|
7
|
+
},
|
|
8
|
+
"main": "bin/cli.js",
|
|
9
|
+
"files": [
|
|
10
|
+
"bin/",
|
|
11
|
+
"src/"
|
|
12
|
+
],
|
|
13
|
+
"keywords": [
|
|
14
|
+
"mcp",
|
|
15
|
+
"apple",
|
|
16
|
+
"voice-memos",
|
|
17
|
+
"macos",
|
|
18
|
+
"model-context-protocol",
|
|
19
|
+
"transcription"
|
|
20
|
+
],
|
|
21
|
+
"license": "MIT",
|
|
22
|
+
"engines": {
|
|
23
|
+
"node": ">=18"
|
|
24
|
+
},
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
27
|
+
"better-sqlite3": "^13.0.0",
|
|
28
|
+
"zod": "^3.25.0"
|
|
29
|
+
},
|
|
30
|
+
"overrides": {
|
|
31
|
+
"better-sqlite3": "^13.0.0"
|
|
32
|
+
},
|
|
33
|
+
"type": "module"
|
|
34
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { VoiceMemoMCPServer } from "./server.js";
|
|
3
|
+
async function main() {
|
|
4
|
+
const server = new VoiceMemoMCPServer();
|
|
5
|
+
// Handle graceful shutdown
|
|
6
|
+
process.on("SIGINT", () => {
|
|
7
|
+
server.close();
|
|
8
|
+
process.exit(0);
|
|
9
|
+
});
|
|
10
|
+
process.on("SIGTERM", () => {
|
|
11
|
+
server.close();
|
|
12
|
+
process.exit(0);
|
|
13
|
+
});
|
|
14
|
+
try {
|
|
15
|
+
await server.run();
|
|
16
|
+
}
|
|
17
|
+
catch (error) {
|
|
18
|
+
console.error("Fatal error:", error);
|
|
19
|
+
server.close();
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
main();
|
|
24
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAEjD,KAAK,UAAU,IAAI;IACjB,MAAM,MAAM,GAAG,IAAI,kBAAkB,EAAE,CAAC;IAExC,2BAA2B;IAC3B,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,EAAE;QACxB,MAAM,CAAC,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,CAAC,CAAC;IAEH,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,GAAG,EAAE;QACzB,MAAM,CAAC,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC;QACH,MAAM,MAAM,CAAC,GAAG,EAAE,CAAC;IACrB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,KAAK,CAAC,cAAc,EAAE,KAAK,CAAC,CAAC;QACrC,MAAM,CAAC,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC;AAED,IAAI,EAAE,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare class VoiceMemoMCPServer {
|
|
2
|
+
private server;
|
|
3
|
+
private db;
|
|
4
|
+
private transcriptExtractor;
|
|
5
|
+
private transcriptionService;
|
|
6
|
+
constructor();
|
|
7
|
+
private setupHandlers;
|
|
8
|
+
run(): Promise<void>;
|
|
9
|
+
close(): void;
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAqCA,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,EAAE,CAAoB;IAC9B,OAAO,CAAC,mBAAmB,CAAsB;IACjD,OAAO,CAAC,oBAAoB,CAAuB;;IAwBnD,OAAO,CAAC,aAAa;IAyEf,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC;IAK1B,KAAK,IAAI,IAAI;CAGd"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
2
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
|
+
import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
4
|
+
import { VoiceMemoDatabase } from "./services/voice-memo-db.js";
|
|
5
|
+
import { TranscriptExtractor } from "./services/transcript-extractor.js";
|
|
6
|
+
import { TranscriptionService } from "./services/transcription-service.js";
|
|
7
|
+
import { listMemos, listMemosSchema, listMemosToolDefinition, } from "./tools/list-memos.js";
|
|
8
|
+
import { getMemo, getMemoSchema, getMemoToolDefinition, } from "./tools/get-memo.js";
|
|
9
|
+
import { getAudio, getAudioSchema, getAudioToolDefinition, } from "./tools/get-audio.js";
|
|
10
|
+
import { getTranscript, getTranscriptSchema, getTranscriptToolDefinition, } from "./tools/get-transcript.js";
|
|
11
|
+
import { transcribeMemo, transcribeSchema, transcribeToolDefinition, } from "./tools/transcribe.js";
|
|
12
|
+
export class VoiceMemoMCPServer {
|
|
13
|
+
server;
|
|
14
|
+
db;
|
|
15
|
+
transcriptExtractor;
|
|
16
|
+
transcriptionService;
|
|
17
|
+
constructor() {
|
|
18
|
+
this.server = new Server({
|
|
19
|
+
name: "apple-voice-memo-mcp",
|
|
20
|
+
version: "0.1.0",
|
|
21
|
+
}, {
|
|
22
|
+
capabilities: {
|
|
23
|
+
tools: {},
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
this.db = new VoiceMemoDatabase();
|
|
27
|
+
this.transcriptExtractor = new TranscriptExtractor();
|
|
28
|
+
this.transcriptionService = new TranscriptionService(process.env.VOICE_MEMO_TRANSCRIBER_PATH);
|
|
29
|
+
this.setupHandlers();
|
|
30
|
+
}
|
|
31
|
+
setupHandlers() {
|
|
32
|
+
// List available tools
|
|
33
|
+
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
34
|
+
tools: [
|
|
35
|
+
listMemosToolDefinition,
|
|
36
|
+
getMemoToolDefinition,
|
|
37
|
+
getAudioToolDefinition,
|
|
38
|
+
getTranscriptToolDefinition,
|
|
39
|
+
transcribeToolDefinition,
|
|
40
|
+
],
|
|
41
|
+
}));
|
|
42
|
+
// Handle tool calls
|
|
43
|
+
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
44
|
+
const { name, arguments: args } = request.params;
|
|
45
|
+
try {
|
|
46
|
+
switch (name) {
|
|
47
|
+
case "list_voice_memos": {
|
|
48
|
+
const input = listMemosSchema.parse(args);
|
|
49
|
+
const result = await listMemos(input, this.db);
|
|
50
|
+
return { content: [{ type: "text", text: result }] };
|
|
51
|
+
}
|
|
52
|
+
case "get_voice_memo": {
|
|
53
|
+
const input = getMemoSchema.parse(args);
|
|
54
|
+
const result = await getMemo(input, this.db);
|
|
55
|
+
return { content: [{ type: "text", text: result }] };
|
|
56
|
+
}
|
|
57
|
+
case "get_audio": {
|
|
58
|
+
const input = getAudioSchema.parse(args);
|
|
59
|
+
const result = await getAudio(input, this.db);
|
|
60
|
+
return { content: [{ type: "text", text: result }] };
|
|
61
|
+
}
|
|
62
|
+
case "get_transcript": {
|
|
63
|
+
const input = getTranscriptSchema.parse(args);
|
|
64
|
+
const result = await getTranscript(input, this.db, this.transcriptExtractor);
|
|
65
|
+
return { content: [{ type: "text", text: result }] };
|
|
66
|
+
}
|
|
67
|
+
case "transcribe_memo": {
|
|
68
|
+
const input = transcribeSchema.parse(args);
|
|
69
|
+
const result = await transcribeMemo(input, this.db, this.transcriptionService);
|
|
70
|
+
return { content: [{ type: "text", text: result }] };
|
|
71
|
+
}
|
|
72
|
+
default:
|
|
73
|
+
return {
|
|
74
|
+
content: [{ type: "text", text: `Unknown tool: ${name}` }],
|
|
75
|
+
isError: true,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
|
|
81
|
+
return {
|
|
82
|
+
content: [{ type: "text", text: `Error: ${errorMessage}` }],
|
|
83
|
+
isError: true,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
async run() {
|
|
89
|
+
const transport = new StdioServerTransport();
|
|
90
|
+
await this.server.connect(transport);
|
|
91
|
+
}
|
|
92
|
+
close() {
|
|
93
|
+
this.db.close();
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { Transcript } from "../types/index.js";
|
|
2
|
+
export declare class TranscriptExtractor {
|
|
3
|
+
extractTranscript(memoPath: string, format?: "text" | "json" | "timestamped"): Transcript | null;
|
|
4
|
+
extractTranscriptById(memoId: number, memoPath: string, format?: "text" | "json" | "timestamped"): Transcript | null;
|
|
5
|
+
}
|
|
6
|
+
//# sourceMappingURL=transcript-extractor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transcript-extractor.d.ts","sourceRoot":"","sources":["../../src/services/transcript-extractor.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAEpD,qBAAa,mBAAmB;IAC9B,iBAAiB,CACf,QAAQ,EAAE,MAAM,EAChB,MAAM,GAAE,MAAM,GAAG,MAAM,GAAG,aAAsB,GAC/C,UAAU,GAAG,IAAI;IA4BpB,qBAAqB,CACnB,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,MAAM,GAAE,MAAM,GAAG,MAAM,GAAG,aAAsB,GAC/C,UAAU,GAAG,IAAI;CAGrB"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { getAudioFilePath } from "../utils/paths.js";
|
|
2
|
+
import { extractTsrpAtom, parseTsrpToText, parseTsrpToSegments, } from "../utils/mp4-parser.js";
|
|
3
|
+
export class TranscriptExtractor {
|
|
4
|
+
extractTranscript(memoPath, format = "text") {
|
|
5
|
+
const fullPath = getAudioFilePath(memoPath);
|
|
6
|
+
if (!fullPath) {
|
|
7
|
+
return null;
|
|
8
|
+
}
|
|
9
|
+
const tsrpData = extractTsrpAtom(fullPath);
|
|
10
|
+
if (!tsrpData) {
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
const text = parseTsrpToText(tsrpData);
|
|
14
|
+
const locale = tsrpData.locale?.identifier || "unknown";
|
|
15
|
+
if (format === "text") {
|
|
16
|
+
return { text, locale };
|
|
17
|
+
}
|
|
18
|
+
if (format === "timestamped" || format === "json") {
|
|
19
|
+
const segments = parseTsrpToSegments(tsrpData);
|
|
20
|
+
return { text, segments, locale };
|
|
21
|
+
}
|
|
22
|
+
return { text, locale };
|
|
23
|
+
}
|
|
24
|
+
extractTranscriptById(memoId, memoPath, format = "text") {
|
|
25
|
+
return this.extractTranscript(memoPath, format);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=transcript-extractor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transcript-extractor.js","sourceRoot":"","sources":["../../src/services/transcript-extractor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,EACL,eAAe,EACf,eAAe,EACf,mBAAmB,GACpB,MAAM,wBAAwB,CAAC;AAGhC,MAAM,OAAO,mBAAmB;IAC9B,iBAAiB,CACf,QAAgB,EAChB,SAA0C,MAAM;QAEhD,MAAM,QAAQ,GAAG,gBAAgB,CAAC,QAAQ,CAAC,CAAC;QAE5C,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,OAAO,IAAI,CAAC;QACd,CAAC;QAED,MAAM,QAAQ,GAAG,eAAe,CAAC,QAAQ,CAAC,CAAC;QAE3C,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,OAAO,IAAI,CAAC;QACd,CAAC;QAED,MAAM,IAAI,GAAG,eAAe,CAAC,QAAQ,CAAC,CAAC;QACvC,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM,EAAE,UAAU,IAAI,SAAS,CAAC;QAExD,IAAI,MAAM,KAAK,MAAM,EAAE,CAAC;YACtB,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;QAC1B,CAAC;QAED,IAAI,MAAM,KAAK,aAAa,IAAI,MAAM,KAAK,MAAM,EAAE,CAAC;YAClD,MAAM,QAAQ,GAAG,mBAAmB,CAAC,QAAQ,CAAC,CAAC;YAC/C,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC;QACpC,CAAC;QAED,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;IAC1B,CAAC;IAED,qBAAqB,CACnB,MAAc,EACd,QAAgB,EAChB,SAA0C,MAAM;QAEhD,OAAO,IAAI,CAAC,iBAAiB,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IAClD,CAAC;CACF"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { TranscriptionResult } from "../types/index.js";
|
|
2
|
+
/**
|
|
3
|
+
* Service for transcribing audio files using Apple's SFSpeechRecognizer.
|
|
4
|
+
*
|
|
5
|
+
* This requires a Swift helper CLI tool to be built and available.
|
|
6
|
+
* The helper uses SFSpeechRecognizer which requires:
|
|
7
|
+
* 1. Speech Recognition permission granted
|
|
8
|
+
* 2. Language packs downloaded via System Settings > Accessibility > Voice Control
|
|
9
|
+
*/
|
|
10
|
+
export declare class TranscriptionService {
|
|
11
|
+
private swiftHelperPath;
|
|
12
|
+
constructor(swiftHelperPath?: string);
|
|
13
|
+
transcribe(memoPath: string, language?: string): Promise<TranscriptionResult>;
|
|
14
|
+
private runSwiftTranscriber;
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=transcription-service.d.ts.map
|