screenpipe-mcp 0.18.9 → 0.18.11

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/dist/index.js CHANGED
@@ -40,7 +40,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
40
40
  const index_js_1 = require("@modelcontextprotocol/sdk/server/index.js");
41
41
  const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
42
42
  const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
43
- const ws_1 = require("ws");
44
43
  const fs = __importStar(require("fs"));
45
44
  const path = __importStar(require("path"));
46
45
  const os = __importStar(require("os"));
@@ -316,6 +315,10 @@ const TOOLS = [
316
315
  },
317
316
  speaker_ids: { type: "string", description: "Comma-separated speaker IDs to filter audio" },
318
317
  speaker_name: { type: "string", description: "Filter audio by speaker name (case-insensitive partial match)" },
318
+ tags: {
319
+ type: "string",
320
+ description: "Comma-separated tags; returns only items carrying ALL of them (e.g. 'person:ada,project:atlas'). Works for screen + audio (content_type 'ocr'/'audio'/'all', tags written by add-tags) AND memories (content_type 'memory', tags written by update-memory). Same tag string links across all three, so two items sharing a tag are connected. Use namespaced tags (person:, project:, topic:) to link people/projects/topics. content_type 'input' and 'accessibility' have no tags and return nothing when this is set.",
321
+ },
319
322
  max_content_length: {
320
323
  type: "integer",
321
324
  description: "Truncate each result's text via middle-truncation. Use 200-500 to keep responses compact.",
@@ -397,15 +400,21 @@ const TOOLS = [
397
400
  },
398
401
  {
399
402
  name: "export-video",
400
- description: "Export an MP4 video of screen recordings for a time range. " +
401
- "Returns the file path. Can take a few minutes for long ranges.",
403
+ description: "Export an MP4 of screen recordings for a time range, with synced microphone audio. " +
404
+ "Frames are placed at their real timestamps, so the clip's duration matches the " +
405
+ "wall-clock span you requested (not a sped-up timelapse). Returns the file path. " +
406
+ "Can take a few minutes for long ranges.",
402
407
  annotations: { title: "Export Video", readOnlyHint: false, destructiveHint: false, openWorldHint: false },
403
408
  inputSchema: {
404
409
  type: "object",
405
410
  properties: {
406
- start_time: { type: "string", description: "ISO 8601 UTC or relative" },
407
- end_time: { type: "string", description: "ISO 8601 UTC or relative" },
408
- fps: { type: "number", description: "Output FPS (default 1.0). Higher = smoother but larger file.", default: 1.0 },
411
+ start_time: { type: "string", description: 'ISO 8601 UTC or relative (e.g. "5m ago", "now")' },
412
+ end_time: { type: "string", description: 'ISO 8601 UTC or relative (e.g. "5m ago", "now")' },
413
+ output_path: {
414
+ type: "string",
415
+ description: "Optional absolute path for the MP4 (e.g. ~/Downloads/clip.mp4). " +
416
+ "Defaults to the screenpipe data dir's exports/ folder.",
417
+ },
409
418
  },
410
419
  required: ["start_time", "end_time"],
411
420
  },
@@ -421,7 +430,7 @@ const TOOLS = [
421
430
  properties: {
422
431
  id: { type: "integer", description: "Memory ID — omit to create new, provide to update/delete" },
423
432
  content: { type: "string", description: "Memory text (required for creation)" },
424
- tags: { type: "array", items: { type: "string" }, description: "Categorization tags (e.g. ['work', 'project-x'])" },
433
+ tags: { type: "array", items: { type: "string" }, description: "Tags. Prefer namespaced (person:ada, project:atlas, topic:pricing) so this memory links to the same people/projects you tag on frames/audio. Retrieve with search-content content_type='memory' tags='person:ada'." },
425
434
  importance: { type: "number", description: "0.0 (trivial) to 1.0 (critical). Default 0.5." },
426
435
  source_context: { type: "object", description: "Optional metadata linking to source (app, timestamp, etc.)" },
427
436
  delete: { type: "boolean", description: "Set true to delete the memory identified by id" },
@@ -481,14 +490,18 @@ const TOOLS = [
481
490
  },
482
491
  {
483
492
  name: "add-tags",
484
- description: "Add tags to a content item (vision frame or audio chunk) for organization and retrieval.",
493
+ description: "Tag a screen frame (vision) or audio chunk (audio) so it can be retrieved later. " +
494
+ "Tags are a shared linking layer: use namespaced tags (person:ada, project:atlas, topic:pricing) to connect a capture to a person, project, or topic. " +
495
+ "The SAME tag string also works on memories (via update-memory), so tagging a frame and a memory with person:ada links them. " +
496
+ "Retrieve later with search-content tags='person:ada' (add content_type+start_time/end_time to scope to a timeframe). " +
497
+ "Note: frames are pruned by retention, so for durable links prefer tagging a memory; tag frames/audio for shorter-term recall.",
485
498
  annotations: { title: "Add Tags", readOnlyHint: false, destructiveHint: false, openWorldHint: false },
486
499
  inputSchema: {
487
500
  type: "object",
488
501
  properties: {
489
- content_type: { type: "string", enum: ["vision", "audio"], description: "Type of content to tag" },
490
- id: { type: "integer", description: "Content item ID" },
491
- tags: { type: "array", items: { type: "string" }, description: "Tags to add" },
502
+ content_type: { type: "string", enum: ["vision", "audio"], description: "vision = screen frame, audio = audio chunk. Get the id from search-content results (frame_id / chunk_id)." },
503
+ id: { type: "integer", description: "Content item ID (OCR result frame_id, or audio result chunk_id)" },
504
+ tags: { type: "array", items: { type: "string" }, description: "Tags to add. Prefer namespaced: person:<name>, project:<name>, topic:<name>." },
492
505
  },
493
506
  required: ["content_type", "id", "tags"],
494
507
  },
@@ -1219,119 +1232,51 @@ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
1219
1232
  case "export-video": {
1220
1233
  const startTime = normalizeTime(args.start_time);
1221
1234
  const endTime = normalizeTime(args.end_time);
1222
- const fps = args.fps || 1.0;
1223
1235
  if (!startTime || !endTime) {
1224
1236
  return {
1225
1237
  content: [{ type: "text", text: "Error: start_time and end_time are required" }],
1226
1238
  };
1227
1239
  }
1228
- // Get frame IDs for the time range
1229
- const searchParams = new URLSearchParams({
1230
- content_type: "ocr",
1231
- start_time: startTime,
1232
- end_time: endTime,
1233
- limit: "10000",
1234
- });
1235
- const searchResponse = await callAPI(`/search?${searchParams.toString()}`);
1236
- const searchData = await searchResponse.json();
1237
- const results = searchData.data || [];
1238
- if (results.length === 0) {
1240
+ // A real-time MP4 with synced microphone audio, rendered server-side by the
1241
+ // engine export core (the `screenpipe export` CLI's HTTP twin). MCP runs on the
1242
+ // same host as the backend, so the returned path is a local file. Frames sit at
1243
+ // their real timestamps, so the clip duration matches the wall-clock span.
1244
+ try {
1245
+ const body = { start: startTime, end: endTime };
1246
+ if (typeof args.output_path === "string" && args.output_path.trim()) {
1247
+ body.output_path = args.output_path;
1248
+ }
1249
+ const response = await callAPI("/export", {
1250
+ method: "POST",
1251
+ body: JSON.stringify(body),
1252
+ });
1253
+ const data = (await response.json());
1254
+ const sizeMb = data.file_size_bytes
1255
+ ? (data.file_size_bytes / (1024 * 1024)).toFixed(1)
1256
+ : null;
1239
1257
  return {
1240
1258
  content: [
1241
1259
  {
1242
1260
  type: "text",
1243
- text: `No screen recordings found between ${startTime} and ${endTime}.`,
1261
+ text: `Video exported (with audio): ${data.output_path}\n` +
1262
+ `${data.frame_count ?? 0} frames | ${data.audio_chunk_count ?? 0} audio chunks` +
1263
+ (sizeMb ? ` | ${sizeMb} MB` : "") +
1264
+ (data.duration_secs ? ` | ${data.duration_secs}s` : "") +
1265
+ ` | ${startTime} → ${endTime}`,
1244
1266
  },
1245
1267
  ],
1246
1268
  };
1247
1269
  }
1248
- const frameIds = [];
1249
- const seenIds = new Set();
1250
- for (const result of results) {
1251
- if (result.type === "OCR" && result.content?.frame_id) {
1252
- const frameId = result.content.frame_id;
1253
- if (!seenIds.has(frameId)) {
1254
- seenIds.add(frameId);
1255
- frameIds.push(frameId);
1256
- }
1257
- }
1258
- }
1259
- if (frameIds.length === 0) {
1260
- return {
1261
- content: [{ type: "text", text: "No valid frame IDs found (audio-only?)." }],
1262
- };
1263
- }
1264
- frameIds.sort((a, b) => a - b);
1265
- const wsUrl = `ws://localhost:${port}/frames/export?fps=${fps}`;
1266
- const exportResult = await new Promise((resolve) => {
1267
- const ws = new ws_1.WebSocket(wsUrl);
1268
- let resolved = false;
1269
- const timeout = setTimeout(() => {
1270
- if (!resolved) {
1271
- resolved = true;
1272
- ws.close();
1273
- resolve({ success: false, error: "Export timed out after 5 minutes" });
1274
- }
1275
- }, 5 * 60 * 1000);
1276
- ws.on("open", () => {
1277
- ws.send(JSON.stringify({ frame_ids: frameIds }));
1278
- });
1279
- ws.on("error", (error) => {
1280
- if (!resolved) {
1281
- resolved = true;
1282
- clearTimeout(timeout);
1283
- resolve({ success: false, error: `WebSocket error: ${error.message}` });
1284
- }
1285
- });
1286
- ws.on("close", () => {
1287
- if (!resolved) {
1288
- resolved = true;
1289
- clearTimeout(timeout);
1290
- resolve({ success: false, error: "Connection closed unexpectedly" });
1291
- }
1292
- });
1293
- ws.on("message", (data) => {
1294
- try {
1295
- const message = JSON.parse(data.toString());
1296
- if (message.status === "completed" && message.video_data) {
1297
- const tempDir = os.tmpdir();
1298
- const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
1299
- const filename = `screenpipe_export_${timestamp}.mp4`;
1300
- const filePath = path.join(tempDir, filename);
1301
- fs.writeFileSync(filePath, Buffer.from(message.video_data));
1302
- resolved = true;
1303
- clearTimeout(timeout);
1304
- ws.close();
1305
- resolve({ success: true, filePath, frameCount: frameIds.length });
1306
- }
1307
- else if (message.status === "error") {
1308
- resolved = true;
1309
- clearTimeout(timeout);
1310
- ws.close();
1311
- resolve({ success: false, error: message.error || "Export failed" });
1312
- }
1313
- }
1314
- catch {
1315
- // Ignore parse errors for progress messages
1316
- }
1317
- });
1318
- });
1319
- if (exportResult.success && exportResult.filePath) {
1270
+ catch (err) {
1320
1271
  return {
1321
1272
  content: [
1322
1273
  {
1323
1274
  type: "text",
1324
- text: `Video exported: ${exportResult.filePath}\n` +
1325
- `Frames: ${exportResult.frameCount} | ${startTime} → ${endTime} | ${fps} fps`,
1275
+ text: `Export failed: ${err instanceof Error ? err.message : String(err)}`,
1326
1276
  },
1327
1277
  ],
1328
1278
  };
1329
1279
  }
1330
- else {
1331
- return {
1332
- content: [{ type: "text", text: `Export failed: ${exportResult.error}` }],
1333
- };
1334
- }
1335
1280
  }
1336
1281
  case "update-memory": {
1337
1282
  if (args.delete && args.id) {
package/manifest.json CHANGED
@@ -32,7 +32,7 @@
32
32
  },
33
33
  {
34
34
  "name": "export-video",
35
- "description": "Export screen recordings as MP4 video for a specific time range"
35
+ "description": "Export screen recordings as an MP4 for a time range — with synced audio by default (pass fps for a silent timelapse)"
36
36
  },
37
37
  {
38
38
  "name": "list-meetings",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "screenpipe-mcp",
3
- "version": "0.18.9",
3
+ "version": "0.18.11",
4
4
  "description": "MCP server for screenpipe - search your screen recordings and audio transcriptions",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -28,14 +28,12 @@
28
28
  "audio-transcription"
29
29
  ],
30
30
  "author": "Screenpipe",
31
- "license": "MIT",
31
+ "license": "SEE LICENSE IN LICENSE.md",
32
32
  "dependencies": {
33
- "@modelcontextprotocol/sdk": "^1.27.1",
34
- "ws": "^8.19.0"
33
+ "@modelcontextprotocol/sdk": "^1.27.1"
35
34
  },
36
35
  "devDependencies": {
37
36
  "@types/node": "^25.3.5",
38
- "@types/ws": "^8.18.1",
39
37
  "typescript": "^5.9.3",
40
38
  "ts-node": "^10.9.2",
41
39
  "vitest": "^4.0.18"