screenpipe-mcp 0.18.6 → 0.18.8
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 +1 -1
- package/dist/http-server.js +6 -3
- package/dist/http-server.test.js +71 -0
- package/dist/index.js +3 -1
- package/package.json +1 -1
- package/src/http-server.test.ts +78 -0
- package/src/http-server.ts +6 -4
- package/src/index.ts +3 -1
package/README.md
CHANGED
|
@@ -148,7 +148,7 @@ Get a lightweight compressed activity overview for a time range:
|
|
|
148
148
|
- Audio speaker summary
|
|
149
149
|
|
|
150
150
|
### list-meetings
|
|
151
|
-
List detected meetings with duration, app, and attendees.
|
|
151
|
+
List detected meetings with duration, app, and attendees. Pass `q` to filter by substring (title, attendees, notes).
|
|
152
152
|
|
|
153
153
|
### search-elements
|
|
154
154
|
Search structured UI elements (accessibility tree nodes and OCR text blocks):
|
package/dist/http-server.js
CHANGED
|
@@ -284,11 +284,14 @@ function buildHttpServer(config) {
|
|
|
284
284
|
const server = createMcpServer(fetchAPI);
|
|
285
285
|
const transport = new streamableHttp_js_1.StreamableHTTPServerTransport({
|
|
286
286
|
sessionIdGenerator: () => crypto.randomUUID(),
|
|
287
|
+
onsessioninitialized: (newSessionId) => {
|
|
288
|
+
sessions.set(newSessionId, { server, transport });
|
|
289
|
+
},
|
|
290
|
+
onsessionclosed: (closedSessionId) => {
|
|
291
|
+
sessions.delete(closedSessionId);
|
|
292
|
+
},
|
|
287
293
|
});
|
|
288
294
|
await server.connect(transport);
|
|
289
|
-
if (transport.sessionId) {
|
|
290
|
-
sessions.set(transport.sessionId, { server, transport });
|
|
291
|
-
}
|
|
292
295
|
session = { server, transport };
|
|
293
296
|
}
|
|
294
297
|
await session.transport.handleRequest(req, res);
|
package/dist/http-server.test.js
CHANGED
|
@@ -147,3 +147,74 @@ const http_server_1 = require("./http-server");
|
|
|
147
147
|
(0, vitest_1.expect)((0, http_server_1.isAuthorized)(lan("Bearer SECRET"), "secret")).toBe(false);
|
|
148
148
|
});
|
|
149
149
|
});
|
|
150
|
+
(0, vitest_1.describe)("buildHttpServer", () => {
|
|
151
|
+
(0, vitest_1.it)("persists initialized sessions so tools/list works on the next request", async () => {
|
|
152
|
+
const server = (0, http_server_1.buildHttpServer)({
|
|
153
|
+
mcpPort: 0,
|
|
154
|
+
screenpipePort: 3030,
|
|
155
|
+
host: "127.0.0.1",
|
|
156
|
+
apiKey: "secret",
|
|
157
|
+
});
|
|
158
|
+
await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));
|
|
159
|
+
const address = server.address();
|
|
160
|
+
if (!address || typeof address === "string") {
|
|
161
|
+
throw new Error("expected server.address() to return a bound port");
|
|
162
|
+
}
|
|
163
|
+
const baseUrl = `http://127.0.0.1:${address.port}`;
|
|
164
|
+
try {
|
|
165
|
+
const initResponse = await fetch(`${baseUrl}/mcp`, {
|
|
166
|
+
method: "POST",
|
|
167
|
+
headers: {
|
|
168
|
+
authorization: "Bearer secret",
|
|
169
|
+
"content-type": "application/json",
|
|
170
|
+
accept: "application/json, text/event-stream",
|
|
171
|
+
},
|
|
172
|
+
body: JSON.stringify({
|
|
173
|
+
jsonrpc: "2.0",
|
|
174
|
+
id: 1,
|
|
175
|
+
method: "initialize",
|
|
176
|
+
params: {
|
|
177
|
+
protocolVersion: "2024-11-05",
|
|
178
|
+
capabilities: {},
|
|
179
|
+
clientInfo: { name: "vitest", version: "1.0.0" },
|
|
180
|
+
},
|
|
181
|
+
}),
|
|
182
|
+
});
|
|
183
|
+
(0, vitest_1.expect)(initResponse.status).toBe(200);
|
|
184
|
+
const sessionId = initResponse.headers.get("mcp-session-id");
|
|
185
|
+
(0, vitest_1.expect)(sessionId).toBeTruthy();
|
|
186
|
+
await initResponse.text();
|
|
187
|
+
const healthResponse = await fetch(`${baseUrl}/health`);
|
|
188
|
+
(0, vitest_1.expect)(healthResponse.status).toBe(200);
|
|
189
|
+
(0, vitest_1.expect)(await healthResponse.json()).toEqual({ status: "ok", sessions: 1 });
|
|
190
|
+
const toolsResponse = await fetch(`${baseUrl}/mcp`, {
|
|
191
|
+
method: "POST",
|
|
192
|
+
headers: {
|
|
193
|
+
authorization: "Bearer secret",
|
|
194
|
+
"content-type": "application/json",
|
|
195
|
+
accept: "application/json, text/event-stream",
|
|
196
|
+
"mcp-session-id": sessionId,
|
|
197
|
+
"mcp-protocol-version": "2024-11-05",
|
|
198
|
+
},
|
|
199
|
+
body: JSON.stringify({
|
|
200
|
+
jsonrpc: "2.0",
|
|
201
|
+
id: 2,
|
|
202
|
+
method: "tools/list",
|
|
203
|
+
params: {},
|
|
204
|
+
}),
|
|
205
|
+
});
|
|
206
|
+
(0, vitest_1.expect)(toolsResponse.status).toBe(200);
|
|
207
|
+
(0, vitest_1.expect)(await toolsResponse.text()).toContain('"name":"search_content"');
|
|
208
|
+
}
|
|
209
|
+
finally {
|
|
210
|
+
await new Promise((resolve, reject) => {
|
|
211
|
+
server.close((err) => {
|
|
212
|
+
if (err)
|
|
213
|
+
reject(err);
|
|
214
|
+
else
|
|
215
|
+
resolve();
|
|
216
|
+
});
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
});
|
package/dist/index.js
CHANGED
|
@@ -326,13 +326,15 @@ const TOOLS = [
|
|
|
326
326
|
{
|
|
327
327
|
name: "list-meetings",
|
|
328
328
|
description: "List detected meetings (Zoom, Teams, Meet, etc.) with duration, app, and attendees. " +
|
|
329
|
-
"Only available when screenpipe runs in smart transcription mode."
|
|
329
|
+
"Only available when screenpipe runs in smart transcription mode. " +
|
|
330
|
+
"Pass `q` to filter by substring match against title, attendees, and notes (e.g. an email or name).",
|
|
330
331
|
annotations: { title: "List Meetings", readOnlyHint: true, openWorldHint: false, idempotentHint: true },
|
|
331
332
|
inputSchema: {
|
|
332
333
|
type: "object",
|
|
333
334
|
properties: {
|
|
334
335
|
start_time: { type: "string", description: "ISO 8601 UTC or relative (e.g. '1d ago')" },
|
|
335
336
|
end_time: { type: "string", description: "ISO 8601 UTC or relative" },
|
|
337
|
+
q: { type: "string", description: "Case-insensitive substring filter on title, attendees, and note" },
|
|
336
338
|
limit: { type: "integer", description: "Max results (default 20)", default: 20 },
|
|
337
339
|
offset: { type: "integer", description: "Pagination offset", default: 0 },
|
|
338
340
|
},
|
package/package.json
CHANGED
package/src/http-server.test.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import { describe, it, expect } from "vitest";
|
|
6
6
|
import {
|
|
7
|
+
buildHttpServer,
|
|
7
8
|
CliError,
|
|
8
9
|
isAuthorized,
|
|
9
10
|
isLoopbackRequest,
|
|
@@ -178,3 +179,80 @@ describe("isAuthorized", () => {
|
|
|
178
179
|
expect(isAuthorized(lan("Bearer SECRET"), "secret")).toBe(false);
|
|
179
180
|
});
|
|
180
181
|
});
|
|
182
|
+
|
|
183
|
+
describe("buildHttpServer", () => {
|
|
184
|
+
it("persists initialized sessions so tools/list works on the next request", async () => {
|
|
185
|
+
const server = buildHttpServer({
|
|
186
|
+
mcpPort: 0,
|
|
187
|
+
screenpipePort: 3030,
|
|
188
|
+
host: "127.0.0.1",
|
|
189
|
+
apiKey: "secret",
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
await new Promise<void>((resolve) => server.listen(0, "127.0.0.1", resolve));
|
|
193
|
+
|
|
194
|
+
const address = server.address();
|
|
195
|
+
if (!address || typeof address === "string") {
|
|
196
|
+
throw new Error("expected server.address() to return a bound port");
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const baseUrl = `http://127.0.0.1:${address.port}`;
|
|
200
|
+
|
|
201
|
+
try {
|
|
202
|
+
const initResponse = await fetch(`${baseUrl}/mcp`, {
|
|
203
|
+
method: "POST",
|
|
204
|
+
headers: {
|
|
205
|
+
authorization: "Bearer secret",
|
|
206
|
+
"content-type": "application/json",
|
|
207
|
+
accept: "application/json, text/event-stream",
|
|
208
|
+
},
|
|
209
|
+
body: JSON.stringify({
|
|
210
|
+
jsonrpc: "2.0",
|
|
211
|
+
id: 1,
|
|
212
|
+
method: "initialize",
|
|
213
|
+
params: {
|
|
214
|
+
protocolVersion: "2024-11-05",
|
|
215
|
+
capabilities: {},
|
|
216
|
+
clientInfo: { name: "vitest", version: "1.0.0" },
|
|
217
|
+
},
|
|
218
|
+
}),
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
expect(initResponse.status).toBe(200);
|
|
222
|
+
const sessionId = initResponse.headers.get("mcp-session-id");
|
|
223
|
+
expect(sessionId).toBeTruthy();
|
|
224
|
+
await initResponse.text();
|
|
225
|
+
|
|
226
|
+
const healthResponse = await fetch(`${baseUrl}/health`);
|
|
227
|
+
expect(healthResponse.status).toBe(200);
|
|
228
|
+
expect(await healthResponse.json()).toEqual({ status: "ok", sessions: 1 });
|
|
229
|
+
|
|
230
|
+
const toolsResponse = await fetch(`${baseUrl}/mcp`, {
|
|
231
|
+
method: "POST",
|
|
232
|
+
headers: {
|
|
233
|
+
authorization: "Bearer secret",
|
|
234
|
+
"content-type": "application/json",
|
|
235
|
+
accept: "application/json, text/event-stream",
|
|
236
|
+
"mcp-session-id": sessionId!,
|
|
237
|
+
"mcp-protocol-version": "2024-11-05",
|
|
238
|
+
},
|
|
239
|
+
body: JSON.stringify({
|
|
240
|
+
jsonrpc: "2.0",
|
|
241
|
+
id: 2,
|
|
242
|
+
method: "tools/list",
|
|
243
|
+
params: {},
|
|
244
|
+
}),
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
expect(toolsResponse.status).toBe(200);
|
|
248
|
+
expect(await toolsResponse.text()).toContain('"name":"search_content"');
|
|
249
|
+
} finally {
|
|
250
|
+
await new Promise<void>((resolve, reject) => {
|
|
251
|
+
server.close((err) => {
|
|
252
|
+
if (err) reject(err);
|
|
253
|
+
else resolve();
|
|
254
|
+
});
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
});
|
package/src/http-server.ts
CHANGED
|
@@ -337,13 +337,15 @@ export function buildHttpServer(config: CliConfig) {
|
|
|
337
337
|
const server = createMcpServer(fetchAPI);
|
|
338
338
|
const transport = new StreamableHTTPServerTransport({
|
|
339
339
|
sessionIdGenerator: () => crypto.randomUUID(),
|
|
340
|
+
onsessioninitialized: (newSessionId) => {
|
|
341
|
+
sessions.set(newSessionId, { server, transport });
|
|
342
|
+
},
|
|
343
|
+
onsessionclosed: (closedSessionId) => {
|
|
344
|
+
sessions.delete(closedSessionId);
|
|
345
|
+
},
|
|
340
346
|
});
|
|
341
347
|
|
|
342
348
|
await server.connect(transport);
|
|
343
|
-
|
|
344
|
-
if (transport.sessionId) {
|
|
345
|
-
sessions.set(transport.sessionId, { server, transport });
|
|
346
|
-
}
|
|
347
349
|
session = { server, transport };
|
|
348
350
|
}
|
|
349
351
|
|
package/src/index.ts
CHANGED
|
@@ -314,13 +314,15 @@ const TOOLS: Tool[] = [
|
|
|
314
314
|
name: "list-meetings",
|
|
315
315
|
description:
|
|
316
316
|
"List detected meetings (Zoom, Teams, Meet, etc.) with duration, app, and attendees. " +
|
|
317
|
-
"Only available when screenpipe runs in smart transcription mode."
|
|
317
|
+
"Only available when screenpipe runs in smart transcription mode. " +
|
|
318
|
+
"Pass `q` to filter by substring match against title, attendees, and notes (e.g. an email or name).",
|
|
318
319
|
annotations: { title: "List Meetings", readOnlyHint: true, openWorldHint: false, idempotentHint: true },
|
|
319
320
|
inputSchema: {
|
|
320
321
|
type: "object",
|
|
321
322
|
properties: {
|
|
322
323
|
start_time: { type: "string", description: "ISO 8601 UTC or relative (e.g. '1d ago')" },
|
|
323
324
|
end_time: { type: "string", description: "ISO 8601 UTC or relative" },
|
|
325
|
+
q: { type: "string", description: "Case-insensitive substring filter on title, attendees, and note" },
|
|
324
326
|
limit: { type: "integer", description: "Max results (default 20)", default: 20 },
|
|
325
327
|
offset: { type: "integer", description: "Pagination offset", default: 0 },
|
|
326
328
|
},
|