@plaud-ai/mcp 0.1.32 → 0.1.58

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.
@@ -1,33 +1,3 @@
1
- var __create = Object.create;
2
- var __defProp = Object.defineProperty;
3
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
- var __getOwnPropNames = Object.getOwnPropertyNames;
5
- var __getProtoOf = Object.getPrototypeOf;
6
- var __hasOwnProp = Object.prototype.hasOwnProperty;
7
- var __commonJS = (cb, mod) => function __require() {
8
- return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
9
- };
10
- var __export = (target, all) => {
11
- for (var name in all)
12
- __defProp(target, name, { get: all[name], enumerable: true });
13
- };
14
- var __copyProps = (to, from, except, desc) => {
15
- if (from && typeof from === "object" || typeof from === "function") {
16
- for (let key of __getOwnPropNames(from))
17
- if (!__hasOwnProp.call(to, key) && key !== except)
18
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
19
- }
20
- return to;
21
- };
22
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
23
- // If the importer is in node compatibility mode or this is not an ESM
24
- // file that has been converted to a CommonJS file using a Babel-
25
- // compatible transform (i.e. "__esModule" has not been set), then set
26
- // "default" to the CommonJS "module.exports" for node compatibility.
27
- isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
28
- mod
29
- ));
30
-
31
1
  // src/skills.ts
32
2
  var SKILLS = [
33
3
  {
@@ -136,9 +106,6 @@ var ACTIVE_SKILLS = SKILLS.filter((s) => s.supported !== false);
136
106
  var SKILLS_COMBINED = ACTIVE_SKILLS.map((s) => s.content).join("\n\n---\n\n");
137
107
 
138
108
  export {
139
- __commonJS,
140
- __export,
141
- __toESM,
142
109
  ACTIVE_SKILLS,
143
110
  SKILLS_COMBINED
144
111
  };
@@ -0,0 +1,334 @@
1
+ // ../shared/dist/oauth.js
2
+ import { randomBytes, createHash } from "crypto";
3
+
4
+ // ../shared/dist/token-store.js
5
+ import { readFile, writeFile, mkdir, rm } from "fs/promises";
6
+ import { join } from "path";
7
+ import { homedir } from "os";
8
+ var TokenStore = class {
9
+ configDir;
10
+ tokenPath;
11
+ constructor(filename = "tokens.json") {
12
+ this.configDir = join(homedir(), ".plaud");
13
+ this.tokenPath = join(this.configDir, filename);
14
+ }
15
+ async save(tokenSet) {
16
+ await mkdir(this.configDir, { recursive: true });
17
+ await writeFile(this.tokenPath, JSON.stringify(tokenSet, null, 2), "utf-8");
18
+ }
19
+ async load() {
20
+ try {
21
+ const data = await readFile(this.tokenPath, "utf-8");
22
+ return JSON.parse(data);
23
+ } catch {
24
+ return null;
25
+ }
26
+ }
27
+ async clear() {
28
+ try {
29
+ await rm(this.tokenPath);
30
+ } catch {
31
+ }
32
+ }
33
+ };
34
+
35
+ // ../shared/dist/oauth.js
36
+ var DEFAULT_AUTHORIZATION_URL = "https://web.plaud.ai/platform/oauth";
37
+ var DEFAULT_TOKEN_URL = "https://platform.plaud.ai/developer/api/oauth/third-party/access-token";
38
+ var DEFAULT_REFRESH_URL = "https://platform.plaud.ai/developer/api/oauth/third-party/access-token/refresh";
39
+ function generateCodeVerifier() {
40
+ return randomBytes(32).toString("base64url");
41
+ }
42
+ function generateCodeChallenge(verifier) {
43
+ return createHash("sha256").update(verifier).digest("base64url");
44
+ }
45
+ function generateState() {
46
+ return randomBytes(16).toString("base64url");
47
+ }
48
+ var OAuth = class {
49
+ config;
50
+ tokenStore;
51
+ authorizationUrl;
52
+ tokenUrl;
53
+ refreshUrl;
54
+ constructor(config) {
55
+ this.config = config;
56
+ this.tokenStore = new TokenStore(config.tokenFile);
57
+ this.authorizationUrl = config.authorizationUrl ?? DEFAULT_AUTHORIZATION_URL;
58
+ this.tokenUrl = config.tokenUrl ?? DEFAULT_TOKEN_URL;
59
+ this.refreshUrl = config.refreshUrl ?? DEFAULT_REFRESH_URL;
60
+ }
61
+ createAuthorizationRequest() {
62
+ const codeVerifier = generateCodeVerifier();
63
+ const codeChallenge = generateCodeChallenge(codeVerifier);
64
+ const state = generateState();
65
+ const params = new URLSearchParams({
66
+ client_id: this.config.clientId,
67
+ redirect_uri: this.config.redirectUri,
68
+ response_type: "code",
69
+ code_challenge: codeChallenge,
70
+ code_challenge_method: "S256",
71
+ state
72
+ });
73
+ return {
74
+ url: `${this.authorizationUrl}?${params.toString()}`,
75
+ codeVerifier,
76
+ state
77
+ };
78
+ }
79
+ /**
80
+ * @deprecated Use createAuthorizationRequest() for PKCE flow
81
+ */
82
+ getAuthorizationUrl() {
83
+ return this.createAuthorizationRequest().url;
84
+ }
85
+ async exchangeCode(code, codeVerifier, state) {
86
+ const basicAuth = Buffer.from(`${this.config.clientId}:${this.config.clientSecret}`).toString("base64");
87
+ const body = {
88
+ code,
89
+ redirect_uri: this.config.redirectUri
90
+ };
91
+ if (codeVerifier) {
92
+ body.code_verifier = codeVerifier;
93
+ }
94
+ if (state) {
95
+ body.state = state;
96
+ }
97
+ const res = await fetch(this.tokenUrl, {
98
+ method: "POST",
99
+ headers: {
100
+ "Content-Type": "application/x-www-form-urlencoded",
101
+ Accept: "application/json",
102
+ Authorization: `Basic ${basicAuth}`,
103
+ ...this.config.extraHeaders
104
+ },
105
+ body: new URLSearchParams(body)
106
+ });
107
+ if (!res.ok) {
108
+ throw new Error(`Token exchange failed: ${res.status} ${await res.text()}`);
109
+ }
110
+ const data = await res.json();
111
+ const tokenSet = {
112
+ access_token: data.access_token,
113
+ refresh_token: data.refresh_token,
114
+ token_type: data.token_type ?? "Bearer",
115
+ expires_at: data.expires_in ? Date.now() + data.expires_in * 1e3 : void 0
116
+ };
117
+ await this.tokenStore.save(tokenSet);
118
+ return tokenSet;
119
+ }
120
+ async getAccessToken() {
121
+ const tokenSet = await this.tokenStore.load();
122
+ if (!tokenSet)
123
+ return null;
124
+ if (tokenSet.expires_at && Date.now() > tokenSet.expires_at - 6e4) {
125
+ if (tokenSet.refresh_token) {
126
+ try {
127
+ const refreshed = await this.refresh(tokenSet.refresh_token);
128
+ return refreshed.access_token;
129
+ } catch {
130
+ return null;
131
+ }
132
+ }
133
+ return null;
134
+ }
135
+ return tokenSet.access_token;
136
+ }
137
+ async refresh(refreshToken) {
138
+ const res = await fetch(this.refreshUrl, {
139
+ method: "POST",
140
+ headers: {
141
+ "Content-Type": "application/x-www-form-urlencoded",
142
+ Accept: "application/json",
143
+ ...this.config.extraHeaders
144
+ },
145
+ body: new URLSearchParams({
146
+ refresh_token: refreshToken
147
+ })
148
+ });
149
+ if (!res.ok) {
150
+ const body = await res.text();
151
+ throw new Error(`Token refresh failed: ${res.status} ${body}`);
152
+ }
153
+ const data = await res.json();
154
+ const tokenSet = {
155
+ access_token: data.access_token,
156
+ refresh_token: data.refresh_token ?? refreshToken,
157
+ token_type: data.token_type ?? "Bearer",
158
+ expires_at: data.expires_in ? Date.now() + data.expires_in * 1e3 : void 0
159
+ };
160
+ await this.tokenStore.save(tokenSet);
161
+ return tokenSet;
162
+ }
163
+ async logout() {
164
+ await this.tokenStore.clear();
165
+ }
166
+ };
167
+
168
+ // ../shared/dist/client.js
169
+ var DEFAULT_API_BASE = "https://platform.plaud.ai/developer/api";
170
+ var PlaudClient = class {
171
+ oauth;
172
+ apiBase;
173
+ extraHeaders;
174
+ staticToken;
175
+ constructor(config) {
176
+ this.oauth = new OAuth(config);
177
+ this.apiBase = config.apiBase ?? DEFAULT_API_BASE;
178
+ this.extraHeaders = config.extraHeaders ?? {};
179
+ this.staticToken = config.staticToken;
180
+ }
181
+ get auth() {
182
+ return this.oauth;
183
+ }
184
+ async request(path, init) {
185
+ const token = this.staticToken ?? await this.oauth.getAccessToken();
186
+ if (!token) {
187
+ throw new Error("Not authenticated. Please login first.");
188
+ }
189
+ const url = `${this.apiBase}${path}`;
190
+ const method = init?.method ?? "GET";
191
+ const headers = {
192
+ Authorization: `Bearer ${token}`,
193
+ Accept: "application/json",
194
+ ...this.extraHeaders,
195
+ ...init?.headers
196
+ };
197
+ const res = await fetch(url, { ...init, headers });
198
+ if (!res.ok) {
199
+ const body = await res.text();
200
+ if (res.status === 422) {
201
+ try {
202
+ const parsed = JSON.parse(body);
203
+ const messages = parsed.detail.map((d) => `${d.loc.at(-1)}: ${d.msg}`).join("; ");
204
+ throw new Error(messages);
205
+ } catch (e) {
206
+ if (e instanceof SyntaxError)
207
+ throw new Error(`API error: ${res.status} ${res.statusText}`);
208
+ throw e;
209
+ }
210
+ }
211
+ throw new Error(`API error: ${res.status} ${res.statusText}`);
212
+ }
213
+ const json = await res.json();
214
+ return json;
215
+ }
216
+ async getCurrentUser() {
217
+ return this.request("/open/third-party/users/current");
218
+ }
219
+ async revokeCurrentUser() {
220
+ await this.request("/open/third-party/users/current/revoke", {
221
+ method: "POST"
222
+ });
223
+ }
224
+ async listFiles(page = 1, pageSize = 20) {
225
+ return this.request(`/open/third-party/files/?page=${page}&page_size=${pageSize}`);
226
+ }
227
+ async getFile(fileId) {
228
+ return this.request(`/open/third-party/files/${fileId}`);
229
+ }
230
+ };
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
+ export {
331
+ PlaudClient,
332
+ logger,
333
+ registerTools
334
+ };