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 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):
@@ -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);
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "screenpipe-mcp",
3
- "version": "0.18.6",
3
+ "version": "0.18.8",
4
4
  "description": "MCP server for screenpipe - search your screen recordings and audio transcriptions",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -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
+ });
@@ -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
  },