@plaud-ai/mcp 0.2.2 → 0.2.4
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 +59 -76
- package/dist/{install-EEQXUUG3.js → install-ALLWR7CL.js} +53 -62
- package/dist/server-VY7ANVCH.js +1039 -0
- package/package.json +12 -11
- package/plugin.json +1 -1
- package/dist/server-QWAZPBHU.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.4"
|
|
21
22
|
});
|
|
22
23
|
var CALLBACK_PORT = 8199;
|
|
23
24
|
var LOGIN_TIMEOUT_MS = 12e4;
|
|
@@ -27,82 +28,64 @@ server.registerTool("login", {
|
|
|
27
28
|
const client = getClient();
|
|
28
29
|
const existingToken = await client.auth.getAccessToken();
|
|
29
30
|
if (existingToken) {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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}` }],
|
|
31
|
+
try {
|
|
32
|
+
await client.getCurrentUser();
|
|
33
|
+
return { content: [{ type: "text", text: "Already logged in." }] };
|
|
34
|
+
} catch (err) {
|
|
35
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
36
|
+
if (msg.includes("401") || msg.includes("Not authenticated")) {
|
|
37
|
+
await client.auth.logout();
|
|
38
|
+
} else {
|
|
39
|
+
return {
|
|
40
|
+
content: [{ type: "text", text: `Failed to verify login state: ${msg}` }],
|
|
67
41
|
isError: true
|
|
68
|
-
}
|
|
42
|
+
};
|
|
69
43
|
}
|
|
70
|
-
});
|
|
71
|
-
let timeoutId;
|
|
72
|
-
function cleanup() {
|
|
73
|
-
clearTimeout(timeoutId);
|
|
74
|
-
httpServer.closeAllConnections?.();
|
|
75
|
-
httpServer.close();
|
|
76
44
|
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
45
|
+
}
|
|
46
|
+
const { url, codeVerifier, state } = client.auth.createAuthorizationRequest();
|
|
47
|
+
const result = await runOAuthCallback({
|
|
48
|
+
port: CALLBACK_PORT,
|
|
49
|
+
expectedState: state,
|
|
50
|
+
timeoutMs: LOGIN_TIMEOUT_MS,
|
|
51
|
+
exchangeCode: async (code) => {
|
|
52
|
+
await client.auth.exchangeCode(code, codeVerifier, state);
|
|
53
|
+
},
|
|
54
|
+
onListening: () => {
|
|
85
55
|
open(url).catch(() => {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
switch (result.status) {
|
|
60
|
+
case "success":
|
|
61
|
+
return { content: [{ type: "text", text: "Successfully authenticated with Plaud!" }] };
|
|
62
|
+
case "timeout":
|
|
63
|
+
return {
|
|
64
|
+
content: [{
|
|
65
|
+
type: "text",
|
|
66
|
+
text: `Authentication timed out after 2 minutes. If no browser opened, open this URL on the same machine and retry:
|
|
90
67
|
${url}
|
|
91
68
|
|
|
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}` }],
|
|
69
|
+
If this is a remote/headless machine, forward local port ${CALLBACK_PORT} first.`
|
|
70
|
+
}],
|
|
102
71
|
isError: true
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
|
|
72
|
+
};
|
|
73
|
+
case "denied":
|
|
74
|
+
return {
|
|
75
|
+
content: [{ type: "text", text: `Authentication denied: ${result.error?.message ?? "user declined authorization"}` }],
|
|
76
|
+
isError: true
|
|
77
|
+
};
|
|
78
|
+
case "exchange-failed":
|
|
79
|
+
return {
|
|
80
|
+
content: [{ type: "text", text: `Authentication failed: ${result.error?.message ?? "token exchange failed"}` }],
|
|
81
|
+
isError: true
|
|
82
|
+
};
|
|
83
|
+
case "listen-failed":
|
|
84
|
+
return {
|
|
85
|
+
content: [{ type: "text", text: `Failed to start callback server: ${result.error?.message ?? "unknown error"}` }],
|
|
86
|
+
isError: true
|
|
87
|
+
};
|
|
88
|
+
}
|
|
106
89
|
});
|
|
107
90
|
registerTools(server, getClient());
|
|
108
91
|
server.registerTool("logout", {
|
|
@@ -134,7 +117,7 @@ async function main() {
|
|
|
134
117
|
const sub = process.argv[2];
|
|
135
118
|
const sub2 = process.argv[3];
|
|
136
119
|
if (sub === "install") {
|
|
137
|
-
const { runInstall } = await import("./install-
|
|
120
|
+
const { runInstall } = await import("./install-ALLWR7CL.js");
|
|
138
121
|
const args = process.argv.slice(3);
|
|
139
122
|
const yes = args.some((a) => a === "--yes" || a === "-y");
|
|
140
123
|
const noLogin = args.some((a) => a === "--no-login");
|
|
@@ -167,7 +150,7 @@ async function main() {
|
|
|
167
150
|
return;
|
|
168
151
|
}
|
|
169
152
|
if (sub === "http") {
|
|
170
|
-
const { startHttpServer } = await import("./server-
|
|
153
|
+
const { startHttpServer } = await import("./server-VY7ANVCH.js");
|
|
171
154
|
startHttpServer();
|
|
172
155
|
return;
|
|
173
156
|
}
|
|
@@ -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
|
};
|
|
@@ -408,71 +411,53 @@ async function runLogin() {
|
|
|
408
411
|
try {
|
|
409
412
|
const user = await client.getCurrentUser();
|
|
410
413
|
return { status: "already-authed", who: pickIdentity(user) };
|
|
411
|
-
} catch {
|
|
412
|
-
|
|
414
|
+
} catch (err) {
|
|
415
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
416
|
+
if (msg.includes("401") || msg.includes("Not authenticated")) {
|
|
417
|
+
await client.auth.logout().catch(() => void 0);
|
|
418
|
+
} else {
|
|
419
|
+
return { status: "already-authed" };
|
|
420
|
+
}
|
|
413
421
|
}
|
|
414
422
|
}
|
|
415
423
|
} catch {
|
|
416
424
|
await client.auth.logout().catch(() => void 0);
|
|
417
425
|
}
|
|
418
426
|
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, () => {
|
|
427
|
+
const result = await runOAuthCallback({
|
|
428
|
+
port: CALLBACK_PORT,
|
|
429
|
+
expectedState: state,
|
|
430
|
+
timeoutMs: LOGIN_TIMEOUT_MS,
|
|
431
|
+
exchangeCode: async (code) => {
|
|
432
|
+
await client.auth.exchangeCode(code, codeVerifier, state);
|
|
433
|
+
},
|
|
434
|
+
onListening: () => {
|
|
465
435
|
console.log(` if no browser opens, open this URL while this command is still running:
|
|
466
436
|
${url}`);
|
|
467
437
|
open(url).catch(() => {
|
|
468
438
|
console.log(" browser launch failed \u2014 waiting for a manual browser callback.");
|
|
469
439
|
});
|
|
470
|
-
}
|
|
471
|
-
httpServer.on("error", (err) => {
|
|
472
|
-
cleanup();
|
|
473
|
-
resolve({ status: "failed", message: `callback server error: ${err.message}` });
|
|
474
|
-
});
|
|
440
|
+
}
|
|
475
441
|
});
|
|
442
|
+
switch (result.status) {
|
|
443
|
+
case "success": {
|
|
444
|
+
let who;
|
|
445
|
+
try {
|
|
446
|
+
const user = await client.getCurrentUser();
|
|
447
|
+
who = pickIdentity(user);
|
|
448
|
+
} catch {
|
|
449
|
+
}
|
|
450
|
+
return { status: "success", who };
|
|
451
|
+
}
|
|
452
|
+
case "timeout":
|
|
453
|
+
return { status: "timeout" };
|
|
454
|
+
case "denied":
|
|
455
|
+
return { status: "failed", message: result.error?.message ?? "authorization denied" };
|
|
456
|
+
case "exchange-failed":
|
|
457
|
+
return { status: "failed", message: result.error?.message ?? "token exchange failed" };
|
|
458
|
+
case "listen-failed":
|
|
459
|
+
return { status: "failed", message: result.error?.message ?? "callback server failed to start" };
|
|
460
|
+
}
|
|
476
461
|
}
|
|
477
462
|
async function runInstall(opts = {}) {
|
|
478
463
|
console.log("Plaud MCP installer\n");
|
|
@@ -554,8 +539,14 @@ async function doLoginStep(nonInteractive) {
|
|
|
554
539
|
const who = pickIdentity(user);
|
|
555
540
|
console.log(` already signed in${who ? ` as ${who}` : ""} \u2014 skipping OAuth.`);
|
|
556
541
|
return { status: "already-authed", who };
|
|
557
|
-
} catch {
|
|
558
|
-
|
|
542
|
+
} catch (err) {
|
|
543
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
544
|
+
if (msg.includes("401") || msg.includes("Not authenticated")) {
|
|
545
|
+
console.log(" saved token was revoked server-side \u2014 clearing and re-authenticating.");
|
|
546
|
+
await client.auth.logout().catch(() => void 0);
|
|
547
|
+
} else {
|
|
548
|
+
console.log(" token present but user lookup failed \u2014 will re-auth.");
|
|
549
|
+
}
|
|
559
550
|
}
|
|
560
551
|
}
|
|
561
552
|
} catch {
|