screenpipe-mcp 0.14.0 → 0.15.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/dist/http-server.js +76 -77
- package/dist/index.js +50 -0
- package/package.json +1 -1
- package/src/http-server.ts +86 -90
- package/src/index.ts +53 -0
package/dist/http-server.js
CHANGED
|
@@ -86,89 +86,86 @@ async function fetchAPI(endpoint, options = {}) {
|
|
|
86
86
|
},
|
|
87
87
|
});
|
|
88
88
|
}
|
|
89
|
-
//
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
tools: {},
|
|
96
|
-
},
|
|
97
|
-
});
|
|
98
|
-
// List tools handler
|
|
99
|
-
server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => {
|
|
100
|
-
return { tools: TOOLS };
|
|
101
|
-
});
|
|
102
|
-
// Call tool handler
|
|
103
|
-
server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
|
|
104
|
-
const { name, arguments: args } = request.params;
|
|
105
|
-
if (!args) {
|
|
106
|
-
throw new Error("Missing arguments");
|
|
107
|
-
}
|
|
108
|
-
if (name === "search_content") {
|
|
109
|
-
const params = new URLSearchParams();
|
|
110
|
-
for (const [key, value] of Object.entries(args)) {
|
|
111
|
-
if (value !== null && value !== undefined) {
|
|
112
|
-
params.append(key, String(value));
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
const response = await fetchAPI(`/search?${params.toString()}`);
|
|
116
|
-
if (!response.ok) {
|
|
117
|
-
throw new Error(`HTTP error: ${response.status}`);
|
|
118
|
-
}
|
|
119
|
-
const data = await response.json();
|
|
120
|
-
const results = data.data || [];
|
|
121
|
-
const pagination = data.pagination || {};
|
|
122
|
-
if (results.length === 0) {
|
|
123
|
-
return {
|
|
124
|
-
content: [
|
|
125
|
-
{
|
|
126
|
-
type: "text",
|
|
127
|
-
text: "No results found. Try: broader search terms, different content_type, or wider time range.",
|
|
128
|
-
},
|
|
129
|
-
],
|
|
130
|
-
};
|
|
131
|
-
}
|
|
132
|
-
const formattedResults = [];
|
|
133
|
-
for (const result of results) {
|
|
134
|
-
const content = result.content;
|
|
135
|
-
if (!content)
|
|
136
|
-
continue;
|
|
137
|
-
if (result.type === "OCR") {
|
|
138
|
-
formattedResults.push(`[OCR] ${content.app_name || "?"} | ${content.window_name || "?"}\n` +
|
|
139
|
-
`${content.timestamp || ""}\n` +
|
|
140
|
-
`${content.text || ""}`);
|
|
141
|
-
}
|
|
142
|
-
else if (result.type === "Audio") {
|
|
143
|
-
formattedResults.push(`[Audio] ${content.device_name || "?"}\n` +
|
|
144
|
-
`${content.timestamp || ""}\n` +
|
|
145
|
-
`${content.transcription || ""}`);
|
|
146
|
-
}
|
|
147
|
-
else if (result.type === "UI" || result.type === "Accessibility") {
|
|
148
|
-
formattedResults.push(`[Accessibility] ${content.app_name || "?"} | ${content.window_name || "?"}\n` +
|
|
149
|
-
`${content.timestamp || ""}\n` +
|
|
150
|
-
`${content.text || ""}`);
|
|
151
|
-
}
|
|
89
|
+
// Tool handler for search_content
|
|
90
|
+
async function handleSearchContent(args) {
|
|
91
|
+
const params = new URLSearchParams();
|
|
92
|
+
for (const [key, value] of Object.entries(args)) {
|
|
93
|
+
if (value !== null && value !== undefined) {
|
|
94
|
+
params.append(key, String(value));
|
|
152
95
|
}
|
|
153
|
-
|
|
154
|
-
|
|
96
|
+
}
|
|
97
|
+
const response = await fetchAPI(`/search?${params.toString()}`);
|
|
98
|
+
if (!response.ok) {
|
|
99
|
+
throw new Error(`HTTP error: ${response.status}`);
|
|
100
|
+
}
|
|
101
|
+
const data = await response.json();
|
|
102
|
+
const results = data.data || [];
|
|
103
|
+
const pagination = data.pagination || {};
|
|
104
|
+
if (results.length === 0) {
|
|
155
105
|
return {
|
|
156
106
|
content: [
|
|
157
107
|
{
|
|
158
108
|
type: "text",
|
|
159
|
-
text:
|
|
109
|
+
text: "No results found. Try: broader search terms, different content_type, or wider time range.",
|
|
160
110
|
},
|
|
161
111
|
],
|
|
162
112
|
};
|
|
163
113
|
}
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
114
|
+
const formattedResults = [];
|
|
115
|
+
for (const result of results) {
|
|
116
|
+
const content = result.content;
|
|
117
|
+
if (!content)
|
|
118
|
+
continue;
|
|
119
|
+
if (result.type === "OCR") {
|
|
120
|
+
formattedResults.push(`[OCR] ${content.app_name || "?"} | ${content.window_name || "?"}\n` +
|
|
121
|
+
`${content.timestamp || ""}\n` +
|
|
122
|
+
`${content.text || ""}`);
|
|
123
|
+
}
|
|
124
|
+
else if (result.type === "Audio") {
|
|
125
|
+
formattedResults.push(`[Audio] ${content.device_name || "?"}\n` +
|
|
126
|
+
`${content.timestamp || ""}\n` +
|
|
127
|
+
`${content.transcription || ""}`);
|
|
128
|
+
}
|
|
129
|
+
else if (result.type === "UI" || result.type === "Accessibility") {
|
|
130
|
+
formattedResults.push(`[Accessibility] ${content.app_name || "?"} | ${content.window_name || "?"}\n` +
|
|
131
|
+
`${content.timestamp || ""}\n` +
|
|
132
|
+
`${content.text || ""}`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
const header = `Results: ${results.length}/${pagination.total || "?"}` +
|
|
136
|
+
(pagination.total > results.length ? ` (use offset=${(pagination.offset || 0) + results.length} for more)` : "");
|
|
137
|
+
return {
|
|
138
|
+
content: [
|
|
139
|
+
{
|
|
140
|
+
type: "text",
|
|
141
|
+
text: header + "\n\n" + formattedResults.join("\n---\n"),
|
|
142
|
+
},
|
|
143
|
+
],
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
// Create a fresh MCP Server instance with handlers registered.
|
|
147
|
+
// Each HTTP session gets its own Server — the MCP SDK requires a 1:1
|
|
148
|
+
// mapping between Server and transport (reusing a Server across
|
|
149
|
+
// transports throws "Already connected to a transport").
|
|
150
|
+
function createMcpServer() {
|
|
151
|
+
const s = new index_js_1.Server({ name: "screenpipe-http", version: "0.14.0" }, { capabilities: { tools: {} } });
|
|
152
|
+
s.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => ({ tools: TOOLS }));
|
|
153
|
+
s.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
|
|
154
|
+
const { name, arguments: args } = request.params;
|
|
155
|
+
if (!args)
|
|
156
|
+
throw new Error("Missing arguments");
|
|
157
|
+
if (name === "search_content")
|
|
158
|
+
return handleSearchContent(args);
|
|
159
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
160
|
+
});
|
|
161
|
+
return s;
|
|
162
|
+
}
|
|
163
|
+
// Per-session state: each session gets its own Server + transport pair.
|
|
164
|
+
const sessions = new Map();
|
|
168
165
|
const httpServer = (0, http_1.createServer)(async (req, res) => {
|
|
169
166
|
// CORS headers
|
|
170
167
|
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
171
|
-
res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
|
|
168
|
+
res.setHeader("Access-Control-Allow-Methods", "GET, POST, DELETE, OPTIONS");
|
|
172
169
|
res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization, mcp-session-id");
|
|
173
170
|
if (req.method === "OPTIONS") {
|
|
174
171
|
res.writeHead(204);
|
|
@@ -178,23 +175,25 @@ const httpServer = (0, http_1.createServer)(async (req, res) => {
|
|
|
178
175
|
// Health check
|
|
179
176
|
if (req.url === "/health") {
|
|
180
177
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
181
|
-
res.end(JSON.stringify({ status: "ok" }));
|
|
178
|
+
res.end(JSON.stringify({ status: "ok", sessions: sessions.size }));
|
|
182
179
|
return;
|
|
183
180
|
}
|
|
184
181
|
// MCP endpoint
|
|
185
182
|
if (req.url === "/mcp" || req.url?.startsWith("/mcp?")) {
|
|
186
183
|
const sessionId = req.headers["mcp-session-id"];
|
|
187
|
-
let
|
|
188
|
-
if (!
|
|
189
|
-
|
|
184
|
+
let session = sessionId ? sessions.get(sessionId) : undefined;
|
|
185
|
+
if (!session) {
|
|
186
|
+
const server = createMcpServer();
|
|
187
|
+
const transport = new streamableHttp_js_1.StreamableHTTPServerTransport({
|
|
190
188
|
sessionIdGenerator: () => crypto.randomUUID(),
|
|
191
189
|
});
|
|
192
190
|
await server.connect(transport);
|
|
193
191
|
if (transport.sessionId) {
|
|
194
|
-
|
|
192
|
+
sessions.set(transport.sessionId, { server, transport });
|
|
195
193
|
}
|
|
194
|
+
session = { server, transport };
|
|
196
195
|
}
|
|
197
|
-
await transport.handleRequest(req, res);
|
|
196
|
+
await session.transport.handleRequest(req, res);
|
|
198
197
|
return;
|
|
199
198
|
}
|
|
200
199
|
res.writeHead(404, { "Content-Type": "application/json" });
|
package/dist/index.js
CHANGED
|
@@ -53,6 +53,55 @@ for (let i = 0; i < args.length; i++) {
|
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
55
|
const SCREENPIPE_API = `http://localhost:${port}`;
|
|
56
|
+
// Discover API key: env var > npx screenpipe auth token > bundled bun
|
|
57
|
+
function discoverApiKey() {
|
|
58
|
+
const envKey = process.env.SCREENPIPE_LOCAL_API_KEY || process.env.SCREENPIPE_API_KEY;
|
|
59
|
+
if (envKey)
|
|
60
|
+
return envKey;
|
|
61
|
+
const { execSync } = require("child_process");
|
|
62
|
+
const os = require("os");
|
|
63
|
+
const fs = require("fs");
|
|
64
|
+
const path = require("path");
|
|
65
|
+
// Try npx first (works if user has Node installed)
|
|
66
|
+
try {
|
|
67
|
+
const token = execSync("npx screenpipe@latest auth token", {
|
|
68
|
+
timeout: 15000,
|
|
69
|
+
encoding: "utf-8",
|
|
70
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
71
|
+
}).trim();
|
|
72
|
+
if (token)
|
|
73
|
+
return token;
|
|
74
|
+
}
|
|
75
|
+
catch { }
|
|
76
|
+
// Try bundled bun inside the screenpipe app
|
|
77
|
+
const bundledBunPaths = [];
|
|
78
|
+
const platform = os.platform();
|
|
79
|
+
if (platform === "darwin") {
|
|
80
|
+
bundledBunPaths.push("/Applications/screenpipe.app/Contents/MacOS/bun");
|
|
81
|
+
}
|
|
82
|
+
else if (platform === "win32") {
|
|
83
|
+
bundledBunPaths.push(path.join(process.env.LOCALAPPDATA || "", "screenpipe", "bun.exe"), path.join(process.env.PROGRAMFILES || "", "screenpipe", "bun.exe"));
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
bundledBunPaths.push("/usr/lib/screenpipe/bun", "/opt/screenpipe/bun");
|
|
87
|
+
}
|
|
88
|
+
for (const bunPath of bundledBunPaths) {
|
|
89
|
+
try {
|
|
90
|
+
if (fs.existsSync(bunPath)) {
|
|
91
|
+
const token = execSync(`"${bunPath}" x screenpipe@latest auth token`, {
|
|
92
|
+
timeout: 15000,
|
|
93
|
+
encoding: "utf-8",
|
|
94
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
95
|
+
}).trim();
|
|
96
|
+
if (token)
|
|
97
|
+
return token;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
catch { }
|
|
101
|
+
}
|
|
102
|
+
return "";
|
|
103
|
+
}
|
|
104
|
+
const API_KEY = discoverApiKey();
|
|
56
105
|
// Read version from package.json (single source of truth)
|
|
57
106
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
58
107
|
const PKG_VERSION = require("../package.json").version;
|
|
@@ -523,6 +572,7 @@ async function fetchAPI(endpoint, options = {}) {
|
|
|
523
572
|
...options,
|
|
524
573
|
headers: {
|
|
525
574
|
"Content-Type": "application/json",
|
|
575
|
+
...(API_KEY ? { Authorization: `Bearer ${API_KEY}` } : {}),
|
|
526
576
|
...options.headers,
|
|
527
577
|
},
|
|
528
578
|
});
|
package/package.json
CHANGED
package/src/http-server.ts
CHANGED
|
@@ -96,109 +96,103 @@ async function fetchAPI(endpoint: string, options: RequestInit = {}): Promise<Re
|
|
|
96
96
|
});
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
-
//
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
capabilities: {
|
|
107
|
-
tools: {},
|
|
108
|
-
},
|
|
99
|
+
// Tool handler for search_content
|
|
100
|
+
async function handleSearchContent(args: Record<string, unknown>) {
|
|
101
|
+
const params = new URLSearchParams();
|
|
102
|
+
for (const [key, value] of Object.entries(args)) {
|
|
103
|
+
if (value !== null && value !== undefined) {
|
|
104
|
+
params.append(key, String(value));
|
|
105
|
+
}
|
|
109
106
|
}
|
|
110
|
-
);
|
|
111
|
-
|
|
112
|
-
// List tools handler
|
|
113
|
-
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
114
|
-
return { tools: TOOLS };
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
// Call tool handler
|
|
118
|
-
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
119
|
-
const { name, arguments: args } = request.params;
|
|
120
107
|
|
|
121
|
-
|
|
122
|
-
|
|
108
|
+
const response = await fetchAPI(`/search?${params.toString()}`);
|
|
109
|
+
if (!response.ok) {
|
|
110
|
+
throw new Error(`HTTP error: ${response.status}`);
|
|
123
111
|
}
|
|
124
112
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
if (value !== null && value !== undefined) {
|
|
129
|
-
params.append(key, String(value));
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
const response = await fetchAPI(`/search?${params.toString()}`);
|
|
134
|
-
if (!response.ok) {
|
|
135
|
-
throw new Error(`HTTP error: ${response.status}`);
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
const data = await response.json();
|
|
139
|
-
const results = data.data || [];
|
|
140
|
-
const pagination = data.pagination || {};
|
|
141
|
-
|
|
142
|
-
if (results.length === 0) {
|
|
143
|
-
return {
|
|
144
|
-
content: [
|
|
145
|
-
{
|
|
146
|
-
type: "text",
|
|
147
|
-
text: "No results found. Try: broader search terms, different content_type, or wider time range.",
|
|
148
|
-
},
|
|
149
|
-
],
|
|
150
|
-
};
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
const formattedResults: string[] = [];
|
|
154
|
-
for (const result of results) {
|
|
155
|
-
const content = result.content;
|
|
156
|
-
if (!content) continue;
|
|
157
|
-
|
|
158
|
-
if (result.type === "OCR") {
|
|
159
|
-
formattedResults.push(
|
|
160
|
-
`[OCR] ${content.app_name || "?"} | ${content.window_name || "?"}\n` +
|
|
161
|
-
`${content.timestamp || ""}\n` +
|
|
162
|
-
`${content.text || ""}`
|
|
163
|
-
);
|
|
164
|
-
} else if (result.type === "Audio") {
|
|
165
|
-
formattedResults.push(
|
|
166
|
-
`[Audio] ${content.device_name || "?"}\n` +
|
|
167
|
-
`${content.timestamp || ""}\n` +
|
|
168
|
-
`${content.transcription || ""}`
|
|
169
|
-
);
|
|
170
|
-
} else if (result.type === "UI" || result.type === "Accessibility") {
|
|
171
|
-
formattedResults.push(
|
|
172
|
-
`[Accessibility] ${content.app_name || "?"} | ${content.window_name || "?"}\n` +
|
|
173
|
-
`${content.timestamp || ""}\n` +
|
|
174
|
-
`${content.text || ""}`
|
|
175
|
-
);
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
const header = `Results: ${results.length}/${pagination.total || "?"}` +
|
|
180
|
-
(pagination.total > results.length ? ` (use offset=${(pagination.offset || 0) + results.length} for more)` : "");
|
|
113
|
+
const data = await response.json();
|
|
114
|
+
const results = data.data || [];
|
|
115
|
+
const pagination = data.pagination || {};
|
|
181
116
|
|
|
117
|
+
if (results.length === 0) {
|
|
182
118
|
return {
|
|
183
119
|
content: [
|
|
184
120
|
{
|
|
185
121
|
type: "text",
|
|
186
|
-
text:
|
|
122
|
+
text: "No results found. Try: broader search terms, different content_type, or wider time range.",
|
|
187
123
|
},
|
|
188
124
|
],
|
|
189
125
|
};
|
|
190
126
|
}
|
|
191
127
|
|
|
192
|
-
|
|
193
|
-
|
|
128
|
+
const formattedResults: string[] = [];
|
|
129
|
+
for (const result of results) {
|
|
130
|
+
const content = result.content;
|
|
131
|
+
if (!content) continue;
|
|
132
|
+
|
|
133
|
+
if (result.type === "OCR") {
|
|
134
|
+
formattedResults.push(
|
|
135
|
+
`[OCR] ${content.app_name || "?"} | ${content.window_name || "?"}\n` +
|
|
136
|
+
`${content.timestamp || ""}\n` +
|
|
137
|
+
`${content.text || ""}`
|
|
138
|
+
);
|
|
139
|
+
} else if (result.type === "Audio") {
|
|
140
|
+
formattedResults.push(
|
|
141
|
+
`[Audio] ${content.device_name || "?"}\n` +
|
|
142
|
+
`${content.timestamp || ""}\n` +
|
|
143
|
+
`${content.transcription || ""}`
|
|
144
|
+
);
|
|
145
|
+
} else if (result.type === "UI" || result.type === "Accessibility") {
|
|
146
|
+
formattedResults.push(
|
|
147
|
+
`[Accessibility] ${content.app_name || "?"} | ${content.window_name || "?"}\n` +
|
|
148
|
+
`${content.timestamp || ""}\n` +
|
|
149
|
+
`${content.text || ""}`
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const header = `Results: ${results.length}/${pagination.total || "?"}` +
|
|
155
|
+
(pagination.total > results.length ? ` (use offset=${(pagination.offset || 0) + results.length} for more)` : "");
|
|
156
|
+
|
|
157
|
+
return {
|
|
158
|
+
content: [
|
|
159
|
+
{
|
|
160
|
+
type: "text",
|
|
161
|
+
text: header + "\n\n" + formattedResults.join("\n---\n"),
|
|
162
|
+
},
|
|
163
|
+
],
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Create a fresh MCP Server instance with handlers registered.
|
|
168
|
+
// Each HTTP session gets its own Server — the MCP SDK requires a 1:1
|
|
169
|
+
// mapping between Server and transport (reusing a Server across
|
|
170
|
+
// transports throws "Already connected to a transport").
|
|
171
|
+
function createMcpServer(): Server {
|
|
172
|
+
const s = new Server(
|
|
173
|
+
{ name: "screenpipe-http", version: "0.14.0" },
|
|
174
|
+
{ capabilities: { tools: {} } }
|
|
175
|
+
);
|
|
176
|
+
|
|
177
|
+
s.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOLS }));
|
|
178
|
+
|
|
179
|
+
s.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
180
|
+
const { name, arguments: args } = request.params;
|
|
181
|
+
if (!args) throw new Error("Missing arguments");
|
|
182
|
+
if (name === "search_content") return handleSearchContent(args);
|
|
183
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
return s;
|
|
187
|
+
}
|
|
194
188
|
|
|
195
|
-
//
|
|
196
|
-
const
|
|
189
|
+
// Per-session state: each session gets its own Server + transport pair.
|
|
190
|
+
const sessions = new Map<string, { server: Server; transport: StreamableHTTPServerTransport }>();
|
|
197
191
|
|
|
198
192
|
const httpServer = createServer(async (req, res) => {
|
|
199
193
|
// CORS headers
|
|
200
194
|
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
201
|
-
res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
|
|
195
|
+
res.setHeader("Access-Control-Allow-Methods", "GET, POST, DELETE, OPTIONS");
|
|
202
196
|
res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization, mcp-session-id");
|
|
203
197
|
|
|
204
198
|
if (req.method === "OPTIONS") {
|
|
@@ -210,7 +204,7 @@ const httpServer = createServer(async (req, res) => {
|
|
|
210
204
|
// Health check
|
|
211
205
|
if (req.url === "/health") {
|
|
212
206
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
213
|
-
res.end(JSON.stringify({ status: "ok" }));
|
|
207
|
+
res.end(JSON.stringify({ status: "ok", sessions: sessions.size }));
|
|
214
208
|
return;
|
|
215
209
|
}
|
|
216
210
|
|
|
@@ -218,21 +212,23 @@ const httpServer = createServer(async (req, res) => {
|
|
|
218
212
|
if (req.url === "/mcp" || req.url?.startsWith("/mcp?")) {
|
|
219
213
|
const sessionId = req.headers["mcp-session-id"] as string | undefined;
|
|
220
214
|
|
|
221
|
-
let
|
|
215
|
+
let session = sessionId ? sessions.get(sessionId) : undefined;
|
|
222
216
|
|
|
223
|
-
if (!
|
|
224
|
-
|
|
217
|
+
if (!session) {
|
|
218
|
+
const server = createMcpServer();
|
|
219
|
+
const transport = new StreamableHTTPServerTransport({
|
|
225
220
|
sessionIdGenerator: () => crypto.randomUUID(),
|
|
226
221
|
});
|
|
227
222
|
|
|
228
223
|
await server.connect(transport);
|
|
229
224
|
|
|
230
225
|
if (transport.sessionId) {
|
|
231
|
-
|
|
226
|
+
sessions.set(transport.sessionId, { server, transport });
|
|
232
227
|
}
|
|
228
|
+
session = { server, transport };
|
|
233
229
|
}
|
|
234
230
|
|
|
235
|
-
await transport.handleRequest(req, res);
|
|
231
|
+
await session.transport.handleRequest(req, res);
|
|
236
232
|
return;
|
|
237
233
|
}
|
|
238
234
|
|
package/src/index.ts
CHANGED
|
@@ -28,6 +28,58 @@ for (let i = 0; i < args.length; i++) {
|
|
|
28
28
|
|
|
29
29
|
const SCREENPIPE_API = `http://localhost:${port}`;
|
|
30
30
|
|
|
31
|
+
// Discover API key: env var > npx screenpipe auth token > bundled bun
|
|
32
|
+
function discoverApiKey(): string {
|
|
33
|
+
const envKey = process.env.SCREENPIPE_LOCAL_API_KEY || process.env.SCREENPIPE_API_KEY;
|
|
34
|
+
if (envKey) return envKey;
|
|
35
|
+
|
|
36
|
+
const { execSync } = require("child_process");
|
|
37
|
+
const os = require("os");
|
|
38
|
+
const fs = require("fs");
|
|
39
|
+
const path = require("path");
|
|
40
|
+
|
|
41
|
+
// Try npx first (works if user has Node installed)
|
|
42
|
+
try {
|
|
43
|
+
const token = execSync("npx screenpipe@latest auth token", {
|
|
44
|
+
timeout: 15000,
|
|
45
|
+
encoding: "utf-8",
|
|
46
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
47
|
+
}).trim();
|
|
48
|
+
if (token) return token;
|
|
49
|
+
} catch {}
|
|
50
|
+
|
|
51
|
+
// Try bundled bun inside the screenpipe app
|
|
52
|
+
const bundledBunPaths: string[] = [];
|
|
53
|
+
const platform = os.platform();
|
|
54
|
+
if (platform === "darwin") {
|
|
55
|
+
bundledBunPaths.push("/Applications/screenpipe.app/Contents/MacOS/bun");
|
|
56
|
+
} else if (platform === "win32") {
|
|
57
|
+
bundledBunPaths.push(
|
|
58
|
+
path.join(process.env.LOCALAPPDATA || "", "screenpipe", "bun.exe"),
|
|
59
|
+
path.join(process.env.PROGRAMFILES || "", "screenpipe", "bun.exe"),
|
|
60
|
+
);
|
|
61
|
+
} else {
|
|
62
|
+
bundledBunPaths.push("/usr/lib/screenpipe/bun", "/opt/screenpipe/bun");
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
for (const bunPath of bundledBunPaths) {
|
|
66
|
+
try {
|
|
67
|
+
if (fs.existsSync(bunPath)) {
|
|
68
|
+
const token = execSync(`"${bunPath}" x screenpipe@latest auth token`, {
|
|
69
|
+
timeout: 15000,
|
|
70
|
+
encoding: "utf-8",
|
|
71
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
72
|
+
}).trim();
|
|
73
|
+
if (token) return token;
|
|
74
|
+
}
|
|
75
|
+
} catch {}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return "";
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const API_KEY = discoverApiKey();
|
|
82
|
+
|
|
31
83
|
// Read version from package.json (single source of truth)
|
|
32
84
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
33
85
|
const PKG_VERSION: string = require("../package.json").version;
|
|
@@ -531,6 +583,7 @@ async function fetchAPI(
|
|
|
531
583
|
...options,
|
|
532
584
|
headers: {
|
|
533
585
|
"Content-Type": "application/json",
|
|
586
|
+
...(API_KEY ? { Authorization: `Bearer ${API_KEY}` } : {}),
|
|
534
587
|
...options.headers,
|
|
535
588
|
},
|
|
536
589
|
});
|