@plaud-ai/mcp 0.1.58 → 0.2.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/chunk-4QBEOJPX.js +92 -0
- package/dist/chunk-7KGB7GSZ.js +33 -0
- package/dist/chunk-MPCF6HMK.js +151 -0
- package/dist/{chunk-YMQLKZLW.js → chunk-SNSGVRCU.js} +1 -101
- package/dist/chunk-UPEENHCG.js +70 -0
- package/dist/index.js +63 -57
- package/dist/install-TU2Y3ARS.js +370 -0
- package/dist/{server-5L7BYLYR.js → server-3256GMAD.js} +5 -15
- package/dist/{setup-KGUMS567.js → setup-7JTG3C2W.js} +17 -61
- package/package.json +14 -10
- package/plugin.json +1 -1
- package/skills/plaud-browse/SKILL.md +39 -0
- package/skills/plaud-digest/SKILL.md +41 -0
- package/skills/plaud-export/SKILL.md +51 -0
- package/skills/plaud-find/SKILL.md +54 -0
- package/skills/plaud-followup/SKILL.md +59 -0
- package/skills/plaud-read/SKILL.md +51 -0
- package/skills/plaud-shared/SKILL.md +67 -0
- package/dist/chunk-7PYQFJJW.js +0 -111
- package/skills/plaud/SKILL.md +0 -40
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
// src/skills.ts
|
|
2
|
+
import { readdir, readFile } from "fs/promises";
|
|
3
|
+
import { statSync } from "fs";
|
|
4
|
+
import { join, dirname, resolve } from "path";
|
|
5
|
+
import { fileURLToPath } from "url";
|
|
6
|
+
function parseFrontmatter(raw) {
|
|
7
|
+
if (!raw.startsWith("---\n")) return { meta: {}, body: raw };
|
|
8
|
+
const end = raw.indexOf("\n---\n", 4);
|
|
9
|
+
if (end === -1) return { meta: {}, body: raw };
|
|
10
|
+
const yaml = raw.slice(4, end);
|
|
11
|
+
const body = raw.slice(end + 5);
|
|
12
|
+
const meta = {};
|
|
13
|
+
let currentKey = null;
|
|
14
|
+
for (const line of yaml.split("\n")) {
|
|
15
|
+
if (!line.trim()) continue;
|
|
16
|
+
const topMatch = line.match(/^([a-zA-Z_][\w-]*):\s*(.*)$/);
|
|
17
|
+
if (topMatch && !line.startsWith(" ")) {
|
|
18
|
+
const [, k, v] = topMatch;
|
|
19
|
+
currentKey = k;
|
|
20
|
+
if (v !== "") {
|
|
21
|
+
const unq = v.replace(/^["']|["']$/g, "");
|
|
22
|
+
meta[k] = unq;
|
|
23
|
+
currentKey = null;
|
|
24
|
+
} else {
|
|
25
|
+
meta[k] = {};
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return { meta, body };
|
|
30
|
+
}
|
|
31
|
+
function findSkillsDir() {
|
|
32
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
33
|
+
const candidates = [
|
|
34
|
+
resolve(here, "..", "skills"),
|
|
35
|
+
resolve(here, "..", "..", "skills")
|
|
36
|
+
];
|
|
37
|
+
for (const c of candidates) {
|
|
38
|
+
try {
|
|
39
|
+
if (statSync(c).isDirectory()) return c;
|
|
40
|
+
} catch {
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return candidates[0];
|
|
44
|
+
}
|
|
45
|
+
var cached = null;
|
|
46
|
+
async function loadSkills() {
|
|
47
|
+
if (cached) return cached;
|
|
48
|
+
const dir = findSkillsDir();
|
|
49
|
+
let entries;
|
|
50
|
+
try {
|
|
51
|
+
entries = await readdir(dir);
|
|
52
|
+
} catch {
|
|
53
|
+
cached = [];
|
|
54
|
+
return cached;
|
|
55
|
+
}
|
|
56
|
+
const skills = [];
|
|
57
|
+
for (const name of entries) {
|
|
58
|
+
try {
|
|
59
|
+
const skillPath = join(dir, name, "SKILL.md");
|
|
60
|
+
const raw = await readFile(skillPath, "utf-8");
|
|
61
|
+
const { meta, body } = parseFrontmatter(raw);
|
|
62
|
+
const skillName = typeof meta.name === "string" ? meta.name : name;
|
|
63
|
+
const description = typeof meta.description === "string" ? meta.description : "";
|
|
64
|
+
const version = typeof meta.version === "string" ? meta.version : "0.0.0";
|
|
65
|
+
skills.push({
|
|
66
|
+
name: skillName,
|
|
67
|
+
version,
|
|
68
|
+
description,
|
|
69
|
+
body: body.trimStart(),
|
|
70
|
+
content: raw,
|
|
71
|
+
supported: true
|
|
72
|
+
});
|
|
73
|
+
} catch {
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
skills.sort((a, b) => {
|
|
77
|
+
if (a.name === "plaud-shared") return -1;
|
|
78
|
+
if (b.name === "plaud-shared") return 1;
|
|
79
|
+
return a.name.localeCompare(b.name);
|
|
80
|
+
});
|
|
81
|
+
cached = skills;
|
|
82
|
+
return cached;
|
|
83
|
+
}
|
|
84
|
+
async function skillsCombined() {
|
|
85
|
+
const skills = await loadSkills();
|
|
86
|
+
return skills.map((s) => s.content).join("\n\n---\n\n");
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export {
|
|
90
|
+
loadSkills,
|
|
91
|
+
skillsCombined
|
|
92
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import {
|
|
2
|
+
PlaudClient
|
|
3
|
+
} from "./chunk-SNSGVRCU.js";
|
|
4
|
+
|
|
5
|
+
// src/config.ts
|
|
6
|
+
function buildExtraHeaders() {
|
|
7
|
+
const headers = {};
|
|
8
|
+
if (process.env.PLAUD_ENV) headers["x-pld-env"] = process.env.PLAUD_ENV;
|
|
9
|
+
if (process.env.PLAUD_REGION) headers["x-pld-region"] = process.env.PLAUD_REGION;
|
|
10
|
+
return headers;
|
|
11
|
+
}
|
|
12
|
+
var CONFIG = {
|
|
13
|
+
clientId: process.env.PLAUD_MCP_CLIENT_ID ?? process.env.PLAUD_CLIENT_ID ?? "client_9c501dad-8a0d-40b2-a7b0-d1cb8787f674",
|
|
14
|
+
clientSecret: process.env.PLAUD_CLIENT_SECRET ?? "",
|
|
15
|
+
redirectUri: "http://localhost:8199/auth/callback",
|
|
16
|
+
tokenFile: "tokens-mcp.json",
|
|
17
|
+
apiBase: process.env.PLAUD_API_BASE,
|
|
18
|
+
authorizationUrl: process.env.PLAUD_AUTH_URL,
|
|
19
|
+
tokenUrl: process.env.PLAUD_TOKEN_URL,
|
|
20
|
+
refreshUrl: process.env.PLAUD_REFRESH_URL,
|
|
21
|
+
extraHeaders: buildExtraHeaders()
|
|
22
|
+
};
|
|
23
|
+
var client = null;
|
|
24
|
+
function getClient() {
|
|
25
|
+
if (!client) {
|
|
26
|
+
client = new PlaudClient(CONFIG);
|
|
27
|
+
}
|
|
28
|
+
return client;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export {
|
|
32
|
+
getClient
|
|
33
|
+
};
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
// src/logger.ts
|
|
2
|
+
import pino from "pino";
|
|
3
|
+
var logger = pino(
|
|
4
|
+
{ level: process.env.LOG_LEVEL ?? "info" },
|
|
5
|
+
pino.destination(2)
|
|
6
|
+
);
|
|
7
|
+
|
|
8
|
+
// src/tools/index.ts
|
|
9
|
+
import { z } from "zod";
|
|
10
|
+
var MAX_FILTER_PAGES = 5;
|
|
11
|
+
var FILTER_PAGE_SIZE = 100;
|
|
12
|
+
function parseDate(s) {
|
|
13
|
+
if (!s) return null;
|
|
14
|
+
const d = new Date(s);
|
|
15
|
+
if (Number.isNaN(d.getTime())) return null;
|
|
16
|
+
return d.getTime();
|
|
17
|
+
}
|
|
18
|
+
function registerTools(server, client) {
|
|
19
|
+
server.tool(
|
|
20
|
+
"list_files",
|
|
21
|
+
"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.",
|
|
22
|
+
{
|
|
23
|
+
page: z.number().optional().default(1).describe("Page number (ignored when filters are set)"),
|
|
24
|
+
page_size: z.number().optional().default(20).describe("Items per page (ignored when filters are set)"),
|
|
25
|
+
query: z.string().optional().describe("Case-insensitive substring match on recording name"),
|
|
26
|
+
date_from: z.string().optional().describe("Start date inclusive, YYYY-MM-DD"),
|
|
27
|
+
date_to: z.string().optional().describe("End date inclusive, YYYY-MM-DD")
|
|
28
|
+
},
|
|
29
|
+
async ({ page, page_size, query, date_from, date_to }) => {
|
|
30
|
+
const start = Date.now();
|
|
31
|
+
const hasFilter = Boolean(query || date_from || date_to);
|
|
32
|
+
logger.info({ event: "tool_call", tool: "list_files", has_filter: hasFilter });
|
|
33
|
+
try {
|
|
34
|
+
if (!hasFilter) {
|
|
35
|
+
const result = await client.listFiles(page, page_size);
|
|
36
|
+
logger.info({ event: "tool_call_end", tool: "list_files", duration_ms: Date.now() - start });
|
|
37
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
38
|
+
}
|
|
39
|
+
const q = query?.toLowerCase();
|
|
40
|
+
const from = parseDate(date_from);
|
|
41
|
+
const toRaw = parseDate(date_to);
|
|
42
|
+
const to = toRaw !== null ? toRaw + 24 * 60 * 60 * 1e3 - 1 : null;
|
|
43
|
+
const matches = [];
|
|
44
|
+
let scanned = 0;
|
|
45
|
+
let truncated = false;
|
|
46
|
+
for (let p = 1; p <= MAX_FILTER_PAGES; p++) {
|
|
47
|
+
const pageResult = await client.listFiles(p, FILTER_PAGE_SIZE);
|
|
48
|
+
const items = pageResult.data;
|
|
49
|
+
scanned += items.length;
|
|
50
|
+
for (const item of items) {
|
|
51
|
+
if (q && !(item.name ?? "").toLowerCase().includes(q)) continue;
|
|
52
|
+
if (from !== null || to !== null) {
|
|
53
|
+
const created = parseDate(item.created_at);
|
|
54
|
+
if (created === null) continue;
|
|
55
|
+
if (from !== null && created < from) continue;
|
|
56
|
+
if (to !== null && created > to) continue;
|
|
57
|
+
}
|
|
58
|
+
matches.push(item);
|
|
59
|
+
}
|
|
60
|
+
if (items.length < FILTER_PAGE_SIZE) break;
|
|
61
|
+
if (p === MAX_FILTER_PAGES) truncated = true;
|
|
62
|
+
}
|
|
63
|
+
logger.info({ event: "tool_call_end", tool: "list_files", duration_ms: Date.now() - start, scanned, matched: matches.length, truncated });
|
|
64
|
+
return {
|
|
65
|
+
content: [{ type: "text", text: JSON.stringify({
|
|
66
|
+
data: matches,
|
|
67
|
+
scanned,
|
|
68
|
+
matched: matches.length,
|
|
69
|
+
truncated,
|
|
70
|
+
note: truncated ? `Scanned first ${MAX_FILTER_PAGES * FILTER_PAGE_SIZE} recordings; narrow filters for a complete match.` : void 0
|
|
71
|
+
}, null, 2) }]
|
|
72
|
+
};
|
|
73
|
+
} catch (err) {
|
|
74
|
+
logger.error({ event: "tool_call_error", tool: "list_files", duration_ms: Date.now() - start, error: String(err) });
|
|
75
|
+
return { content: [{ type: "text", text: `Failed to list files: ${err}` }], isError: true };
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
);
|
|
79
|
+
server.tool(
|
|
80
|
+
"get_file",
|
|
81
|
+
"Get details of a specific Plaud recording by ID",
|
|
82
|
+
{ file_id: z.string().describe("The file ID to retrieve") },
|
|
83
|
+
async ({ file_id }) => {
|
|
84
|
+
const start = Date.now();
|
|
85
|
+
logger.info({ event: "tool_call", tool: "get_file", file_id });
|
|
86
|
+
try {
|
|
87
|
+
const file = await client.getFile(file_id);
|
|
88
|
+
logger.info({ event: "tool_call_end", tool: "get_file", duration_ms: Date.now() - start });
|
|
89
|
+
return { content: [{ type: "text", text: JSON.stringify(file, null, 2) }] };
|
|
90
|
+
} catch (err) {
|
|
91
|
+
logger.error({ event: "tool_call_error", tool: "get_file", duration_ms: Date.now() - start, error: String(err) });
|
|
92
|
+
return { content: [{ type: "text", text: `Failed to get file: ${err}` }], isError: true };
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
);
|
|
96
|
+
server.tool(
|
|
97
|
+
"get_note",
|
|
98
|
+
"Fetch AI-generated notes for a Plaud recording \u2014 compact summary, action items, and key topics",
|
|
99
|
+
{ file_id: z.string().describe("The file ID to retrieve notes for") },
|
|
100
|
+
async ({ file_id }) => {
|
|
101
|
+
const start = Date.now();
|
|
102
|
+
logger.info({ event: "tool_call", tool: "get_note", file_id });
|
|
103
|
+
try {
|
|
104
|
+
const file = await client.getFile(file_id);
|
|
105
|
+
logger.info({ event: "tool_call_end", tool: "get_note", duration_ms: Date.now() - start });
|
|
106
|
+
return { content: [{ type: "text", text: JSON.stringify(file.note_list ?? [], null, 2) }] };
|
|
107
|
+
} catch (err) {
|
|
108
|
+
logger.error({ event: "tool_call_error", tool: "get_note", duration_ms: Date.now() - start, error: String(err) });
|
|
109
|
+
return { content: [{ type: "text", text: `Failed to get note: ${err}` }], isError: true };
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
);
|
|
113
|
+
server.tool(
|
|
114
|
+
"get_transcript",
|
|
115
|
+
"Fetch the full timestamped transcript with speaker attribution for a Plaud recording",
|
|
116
|
+
{ file_id: z.string().describe("The file ID to retrieve transcript for") },
|
|
117
|
+
async ({ file_id }) => {
|
|
118
|
+
const start = Date.now();
|
|
119
|
+
logger.info({ event: "tool_call", tool: "get_transcript", file_id });
|
|
120
|
+
try {
|
|
121
|
+
const file = await client.getFile(file_id);
|
|
122
|
+
logger.info({ event: "tool_call_end", tool: "get_transcript", duration_ms: Date.now() - start });
|
|
123
|
+
return { content: [{ type: "text", text: JSON.stringify(file.source_list ?? [], null, 2) }] };
|
|
124
|
+
} catch (err) {
|
|
125
|
+
logger.error({ event: "tool_call_error", tool: "get_transcript", duration_ms: Date.now() - start, error: String(err) });
|
|
126
|
+
return { content: [{ type: "text", text: `Failed to get transcript: ${err}` }], isError: true };
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
);
|
|
130
|
+
server.tool(
|
|
131
|
+
"get_current_user",
|
|
132
|
+
"Get current authenticated user info",
|
|
133
|
+
async () => {
|
|
134
|
+
const start = Date.now();
|
|
135
|
+
logger.info({ event: "tool_call", tool: "get_current_user" });
|
|
136
|
+
try {
|
|
137
|
+
const user = await client.getCurrentUser();
|
|
138
|
+
logger.info({ event: "tool_call_end", tool: "get_current_user", duration_ms: Date.now() - start });
|
|
139
|
+
return { content: [{ type: "text", text: JSON.stringify(user, null, 2) }] };
|
|
140
|
+
} catch (err) {
|
|
141
|
+
logger.error({ event: "tool_call_error", tool: "get_current_user", duration_ms: Date.now() - start, error: String(err) });
|
|
142
|
+
return { content: [{ type: "text", text: `Failed to get user info: ${err}` }], isError: true };
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export {
|
|
149
|
+
logger,
|
|
150
|
+
registerTools
|
|
151
|
+
};
|
|
@@ -229,106 +229,6 @@ var PlaudClient = class {
|
|
|
229
229
|
}
|
|
230
230
|
};
|
|
231
231
|
|
|
232
|
-
// src/logger.ts
|
|
233
|
-
import pino from "pino";
|
|
234
|
-
var logger = pino({
|
|
235
|
-
level: process.env.LOG_LEVEL ?? "info"
|
|
236
|
-
});
|
|
237
|
-
|
|
238
|
-
// src/tools/index.ts
|
|
239
|
-
import { z } from "zod";
|
|
240
|
-
function registerTools(server, client) {
|
|
241
|
-
server.tool(
|
|
242
|
-
"list_files",
|
|
243
|
-
"List Plaud recordings",
|
|
244
|
-
{
|
|
245
|
-
page: z.number().optional().default(1).describe("Page number"),
|
|
246
|
-
page_size: z.number().optional().default(20).describe("Items per page")
|
|
247
|
-
},
|
|
248
|
-
async ({ page, page_size }) => {
|
|
249
|
-
const start = Date.now();
|
|
250
|
-
logger.info({ event: "tool_call", tool: "list_files" });
|
|
251
|
-
try {
|
|
252
|
-
const result = await client.listFiles(page, page_size);
|
|
253
|
-
logger.info({ event: "tool_call_end", tool: "list_files", duration_ms: Date.now() - start });
|
|
254
|
-
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
255
|
-
} catch (err) {
|
|
256
|
-
logger.error({ event: "tool_call_error", tool: "list_files", duration_ms: Date.now() - start, error: String(err) });
|
|
257
|
-
return { content: [{ type: "text", text: `Failed to list files: ${err}` }], isError: true };
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
);
|
|
261
|
-
server.tool(
|
|
262
|
-
"get_file",
|
|
263
|
-
"Get details of a specific Plaud recording by ID",
|
|
264
|
-
{ file_id: z.string().describe("The file ID to retrieve") },
|
|
265
|
-
async ({ file_id }) => {
|
|
266
|
-
const start = Date.now();
|
|
267
|
-
logger.info({ event: "tool_call", tool: "get_file", file_id });
|
|
268
|
-
try {
|
|
269
|
-
const file = await client.getFile(file_id);
|
|
270
|
-
logger.info({ event: "tool_call_end", tool: "get_file", duration_ms: Date.now() - start });
|
|
271
|
-
return { content: [{ type: "text", text: JSON.stringify(file, null, 2) }] };
|
|
272
|
-
} catch (err) {
|
|
273
|
-
logger.error({ event: "tool_call_error", tool: "get_file", duration_ms: Date.now() - start, error: String(err) });
|
|
274
|
-
return { content: [{ type: "text", text: `Failed to get file: ${err}` }], isError: true };
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
);
|
|
278
|
-
server.tool(
|
|
279
|
-
"get_note",
|
|
280
|
-
"Fetch AI-generated notes for a Plaud recording \u2014 compact summary, action items, and key topics",
|
|
281
|
-
{ file_id: z.string().describe("The file ID to retrieve notes for") },
|
|
282
|
-
async ({ file_id }) => {
|
|
283
|
-
const start = Date.now();
|
|
284
|
-
logger.info({ event: "tool_call", tool: "get_note", file_id });
|
|
285
|
-
try {
|
|
286
|
-
const file = await client.getFile(file_id);
|
|
287
|
-
logger.info({ event: "tool_call_end", tool: "get_note", duration_ms: Date.now() - start });
|
|
288
|
-
return { content: [{ type: "text", text: JSON.stringify(file.note_list ?? [], null, 2) }] };
|
|
289
|
-
} catch (err) {
|
|
290
|
-
logger.error({ event: "tool_call_error", tool: "get_note", duration_ms: Date.now() - start, error: String(err) });
|
|
291
|
-
return { content: [{ type: "text", text: `Failed to get note: ${err}` }], isError: true };
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
);
|
|
295
|
-
server.tool(
|
|
296
|
-
"get_transcript",
|
|
297
|
-
"Fetch the full timestamped transcript with speaker attribution for a Plaud recording",
|
|
298
|
-
{ file_id: z.string().describe("The file ID to retrieve transcript for") },
|
|
299
|
-
async ({ file_id }) => {
|
|
300
|
-
const start = Date.now();
|
|
301
|
-
logger.info({ event: "tool_call", tool: "get_transcript", file_id });
|
|
302
|
-
try {
|
|
303
|
-
const file = await client.getFile(file_id);
|
|
304
|
-
logger.info({ event: "tool_call_end", tool: "get_transcript", duration_ms: Date.now() - start });
|
|
305
|
-
return { content: [{ type: "text", text: JSON.stringify(file.source_list ?? [], null, 2) }] };
|
|
306
|
-
} catch (err) {
|
|
307
|
-
logger.error({ event: "tool_call_error", tool: "get_transcript", duration_ms: Date.now() - start, error: String(err) });
|
|
308
|
-
return { content: [{ type: "text", text: `Failed to get transcript: ${err}` }], isError: true };
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
);
|
|
312
|
-
server.tool(
|
|
313
|
-
"get_current_user",
|
|
314
|
-
"Get current authenticated user info",
|
|
315
|
-
async () => {
|
|
316
|
-
const start = Date.now();
|
|
317
|
-
logger.info({ event: "tool_call", tool: "get_current_user" });
|
|
318
|
-
try {
|
|
319
|
-
const user = await client.getCurrentUser();
|
|
320
|
-
logger.info({ event: "tool_call_end", tool: "get_current_user", duration_ms: Date.now() - start });
|
|
321
|
-
return { content: [{ type: "text", text: JSON.stringify(user, null, 2) }] };
|
|
322
|
-
} catch (err) {
|
|
323
|
-
logger.error({ event: "tool_call_error", tool: "get_current_user", duration_ms: Date.now() - start, error: String(err) });
|
|
324
|
-
return { content: [{ type: "text", text: `Failed to get user info: ${err}` }], isError: true };
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
);
|
|
328
|
-
}
|
|
329
|
-
|
|
330
232
|
export {
|
|
331
|
-
PlaudClient
|
|
332
|
-
logger,
|
|
333
|
-
registerTools
|
|
233
|
+
PlaudClient
|
|
334
234
|
};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import {
|
|
2
|
+
skillsCombined
|
|
3
|
+
} from "./chunk-4QBEOJPX.js";
|
|
4
|
+
|
|
5
|
+
// src/install-utils.ts
|
|
6
|
+
import { readFile, writeFile, mkdir } from "fs/promises";
|
|
7
|
+
import { join, dirname } from "path";
|
|
8
|
+
import { homedir, platform } from "os";
|
|
9
|
+
import { spawnSync } from "child_process";
|
|
10
|
+
var SKILLS_MARKER_START = "<!-- plaud-skills:start -->";
|
|
11
|
+
var SKILLS_MARKER_END = "<!-- plaud-skills:end -->";
|
|
12
|
+
function getMcpEntry() {
|
|
13
|
+
return {
|
|
14
|
+
command: "npx",
|
|
15
|
+
args: ["-y", "@plaud-ai/mcp@latest"]
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
function copyToClipboard(content) {
|
|
19
|
+
if (platform() === "win32") {
|
|
20
|
+
return spawnSync("clip", [], { input: content }).status === 0;
|
|
21
|
+
}
|
|
22
|
+
if (platform() === "darwin") {
|
|
23
|
+
return spawnSync("pbcopy", [], { input: content }).status === 0;
|
|
24
|
+
}
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
async function writeSkillsToClaudeCode() {
|
|
28
|
+
const claudeMdPath = join(homedir(), ".claude", "CLAUDE.md");
|
|
29
|
+
const combined = await skillsCombined();
|
|
30
|
+
const block = `${SKILLS_MARKER_START}
|
|
31
|
+
${combined}
|
|
32
|
+
${SKILLS_MARKER_END}`;
|
|
33
|
+
let existing = "";
|
|
34
|
+
try {
|
|
35
|
+
existing = await readFile(claudeMdPath, "utf-8");
|
|
36
|
+
} catch {
|
|
37
|
+
}
|
|
38
|
+
if (existing.includes(SKILLS_MARKER_START)) {
|
|
39
|
+
const updated = existing.replace(
|
|
40
|
+
new RegExp(`${SKILLS_MARKER_START}[\\s\\S]*?${SKILLS_MARKER_END}`),
|
|
41
|
+
block
|
|
42
|
+
);
|
|
43
|
+
await mkdir(dirname(claudeMdPath), { recursive: true });
|
|
44
|
+
await writeFile(claudeMdPath, updated, "utf-8");
|
|
45
|
+
} else {
|
|
46
|
+
await mkdir(dirname(claudeMdPath), { recursive: true });
|
|
47
|
+
await writeFile(claudeMdPath, existing + (existing.endsWith("\n") ? "" : "\n") + block + "\n", "utf-8");
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
async function removeSkillsFromClaudeCode() {
|
|
51
|
+
const claudeMdPath = join(homedir(), ".claude", "CLAUDE.md");
|
|
52
|
+
let existing = "";
|
|
53
|
+
try {
|
|
54
|
+
existing = await readFile(claudeMdPath, "utf-8");
|
|
55
|
+
} catch {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
if (!existing.includes(SKILLS_MARKER_START)) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
const updated = existing.replace(new RegExp(`\\n?${SKILLS_MARKER_START}[\\s\\S]*?${SKILLS_MARKER_END}\\n?`), "").trimEnd();
|
|
62
|
+
await writeFile(claudeMdPath, updated ? updated + "\n" : "", "utf-8");
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export {
|
|
66
|
+
getMcpEntry,
|
|
67
|
+
copyToClipboard,
|
|
68
|
+
writeSkillsToClaudeCode,
|
|
69
|
+
removeSkillsFromClaudeCode
|
|
70
|
+
};
|
package/dist/index.js
CHANGED
|
@@ -1,58 +1,33 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
} from "./chunk-
|
|
3
|
+
getClient
|
|
4
|
+
} from "./chunk-7KGB7GSZ.js";
|
|
5
|
+
import {
|
|
6
|
+
loadSkills
|
|
7
|
+
} from "./chunk-4QBEOJPX.js";
|
|
5
8
|
import {
|
|
6
|
-
PlaudClient,
|
|
7
9
|
registerTools
|
|
8
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-MPCF6HMK.js";
|
|
11
|
+
import "./chunk-SNSGVRCU.js";
|
|
9
12
|
|
|
10
13
|
// src/index.ts
|
|
11
14
|
import { createServer } from "http";
|
|
12
15
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
13
16
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
14
17
|
import open from "open";
|
|
15
|
-
|
|
16
|
-
// src/config.ts
|
|
17
|
-
function buildExtraHeaders() {
|
|
18
|
-
const headers = {};
|
|
19
|
-
if (process.env.PLAUD_ENV) headers["x-pld-env"] = process.env.PLAUD_ENV;
|
|
20
|
-
if (process.env.PLAUD_REGION) headers["x-pld-region"] = process.env.PLAUD_REGION;
|
|
21
|
-
return headers;
|
|
22
|
-
}
|
|
23
|
-
var CONFIG = {
|
|
24
|
-
clientId: process.env.PLAUD_MCP_CLIENT_ID ?? process.env.PLAUD_CLIENT_ID ?? "client_9c501dad-8a0d-40b2-a7b0-d1cb8787f674",
|
|
25
|
-
clientSecret: process.env.PLAUD_CLIENT_SECRET ?? "",
|
|
26
|
-
redirectUri: "http://localhost:8199/auth/callback",
|
|
27
|
-
tokenFile: "tokens-mcp.json",
|
|
28
|
-
apiBase: process.env.PLAUD_API_BASE,
|
|
29
|
-
authorizationUrl: process.env.PLAUD_AUTH_URL,
|
|
30
|
-
tokenUrl: process.env.PLAUD_TOKEN_URL,
|
|
31
|
-
refreshUrl: process.env.PLAUD_REFRESH_URL,
|
|
32
|
-
extraHeaders: buildExtraHeaders()
|
|
33
|
-
};
|
|
34
|
-
var client = null;
|
|
35
|
-
function getClient() {
|
|
36
|
-
if (!client) {
|
|
37
|
-
client = new PlaudClient(CONFIG);
|
|
38
|
-
}
|
|
39
|
-
return client;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// src/index.ts
|
|
43
18
|
var server = new McpServer({
|
|
44
19
|
name: "plaud",
|
|
45
|
-
version: "0.
|
|
20
|
+
version: "0.2.0"
|
|
46
21
|
});
|
|
47
22
|
var CALLBACK_PORT = 8199;
|
|
48
23
|
var LOGIN_TIMEOUT_MS = 12e4;
|
|
49
24
|
server.tool("login", "Log in, sign in, or authenticate with Plaud account via OAuth (opens browser)", async () => {
|
|
50
|
-
const
|
|
51
|
-
const existingToken = await
|
|
25
|
+
const client = getClient();
|
|
26
|
+
const existingToken = await client.auth.getAccessToken();
|
|
52
27
|
if (existingToken) {
|
|
53
28
|
return { content: [{ type: "text", text: "Already logged in." }] };
|
|
54
29
|
}
|
|
55
|
-
const { url, codeVerifier, state } =
|
|
30
|
+
const { url, codeVerifier, state } = client.auth.createAuthorizationRequest();
|
|
56
31
|
return new Promise((resolve) => {
|
|
57
32
|
const httpServer = createServer(async (req, res) => {
|
|
58
33
|
const reqUrl = new URL(req.url, `http://localhost:${CALLBACK_PORT}`);
|
|
@@ -73,7 +48,7 @@ server.tool("login", "Log in, sign in, or authenticate with Plaud account via OA
|
|
|
73
48
|
return;
|
|
74
49
|
}
|
|
75
50
|
try {
|
|
76
|
-
await
|
|
51
|
+
await client.auth.exchangeCode(code, codeVerifier, state);
|
|
77
52
|
res.writeHead(200, { "Content-Type": "text/html" });
|
|
78
53
|
res.end("<h1>Authentication successful!</h1><p>You can close this tab.</p>");
|
|
79
54
|
cleanup();
|
|
@@ -125,56 +100,87 @@ ${url}` }],
|
|
|
125
100
|
});
|
|
126
101
|
registerTools(server, getClient());
|
|
127
102
|
server.tool("logout", "Log out, sign out, revoke authorization, and disconnect from Plaud account", async () => {
|
|
128
|
-
const
|
|
129
|
-
const existingToken = await
|
|
103
|
+
const client = getClient();
|
|
104
|
+
const existingToken = await client.auth.getAccessToken();
|
|
130
105
|
if (!existingToken) {
|
|
131
106
|
return { content: [{ type: "text", text: "Already logged out." }] };
|
|
132
107
|
}
|
|
133
108
|
try {
|
|
134
|
-
await
|
|
109
|
+
await client.revokeCurrentUser();
|
|
135
110
|
} catch {
|
|
136
111
|
}
|
|
137
|
-
await
|
|
112
|
+
await client.auth.logout();
|
|
138
113
|
return {
|
|
139
114
|
content: [{ type: "text", text: "Logged out and revoked authorization." }]
|
|
140
115
|
};
|
|
141
116
|
});
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
117
|
+
async function registerSkillPrompts() {
|
|
118
|
+
const skills = await loadSkills();
|
|
119
|
+
for (const skill of skills) {
|
|
120
|
+
server.prompt(skill.name, skill.description, () => ({
|
|
121
|
+
messages: [{ role: "user", content: { type: "text", text: skill.content } }]
|
|
122
|
+
}));
|
|
123
|
+
}
|
|
146
124
|
}
|
|
147
125
|
async function main() {
|
|
148
|
-
|
|
149
|
-
|
|
126
|
+
const sub = process.argv[2];
|
|
127
|
+
const sub2 = process.argv[3];
|
|
128
|
+
if (sub === "install") {
|
|
129
|
+
const { runInstall } = await import("./install-TU2Y3ARS.js");
|
|
130
|
+
const args = process.argv.slice(3);
|
|
131
|
+
const yes = args.some((a) => a === "--yes" || a === "-y");
|
|
132
|
+
const noLogin = args.some((a) => a === "--no-login");
|
|
133
|
+
await runInstall({ yes, noLogin });
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
if (sub === "clean-plugin") {
|
|
137
|
+
const { runCleanPlugin } = await import("./setup-7JTG3C2W.js");
|
|
150
138
|
await runCleanPlugin();
|
|
151
139
|
return;
|
|
152
140
|
}
|
|
153
|
-
if (
|
|
154
|
-
const { runSetupCodex } = await import("./setup-
|
|
141
|
+
if (sub === "setup" && sub2 === "codex") {
|
|
142
|
+
const { runSetupCodex } = await import("./setup-7JTG3C2W.js");
|
|
155
143
|
await runSetupCodex();
|
|
156
144
|
return;
|
|
157
145
|
}
|
|
158
|
-
if (
|
|
159
|
-
const { runUnsetupCodex } = await import("./setup-
|
|
146
|
+
if (sub === "unsetup" && sub2 === "codex") {
|
|
147
|
+
const { runUnsetupCodex } = await import("./setup-7JTG3C2W.js");
|
|
160
148
|
await runUnsetupCodex();
|
|
161
149
|
return;
|
|
162
150
|
}
|
|
163
|
-
if (
|
|
164
|
-
const { runSetup } = await import("./setup-
|
|
151
|
+
if (sub === "setup") {
|
|
152
|
+
const { runSetup } = await import("./setup-7JTG3C2W.js");
|
|
165
153
|
await runSetup();
|
|
166
154
|
return;
|
|
167
155
|
}
|
|
168
|
-
if (
|
|
169
|
-
const { runUnsetup } = await import("./setup-
|
|
156
|
+
if (sub === "unsetup") {
|
|
157
|
+
const { runUnsetup } = await import("./setup-7JTG3C2W.js");
|
|
170
158
|
await runUnsetup();
|
|
171
159
|
return;
|
|
172
160
|
}
|
|
173
|
-
if (
|
|
174
|
-
const { startHttpServer } = await import("./server-
|
|
161
|
+
if (sub === "http") {
|
|
162
|
+
const { startHttpServer } = await import("./server-3256GMAD.js");
|
|
175
163
|
startHttpServer();
|
|
176
164
|
return;
|
|
177
165
|
}
|
|
166
|
+
if (sub === "--help" || sub === "-h") {
|
|
167
|
+
console.log(`plaud-mcp \u2014 Plaud MCP server
|
|
168
|
+
|
|
169
|
+
Usage:
|
|
170
|
+
plaud-mcp start MCP over stdio
|
|
171
|
+
plaud-mcp install detect AI clients, configure them, and run OAuth inline (recommended)
|
|
172
|
+
plaud-mcp install --yes non-interactive; auto-configure every detected client (for agent-driven install)
|
|
173
|
+
plaud-mcp install --no-login skip the OAuth step (for CI / headless setup)
|
|
174
|
+
plaud-mcp http start HTTP MCP server (advanced)
|
|
175
|
+
plaud-mcp setup [deprecated] alias for install (Claude Desktop only)
|
|
176
|
+
plaud-mcp setup codex [deprecated] alias for install (Codex only)
|
|
177
|
+
plaud-mcp unsetup remove Plaud from Claude Desktop
|
|
178
|
+
plaud-mcp unsetup codex remove Plaud from Codex Desktop
|
|
179
|
+
plaud-mcp clean-plugin clear Claude Code plugin cache
|
|
180
|
+
`);
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
await registerSkillPrompts();
|
|
178
184
|
const transport = new StdioServerTransport();
|
|
179
185
|
await server.connect(transport);
|
|
180
186
|
}
|