@plaud-ai/mcp 0.2.2-beta.0 → 0.2.3
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/{chunk-SNSGVRCU.js → chunk-3XRFIJUG.js} +119 -1
- package/dist/{chunk-IZKXHQM3.js → chunk-BZOIX6WC.js} +31 -1
- package/dist/{chunk-7KGB7GSZ.js → chunk-GZ7QPRKV.js} +1 -1
- package/dist/index.js +46 -76
- package/dist/{install-EEQXUUG3.js → install-C7WMVIPG.js} +38 -58
- package/dist/server-ORMBAVW6.js +1039 -0
- package/package.json +1 -1
- package/plugin.json +1 -1
- package/dist/server-JP2TN7XF.js +0 -293
|
@@ -229,6 +229,124 @@ var PlaudClient = class {
|
|
|
229
229
|
}
|
|
230
230
|
};
|
|
231
231
|
|
|
232
|
+
// ../shared/dist/oauth-callback-server.js
|
|
233
|
+
import { createServer } from "http";
|
|
234
|
+
var SUCCESS_HTML = '<!doctype html><html><head><meta charset="utf-8"><title>Plaud</title></head><body style="font-family:system-ui;padding:2rem;text-align:center;"><h1>Authorization successful!</h1><p>You can close this tab.</p></body></html>';
|
|
235
|
+
var NEUTRAL_HTML = '<!doctype html><html><head><meta charset="utf-8"><title>Plaud</title></head><body style="font-family:system-ui;padding:2rem;text-align:center;"><h1>Continue authorization in the original window.</h1><p>This page can be closed.</p></body></html>';
|
|
236
|
+
function errorHtml(message) {
|
|
237
|
+
const escaped = message.replace(/[&<>]/g, (c) => ({ "&": "&", "<": "<", ">": ">" })[c]);
|
|
238
|
+
return '<!doctype html><html><head><meta charset="utf-8"><title>Plaud</title></head><body style="font-family:system-ui;padding:2rem;text-align:center;"><h1>Authorization failed</h1><pre style="white-space:pre-wrap;">' + escaped + "</pre></body></html>";
|
|
239
|
+
}
|
|
240
|
+
var CORS_HEADERS = {
|
|
241
|
+
"Access-Control-Allow-Origin": "*",
|
|
242
|
+
"Access-Control-Allow-Methods": "GET, OPTIONS",
|
|
243
|
+
"Access-Control-Allow-Headers": "*"
|
|
244
|
+
};
|
|
245
|
+
function runOAuthCallback(opts) {
|
|
246
|
+
const { port, expectedState, exchangeCode, timeoutMs = 12e4, onListening, postSuccessDelayMs = 1500 } = opts;
|
|
247
|
+
return new Promise((resolve) => {
|
|
248
|
+
let settled = false;
|
|
249
|
+
let exchangeStarted = false;
|
|
250
|
+
let exchangeSucceeded = false;
|
|
251
|
+
let timeoutId = null;
|
|
252
|
+
let closeTimeoutId = null;
|
|
253
|
+
const server = createServer((req, res) => {
|
|
254
|
+
if (req.method === "OPTIONS") {
|
|
255
|
+
res.writeHead(204, CORS_HEADERS);
|
|
256
|
+
res.end();
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
const reqUrl = new URL(req.url ?? "/", `http://localhost:${port}`);
|
|
260
|
+
if (reqUrl.pathname !== "/auth/callback") {
|
|
261
|
+
res.writeHead(404, CORS_HEADERS);
|
|
262
|
+
res.end();
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
const params = reqUrl.searchParams;
|
|
266
|
+
const error = params.get("error");
|
|
267
|
+
const state = params.get("state");
|
|
268
|
+
const code = params.get("code");
|
|
269
|
+
if (error) {
|
|
270
|
+
const desc = params.get("error_description") ?? error;
|
|
271
|
+
res.writeHead(400, { "Content-Type": "text/html", ...CORS_HEADERS });
|
|
272
|
+
res.end(errorHtml(`Authorization denied: ${desc}`));
|
|
273
|
+
finalize({ status: "denied", error: new Error(desc) });
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
if (!state || state !== expectedState) {
|
|
277
|
+
respondNeutral(res);
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
if (exchangeSucceeded) {
|
|
281
|
+
respondSuccess(res);
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
if (!code) {
|
|
285
|
+
respondNeutral(res);
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
if (exchangeStarted) {
|
|
289
|
+
respondNeutral(res);
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
exchangeStarted = true;
|
|
293
|
+
exchangeCode(code).then(() => {
|
|
294
|
+
exchangeSucceeded = true;
|
|
295
|
+
respondSuccess(res);
|
|
296
|
+
finalize({ status: "success" });
|
|
297
|
+
}, (err) => {
|
|
298
|
+
const e = err instanceof Error ? err : new Error(String(err));
|
|
299
|
+
res.writeHead(500, { "Content-Type": "text/html", ...CORS_HEADERS });
|
|
300
|
+
res.end(errorHtml(e.message));
|
|
301
|
+
finalize({ status: "exchange-failed", error: e });
|
|
302
|
+
});
|
|
303
|
+
});
|
|
304
|
+
server.on("error", (err) => {
|
|
305
|
+
if (settled)
|
|
306
|
+
return;
|
|
307
|
+
const message = err.code === "EADDRINUSE" ? `port ${port} is in use \u2014 another \`plaud login\` may still be running. Wait a few seconds and retry.` : `callback server error: ${err.message}`;
|
|
308
|
+
finalize({ status: "listen-failed", error: new Error(message) }, true);
|
|
309
|
+
});
|
|
310
|
+
timeoutId = setTimeout(() => {
|
|
311
|
+
finalize({ status: "timeout" }, true);
|
|
312
|
+
}, timeoutMs);
|
|
313
|
+
server.listen(port, () => {
|
|
314
|
+
onListening?.();
|
|
315
|
+
});
|
|
316
|
+
function respondSuccess(res) {
|
|
317
|
+
res.writeHead(200, { "Content-Type": "text/html", ...CORS_HEADERS });
|
|
318
|
+
res.end(SUCCESS_HTML);
|
|
319
|
+
}
|
|
320
|
+
function respondNeutral(res) {
|
|
321
|
+
res.writeHead(200, { "Content-Type": "text/html", ...CORS_HEADERS });
|
|
322
|
+
res.end(NEUTRAL_HTML);
|
|
323
|
+
}
|
|
324
|
+
function finalize(result, immediate = false) {
|
|
325
|
+
if (settled)
|
|
326
|
+
return;
|
|
327
|
+
settled = true;
|
|
328
|
+
if (timeoutId) {
|
|
329
|
+
clearTimeout(timeoutId);
|
|
330
|
+
timeoutId = null;
|
|
331
|
+
}
|
|
332
|
+
const close = () => {
|
|
333
|
+
try {
|
|
334
|
+
server.closeAllConnections?.();
|
|
335
|
+
} catch {
|
|
336
|
+
}
|
|
337
|
+
server.close(() => resolve(result));
|
|
338
|
+
};
|
|
339
|
+
if (immediate || result.status !== "success") {
|
|
340
|
+
close();
|
|
341
|
+
} else {
|
|
342
|
+
closeTimeoutId = setTimeout(close, postSuccessDelayMs);
|
|
343
|
+
closeTimeoutId.unref?.();
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
|
|
232
349
|
export {
|
|
233
|
-
PlaudClient
|
|
350
|
+
PlaudClient,
|
|
351
|
+
runOAuthCallback
|
|
234
352
|
};
|
|
@@ -20,6 +20,12 @@ function registerTools(server, client) {
|
|
|
20
20
|
"list_files",
|
|
21
21
|
{
|
|
22
22
|
description: "List Plaud recordings. Supports optional client-side filtering: `query` (case-insensitive name substring), `date_from`/`date_to` (YYYY-MM-DD, inclusive). When any filter is set, paginates up to 5 pages \xD7 100 recordings and returns all matches.",
|
|
23
|
+
annotations: {
|
|
24
|
+
title: "List recordings",
|
|
25
|
+
readOnlyHint: true,
|
|
26
|
+
destructiveHint: false,
|
|
27
|
+
openWorldHint: true
|
|
28
|
+
},
|
|
23
29
|
inputSchema: {
|
|
24
30
|
page: z.number().optional().default(1).describe("Page number (ignored when filters are set)"),
|
|
25
31
|
page_size: z.number().optional().default(20).describe("Items per page (ignored when filters are set)"),
|
|
@@ -82,6 +88,12 @@ function registerTools(server, client) {
|
|
|
82
88
|
"get_file",
|
|
83
89
|
{
|
|
84
90
|
description: "Get details of a specific Plaud recording by ID",
|
|
91
|
+
annotations: {
|
|
92
|
+
title: "Get recording details",
|
|
93
|
+
readOnlyHint: true,
|
|
94
|
+
destructiveHint: false,
|
|
95
|
+
openWorldHint: true
|
|
96
|
+
},
|
|
85
97
|
inputSchema: { file_id: z.string().describe("The file ID to retrieve") }
|
|
86
98
|
},
|
|
87
99
|
async ({ file_id }) => {
|
|
@@ -101,6 +113,12 @@ function registerTools(server, client) {
|
|
|
101
113
|
"get_note",
|
|
102
114
|
{
|
|
103
115
|
description: "Fetch AI-generated notes for a Plaud recording \u2014 compact summary, action items, and key topics",
|
|
116
|
+
annotations: {
|
|
117
|
+
title: "Get recording notes",
|
|
118
|
+
readOnlyHint: true,
|
|
119
|
+
destructiveHint: false,
|
|
120
|
+
openWorldHint: true
|
|
121
|
+
},
|
|
104
122
|
inputSchema: { file_id: z.string().describe("The file ID to retrieve notes for") }
|
|
105
123
|
},
|
|
106
124
|
async ({ file_id }) => {
|
|
@@ -120,6 +138,12 @@ function registerTools(server, client) {
|
|
|
120
138
|
"get_transcript",
|
|
121
139
|
{
|
|
122
140
|
description: "Fetch the full timestamped transcript with speaker attribution for a Plaud recording",
|
|
141
|
+
annotations: {
|
|
142
|
+
title: "Get recording transcript",
|
|
143
|
+
readOnlyHint: true,
|
|
144
|
+
destructiveHint: false,
|
|
145
|
+
openWorldHint: true
|
|
146
|
+
},
|
|
123
147
|
inputSchema: { file_id: z.string().describe("The file ID to retrieve transcript for") }
|
|
124
148
|
},
|
|
125
149
|
async ({ file_id }) => {
|
|
@@ -138,7 +162,13 @@ function registerTools(server, client) {
|
|
|
138
162
|
server.registerTool(
|
|
139
163
|
"get_current_user",
|
|
140
164
|
{
|
|
141
|
-
description: "Get current authenticated user info"
|
|
165
|
+
description: "Get current authenticated user info",
|
|
166
|
+
annotations: {
|
|
167
|
+
title: "Get current user",
|
|
168
|
+
readOnlyHint: true,
|
|
169
|
+
destructiveHint: false,
|
|
170
|
+
openWorldHint: true
|
|
171
|
+
}
|
|
142
172
|
},
|
|
143
173
|
async () => {
|
|
144
174
|
const start = Date.now();
|
package/dist/index.js
CHANGED
|
@@ -1,23 +1,24 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
getClient
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-GZ7QPRKV.js";
|
|
5
5
|
import {
|
|
6
6
|
loadSkills
|
|
7
7
|
} from "./chunk-4QBEOJPX.js";
|
|
8
8
|
import {
|
|
9
9
|
registerTools
|
|
10
|
-
} from "./chunk-
|
|
11
|
-
import
|
|
10
|
+
} from "./chunk-BZOIX6WC.js";
|
|
11
|
+
import {
|
|
12
|
+
runOAuthCallback
|
|
13
|
+
} from "./chunk-3XRFIJUG.js";
|
|
12
14
|
|
|
13
15
|
// src/index.ts
|
|
14
|
-
import { createServer } from "http";
|
|
15
16
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
16
17
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
17
18
|
import open from "open";
|
|
18
19
|
var server = new McpServer({
|
|
19
20
|
name: "plaud",
|
|
20
|
-
version: "0.2.
|
|
21
|
+
version: "0.2.3"
|
|
21
22
|
});
|
|
22
23
|
var CALLBACK_PORT = 8199;
|
|
23
24
|
var LOGIN_TIMEOUT_MS = 12e4;
|
|
@@ -30,79 +31,48 @@ server.registerTool("login", {
|
|
|
30
31
|
return { content: [{ type: "text", text: "Already logged in." }] };
|
|
31
32
|
}
|
|
32
33
|
const { url, codeVerifier, state } = client.auth.createAuthorizationRequest();
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
const code = reqUrl.searchParams.get("code");
|
|
42
|
-
if (!code) {
|
|
43
|
-
res.writeHead(400);
|
|
44
|
-
res.end("Missing code");
|
|
45
|
-
cleanup();
|
|
46
|
-
resolve({
|
|
47
|
-
content: [{ type: "text", text: "Authentication failed: missing authorization code in callback" }],
|
|
48
|
-
isError: true
|
|
49
|
-
});
|
|
50
|
-
return;
|
|
51
|
-
}
|
|
52
|
-
try {
|
|
53
|
-
await client.auth.exchangeCode(code, codeVerifier, state);
|
|
54
|
-
res.writeHead(200, { "Content-Type": "text/html" });
|
|
55
|
-
res.end("<h1>Authentication successful!</h1><p>You can close this tab.</p>");
|
|
56
|
-
cleanup();
|
|
57
|
-
resolve({
|
|
58
|
-
content: [{ type: "text", text: "Successfully authenticated with Plaud!" }]
|
|
59
|
-
});
|
|
60
|
-
} catch (err) {
|
|
61
|
-
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
62
|
-
res.writeHead(500, { "Content-Type": "text/html" });
|
|
63
|
-
res.end(`<h1>Token exchange failed</h1><pre>${errorMsg}</pre>`);
|
|
64
|
-
cleanup();
|
|
65
|
-
resolve({
|
|
66
|
-
content: [{ type: "text", text: `Authentication failed: ${err}` }],
|
|
67
|
-
isError: true
|
|
68
|
-
});
|
|
69
|
-
}
|
|
70
|
-
});
|
|
71
|
-
let timeoutId;
|
|
72
|
-
function cleanup() {
|
|
73
|
-
clearTimeout(timeoutId);
|
|
74
|
-
httpServer.closeAllConnections?.();
|
|
75
|
-
httpServer.close();
|
|
76
|
-
}
|
|
77
|
-
timeoutId = setTimeout(() => {
|
|
78
|
-
cleanup();
|
|
79
|
-
resolve({
|
|
80
|
-
content: [{ type: "text", text: "Authentication timed out after 2 minutes. Please try again." }],
|
|
81
|
-
isError: true
|
|
82
|
-
});
|
|
83
|
-
}, LOGIN_TIMEOUT_MS);
|
|
84
|
-
httpServer.listen(CALLBACK_PORT, () => {
|
|
34
|
+
const result = await runOAuthCallback({
|
|
35
|
+
port: CALLBACK_PORT,
|
|
36
|
+
expectedState: state,
|
|
37
|
+
timeoutMs: LOGIN_TIMEOUT_MS,
|
|
38
|
+
exchangeCode: async (code) => {
|
|
39
|
+
await client.auth.exchangeCode(code, codeVerifier, state);
|
|
40
|
+
},
|
|
41
|
+
onListening: () => {
|
|
85
42
|
open(url).catch(() => {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
switch (result.status) {
|
|
47
|
+
case "success":
|
|
48
|
+
return { content: [{ type: "text", text: "Successfully authenticated with Plaud!" }] };
|
|
49
|
+
case "timeout":
|
|
50
|
+
return {
|
|
51
|
+
content: [{
|
|
52
|
+
type: "text",
|
|
53
|
+
text: `Authentication timed out after 2 minutes. If no browser opened, open this URL on the same machine and retry:
|
|
90
54
|
${url}
|
|
91
55
|
|
|
92
|
-
If this is a remote/headless machine, forward local port
|
|
93
|
-
|
|
94
|
-
isError: true
|
|
95
|
-
});
|
|
96
|
-
});
|
|
97
|
-
});
|
|
98
|
-
httpServer.on("error", (err) => {
|
|
99
|
-
cleanup();
|
|
100
|
-
resolve({
|
|
101
|
-
content: [{ type: "text", text: `Failed to start callback server: ${err.message}` }],
|
|
56
|
+
If this is a remote/headless machine, forward local port ${CALLBACK_PORT} first.`
|
|
57
|
+
}],
|
|
102
58
|
isError: true
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
|
|
59
|
+
};
|
|
60
|
+
case "denied":
|
|
61
|
+
return {
|
|
62
|
+
content: [{ type: "text", text: `Authentication denied: ${result.error?.message ?? "user declined authorization"}` }],
|
|
63
|
+
isError: true
|
|
64
|
+
};
|
|
65
|
+
case "exchange-failed":
|
|
66
|
+
return {
|
|
67
|
+
content: [{ type: "text", text: `Authentication failed: ${result.error?.message ?? "token exchange failed"}` }],
|
|
68
|
+
isError: true
|
|
69
|
+
};
|
|
70
|
+
case "listen-failed":
|
|
71
|
+
return {
|
|
72
|
+
content: [{ type: "text", text: `Failed to start callback server: ${result.error?.message ?? "unknown error"}` }],
|
|
73
|
+
isError: true
|
|
74
|
+
};
|
|
75
|
+
}
|
|
106
76
|
});
|
|
107
77
|
registerTools(server, getClient());
|
|
108
78
|
server.registerTool("logout", {
|
|
@@ -134,7 +104,7 @@ async function main() {
|
|
|
134
104
|
const sub = process.argv[2];
|
|
135
105
|
const sub2 = process.argv[3];
|
|
136
106
|
if (sub === "install") {
|
|
137
|
-
const { runInstall } = await import("./install-
|
|
107
|
+
const { runInstall } = await import("./install-C7WMVIPG.js");
|
|
138
108
|
const args = process.argv.slice(3);
|
|
139
109
|
const yes = args.some((a) => a === "--yes" || a === "-y");
|
|
140
110
|
const noLogin = args.some((a) => a === "--no-login");
|
|
@@ -167,7 +137,7 @@ async function main() {
|
|
|
167
137
|
return;
|
|
168
138
|
}
|
|
169
139
|
if (sub === "http") {
|
|
170
|
-
const { startHttpServer } = await import("./server-
|
|
140
|
+
const { startHttpServer } = await import("./server-ORMBAVW6.js");
|
|
171
141
|
startHttpServer();
|
|
172
142
|
return;
|
|
173
143
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
getClient
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-GZ7QPRKV.js";
|
|
4
4
|
import {
|
|
5
5
|
copyToClipboard,
|
|
6
6
|
getMcpEntry,
|
|
@@ -9,11 +9,12 @@ import {
|
|
|
9
9
|
import {
|
|
10
10
|
skillsCombined
|
|
11
11
|
} from "./chunk-4QBEOJPX.js";
|
|
12
|
-
import
|
|
12
|
+
import {
|
|
13
|
+
runOAuthCallback
|
|
14
|
+
} from "./chunk-3XRFIJUG.js";
|
|
13
15
|
|
|
14
16
|
// src/install.ts
|
|
15
17
|
import { createInterface } from "readline/promises";
|
|
16
|
-
import { createServer } from "http";
|
|
17
18
|
import open from "open";
|
|
18
19
|
|
|
19
20
|
// src/installers/registry.ts
|
|
@@ -196,12 +197,15 @@ function jsonMcpServersAdapter(spec) {
|
|
|
196
197
|
}
|
|
197
198
|
|
|
198
199
|
// src/installers/web-client.ts
|
|
200
|
+
var DEFAULT_REMOTE_MCP_URL = "https://mcp.plaud.ai/mcp";
|
|
199
201
|
function remoteMcpUrl() {
|
|
200
202
|
const explicit = process.env.PLAUD_REMOTE_MCP_URL?.trim();
|
|
201
203
|
if (explicit) return explicit;
|
|
202
204
|
const serverUrl = process.env.PLAUD_SERVER_URL?.trim();
|
|
203
|
-
if (
|
|
204
|
-
|
|
205
|
+
if (serverUrl) {
|
|
206
|
+
return /\/(mcp|sse)\/?$/.test(serverUrl) ? serverUrl : `${serverUrl.replace(/\/+$/, "")}/mcp`;
|
|
207
|
+
}
|
|
208
|
+
return DEFAULT_REMOTE_MCP_URL;
|
|
205
209
|
}
|
|
206
210
|
function webClientAdapter(spec) {
|
|
207
211
|
return {
|
|
@@ -213,7 +217,6 @@ function webClientAdapter(spec) {
|
|
|
213
217
|
},
|
|
214
218
|
async install() {
|
|
215
219
|
const url = remoteMcpUrl();
|
|
216
|
-
const remoteLine = url ? `Remote MCP URL: ${url}` : "Remote MCP URL: not configured. Deploy `plaud-mcp http` behind HTTPS, then rerun with PLAUD_REMOTE_MCP_URL=https://your-host.example/mcp.";
|
|
217
220
|
return {
|
|
218
221
|
status: "configured",
|
|
219
222
|
requiresLocalAuth: false,
|
|
@@ -221,7 +224,7 @@ function webClientAdapter(spec) {
|
|
|
221
224
|
message: [
|
|
222
225
|
"manual setup only:",
|
|
223
226
|
` Open: ${spec.setupUrl}`,
|
|
224
|
-
` ${
|
|
227
|
+
` Remote MCP URL: ${url}`,
|
|
225
228
|
` Step: ${spec.setupStep}`
|
|
226
229
|
].join("\n")
|
|
227
230
|
};
|
|
@@ -416,63 +419,40 @@ async function runLogin() {
|
|
|
416
419
|
await client.auth.logout().catch(() => void 0);
|
|
417
420
|
}
|
|
418
421
|
const { url, codeVerifier, state } = client.auth.createAuthorizationRequest();
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
const code = reqUrl.searchParams.get("code");
|
|
428
|
-
if (!code) {
|
|
429
|
-
res.writeHead(400);
|
|
430
|
-
res.end("Missing code");
|
|
431
|
-
cleanup();
|
|
432
|
-
resolve({ status: "failed", message: "missing authorization code in callback" });
|
|
433
|
-
return;
|
|
434
|
-
}
|
|
435
|
-
try {
|
|
436
|
-
await client.auth.exchangeCode(code, codeVerifier, state);
|
|
437
|
-
res.writeHead(200, { "Content-Type": "text/html" });
|
|
438
|
-
res.end("<h1>Authentication successful!</h1><p>You can close this tab.</p>");
|
|
439
|
-
cleanup();
|
|
440
|
-
let who;
|
|
441
|
-
try {
|
|
442
|
-
const user = await client.getCurrentUser();
|
|
443
|
-
who = pickIdentity(user);
|
|
444
|
-
} catch {
|
|
445
|
-
}
|
|
446
|
-
resolve({ status: "success", who });
|
|
447
|
-
} catch (err) {
|
|
448
|
-
res.writeHead(500, { "Content-Type": "text/html" });
|
|
449
|
-
res.end("<h1>Token exchange failed</h1>");
|
|
450
|
-
cleanup();
|
|
451
|
-
resolve({ status: "failed", message: err instanceof Error ? err.message : String(err) });
|
|
452
|
-
}
|
|
453
|
-
});
|
|
454
|
-
let timeoutId;
|
|
455
|
-
function cleanup() {
|
|
456
|
-
clearTimeout(timeoutId);
|
|
457
|
-
httpServer.closeAllConnections?.();
|
|
458
|
-
httpServer.close();
|
|
459
|
-
}
|
|
460
|
-
timeoutId = setTimeout(() => {
|
|
461
|
-
cleanup();
|
|
462
|
-
resolve({ status: "timeout" });
|
|
463
|
-
}, LOGIN_TIMEOUT_MS);
|
|
464
|
-
httpServer.listen(CALLBACK_PORT, () => {
|
|
422
|
+
const result = await runOAuthCallback({
|
|
423
|
+
port: CALLBACK_PORT,
|
|
424
|
+
expectedState: state,
|
|
425
|
+
timeoutMs: LOGIN_TIMEOUT_MS,
|
|
426
|
+
exchangeCode: async (code) => {
|
|
427
|
+
await client.auth.exchangeCode(code, codeVerifier, state);
|
|
428
|
+
},
|
|
429
|
+
onListening: () => {
|
|
465
430
|
console.log(` if no browser opens, open this URL while this command is still running:
|
|
466
431
|
${url}`);
|
|
467
432
|
open(url).catch(() => {
|
|
468
433
|
console.log(" browser launch failed \u2014 waiting for a manual browser callback.");
|
|
469
434
|
});
|
|
470
|
-
}
|
|
471
|
-
httpServer.on("error", (err) => {
|
|
472
|
-
cleanup();
|
|
473
|
-
resolve({ status: "failed", message: `callback server error: ${err.message}` });
|
|
474
|
-
});
|
|
435
|
+
}
|
|
475
436
|
});
|
|
437
|
+
switch (result.status) {
|
|
438
|
+
case "success": {
|
|
439
|
+
let who;
|
|
440
|
+
try {
|
|
441
|
+
const user = await client.getCurrentUser();
|
|
442
|
+
who = pickIdentity(user);
|
|
443
|
+
} catch {
|
|
444
|
+
}
|
|
445
|
+
return { status: "success", who };
|
|
446
|
+
}
|
|
447
|
+
case "timeout":
|
|
448
|
+
return { status: "timeout" };
|
|
449
|
+
case "denied":
|
|
450
|
+
return { status: "failed", message: result.error?.message ?? "authorization denied" };
|
|
451
|
+
case "exchange-failed":
|
|
452
|
+
return { status: "failed", message: result.error?.message ?? "token exchange failed" };
|
|
453
|
+
case "listen-failed":
|
|
454
|
+
return { status: "failed", message: result.error?.message ?? "callback server failed to start" };
|
|
455
|
+
}
|
|
476
456
|
}
|
|
477
457
|
async function runInstall(opts = {}) {
|
|
478
458
|
console.log("Plaud MCP installer\n");
|