@plaud-ai/mcp 0.2.1 → 0.2.2
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-MPCF6HMK.js → chunk-IZKXHQM3.js} +28 -18
- package/dist/{chunk-K22WPZ7P.js → chunk-QT25MKZJ.js} +17 -7
- package/dist/index.js +17 -13
- package/dist/install-EEQXUUG3.js +657 -0
- package/dist/{server-D3H6AMVD.js → server-QWAZPBHU.js} +20 -21
- package/dist/{setup-QRUDA7ES.js → setup-QXZHVVDP.js} +1 -1
- package/package.json +11 -12
- package/plugin.json +1 -1
- package/dist/install-I2N3DJG4.js +0 -384
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
logger,
|
|
3
3
|
registerTools
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-IZKXHQM3.js";
|
|
5
5
|
import {
|
|
6
6
|
PlaudClient
|
|
7
7
|
} from "./chunk-SNSGVRCU.js";
|
|
@@ -21,7 +21,6 @@ import { randomUUID } from "crypto";
|
|
|
21
21
|
import { ProxyOAuthServerProvider } from "@modelcontextprotocol/sdk/server/auth/providers/proxyProvider.js";
|
|
22
22
|
var PlaudOAuthProvider = class extends ProxyOAuthServerProvider {
|
|
23
23
|
_plaudClientId;
|
|
24
|
-
_plaudClientSecret;
|
|
25
24
|
_plaudTokenUrl;
|
|
26
25
|
_plaudApiBase;
|
|
27
26
|
_callbackUrl;
|
|
@@ -29,8 +28,6 @@ var PlaudOAuthProvider = class extends ProxyOAuthServerProvider {
|
|
|
29
28
|
// internalState → { clientRedirectUri, originalState }
|
|
30
29
|
// We generate our own state to track the pending flow regardless of whether the client sent one.
|
|
31
30
|
_pendingStates = /* @__PURE__ */ new Map();
|
|
32
|
-
// code → internalState: lets exchangeAuthorizationCode include state in the Plaud token request
|
|
33
|
-
_pendingCodes = /* @__PURE__ */ new Map();
|
|
34
31
|
constructor(options) {
|
|
35
32
|
const authUrl = options.authUrl ?? "https://web.plaud.ai/platform/oauth";
|
|
36
33
|
const tokenUrl = options.tokenUrl ?? "https://platform.plaud.ai/developer/api/oauth/third-party/access-token";
|
|
@@ -43,7 +40,7 @@ var PlaudOAuthProvider = class extends ProxyOAuthServerProvider {
|
|
|
43
40
|
verifyAccessToken: async (token) => {
|
|
44
41
|
const client = new PlaudClient({
|
|
45
42
|
clientId: options.clientId,
|
|
46
|
-
clientSecret:
|
|
43
|
+
clientSecret: "",
|
|
47
44
|
redirectUri: "",
|
|
48
45
|
apiBase,
|
|
49
46
|
staticToken: token
|
|
@@ -75,7 +72,6 @@ var PlaudOAuthProvider = class extends ProxyOAuthServerProvider {
|
|
|
75
72
|
getClient: async (id) => this._registeredClients.get(id)
|
|
76
73
|
});
|
|
77
74
|
this._plaudClientId = options.clientId;
|
|
78
|
-
this._plaudClientSecret = options.clientSecret;
|
|
79
75
|
this._plaudTokenUrl = tokenUrl;
|
|
80
76
|
this._plaudApiBase = apiBase;
|
|
81
77
|
this._callbackUrl = options.callbackUrl;
|
|
@@ -133,7 +129,6 @@ var PlaudOAuthProvider = class extends ProxyOAuthServerProvider {
|
|
|
133
129
|
return;
|
|
134
130
|
}
|
|
135
131
|
this._pendingStates.delete(state);
|
|
136
|
-
this._pendingCodes.set(code, state);
|
|
137
132
|
logger.info({ event: "oauth_callback_received", internal_state: state });
|
|
138
133
|
const target = new URL(pending.clientRedirectUri);
|
|
139
134
|
target.searchParams.set("code", code);
|
|
@@ -142,26 +137,20 @@ var PlaudOAuthProvider = class extends ProxyOAuthServerProvider {
|
|
|
142
137
|
}
|
|
143
138
|
res.redirect(target.toString());
|
|
144
139
|
}
|
|
145
|
-
// Override to use
|
|
140
|
+
// Override to use PKCE public-client flow — no Basic auth, client_id sent in body.
|
|
146
141
|
async exchangeAuthorizationCode(_client, authorizationCode, codeVerifier, _redirectUri) {
|
|
147
|
-
const basicAuth = Buffer.from(
|
|
148
|
-
`${this._plaudClientId}:${this._plaudClientSecret}`
|
|
149
|
-
).toString("base64");
|
|
150
|
-
const internalState = this._pendingCodes.get(authorizationCode);
|
|
151
|
-
this._pendingCodes.delete(authorizationCode);
|
|
152
142
|
const body = {
|
|
153
143
|
grant_type: "authorization_code",
|
|
144
|
+
client_id: this._plaudClientId,
|
|
154
145
|
code: authorizationCode,
|
|
155
146
|
redirect_uri: this._callbackUrl
|
|
156
147
|
};
|
|
157
148
|
if (codeVerifier) body.code_verifier = codeVerifier;
|
|
158
|
-
if (internalState) body.state = internalState;
|
|
159
149
|
const fetchRes = await fetch(this._plaudTokenUrl, {
|
|
160
150
|
method: "POST",
|
|
161
151
|
headers: {
|
|
162
152
|
"Content-Type": "application/x-www-form-urlencoded",
|
|
163
|
-
Accept: "application/json"
|
|
164
|
-
Authorization: `Basic ${basicAuth}`
|
|
153
|
+
Accept: "application/json"
|
|
165
154
|
},
|
|
166
155
|
body: new URLSearchParams(body)
|
|
167
156
|
});
|
|
@@ -188,13 +177,11 @@ var CALLBACK_PORT = 8199;
|
|
|
188
177
|
var CALLBACK_PATH = "/auth/callback";
|
|
189
178
|
var CALLBACK_URL = process.env.PLAUD_CALLBACK_URL ?? `http://localhost:${CALLBACK_PORT}${CALLBACK_PATH}`;
|
|
190
179
|
function startHttpServer() {
|
|
191
|
-
const clientId = process.env.PLAUD_MCP_CLIENT_ID ??
|
|
192
|
-
const clientSecret = process.env.PLAUD_CLIENT_SECRET ?? "";
|
|
180
|
+
const clientId = process.env.PLAUD_MCP_CLIENT_ID ?? "client_37d250cb-50f8-4af1-8cd6-bc6711c5d684";
|
|
193
181
|
const apiBase = process.env.PLAUD_API_BASE;
|
|
194
182
|
const serverUrl = process.env.PLAUD_SERVER_URL ?? `http://localhost:${HTTP_PORT}`;
|
|
195
183
|
const provider = new PlaudOAuthProvider({
|
|
196
184
|
clientId,
|
|
197
|
-
clientSecret,
|
|
198
185
|
callbackUrl: CALLBACK_URL,
|
|
199
186
|
authUrl: process.env.PLAUD_AUTH_URL,
|
|
200
187
|
tokenUrl: process.env.PLAUD_TOKEN_URL,
|
|
@@ -205,6 +192,18 @@ function startHttpServer() {
|
|
|
205
192
|
app.get("/health", (_req, res) => {
|
|
206
193
|
res.json({ status: "ok", uptime_s: Math.floor(process.uptime()) });
|
|
207
194
|
});
|
|
195
|
+
if (process.env.PLAUD_CALLBACK_URL) {
|
|
196
|
+
app.get(CALLBACK_PATH, (req, res) => {
|
|
197
|
+
const code = req.query["code"];
|
|
198
|
+
const state = req.query["state"];
|
|
199
|
+
if (!code || !state) {
|
|
200
|
+
logger.warn({ event: "oauth_callback_invalid", code: !!code, state: !!state });
|
|
201
|
+
res.status(400).send("Missing code or state");
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
provider.handleCallback(code, state, res);
|
|
205
|
+
});
|
|
206
|
+
}
|
|
208
207
|
app.use((req, res, next) => {
|
|
209
208
|
const reqId = req.headers["x-request-id"] ?? randomUUID2();
|
|
210
209
|
res.locals["reqId"] = reqId;
|
|
@@ -236,12 +235,12 @@ function startHttpServer() {
|
|
|
236
235
|
reqLog.info({ event: "mcp_request_start", client_id: req.auth.clientId });
|
|
237
236
|
const client = new PlaudClient({
|
|
238
237
|
clientId,
|
|
239
|
-
clientSecret,
|
|
238
|
+
clientSecret: "",
|
|
240
239
|
redirectUri: "",
|
|
241
240
|
apiBase,
|
|
242
241
|
staticToken: token
|
|
243
242
|
});
|
|
244
|
-
const mcpServer = new McpServer({ name: "plaud", version: "0.
|
|
243
|
+
const mcpServer = new McpServer({ name: "plaud", version: "0.2.2" });
|
|
245
244
|
registerTools(mcpServer, client);
|
|
246
245
|
const transport = new StreamableHTTPServerTransport({
|
|
247
246
|
sessionIdGenerator: void 0
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@plaud-ai/mcp",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -12,14 +12,6 @@
|
|
|
12
12
|
".mcp.json",
|
|
13
13
|
"skills"
|
|
14
14
|
],
|
|
15
|
-
"scripts": {
|
|
16
|
-
"version:show": "node -p \"require('./package.json').version\"",
|
|
17
|
-
"prebuild": "node ../../scripts/sync-skills.mjs",
|
|
18
|
-
"build": "tsup",
|
|
19
|
-
"dev": "tsup --watch",
|
|
20
|
-
"clean": "rm -rf dist skills",
|
|
21
|
-
"prepublishOnly": "node -e \"const fs=require('fs'),v=require('./package.json').version,p=JSON.parse(fs.readFileSync('./plugin.json','utf8'));p.version=v;fs.writeFileSync('./plugin.json',JSON.stringify(p,null,2)+'\\n');\""
|
|
22
|
-
},
|
|
23
15
|
"dependencies": {
|
|
24
16
|
"@modelcontextprotocol/sdk": "^1.12.0",
|
|
25
17
|
"express": "^5.2.1",
|
|
@@ -28,9 +20,16 @@
|
|
|
28
20
|
"zod": "^4.3.6"
|
|
29
21
|
},
|
|
30
22
|
"devDependencies": {
|
|
31
|
-
"@plaud-ai/shared": "workspace:*",
|
|
32
23
|
"@types/express": "^5.0.6",
|
|
33
24
|
"@types/node": "^25.5.0",
|
|
34
|
-
"typescript": "^5.7.0"
|
|
25
|
+
"typescript": "^5.7.0",
|
|
26
|
+
"@plaud-ai/shared": "0.1.0"
|
|
27
|
+
},
|
|
28
|
+
"scripts": {
|
|
29
|
+
"version:show": "node -p \"require('./package.json').version\"",
|
|
30
|
+
"prebuild": "node ../../scripts/sync-skills.mjs",
|
|
31
|
+
"build": "tsup",
|
|
32
|
+
"dev": "tsup --watch",
|
|
33
|
+
"clean": "rm -rf dist skills"
|
|
35
34
|
}
|
|
36
|
-
}
|
|
35
|
+
}
|
package/plugin.json
CHANGED
package/dist/install-I2N3DJG4.js
DELETED
|
@@ -1,384 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
getClient
|
|
3
|
-
} from "./chunk-7KGB7GSZ.js";
|
|
4
|
-
import {
|
|
5
|
-
copyToClipboard,
|
|
6
|
-
getMcpEntry,
|
|
7
|
-
writeSkillsToClaudeCode
|
|
8
|
-
} from "./chunk-K22WPZ7P.js";
|
|
9
|
-
import {
|
|
10
|
-
skillsCombined
|
|
11
|
-
} from "./chunk-4QBEOJPX.js";
|
|
12
|
-
import "./chunk-SNSGVRCU.js";
|
|
13
|
-
|
|
14
|
-
// src/install.ts
|
|
15
|
-
import { readFile, writeFile, mkdir } from "fs/promises";
|
|
16
|
-
import { existsSync } from "fs";
|
|
17
|
-
import { createInterface } from "readline/promises";
|
|
18
|
-
import { createServer } from "http";
|
|
19
|
-
import { join, dirname } from "path";
|
|
20
|
-
import { homedir, platform } from "os";
|
|
21
|
-
import { spawnSync } from "child_process";
|
|
22
|
-
import open from "open";
|
|
23
|
-
function claudeDesktopConfigPath() {
|
|
24
|
-
if (platform() === "darwin") {
|
|
25
|
-
return join(homedir(), "Library", "Application Support", "Claude", "claude_desktop_config.json");
|
|
26
|
-
}
|
|
27
|
-
if (platform() === "win32") {
|
|
28
|
-
return join(process.env.APPDATA ?? "", "Claude", "claude_desktop_config.json");
|
|
29
|
-
}
|
|
30
|
-
return null;
|
|
31
|
-
}
|
|
32
|
-
function claudeCodeDir() {
|
|
33
|
-
return join(homedir(), ".claude");
|
|
34
|
-
}
|
|
35
|
-
function claudeCodeUserConfigPath() {
|
|
36
|
-
return join(homedir(), ".claude.json");
|
|
37
|
-
}
|
|
38
|
-
function codexConfigPath() {
|
|
39
|
-
return join(homedir(), ".codex", "config.toml");
|
|
40
|
-
}
|
|
41
|
-
function detectClients() {
|
|
42
|
-
const list = [];
|
|
43
|
-
const cdPath = claudeDesktopConfigPath();
|
|
44
|
-
list.push({
|
|
45
|
-
id: "claude-desktop",
|
|
46
|
-
label: "Claude Desktop",
|
|
47
|
-
detected: cdPath !== null && (existsSync(cdPath) || existsSync(dirname(cdPath))),
|
|
48
|
-
configPath: cdPath ?? "(unsupported on this OS)"
|
|
49
|
-
});
|
|
50
|
-
const ccDir = claudeCodeDir();
|
|
51
|
-
list.push({
|
|
52
|
-
id: "claude-code",
|
|
53
|
-
label: "Claude Code",
|
|
54
|
-
detected: existsSync(ccDir),
|
|
55
|
-
configPath: ccDir
|
|
56
|
-
});
|
|
57
|
-
const codexPath = codexConfigPath();
|
|
58
|
-
list.push({
|
|
59
|
-
id: "codex",
|
|
60
|
-
label: "Codex Desktop",
|
|
61
|
-
detected: existsSync(codexPath) || existsSync(dirname(codexPath)),
|
|
62
|
-
configPath: codexPath
|
|
63
|
-
});
|
|
64
|
-
return list;
|
|
65
|
-
}
|
|
66
|
-
async function prompt(question, defaultYes = true) {
|
|
67
|
-
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
68
|
-
const hint = defaultYes ? "Y/n" : "y/N";
|
|
69
|
-
const answer = (await rl.question(`${question} [${hint}] `)).trim().toLowerCase();
|
|
70
|
-
rl.close();
|
|
71
|
-
if (answer === "") return defaultYes;
|
|
72
|
-
return answer.startsWith("y");
|
|
73
|
-
}
|
|
74
|
-
async function installClaudeDesktop() {
|
|
75
|
-
const configPath = claudeDesktopConfigPath();
|
|
76
|
-
if (!configPath) return "skipped (OS not supported)";
|
|
77
|
-
let config = {};
|
|
78
|
-
try {
|
|
79
|
-
const raw = await readFile(configPath, "utf-8");
|
|
80
|
-
config = JSON.parse(raw);
|
|
81
|
-
} catch {
|
|
82
|
-
}
|
|
83
|
-
const mcpServers = config.mcpServers ?? {};
|
|
84
|
-
if (mcpServers.plaud) return "already configured";
|
|
85
|
-
config.mcpServers = { ...mcpServers, plaud: getMcpEntry() };
|
|
86
|
-
await mkdir(dirname(configPath), { recursive: true });
|
|
87
|
-
await writeFile(configPath, JSON.stringify(config, null, 2), "utf-8");
|
|
88
|
-
return "configured. Restart Claude Desktop to load the Plaud MCP.";
|
|
89
|
-
}
|
|
90
|
-
async function installClaudeCode() {
|
|
91
|
-
await writeSkillsToClaudeCode();
|
|
92
|
-
const { command, args } = getMcpEntry();
|
|
93
|
-
const serverCommand = [command, ...args].join(" ");
|
|
94
|
-
const manualCmd = `claude mcp add --scope user plaud -- ${serverCommand}`;
|
|
95
|
-
const cliCheck = spawnSync("which", ["claude"], { encoding: "utf-8" });
|
|
96
|
-
if (cliCheck.status !== 0) {
|
|
97
|
-
const configPath = claudeCodeUserConfigPath();
|
|
98
|
-
let config = {};
|
|
99
|
-
try {
|
|
100
|
-
config = JSON.parse(await readFile(configPath, "utf-8"));
|
|
101
|
-
} catch {
|
|
102
|
-
if (existsSync(configPath)) {
|
|
103
|
-
return `failed: skills written to ~/.claude/CLAUDE.md, but ~/.claude.json could not be parsed. Run manually once Claude Code CLI is available:
|
|
104
|
-
${manualCmd}`;
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
const mcpServers = config.mcpServers ?? {};
|
|
108
|
-
config.mcpServers = { ...mcpServers, plaud: { type: "stdio", command, args, env: {} } };
|
|
109
|
-
await writeFile(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
110
|
-
return "skills written to ~/.claude/CLAUDE.md. Claude Code CLI not on PATH; MCP registered in ~/.claude.json.";
|
|
111
|
-
}
|
|
112
|
-
spawnSync("claude", ["mcp", "remove", "plaud", "--scope", "user"], { encoding: "utf-8" });
|
|
113
|
-
const register = spawnSync(
|
|
114
|
-
"claude",
|
|
115
|
-
["mcp", "add", "--scope", "user", "plaud", "--", command, ...args],
|
|
116
|
-
{ encoding: "utf-8" }
|
|
117
|
-
);
|
|
118
|
-
if (register.status !== 0) {
|
|
119
|
-
const err = (register.stderr || register.stdout || "").trim();
|
|
120
|
-
return `failed: skills written to ~/.claude/CLAUDE.md, but auto-registering MCP failed (${err || "unknown"}). Run manually:
|
|
121
|
-
${manualCmd}`;
|
|
122
|
-
}
|
|
123
|
-
return "skills written to ~/.claude/CLAUDE.md and MCP registered at user scope.";
|
|
124
|
-
}
|
|
125
|
-
async function installCodex() {
|
|
126
|
-
const configPath = codexConfigPath();
|
|
127
|
-
const { command, args } = getMcpEntry();
|
|
128
|
-
const argsStr = args.map((a) => `"${a}"`).join(", ");
|
|
129
|
-
const entry = `
|
|
130
|
-
[mcp_servers.plaud]
|
|
131
|
-
command = "${command}"
|
|
132
|
-
args = [${argsStr}]
|
|
133
|
-
`;
|
|
134
|
-
let content = "";
|
|
135
|
-
try {
|
|
136
|
-
content = await readFile(configPath, "utf-8");
|
|
137
|
-
} catch {
|
|
138
|
-
}
|
|
139
|
-
if (content.includes("[mcp_servers.plaud]")) return "already configured";
|
|
140
|
-
await mkdir(dirname(configPath), { recursive: true });
|
|
141
|
-
await writeFile(configPath, content + entry, "utf-8");
|
|
142
|
-
await writeSkillsToClaudeCode();
|
|
143
|
-
const combined = await skillsCombined();
|
|
144
|
-
const copied = copyToClipboard(combined);
|
|
145
|
-
return copied ? "configured. Skills copied to clipboard \u2014 paste into Codex custom instructions, then restart." : "configured. Paste the Plaud skills into Codex custom instructions manually (see docs).";
|
|
146
|
-
}
|
|
147
|
-
var CALLBACK_PORT = 8199;
|
|
148
|
-
var LOGIN_TIMEOUT_MS = 12e4;
|
|
149
|
-
function pickIdentity(user) {
|
|
150
|
-
for (const k of ["email", "username", "name", "id"]) {
|
|
151
|
-
const v = user[k];
|
|
152
|
-
if (typeof v === "string" && v.length > 0) return v;
|
|
153
|
-
}
|
|
154
|
-
return void 0;
|
|
155
|
-
}
|
|
156
|
-
async function runLogin() {
|
|
157
|
-
const client = getClient();
|
|
158
|
-
try {
|
|
159
|
-
const existing = await client.auth.getAccessToken();
|
|
160
|
-
if (existing) {
|
|
161
|
-
try {
|
|
162
|
-
const user = await client.getCurrentUser();
|
|
163
|
-
return { status: "already-authed", who: pickIdentity(user) };
|
|
164
|
-
} catch {
|
|
165
|
-
return { status: "already-authed" };
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
} catch {
|
|
169
|
-
await client.auth.logout().catch(() => void 0);
|
|
170
|
-
}
|
|
171
|
-
const { url, codeVerifier, state } = client.auth.createAuthorizationRequest();
|
|
172
|
-
return new Promise((resolve) => {
|
|
173
|
-
const httpServer = createServer(async (req, res) => {
|
|
174
|
-
const reqUrl = new URL(req.url, `http://localhost:${CALLBACK_PORT}`);
|
|
175
|
-
if (reqUrl.pathname !== "/auth/callback") {
|
|
176
|
-
res.writeHead(404);
|
|
177
|
-
res.end();
|
|
178
|
-
return;
|
|
179
|
-
}
|
|
180
|
-
const code = reqUrl.searchParams.get("code");
|
|
181
|
-
if (!code) {
|
|
182
|
-
res.writeHead(400);
|
|
183
|
-
res.end("Missing code");
|
|
184
|
-
cleanup();
|
|
185
|
-
resolve({ status: "failed", message: "missing authorization code in callback" });
|
|
186
|
-
return;
|
|
187
|
-
}
|
|
188
|
-
try {
|
|
189
|
-
await client.auth.exchangeCode(code, codeVerifier, state);
|
|
190
|
-
res.writeHead(200, { "Content-Type": "text/html" });
|
|
191
|
-
res.end("<h1>Authentication successful!</h1><p>You can close this tab.</p>");
|
|
192
|
-
cleanup();
|
|
193
|
-
let who;
|
|
194
|
-
try {
|
|
195
|
-
const user = await client.getCurrentUser();
|
|
196
|
-
who = pickIdentity(user);
|
|
197
|
-
} catch {
|
|
198
|
-
}
|
|
199
|
-
resolve({ status: "success", who });
|
|
200
|
-
} catch (err) {
|
|
201
|
-
res.writeHead(500, { "Content-Type": "text/html" });
|
|
202
|
-
res.end("<h1>Token exchange failed</h1>");
|
|
203
|
-
cleanup();
|
|
204
|
-
resolve({ status: "failed", message: err instanceof Error ? err.message : String(err) });
|
|
205
|
-
}
|
|
206
|
-
});
|
|
207
|
-
let timeoutId;
|
|
208
|
-
function cleanup() {
|
|
209
|
-
clearTimeout(timeoutId);
|
|
210
|
-
httpServer.closeAllConnections?.();
|
|
211
|
-
httpServer.close();
|
|
212
|
-
}
|
|
213
|
-
timeoutId = setTimeout(() => {
|
|
214
|
-
cleanup();
|
|
215
|
-
resolve({ status: "timeout" });
|
|
216
|
-
}, LOGIN_TIMEOUT_MS);
|
|
217
|
-
httpServer.listen(CALLBACK_PORT, () => {
|
|
218
|
-
console.log(` if no browser opens, open this URL while this command is still running:
|
|
219
|
-
${url}`);
|
|
220
|
-
open(url).catch(() => {
|
|
221
|
-
console.log(" browser launch failed \u2014 waiting for a manual browser callback.");
|
|
222
|
-
});
|
|
223
|
-
});
|
|
224
|
-
httpServer.on("error", (err) => {
|
|
225
|
-
cleanup();
|
|
226
|
-
resolve({ status: "failed", message: `callback server error: ${err.message}` });
|
|
227
|
-
});
|
|
228
|
-
});
|
|
229
|
-
}
|
|
230
|
-
async function runInstall(opts = {}) {
|
|
231
|
-
console.log("Plaud MCP installer\n");
|
|
232
|
-
const clients = detectClients();
|
|
233
|
-
console.log("Detected AI clients:");
|
|
234
|
-
for (const c of clients) {
|
|
235
|
-
const mark = c.detected ? "\u2713" : "\xB7";
|
|
236
|
-
console.log(` ${mark} ${c.label.padEnd(16)} ${c.detected ? c.configPath : "(not detected)"}`);
|
|
237
|
-
}
|
|
238
|
-
console.log();
|
|
239
|
-
const selected = [];
|
|
240
|
-
if (opts.yes) {
|
|
241
|
-
for (const c of clients) {
|
|
242
|
-
if (c.detected) selected.push(c);
|
|
243
|
-
}
|
|
244
|
-
if (selected.length > 0) {
|
|
245
|
-
console.log(`--yes: configuring ${selected.map((c) => c.label).join(", ")} without prompting.
|
|
246
|
-
`);
|
|
247
|
-
}
|
|
248
|
-
} else {
|
|
249
|
-
for (const c of clients) {
|
|
250
|
-
if (!c.detected) continue;
|
|
251
|
-
const yes = await prompt(`Configure ${c.label}?`, true);
|
|
252
|
-
if (yes) selected.push(c);
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
if (selected.length === 0) {
|
|
256
|
-
console.log("\nNothing to configure. Exiting.");
|
|
257
|
-
process.exit(0);
|
|
258
|
-
}
|
|
259
|
-
console.log();
|
|
260
|
-
const succeeded = [];
|
|
261
|
-
for (const c of selected) {
|
|
262
|
-
process.stdout.write(`\u2192 ${c.label}... `);
|
|
263
|
-
try {
|
|
264
|
-
let result = "";
|
|
265
|
-
if (c.id === "claude-desktop") result = await installClaudeDesktop();
|
|
266
|
-
else if (c.id === "claude-code") result = await installClaudeCode();
|
|
267
|
-
else if (c.id === "codex") result = await installCodex();
|
|
268
|
-
console.log(result);
|
|
269
|
-
if (!/^(skipped|failed)/i.test(result)) succeeded.push(c);
|
|
270
|
-
} catch (err) {
|
|
271
|
-
console.log(`failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
const loginOutcome = opts.noLogin ? null : await doLoginStep(Boolean(opts.yes));
|
|
275
|
-
printNextSteps(succeeded, loginOutcome);
|
|
276
|
-
process.exit(0);
|
|
277
|
-
}
|
|
278
|
-
async function doLoginStep(nonInteractive) {
|
|
279
|
-
console.log();
|
|
280
|
-
console.log("\u2192 Authenticating with Plaud...");
|
|
281
|
-
const client = getClient();
|
|
282
|
-
try {
|
|
283
|
-
const existing = await client.auth.getAccessToken();
|
|
284
|
-
if (existing) {
|
|
285
|
-
try {
|
|
286
|
-
const user = await client.getCurrentUser();
|
|
287
|
-
const who = pickIdentity(user);
|
|
288
|
-
console.log(` already signed in${who ? ` as ${who}` : ""} \u2014 skipping OAuth.`);
|
|
289
|
-
return { status: "already-authed", who };
|
|
290
|
-
} catch {
|
|
291
|
-
console.log(" token present but user lookup failed \u2014 will re-auth.");
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
} catch {
|
|
295
|
-
}
|
|
296
|
-
if (!nonInteractive) {
|
|
297
|
-
const yes = await prompt("Log in to Plaud now? (opens browser)", true);
|
|
298
|
-
if (!yes) {
|
|
299
|
-
console.log(" skipped \u2014 you'll be prompted on first tool call after restart.");
|
|
300
|
-
return null;
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
console.log(" opening browser \u2014 click Authorize to finish.");
|
|
304
|
-
const outcome = await runLogin();
|
|
305
|
-
switch (outcome.status) {
|
|
306
|
-
case "already-authed":
|
|
307
|
-
console.log(` already signed in${outcome.who ? ` as ${outcome.who}` : ""}.`);
|
|
308
|
-
break;
|
|
309
|
-
case "success":
|
|
310
|
-
console.log(` \u2713 authenticated${outcome.who ? ` as ${outcome.who}` : ""}.`);
|
|
311
|
-
break;
|
|
312
|
-
case "timeout":
|
|
313
|
-
console.log(" \u2717 timed out after 2 minutes \u2014 you can retry with `plaud-mcp install --yes` or run `login` from your AI client.");
|
|
314
|
-
break;
|
|
315
|
-
case "failed":
|
|
316
|
-
console.log(` \u2717 failed: ${outcome.message}`);
|
|
317
|
-
break;
|
|
318
|
-
}
|
|
319
|
-
return outcome;
|
|
320
|
-
}
|
|
321
|
-
function restartLine(id) {
|
|
322
|
-
switch (id) {
|
|
323
|
-
case "claude-desktop":
|
|
324
|
-
return "Fully quit Claude Desktop (\u2318Q on macOS) and reopen it \u2014 closing the window is not enough.";
|
|
325
|
-
case "claude-code":
|
|
326
|
-
return "Exit any running Claude Code session and start a new `claude` session.";
|
|
327
|
-
case "codex":
|
|
328
|
-
return "Quit Codex Desktop and reopen it.";
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
function printNextSteps(clients, login) {
|
|
332
|
-
console.log();
|
|
333
|
-
if (clients.length === 0) {
|
|
334
|
-
console.log("No clients were configured. Nothing to do.");
|
|
335
|
-
return;
|
|
336
|
-
}
|
|
337
|
-
const authed = login?.status === "already-authed" || login?.status === "success";
|
|
338
|
-
const bar = "\u2500".repeat(60);
|
|
339
|
-
console.log(bar);
|
|
340
|
-
console.log(`\u2713 Plaud MCP installed for: ${clients.map((c) => c.label).join(", ")}`);
|
|
341
|
-
if (authed) {
|
|
342
|
-
console.log(`\u2713 Authenticated${login?.who ? ` as ${login.who}` : ""}`);
|
|
343
|
-
}
|
|
344
|
-
console.log(bar);
|
|
345
|
-
console.log();
|
|
346
|
-
if (authed) {
|
|
347
|
-
console.log("NEXT \u2014 one thing left:");
|
|
348
|
-
console.log();
|
|
349
|
-
if (clients.length === 1) {
|
|
350
|
-
console.log(` ${restartLine(clients[0].id)}`);
|
|
351
|
-
} else {
|
|
352
|
-
console.log(" Restart each configured client:");
|
|
353
|
-
for (const c of clients) {
|
|
354
|
-
console.log(` \u2022 ${c.label}: ${restartLine(c.id)}`);
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
console.log();
|
|
358
|
-
console.log(" After restart, Plaud tools are live \u2014 just ask your AI client");
|
|
359
|
-
console.log(' about your recordings (e.g. "list my recent Plaud recordings").');
|
|
360
|
-
} else {
|
|
361
|
-
console.log("NEXT \u2014 do these two things:");
|
|
362
|
-
console.log();
|
|
363
|
-
if (clients.length === 1) {
|
|
364
|
-
console.log(` 1. ${restartLine(clients[0].id)}`);
|
|
365
|
-
} else {
|
|
366
|
-
console.log(" 1. Restart each configured client:");
|
|
367
|
-
for (const c of clients) {
|
|
368
|
-
console.log(` \u2022 ${c.label}: ${restartLine(c.id)}`);
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
console.log();
|
|
372
|
-
console.log(" 2. In any chat with your AI client, type:");
|
|
373
|
-
console.log();
|
|
374
|
-
console.log(" list my recent Plaud recordings");
|
|
375
|
-
console.log();
|
|
376
|
-
console.log(" This triggers OAuth in your browser (once). Tokens are then");
|
|
377
|
-
console.log(" stored in ~/.plaud/tokens-mcp.json and refreshed automatically.");
|
|
378
|
-
}
|
|
379
|
-
console.log();
|
|
380
|
-
console.log(bar);
|
|
381
|
-
}
|
|
382
|
-
export {
|
|
383
|
-
runInstall
|
|
384
|
-
};
|