screenpipe-mcp 0.18.12 → 0.18.13
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 +259 -0
- package/package.json +1 -1
- package/server.json +1 -1
- package/src/index.ts +272 -0
package/dist/index.js
CHANGED
|
@@ -656,6 +656,82 @@ const TOOLS = [
|
|
|
656
656
|
required: ["action"],
|
|
657
657
|
},
|
|
658
658
|
},
|
|
659
|
+
// ----- Pipes: scheduled AI automations ----------------------------------
|
|
660
|
+
// The pipe lifecycle. The rich how-to (prompt format, schedule syntax,
|
|
661
|
+
// presets, how a pipe should query screenpipe) lives in the
|
|
662
|
+
// screenpipe://guide/pipes resource — keep these descriptions short and
|
|
663
|
+
// point there, per progressive disclosure.
|
|
664
|
+
{
|
|
665
|
+
name: "list-pipes",
|
|
666
|
+
description: "List the user's pipes (scheduled AI automations) with their enabled state + schedule. " +
|
|
667
|
+
"USE WHEN: the user asks what automations/pipes exist, or before you create or edit one.",
|
|
668
|
+
annotations: { title: "List Pipes", readOnlyHint: true, openWorldHint: false, idempotentHint: true },
|
|
669
|
+
inputSchema: { type: "object", properties: {} },
|
|
670
|
+
},
|
|
671
|
+
{
|
|
672
|
+
name: "create-pipe",
|
|
673
|
+
description: "Create a pipe — a scheduled AI automation that runs a markdown prompt on a schedule (e.g. 'every day at 9am'). " +
|
|
674
|
+
"Writes ~/.screenpipe/pipes/<name>/pipe.md, installs it, enables it, and (by default) runs it once to test. " +
|
|
675
|
+
"USE WHEN: the user wants to automate a recurring task (daily summary, reminder, report, monitor, sync). " +
|
|
676
|
+
"IMPORTANT: read the screenpipe://guide/pipes resource FIRST — it documents the prompt format, schedule syntax, presets, and how the pipe prompt should query screenpipe. After creating, check pipe-logs to confirm the test run worked.",
|
|
677
|
+
annotations: { title: "Create Pipe", readOnlyHint: false, openWorldHint: false, idempotentHint: false },
|
|
678
|
+
inputSchema: {
|
|
679
|
+
type: "object",
|
|
680
|
+
properties: {
|
|
681
|
+
name: {
|
|
682
|
+
type: "string",
|
|
683
|
+
description: "kebab-case id, e.g. 'daily-time-report'. Becomes the folder name + pipe id.",
|
|
684
|
+
},
|
|
685
|
+
prompt: {
|
|
686
|
+
type: "string",
|
|
687
|
+
description: "The markdown instructions the AI agent runs every scheduled execution. Be specific: what to query (which screenpipe endpoints + time range), how to process it, and what to output (write a note, send a notification, push to a connection). screenpipe prepends a context header (time range, timezone, OS, API base + key) before each run, so no template variables are needed. See screenpipe://guide/pipes.",
|
|
688
|
+
},
|
|
689
|
+
schedule: {
|
|
690
|
+
type: "string",
|
|
691
|
+
description: "When to run: 'every 30m', 'every 1h', 'every day at 9am', 'every monday at 9am', or a cron expression like '0 9 * * *'.",
|
|
692
|
+
},
|
|
693
|
+
enabled: { type: "boolean", description: "Enable on creation (default true).", default: true },
|
|
694
|
+
preset: {
|
|
695
|
+
type: "array",
|
|
696
|
+
items: { type: "string" },
|
|
697
|
+
description: "Optional AI model preset name(s), e.g. ['Primary','Fallback']. Omit to use the default preset.",
|
|
698
|
+
},
|
|
699
|
+
history: {
|
|
700
|
+
type: "boolean",
|
|
701
|
+
description: "Feed the previous run's output back in as context on the next run (default false).",
|
|
702
|
+
default: false,
|
|
703
|
+
},
|
|
704
|
+
run_now: {
|
|
705
|
+
type: "boolean",
|
|
706
|
+
description: "Run once immediately after creating, to test it (default true).",
|
|
707
|
+
default: true,
|
|
708
|
+
},
|
|
709
|
+
},
|
|
710
|
+
required: ["name", "prompt", "schedule"],
|
|
711
|
+
},
|
|
712
|
+
},
|
|
713
|
+
{
|
|
714
|
+
name: "run-pipe",
|
|
715
|
+
description: "Run a pipe once immediately (a test run), independent of its schedule. " +
|
|
716
|
+
"USE WHEN: you just created/edited a pipe and want to verify it, or the user says 'run X now'. Then read pipe-logs to see what it did.",
|
|
717
|
+
annotations: { title: "Run Pipe", readOnlyHint: false, openWorldHint: false, idempotentHint: false },
|
|
718
|
+
inputSchema: {
|
|
719
|
+
type: "object",
|
|
720
|
+
properties: { name: { type: "string", description: "The pipe id/name." } },
|
|
721
|
+
required: ["name"],
|
|
722
|
+
},
|
|
723
|
+
},
|
|
724
|
+
{
|
|
725
|
+
name: "pipe-logs",
|
|
726
|
+
description: "Get a pipe's recent execution logs / output. " +
|
|
727
|
+
"USE WHEN: debugging why a pipe misbehaved, or reading the result of a test run.",
|
|
728
|
+
annotations: { title: "Pipe Logs", readOnlyHint: true, openWorldHint: false, idempotentHint: true },
|
|
729
|
+
inputSchema: {
|
|
730
|
+
type: "object",
|
|
731
|
+
properties: { name: { type: "string", description: "The pipe id/name." } },
|
|
732
|
+
required: ["name"],
|
|
733
|
+
},
|
|
734
|
+
},
|
|
659
735
|
];
|
|
660
736
|
// ---------------------------------------------------------------------------
|
|
661
737
|
// Enterprise team tools — registered only when a team API token is present.
|
|
@@ -760,6 +836,12 @@ const RESOURCES = [
|
|
|
760
836
|
description: "How to use screenpipe tools effectively — search strategy, progressive disclosure, and common patterns",
|
|
761
837
|
mimeType: "text/markdown",
|
|
762
838
|
},
|
|
839
|
+
{
|
|
840
|
+
uri: "screenpipe://guide/pipes",
|
|
841
|
+
name: "Creating Pipes",
|
|
842
|
+
description: "How to create pipes (scheduled AI automations): the pipe.md prompt format, schedule syntax, presets, how the prompt should query screenpipe, and the create→run→logs lifecycle. Read before using create-pipe.",
|
|
843
|
+
mimeType: "text/markdown",
|
|
844
|
+
},
|
|
763
845
|
];
|
|
764
846
|
server.setRequestHandler(types_js_1.ListResourcesRequestSchema, async () => {
|
|
765
847
|
return { resources: RESOURCES };
|
|
@@ -829,6 +911,7 @@ server.setRequestHandler(types_js_1.ReadResourceRequestSchema, async (request) =
|
|
|
829
911
|
- "Find when I was on Twitter" → search-content with app_name='Arc' (or the browser name), q='twitter'
|
|
830
912
|
- "Remember that I prefer X" → update-memory with content describing the preference
|
|
831
913
|
- "What do you remember about X?" → search-content with content_type='memory', q='X'
|
|
914
|
+
- "Automate X every day / on a schedule" → read the screenpipe://guide/pipes resource, then create-pipe (a scheduled AI automation)
|
|
832
915
|
|
|
833
916
|
## Deep Links
|
|
834
917
|
|
|
@@ -836,6 +919,64 @@ When referencing specific moments in results, create clickable links:
|
|
|
836
919
|
- Frame: [10:30 AM — Chrome](screenpipe://frame/{frame_id}) — use frame_id from search results
|
|
837
920
|
- Timeline: [meeting at 3pm](screenpipe://timeline?timestamp=2024-01-15T15:00:00Z) — use exact timestamp from results
|
|
838
921
|
Never fabricate IDs or timestamps — only use values from actual results.
|
|
922
|
+
`,
|
|
923
|
+
},
|
|
924
|
+
],
|
|
925
|
+
};
|
|
926
|
+
}
|
|
927
|
+
if (uri === "screenpipe://guide/pipes") {
|
|
928
|
+
return {
|
|
929
|
+
contents: [
|
|
930
|
+
{
|
|
931
|
+
uri,
|
|
932
|
+
mimeType: "text/markdown",
|
|
933
|
+
text: `# Creating Pipes — scheduled AI automations
|
|
934
|
+
|
|
935
|
+
A **pipe** is a markdown prompt that an AI agent runs on a schedule. Each pipe is a folder \`~/.screenpipe/pipes/<name>/pipe.md\` with YAML frontmatter + a prompt body. Use the **create-pipe** tool — it writes the file, installs, enables, and (by default) runs it once to test. Manage with **list-pipes**, **run-pipe**, and **pipe-logs**.
|
|
936
|
+
|
|
937
|
+
## pipe.md anatomy
|
|
938
|
+
|
|
939
|
+
\`\`\`markdown
|
|
940
|
+
---
|
|
941
|
+
schedule: every day at 9am
|
|
942
|
+
enabled: true
|
|
943
|
+
preset: ["Primary", "Fallback"] # optional model preset(s); omit for default
|
|
944
|
+
history: false # optional; feed prior run's output back in
|
|
945
|
+
---
|
|
946
|
+
|
|
947
|
+
Your instructions here. This prompt is what the AI agent executes on schedule.
|
|
948
|
+
\`\`\`
|
|
949
|
+
|
|
950
|
+
**schedule** (required): \`every 30m\` · \`every 1h\` · \`every day at 9am\` · \`every monday at 9am\` · or cron \`0 9 * * *\`.
|
|
951
|
+
|
|
952
|
+
screenpipe **prepends a context header** before every run (current time range, timezone, OS, API base URL + auth). So the prompt does NOT need template variables or to hardcode the key — it just says what to do.
|
|
953
|
+
|
|
954
|
+
## Writing a good pipe prompt
|
|
955
|
+
|
|
956
|
+
Make the prompt do three things, concretely:
|
|
957
|
+
1. **Query** the relevant window of activity. Prefer the same endpoints these MCP tools wrap:
|
|
958
|
+
- \`GET /activity-summary?start_time=...&end_time=now\` — apps/windows/durations. **Let this endpoint own all time math; never sum minutes in the prompt (the model drifts).**
|
|
959
|
+
- \`GET /search?q=...&content_type=all&start_time=...\` — specific screen text, audio transcripts, memories.
|
|
960
|
+
- \`GET /memories?...\`, \`GET /meetings?...\` for curated facts / meetings.
|
|
961
|
+
Always pass \`start_time\` — never scan the whole history.
|
|
962
|
+
2. **Process / summarize** the results.
|
|
963
|
+
3. **Output** somewhere: write a note/file, send a desktop notification (\`POST\` the Tauri sidecar on port 11435 \`/notify\`), or push to a configured connection (Telegram/Slack/Discord/Email — see the CLI \`connection\` commands).
|
|
964
|
+
|
|
965
|
+
Keep each pipe to **one bounded job**. A focused "summarize my day and write it to a note" beats a vague "monitor everything".
|
|
966
|
+
|
|
967
|
+
## Lifecycle
|
|
968
|
+
|
|
969
|
+
- **create-pipe** → writes pipe.md + installs + enables (+ optional \`run_now\` test).
|
|
970
|
+
- **run-pipe** → run once now to test, independent of schedule.
|
|
971
|
+
- **pipe-logs** → read the output / debug.
|
|
972
|
+
- To change config later: \`POST /pipes/<name>/config\` with e.g. \`{ "schedule": "every 1h", "enabled": true }\`.
|
|
973
|
+
|
|
974
|
+
## Example
|
|
975
|
+
|
|
976
|
+
A daily time-audit pipe:
|
|
977
|
+
- name: \`daily-time-report\`
|
|
978
|
+
- schedule: \`every day at 6pm\`
|
|
979
|
+
- prompt: "Call /activity-summary for today (start_time='today', end_time=now). Group time by app and project. Write a concise markdown report of where my time went and the top 3 time sinks, then send it as a desktop notification with a link to the timeline."
|
|
839
980
|
`,
|
|
840
981
|
},
|
|
841
982
|
],
|
|
@@ -1000,6 +1141,124 @@ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
|
|
|
1000
1141
|
}
|
|
1001
1142
|
try {
|
|
1002
1143
|
switch (name) {
|
|
1144
|
+
case "list-pipes": {
|
|
1145
|
+
const res = await callAPI("/pipes");
|
|
1146
|
+
const data = await res.json();
|
|
1147
|
+
const pipes = Array.isArray(data) ? data : data.data || [];
|
|
1148
|
+
if (!pipes.length) {
|
|
1149
|
+
return {
|
|
1150
|
+
content: [
|
|
1151
|
+
{
|
|
1152
|
+
type: "text",
|
|
1153
|
+
text: "No pipes yet. Use create-pipe to add a scheduled automation (read the screenpipe://guide/pipes resource first).",
|
|
1154
|
+
},
|
|
1155
|
+
],
|
|
1156
|
+
};
|
|
1157
|
+
}
|
|
1158
|
+
const rows = pipes.map((p) => {
|
|
1159
|
+
const id = p.id || p.name || p.pipe_id || "?";
|
|
1160
|
+
const cfg = p.config || p;
|
|
1161
|
+
const en = (cfg.enabled ?? p.enabled) ? "on " : "off";
|
|
1162
|
+
const sch = cfg.schedule || p.schedule || "?";
|
|
1163
|
+
return `${en} | ${id} | ${sch}`;
|
|
1164
|
+
});
|
|
1165
|
+
return {
|
|
1166
|
+
content: [
|
|
1167
|
+
{ type: "text", text: `pipes (enabled | name | schedule):\n${rows.join("\n")}` },
|
|
1168
|
+
],
|
|
1169
|
+
};
|
|
1170
|
+
}
|
|
1171
|
+
case "create-pipe": {
|
|
1172
|
+
const pipeName = String(args.name || "").trim();
|
|
1173
|
+
if (!/^[a-z0-9][a-z0-9-_]*$/i.test(pipeName)) {
|
|
1174
|
+
throw new Error("invalid pipe name — use kebab-case letters/numbers/dashes, e.g. 'daily-time-report'");
|
|
1175
|
+
}
|
|
1176
|
+
const prompt = String(args.prompt || "").trim();
|
|
1177
|
+
const schedule = String(args.schedule || "").trim();
|
|
1178
|
+
if (!prompt)
|
|
1179
|
+
throw new Error("prompt is required");
|
|
1180
|
+
if (!schedule)
|
|
1181
|
+
throw new Error("schedule is required");
|
|
1182
|
+
const enabled = args.enabled !== false;
|
|
1183
|
+
const runNow = args.run_now !== false;
|
|
1184
|
+
const fm = ["---", `schedule: ${schedule}`, `enabled: ${enabled}`];
|
|
1185
|
+
if (Array.isArray(args.preset) && args.preset.length) {
|
|
1186
|
+
fm.push(`preset: ${JSON.stringify(args.preset)}`);
|
|
1187
|
+
}
|
|
1188
|
+
if (args.history === true)
|
|
1189
|
+
fm.push("history: true");
|
|
1190
|
+
fm.push("---", "", prompt, "");
|
|
1191
|
+
const md = fm.join("\n");
|
|
1192
|
+
const dir = path.join(os.homedir(), ".screenpipe", "pipes", pipeName);
|
|
1193
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
1194
|
+
fs.writeFileSync(path.join(dir, "pipe.md"), md, "utf8");
|
|
1195
|
+
const steps = [`wrote ${path.join(dir, "pipe.md")}`];
|
|
1196
|
+
const instRes = await callAPI("/pipes/install", {
|
|
1197
|
+
method: "POST",
|
|
1198
|
+
body: JSON.stringify({ source: dir }),
|
|
1199
|
+
});
|
|
1200
|
+
const inst = await instRes.json();
|
|
1201
|
+
if (inst?.error)
|
|
1202
|
+
throw new Error(`install failed: ${inst.error}`);
|
|
1203
|
+
const pipeId = inst?.name || pipeName;
|
|
1204
|
+
steps.push(`installed as "${pipeId}"`);
|
|
1205
|
+
if (enabled) {
|
|
1206
|
+
await callAPI(`/pipes/${encodeURIComponent(pipeId)}/enable`, {
|
|
1207
|
+
method: "POST",
|
|
1208
|
+
body: JSON.stringify({ enabled: true }),
|
|
1209
|
+
});
|
|
1210
|
+
steps.push("enabled");
|
|
1211
|
+
}
|
|
1212
|
+
let runNote = "";
|
|
1213
|
+
if (runNow) {
|
|
1214
|
+
try {
|
|
1215
|
+
await callAPI(`/pipes/${encodeURIComponent(pipeId)}/run`, { method: "POST" });
|
|
1216
|
+
steps.push("started a test run");
|
|
1217
|
+
runNote = `\n\nA test run was started — read it with pipe-logs (name="${pipeId}").`;
|
|
1218
|
+
}
|
|
1219
|
+
catch (e) {
|
|
1220
|
+
runNote = `\n\nCreated, but the test run couldn't start: ${e.message}. Try run-pipe later.`;
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
return {
|
|
1224
|
+
content: [
|
|
1225
|
+
{
|
|
1226
|
+
type: "text",
|
|
1227
|
+
text: `Created pipe "${pipeId}" — schedule: ${schedule}, ${enabled ? "enabled" : "disabled"}.\n` +
|
|
1228
|
+
steps.map((s) => `- ${s}`).join("\n") +
|
|
1229
|
+
runNote,
|
|
1230
|
+
},
|
|
1231
|
+
],
|
|
1232
|
+
};
|
|
1233
|
+
}
|
|
1234
|
+
case "run-pipe": {
|
|
1235
|
+
const pipeName = String(args.name || "").trim();
|
|
1236
|
+
if (!pipeName)
|
|
1237
|
+
throw new Error("name is required");
|
|
1238
|
+
const res = await callAPI(`/pipes/${encodeURIComponent(pipeName)}/run`, {
|
|
1239
|
+
method: "POST",
|
|
1240
|
+
});
|
|
1241
|
+
const data = await res.json().catch(() => ({}));
|
|
1242
|
+
if (data?.error)
|
|
1243
|
+
throw new Error(String(data.error));
|
|
1244
|
+
return {
|
|
1245
|
+
content: [
|
|
1246
|
+
{
|
|
1247
|
+
type: "text",
|
|
1248
|
+
text: `Started a run of "${pipeName}". Read pipe-logs (name="${pipeName}") for the output.`,
|
|
1249
|
+
},
|
|
1250
|
+
],
|
|
1251
|
+
};
|
|
1252
|
+
}
|
|
1253
|
+
case "pipe-logs": {
|
|
1254
|
+
const pipeName = String(args.name || "").trim();
|
|
1255
|
+
if (!pipeName)
|
|
1256
|
+
throw new Error("name is required");
|
|
1257
|
+
const res = await callAPI(`/pipes/${encodeURIComponent(pipeName)}/logs`);
|
|
1258
|
+
const text = await res.text();
|
|
1259
|
+
const trimmed = text.length > 6000 ? `…${text.slice(-6000)}` : text;
|
|
1260
|
+
return { content: [{ type: "text", text: trimmed || "(no logs yet)" }] };
|
|
1261
|
+
}
|
|
1003
1262
|
case "search-content": {
|
|
1004
1263
|
const includeFrames = args.include_frames === true;
|
|
1005
1264
|
const normalized = normalizeTimeFields(args);
|
package/package.json
CHANGED
package/server.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
|
|
3
3
|
"name": "io.github.screenpipe/screenpipe-mcp",
|
|
4
4
|
"title": "screenpipe",
|
|
5
|
-
"description": "Search your local screen recordings, audio
|
|
5
|
+
"description": "Search your local screen recordings, audio transcripts, and computer activity from screenpipe.",
|
|
6
6
|
"repository": {
|
|
7
7
|
"url": "https://github.com/screenpipe/screenpipe",
|
|
8
8
|
"source": "github",
|
package/src/index.ts
CHANGED
|
@@ -659,6 +659,88 @@ const TOOLS: Tool[] = [
|
|
|
659
659
|
required: ["action"],
|
|
660
660
|
},
|
|
661
661
|
},
|
|
662
|
+
// ----- Pipes: scheduled AI automations ----------------------------------
|
|
663
|
+
// The pipe lifecycle. The rich how-to (prompt format, schedule syntax,
|
|
664
|
+
// presets, how a pipe should query screenpipe) lives in the
|
|
665
|
+
// screenpipe://guide/pipes resource — keep these descriptions short and
|
|
666
|
+
// point there, per progressive disclosure.
|
|
667
|
+
{
|
|
668
|
+
name: "list-pipes",
|
|
669
|
+
description:
|
|
670
|
+
"List the user's pipes (scheduled AI automations) with their enabled state + schedule. " +
|
|
671
|
+
"USE WHEN: the user asks what automations/pipes exist, or before you create or edit one.",
|
|
672
|
+
annotations: { title: "List Pipes", readOnlyHint: true, openWorldHint: false, idempotentHint: true },
|
|
673
|
+
inputSchema: { type: "object", properties: {} },
|
|
674
|
+
},
|
|
675
|
+
{
|
|
676
|
+
name: "create-pipe",
|
|
677
|
+
description:
|
|
678
|
+
"Create a pipe — a scheduled AI automation that runs a markdown prompt on a schedule (e.g. 'every day at 9am'). " +
|
|
679
|
+
"Writes ~/.screenpipe/pipes/<name>/pipe.md, installs it, enables it, and (by default) runs it once to test. " +
|
|
680
|
+
"USE WHEN: the user wants to automate a recurring task (daily summary, reminder, report, monitor, sync). " +
|
|
681
|
+
"IMPORTANT: read the screenpipe://guide/pipes resource FIRST — it documents the prompt format, schedule syntax, presets, and how the pipe prompt should query screenpipe. After creating, check pipe-logs to confirm the test run worked.",
|
|
682
|
+
annotations: { title: "Create Pipe", readOnlyHint: false, openWorldHint: false, idempotentHint: false },
|
|
683
|
+
inputSchema: {
|
|
684
|
+
type: "object",
|
|
685
|
+
properties: {
|
|
686
|
+
name: {
|
|
687
|
+
type: "string",
|
|
688
|
+
description: "kebab-case id, e.g. 'daily-time-report'. Becomes the folder name + pipe id.",
|
|
689
|
+
},
|
|
690
|
+
prompt: {
|
|
691
|
+
type: "string",
|
|
692
|
+
description:
|
|
693
|
+
"The markdown instructions the AI agent runs every scheduled execution. Be specific: what to query (which screenpipe endpoints + time range), how to process it, and what to output (write a note, send a notification, push to a connection). screenpipe prepends a context header (time range, timezone, OS, API base + key) before each run, so no template variables are needed. See screenpipe://guide/pipes.",
|
|
694
|
+
},
|
|
695
|
+
schedule: {
|
|
696
|
+
type: "string",
|
|
697
|
+
description:
|
|
698
|
+
"When to run: 'every 30m', 'every 1h', 'every day at 9am', 'every monday at 9am', or a cron expression like '0 9 * * *'.",
|
|
699
|
+
},
|
|
700
|
+
enabled: { type: "boolean", description: "Enable on creation (default true).", default: true },
|
|
701
|
+
preset: {
|
|
702
|
+
type: "array",
|
|
703
|
+
items: { type: "string" },
|
|
704
|
+
description: "Optional AI model preset name(s), e.g. ['Primary','Fallback']. Omit to use the default preset.",
|
|
705
|
+
},
|
|
706
|
+
history: {
|
|
707
|
+
type: "boolean",
|
|
708
|
+
description: "Feed the previous run's output back in as context on the next run (default false).",
|
|
709
|
+
default: false,
|
|
710
|
+
},
|
|
711
|
+
run_now: {
|
|
712
|
+
type: "boolean",
|
|
713
|
+
description: "Run once immediately after creating, to test it (default true).",
|
|
714
|
+
default: true,
|
|
715
|
+
},
|
|
716
|
+
},
|
|
717
|
+
required: ["name", "prompt", "schedule"],
|
|
718
|
+
},
|
|
719
|
+
},
|
|
720
|
+
{
|
|
721
|
+
name: "run-pipe",
|
|
722
|
+
description:
|
|
723
|
+
"Run a pipe once immediately (a test run), independent of its schedule. " +
|
|
724
|
+
"USE WHEN: you just created/edited a pipe and want to verify it, or the user says 'run X now'. Then read pipe-logs to see what it did.",
|
|
725
|
+
annotations: { title: "Run Pipe", readOnlyHint: false, openWorldHint: false, idempotentHint: false },
|
|
726
|
+
inputSchema: {
|
|
727
|
+
type: "object",
|
|
728
|
+
properties: { name: { type: "string", description: "The pipe id/name." } },
|
|
729
|
+
required: ["name"],
|
|
730
|
+
},
|
|
731
|
+
},
|
|
732
|
+
{
|
|
733
|
+
name: "pipe-logs",
|
|
734
|
+
description:
|
|
735
|
+
"Get a pipe's recent execution logs / output. " +
|
|
736
|
+
"USE WHEN: debugging why a pipe misbehaved, or reading the result of a test run.",
|
|
737
|
+
annotations: { title: "Pipe Logs", readOnlyHint: true, openWorldHint: false, idempotentHint: true },
|
|
738
|
+
inputSchema: {
|
|
739
|
+
type: "object",
|
|
740
|
+
properties: { name: { type: "string", description: "The pipe id/name." } },
|
|
741
|
+
required: ["name"],
|
|
742
|
+
},
|
|
743
|
+
},
|
|
662
744
|
];
|
|
663
745
|
|
|
664
746
|
// ---------------------------------------------------------------------------
|
|
@@ -772,6 +854,13 @@ const RESOURCES = [
|
|
|
772
854
|
description: "How to use screenpipe tools effectively — search strategy, progressive disclosure, and common patterns",
|
|
773
855
|
mimeType: "text/markdown",
|
|
774
856
|
},
|
|
857
|
+
{
|
|
858
|
+
uri: "screenpipe://guide/pipes",
|
|
859
|
+
name: "Creating Pipes",
|
|
860
|
+
description:
|
|
861
|
+
"How to create pipes (scheduled AI automations): the pipe.md prompt format, schedule syntax, presets, how the prompt should query screenpipe, and the create→run→logs lifecycle. Read before using create-pipe.",
|
|
862
|
+
mimeType: "text/markdown",
|
|
863
|
+
},
|
|
775
864
|
];
|
|
776
865
|
|
|
777
866
|
server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
@@ -849,6 +938,7 @@ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
|
849
938
|
- "Find when I was on Twitter" → search-content with app_name='Arc' (or the browser name), q='twitter'
|
|
850
939
|
- "Remember that I prefer X" → update-memory with content describing the preference
|
|
851
940
|
- "What do you remember about X?" → search-content with content_type='memory', q='X'
|
|
941
|
+
- "Automate X every day / on a schedule" → read the screenpipe://guide/pipes resource, then create-pipe (a scheduled AI automation)
|
|
852
942
|
|
|
853
943
|
## Deep Links
|
|
854
944
|
|
|
@@ -862,6 +952,65 @@ Never fabricate IDs or timestamps — only use values from actual results.
|
|
|
862
952
|
};
|
|
863
953
|
}
|
|
864
954
|
|
|
955
|
+
if (uri === "screenpipe://guide/pipes") {
|
|
956
|
+
return {
|
|
957
|
+
contents: [
|
|
958
|
+
{
|
|
959
|
+
uri,
|
|
960
|
+
mimeType: "text/markdown",
|
|
961
|
+
text: `# Creating Pipes — scheduled AI automations
|
|
962
|
+
|
|
963
|
+
A **pipe** is a markdown prompt that an AI agent runs on a schedule. Each pipe is a folder \`~/.screenpipe/pipes/<name>/pipe.md\` with YAML frontmatter + a prompt body. Use the **create-pipe** tool — it writes the file, installs, enables, and (by default) runs it once to test. Manage with **list-pipes**, **run-pipe**, and **pipe-logs**.
|
|
964
|
+
|
|
965
|
+
## pipe.md anatomy
|
|
966
|
+
|
|
967
|
+
\`\`\`markdown
|
|
968
|
+
---
|
|
969
|
+
schedule: every day at 9am
|
|
970
|
+
enabled: true
|
|
971
|
+
preset: ["Primary", "Fallback"] # optional model preset(s); omit for default
|
|
972
|
+
history: false # optional; feed prior run's output back in
|
|
973
|
+
---
|
|
974
|
+
|
|
975
|
+
Your instructions here. This prompt is what the AI agent executes on schedule.
|
|
976
|
+
\`\`\`
|
|
977
|
+
|
|
978
|
+
**schedule** (required): \`every 30m\` · \`every 1h\` · \`every day at 9am\` · \`every monday at 9am\` · or cron \`0 9 * * *\`.
|
|
979
|
+
|
|
980
|
+
screenpipe **prepends a context header** before every run (current time range, timezone, OS, API base URL + auth). So the prompt does NOT need template variables or to hardcode the key — it just says what to do.
|
|
981
|
+
|
|
982
|
+
## Writing a good pipe prompt
|
|
983
|
+
|
|
984
|
+
Make the prompt do three things, concretely:
|
|
985
|
+
1. **Query** the relevant window of activity. Prefer the same endpoints these MCP tools wrap:
|
|
986
|
+
- \`GET /activity-summary?start_time=...&end_time=now\` — apps/windows/durations. **Let this endpoint own all time math; never sum minutes in the prompt (the model drifts).**
|
|
987
|
+
- \`GET /search?q=...&content_type=all&start_time=...\` — specific screen text, audio transcripts, memories.
|
|
988
|
+
- \`GET /memories?...\`, \`GET /meetings?...\` for curated facts / meetings.
|
|
989
|
+
Always pass \`start_time\` — never scan the whole history.
|
|
990
|
+
2. **Process / summarize** the results.
|
|
991
|
+
3. **Output** somewhere: write a note/file, send a desktop notification (\`POST\` the Tauri sidecar on port 11435 \`/notify\`), or push to a configured connection (Telegram/Slack/Discord/Email — see the CLI \`connection\` commands).
|
|
992
|
+
|
|
993
|
+
Keep each pipe to **one bounded job**. A focused "summarize my day and write it to a note" beats a vague "monitor everything".
|
|
994
|
+
|
|
995
|
+
## Lifecycle
|
|
996
|
+
|
|
997
|
+
- **create-pipe** → writes pipe.md + installs + enables (+ optional \`run_now\` test).
|
|
998
|
+
- **run-pipe** → run once now to test, independent of schedule.
|
|
999
|
+
- **pipe-logs** → read the output / debug.
|
|
1000
|
+
- To change config later: \`POST /pipes/<name>/config\` with e.g. \`{ "schedule": "every 1h", "enabled": true }\`.
|
|
1001
|
+
|
|
1002
|
+
## Example
|
|
1003
|
+
|
|
1004
|
+
A daily time-audit pipe:
|
|
1005
|
+
- name: \`daily-time-report\`
|
|
1006
|
+
- schedule: \`every day at 6pm\`
|
|
1007
|
+
- prompt: "Call /activity-summary for today (start_time='today', end_time=now). Group time by app and project. Write a concise markdown report of where my time went and the top 3 time sinks, then send it as a desktop notification with a link to the timeline."
|
|
1008
|
+
`,
|
|
1009
|
+
},
|
|
1010
|
+
],
|
|
1011
|
+
};
|
|
1012
|
+
}
|
|
1013
|
+
|
|
865
1014
|
throw new Error(`Unknown resource: ${uri}`);
|
|
866
1015
|
});
|
|
867
1016
|
|
|
@@ -1028,6 +1177,129 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1028
1177
|
|
|
1029
1178
|
try {
|
|
1030
1179
|
switch (name) {
|
|
1180
|
+
case "list-pipes": {
|
|
1181
|
+
const res = await callAPI("/pipes");
|
|
1182
|
+
const data = await res.json();
|
|
1183
|
+
const pipes: any[] = Array.isArray(data) ? data : data.data || [];
|
|
1184
|
+
if (!pipes.length) {
|
|
1185
|
+
return {
|
|
1186
|
+
content: [
|
|
1187
|
+
{
|
|
1188
|
+
type: "text",
|
|
1189
|
+
text: "No pipes yet. Use create-pipe to add a scheduled automation (read the screenpipe://guide/pipes resource first).",
|
|
1190
|
+
},
|
|
1191
|
+
],
|
|
1192
|
+
};
|
|
1193
|
+
}
|
|
1194
|
+
const rows = pipes.map((p: any) => {
|
|
1195
|
+
const id = p.id || p.name || p.pipe_id || "?";
|
|
1196
|
+
const cfg = p.config || p;
|
|
1197
|
+
const en = (cfg.enabled ?? p.enabled) ? "on " : "off";
|
|
1198
|
+
const sch = cfg.schedule || p.schedule || "?";
|
|
1199
|
+
return `${en} | ${id} | ${sch}`;
|
|
1200
|
+
});
|
|
1201
|
+
return {
|
|
1202
|
+
content: [
|
|
1203
|
+
{ type: "text", text: `pipes (enabled | name | schedule):\n${rows.join("\n")}` },
|
|
1204
|
+
],
|
|
1205
|
+
};
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
case "create-pipe": {
|
|
1209
|
+
const pipeName = String(args.name || "").trim();
|
|
1210
|
+
if (!/^[a-z0-9][a-z0-9-_]*$/i.test(pipeName)) {
|
|
1211
|
+
throw new Error(
|
|
1212
|
+
"invalid pipe name — use kebab-case letters/numbers/dashes, e.g. 'daily-time-report'"
|
|
1213
|
+
);
|
|
1214
|
+
}
|
|
1215
|
+
const prompt = String(args.prompt || "").trim();
|
|
1216
|
+
const schedule = String(args.schedule || "").trim();
|
|
1217
|
+
if (!prompt) throw new Error("prompt is required");
|
|
1218
|
+
if (!schedule) throw new Error("schedule is required");
|
|
1219
|
+
const enabled = args.enabled !== false;
|
|
1220
|
+
const runNow = args.run_now !== false;
|
|
1221
|
+
|
|
1222
|
+
const fm: string[] = ["---", `schedule: ${schedule}`, `enabled: ${enabled}`];
|
|
1223
|
+
if (Array.isArray(args.preset) && args.preset.length) {
|
|
1224
|
+
fm.push(`preset: ${JSON.stringify(args.preset)}`);
|
|
1225
|
+
}
|
|
1226
|
+
if (args.history === true) fm.push("history: true");
|
|
1227
|
+
fm.push("---", "", prompt, "");
|
|
1228
|
+
const md = fm.join("\n");
|
|
1229
|
+
|
|
1230
|
+
const dir = path.join(os.homedir(), ".screenpipe", "pipes", pipeName);
|
|
1231
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
1232
|
+
fs.writeFileSync(path.join(dir, "pipe.md"), md, "utf8");
|
|
1233
|
+
const steps: string[] = [`wrote ${path.join(dir, "pipe.md")}`];
|
|
1234
|
+
|
|
1235
|
+
const instRes = await callAPI("/pipes/install", {
|
|
1236
|
+
method: "POST",
|
|
1237
|
+
body: JSON.stringify({ source: dir }),
|
|
1238
|
+
});
|
|
1239
|
+
const inst = await instRes.json();
|
|
1240
|
+
if (inst?.error) throw new Error(`install failed: ${inst.error}`);
|
|
1241
|
+
const pipeId = inst?.name || pipeName;
|
|
1242
|
+
steps.push(`installed as "${pipeId}"`);
|
|
1243
|
+
|
|
1244
|
+
if (enabled) {
|
|
1245
|
+
await callAPI(`/pipes/${encodeURIComponent(pipeId)}/enable`, {
|
|
1246
|
+
method: "POST",
|
|
1247
|
+
body: JSON.stringify({ enabled: true }),
|
|
1248
|
+
});
|
|
1249
|
+
steps.push("enabled");
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
let runNote = "";
|
|
1253
|
+
if (runNow) {
|
|
1254
|
+
try {
|
|
1255
|
+
await callAPI(`/pipes/${encodeURIComponent(pipeId)}/run`, { method: "POST" });
|
|
1256
|
+
steps.push("started a test run");
|
|
1257
|
+
runNote = `\n\nA test run was started — read it with pipe-logs (name="${pipeId}").`;
|
|
1258
|
+
} catch (e) {
|
|
1259
|
+
runNote = `\n\nCreated, but the test run couldn't start: ${(e as Error).message}. Try run-pipe later.`;
|
|
1260
|
+
}
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
return {
|
|
1264
|
+
content: [
|
|
1265
|
+
{
|
|
1266
|
+
type: "text",
|
|
1267
|
+
text:
|
|
1268
|
+
`Created pipe "${pipeId}" — schedule: ${schedule}, ${enabled ? "enabled" : "disabled"}.\n` +
|
|
1269
|
+
steps.map((s) => `- ${s}`).join("\n") +
|
|
1270
|
+
runNote,
|
|
1271
|
+
},
|
|
1272
|
+
],
|
|
1273
|
+
};
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1276
|
+
case "run-pipe": {
|
|
1277
|
+
const pipeName = String(args.name || "").trim();
|
|
1278
|
+
if (!pipeName) throw new Error("name is required");
|
|
1279
|
+
const res = await callAPI(`/pipes/${encodeURIComponent(pipeName)}/run`, {
|
|
1280
|
+
method: "POST",
|
|
1281
|
+
});
|
|
1282
|
+
const data = await res.json().catch(() => ({}));
|
|
1283
|
+
if (data?.error) throw new Error(String(data.error));
|
|
1284
|
+
return {
|
|
1285
|
+
content: [
|
|
1286
|
+
{
|
|
1287
|
+
type: "text",
|
|
1288
|
+
text: `Started a run of "${pipeName}". Read pipe-logs (name="${pipeName}") for the output.`,
|
|
1289
|
+
},
|
|
1290
|
+
],
|
|
1291
|
+
};
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1294
|
+
case "pipe-logs": {
|
|
1295
|
+
const pipeName = String(args.name || "").trim();
|
|
1296
|
+
if (!pipeName) throw new Error("name is required");
|
|
1297
|
+
const res = await callAPI(`/pipes/${encodeURIComponent(pipeName)}/logs`);
|
|
1298
|
+
const text = await res.text();
|
|
1299
|
+
const trimmed = text.length > 6000 ? `…${text.slice(-6000)}` : text;
|
|
1300
|
+
return { content: [{ type: "text", text: trimmed || "(no logs yet)" }] };
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1031
1303
|
case "search-content": {
|
|
1032
1304
|
const includeFrames = args.include_frames === true;
|
|
1033
1305
|
const normalized = normalizeTimeFields(args);
|