screenpipe-mcp 0.2.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 +103 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +717 -0
- package/manifest.json +58 -0
- package/package.json +38 -0
- package/src/index.ts +799 -0
- package/tsconfig.json +17 -0
package/src/index.ts
ADDED
|
@@ -0,0 +1,799 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
4
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
5
|
+
import {
|
|
6
|
+
CallToolRequestSchema,
|
|
7
|
+
ListToolsRequestSchema,
|
|
8
|
+
Tool,
|
|
9
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
10
|
+
|
|
11
|
+
// Detect OS
|
|
12
|
+
const CURRENT_OS = process.platform;
|
|
13
|
+
const IS_MACOS = CURRENT_OS === "darwin";
|
|
14
|
+
const IS_WINDOWS = CURRENT_OS === "win32";
|
|
15
|
+
const IS_LINUX = CURRENT_OS === "linux";
|
|
16
|
+
|
|
17
|
+
// Parse command line arguments
|
|
18
|
+
const args = process.argv.slice(2);
|
|
19
|
+
let port = 3030;
|
|
20
|
+
for (let i = 0; i < args.length; i++) {
|
|
21
|
+
if (args[i] === "--port" && args[i + 1]) {
|
|
22
|
+
port = parseInt(args[i + 1], 10);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const SCREENPIPE_API = `http://localhost:${port}`;
|
|
27
|
+
|
|
28
|
+
// Initialize server
|
|
29
|
+
const server = new Server(
|
|
30
|
+
{
|
|
31
|
+
name: "screenpipe",
|
|
32
|
+
version: "0.2.0",
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
capabilities: {
|
|
36
|
+
tools: {},
|
|
37
|
+
},
|
|
38
|
+
}
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
// Tool definitions
|
|
42
|
+
const BASE_TOOLS: Tool[] = [
|
|
43
|
+
{
|
|
44
|
+
name: "search-content",
|
|
45
|
+
description:
|
|
46
|
+
"Search through screenpipe recorded content (OCR text, audio transcriptions, UI elements). " +
|
|
47
|
+
"Use this to find specific content that has appeared on your screen or been spoken. " +
|
|
48
|
+
"Results include timestamps, app context, and the content itself.",
|
|
49
|
+
inputSchema: {
|
|
50
|
+
type: "object",
|
|
51
|
+
properties: {
|
|
52
|
+
q: {
|
|
53
|
+
type: "string",
|
|
54
|
+
description: "Search query to find in recorded content",
|
|
55
|
+
},
|
|
56
|
+
content_type: {
|
|
57
|
+
type: "string",
|
|
58
|
+
enum: ["all", "ocr", "audio", "ui"],
|
|
59
|
+
description:
|
|
60
|
+
"Type of content to search: 'ocr' for screen text, 'audio' for spoken words, 'ui' for UI elements, or 'all' for everything",
|
|
61
|
+
default: "all",
|
|
62
|
+
},
|
|
63
|
+
limit: {
|
|
64
|
+
type: "integer",
|
|
65
|
+
description: "Maximum number of results to return",
|
|
66
|
+
default: 10,
|
|
67
|
+
},
|
|
68
|
+
offset: {
|
|
69
|
+
type: "integer",
|
|
70
|
+
description: "Number of results to skip (for pagination)",
|
|
71
|
+
default: 0,
|
|
72
|
+
},
|
|
73
|
+
start_time: {
|
|
74
|
+
type: "string",
|
|
75
|
+
format: "date-time",
|
|
76
|
+
description:
|
|
77
|
+
"Start time in ISO format UTC (e.g. 2024-01-01T00:00:00Z). Filter results from this time onward.",
|
|
78
|
+
},
|
|
79
|
+
end_time: {
|
|
80
|
+
type: "string",
|
|
81
|
+
format: "date-time",
|
|
82
|
+
description:
|
|
83
|
+
"End time in ISO format UTC (e.g. 2024-01-01T00:00:00Z). Filter results up to this time.",
|
|
84
|
+
},
|
|
85
|
+
app_name: {
|
|
86
|
+
type: "string",
|
|
87
|
+
description:
|
|
88
|
+
"Filter by application name (e.g. 'Chrome', 'Safari', 'Terminal')",
|
|
89
|
+
},
|
|
90
|
+
window_name: {
|
|
91
|
+
type: "string",
|
|
92
|
+
description: "Filter by window name or title",
|
|
93
|
+
},
|
|
94
|
+
min_length: {
|
|
95
|
+
type: "integer",
|
|
96
|
+
description: "Minimum content length in characters",
|
|
97
|
+
},
|
|
98
|
+
max_length: {
|
|
99
|
+
type: "integer",
|
|
100
|
+
description: "Maximum content length in characters",
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
name: "pixel-control",
|
|
107
|
+
description:
|
|
108
|
+
"Control mouse and keyboard at the pixel level. This is a cross-platform tool that works on all operating systems. " +
|
|
109
|
+
"Use this to type text, press keys, move the mouse, and click buttons.",
|
|
110
|
+
inputSchema: {
|
|
111
|
+
type: "object",
|
|
112
|
+
properties: {
|
|
113
|
+
action_type: {
|
|
114
|
+
type: "string",
|
|
115
|
+
enum: ["WriteText", "KeyPress", "MouseMove", "MouseClick"],
|
|
116
|
+
description: "Type of input action to perform",
|
|
117
|
+
},
|
|
118
|
+
data: {
|
|
119
|
+
oneOf: [
|
|
120
|
+
{
|
|
121
|
+
type: "string",
|
|
122
|
+
description:
|
|
123
|
+
"Text to type or key to press (for WriteText and KeyPress)",
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
type: "object",
|
|
127
|
+
properties: {
|
|
128
|
+
x: {
|
|
129
|
+
type: "integer",
|
|
130
|
+
description: "X coordinate for mouse movement",
|
|
131
|
+
},
|
|
132
|
+
y: {
|
|
133
|
+
type: "integer",
|
|
134
|
+
description: "Y coordinate for mouse movement",
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
description: "Coordinates for MouseMove",
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
type: "string",
|
|
141
|
+
enum: ["left", "right", "middle"],
|
|
142
|
+
description: "Button to click for MouseClick",
|
|
143
|
+
},
|
|
144
|
+
],
|
|
145
|
+
description: "Action-specific data",
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
required: ["action_type", "data"],
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
];
|
|
152
|
+
|
|
153
|
+
const MACOS_TOOLS: Tool[] = [
|
|
154
|
+
{
|
|
155
|
+
name: "find-elements",
|
|
156
|
+
description:
|
|
157
|
+
"Find UI elements with a specific role in an application. " +
|
|
158
|
+
"This tool is especially useful for identifying interactive elements. " +
|
|
159
|
+
"\n\nMacOS Accessibility Roles Guide:\n" +
|
|
160
|
+
"- Basic roles: 'button', 'textfield', 'checkbox', 'menu', 'list'\n" +
|
|
161
|
+
"- MacOS specific roles: 'AXButton', 'AXTextField', 'AXCheckBox', 'AXMenu', etc.\n" +
|
|
162
|
+
"- Text inputs can be: 'AXTextField', 'AXTextArea', 'AXComboBox', 'AXSearchField'\n" +
|
|
163
|
+
"- Clickable items: 'AXButton', 'AXMenuItem', 'AXMenuBarItem', 'AXImage', 'AXStaticText'\n" +
|
|
164
|
+
"- Web content may use: 'AXWebArea', 'AXLink', 'AXHeading', 'AXRadioButton'\n\n" +
|
|
165
|
+
"Use MacOS Accessibility Inspector app to identify the exact roles in your target application.",
|
|
166
|
+
inputSchema: {
|
|
167
|
+
type: "object",
|
|
168
|
+
properties: {
|
|
169
|
+
app: {
|
|
170
|
+
type: "string",
|
|
171
|
+
description:
|
|
172
|
+
"The name of the application (e.g., 'Chrome', 'Finder', 'Terminal')",
|
|
173
|
+
},
|
|
174
|
+
window: {
|
|
175
|
+
type: "string",
|
|
176
|
+
description: "The window name or title (optional)",
|
|
177
|
+
},
|
|
178
|
+
role: {
|
|
179
|
+
type: "string",
|
|
180
|
+
description:
|
|
181
|
+
"The role to search for (e.g., 'button', 'textfield', 'AXButton', 'AXTextField'). For best results, use MacOS AX prefixed roles.",
|
|
182
|
+
},
|
|
183
|
+
max_results: {
|
|
184
|
+
type: "integer",
|
|
185
|
+
description: "Maximum number of elements to return",
|
|
186
|
+
default: 10,
|
|
187
|
+
},
|
|
188
|
+
max_depth: {
|
|
189
|
+
type: "integer",
|
|
190
|
+
description: "Maximum depth of element tree to search",
|
|
191
|
+
},
|
|
192
|
+
use_background_apps: {
|
|
193
|
+
type: "boolean",
|
|
194
|
+
description: "Whether to look in background apps",
|
|
195
|
+
default: true,
|
|
196
|
+
},
|
|
197
|
+
activate_app: {
|
|
198
|
+
type: "boolean",
|
|
199
|
+
description: "Whether to activate the app before searching",
|
|
200
|
+
default: true,
|
|
201
|
+
},
|
|
202
|
+
},
|
|
203
|
+
required: ["app", "role"],
|
|
204
|
+
},
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
name: "click-element",
|
|
208
|
+
description:
|
|
209
|
+
"Click an element in an application using its id (MacOS only)",
|
|
210
|
+
inputSchema: {
|
|
211
|
+
type: "object",
|
|
212
|
+
properties: {
|
|
213
|
+
app: {
|
|
214
|
+
type: "string",
|
|
215
|
+
description: "The name of the application",
|
|
216
|
+
},
|
|
217
|
+
window: {
|
|
218
|
+
type: "string",
|
|
219
|
+
description: "The window name (optional)",
|
|
220
|
+
},
|
|
221
|
+
id: {
|
|
222
|
+
type: "string",
|
|
223
|
+
description: "The id of the element to click",
|
|
224
|
+
},
|
|
225
|
+
use_background_apps: {
|
|
226
|
+
type: "boolean",
|
|
227
|
+
description: "Whether to look in background apps",
|
|
228
|
+
default: true,
|
|
229
|
+
},
|
|
230
|
+
activate_app: {
|
|
231
|
+
type: "boolean",
|
|
232
|
+
description: "Whether to activate the app before clicking",
|
|
233
|
+
default: true,
|
|
234
|
+
},
|
|
235
|
+
},
|
|
236
|
+
required: ["app", "id"],
|
|
237
|
+
},
|
|
238
|
+
},
|
|
239
|
+
{
|
|
240
|
+
name: "fill-element",
|
|
241
|
+
description: "Type text into an element in an application (MacOS only)",
|
|
242
|
+
inputSchema: {
|
|
243
|
+
type: "object",
|
|
244
|
+
properties: {
|
|
245
|
+
app: {
|
|
246
|
+
type: "string",
|
|
247
|
+
description: "The name of the application",
|
|
248
|
+
},
|
|
249
|
+
window: {
|
|
250
|
+
type: "string",
|
|
251
|
+
description: "The window name (optional)",
|
|
252
|
+
},
|
|
253
|
+
id: {
|
|
254
|
+
type: "string",
|
|
255
|
+
description: "The id of the element to fill",
|
|
256
|
+
},
|
|
257
|
+
text: {
|
|
258
|
+
type: "string",
|
|
259
|
+
description: "The text to type into the element",
|
|
260
|
+
},
|
|
261
|
+
use_background_apps: {
|
|
262
|
+
type: "boolean",
|
|
263
|
+
description: "Whether to look in background apps",
|
|
264
|
+
default: true,
|
|
265
|
+
},
|
|
266
|
+
activate_app: {
|
|
267
|
+
type: "boolean",
|
|
268
|
+
description: "Whether to activate the app before typing",
|
|
269
|
+
default: true,
|
|
270
|
+
},
|
|
271
|
+
},
|
|
272
|
+
required: ["app", "id", "text"],
|
|
273
|
+
},
|
|
274
|
+
},
|
|
275
|
+
{
|
|
276
|
+
name: "scroll-element",
|
|
277
|
+
description: "Scroll an element in a specific direction (MacOS only)",
|
|
278
|
+
inputSchema: {
|
|
279
|
+
type: "object",
|
|
280
|
+
properties: {
|
|
281
|
+
app: {
|
|
282
|
+
type: "string",
|
|
283
|
+
description: "The name of the application",
|
|
284
|
+
},
|
|
285
|
+
window: {
|
|
286
|
+
type: "string",
|
|
287
|
+
description: "The window name (optional)",
|
|
288
|
+
},
|
|
289
|
+
id: {
|
|
290
|
+
type: "string",
|
|
291
|
+
description: "The id of the element to scroll",
|
|
292
|
+
},
|
|
293
|
+
direction: {
|
|
294
|
+
type: "string",
|
|
295
|
+
enum: ["up", "down", "left", "right"],
|
|
296
|
+
description: "The direction to scroll",
|
|
297
|
+
},
|
|
298
|
+
amount: {
|
|
299
|
+
type: "integer",
|
|
300
|
+
description: "The amount to scroll in pixels",
|
|
301
|
+
},
|
|
302
|
+
use_background_apps: {
|
|
303
|
+
type: "boolean",
|
|
304
|
+
description: "Whether to look in background apps",
|
|
305
|
+
default: true,
|
|
306
|
+
},
|
|
307
|
+
activate_app: {
|
|
308
|
+
type: "boolean",
|
|
309
|
+
description: "Whether to activate the app before scrolling",
|
|
310
|
+
default: true,
|
|
311
|
+
},
|
|
312
|
+
},
|
|
313
|
+
required: ["app", "id", "direction", "amount"],
|
|
314
|
+
},
|
|
315
|
+
},
|
|
316
|
+
{
|
|
317
|
+
name: "open-application",
|
|
318
|
+
description: "Open an application by name",
|
|
319
|
+
inputSchema: {
|
|
320
|
+
type: "object",
|
|
321
|
+
properties: {
|
|
322
|
+
app_name: {
|
|
323
|
+
type: "string",
|
|
324
|
+
description: "The name of the application to open",
|
|
325
|
+
},
|
|
326
|
+
},
|
|
327
|
+
required: ["app_name"],
|
|
328
|
+
},
|
|
329
|
+
},
|
|
330
|
+
{
|
|
331
|
+
name: "open-url",
|
|
332
|
+
description: "Open a URL in a browser",
|
|
333
|
+
inputSchema: {
|
|
334
|
+
type: "object",
|
|
335
|
+
properties: {
|
|
336
|
+
url: {
|
|
337
|
+
type: "string",
|
|
338
|
+
description: "The URL to open",
|
|
339
|
+
},
|
|
340
|
+
browser: {
|
|
341
|
+
type: "string",
|
|
342
|
+
description: "The browser to use (optional)",
|
|
343
|
+
},
|
|
344
|
+
},
|
|
345
|
+
required: ["url"],
|
|
346
|
+
},
|
|
347
|
+
},
|
|
348
|
+
];
|
|
349
|
+
|
|
350
|
+
// List tools handler
|
|
351
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
352
|
+
const tools = [...BASE_TOOLS];
|
|
353
|
+
if (IS_MACOS) {
|
|
354
|
+
tools.push(...MACOS_TOOLS);
|
|
355
|
+
}
|
|
356
|
+
return { tools };
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
// Helper function to make HTTP requests
|
|
360
|
+
async function fetchAPI(
|
|
361
|
+
endpoint: string,
|
|
362
|
+
options: RequestInit = {}
|
|
363
|
+
): Promise<Response> {
|
|
364
|
+
const url = `${SCREENPIPE_API}${endpoint}`;
|
|
365
|
+
return fetch(url, {
|
|
366
|
+
...options,
|
|
367
|
+
headers: {
|
|
368
|
+
"Content-Type": "application/json",
|
|
369
|
+
...options.headers,
|
|
370
|
+
},
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// Call tool handler
|
|
375
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
376
|
+
const { name, arguments: args } = request.params;
|
|
377
|
+
|
|
378
|
+
if (!args) {
|
|
379
|
+
throw new Error("Missing arguments");
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// Check if the tool is MacOS-only and we're not on MacOS
|
|
383
|
+
const macosOnlyTools = [
|
|
384
|
+
"click-element",
|
|
385
|
+
"fill-element",
|
|
386
|
+
"find-elements",
|
|
387
|
+
"scroll-element",
|
|
388
|
+
"open-application",
|
|
389
|
+
"open-url",
|
|
390
|
+
];
|
|
391
|
+
|
|
392
|
+
if (macosOnlyTools.includes(name) && !IS_MACOS) {
|
|
393
|
+
return {
|
|
394
|
+
content: [
|
|
395
|
+
{
|
|
396
|
+
type: "text",
|
|
397
|
+
text: `The '${name}' tool is only available on MacOS. Current platform: ${CURRENT_OS}`,
|
|
398
|
+
},
|
|
399
|
+
],
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
try {
|
|
404
|
+
switch (name) {
|
|
405
|
+
case "search-content": {
|
|
406
|
+
const params = new URLSearchParams();
|
|
407
|
+
for (const [key, value] of Object.entries(args)) {
|
|
408
|
+
if (value !== null && value !== undefined) {
|
|
409
|
+
params.append(key, String(value));
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
const response = await fetchAPI(`/search?${params.toString()}`);
|
|
414
|
+
if (!response.ok) {
|
|
415
|
+
throw new Error(`HTTP error: ${response.status}`);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
const data = await response.json();
|
|
419
|
+
const results = data.data || [];
|
|
420
|
+
|
|
421
|
+
if (results.length === 0) {
|
|
422
|
+
return {
|
|
423
|
+
content: [{ type: "text", text: "No results found" }],
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
const formattedResults = results
|
|
428
|
+
.map((result: any) => {
|
|
429
|
+
const content = result.content;
|
|
430
|
+
if (!content) return null;
|
|
431
|
+
|
|
432
|
+
if (result.type === "OCR") {
|
|
433
|
+
return (
|
|
434
|
+
`OCR Text: ${content.text || "N/A"}\n` +
|
|
435
|
+
`App: ${content.app_name || "N/A"}\n` +
|
|
436
|
+
`Window: ${content.window_name || "N/A"}\n` +
|
|
437
|
+
`Time: ${content.timestamp || "N/A"}\n` +
|
|
438
|
+
"---"
|
|
439
|
+
);
|
|
440
|
+
} else if (result.type === "Audio") {
|
|
441
|
+
return (
|
|
442
|
+
`Audio Transcription: ${content.transcription || "N/A"}\n` +
|
|
443
|
+
`Device: ${content.device_name || "N/A"}\n` +
|
|
444
|
+
`Time: ${content.timestamp || "N/A"}\n` +
|
|
445
|
+
"---"
|
|
446
|
+
);
|
|
447
|
+
} else if (result.type === "UI") {
|
|
448
|
+
return (
|
|
449
|
+
`UI Text: ${content.text || "N/A"}\n` +
|
|
450
|
+
`App: ${content.app_name || "N/A"}\n` +
|
|
451
|
+
`Window: ${content.window_name || "N/A"}\n` +
|
|
452
|
+
`Time: ${content.timestamp || "N/A"}\n` +
|
|
453
|
+
"---"
|
|
454
|
+
);
|
|
455
|
+
}
|
|
456
|
+
return null;
|
|
457
|
+
})
|
|
458
|
+
.filter(Boolean);
|
|
459
|
+
|
|
460
|
+
return {
|
|
461
|
+
content: [
|
|
462
|
+
{
|
|
463
|
+
type: "text",
|
|
464
|
+
text: "Search Results:\n\n" + formattedResults.join("\n"),
|
|
465
|
+
},
|
|
466
|
+
],
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
case "pixel-control": {
|
|
471
|
+
const action = {
|
|
472
|
+
type: args.action_type,
|
|
473
|
+
data: args.data,
|
|
474
|
+
};
|
|
475
|
+
|
|
476
|
+
const response = await fetchAPI("/experimental/operator/pixel", {
|
|
477
|
+
method: "POST",
|
|
478
|
+
body: JSON.stringify({ action }),
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
if (!response.ok) {
|
|
482
|
+
throw new Error(`HTTP error: ${response.status}`);
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
const data = await response.json();
|
|
486
|
+
if (!data.success) {
|
|
487
|
+
return {
|
|
488
|
+
content: [
|
|
489
|
+
{
|
|
490
|
+
type: "text",
|
|
491
|
+
text: `Failed to perform input control: ${data.error || "unknown error"}`,
|
|
492
|
+
},
|
|
493
|
+
],
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
let resultText = "Successfully performed input control action";
|
|
498
|
+
if (args.action_type === "WriteText") {
|
|
499
|
+
resultText = `Successfully typed text: '${args.data}'`;
|
|
500
|
+
} else if (args.action_type === "KeyPress") {
|
|
501
|
+
resultText = `Successfully pressed key: '${args.data}'`;
|
|
502
|
+
} else if (args.action_type === "MouseMove") {
|
|
503
|
+
const coords = args.data as { x: number; y: number };
|
|
504
|
+
resultText = `Successfully moved mouse to coordinates: x=${coords.x}, y=${coords.y}`;
|
|
505
|
+
} else if (args.action_type === "MouseClick") {
|
|
506
|
+
resultText = `Successfully clicked ${args.data} mouse button`;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
return {
|
|
510
|
+
content: [{ type: "text", text: resultText }],
|
|
511
|
+
};
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
case "click-element": {
|
|
515
|
+
const selector = {
|
|
516
|
+
app_name: args.app,
|
|
517
|
+
window_name: args.window,
|
|
518
|
+
locator: `#${args.id}`,
|
|
519
|
+
use_background_apps: args.use_background_apps ?? true,
|
|
520
|
+
activate_app: args.activate_app ?? true,
|
|
521
|
+
};
|
|
522
|
+
|
|
523
|
+
const response = await fetchAPI("/experimental/operator/click", {
|
|
524
|
+
method: "POST",
|
|
525
|
+
body: JSON.stringify({ selector }),
|
|
526
|
+
});
|
|
527
|
+
|
|
528
|
+
if (!response.ok) {
|
|
529
|
+
throw new Error(`HTTP error: ${response.status}`);
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
const data = await response.json();
|
|
533
|
+
if (!data.success) {
|
|
534
|
+
return {
|
|
535
|
+
content: [
|
|
536
|
+
{
|
|
537
|
+
type: "text",
|
|
538
|
+
text: `Failed to click element: ${data.error || "unknown error"}`,
|
|
539
|
+
},
|
|
540
|
+
],
|
|
541
|
+
};
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
const result = data.result || {};
|
|
545
|
+
const method = result.method || "unknown";
|
|
546
|
+
const details = result.details || "click operation completed";
|
|
547
|
+
|
|
548
|
+
return {
|
|
549
|
+
content: [
|
|
550
|
+
{
|
|
551
|
+
type: "text",
|
|
552
|
+
text: `Successfully clicked element using ${method}. ${details}`,
|
|
553
|
+
},
|
|
554
|
+
],
|
|
555
|
+
};
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
case "fill-element": {
|
|
559
|
+
const selector = {
|
|
560
|
+
app_name: args.app,
|
|
561
|
+
window_name: args.window,
|
|
562
|
+
locator: `#${args.id}`,
|
|
563
|
+
use_background_apps: args.use_background_apps ?? true,
|
|
564
|
+
activate_app: args.activate_app ?? true,
|
|
565
|
+
};
|
|
566
|
+
|
|
567
|
+
const response = await fetchAPI("/experimental/operator/type", {
|
|
568
|
+
method: "POST",
|
|
569
|
+
body: JSON.stringify({ selector, text: args.text || "" }),
|
|
570
|
+
});
|
|
571
|
+
|
|
572
|
+
if (!response.ok) {
|
|
573
|
+
throw new Error(`HTTP error: ${response.status}`);
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
const data = await response.json();
|
|
577
|
+
if (!data.success) {
|
|
578
|
+
return {
|
|
579
|
+
content: [
|
|
580
|
+
{
|
|
581
|
+
type: "text",
|
|
582
|
+
text: `Failed to fill element: ${data.error || "unknown error"}`,
|
|
583
|
+
},
|
|
584
|
+
],
|
|
585
|
+
};
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
return {
|
|
589
|
+
content: [
|
|
590
|
+
{ type: "text", text: "Successfully filled element with text" },
|
|
591
|
+
],
|
|
592
|
+
};
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
case "find-elements": {
|
|
596
|
+
const selector = {
|
|
597
|
+
app_name: args.app,
|
|
598
|
+
window_name: args.window,
|
|
599
|
+
locator: args.role || "",
|
|
600
|
+
use_background_apps: args.use_background_apps ?? true,
|
|
601
|
+
activate_app: args.activate_app ?? true,
|
|
602
|
+
};
|
|
603
|
+
|
|
604
|
+
const response = await fetchAPI("/experimental/operator", {
|
|
605
|
+
method: "POST",
|
|
606
|
+
body: JSON.stringify({
|
|
607
|
+
selector,
|
|
608
|
+
max_results: args.max_results || 10,
|
|
609
|
+
max_depth: args.max_depth,
|
|
610
|
+
}),
|
|
611
|
+
});
|
|
612
|
+
|
|
613
|
+
if (!response.ok) {
|
|
614
|
+
throw new Error(`HTTP error: ${response.status}`);
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
const data = await response.json();
|
|
618
|
+
if (!data.success) {
|
|
619
|
+
return {
|
|
620
|
+
content: [
|
|
621
|
+
{
|
|
622
|
+
type: "text",
|
|
623
|
+
text: `Failed to find elements: ${data.error || "unknown error"}`,
|
|
624
|
+
},
|
|
625
|
+
],
|
|
626
|
+
};
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
const elements = data.data || [];
|
|
630
|
+
if (elements.length === 0) {
|
|
631
|
+
return {
|
|
632
|
+
content: [
|
|
633
|
+
{
|
|
634
|
+
type: "text",
|
|
635
|
+
text: `No elements found matching role '${args.role}' in app '${args.app}'`,
|
|
636
|
+
},
|
|
637
|
+
],
|
|
638
|
+
};
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
let resultText = `Found ${elements.length} elements matching role '${args.role}' in app '${args.app}':\n\n`;
|
|
642
|
+
elements.forEach((element: any, i: number) => {
|
|
643
|
+
resultText +=
|
|
644
|
+
`Element ${i + 1}:\n` +
|
|
645
|
+
`ID: ${element.id || "N/A"}\n` +
|
|
646
|
+
`Role: ${element.role || "N/A"}\n` +
|
|
647
|
+
`Text: ${element.text || "N/A"}\n` +
|
|
648
|
+
`Description: ${element.description || "N/A"}\n` +
|
|
649
|
+
"---\n";
|
|
650
|
+
});
|
|
651
|
+
|
|
652
|
+
return {
|
|
653
|
+
content: [{ type: "text", text: resultText }],
|
|
654
|
+
};
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
case "scroll-element": {
|
|
658
|
+
const selector = {
|
|
659
|
+
app_name: args.app,
|
|
660
|
+
window_name: args.window,
|
|
661
|
+
locator: `#${args.id}`,
|
|
662
|
+
use_background_apps: args.use_background_apps ?? true,
|
|
663
|
+
activate_app: args.activate_app ?? true,
|
|
664
|
+
};
|
|
665
|
+
|
|
666
|
+
const response = await fetchAPI("/experimental/operator/scroll", {
|
|
667
|
+
method: "POST",
|
|
668
|
+
body: JSON.stringify({
|
|
669
|
+
selector,
|
|
670
|
+
direction: args.direction,
|
|
671
|
+
amount: args.amount,
|
|
672
|
+
}),
|
|
673
|
+
});
|
|
674
|
+
|
|
675
|
+
if (!response.ok) {
|
|
676
|
+
throw new Error(`HTTP error: ${response.status}`);
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
const data = await response.json();
|
|
680
|
+
if (!data.success) {
|
|
681
|
+
return {
|
|
682
|
+
content: [
|
|
683
|
+
{
|
|
684
|
+
type: "text",
|
|
685
|
+
text: `Failed to scroll element: ${data.error || "unknown error"}`,
|
|
686
|
+
},
|
|
687
|
+
],
|
|
688
|
+
};
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
return {
|
|
692
|
+
content: [
|
|
693
|
+
{
|
|
694
|
+
type: "text",
|
|
695
|
+
text: `Successfully scrolled element ${args.direction} by ${args.amount} pixels`,
|
|
696
|
+
},
|
|
697
|
+
],
|
|
698
|
+
};
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
case "open-application": {
|
|
702
|
+
const response = await fetchAPI(
|
|
703
|
+
"/experimental/operator/open-application",
|
|
704
|
+
{
|
|
705
|
+
method: "POST",
|
|
706
|
+
body: JSON.stringify({ app_name: args.app_name || "" }),
|
|
707
|
+
}
|
|
708
|
+
);
|
|
709
|
+
|
|
710
|
+
if (!response.ok) {
|
|
711
|
+
throw new Error(`HTTP error: ${response.status}`);
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
const data = await response.json();
|
|
715
|
+
if (!data.success) {
|
|
716
|
+
return {
|
|
717
|
+
content: [
|
|
718
|
+
{
|
|
719
|
+
type: "text",
|
|
720
|
+
text: `Failed to open application: ${data.error || "unknown error"}`,
|
|
721
|
+
},
|
|
722
|
+
],
|
|
723
|
+
};
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
return {
|
|
727
|
+
content: [
|
|
728
|
+
{
|
|
729
|
+
type: "text",
|
|
730
|
+
text: `Successfully opened application '${args.app_name}'`,
|
|
731
|
+
},
|
|
732
|
+
],
|
|
733
|
+
};
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
case "open-url": {
|
|
737
|
+
const response = await fetchAPI("/experimental/operator/open-url", {
|
|
738
|
+
method: "POST",
|
|
739
|
+
body: JSON.stringify({
|
|
740
|
+
url: args.url || "",
|
|
741
|
+
browser: args.browser,
|
|
742
|
+
}),
|
|
743
|
+
});
|
|
744
|
+
|
|
745
|
+
if (!response.ok) {
|
|
746
|
+
throw new Error(`HTTP error: ${response.status}`);
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
const data = await response.json();
|
|
750
|
+
if (!data.success) {
|
|
751
|
+
return {
|
|
752
|
+
content: [
|
|
753
|
+
{
|
|
754
|
+
type: "text",
|
|
755
|
+
text: `Failed to open URL: ${data.error || "unknown error"}`,
|
|
756
|
+
},
|
|
757
|
+
],
|
|
758
|
+
};
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
const browserInfo = args.browser ? ` using ${args.browser}` : "";
|
|
762
|
+
return {
|
|
763
|
+
content: [
|
|
764
|
+
{
|
|
765
|
+
type: "text",
|
|
766
|
+
text: `Successfully opened URL '${args.url}'${browserInfo}`,
|
|
767
|
+
},
|
|
768
|
+
],
|
|
769
|
+
};
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
default:
|
|
773
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
774
|
+
}
|
|
775
|
+
} catch (error) {
|
|
776
|
+
const errorMessage =
|
|
777
|
+
error instanceof Error ? error.message : "Unknown error";
|
|
778
|
+
return {
|
|
779
|
+
content: [
|
|
780
|
+
{
|
|
781
|
+
type: "text",
|
|
782
|
+
text: `Error executing ${name}: ${errorMessage}`,
|
|
783
|
+
},
|
|
784
|
+
],
|
|
785
|
+
};
|
|
786
|
+
}
|
|
787
|
+
});
|
|
788
|
+
|
|
789
|
+
// Run the server
|
|
790
|
+
async function main() {
|
|
791
|
+
const transport = new StdioServerTransport();
|
|
792
|
+
await server.connect(transport);
|
|
793
|
+
console.error("Screenpipe MCP server running on stdio");
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
main().catch((error) => {
|
|
797
|
+
console.error("Fatal error:", error);
|
|
798
|
+
process.exit(1);
|
|
799
|
+
});
|