screenpipe-mcp 0.18.1 → 0.18.2

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.
Files changed (3) hide show
  1. package/dist/index.js +65 -64
  2. package/package.json +1 -1
  3. package/src/index.ts +68 -67
package/dist/index.js CHANGED
@@ -56,16 +56,20 @@ const SCREENPIPE_API = `http://localhost:${port}`;
56
56
  // Discover the local API key, in priority order:
57
57
  //
58
58
  // 1. env vars set by the launcher (Claude Desktop config, terminal, etc.)
59
- // 2. direct sqlite3 read of ~/.screenpipe/db.sqlite (plaintext entries only
60
- // encrypted ones need keychain, handled by 3+)
61
- // 3. bundled `bun` shipped with the desktop app → `bun x screenpipe@latest auth token`
62
- // this is the kill-shot for Claude-Desktop-via-MCP: Claude strips PATH so
63
- // `npx` and `sqlite3` lookups fail, but the desktop app's bundled bun is
64
- // at a deterministic path. We invoke it with an absolute path, which
65
- // then runs the screenpipe CLI's `auth token` command — which goes
66
- // through `find_api_auth_key` (handles the encrypted-secret-store case).
67
- // 4. node-adjacent npx (legacy fallback for users without the desktop app)
68
- // 5. PATH-based npx (very last resort)
59
+ // 2. CLI via bundled `bun` from screenpipe.app at a deterministic absolute
60
+ // path. Runs `bun x screenpipe@latest auth token` → goes through the
61
+ // Rust CLI's `find_api_auth_key` resolver, which handles the encrypted
62
+ // keychain-backed secret store. This is the canonical path: same
63
+ // contract as `screenpipe auth token` in a terminal, no PATH needed.
64
+ // 3. CLI via node-adjacent npx for dev environments that have node but
65
+ // not the desktop app.
66
+ // 4. CLI via PATH-based npx — last CLI fallback.
67
+ // 5. Direct sqlite3 read of ~/.screenpipe/db.sqlite plaintext entries
68
+ // only (encrypted entries need the keychain, which only the CLI can
69
+ // reach). Kept as a final last-resort for users who have screenpipe
70
+ // *data* but no working CLI install (rare). Demoted below the CLI
71
+ // paths because it reimplements logic that lives in `auth_key.rs` and
72
+ // can silently drift on storage-format changes.
69
73
  //
70
74
  // If all 5 miss we log a loud stderr warning so it surfaces in the host's
71
75
  // MCP log instead of the user just seeing 403s with no explanation.
@@ -81,55 +85,13 @@ function discoverApiKey() {
81
85
  const fs = require("fs");
82
86
  // eslint-disable-next-line @typescript-eslint/no-var-requires
83
87
  const { execFileSync, execSync } = require("child_process");
84
- // Common absolute paths for `sqlite3`. Claude Desktop's MCP launcher
85
- // strips PATH so the bare command name `sqlite3` would fail spawn
86
- // even though `/usr/bin/sqlite3` is always present on macOS. Try the
87
- // bare name first (cheap; works on dev machines with a normal shell)
88
- // then walk known absolute paths.
89
- const sqliteCandidates = process.platform === "win32"
90
- ? ["sqlite3.exe", "C\\:Windows\\System32\\sqlite3.exe"]
91
- : process.platform === "darwin"
92
- ? ["sqlite3", "/usr/bin/sqlite3", "/opt/homebrew/bin/sqlite3", "/usr/local/bin/sqlite3"]
93
- : ["sqlite3", "/usr/bin/sqlite3", "/usr/local/bin/sqlite3"];
94
- // 2. Direct sqlite3 read of the secret store. Only succeeds for
95
- // plaintext entries (nonce all zeros). Encrypted entries fall
96
- // through to the CLI path which can decrypt via keychain.
97
- try {
98
- const dbPath = path.join(os.homedir(), ".screenpipe", "db.sqlite");
99
- if (fs.existsSync(dbPath)) {
100
- let row = null;
101
- for (const candidate of sqliteCandidates) {
102
- try {
103
- row = execFileSync(candidate, [dbPath, "SELECT hex(nonce), value FROM secrets WHERE key = 'api_auth_key';"], { timeout: 5000, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
104
- break;
105
- }
106
- catch {
107
- // try next candidate
108
- }
109
- }
110
- if (row) {
111
- const sepIdx = row.indexOf("|");
112
- const nonceHex = sepIdx >= 0 ? row.substring(0, sepIdx) : "";
113
- const value = sepIdx >= 0 ? row.substring(sepIdx + 1) : row;
114
- const isPlaintext = !nonceHex || /^0+$/.test(nonceHex);
115
- if (isPlaintext && value) {
116
- const decoded = Buffer.from(value, "base64").toString("utf-8");
117
- if (decoded && decoded.startsWith("sp-"))
118
- return decoded;
119
- if (value.startsWith("sp-"))
120
- return value;
121
- }
122
- // Non-zero nonce = encrypted — fall through to bun/npx which decrypt via keychain.
123
- }
124
- }
125
- }
126
- catch { }
127
- // 3. Bundled `bun` shipped with the desktop app. The Tauri externalBin
128
- // config (apps/screenpipe-app-tauri/src-tauri/tauri.prod.conf.json)
129
- // places it next to the main app executable; on each OS the install
130
- // path is deterministic so we don't need PATH or current_exe — both
131
- // of which Claude Desktop's MCP launcher rolls back.
132
88
  const home = os.homedir();
89
+ // 2. CLI via bundled `bun` shipped with the desktop app. The Tauri
90
+ // externalBin config places `bun` next to the main app exe at a
91
+ // deterministic install path on each OS, so we don't need PATH —
92
+ // which Claude Desktop's MCP launcher strips. The CLI's `auth
93
+ // token` goes through `find_api_auth_key` and decrypts via
94
+ // keychain when needed.
133
95
  const bunCandidates = process.platform === "darwin"
134
96
  ? [
135
97
  // Standard system-wide install
@@ -168,9 +130,8 @@ function discoverApiKey() {
168
130
  // try next candidate
169
131
  }
170
132
  }
171
- // 4. npx adjacent to the running node works in dev environments
172
- // where the user installed @screenpipe/mcp via npx without the
173
- // desktop app.
133
+ // 3. CLI via npx adjacent to the running node. Works for dev
134
+ // environments without the desktop app.
174
135
  try {
175
136
  const npxName = process.platform === "win32" ? "npx.cmd" : "npx";
176
137
  const npxPath = path.join(path.dirname(process.execPath), npxName);
@@ -185,8 +146,8 @@ function discoverApiKey() {
185
146
  }
186
147
  }
187
148
  catch { }
188
- // 5. PATH-based npx last-ditch. Will fail under Claude Desktop's
189
- // sanitized env; useful only on raw shells.
149
+ // 4. CLI via PATH-based npx. Last CLI try; works on raw shells with
150
+ // npx on PATH.
190
151
  try {
191
152
  const token = execSync("npx screenpipe@latest auth token", {
192
153
  timeout: 30000,
@@ -197,15 +158,55 @@ function discoverApiKey() {
197
158
  return token;
198
159
  }
199
160
  catch { }
161
+ // 5. Direct sqlite3 read of the secret store (last-resort). Plaintext
162
+ // entries only — encrypted ones live behind the keychain, which the
163
+ // CLI paths above already cover. Used when the user has screenpipe
164
+ // data on disk but no working CLI install.
165
+ const sqliteCandidates = process.platform === "win32"
166
+ ? ["sqlite3.exe", "C:\\Windows\\System32\\sqlite3.exe"]
167
+ : process.platform === "darwin"
168
+ ? ["sqlite3", "/usr/bin/sqlite3", "/opt/homebrew/bin/sqlite3", "/usr/local/bin/sqlite3"]
169
+ : ["sqlite3", "/usr/bin/sqlite3", "/usr/local/bin/sqlite3"];
170
+ try {
171
+ const dbPath = path.join(home, ".screenpipe", "db.sqlite");
172
+ if (fs.existsSync(dbPath)) {
173
+ let row = null;
174
+ for (const candidate of sqliteCandidates) {
175
+ try {
176
+ row = execFileSync(candidate, [dbPath, "SELECT hex(nonce), value FROM secrets WHERE key = 'api_auth_key';"], { timeout: 5000, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
177
+ break;
178
+ }
179
+ catch {
180
+ // try next candidate
181
+ }
182
+ }
183
+ if (row) {
184
+ const sepIdx = row.indexOf("|");
185
+ const nonceHex = sepIdx >= 0 ? row.substring(0, sepIdx) : "";
186
+ const value = sepIdx >= 0 ? row.substring(sepIdx + 1) : row;
187
+ const isPlaintext = !nonceHex || /^0+$/.test(nonceHex);
188
+ if (isPlaintext && value) {
189
+ const decoded = Buffer.from(value, "base64").toString("utf-8");
190
+ if (decoded && decoded.startsWith("sp-"))
191
+ return decoded;
192
+ if (value.startsWith("sp-"))
193
+ return value;
194
+ }
195
+ // Encrypted — only the CLI paths above can decrypt this; we
196
+ // already tried them.
197
+ }
198
+ }
199
+ }
200
+ catch { }
200
201
  // All five paths missed. Log loudly to stderr so the host's MCP
201
202
  // panel surfaces this instead of the user seeing cryptic 403s from
202
203
  // the screenpipe server on every tool call.
203
204
  process.stderr.write([
204
205
  "[screenpipe-mcp] could not discover SCREENPIPE_LOCAL_API_KEY from any source.",
205
206
  " - env vars (SCREENPIPE_LOCAL_API_KEY / SCREENPIPE_API_KEY) not set",
206
- " - direct sqlite3 read of ~/.screenpipe/db.sqlite failed",
207
207
  " - bundled `bun` from screenpipe.app not found at any known install path",
208
208
  " - npx fallback unavailable",
209
+ " - direct sqlite3 read of ~/.screenpipe/db.sqlite failed",
209
210
  "Fix: set SCREENPIPE_LOCAL_API_KEY in your MCP launcher's env block,",
210
211
  "or install the screenpipe desktop app (https://screenpi.pe).",
211
212
  "",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "screenpipe-mcp",
3
- "version": "0.18.1",
3
+ "version": "0.18.2",
4
4
  "description": "MCP server for screenpipe - search your screen recordings and audio transcriptions",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
package/src/index.ts CHANGED
@@ -31,16 +31,20 @@ const SCREENPIPE_API = `http://localhost:${port}`;
31
31
  // Discover the local API key, in priority order:
32
32
  //
33
33
  // 1. env vars set by the launcher (Claude Desktop config, terminal, etc.)
34
- // 2. direct sqlite3 read of ~/.screenpipe/db.sqlite (plaintext entries only
35
- // encrypted ones need keychain, handled by 3+)
36
- // 3. bundled `bun` shipped with the desktop app → `bun x screenpipe@latest auth token`
37
- // this is the kill-shot for Claude-Desktop-via-MCP: Claude strips PATH so
38
- // `npx` and `sqlite3` lookups fail, but the desktop app's bundled bun is
39
- // at a deterministic path. We invoke it with an absolute path, which
40
- // then runs the screenpipe CLI's `auth token` command — which goes
41
- // through `find_api_auth_key` (handles the encrypted-secret-store case).
42
- // 4. node-adjacent npx (legacy fallback for users without the desktop app)
43
- // 5. PATH-based npx (very last resort)
34
+ // 2. CLI via bundled `bun` from screenpipe.app at a deterministic absolute
35
+ // path. Runs `bun x screenpipe@latest auth token` → goes through the
36
+ // Rust CLI's `find_api_auth_key` resolver, which handles the encrypted
37
+ // keychain-backed secret store. This is the canonical path: same
38
+ // contract as `screenpipe auth token` in a terminal, no PATH needed.
39
+ // 3. CLI via node-adjacent npx for dev environments that have node but
40
+ // not the desktop app.
41
+ // 4. CLI via PATH-based npx — last CLI fallback.
42
+ // 5. Direct sqlite3 read of ~/.screenpipe/db.sqlite plaintext entries
43
+ // only (encrypted entries need the keychain, which only the CLI can
44
+ // reach). Kept as a final last-resort for users who have screenpipe
45
+ // *data* but no working CLI install (rare). Demoted below the CLI
46
+ // paths because it reimplements logic that lives in `auth_key.rs` and
47
+ // can silently drift on storage-format changes.
44
48
  //
45
49
  // If all 5 miss we log a loud stderr warning so it surfaces in the host's
46
50
  // MCP log instead of the user just seeing 403s with no explanation.
@@ -57,58 +61,14 @@ function discoverApiKey(): string {
57
61
  // eslint-disable-next-line @typescript-eslint/no-var-requires
58
62
  const { execFileSync, execSync } = require("child_process");
59
63
 
60
- // Common absolute paths for `sqlite3`. Claude Desktop's MCP launcher
61
- // strips PATH so the bare command name `sqlite3` would fail spawn
62
- // even though `/usr/bin/sqlite3` is always present on macOS. Try the
63
- // bare name first (cheap; works on dev machines with a normal shell)
64
- // then walk known absolute paths.
65
- const sqliteCandidates: string[] =
66
- process.platform === "win32"
67
- ? ["sqlite3.exe", "C\\:Windows\\System32\\sqlite3.exe"]
68
- : process.platform === "darwin"
69
- ? ["sqlite3", "/usr/bin/sqlite3", "/opt/homebrew/bin/sqlite3", "/usr/local/bin/sqlite3"]
70
- : ["sqlite3", "/usr/bin/sqlite3", "/usr/local/bin/sqlite3"];
71
-
72
- // 2. Direct sqlite3 read of the secret store. Only succeeds for
73
- // plaintext entries (nonce all zeros). Encrypted entries fall
74
- // through to the CLI path which can decrypt via keychain.
75
- try {
76
- const dbPath = path.join(os.homedir(), ".screenpipe", "db.sqlite");
77
- if (fs.existsSync(dbPath)) {
78
- let row: string | null = null;
79
- for (const candidate of sqliteCandidates) {
80
- try {
81
- row = execFileSync(
82
- candidate,
83
- [dbPath, "SELECT hex(nonce), value FROM secrets WHERE key = 'api_auth_key';"],
84
- { timeout: 5000, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] },
85
- ).trim();
86
- break;
87
- } catch {
88
- // try next candidate
89
- }
90
- }
91
- if (row) {
92
- const sepIdx = row.indexOf("|");
93
- const nonceHex = sepIdx >= 0 ? row.substring(0, sepIdx) : "";
94
- const value = sepIdx >= 0 ? row.substring(sepIdx + 1) : row;
95
- const isPlaintext = !nonceHex || /^0+$/.test(nonceHex);
96
- if (isPlaintext && value) {
97
- const decoded = Buffer.from(value, "base64").toString("utf-8");
98
- if (decoded && decoded.startsWith("sp-")) return decoded;
99
- if (value.startsWith("sp-")) return value;
100
- }
101
- // Non-zero nonce = encrypted — fall through to bun/npx which decrypt via keychain.
102
- }
103
- }
104
- } catch {}
105
-
106
- // 3. Bundled `bun` shipped with the desktop app. The Tauri externalBin
107
- // config (apps/screenpipe-app-tauri/src-tauri/tauri.prod.conf.json)
108
- // places it next to the main app executable; on each OS the install
109
- // path is deterministic so we don't need PATH or current_exe — both
110
- // of which Claude Desktop's MCP launcher rolls back.
111
64
  const home = os.homedir();
65
+
66
+ // 2. CLI via bundled `bun` shipped with the desktop app. The Tauri
67
+ // externalBin config places `bun` next to the main app exe at a
68
+ // deterministic install path on each OS, so we don't need PATH —
69
+ // which Claude Desktop's MCP launcher strips. The CLI's `auth
70
+ // token` goes through `find_api_auth_key` and decrypts via
71
+ // keychain when needed.
112
72
  const bunCandidates: string[] =
113
73
  process.platform === "darwin"
114
74
  ? [
@@ -146,9 +106,8 @@ function discoverApiKey(): string {
146
106
  }
147
107
  }
148
108
 
149
- // 4. npx adjacent to the running node works in dev environments
150
- // where the user installed @screenpipe/mcp via npx without the
151
- // desktop app.
109
+ // 3. CLI via npx adjacent to the running node. Works for dev
110
+ // environments without the desktop app.
152
111
  try {
153
112
  const npxName = process.platform === "win32" ? "npx.cmd" : "npx";
154
113
  const npxPath = path.join(path.dirname(process.execPath), npxName);
@@ -162,8 +121,8 @@ function discoverApiKey(): string {
162
121
  }
163
122
  } catch {}
164
123
 
165
- // 5. PATH-based npx last-ditch. Will fail under Claude Desktop's
166
- // sanitized env; useful only on raw shells.
124
+ // 4. CLI via PATH-based npx. Last CLI try; works on raw shells with
125
+ // npx on PATH.
167
126
  try {
168
127
  const token = execSync("npx screenpipe@latest auth token", {
169
128
  timeout: 30000,
@@ -173,6 +132,48 @@ function discoverApiKey(): string {
173
132
  if (token && token.startsWith("sp-")) return token;
174
133
  } catch {}
175
134
 
135
+ // 5. Direct sqlite3 read of the secret store (last-resort). Plaintext
136
+ // entries only — encrypted ones live behind the keychain, which the
137
+ // CLI paths above already cover. Used when the user has screenpipe
138
+ // data on disk but no working CLI install.
139
+ const sqliteCandidates: string[] =
140
+ process.platform === "win32"
141
+ ? ["sqlite3.exe", "C:\\Windows\\System32\\sqlite3.exe"]
142
+ : process.platform === "darwin"
143
+ ? ["sqlite3", "/usr/bin/sqlite3", "/opt/homebrew/bin/sqlite3", "/usr/local/bin/sqlite3"]
144
+ : ["sqlite3", "/usr/bin/sqlite3", "/usr/local/bin/sqlite3"];
145
+ try {
146
+ const dbPath = path.join(home, ".screenpipe", "db.sqlite");
147
+ if (fs.existsSync(dbPath)) {
148
+ let row: string | null = null;
149
+ for (const candidate of sqliteCandidates) {
150
+ try {
151
+ row = execFileSync(
152
+ candidate,
153
+ [dbPath, "SELECT hex(nonce), value FROM secrets WHERE key = 'api_auth_key';"],
154
+ { timeout: 5000, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] },
155
+ ).trim();
156
+ break;
157
+ } catch {
158
+ // try next candidate
159
+ }
160
+ }
161
+ if (row) {
162
+ const sepIdx = row.indexOf("|");
163
+ const nonceHex = sepIdx >= 0 ? row.substring(0, sepIdx) : "";
164
+ const value = sepIdx >= 0 ? row.substring(sepIdx + 1) : row;
165
+ const isPlaintext = !nonceHex || /^0+$/.test(nonceHex);
166
+ if (isPlaintext && value) {
167
+ const decoded = Buffer.from(value, "base64").toString("utf-8");
168
+ if (decoded && decoded.startsWith("sp-")) return decoded;
169
+ if (value.startsWith("sp-")) return value;
170
+ }
171
+ // Encrypted — only the CLI paths above can decrypt this; we
172
+ // already tried them.
173
+ }
174
+ }
175
+ } catch {}
176
+
176
177
  // All five paths missed. Log loudly to stderr so the host's MCP
177
178
  // panel surfaces this instead of the user seeing cryptic 403s from
178
179
  // the screenpipe server on every tool call.
@@ -180,9 +181,9 @@ function discoverApiKey(): string {
180
181
  [
181
182
  "[screenpipe-mcp] could not discover SCREENPIPE_LOCAL_API_KEY from any source.",
182
183
  " - env vars (SCREENPIPE_LOCAL_API_KEY / SCREENPIPE_API_KEY) not set",
183
- " - direct sqlite3 read of ~/.screenpipe/db.sqlite failed",
184
184
  " - bundled `bun` from screenpipe.app not found at any known install path",
185
185
  " - npx fallback unavailable",
186
+ " - direct sqlite3 read of ~/.screenpipe/db.sqlite failed",
186
187
  "Fix: set SCREENPIPE_LOCAL_API_KEY in your MCP launcher's env block,",
187
188
  "or install the screenpipe desktop app (https://screenpi.pe).",
188
189
  "",