tracegist-mcp-bridge 0.1.4 → 0.2.2

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/README.md CHANGED
@@ -48,13 +48,13 @@ Set `TRACEGIST_DIR` to change where the bridge looks for packages (defaults to `
48
48
 
49
49
  Each session ZIP may include:
50
50
 
51
- | Path | Description |
52
- |---|---|
53
- | `*-coding-agent-handoff.md` | Structured handoff for the coding agent |
54
- | `*-manifest.json` | Session metadata and file index |
55
- | `*-playwright-repro.spec.ts` | Auto-generated Playwright repro script skeleton |
51
+ | Path | Description |
52
+ | ---------------------------- | -------------------------------------------------------------------------------------- |
53
+ | `*-coding-agent-handoff.md` | Structured handoff for the coding agent |
54
+ | `*-manifest.json` | Session metadata and file index |
55
+ | `*-playwright-repro.spec.ts` | Auto-generated Playwright repro script skeleton |
56
56
  | `network/api-requests.jsonl` | API request/response bodies (opt-in, requires Deep Diagnostics + body capture enabled) |
57
- | `markers/marker-NN-*/` | Per-marker screenshots, voice notes, highlight captures |
57
+ | `markers/marker-NN-*/` | Per-marker screenshots, voice notes, highlight captures |
58
58
 
59
59
  ## Prerequisites for transcription
60
60
 
@@ -11,14 +11,14 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
11
11
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
12
12
 
13
13
  const BRIDGE_NAME = "tracegist-mcp-bridge";
14
- const BRIDGE_VERSION = "0.1.4";
14
+ const { version: BRIDGE_VERSION } = JSON.parse(
15
+ await fs.readFile(new URL("../package.json", import.meta.url), "utf8"),
16
+ );
15
17
 
16
18
  function resolveDefaultDirectory() {
17
19
  const envDir = process.env.TRACEGIST_DIR;
18
20
  if (envDir) {
19
- const resolved = envDir.startsWith("~")
20
- ? path.join(os.homedir(), envDir.slice(1))
21
- : envDir;
21
+ const resolved = envDir.startsWith("~") ? path.join(os.homedir(), envDir.slice(1)) : envDir;
22
22
  return path.resolve(resolved);
23
23
  }
24
24
  return path.join(os.homedir(), "Downloads");
@@ -126,7 +126,9 @@ async function readZipEntries(zipPath) {
126
126
  }
127
127
 
128
128
  async function tryReadManifest(zip) {
129
- const manifestEntry = Object.values(zip.files).find((entry) => entry.name.endsWith("-manifest.json"));
129
+ const manifestEntry = Object.values(zip.files).find((entry) =>
130
+ entry.name.endsWith("-manifest.json"),
131
+ );
130
132
  if (!manifestEntry) return { manifest: null, manifestEntryName: null };
131
133
  const manifestText = await manifestEntry.async("text");
132
134
  try {
@@ -147,9 +149,10 @@ function normalizeZipBaseName(entryName) {
147
149
  }
148
150
 
149
151
  async function tryReadHandoffMarkdown(zip, entryNames, manifest = null) {
150
- const manifestNamedHandoff = typeof manifest?.handoffMarkdownFilename === "string"
151
- ? normalizeZipBaseName(manifest.handoffMarkdownFilename)
152
- : null;
152
+ const manifestNamedHandoff =
153
+ typeof manifest?.handoffMarkdownFilename === "string"
154
+ ? normalizeZipBaseName(manifest.handoffMarkdownFilename)
155
+ : null;
153
156
  const byBaseName = new Map();
154
157
  for (const name of entryNames) {
155
158
  byBaseName.set(normalizeZipBaseName(name), name);
@@ -160,11 +163,12 @@ async function tryReadHandoffMarkdown(zip, entryNames, manifest = null) {
160
163
  selectedEntryName = byBaseName.get(manifestNamedHandoff) || null;
161
164
  }
162
165
  if (!selectedEntryName) {
163
- selectedEntryName = entryNames.find((name) => {
164
- const baseName = normalizeZipBaseName(name);
165
- if (HANDOFF_SUFFIXES.some((suffix) => baseName.endsWith(suffix))) return true;
166
- return /handoff.*\.md$/i.test(baseName);
167
- }) || null;
166
+ selectedEntryName =
167
+ entryNames.find((name) => {
168
+ const baseName = normalizeZipBaseName(name);
169
+ if (HANDOFF_SUFFIXES.some((suffix) => baseName.endsWith(suffix))) return true;
170
+ return /handoff.*\.md$/i.test(baseName);
171
+ }) || null;
168
172
  }
169
173
  if (!selectedEntryName) {
170
174
  return { handoffMarkdown: null, handoffEntryName: null, debugCandidates: [] };
@@ -354,10 +358,7 @@ async function injectTranscriptsIntoMarkdown(markdown, zip, entryNames) {
354
358
  } else {
355
359
  const hint = whisperDependencyWarnings.join(" | ");
356
360
  for (const shortId of voiceByShortId.keys()) {
357
- transcriptByShortId.set(
358
- shortId,
359
- `[TRANSCRIPTION REQUIRED — Whisper not available: ${hint}]`,
360
- );
361
+ transcriptByShortId.set(shortId, `[TRANSCRIPTION REQUIRED — Whisper not available: ${hint}]`);
361
362
  }
362
363
  }
363
364
 
@@ -414,7 +415,7 @@ server.registerPrompt(
414
415
  "",
415
416
  "1. `list_tracegist_packages` — find available packages (searches TRACEGIST_DIR env var, or ~/Downloads by default).",
416
417
  "2. `get_tracegist_handoff_markdown` — call without a `section` parameter to get a table of contents",
417
- " showing all sections and their sizes. Then request specific sections by name (e.g. `section: \"Marker Timeline\"`).",
418
+ ' showing all sections and their sizes. Then request specific sections by name (e.g. `section: "Marker Timeline"`).',
418
419
  ' Use `section: "all"` to load the full document at once (can be large for deep exports).',
419
420
  " **Voice note transcripts are injected automatically** into the marker sections — no separate step needed.",
420
421
  " If Whisper is unavailable, a placeholder is injected with setup instructions.",
@@ -462,23 +463,25 @@ server.registerTool(
462
463
  async ({ zipPath, model = "base", language }) => {
463
464
  if (whisperDependencyWarnings.length > 0) {
464
465
  return {
465
- content: [{
466
- type: "text",
467
- text: JSON.stringify(
468
- {
469
- zipPath,
470
- model,
471
- language: language || null,
472
- transcriptions: [],
473
- failures: [],
474
- warning:
475
- "Local Whisper dependencies are missing. Fix the dependency warnings and retry transcription.",
476
- dependencyWarnings: whisperDependencyWarnings,
477
- },
478
- null,
479
- 2,
480
- ),
481
- }],
466
+ content: [
467
+ {
468
+ type: "text",
469
+ text: JSON.stringify(
470
+ {
471
+ zipPath,
472
+ model,
473
+ language: language || null,
474
+ transcriptions: [],
475
+ failures: [],
476
+ warning:
477
+ "Local Whisper dependencies are missing. Fix the dependency warnings and retry transcription.",
478
+ dependencyWarnings: whisperDependencyWarnings,
479
+ },
480
+ null,
481
+ 2,
482
+ ),
483
+ },
484
+ ],
482
485
  isError: true,
483
486
  };
484
487
  }
@@ -487,20 +490,22 @@ server.registerTool(
487
490
  const voiceEntries = entryNames.filter((entryName) => looksLikeVoiceNote(entryName));
488
491
  if (voiceEntries.length === 0) {
489
492
  return {
490
- content: [{
491
- type: "text",
492
- text: JSON.stringify(
493
- {
494
- zipPath,
495
- model,
496
- language: language || null,
497
- transcriptions: [],
498
- warning: "No voice-note files found in package.",
499
- },
500
- null,
501
- 2,
502
- ),
503
- }],
493
+ content: [
494
+ {
495
+ type: "text",
496
+ text: JSON.stringify(
497
+ {
498
+ zipPath,
499
+ model,
500
+ language: language || null,
501
+ transcriptions: [],
502
+ warning: "No voice-note files found in package.",
503
+ },
504
+ null,
505
+ 2,
506
+ ),
507
+ },
508
+ ],
504
509
  };
505
510
  }
506
511
 
@@ -562,7 +567,8 @@ server.registerTool(
562
567
  server.registerTool(
563
568
  "list_tracegist_packages",
564
569
  {
565
- description: "List TraceGist marker package zip files. Searches TRACEGIST_DIR (env var), falls back to ~/Downloads, or uses the given directory.",
570
+ description:
571
+ "List TraceGist marker package zip files. Searches TRACEGIST_DIR (env var), falls back to ~/Downloads, or uses the given directory.",
566
572
  inputSchema: z.object({
567
573
  directory: z.string().optional(),
568
574
  limit: z.number().int().min(1).max(200).optional(),
@@ -591,7 +597,11 @@ server.registerTool(
591
597
  async ({ zipPath }) => {
592
598
  const { zip, entryNames } = await readZipEntries(zipPath);
593
599
  const { manifest, manifestEntryName } = await tryReadManifest(zip);
594
- const { handoffMarkdown, handoffEntryName } = await tryReadHandoffMarkdown(zip, entryNames, manifest);
600
+ const { handoffMarkdown, handoffEntryName } = await tryReadHandoffMarkdown(
601
+ zip,
602
+ entryNames,
603
+ manifest,
604
+ );
595
605
 
596
606
  const overview = {
597
607
  zipPath,
@@ -637,17 +647,19 @@ server.registerTool(
637
647
  .filter((name) => /handoff|coding-agent/i.test(path.posix.basename(name)))
638
648
  .slice(0, 5);
639
649
  return {
640
- content: [{
641
- type: "text",
642
- text: [
643
- `No handoff markdown entry found in ${zipPath}.`,
644
- manifest?.handoffMarkdownFilename
645
- ? `Manifest handoff filename: ${manifest.handoffMarkdownFilename}`
646
- : "Manifest handoff filename: (missing)",
647
- `Matching entry candidates: ${hintedEntries.length > 0 ? hintedEntries.join(", ") : "(none)"}`,
648
- `Debug candidates: ${debugCandidates.length > 0 ? debugCandidates.join(", ") : "(none)"}`,
649
- ].join("\n"),
650
- }],
650
+ content: [
651
+ {
652
+ type: "text",
653
+ text: [
654
+ `No handoff markdown entry found in ${zipPath}.`,
655
+ manifest?.handoffMarkdownFilename
656
+ ? `Manifest handoff filename: ${manifest.handoffMarkdownFilename}`
657
+ : "Manifest handoff filename: (missing)",
658
+ `Matching entry candidates: ${hintedEntries.length > 0 ? hintedEntries.join(", ") : "(none)"}`,
659
+ `Debug candidates: ${debugCandidates.length > 0 ? debugCandidates.join(", ") : "(none)"}`,
660
+ ].join("\n"),
661
+ },
662
+ ],
651
663
  isError: true,
652
664
  };
653
665
  }
@@ -657,10 +669,12 @@ server.registerTool(
657
669
  // Full document retrieval
658
670
  if (section && section.toLowerCase() === "all") {
659
671
  return {
660
- content: [{
661
- type: "text",
662
- text: `# Source: ${handoffEntryName}\n\n${enrichedMarkdown}`,
663
- }],
672
+ content: [
673
+ {
674
+ type: "text",
675
+ text: `# Source: ${handoffEntryName}\n\n${enrichedMarkdown}`,
676
+ },
677
+ ],
664
678
  };
665
679
  }
666
680
 
@@ -669,10 +683,12 @@ server.registerTool(
669
683
  // No section requested — return table of contents
670
684
  if (!section) {
671
685
  return {
672
- content: [{
673
- type: "text",
674
- text: `# Source: ${handoffEntryName}\n\n${renderSectionToc(sections, zipPath)}`,
675
- }],
686
+ content: [
687
+ {
688
+ type: "text",
689
+ text: `# Source: ${handoffEntryName}\n\n${renderSectionToc(sections, zipPath)}`,
690
+ },
691
+ ],
676
692
  };
677
693
  }
678
694
 
@@ -682,18 +698,22 @@ server.registerTool(
682
698
  if (!match) {
683
699
  const available = sections.map((s) => s.name).join(", ");
684
700
  return {
685
- content: [{
686
- type: "text",
687
- text: `Section "${section}" not found. Available sections: ${available}`,
688
- }],
701
+ content: [
702
+ {
703
+ type: "text",
704
+ text: `Section "${section}" not found. Available sections: ${available}`,
705
+ },
706
+ ],
689
707
  isError: true,
690
708
  };
691
709
  }
692
710
  return {
693
- content: [{
694
- type: "text",
695
- text: match.content,
696
- }],
711
+ content: [
712
+ {
713
+ type: "text",
714
+ text: match.content,
715
+ },
716
+ ],
697
717
  };
698
718
  },
699
719
  );
@@ -749,7 +769,9 @@ server.registerTool(
749
769
  const outputPath = path.resolve(outDir, path.basename(entryName));
750
770
  if (!outputPath.startsWith(outDir + path.sep) && outputPath !== outDir) {
751
771
  return {
752
- content: [{ type: "text", text: `Refusing to write outside output directory: ${outputPath}` }],
772
+ content: [
773
+ { type: "text", text: `Refusing to write outside output directory: ${outputPath}` },
774
+ ],
753
775
  isError: true,
754
776
  };
755
777
  }
@@ -763,7 +785,9 @@ server.registerTool(
763
785
  );
764
786
 
765
787
  whisperDependencyWarnings = await checkWhisperDependencies();
766
- console.error(`[${BRIDGE_NAME}] Default package directory: ${DEFAULT_DOWNLOADS_DIR}${process.env.TRACEGIST_DIR ? " (from TRACEGIST_DIR)" : ""}`);
788
+ console.error(
789
+ `[${BRIDGE_NAME}] Default package directory: ${DEFAULT_DOWNLOADS_DIR}${process.env.TRACEGIST_DIR ? " (from TRACEGIST_DIR)" : ""}`,
790
+ );
767
791
  if (whisperDependencyWarnings.length > 0) {
768
792
  for (const warning of whisperDependencyWarnings) {
769
793
  console.error(`[${BRIDGE_NAME}] WARNING: ${warning}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tracegist-mcp-bridge",
3
- "version": "0.1.4",
3
+ "version": "0.2.2",
4
4
  "description": "Local-first MCP bridge for reading and transcribing TraceGist package zips.",
5
5
  "type": "module",
6
6
  "bin": {