minutes-mcp 0.8.4 → 0.9.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/index.d.ts +1 -0
- package/dist/index.js +192 -25
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
* - register_qmd_collection: Register Minutes output as QMD collection
|
|
21
21
|
* - list_voices: List enrolled voice profiles for speaker identification
|
|
22
22
|
* - confirm_speaker: Confirm/correct speaker attribution in a meeting
|
|
23
|
+
* - get_meeting_insights: Query structured insights (decisions, commitments, etc.) with confidence filtering
|
|
23
24
|
*
|
|
24
25
|
* All tools use execFile (not exec) to shell out to the `minutes` CLI binary.
|
|
25
26
|
* No shell interpolation — safe from injection.
|
package/dist/index.js
CHANGED
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
* - register_qmd_collection: Register Minutes output as QMD collection
|
|
21
21
|
* - list_voices: List enrolled voice profiles for speaker identification
|
|
22
22
|
* - confirm_speaker: Confirm/correct speaker attribution in a meeting
|
|
23
|
+
* - get_meeting_insights: Query structured insights (decisions, commitments, etc.) with confidence filtering
|
|
23
24
|
*
|
|
24
25
|
* All tools use execFile (not exec) to shell out to the `minutes` CLI binary.
|
|
25
26
|
* No shell interpolation — safe from injection.
|
|
@@ -32,7 +33,7 @@ import { execFile, spawn } from "child_process";
|
|
|
32
33
|
import { promisify } from "util";
|
|
33
34
|
import { existsSync } from "fs";
|
|
34
35
|
import { readFile } from "fs/promises";
|
|
35
|
-
import { dirname, join } from "path";
|
|
36
|
+
import { delimiter, dirname, join } from "path";
|
|
36
37
|
import { fileURLToPath } from "url";
|
|
37
38
|
import { homedir } from "os";
|
|
38
39
|
import * as reader from "minutes-sdk";
|
|
@@ -238,7 +239,7 @@ async function tryAutoInstall() {
|
|
|
238
239
|
// ── CLI version check ───────────────────────────────────────
|
|
239
240
|
async function checkCliVersion() {
|
|
240
241
|
try {
|
|
241
|
-
const { stdout } = await execFileAsync(MINUTES_BIN, ["--version"], { timeout: 5000 });
|
|
242
|
+
const { stdout } = await execFileAsync(MINUTES_BIN, ["--version"], { timeout: 5000, env: augmentedEnv() });
|
|
242
243
|
// Output is like "minutes 0.8.0" or just "0.8.0"
|
|
243
244
|
const match = stdout.trim().match(/(\d+\.\d+\.\d+)/);
|
|
244
245
|
if (match) {
|
|
@@ -268,7 +269,7 @@ async function ensureWhisperModel() {
|
|
|
268
269
|
try {
|
|
269
270
|
// health --json returns an array of { label, state, detail, optional } items.
|
|
270
271
|
// The "Speech model" item has state "ready" when downloaded.
|
|
271
|
-
const { stdout } = await execFileAsync(MINUTES_BIN, ["health", "--json"], { timeout: 10000 });
|
|
272
|
+
const { stdout } = await execFileAsync(MINUTES_BIN, ["health", "--json"], { timeout: 10000, env: augmentedEnv() });
|
|
272
273
|
const items = JSON.parse(stdout);
|
|
273
274
|
const modelItem = Array.isArray(items) && items.find((i) => i.label === "Speech model");
|
|
274
275
|
if (modelItem && modelItem.state === "ready") {
|
|
@@ -282,7 +283,7 @@ async function ensureWhisperModel() {
|
|
|
282
283
|
// Model not found — download tiny model in background
|
|
283
284
|
console.error("[Minutes] Whisper model not found — downloading tiny model (~75MB)...");
|
|
284
285
|
try {
|
|
285
|
-
await execFileAsync(MINUTES_BIN, ["setup", "--model", "tiny"], { timeout: 300000 });
|
|
286
|
+
await execFileAsync(MINUTES_BIN, ["setup", "--model", "tiny"], { timeout: 300000, env: augmentedEnv() });
|
|
286
287
|
console.error("[Minutes] ✓ Whisper tiny model downloaded — recording is ready");
|
|
287
288
|
}
|
|
288
289
|
catch (e) {
|
|
@@ -304,7 +305,7 @@ async function isCliAvailable() {
|
|
|
304
305
|
if (cliAvailable === false && Date.now() - cliCheckedAt < CLI_CACHE_TTL_MS)
|
|
305
306
|
return false;
|
|
306
307
|
try {
|
|
307
|
-
await execFileAsync(MINUTES_BIN, ["--version"], { timeout: 5000 });
|
|
308
|
+
await execFileAsync(MINUTES_BIN, ["--version"], { timeout: 5000, env: augmentedEnv() });
|
|
308
309
|
cliAvailable = true;
|
|
309
310
|
cliCheckedAt = Date.now();
|
|
310
311
|
console.error("[Minutes] CLI found — full mode (all tools enabled)");
|
|
@@ -318,7 +319,7 @@ async function isCliAvailable() {
|
|
|
318
319
|
const installed = await tryAutoInstall();
|
|
319
320
|
if (installed) {
|
|
320
321
|
try {
|
|
321
|
-
await execFileAsync(MINUTES_BIN, ["--version"], { timeout: 5000 });
|
|
322
|
+
await execFileAsync(MINUTES_BIN, ["--version"], { timeout: 5000, env: augmentedEnv() });
|
|
322
323
|
cliAvailable = true;
|
|
323
324
|
cliCheckedAt = Date.now();
|
|
324
325
|
console.error("[Minutes] CLI now available after auto-install — full mode");
|
|
@@ -337,16 +338,32 @@ async function isCliAvailable() {
|
|
|
337
338
|
}
|
|
338
339
|
return cliAvailable;
|
|
339
340
|
}
|
|
340
|
-
const CLI_INSTALL_MSG =
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
341
|
+
const CLI_INSTALL_MSG = `Recording requires the minutes CLI binary.\n` +
|
|
342
|
+
`Searched: ${MINUTES_BIN}\n\n` +
|
|
343
|
+
`Install it:\n` +
|
|
344
|
+
` macOS: brew tap silverstein/tap && brew install minutes\n` +
|
|
345
|
+
` Any: cargo install minutes-cli\n` +
|
|
346
|
+
` Source: https://github.com/silverstein/minutes\n\n` +
|
|
347
|
+
`If already installed via Homebrew, try:\n` +
|
|
348
|
+
` sudo ln -s /opt/homebrew/bin/minutes /usr/local/bin/minutes`;
|
|
349
|
+
// Common binary locations that may not be in Claude Desktop's restricted PATH.
|
|
350
|
+
const EXTRA_PATH_DIRS = [
|
|
351
|
+
join(homedir(), ".local", "bin"),
|
|
352
|
+
join(homedir(), ".cargo", "bin"),
|
|
353
|
+
"/opt/homebrew/bin",
|
|
354
|
+
"/usr/local/bin",
|
|
355
|
+
];
|
|
356
|
+
function augmentedEnv(extra) {
|
|
357
|
+
const currentPath = process.env.PATH || "";
|
|
358
|
+
const augmentedPath = [...EXTRA_PATH_DIRS, currentPath].join(delimiter);
|
|
359
|
+
return { ...process.env, PATH: augmentedPath, ...extra };
|
|
360
|
+
}
|
|
344
361
|
// ── Helper: run minutes CLI command (uses execFile, not exec) ──
|
|
345
362
|
async function runMinutes(args, timeoutMs = 30000) {
|
|
346
363
|
try {
|
|
347
364
|
const { stdout, stderr } = await execFileAsync(MINUTES_BIN, args, {
|
|
348
365
|
timeout: timeoutMs,
|
|
349
|
-
env: {
|
|
366
|
+
env: augmentedEnv({ RUST_LOG: "info" }),
|
|
350
367
|
});
|
|
351
368
|
return { stdout: stdout.trim(), stderr: stderr.trim() };
|
|
352
369
|
}
|
|
@@ -370,7 +387,7 @@ function parseJsonOutput(stdout) {
|
|
|
370
387
|
// ── MCP Server ──────────────────────────────────────────────
|
|
371
388
|
const server = new McpServer({
|
|
372
389
|
name: "minutes",
|
|
373
|
-
version: "0.
|
|
390
|
+
version: "0.9.0",
|
|
374
391
|
});
|
|
375
392
|
// Declare MCP Apps extension support so hosts classify this server as interactive.
|
|
376
393
|
// The `extensions` field is part of the draft MCP spec (SEP-1724) — not yet in the
|
|
@@ -424,7 +441,8 @@ server.tool("start_recording", "Start recording audio from the default input dev
|
|
|
424
441
|
.optional()
|
|
425
442
|
.default("meeting")
|
|
426
443
|
.describe("Live capture mode"),
|
|
427
|
-
|
|
444
|
+
language: z.string().optional().describe("Transcription language code (e.g. 'en', 'ur', 'es', 'zh'). Overrides config.toml setting."),
|
|
445
|
+
}, { title: "Start Recording", readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false }, async ({ title, mode, language }) => {
|
|
428
446
|
if (!(await isCliAvailable())) {
|
|
429
447
|
return { content: [{ type: "text", text: CLI_INSTALL_MSG }] };
|
|
430
448
|
}
|
|
@@ -445,6 +463,8 @@ server.tool("start_recording", "Start recording audio from the default input dev
|
|
|
445
463
|
const args = ["record", "--mode", mode];
|
|
446
464
|
if (title)
|
|
447
465
|
args.push("--title", title);
|
|
466
|
+
if (language)
|
|
467
|
+
args.push("--language", language);
|
|
448
468
|
const child = spawn(MINUTES_BIN, args, {
|
|
449
469
|
detached: true,
|
|
450
470
|
stdio: "ignore",
|
|
@@ -474,13 +494,60 @@ server.tool("stop_recording", "Stop the current recording and process it (transc
|
|
|
474
494
|
try {
|
|
475
495
|
const { stdout, stderr } = await runMinutes(["stop"], 180000);
|
|
476
496
|
const result = parseJsonOutput(stdout);
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
497
|
+
if (result.status === "queued") {
|
|
498
|
+
const title = result.title ? ` for ${result.title}` : "";
|
|
499
|
+
const jobLine = result.job_id ? ` Job: ${result.job_id}.` : "";
|
|
500
|
+
return {
|
|
501
|
+
content: [
|
|
502
|
+
{
|
|
503
|
+
type: "text",
|
|
504
|
+
text: `Recording stopped. Processing queued${title}.${jobLine}`,
|
|
505
|
+
},
|
|
506
|
+
],
|
|
507
|
+
};
|
|
508
|
+
}
|
|
509
|
+
if (!result.file) {
|
|
510
|
+
return { content: [{ type: "text", text: stderr || "Recording stopped." }] };
|
|
511
|
+
}
|
|
480
512
|
// Trigger QMD re-index so new meeting is immediately searchable
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
513
|
+
triggerQmdIndex();
|
|
514
|
+
// Build a rich summary by reading the meeting frontmatter
|
|
515
|
+
let summary = `## ${result.title ?? "Recording"}\n\n`;
|
|
516
|
+
summary += `**Saved:** ${result.file}\n`;
|
|
517
|
+
if (result.words != null)
|
|
518
|
+
summary += `**Words:** ${result.words}\n`;
|
|
519
|
+
try {
|
|
520
|
+
const meeting = await reader.getMeeting(result.file);
|
|
521
|
+
if (meeting) {
|
|
522
|
+
const fm = meeting.frontmatter;
|
|
523
|
+
if (fm.duration)
|
|
524
|
+
summary += `**Duration:** ${fm.duration}\n`;
|
|
525
|
+
if (fm.people?.length)
|
|
526
|
+
summary += `**People:** ${fm.people.join(", ")}\n`;
|
|
527
|
+
const actions = fm.action_items?.filter((a) => a.status === "open") || [];
|
|
528
|
+
if (actions.length > 0) {
|
|
529
|
+
summary += `\n### Action Items\n`;
|
|
530
|
+
for (const item of actions) {
|
|
531
|
+
summary += `- [ ] ${item.task}`;
|
|
532
|
+
if (item.assignee)
|
|
533
|
+
summary += ` (${item.assignee})`;
|
|
534
|
+
if (item.due)
|
|
535
|
+
summary += ` — due ${item.due}`;
|
|
536
|
+
summary += `\n`;
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
if (fm.decisions?.length) {
|
|
540
|
+
summary += `\n### Decisions\n`;
|
|
541
|
+
for (const d of fm.decisions) {
|
|
542
|
+
summary += `- ${d.text}\n`;
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
catch {
|
|
548
|
+
// Frontmatter read is best-effort — basic info is already in the summary
|
|
549
|
+
}
|
|
550
|
+
return { content: [{ type: "text", text: summary }] };
|
|
484
551
|
}
|
|
485
552
|
catch (error) {
|
|
486
553
|
return {
|
|
@@ -500,10 +567,47 @@ server.tool("get_status", "Check if a recording is currently in progress.", {},
|
|
|
500
567
|
const text = status.recording
|
|
501
568
|
? `${modeLabel} in progress (PID: ${status.pid})`
|
|
502
569
|
: status.processing
|
|
503
|
-
? `${processingLabel}${status.processing_stage ? `: ${status.processing_stage}` : "."}`
|
|
570
|
+
? `${processingLabel}${status.processing_title ? ` for ${status.processing_title}` : ""}${status.processing_stage ? `: ${status.processing_stage}` : "."}${status.processing_job_count > 1 ? ` (${status.processing_job_count} jobs queued)` : ""}`
|
|
504
571
|
: "No recording in progress.";
|
|
505
572
|
return { content: [{ type: "text", text }] };
|
|
506
573
|
});
|
|
574
|
+
server.tool("list_processing_jobs", "List background processing jobs for recent recordings, including queued, transcript-ready, failed, and completed work.", {
|
|
575
|
+
limit: z.number().optional().default(10).describe("Maximum number of jobs"),
|
|
576
|
+
include_completed: z.boolean().optional().default(true).describe("Include completed and failed jobs"),
|
|
577
|
+
}, { title: "Processing Jobs", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }, async ({ limit, include_completed }) => {
|
|
578
|
+
if (!(await isCliAvailable())) {
|
|
579
|
+
return { content: [{ type: "text", text: CLI_INSTALL_MSG }] };
|
|
580
|
+
}
|
|
581
|
+
const args = ["jobs", "--json", "--limit", String(limit)];
|
|
582
|
+
if (include_completed)
|
|
583
|
+
args.push("--all");
|
|
584
|
+
try {
|
|
585
|
+
const { stdout } = await runMinutes(args);
|
|
586
|
+
const jobs = parseJsonOutput(stdout);
|
|
587
|
+
if (!Array.isArray(jobs) || jobs.length === 0) {
|
|
588
|
+
return {
|
|
589
|
+
content: [{ type: "text", text: "No processing jobs right now." }],
|
|
590
|
+
structuredContent: { jobs: [] },
|
|
591
|
+
};
|
|
592
|
+
}
|
|
593
|
+
const lines = jobs.map((job) => {
|
|
594
|
+
const title = job.title || "Queued recording";
|
|
595
|
+
const state = job.state || "queued";
|
|
596
|
+
const stage = job.stage ? ` — ${job.stage}` : "";
|
|
597
|
+
return `- ${job.id}: ${state} — ${title}${stage}`;
|
|
598
|
+
});
|
|
599
|
+
return {
|
|
600
|
+
content: [{ type: "text", text: `Processing jobs:\n\n${lines.join("\n")}` }],
|
|
601
|
+
structuredContent: { jobs },
|
|
602
|
+
};
|
|
603
|
+
}
|
|
604
|
+
catch (error) {
|
|
605
|
+
return {
|
|
606
|
+
content: [{ type: "text", text: `Failed to list processing jobs: ${error.message}` }],
|
|
607
|
+
isError: true,
|
|
608
|
+
};
|
|
609
|
+
}
|
|
610
|
+
});
|
|
507
611
|
// ── Tool: list_meetings ─────────────────────────────────────
|
|
508
612
|
registerAppTool(server, "list_meetings", {
|
|
509
613
|
description: "List recent meetings and voice memos.",
|
|
@@ -933,7 +1037,8 @@ server.tool("process_audio", "Process an audio file through the transcription pi
|
|
|
933
1037
|
file_path: z.string().describe("Path to audio file (.wav, .m4a, .mp3)"),
|
|
934
1038
|
type: z.enum(["meeting", "memo"]).optional().default("memo").describe("Content type"),
|
|
935
1039
|
title: z.string().optional().describe("Optional title"),
|
|
936
|
-
|
|
1040
|
+
language: z.string().optional().describe("Transcription language code (e.g. 'en', 'ur', 'es', 'zh'). Overrides config.toml setting."),
|
|
1041
|
+
}, { title: "Process Audio", readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false }, async ({ file_path, type: contentType, title, language }) => {
|
|
937
1042
|
if (!(await isCliAvailable())) {
|
|
938
1043
|
return { content: [{ type: "text", text: CLI_INSTALL_MSG }] };
|
|
939
1044
|
}
|
|
@@ -948,6 +1053,8 @@ server.tool("process_audio", "Process an audio file through the transcription pi
|
|
|
948
1053
|
const args = ["process", resolved, "-t", contentType];
|
|
949
1054
|
if (title)
|
|
950
1055
|
args.push("--title", title);
|
|
1056
|
+
if (language)
|
|
1057
|
+
args.push("--language", language);
|
|
951
1058
|
const { stdout } = await runMinutes(args, 300000);
|
|
952
1059
|
const result = parseJsonOutput(stdout);
|
|
953
1060
|
return {
|
|
@@ -1282,7 +1389,9 @@ server.resource("recent-ideas", "minutes://ideas/recent", { description: "Recent
|
|
|
1282
1389
|
};
|
|
1283
1390
|
});
|
|
1284
1391
|
// ── Tool: start_dictation ──────────────────────────────────
|
|
1285
|
-
server.tool("start_dictation", "Start dictation mode. Speak naturally — text accumulates across pauses and the combined result is written when dictation ends. Runs until stop_dictation is called or silence timeout.", {
|
|
1392
|
+
server.tool("start_dictation", "Start dictation mode. Speak naturally — text accumulates across pauses and the combined result is written when dictation ends. Runs until stop_dictation is called or silence timeout.", {
|
|
1393
|
+
language: z.string().optional().describe("Transcription language code (e.g. 'en', 'ur', 'es', 'zh'). Overrides config.toml setting."),
|
|
1394
|
+
}, { title: "Start Dictation", readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false }, async ({ language }) => {
|
|
1286
1395
|
if (!(await isCliAvailable())) {
|
|
1287
1396
|
return { content: [{ type: "text", text: CLI_INSTALL_MSG }] };
|
|
1288
1397
|
}
|
|
@@ -1299,7 +1408,10 @@ server.tool("start_dictation", "Start dictation mode. Speak naturally — text a
|
|
|
1299
1408
|
};
|
|
1300
1409
|
}
|
|
1301
1410
|
// Spawn detached dictation process
|
|
1302
|
-
const
|
|
1411
|
+
const dictArgs = ["dictate"];
|
|
1412
|
+
if (language)
|
|
1413
|
+
dictArgs.push("--language", language);
|
|
1414
|
+
const child = spawn(MINUTES_BIN, dictArgs, {
|
|
1303
1415
|
detached: true,
|
|
1304
1416
|
stdio: "ignore",
|
|
1305
1417
|
env: { ...process.env, RUST_LOG: "info" },
|
|
@@ -1389,8 +1501,60 @@ server.tool("confirm_speaker", "Confirm or correct a speaker attribution in a me
|
|
|
1389
1501
|
};
|
|
1390
1502
|
}
|
|
1391
1503
|
});
|
|
1504
|
+
// ── Tool: get_meeting_insights ─────────────────────────────
|
|
1505
|
+
server.tool("get_meeting_insights", "Query structured insights extracted from meetings — decisions, commitments, approvals, questions, blockers, follow-ups, and risks. Each insight has a confidence level (tentative/inferred/strong/explicit). Use this to find what was decided, who committed to what, and what's still open across all meetings. External systems can subscribe to these events for workflow automation.", {
|
|
1506
|
+
kind: z.enum(["decision", "commitment", "approval", "question", "blocker", "follow_up", "risk"]).optional().describe("Filter by insight type"),
|
|
1507
|
+
confidence: z.enum(["tentative", "inferred", "strong", "explicit"]).optional().describe("Minimum confidence level"),
|
|
1508
|
+
participant: z.string().optional().describe("Filter by participant name (partial match)"),
|
|
1509
|
+
since: z.string().optional().describe("Only insights since this date (YYYY-MM-DD)"),
|
|
1510
|
+
limit: z.number().optional().default(50).describe("Maximum number of results"),
|
|
1511
|
+
actionable_only: z.boolean().optional().default(false).describe("Only return actionable insights (Strong or Explicit confidence)"),
|
|
1512
|
+
}, { title: "Get Meeting Insights", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }, async ({ kind, confidence, participant, since, limit, actionable_only }) => {
|
|
1513
|
+
if (!(await isCliAvailable())) {
|
|
1514
|
+
return { content: [{ type: "text", text: CLI_INSTALL_MSG }] };
|
|
1515
|
+
}
|
|
1516
|
+
const args = ["insights", "--limit", String(limit ?? 50)];
|
|
1517
|
+
if (kind) {
|
|
1518
|
+
args.push("--kind", kind);
|
|
1519
|
+
}
|
|
1520
|
+
if (actionable_only) {
|
|
1521
|
+
args.push("--actionable");
|
|
1522
|
+
}
|
|
1523
|
+
else if (confidence) {
|
|
1524
|
+
args.push("--confidence", confidence);
|
|
1525
|
+
}
|
|
1526
|
+
if (participant) {
|
|
1527
|
+
args.push("--participant", participant);
|
|
1528
|
+
}
|
|
1529
|
+
if (since) {
|
|
1530
|
+
args.push("--since", since);
|
|
1531
|
+
}
|
|
1532
|
+
try {
|
|
1533
|
+
const { stdout } = await runMinutes(args);
|
|
1534
|
+
const insights = parseJsonOutput(stdout);
|
|
1535
|
+
const count = Array.isArray(insights) ? insights.length : 0;
|
|
1536
|
+
if (count === 0) {
|
|
1537
|
+
return {
|
|
1538
|
+
content: [{ type: "text", text: "No meeting insights found matching the filter criteria. Insights are extracted when meetings are processed with summarization enabled." }],
|
|
1539
|
+
};
|
|
1540
|
+
}
|
|
1541
|
+
return {
|
|
1542
|
+
content: [{ type: "text", text: `Found ${count} insight(s):\n\n${JSON.stringify(insights, null, 2)}` }],
|
|
1543
|
+
structuredContent: { count, insights },
|
|
1544
|
+
};
|
|
1545
|
+
}
|
|
1546
|
+
catch (error) {
|
|
1547
|
+
const msg = error?.stderr || error?.message || String(error);
|
|
1548
|
+
return {
|
|
1549
|
+
content: [{ type: "text", text: `Failed to query insights: ${msg}` }],
|
|
1550
|
+
isError: true,
|
|
1551
|
+
};
|
|
1552
|
+
}
|
|
1553
|
+
});
|
|
1392
1554
|
// ── Tool: start_live_transcript ──────────────────────────────
|
|
1393
|
-
server.tool("start_live_transcript", "Start a live transcript session. Records audio and transcribes in real-time, writing utterances to a JSONL file. Use read_live_transcript to read the transcript during the session. Runs until stop is called.", {
|
|
1555
|
+
server.tool("start_live_transcript", "Start a live transcript session. Records audio and transcribes in real-time, writing utterances to a JSONL file. Use read_live_transcript to read the transcript during the session. Runs until stop is called.", {
|
|
1556
|
+
language: z.string().optional().describe("Transcription language code (e.g. 'en', 'ur', 'es', 'zh'). Overrides config.toml setting."),
|
|
1557
|
+
}, { title: "Start Live Transcript", readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false }, async ({ language }) => {
|
|
1394
1558
|
if (!(await isCliAvailable())) {
|
|
1395
1559
|
return { content: [{ type: "text", text: CLI_INSTALL_MSG }] };
|
|
1396
1560
|
}
|
|
@@ -1414,7 +1578,10 @@ server.tool("start_live_transcript", "Start a live transcript session. Records a
|
|
|
1414
1578
|
}
|
|
1415
1579
|
catch { /* no active session, proceed */ }
|
|
1416
1580
|
// Spawn detached live transcript process
|
|
1417
|
-
const
|
|
1581
|
+
const liveArgs = ["live"];
|
|
1582
|
+
if (language)
|
|
1583
|
+
liveArgs.push("--language", language);
|
|
1584
|
+
const child = spawn(MINUTES_BIN, liveArgs, {
|
|
1418
1585
|
detached: true,
|
|
1419
1586
|
stdio: "ignore",
|
|
1420
1587
|
env: { ...process.env, RUST_LOG: "info" },
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "minutes-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "MCP server for minutes — conversation memory for AI assistants. Works with Claude Desktop, Mistral Vibe, Cursor, Windsurf, and any MCP client.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"dependencies": {
|
|
41
41
|
"@modelcontextprotocol/ext-apps": "^1.2.2",
|
|
42
42
|
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
43
|
-
"minutes-sdk": "^0.
|
|
43
|
+
"minutes-sdk": "^0.9.0",
|
|
44
44
|
"yaml": "^2.8.3"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|