@plaud-ai/mcp 0.3.7 → 0.3.8
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-OYI4PXYP.js → chunk-5NWKLF3V.js} +58 -4
- package/dist/{chunk-VLZPO2CO.js → chunk-DKOQXKKG.js} +10 -14
- package/dist/{chunk-3K3M2X74.js → chunk-EY5K2UXG.js} +1 -1
- package/dist/index.js +7 -7
- package/dist/{install-3E63G6QX.js → install-RTIXREYV.js} +2 -2
- package/dist/{server-2HG4R4LT.js → server-VJAFEGJ6.js} +63 -9
- package/package.json +1 -1
- package/plugin.json +1 -1
|
@@ -446,6 +446,49 @@ async function loadBlockContent(block) {
|
|
|
446
446
|
return "";
|
|
447
447
|
}
|
|
448
448
|
|
|
449
|
+
// ../shared/dist/time.js
|
|
450
|
+
var HAS_TIMEZONE = /(?:[Zz]|[+-]\d{2}(?::?\d{2})?)$/;
|
|
451
|
+
var HAS_TIME = /\d{2}:\d{2}/;
|
|
452
|
+
var DATE_ONLY = /^(\d{4})-(\d{2})-(\d{2})$/;
|
|
453
|
+
function parseApiTimestamp(value) {
|
|
454
|
+
if (!value)
|
|
455
|
+
return null;
|
|
456
|
+
const trimmed = value.trim();
|
|
457
|
+
if (!trimmed)
|
|
458
|
+
return null;
|
|
459
|
+
const normalised = HAS_TIME.test(trimmed) && !HAS_TIMEZONE.test(trimmed) ? `${trimmed}Z` : trimmed;
|
|
460
|
+
const ms = new Date(normalised).getTime();
|
|
461
|
+
return Number.isNaN(ms) ? null : ms;
|
|
462
|
+
}
|
|
463
|
+
function localDayStart(value) {
|
|
464
|
+
const parts = parseDateOnly(value);
|
|
465
|
+
if (!parts)
|
|
466
|
+
return null;
|
|
467
|
+
const [y, m, d] = parts;
|
|
468
|
+
return new Date(y, m - 1, d, 0, 0, 0, 0).getTime();
|
|
469
|
+
}
|
|
470
|
+
function localDayEnd(value) {
|
|
471
|
+
const parts = parseDateOnly(value);
|
|
472
|
+
if (!parts)
|
|
473
|
+
return null;
|
|
474
|
+
const [y, m, d] = parts;
|
|
475
|
+
return new Date(y, m - 1, d + 1, 0, 0, 0, 0).getTime() - 1;
|
|
476
|
+
}
|
|
477
|
+
function parseDateOnly(value) {
|
|
478
|
+
if (!value)
|
|
479
|
+
return null;
|
|
480
|
+
const match = DATE_ONLY.exec(value.trim());
|
|
481
|
+
if (!match)
|
|
482
|
+
return null;
|
|
483
|
+
const y = Number(match[1]);
|
|
484
|
+
const m = Number(match[2]);
|
|
485
|
+
const d = Number(match[3]);
|
|
486
|
+
const probe = new Date(y, m - 1, d);
|
|
487
|
+
if (probe.getFullYear() !== y || probe.getMonth() !== m - 1 || probe.getDate() !== d)
|
|
488
|
+
return null;
|
|
489
|
+
return [y, m, d];
|
|
490
|
+
}
|
|
491
|
+
|
|
449
492
|
// ../telemetry/dist/client.js
|
|
450
493
|
import { PostHog } from "posthog-node";
|
|
451
494
|
|
|
@@ -510,10 +553,18 @@ async function shutdown() {
|
|
|
510
553
|
return;
|
|
511
554
|
const client = cachedClient;
|
|
512
555
|
cachedClient = null;
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
556
|
+
let timer;
|
|
557
|
+
try {
|
|
558
|
+
await Promise.race([
|
|
559
|
+
client.shutdown(),
|
|
560
|
+
new Promise((resolve) => {
|
|
561
|
+
timer = setTimeout(resolve, SHUTDOWN_TIMEOUT_MS);
|
|
562
|
+
})
|
|
563
|
+
]);
|
|
564
|
+
} finally {
|
|
565
|
+
if (timer)
|
|
566
|
+
clearTimeout(timer);
|
|
567
|
+
}
|
|
517
568
|
}
|
|
518
569
|
|
|
519
570
|
// ../telemetry/dist/api.js
|
|
@@ -859,6 +910,9 @@ export {
|
|
|
859
910
|
PlaudClient,
|
|
860
911
|
runOAuthCallback,
|
|
861
912
|
loadBlockContent,
|
|
913
|
+
parseApiTimestamp,
|
|
914
|
+
localDayStart,
|
|
915
|
+
localDayEnd,
|
|
862
916
|
shutdown,
|
|
863
917
|
initTelemetry,
|
|
864
918
|
setUser,
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
capture,
|
|
3
3
|
classifyError,
|
|
4
|
-
loadBlockContent
|
|
5
|
-
|
|
4
|
+
loadBlockContent,
|
|
5
|
+
localDayEnd,
|
|
6
|
+
localDayStart,
|
|
7
|
+
parseApiTimestamp
|
|
8
|
+
} from "./chunk-5NWKLF3V.js";
|
|
6
9
|
import {
|
|
7
10
|
logger
|
|
8
11
|
} from "./chunk-NPCCDRWQ.js";
|
|
@@ -33,12 +36,6 @@ function decodeTranscriptCursor(cursor) {
|
|
|
33
36
|
return null;
|
|
34
37
|
}
|
|
35
38
|
}
|
|
36
|
-
function parseDate(s) {
|
|
37
|
-
if (!s) return null;
|
|
38
|
-
const d = new Date(s);
|
|
39
|
-
if (Number.isNaN(d.getTime())) return null;
|
|
40
|
-
return d.getTime();
|
|
41
|
-
}
|
|
42
39
|
function normalizeMcpHost(name) {
|
|
43
40
|
if (!name) return void 0;
|
|
44
41
|
const n = name.trim().toLowerCase();
|
|
@@ -98,8 +95,8 @@ function registerTools(server, client, hooks) {
|
|
|
98
95
|
page: z.number().optional().default(1).describe("Page number (ignored when filters are set)"),
|
|
99
96
|
page_size: z.number().optional().default(20).describe("Items per page (ignored when filters are set)"),
|
|
100
97
|
query: z.string().optional().describe("Case-insensitive substring match on recording name"),
|
|
101
|
-
date_from: z.string().optional().describe("Start date inclusive, YYYY-MM-DD"),
|
|
102
|
-
date_to: z.string().optional().describe("End date inclusive, YYYY-MM-DD")
|
|
98
|
+
date_from: z.string().optional().describe("Start date inclusive, YYYY-MM-DD, interpreted in the server's timezone"),
|
|
99
|
+
date_to: z.string().optional().describe("End date inclusive, YYYY-MM-DD, interpreted in the server's timezone")
|
|
103
100
|
}
|
|
104
101
|
},
|
|
105
102
|
async ({ page, page_size, query, date_from, date_to }) => {
|
|
@@ -115,9 +112,8 @@ function registerTools(server, client, hooks) {
|
|
|
115
112
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
116
113
|
}
|
|
117
114
|
const q = query?.toLowerCase();
|
|
118
|
-
const from =
|
|
119
|
-
const
|
|
120
|
-
const to = toRaw !== null ? toRaw + 24 * 60 * 60 * 1e3 - 1 : null;
|
|
115
|
+
const from = localDayStart(date_from);
|
|
116
|
+
const to = localDayEnd(date_to);
|
|
121
117
|
const matches = [];
|
|
122
118
|
let scanned = 0;
|
|
123
119
|
let truncated = false;
|
|
@@ -128,7 +124,7 @@ function registerTools(server, client, hooks) {
|
|
|
128
124
|
for (const item of items) {
|
|
129
125
|
if (q && !(item.name ?? "").toLowerCase().includes(q)) continue;
|
|
130
126
|
if (from !== null || to !== null) {
|
|
131
|
-
const created =
|
|
127
|
+
const created = parseApiTimestamp(item.created_at);
|
|
132
128
|
if (created === null) continue;
|
|
133
129
|
if (from !== null && created < from) continue;
|
|
134
130
|
if (to !== null && created > to) continue;
|
package/dist/index.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
getClient
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-EY5K2UXG.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-
|
|
11
|
+
} from "./chunk-DKOQXKKG.js";
|
|
12
12
|
import {
|
|
13
13
|
capture,
|
|
14
14
|
classifyError,
|
|
@@ -21,7 +21,7 @@ import {
|
|
|
21
21
|
setMcpHost,
|
|
22
22
|
setUser,
|
|
23
23
|
shutdown
|
|
24
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-5NWKLF3V.js";
|
|
25
25
|
import "./chunk-NPCCDRWQ.js";
|
|
26
26
|
import "./chunk-RUFCT6DQ.js";
|
|
27
27
|
|
|
@@ -31,7 +31,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
31
31
|
import open from "open";
|
|
32
32
|
var server = new McpServer({
|
|
33
33
|
name: "plaud",
|
|
34
|
-
version: "0.3.
|
|
34
|
+
version: "0.3.8"
|
|
35
35
|
});
|
|
36
36
|
var CALLBACK_PORT = 8199;
|
|
37
37
|
var LOGIN_TIMEOUT_MS = 12e4;
|
|
@@ -158,7 +158,7 @@ async function main() {
|
|
|
158
158
|
const sub = process.argv[2];
|
|
159
159
|
const sub2 = process.argv[3];
|
|
160
160
|
if (sub === "install") {
|
|
161
|
-
const { runInstall } = await import("./install-
|
|
161
|
+
const { runInstall } = await import("./install-RTIXREYV.js");
|
|
162
162
|
const args = process.argv.slice(3);
|
|
163
163
|
const yes = args.some((a) => a === "--yes" || a === "-y");
|
|
164
164
|
const noLogin = args.some((a) => a === "--no-login");
|
|
@@ -191,7 +191,7 @@ async function main() {
|
|
|
191
191
|
return;
|
|
192
192
|
}
|
|
193
193
|
if (sub === "http") {
|
|
194
|
-
const { startHttpServer } = await import("./server-
|
|
194
|
+
const { startHttpServer } = await import("./server-VJAFEGJ6.js");
|
|
195
195
|
const { startMetricsServer } = await import("./server-NKCNUA6P.js");
|
|
196
196
|
startMetricsServer();
|
|
197
197
|
startHttpServer();
|
|
@@ -217,7 +217,7 @@ Usage:
|
|
|
217
217
|
try {
|
|
218
218
|
await initTelemetry({
|
|
219
219
|
surface: "mcp",
|
|
220
|
-
appVersion: "0.3.
|
|
220
|
+
appVersion: "0.3.8",
|
|
221
221
|
transport: "stdio"
|
|
222
222
|
});
|
|
223
223
|
} catch {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
getClient
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-EY5K2UXG.js";
|
|
4
4
|
import {
|
|
5
5
|
commandPathIsStale,
|
|
6
6
|
copyToClipboard,
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
} from "./chunk-242FRP4P.js";
|
|
13
13
|
import {
|
|
14
14
|
runOAuthCallback
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-5NWKLF3V.js";
|
|
16
16
|
import "./chunk-RUFCT6DQ.js";
|
|
17
17
|
|
|
18
18
|
// src/install.ts
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
normalizeMcpHost,
|
|
3
3
|
registerTools
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-DKOQXKKG.js";
|
|
5
5
|
import {
|
|
6
6
|
PlaudClient
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-5NWKLF3V.js";
|
|
8
8
|
import {
|
|
9
9
|
logger
|
|
10
10
|
} from "./chunk-NPCCDRWQ.js";
|
|
@@ -380,7 +380,7 @@ var PlaudOAuthProvider = class extends ProxyOAuthServerProvider {
|
|
|
380
380
|
return this._registeredClients.get(id);
|
|
381
381
|
},
|
|
382
382
|
registerClient: async (client) => {
|
|
383
|
-
const rawId =
|
|
383
|
+
const rawId = this.encodeRawId(client.client_name);
|
|
384
384
|
const tokenEndpointAuthMethod = client.token_endpoint_auth_method ?? "none";
|
|
385
385
|
const full = {
|
|
386
386
|
...client,
|
|
@@ -428,6 +428,34 @@ var PlaudOAuthProvider = class extends ProxyOAuthServerProvider {
|
|
|
428
428
|
const sig = createHmac("sha256", this._clientIdSecret).update(rawId).digest().subarray(0, 16);
|
|
429
429
|
return `${rawId}.${sig.toString("base64url")}`;
|
|
430
430
|
}
|
|
431
|
+
// Self-describing client_id: we can't persist the DCR registry across
|
|
432
|
+
// container restarts (compliance — no storage), so we encode the DCR
|
|
433
|
+
// client_name into the signed client_id itself. verifyAndRecover decodes the
|
|
434
|
+
// real name back instead of a placeholder, keeping mcp_host attribution
|
|
435
|
+
// correct across restarts (fixes the "auto-recovered client" bucket). The
|
|
436
|
+
// payload holds the app's self-reported name (not PII, capped) plus a nonce
|
|
437
|
+
// so repeat registrations get distinct ids. base64url contains no ".", so it
|
|
438
|
+
// never collides with the "<rawId>.<sig>" separator.
|
|
439
|
+
encodeRawId(clientName) {
|
|
440
|
+
const name = typeof clientName === "string" && clientName.length > 0 ? clientName.slice(0, 64) : null;
|
|
441
|
+
const payload = JSON.stringify({ v: 1, n: name, r: randomBytes(8).toString("base64url") });
|
|
442
|
+
return Buffer.from(payload, "utf8").toString("base64url");
|
|
443
|
+
}
|
|
444
|
+
// Decode the client_name embedded by encodeRawId. Returns undefined for
|
|
445
|
+
// legacy ids (random rawId issued before this fix) or anything malformed —
|
|
446
|
+
// callers fall back to the placeholder, so recovery still degrades gracefully.
|
|
447
|
+
decodeClientName(clientId) {
|
|
448
|
+
const idx = clientId.lastIndexOf(".");
|
|
449
|
+
const rawId = idx > 0 ? clientId.slice(0, idx) : clientId;
|
|
450
|
+
try {
|
|
451
|
+
const payload = JSON.parse(Buffer.from(rawId, "base64url").toString("utf8"));
|
|
452
|
+
if (payload && payload.v === 1 && typeof payload.n === "string" && payload.n.length > 0) {
|
|
453
|
+
return payload.n;
|
|
454
|
+
}
|
|
455
|
+
} catch {
|
|
456
|
+
}
|
|
457
|
+
return void 0;
|
|
458
|
+
}
|
|
431
459
|
// Verify a previously-issued client_id. Returns true only for ids whose HMAC
|
|
432
460
|
// signature matches our secret — i.e. ids this server (or its predecessor with
|
|
433
461
|
// the same PLAUD_DCR_HMAC_SECRET) issued via registerClient.
|
|
@@ -509,6 +537,7 @@ var PlaudOAuthProvider = class extends ProxyOAuthServerProvider {
|
|
|
509
537
|
if (!this.verifyClientIdSignature(clientId)) {
|
|
510
538
|
return false;
|
|
511
539
|
}
|
|
540
|
+
const recoveredName = this.decodeClientName(clientId);
|
|
512
541
|
this._registeredClients.set(clientId, {
|
|
513
542
|
client_id: clientId,
|
|
514
543
|
client_id_issued_at: Math.floor(Date.now() / 1e3),
|
|
@@ -516,9 +545,14 @@ var PlaudOAuthProvider = class extends ProxyOAuthServerProvider {
|
|
|
516
545
|
grant_types: ["authorization_code", "refresh_token"],
|
|
517
546
|
response_types: ["code"],
|
|
518
547
|
token_endpoint_auth_method: "none",
|
|
519
|
-
client_name: "auto-recovered client"
|
|
548
|
+
client_name: recoveredName ?? "auto-recovered client"
|
|
549
|
+
});
|
|
550
|
+
logger.info({
|
|
551
|
+
event: "oauth_client_recovered",
|
|
552
|
+
client_id: clientId,
|
|
553
|
+
redirect_uri: redirectUri ?? null,
|
|
554
|
+
name_recovered: recoveredName != null
|
|
520
555
|
});
|
|
521
|
-
logger.info({ event: "oauth_client_recovered", client_id: clientId, redirect_uri: redirectUri ?? null });
|
|
522
556
|
return true;
|
|
523
557
|
}
|
|
524
558
|
/**
|
|
@@ -1205,8 +1239,8 @@ function startHttpServer() {
|
|
|
1205
1239
|
common: {
|
|
1206
1240
|
serviceName: process.env.PLAUD_WAREHOUSE_SERVICE_NAME ?? "plaudmcp",
|
|
1207
1241
|
// matches §1.3 key label `plaudmcp:TRACKING_KEY_PLAUDMCP`; confirm exact string with David
|
|
1208
|
-
serviceVersion: "0.3.
|
|
1209
|
-
buildId: "
|
|
1242
|
+
serviceVersion: "0.3.8",
|
|
1243
|
+
buildId: "be0dfaa",
|
|
1210
1244
|
// mcp tsup TODO: inject git short SHA (like CLI)
|
|
1211
1245
|
region: process.env.PLAUD_REGION ?? "US",
|
|
1212
1246
|
env: process.env.PLAUD_ENV ?? process.env.NODE_ENV ?? "development"
|
|
@@ -1436,7 +1470,27 @@ function startHttpServer() {
|
|
|
1436
1470
|
// register…" once new-registration rate exceeds ~20/hour. Real auth still
|
|
1437
1471
|
// happens upstream at Plaud; abuse/unbounded-growth should be bounded on
|
|
1438
1472
|
// the in-memory client store (size/TTL cap) rather than via this per-IP limiter.
|
|
1439
|
-
clientRegistrationOptions: { rateLimit: false }
|
|
1473
|
+
clientRegistrationOptions: { rateLimit: false },
|
|
1474
|
+
// Same failure mode on the two handshake endpoints — /register was only
|
|
1475
|
+
// the first one users hit. The SDK also defaults /token to 50 requests per
|
|
1476
|
+
// 15 min and /authorize to 100 per 15 min, both keyed per client IP, and
|
|
1477
|
+
// behind our proxy every request presents the same upstream address, so
|
|
1478
|
+
// the per-IP bucket degenerates into ONE bucket shared by all users
|
|
1479
|
+
// worldwide. A reporter measured this from two continents at once: two
|
|
1480
|
+
// requests 2s apart from Cyprus and Germany saw the same counter (13 and
|
|
1481
|
+
// 11 remaining, reset 671s/672s), and the bucket drained ~13 req/min from
|
|
1482
|
+
// other traffic alone, i.e. it is empty almost continuously.
|
|
1483
|
+
//
|
|
1484
|
+
// /token is the worst place to cap: it is the only handshake endpoint that
|
|
1485
|
+
// repeats after login, because every silent refresh_token renewal goes
|
|
1486
|
+
// through it. Exhausting it does not just block new logins — it breaks
|
|
1487
|
+
// token renewal for users who are already connected.
|
|
1488
|
+
//
|
|
1489
|
+
// Real authentication and abuse control live upstream at Plaud (and at the
|
|
1490
|
+
// edge); this in-process per-IP limiter cannot see past the proxy, so it
|
|
1491
|
+
// can only produce false positives.
|
|
1492
|
+
tokenOptions: { rateLimit: false },
|
|
1493
|
+
authorizationOptions: { rateLimit: false }
|
|
1440
1494
|
})
|
|
1441
1495
|
);
|
|
1442
1496
|
app.post(
|
|
@@ -1455,7 +1509,7 @@ function startHttpServer() {
|
|
|
1455
1509
|
apiBase,
|
|
1456
1510
|
staticToken: token
|
|
1457
1511
|
});
|
|
1458
|
-
const mcpServer = new McpServer({ name: "plaud", version: "0.3.
|
|
1512
|
+
const mcpServer = new McpServer({ name: "plaud", version: "0.3.8" });
|
|
1459
1513
|
registerTools(mcpServer, client, warehouseToolHooks);
|
|
1460
1514
|
const transport = new StreamableHTTPServerTransport({
|
|
1461
1515
|
sessionIdGenerator: void 0,
|
package/package.json
CHANGED