@plaud-ai/mcp 0.3.10 → 0.3.11

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,14 +1,15 @@
1
1
  import {
2
2
  capture,
3
3
  classifyError,
4
+ describeError,
4
5
  loadBlockContent,
5
6
  localDayEnd,
6
7
  localDayStart,
7
8
  parseApiTimestamp
8
- } from "./chunk-5NWKLF3V.js";
9
+ } from "./chunk-VUPXBO2J.js";
9
10
  import {
10
11
  logger
11
- } from "./chunk-NPCCDRWQ.js";
12
+ } from "./chunk-OEZV5MA4.js";
12
13
  import {
13
14
  mcpToolCalls,
14
15
  mcpToolDuration
@@ -20,6 +21,7 @@ import { randomUUID } from "crypto";
20
21
  var toolHooks = null;
21
22
  var MAX_FILTER_PAGES = 5;
22
23
  var FILTER_PAGE_SIZE = 100;
24
+ var MIN_PAGE_SIZE = 10;
23
25
  var TRANSCRIPT_PAGE_SIZE = 50;
24
26
  var TRANSCRIPT_BLOCKS = ["transaction", "outline", "transaction_polish", "mark_memo"];
25
27
  var BLOCK_ITEMS_KEY = { mark_memo: "marks" };
@@ -38,7 +40,7 @@ async function resolveNotes(noteList) {
38
40
  } catch (err) {
39
41
  return {
40
42
  ...note,
41
- data_content_error: err instanceof Error ? err.message : "Failed to fetch content from data_link"
43
+ data_content_error: err ? describeError(err) : "Failed to fetch content from data_link"
42
44
  };
43
45
  }
44
46
  })
@@ -114,8 +116,13 @@ function registerTools(server, client, hooks) {
114
116
  openWorldHint: true
115
117
  },
116
118
  inputSchema: {
117
- page: z.number().optional().default(1).describe("Page number (ignored when filters are set)"),
118
- page_size: z.number().optional().default(20).describe("Items per page (ignored when filters are set)"),
119
+ // Advertise the lower bound instead of making clients discover it:
120
+ // the API rejects page_size < 10 with a 422, and an unconstrained
121
+ // schema meant they learned that from a failed call (#670875).
122
+ // Deliberately NO upper bound — the API's real ceiling is unverified,
123
+ // and capping here would newly reject page sizes it may well accept.
124
+ page: z.number().int().min(1).optional().default(1).describe("Page number, 1-based (ignored when filters are set)"),
125
+ page_size: z.number().int().min(MIN_PAGE_SIZE).optional().default(20).describe(`Items per page, minimum ${MIN_PAGE_SIZE} (ignored when filters are set)`),
119
126
  query: z.string().optional().describe("Case-insensitive substring match on recording name"),
120
127
  date_from: z.string().optional().describe("Start date inclusive, YYYY-MM-DD, interpreted in the server's timezone"),
121
128
  date_to: z.string().optional().describe("End date inclusive, YYYY-MM-DD, interpreted in the server's timezone")
@@ -169,17 +176,17 @@ function registerTools(server, client, hooks) {
169
176
  };
170
177
  } catch (err) {
171
178
  recordToolMetric("list_files", "error", Date.now() - start, requestId, {}, err);
172
- logger.error({ event: "tool_call_error", tool: "list_files", duration_ms: Date.now() - start, error: String(err) });
173
- return { content: [{ type: "text", text: `Failed to list files: ${err}` }], isError: true };
179
+ logger.error({ event: "tool_call_error", tool: "list_files", duration_ms: Date.now() - start, error: describeError(err) });
180
+ return { content: [{ type: "text", text: `Failed to list files: ${describeError(err)}` }], isError: true };
174
181
  }
175
182
  }
176
183
  );
177
184
  server.registerTool(
178
185
  "get_file",
179
186
  {
180
- description: "Get details of a specific Plaud recording by ID",
187
+ description: "Get the full record for a Plaud recording: metadata, the audio download URL, and an inventory of its transcript blocks and notes. This is the tool that returns audio \u2014 `presigned_url` is a signed link to the recording's audio file, valid 24h, and is how you download it or hand the user a download link. Call this whenever the user asks for the audio, the download link, or the recording file itself; use get_transcript or get_note to read transcript or note bodies instead.",
181
188
  annotations: {
182
- title: "Get recording details",
189
+ title: "Get recording details and audio download URL",
183
190
  readOnlyHint: true,
184
191
  destructiveHint: false,
185
192
  openWorldHint: true
@@ -214,8 +221,8 @@ Note: note_list ${linkedNotes.map((t) => `\`${t}\``).join(", ")} returned an emp
214
221
  return { content: [{ type: "text", text }] };
215
222
  } catch (err) {
216
223
  recordToolMetric("get_file", "error", Date.now() - start, requestId, { file_id }, err);
217
- logger.error({ event: "tool_call_error", tool: "get_file", duration_ms: Date.now() - start, error: String(err) });
218
- return { content: [{ type: "text", text: `Failed to get file: ${err}` }], isError: true };
224
+ logger.error({ event: "tool_call_error", tool: "get_file", duration_ms: Date.now() - start, error: describeError(err) });
225
+ return { content: [{ type: "text", text: `Failed to get file: ${describeError(err)}` }], isError: true };
219
226
  }
220
227
  }
221
228
  );
@@ -243,8 +250,8 @@ Note: note_list ${linkedNotes.map((t) => `\`${t}\``).join(", ")} returned an emp
243
250
  return { content: [{ type: "text", text: JSON.stringify(notes, null, 2) }] };
244
251
  } catch (err) {
245
252
  recordToolMetric("get_note", "error", Date.now() - start, requestId, { file_id }, err);
246
- logger.error({ event: "tool_call_error", tool: "get_note", duration_ms: Date.now() - start, error: String(err) });
247
- return { content: [{ type: "text", text: `Failed to get note: ${err}` }], isError: true };
253
+ logger.error({ event: "tool_call_error", tool: "get_note", duration_ms: Date.now() - start, error: describeError(err) });
254
+ return { content: [{ type: "text", text: `Failed to get note: ${describeError(err)}` }], isError: true };
248
255
  }
249
256
  }
250
257
  );
@@ -337,8 +344,8 @@ Note: note_list ${linkedNotes.map((t) => `\`${t}\``).join(", ")} returned an emp
337
344
  return { content: [{ type: "text", text: JSON.stringify(payload, null, 2) }] };
338
345
  } catch (err) {
339
346
  recordToolMetric("get_transcript", "error", Date.now() - start, requestId, { file_id }, err);
340
- logger.error({ event: "tool_call_error", tool: "get_transcript", duration_ms: Date.now() - start, error: String(err) });
341
- return { content: [{ type: "text", text: `Failed to get transcript: ${err}` }], isError: true };
347
+ logger.error({ event: "tool_call_error", tool: "get_transcript", duration_ms: Date.now() - start, error: describeError(err) });
348
+ return { content: [{ type: "text", text: `Failed to get transcript: ${describeError(err)}` }], isError: true };
342
349
  }
343
350
  }
344
351
  );
@@ -364,8 +371,8 @@ Note: note_list ${linkedNotes.map((t) => `\`${t}\``).join(", ")} returned an emp
364
371
  return { content: [{ type: "text", text: JSON.stringify(user, null, 2) }] };
365
372
  } catch (err) {
366
373
  recordToolMetric("get_current_user", "error", Date.now() - start, requestId, {}, err);
367
- logger.error({ event: "tool_call_error", tool: "get_current_user", duration_ms: Date.now() - start, error: String(err) });
368
- return { content: [{ type: "text", text: `Failed to get user info: ${err}` }], isError: true };
374
+ logger.error({ event: "tool_call_error", tool: "get_current_user", duration_ms: Date.now() - start, error: describeError(err) });
375
+ return { content: [{ type: "text", text: `Failed to get user info: ${describeError(err)}` }], isError: true };
369
376
  }
370
377
  }
371
378
  );
@@ -0,0 +1,20 @@
1
+ // src/logger.ts
2
+ import pino from "pino";
3
+ import { trace } from "@opentelemetry/api";
4
+ var logger = pino(
5
+ {
6
+ level: process.env.LOG_LEVEL ?? "info",
7
+ // Tempo tracing (SOP §5⑤): stamp trace_id/span_id on every log line so logs
8
+ // can be found in OpenSearch by trace_id. No-op when no span is active
9
+ // (stdio mode, or OTEL_SDK_DISABLED=true).
10
+ mixin() {
11
+ const ctx = trace.getActiveSpan()?.spanContext();
12
+ return ctx ? { trace_id: ctx.traceId, span_id: ctx.spanId } : {};
13
+ }
14
+ },
15
+ pino.destination(2)
16
+ );
17
+
18
+ export {
19
+ logger
20
+ };
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  PlaudClient,
3
3
  capture
4
- } from "./chunk-5NWKLF3V.js";
4
+ } from "./chunk-VUPXBO2J.js";
5
5
  import {
6
6
  httpClientDuration,
7
7
  httpClientRequests
@@ -32,6 +32,38 @@ function classifyError(err) {
32
32
  return "client_error";
33
33
  return "unknown";
34
34
  }
35
+ function isTransportError(err) {
36
+ const type = classifyError(err);
37
+ return type === "network" || type === "timeout" || type === "server_error";
38
+ }
39
+ var MAX_CAUSE_DEPTH = 5;
40
+ var MAX_DESCRIPTION_LENGTH = 400;
41
+ function summarizeError(err) {
42
+ if (!(err instanceof Error))
43
+ return String(err ?? "unknown error");
44
+ const code = err.code;
45
+ const suffix = typeof code === "string" && !err.message.includes(code) ? ` (${code})` : "";
46
+ return `${err.name}: ${err.message}${suffix}`;
47
+ }
48
+ function describeError(err) {
49
+ const chain = [];
50
+ const seen = /* @__PURE__ */ new Set();
51
+ let current = err;
52
+ for (let depth = 0; depth < MAX_CAUSE_DEPTH && current && !seen.has(current); depth++) {
53
+ seen.add(current);
54
+ chain.push(summarizeError(current));
55
+ const aggregated = current instanceof AggregateError ? current.errors : void 0;
56
+ if (aggregated && aggregated.length > 0) {
57
+ chain.push(aggregated.map(summarizeError).join("; "));
58
+ break;
59
+ }
60
+ current = current instanceof Error ? current.cause : void 0;
61
+ }
62
+ const text = chain.join(" | cause: ");
63
+ if (text.length === 0)
64
+ return "unknown error";
65
+ return text.length > MAX_DESCRIPTION_LENGTH ? `${text.slice(0, MAX_DESCRIPTION_LENGTH - 1)}\u2026` : text;
66
+ }
35
67
  function oauthCallbackErrorType(status) {
36
68
  switch (status) {
37
69
  case "denied":
@@ -176,6 +208,21 @@ var OAuth = class {
176
208
  await this.tokenStore.save(tokenSet);
177
209
  return tokenSet;
178
210
  }
211
+ /** Are there stored credentials? Local-only — never touches the network, so
212
+ * logout can tell "nothing to do" from "cannot reach Plaud". */
213
+ async hasStoredToken() {
214
+ return await this.tokenStore.load() !== null;
215
+ }
216
+ /**
217
+ * The current access token, refreshing it first when it is about to expire.
218
+ *
219
+ * `null` means genuinely unauthenticated: nothing stored, no refresh token, or
220
+ * the refresh token was rejected. A transport failure instead THROWS — it says
221
+ * nothing about the credentials, and reporting it as unauthenticated is what
222
+ * pushed #670875's reporter into a re-login loop (and would have let callers
223
+ * delete a perfectly good token file). Callers that only want to know whether
224
+ * we are logged in should use `isTransportError` to tell the two apart.
225
+ */
179
226
  async getAccessToken() {
180
227
  const tokenSet = await this.tokenStore.load();
181
228
  if (!tokenSet)
@@ -185,8 +232,10 @@ var OAuth = class {
185
232
  try {
186
233
  const refreshed = await this.refresh(tokenSet.refresh_token);
187
234
  return refreshed.access_token;
188
- } catch {
189
- return null;
235
+ } catch (err) {
236
+ if (!isTransportError(err))
237
+ return null;
238
+ throw err;
190
239
  }
191
240
  }
192
241
  return null;
@@ -312,6 +361,46 @@ var PlaudClient = class {
312
361
  }
313
362
  };
314
363
 
364
+ // ../shared/dist/proxy.js
365
+ var PROXY_ENV_VARS = [
366
+ "HTTP_PROXY",
367
+ "http_proxy",
368
+ "HTTPS_PROXY",
369
+ "https_proxy",
370
+ "ALL_PROXY",
371
+ "all_proxy"
372
+ ];
373
+ function proxyEnvVars(env = process.env) {
374
+ return PROXY_ENV_VARS.filter((key) => (env[key] ?? "").trim().length > 0);
375
+ }
376
+ async function installProxyDispatcher(env = process.env) {
377
+ const active = proxyEnvVars(env);
378
+ if (active.length === 0)
379
+ return null;
380
+ try {
381
+ const { EnvHttpProxyAgent, setGlobalDispatcher } = await import("undici");
382
+ setGlobalDispatcher(withoutExperimentalWarning(() => new EnvHttpProxyAgent()));
383
+ return active;
384
+ } catch {
385
+ return null;
386
+ }
387
+ }
388
+ function withoutExperimentalWarning(build) {
389
+ const original = process.emitWarning;
390
+ process.emitWarning = ((warning, ...rest) => {
391
+ const [first, second] = rest;
392
+ const code = typeof first === "object" && first !== null ? first.code : second;
393
+ if (code === "UNDICI-EHPA")
394
+ return;
395
+ return original.call(process, warning, ...rest);
396
+ });
397
+ try {
398
+ return build();
399
+ } finally {
400
+ process.emitWarning = original;
401
+ }
402
+ }
403
+
315
404
  // ../shared/dist/oauth-callback-server.js
316
405
  import { createServer } from "http";
317
406
  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>';
@@ -905,9 +994,12 @@ function extractIdentity(user) {
905
994
 
906
995
  export {
907
996
  classifyError,
997
+ isTransportError,
998
+ describeError,
908
999
  oauthCallbackErrorType,
909
1000
  clientUserIdFromAccessToken,
910
1001
  PlaudClient,
1002
+ installProxyDispatcher,
911
1003
  runOAuthCallback,
912
1004
  loadBlockContent,
913
1005
  parseApiTimestamp,
package/dist/index.js CHANGED
@@ -1,28 +1,32 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  getClient
4
- } from "./chunk-PLFBTSKM.js";
4
+ } from "./chunk-PZSLLSMX.js";
5
5
  import {
6
6
  loadSkills
7
7
  } from "./chunk-242FRP4P.js";
8
8
  import {
9
9
  normalizeMcpHost,
10
10
  registerTools
11
- } from "./chunk-ZVOHKRNC.js";
11
+ } from "./chunk-O6BKY23H.js";
12
12
  import {
13
13
  capture,
14
14
  classifyError,
15
15
  clearUser,
16
16
  clientUserIdFromAccessToken,
17
+ describeError,
17
18
  extractIdentity,
18
19
  initTelemetry,
20
+ installProxyDispatcher,
19
21
  oauthCallbackErrorType,
20
22
  runOAuthCallback,
21
23
  setMcpHost,
22
24
  setUser,
23
25
  shutdown
24
- } from "./chunk-5NWKLF3V.js";
25
- import "./chunk-NPCCDRWQ.js";
26
+ } from "./chunk-VUPXBO2J.js";
27
+ import {
28
+ logger
29
+ } from "./chunk-OEZV5MA4.js";
26
30
  import "./chunk-DIPROABB.js";
27
31
 
28
32
  // src/index.ts
@@ -31,7 +35,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
31
35
  import open from "open";
32
36
  var server = new McpServer({
33
37
  name: "plaud",
34
- version: "0.3.10"
38
+ version: "0.3.11"
35
39
  });
36
40
  var CALLBACK_PORT = 8199;
37
41
  var LOGIN_TIMEOUT_MS = 12e4;
@@ -39,7 +43,15 @@ server.registerTool("login", {
39
43
  description: "Log in, sign in, or authenticate with Plaud account via OAuth (opens browser)"
40
44
  }, async () => {
41
45
  const client = getClient();
42
- const existingToken = await client.auth.getAccessToken();
46
+ let existingToken;
47
+ try {
48
+ existingToken = await client.auth.getAccessToken();
49
+ } catch (err) {
50
+ return {
51
+ content: [{ type: "text", text: `Cannot reach Plaud to check the saved credentials: ${describeError(err)}` }],
52
+ isError: true
53
+ };
54
+ }
43
55
  if (existingToken) {
44
56
  try {
45
57
  await client.getCurrentUser();
@@ -50,7 +62,7 @@ server.registerTool("login", {
50
62
  await client.auth.logout();
51
63
  } else {
52
64
  return {
53
- content: [{ type: "text", text: `Failed to verify login state: ${msg}` }],
65
+ content: [{ type: "text", text: `Failed to verify login state: ${describeError(err)}` }],
54
66
  isError: true
55
67
  };
56
68
  }
@@ -124,8 +136,7 @@ server.registerTool("logout", {
124
136
  description: "Log out, sign out, revoke authorization, and disconnect from Plaud account"
125
137
  }, async () => {
126
138
  const client = getClient();
127
- const existingToken = await client.auth.getAccessToken();
128
- if (!existingToken) {
139
+ if (!await client.auth.hasStoredToken()) {
129
140
  return { content: [{ type: "text", text: "Already logged out." }] };
130
141
  }
131
142
  try {
@@ -155,10 +166,14 @@ async function registerSkillPrompts() {
155
166
  }
156
167
  }
157
168
  async function main() {
169
+ const proxied = await installProxyDispatcher();
170
+ if (proxied) {
171
+ logger.info({ event: "proxy_dispatcher_installed", proxy_env: proxied });
172
+ }
158
173
  const sub = process.argv[2];
159
174
  const sub2 = process.argv[3];
160
175
  if (sub === "install") {
161
- const { runInstall } = await import("./install-SWKIKJ5C.js");
176
+ const { runInstall } = await import("./install-XYADZE7L.js");
162
177
  const args = process.argv.slice(3);
163
178
  const yes = args.some((a) => a === "--yes" || a === "-y");
164
179
  const noLogin = args.some((a) => a === "--no-login");
@@ -191,8 +206,8 @@ async function main() {
191
206
  return;
192
207
  }
193
208
  if (sub === "http") {
194
- const { startHttpServer } = await import("./server-SB6RRGY7.js");
195
- const { startMetricsServer } = await import("./server-6RAC6S7E.js");
209
+ const { startHttpServer } = await import("./server-KMU2TWA5.js");
210
+ const { startMetricsServer } = await import("./server-P6CNPNJ6.js");
196
211
  startMetricsServer();
197
212
  startHttpServer();
198
213
  return;
@@ -217,7 +232,7 @@ Usage:
217
232
  try {
218
233
  await initTelemetry({
219
234
  surface: "mcp",
220
- appVersion: "0.3.10",
235
+ appVersion: "0.3.11",
221
236
  transport: "stdio"
222
237
  });
223
238
  } catch {
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  getClient
3
- } from "./chunk-PLFBTSKM.js";
3
+ } from "./chunk-PZSLLSMX.js";
4
4
  import {
5
5
  commandPathIsStale,
6
6
  copyToClipboard,
@@ -11,8 +11,10 @@ import {
11
11
  skillsCombined
12
12
  } from "./chunk-242FRP4P.js";
13
13
  import {
14
+ describeError,
15
+ isTransportError,
14
16
  runOAuthCallback
15
- } from "./chunk-5NWKLF3V.js";
17
+ } from "./chunk-VUPXBO2J.js";
16
18
  import "./chunk-DIPROABB.js";
17
19
 
18
20
  // src/install.ts
@@ -489,7 +491,8 @@ async function runLogin() {
489
491
  }
490
492
  }
491
493
  }
492
- } catch {
494
+ } catch (err) {
495
+ if (isTransportError(err)) return { status: "already-authed" };
493
496
  await client.auth.logout().catch(() => void 0);
494
497
  }
495
498
  const { url, codeVerifier, state } = client.auth.createAuthorizationRequest();
@@ -613,12 +616,19 @@ async function doLoginStep(nonInteractive) {
613
616
  if (msg.includes("401") || msg.includes("Not authenticated")) {
614
617
  console.log(" saved token was revoked server-side \u2014 clearing and re-authenticating.");
615
618
  await client.auth.logout().catch(() => void 0);
619
+ } else if (isTransportError(err)) {
620
+ console.log(` cannot reach Plaud to verify the saved login \u2014 keeping it: ${describeError(err)}`);
621
+ return { status: "already-authed" };
616
622
  } else {
617
623
  console.log(" token present but user lookup failed \u2014 will re-auth.");
618
624
  }
619
625
  }
620
626
  }
621
- } catch {
627
+ } catch (err) {
628
+ if (isTransportError(err)) {
629
+ console.log(` cannot reach Plaud to refresh the saved login \u2014 keeping it: ${describeError(err)}`);
630
+ return { status: "already-authed" };
631
+ }
622
632
  }
623
633
  if (!nonInteractive) {
624
634
  const yes = await prompt("Log in to Plaud now? (opens browser)", true);
@@ -0,0 +1,54 @@
1
+ // src/instrumentation.ts
2
+ import { NodeSDK } from "@opentelemetry/sdk-node";
3
+ import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-grpc";
4
+ import { HttpInstrumentation } from "@opentelemetry/instrumentation-http";
5
+ import { ExpressInstrumentation } from "@opentelemetry/instrumentation-express";
6
+ import { UndiciInstrumentation } from "@opentelemetry/instrumentation-undici";
7
+ var URL_ATTRIBUTES = ["http.url", "url.full", "http.target", "url.query"];
8
+ function redactUrl(url) {
9
+ const q = url.indexOf("?");
10
+ return q === -1 ? url : `${url.slice(0, q)}?REDACTED`;
11
+ }
12
+ function redactSpanUrlAttributes(span) {
13
+ const attrs = span.attributes;
14
+ for (const key of URL_ATTRIBUTES) {
15
+ const value = attrs[key];
16
+ if (typeof value !== "string" || value === "") continue;
17
+ attrs[key] = key === "url.query" ? "REDACTED" : redactUrl(value);
18
+ }
19
+ }
20
+ var RedactingSpanExporter = class {
21
+ constructor(inner) {
22
+ this.inner = inner;
23
+ }
24
+ inner;
25
+ export(spans, resultCallback) {
26
+ for (const span of spans) redactSpanUrlAttributes(span);
27
+ this.inner.export(spans, resultCallback);
28
+ }
29
+ shutdown() {
30
+ return this.inner.shutdown();
31
+ }
32
+ forceFlush() {
33
+ return this.inner.forceFlush?.() ?? Promise.resolve();
34
+ }
35
+ };
36
+ var sdk = new NodeSDK({
37
+ traceExporter: new RedactingSpanExporter(new OTLPTraceExporter()),
38
+ instrumentations: [
39
+ new HttpInstrumentation(),
40
+ // inbound server spans
41
+ new ExpressInstrumentation(),
42
+ // route-level spans
43
+ new UndiciInstrumentation()
44
+ // outbound: native fetch, injects traceparent
45
+ ]
46
+ });
47
+ sdk.start();
48
+ process.on("SIGTERM", () => sdk.shutdown().catch(() => {
49
+ }));
50
+ export {
51
+ RedactingSpanExporter,
52
+ redactSpanUrlAttributes,
53
+ redactUrl
54
+ };
@@ -1,14 +1,14 @@
1
1
  import {
2
2
  normalizeMcpHost,
3
3
  registerTools
4
- } from "./chunk-ZVOHKRNC.js";
4
+ } from "./chunk-O6BKY23H.js";
5
5
  import {
6
6
  PlaudClient,
7
7
  classifyError
8
- } from "./chunk-5NWKLF3V.js";
8
+ } from "./chunk-VUPXBO2J.js";
9
9
  import {
10
10
  logger
11
- } from "./chunk-NPCCDRWQ.js";
11
+ } from "./chunk-OEZV5MA4.js";
12
12
  import {
13
13
  httpRequestDuration,
14
14
  httpRequestsInProgress,
@@ -455,6 +455,10 @@ var TokenVerifier = class {
455
455
  logger.warn({ event: "token_verify_failed", reason: "invalid" });
456
456
  return { ok: false, kind: "invalid" };
457
457
  }
458
+ if (verdict.kind === "rate_limited") {
459
+ logger.warn({ event: "token_verify_upstream_rate_limited" });
460
+ return { ok: false, kind: "rate_limited" };
461
+ }
458
462
  logger.error({ event: "token_verify_unavailable", reason: verdict.reason });
459
463
  return { ok: false, kind: "unavailable" };
460
464
  }
@@ -517,6 +521,10 @@ function subFromJwt(token) {
517
521
  return void 0;
518
522
  }
519
523
  }
524
+ function isUpstreamRateLimited(err) {
525
+ const msg = err instanceof Error ? err.message : String(err ?? "");
526
+ return /\bAPI error: 429\b/.test(msg);
527
+ }
520
528
  var AUTO_RECOVERED_CLIENT_NAME = "auto-recovered client";
521
529
  var REDIRECT_HOST_CLIENT_NAMES = {
522
530
  "chatgpt.com": "chatgpt",
@@ -600,6 +608,7 @@ var PlaudOAuthProvider = class extends ProxyOAuthServerProvider {
600
608
  } catch (err) {
601
609
  const type = classifyError(err);
602
610
  if (type === "auth") return { kind: "invalid" };
611
+ if (isUpstreamRateLimited(err)) return { kind: "rate_limited" };
603
612
  return { kind: "unavailable", reason: type };
604
613
  }
605
614
  };
@@ -1657,8 +1666,8 @@ function startHttpServer() {
1657
1666
  common: {
1658
1667
  serviceName: process.env.PLAUD_WAREHOUSE_SERVICE_NAME ?? "plaudmcp",
1659
1668
  // matches §1.3 key label `plaudmcp:TRACKING_KEY_PLAUDMCP`; confirm exact string with David
1660
- serviceVersion: "0.3.10",
1661
- buildId: "e5eb255",
1669
+ serviceVersion: "0.3.11",
1670
+ buildId: "516b086",
1662
1671
  // mcp tsup TODO: inject git short SHA (like CLI)
1663
1672
  region: process.env.PLAUD_REGION ?? "US",
1664
1673
  env: process.env.PLAUD_ENV ?? process.env.NODE_ENV ?? "development"
@@ -1933,7 +1942,7 @@ function startHttpServer() {
1933
1942
  apiBase,
1934
1943
  staticToken: token
1935
1944
  });
1936
- const mcpServer = new McpServer({ name: "plaud", version: "0.3.10" });
1945
+ const mcpServer = new McpServer({ name: "plaud", version: "0.3.11" });
1937
1946
  registerTools(mcpServer, client, warehouseToolHooks);
1938
1947
  const transport = new StreamableHTTPServerTransport({
1939
1948
  sessionIdGenerator: void 0,
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  logger
3
- } from "./chunk-NPCCDRWQ.js";
3
+ } from "./chunk-OEZV5MA4.js";
4
4
  import {
5
5
  registry
6
6
  } from "./chunk-DIPROABB.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plaud-ai/mcp",
3
- "version": "0.3.10",
3
+ "version": "0.3.11",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -21,11 +21,20 @@
21
21
  },
22
22
  "dependencies": {
23
23
  "@modelcontextprotocol/sdk": "^1.12.0",
24
+ "@opentelemetry/api": "^1.9.1",
25
+ "@opentelemetry/core": "^2.10.0",
26
+ "@opentelemetry/exporter-trace-otlp-grpc": "^0.221.0",
27
+ "@opentelemetry/instrumentation-express": "^0.69.0",
28
+ "@opentelemetry/instrumentation-http": "^0.221.0",
29
+ "@opentelemetry/instrumentation-undici": "^0.31.0",
30
+ "@opentelemetry/sdk-node": "^0.221.0",
31
+ "@opentelemetry/sdk-trace-base": "^2.10.0",
24
32
  "express": "^5.2.1",
25
33
  "open": "^10.2.0",
26
34
  "pino": "^10.3.1",
27
35
  "posthog-node": "^4.18.0",
28
36
  "prom-client": "^15.1.3",
37
+ "undici": "^6.28.1",
29
38
  "zod": "^4.3.6"
30
39
  },
31
40
  "devDependencies": {
@@ -1,10 +0,0 @@
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
- export {
9
- logger
10
- };