@redaksjon/protokoll 1.0.2 → 1.0.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.
@@ -2,11 +2,11 @@
2
2
  import 'dotenv/config';
3
3
  import { Server } from '@modelcontextprotocol/sdk/server/index.js';
4
4
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
5
- import { ListToolsRequestSchema, CallToolRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema, ListPromptsRequestSchema, GetPromptRequestSchema } from '@modelcontextprotocol/sdk/types.js';
5
+ import { ListRootsRequestSchema, ListToolsRequestSchema, CallToolRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema, ListPromptsRequestSchema, GetPromptRequestSchema } from '@modelcontextprotocol/sdk/types.js';
6
6
  import { fileURLToPath } from 'node:url';
7
- import { resolve, dirname } from 'node:path';
8
- import { readFile, stat, mkdir, writeFile, unlink } from 'node:fs/promises';
9
- import { q as create, B as listTranscripts, d as create$1, c as create$2, g as getLogger, i as DEFAULT_OUTPUT_DIRECTORY, l as DEFAULT_OUTPUT_STRUCTURE, k as DEFAULT_OUTPUT_FILENAME_OPTIONS, p as create$3, w as DEFAULT_TRANSCRIPTION_MODEL, v as DEFAULT_MODEL, C as DEFAULT_TEMP_DIRECTORY, b as DEFAULT_MAX_AUDIO_SIZE, e as DEFAULT_INTERMEDIATE_DIRECTORY, D as DEFAULT_REASONING_LEVEL, r as create$4, E as create$6, F as create$8, G as processFeedback, H as applyChanges, I as combineTranscripts, J as editTranscript, K as parseTranscript, P as PROGRAM_NAME, V as VERSION } from '../transcript.js';
7
+ import { resolve, extname, join, dirname, basename } from 'node:path';
8
+ import { q as create, B as listTranscripts, m as DEFAULT_AUDIO_EXTENSIONS, d as create$1, c as create$2, g as getLogger, l as DEFAULT_OUTPUT_STRUCTURE, k as DEFAULT_OUTPUT_FILENAME_OPTIONS, p as create$3, w as DEFAULT_TRANSCRIPTION_MODEL, v as DEFAULT_MODEL, C as DEFAULT_TEMP_DIRECTORY, b as DEFAULT_MAX_AUDIO_SIZE, e as DEFAULT_INTERMEDIATE_DIRECTORY, D as DEFAULT_REASONING_LEVEL, r as create$4, E as create$6, F as create$8, G as processFeedback, H as applyChanges, I as combineTranscripts, J as editTranscript, K as parseTranscript, P as PROGRAM_NAME, V as VERSION } from '../transcript.js';
9
+ import { readFile, readdir, stat, mkdir, writeFile, unlink } from 'node:fs/promises';
10
10
  import * as yaml from 'js-yaml';
11
11
  import { readFileSync } from 'node:fs';
12
12
  import { glob } from 'glob';
@@ -18,12 +18,12 @@ import 'fs/promises';
18
18
  import 'openai';
19
19
  import 'fluent-ffmpeg';
20
20
  import 'node:os';
21
- import '@riotprompt/riotprompt';
21
+ import '@kjerneverk/riotprompt';
22
22
  import 'zod';
23
23
  import 'node:crypto';
24
24
  import 'html-to-text';
25
25
  import 'commander';
26
- import '@theunwalked/overcontext';
26
+ import '@utilarium/overcontext';
27
27
  import '@redaksjon/context';
28
28
  import 'winston';
29
29
 
@@ -53,6 +53,8 @@ function parseUri(uri) {
53
53
  case "entities":
54
54
  case "entities-list":
55
55
  return parseEntitiesListUri(uri, segments, params);
56
+ case "audio":
57
+ return parseAudioUri(uri, segments, params);
56
58
  default:
57
59
  throw new Error(`Unknown resource type: ${firstSegment}`);
58
60
  }
@@ -135,6 +137,27 @@ function parseEntitiesListUri(uri, segments, params) {
135
137
  entityType
136
138
  };
137
139
  }
140
+ function parseAudioUri(uri, segments, params) {
141
+ const audioType = segments[1];
142
+ if (audioType === "inbound") {
143
+ return {
144
+ scheme: SCHEME,
145
+ resourceType: "audio-inbound",
146
+ path: "audio/inbound",
147
+ params,
148
+ directory: params.directory
149
+ };
150
+ } else if (audioType === "processed") {
151
+ return {
152
+ scheme: SCHEME,
153
+ resourceType: "audio-processed",
154
+ path: "audio/processed",
155
+ params,
156
+ directory: params.directory
157
+ };
158
+ }
159
+ throw new Error(`Invalid audio URI: ${uri}. Expected protokoll://audio/inbound or protokoll://audio/processed`);
160
+ }
138
161
  function buildTranscriptUri(transcriptPath) {
139
162
  const encoded = encodeURIComponent(transcriptPath).replace(/%2F/g, "/");
140
163
  return `${SCHEME}://transcript/${encoded}`;
@@ -160,6 +183,18 @@ function buildTranscriptsListUri(options) {
160
183
  function buildEntitiesListUri(entityType) {
161
184
  return `${SCHEME}://entities/${entityType}`;
162
185
  }
186
+ function buildAudioInboundUri(directory) {
187
+ if (directory) {
188
+ return `${SCHEME}://audio/inbound?directory=${encodeURIComponent(directory)}`;
189
+ }
190
+ return `${SCHEME}://audio/inbound`;
191
+ }
192
+ function buildAudioProcessedUri(directory) {
193
+ if (directory) {
194
+ return `${SCHEME}://audio/processed?directory=${encodeURIComponent(directory)}`;
195
+ }
196
+ return `${SCHEME}://audio/processed`;
197
+ }
163
198
 
164
199
  const directResources = [
165
200
  // Will be populated dynamically based on context
@@ -194,15 +229,21 @@ const resourceTemplates = [
194
229
  name: "Entities List",
195
230
  description: "List of entities of a given type",
196
231
  mimeType: "application/json"
232
+ },
233
+ {
234
+ uriTemplate: "protokoll://audio/inbound?directory={directory}",
235
+ name: "Inbound Audio Files",
236
+ description: "List of audio files waiting to be processed",
237
+ mimeType: "application/json"
238
+ },
239
+ {
240
+ uriTemplate: "protokoll://audio/processed?directory={directory}",
241
+ name: "Processed Audio Files",
242
+ description: "List of audio files that have been processed",
243
+ mimeType: "application/json"
197
244
  }
198
245
  ];
199
- async function handleListResources(contextDirectory) {
200
- const dynamicResources = await getDynamicResources(contextDirectory);
201
- return {
202
- resources: [...directResources, ...dynamicResources],
203
- resourceTemplates
204
- };
205
- }
246
+
206
247
  async function getDynamicResources(contextDirectory) {
207
248
  const resources = [];
208
249
  try {
@@ -212,6 +253,7 @@ async function getDynamicResources(contextDirectory) {
212
253
  if (!context.hasContext()) {
213
254
  return resources;
214
255
  }
256
+ const config = context.getConfig();
215
257
  const dirs = context.getDiscoveredDirs();
216
258
  const configPath = dirs[0]?.path;
217
259
  if (configPath) {
@@ -222,37 +264,72 @@ async function getDynamicResources(contextDirectory) {
222
264
  mimeType: "application/json"
223
265
  });
224
266
  }
225
- } catch {
226
- }
227
- return resources;
228
- }
229
- async function handleReadResource(uri) {
230
- const parsed = parseUri(uri);
231
- switch (parsed.resourceType) {
232
- case "transcript":
233
- return readTranscriptResource(parsed.transcriptPath);
234
- case "entity": {
235
- const entityUri = parsed;
236
- return readEntityResource(entityUri.entityType, entityUri.entityId);
267
+ const inputDirectory = config.inputDirectory || "./recordings";
268
+ resources.push({
269
+ uri: buildAudioInboundUri(inputDirectory),
270
+ name: "Inbound Audio Files",
271
+ description: `Audio files waiting to be processed in ${inputDirectory}`,
272
+ mimeType: "application/json"
273
+ });
274
+ const processedDirectory = config.processedDirectory;
275
+ if (processedDirectory) {
276
+ resources.push({
277
+ uri: buildAudioProcessedUri(processedDirectory),
278
+ name: "Processed Audio Files",
279
+ description: `Audio files that have been processed in ${processedDirectory}`,
280
+ mimeType: "application/json"
281
+ });
237
282
  }
238
- case "config":
239
- return readConfigResource(parsed.configPath);
240
- case "transcripts-list": {
241
- const listUri = parsed;
242
- return readTranscriptsListResource({
243
- directory: listUri.directory,
244
- startDate: listUri.startDate,
245
- endDate: listUri.endDate,
246
- limit: listUri.limit,
247
- offset: listUri.offset
283
+ const entityCounts = {
284
+ projects: context.getAllProjects().length,
285
+ people: context.getAllPeople().length,
286
+ terms: context.getAllTerms().length,
287
+ companies: context.getAllCompanies().length
288
+ };
289
+ if (entityCounts.projects > 0) {
290
+ resources.push({
291
+ uri: buildEntitiesListUri("project"),
292
+ name: "All Projects",
293
+ description: `${entityCounts.projects} project(s) in context`,
294
+ mimeType: "application/json"
248
295
  });
249
296
  }
250
- case "entities-list":
251
- return readEntitiesListResource(parsed.entityType);
252
- default:
253
- throw new Error(`Unknown resource type: ${parsed.resourceType}`);
297
+ if (entityCounts.people > 0) {
298
+ resources.push({
299
+ uri: buildEntitiesListUri("person"),
300
+ name: "All People",
301
+ description: `${entityCounts.people} person/people in context`,
302
+ mimeType: "application/json"
303
+ });
304
+ }
305
+ if (entityCounts.terms > 0) {
306
+ resources.push({
307
+ uri: buildEntitiesListUri("term"),
308
+ name: "All Terms",
309
+ description: `${entityCounts.terms} term(s) in context`,
310
+ mimeType: "application/json"
311
+ });
312
+ }
313
+ if (entityCounts.companies > 0) {
314
+ resources.push({
315
+ uri: buildEntitiesListUri("company"),
316
+ name: "All Companies",
317
+ description: `${entityCounts.companies} company/companies in context`,
318
+ mimeType: "application/json"
319
+ });
320
+ }
321
+ const outputDirectory = config.outputDirectory || "~/notes";
322
+ resources.push({
323
+ uri: buildTranscriptsListUri({ directory: outputDirectory, limit: 10 }),
324
+ name: "Recent Transcripts",
325
+ description: `10 most recent transcripts in ${outputDirectory}`,
326
+ mimeType: "application/json"
327
+ });
328
+ } catch {
254
329
  }
330
+ return resources;
255
331
  }
332
+
256
333
  async function readTranscriptResource(transcriptPath) {
257
334
  const fullPath = transcriptPath.startsWith("/") ? transcriptPath : resolve(process.cwd(), transcriptPath);
258
335
  try {
@@ -269,84 +346,6 @@ async function readTranscriptResource(transcriptPath) {
269
346
  throw error;
270
347
  }
271
348
  }
272
- async function readEntityResource(entityType, entityId, contextDirectory) {
273
- const context = await create({
274
- startingDir: process.cwd()
275
- });
276
- let entity;
277
- switch (entityType) {
278
- case "person":
279
- entity = context.getPerson(entityId);
280
- break;
281
- case "project":
282
- entity = context.getProject(entityId);
283
- break;
284
- case "term":
285
- entity = context.getTerm(entityId);
286
- break;
287
- case "company":
288
- entity = context.getCompany(entityId);
289
- break;
290
- case "ignored":
291
- entity = context.getIgnored(entityId);
292
- break;
293
- default:
294
- throw new Error(`Unknown entity type: ${entityType}`);
295
- }
296
- if (!entity) {
297
- throw new Error(`${entityType} "${entityId}" not found`);
298
- }
299
- const yamlContent = yaml.dump(entity);
300
- return {
301
- uri: buildEntityUri(entityType, entityId),
302
- mimeType: "application/yaml",
303
- text: yamlContent
304
- };
305
- }
306
- async function readConfigResource(configPath) {
307
- const startDir = configPath || process.cwd();
308
- const context = await create({
309
- startingDir: startDir
310
- });
311
- if (!context.hasContext()) {
312
- throw new Error(`No Protokoll context found at or above: ${startDir}`);
313
- }
314
- const dirs = context.getDiscoveredDirs();
315
- const config = context.getConfig();
316
- const configData = {
317
- hasContext: true,
318
- discoveredDirectories: dirs.map((d) => ({
319
- path: d.path,
320
- level: d.level,
321
- isPrimary: d.level === 0
322
- })),
323
- entityCounts: {
324
- projects: context.getAllProjects().length,
325
- people: context.getAllPeople().length,
326
- terms: context.getAllTerms().length,
327
- companies: context.getAllCompanies().length,
328
- ignored: context.getAllIgnored().length
329
- },
330
- config: {
331
- outputDirectory: config.outputDirectory,
332
- outputStructure: config.outputStructure,
333
- model: config.model,
334
- smartAssistance: context.getSmartAssistanceConfig()
335
- },
336
- // Include URIs for easy navigation
337
- resourceUris: {
338
- projects: buildEntitiesListUri("project"),
339
- people: buildEntitiesListUri("person"),
340
- terms: buildEntitiesListUri("term"),
341
- companies: buildEntitiesListUri("company")
342
- }
343
- };
344
- return {
345
- uri: buildConfigUri(configPath),
346
- mimeType: "application/json",
347
- text: JSON.stringify(configData, null, 2)
348
- };
349
- }
350
349
  async function readTranscriptsListResource(options) {
351
350
  const { directory, startDate, endDate, limit = 50, offset = 0 } = options;
352
351
  if (!directory) {
@@ -388,6 +387,41 @@ async function readTranscriptsListResource(options) {
388
387
  text: JSON.stringify(responseData, null, 2)
389
388
  };
390
389
  }
390
+
391
+ async function readEntityResource(entityType, entityId, contextDirectory) {
392
+ const context = await create({
393
+ startingDir: process.cwd()
394
+ });
395
+ let entity;
396
+ switch (entityType) {
397
+ case "person":
398
+ entity = context.getPerson(entityId);
399
+ break;
400
+ case "project":
401
+ entity = context.getProject(entityId);
402
+ break;
403
+ case "term":
404
+ entity = context.getTerm(entityId);
405
+ break;
406
+ case "company":
407
+ entity = context.getCompany(entityId);
408
+ break;
409
+ case "ignored":
410
+ entity = context.getIgnored(entityId);
411
+ break;
412
+ default:
413
+ throw new Error(`Unknown entity type: ${entityType}`);
414
+ }
415
+ if (!entity) {
416
+ throw new Error(`${entityType} "${entityId}" not found`);
417
+ }
418
+ const yamlContent = yaml.dump(entity);
419
+ return {
420
+ uri: buildEntityUri(entityType, entityId),
421
+ mimeType: "application/yaml",
422
+ text: yamlContent
423
+ };
424
+ }
391
425
  async function readEntitiesListResource(entityType, contextDirectory) {
392
426
  const context = await create({
393
427
  startingDir: process.cwd()
@@ -456,6 +490,190 @@ async function readEntitiesListResource(entityType, contextDirectory) {
456
490
  };
457
491
  }
458
492
 
493
+ async function listAudioFiles(directory) {
494
+ const dirPath = resolve(directory);
495
+ try {
496
+ const entries = await readdir(dirPath, { withFileTypes: true });
497
+ const audioFiles = [];
498
+ for (const entry of entries) {
499
+ if (entry.isFile()) {
500
+ const ext = extname(entry.name).toLowerCase().substring(1);
501
+ if (DEFAULT_AUDIO_EXTENSIONS.includes(ext)) {
502
+ const filePath = join(dirPath, entry.name);
503
+ const stats = await stat(filePath);
504
+ audioFiles.push({
505
+ filename: entry.name,
506
+ path: filePath,
507
+ size: stats.size,
508
+ modified: stats.mtime.toISOString(),
509
+ extension: ext
510
+ });
511
+ }
512
+ }
513
+ }
514
+ audioFiles.sort(
515
+ (a, b) => new Date(b.modified).getTime() - new Date(a.modified).getTime()
516
+ );
517
+ return audioFiles;
518
+ } catch (error) {
519
+ if (error.code === "ENOENT") {
520
+ return [];
521
+ }
522
+ throw error;
523
+ }
524
+ }
525
+ function formatBytes(bytes) {
526
+ if (bytes === 0) return "0 B";
527
+ const k = 1024;
528
+ const sizes = ["B", "KB", "MB", "GB"];
529
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
530
+ return `${(bytes / Math.pow(k, i)).toFixed(2)} ${sizes[i]}`;
531
+ }
532
+ async function readAudioInboundResource(directory) {
533
+ const context = await create({
534
+ startingDir: directory || process.cwd()
535
+ });
536
+ if (!context.hasContext()) {
537
+ throw new Error("No Protokoll context found");
538
+ }
539
+ const config = context.getConfig();
540
+ const inputDirectory = directory || config.inputDirectory || "./recordings";
541
+ const audioFiles = await listAudioFiles(inputDirectory);
542
+ const responseData = {
543
+ directory: resolve(inputDirectory),
544
+ count: audioFiles.length,
545
+ totalSize: audioFiles.reduce((sum, f) => sum + f.size, 0),
546
+ files: audioFiles.map((f) => ({
547
+ filename: f.filename,
548
+ path: f.path,
549
+ size: f.size,
550
+ sizeHuman: formatBytes(f.size),
551
+ modified: f.modified,
552
+ extension: f.extension
553
+ })),
554
+ supportedExtensions: DEFAULT_AUDIO_EXTENSIONS
555
+ };
556
+ return {
557
+ uri: buildAudioInboundUri(inputDirectory),
558
+ mimeType: "application/json",
559
+ text: JSON.stringify(responseData, null, 2)
560
+ };
561
+ }
562
+ async function readAudioProcessedResource(directory) {
563
+ const context = await create({
564
+ startingDir: directory || process.cwd()
565
+ });
566
+ if (!context.hasContext()) {
567
+ throw new Error("No Protokoll context found");
568
+ }
569
+ const config = context.getConfig();
570
+ const processedDirectory = directory || config.processedDirectory || "./processed";
571
+ const audioFiles = await listAudioFiles(processedDirectory);
572
+ const responseData = {
573
+ directory: resolve(processedDirectory),
574
+ count: audioFiles.length,
575
+ totalSize: audioFiles.reduce((sum, f) => sum + f.size, 0),
576
+ files: audioFiles.map((f) => ({
577
+ filename: f.filename,
578
+ path: f.path,
579
+ size: f.size,
580
+ sizeHuman: formatBytes(f.size),
581
+ modified: f.modified,
582
+ extension: f.extension
583
+ })),
584
+ supportedExtensions: DEFAULT_AUDIO_EXTENSIONS
585
+ };
586
+ return {
587
+ uri: buildAudioProcessedUri(processedDirectory),
588
+ mimeType: "application/json",
589
+ text: JSON.stringify(responseData, null, 2)
590
+ };
591
+ }
592
+
593
+ async function readConfigResource(configPath) {
594
+ const startDir = configPath || process.cwd();
595
+ const context = await create({
596
+ startingDir: startDir
597
+ });
598
+ if (!context.hasContext()) {
599
+ throw new Error(`No Protokoll context found at or above: ${startDir}`);
600
+ }
601
+ const dirs = context.getDiscoveredDirs();
602
+ const config = context.getConfig();
603
+ const configData = {
604
+ hasContext: true,
605
+ discoveredDirectories: dirs.map((d) => ({
606
+ path: d.path,
607
+ level: d.level,
608
+ isPrimary: d.level === 0
609
+ })),
610
+ entityCounts: {
611
+ projects: context.getAllProjects().length,
612
+ people: context.getAllPeople().length,
613
+ terms: context.getAllTerms().length,
614
+ companies: context.getAllCompanies().length,
615
+ ignored: context.getAllIgnored().length
616
+ },
617
+ config: {
618
+ outputDirectory: config.outputDirectory,
619
+ outputStructure: config.outputStructure,
620
+ model: config.model,
621
+ smartAssistance: context.getSmartAssistanceConfig()
622
+ },
623
+ // Include URIs for easy navigation
624
+ resourceUris: {
625
+ projects: buildEntitiesListUri("project"),
626
+ people: buildEntitiesListUri("person"),
627
+ terms: buildEntitiesListUri("term"),
628
+ companies: buildEntitiesListUri("company")
629
+ }
630
+ };
631
+ return {
632
+ uri: buildConfigUri(configPath),
633
+ mimeType: "application/json",
634
+ text: JSON.stringify(configData, null, 2)
635
+ };
636
+ }
637
+
638
+ async function handleListResources(contextDirectory) {
639
+ const dynamicResources = await getDynamicResources(contextDirectory);
640
+ return {
641
+ resources: [...directResources, ...dynamicResources],
642
+ resourceTemplates
643
+ };
644
+ }
645
+ async function handleReadResource(uri) {
646
+ const parsed = parseUri(uri);
647
+ switch (parsed.resourceType) {
648
+ case "transcript":
649
+ return readTranscriptResource(parsed.transcriptPath);
650
+ case "entity": {
651
+ const entityUri = parsed;
652
+ return readEntityResource(entityUri.entityType, entityUri.entityId);
653
+ }
654
+ case "config":
655
+ return readConfigResource(parsed.configPath);
656
+ case "transcripts-list": {
657
+ const listUri = parsed;
658
+ return readTranscriptsListResource({
659
+ directory: listUri.directory,
660
+ startDate: listUri.startDate,
661
+ endDate: listUri.endDate,
662
+ limit: listUri.limit,
663
+ offset: listUri.offset
664
+ });
665
+ }
666
+ case "entities-list":
667
+ return readEntitiesListResource(parsed.entityType);
668
+ case "audio-inbound":
669
+ return readAudioInboundResource(parsed.directory);
670
+ case "audio-processed":
671
+ return readAudioProcessedResource(parsed.directory);
672
+ default:
673
+ throw new Error(`Unknown resource type: ${parsed.resourceType}`);
674
+ }
675
+ }
676
+
459
677
  const __filename$1 = fileURLToPath(import.meta.url);
460
678
  const __dirname$1 = dirname(__filename$1);
461
679
  function getPromptsDir() {
@@ -898,6 +1116,17 @@ async function fileExists(path) {
898
1116
  return false;
899
1117
  }
900
1118
  }
1119
+ async function getConfiguredDirectory(key, _contextDirectory) {
1120
+ const ServerConfig = await Promise.resolve().then(() => serverConfig$1);
1121
+ switch (key) {
1122
+ case "inputDirectory":
1123
+ return ServerConfig.getInputDirectory();
1124
+ case "outputDirectory":
1125
+ return ServerConfig.getOutputDirectory();
1126
+ case "processedDirectory":
1127
+ return ServerConfig.getProcessedDirectory() || resolve(process.cwd(), "./processed");
1128
+ }
1129
+ }
901
1130
  function slugify(text) {
902
1131
  return text.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/--+/g, "-").replace(/^-|-$/g, "");
903
1132
  }
@@ -1151,19 +1380,46 @@ async function handleSuggestProject(args) {
1151
1380
  };
1152
1381
  }
1153
1382
 
1383
+ async function findAudioFile(filenameOrPath) {
1384
+ if (filenameOrPath.startsWith("/") && await fileExists(filenameOrPath)) {
1385
+ return filenameOrPath;
1386
+ }
1387
+ const inputDirectory = await getConfiguredDirectory("inputDirectory");
1388
+ const entries = await readdir(inputDirectory, { withFileTypes: true });
1389
+ const matches = [];
1390
+ for (const entry of entries) {
1391
+ if (entry.isFile()) {
1392
+ const filename = entry.name;
1393
+ const ext = filename.split(".").pop()?.toLowerCase();
1394
+ if (ext && DEFAULT_AUDIO_EXTENSIONS.includes(ext)) {
1395
+ if (filename === filenameOrPath || filename.includes(filenameOrPath) || basename(filename, `.${ext}`) === filenameOrPath) {
1396
+ matches.push(join(inputDirectory, filename));
1397
+ }
1398
+ }
1399
+ }
1400
+ }
1401
+ if (matches.length === 0) {
1402
+ throw new Error(
1403
+ `No audio file found matching "${filenameOrPath}" in ${inputDirectory}. Try using the protokoll://audio/inbound resource to see available audio files.`
1404
+ );
1405
+ }
1406
+ if (matches.length === 1) {
1407
+ return matches[0];
1408
+ }
1409
+ const matchNames = matches.map((m) => basename(m)).join(", ");
1410
+ throw new Error(
1411
+ `Multiple audio files match "${filenameOrPath}": ${matchNames}. Please be more specific.`
1412
+ );
1413
+ }
1154
1414
  const processAudioTool = {
1155
1415
  name: "protokoll_process_audio",
1156
- description: "Process an audio file through Protokoll's intelligent transcription pipeline. IMPORTANT: Before calling this, use protokoll_discover_config or protokoll_suggest_project to understand which configuration/project should be used. This tool transcribes audio using Whisper, then enhances it with context-aware processing that corrects names, terms, and routes the output to the appropriate project folder. If no contextDirectory is specified, the tool walks up from the audio file to find .protokoll. Returns the enhanced transcript text and output file path.",
1416
+ description: "Process an audio file through Protokoll's intelligent transcription pipeline. You can provide either an absolute path OR just a filename/partial filename. If you provide a filename, it will search in the workspace's configured input directory. This tool uses workspace-level configuration automatically - no need to specify directories. Transcribes audio using Whisper, then enhances it with context-aware processing that corrects names, terms, and routes the output to the appropriate project folder. Returns the enhanced transcript text and output file path.",
1157
1417
  inputSchema: {
1158
1418
  type: "object",
1159
1419
  properties: {
1160
1420
  audioFile: {
1161
1421
  type: "string",
1162
- description: "Absolute path to the audio file to process (m4a, mp3, wav, webm, etc.)"
1163
- },
1164
- contextDirectory: {
1165
- type: "string",
1166
- description: "Path to the .protokoll context directory. If not specified, walks up from the audio file location to find one."
1422
+ description: 'Filename, partial filename, or absolute path to the audio file. Examples: "recording.m4a", "2026-01-29", "/full/path/to/audio.m4a"'
1167
1423
  },
1168
1424
  projectId: {
1169
1425
  type: "string",
@@ -1171,7 +1427,7 @@ const processAudioTool = {
1171
1427
  },
1172
1428
  outputDirectory: {
1173
1429
  type: "string",
1174
- description: "Override the default output directory"
1430
+ description: "Override the workspace output directory"
1175
1431
  },
1176
1432
  model: {
1177
1433
  type: "string",
@@ -1187,44 +1443,40 @@ const processAudioTool = {
1187
1443
  };
1188
1444
  const batchProcessTool = {
1189
1445
  name: "protokoll_batch_process",
1190
- description: "Process multiple audio files in a directory. Finds all audio files matching the configured extensions and processes them sequentially. Returns a summary of all processed files with their output paths.",
1446
+ description: "Process multiple audio files in a directory. If no directory is specified, uses the workspace's configured input directory. This tool uses workspace-level configuration automatically - no need to specify directories. Finds all audio files matching the configured extensions and processes them sequentially. Returns a summary of all processed files with their output paths.",
1191
1447
  inputSchema: {
1192
1448
  type: "object",
1193
1449
  properties: {
1194
1450
  inputDirectory: {
1195
1451
  type: "string",
1196
- description: "Absolute path to directory containing audio files"
1452
+ description: "Optional: Directory containing audio files. If not specified, uses the workspace input directory."
1197
1453
  },
1198
1454
  extensions: {
1199
1455
  type: "array",
1200
1456
  items: { type: "string" },
1201
1457
  description: 'Audio file extensions to process (default: [".m4a", ".mp3", ".wav", ".webm"])'
1202
1458
  },
1203
- contextDirectory: {
1204
- type: "string",
1205
- description: "Path to the .protokoll context directory"
1206
- },
1207
1459
  outputDirectory: {
1208
1460
  type: "string",
1209
- description: "Override the default output directory"
1461
+ description: "Override the workspace output directory"
1210
1462
  }
1211
1463
  },
1212
- required: ["inputDirectory"]
1464
+ required: []
1213
1465
  }
1214
1466
  };
1215
1467
  async function handleProcessAudio(args) {
1216
- const audioFile = resolve(args.audioFile);
1217
- if (!await fileExists(audioFile)) {
1218
- throw new Error(`Audio file not found: ${audioFile}`);
1219
- }
1220
- const context = await create({
1221
- startingDir: args.contextDirectory || dirname(audioFile)
1222
- });
1223
- const config = context.getConfig();
1224
- const outputDirectory = args.outputDirectory || config.outputDirectory || DEFAULT_OUTPUT_DIRECTORY;
1225
- const outputStructure = config.outputStructure || DEFAULT_OUTPUT_STRUCTURE;
1226
- const outputFilenameOptions = config.outputFilenameOptions || DEFAULT_OUTPUT_FILENAME_OPTIONS;
1227
- const processedDirectory = config.processedDirectory || void 0;
1468
+ const ServerConfig = await Promise.resolve().then(() => serverConfig$1);
1469
+ const audioFile = await findAudioFile(args.audioFile);
1470
+ const config = ServerConfig.getServerConfig();
1471
+ const context = config.context;
1472
+ if (!context) {
1473
+ throw new Error("Protokoll context not available. Ensure .protokoll directory exists in workspace.");
1474
+ }
1475
+ const contextConfig = context.getConfig();
1476
+ const outputDirectory = args.outputDirectory || config.outputDirectory;
1477
+ const outputStructure = contextConfig.outputStructure || DEFAULT_OUTPUT_STRUCTURE;
1478
+ const outputFilenameOptions = contextConfig.outputFilenameOptions || DEFAULT_OUTPUT_FILENAME_OPTIONS;
1479
+ const processedDirectory = config.processedDirectory ?? void 0;
1228
1480
  const { creationTime, hash } = await getAudioMetadata(audioFile);
1229
1481
  const pipeline = await create$3({
1230
1482
  model: args.model || DEFAULT_MODEL,
@@ -1236,13 +1488,13 @@ async function handleProcessAudio(args) {
1236
1488
  silent: true,
1237
1489
  debug: false,
1238
1490
  dryRun: false,
1239
- contextDirectory: args.contextDirectory,
1491
+ contextDirectory: config.workspaceRoot || void 0,
1240
1492
  intermediateDir: DEFAULT_INTERMEDIATE_DIRECTORY,
1241
1493
  keepIntermediates: false,
1242
1494
  outputDirectory,
1243
1495
  outputStructure,
1244
1496
  outputFilenameOptions,
1245
- processedDirectory,
1497
+ processedDirectory: processedDirectory || void 0,
1246
1498
  maxAudioSize: DEFAULT_MAX_AUDIO_SIZE,
1247
1499
  tempDirectory: DEFAULT_TEMP_DIRECTORY
1248
1500
  });
@@ -1263,7 +1515,7 @@ async function handleProcessAudio(args) {
1263
1515
  };
1264
1516
  }
1265
1517
  async function handleBatchProcess(args) {
1266
- const inputDir = resolve(args.inputDirectory);
1518
+ const inputDir = args.inputDirectory ? resolve(args.inputDirectory) : await getConfiguredDirectory("inputDirectory");
1267
1519
  const extensions = args.extensions || [".m4a", ".mp3", ".wav", ".webm"];
1268
1520
  if (!await fileExists(inputDir)) {
1269
1521
  throw new Error(`Input directory not found: ${inputDir}`);
@@ -1279,7 +1531,6 @@ async function handleBatchProcess(args) {
1279
1531
  try {
1280
1532
  const result = await handleProcessAudio({
1281
1533
  audioFile: file,
1282
- contextDirectory: args.contextDirectory,
1283
1534
  outputDirectory: args.outputDirectory
1284
1535
  });
1285
1536
  processed.push(result);
@@ -2184,7 +2435,7 @@ async function handleEditPerson(args) {
2184
2435
  } else if (existingPerson.sounds_like && (args.sounds_like !== void 0 || args.remove_sounds_like)) {
2185
2436
  delete updatedPerson.sounds_like;
2186
2437
  }
2187
- await context.saveEntity(updatedPerson);
2438
+ await context.saveEntity(updatedPerson, true);
2188
2439
  const changes = [];
2189
2440
  if (args.name !== void 0) changes.push(`name: "${args.name}"`);
2190
2441
  if (args.firstName !== void 0) changes.push(`firstName: "${args.firstName}"`);
@@ -2398,7 +2649,7 @@ async function handleEditProject(args) {
2398
2649
  updatedProject.relationships.relatedTerms = updatedRelatedTerms;
2399
2650
  }
2400
2651
  }
2401
- await context.saveEntity(updatedProject);
2652
+ await context.saveEntity(updatedProject, true);
2402
2653
  const changes = [];
2403
2654
  if (args.name !== void 0) changes.push(`name: "${args.name}"`);
2404
2655
  if (args.description !== void 0) changes.push(`description updated`);
@@ -2465,7 +2716,7 @@ async function handleUpdateProject(args) {
2465
2716
  },
2466
2717
  updatedAt: /* @__PURE__ */ new Date()
2467
2718
  };
2468
- await context.saveEntity(updatedProject);
2719
+ await context.saveEntity(updatedProject, true);
2469
2720
  return {
2470
2721
  success: true,
2471
2722
  message: `Updated project "${existingProject.name}" from source`,
@@ -2558,7 +2809,7 @@ async function handleEditTerm(args) {
2558
2809
  } else if (existingTerm.projects && (args.projects !== void 0 || args.remove_projects)) {
2559
2810
  delete updatedTerm.projects;
2560
2811
  }
2561
- await context.saveEntity(updatedTerm);
2812
+ await context.saveEntity(updatedTerm, true);
2562
2813
  const changes = [];
2563
2814
  if (args.expansion !== void 0) changes.push(`expansion: "${args.expansion}"`);
2564
2815
  if (args.domain !== void 0) changes.push(`domain: "${args.domain}"`);
@@ -2629,7 +2880,7 @@ async function handleUpdateTerm(args) {
2629
2880
  const projects = termContextHelper.findProjectsByTopic(suggestions.topics);
2630
2881
  suggestedProjects = projects.map((p) => p.id);
2631
2882
  }
2632
- await context.saveEntity(updatedTerm);
2883
+ await context.saveEntity(updatedTerm, true);
2633
2884
  return {
2634
2885
  success: true,
2635
2886
  message: `Updated term "${existingTerm.name}" from source`,
@@ -2876,15 +3127,48 @@ async function handleSuggestTermMetadata(args) {
2876
3127
  };
2877
3128
  }
2878
3129
 
3130
+ async function findTranscript(filenameOrPath, contextDirectory) {
3131
+ if (filenameOrPath.startsWith("/") && await fileExists(filenameOrPath)) {
3132
+ return filenameOrPath;
3133
+ }
3134
+ const outputDirectory = await getConfiguredDirectory("outputDirectory");
3135
+ const result = await listTranscripts({
3136
+ directory: outputDirectory,
3137
+ search: filenameOrPath,
3138
+ limit: 10
3139
+ });
3140
+ if (result.transcripts.length === 0) {
3141
+ throw new Error(
3142
+ `No transcript found matching "${filenameOrPath}" in ${outputDirectory}. Try using protokoll_list_transcripts to see available transcripts.`
3143
+ );
3144
+ }
3145
+ if (result.transcripts.length === 1) {
3146
+ return result.transcripts[0].path;
3147
+ }
3148
+ const exactMatch = result.transcripts.find(
3149
+ (t) => t.filename === filenameOrPath || t.filename.includes(filenameOrPath)
3150
+ );
3151
+ if (exactMatch) {
3152
+ return exactMatch.path;
3153
+ }
3154
+ const matches = result.transcripts.map((t) => t.filename).join(", ");
3155
+ throw new Error(
3156
+ `Multiple transcripts match "${filenameOrPath}": ${matches}. Please be more specific.`
3157
+ );
3158
+ }
2879
3159
  const readTranscriptTool = {
2880
3160
  name: "protokoll_read_transcript",
2881
- description: "Read a transcript file and parse its metadata and content. Returns structured data including title, metadata, routing info, and content.",
3161
+ description: "Read a transcript file and parse its metadata and content. You can provide either an absolute path OR just a filename/partial filename. If you provide a filename, it will search in the configured output directory. Returns structured data including title, metadata, routing info, and content.",
2882
3162
  inputSchema: {
2883
3163
  type: "object",
2884
3164
  properties: {
2885
3165
  transcriptPath: {
2886
3166
  type: "string",
2887
- description: "Absolute path to the transcript file"
3167
+ description: 'Filename, partial filename, or absolute path to the transcript. Examples: "meeting-notes.md", "2026-01-29", "/full/path/to/transcript.md"'
3168
+ },
3169
+ contextDirectory: {
3170
+ type: "string",
3171
+ description: "Optional: Path to the .protokoll context directory"
2888
3172
  }
2889
3173
  },
2890
3174
  required: ["transcriptPath"]
@@ -2892,13 +3176,13 @@ const readTranscriptTool = {
2892
3176
  };
2893
3177
  const listTranscriptsTool = {
2894
3178
  name: "protokoll_list_transcripts",
2895
- description: "List transcripts in a directory with pagination, filtering, and search. Returns transcript metadata including date, time, title, and file path. Supports sorting by date (default), filename, or title. Can filter by date range and search within transcript content.",
3179
+ description: "List transcripts with pagination, filtering, and search. If no directory is specified, uses the configured output directory. Returns transcript metadata including date, time, title, and file path. Supports sorting by date (default), filename, or title. Can filter by date range and search within transcript content.",
2896
3180
  inputSchema: {
2897
3181
  type: "object",
2898
3182
  properties: {
2899
3183
  directory: {
2900
3184
  type: "string",
2901
- description: "Directory to search for transcripts (searches recursively)"
3185
+ description: "Optional: Directory to search for transcripts (searches recursively). If not specified, uses the configured output directory."
2902
3186
  },
2903
3187
  limit: {
2904
3188
  type: "number",
@@ -2927,20 +3211,24 @@ const listTranscriptsTool = {
2927
3211
  search: {
2928
3212
  type: "string",
2929
3213
  description: "Search for transcripts containing this text (searches filename and content)"
3214
+ },
3215
+ contextDirectory: {
3216
+ type: "string",
3217
+ description: "Optional: Path to the .protokoll context directory"
2930
3218
  }
2931
3219
  },
2932
- required: ["directory"]
3220
+ required: []
2933
3221
  }
2934
3222
  };
2935
3223
  const editTranscriptTool = {
2936
3224
  name: "protokoll_edit_transcript",
2937
- description: "Edit an existing transcript's title and/or project assignment. IMPORTANT: When you change the title, this tool RENAMES THE FILE to match the new title (slugified). Always use this tool instead of directly editing transcript files when changing titles. Changing the project will update metadata and may move the file to a new location based on the project's routing configuration.",
3225
+ description: "Edit an existing transcript's title and/or project assignment. You can provide either an absolute path OR just a filename/partial filename. IMPORTANT: When you change the title, this tool RENAMES THE FILE to match the new title (slugified). Always use this tool instead of directly editing transcript files when changing titles. Changing the project will update metadata and may move the file to a new location based on the project's routing configuration.",
2938
3226
  inputSchema: {
2939
3227
  type: "object",
2940
3228
  properties: {
2941
3229
  transcriptPath: {
2942
3230
  type: "string",
2943
- description: "Absolute path to the transcript file"
3231
+ description: 'Filename, partial filename, or absolute path to the transcript. Examples: "meeting-notes.md", "2026-01-29", "/full/path/to/transcript.md"'
2944
3232
  },
2945
3233
  title: {
2946
3234
  type: "string",
@@ -2952,7 +3240,7 @@ const editTranscriptTool = {
2952
3240
  },
2953
3241
  contextDirectory: {
2954
3242
  type: "string",
2955
- description: "Path to the .protokoll context directory"
3243
+ description: "Optional: Path to the .protokoll context directory"
2956
3244
  }
2957
3245
  },
2958
3246
  required: ["transcriptPath"]
@@ -2960,14 +3248,14 @@ const editTranscriptTool = {
2960
3248
  };
2961
3249
  const combineTranscriptsTool = {
2962
3250
  name: "protokoll_combine_transcripts",
2963
- description: "Combine multiple transcripts into a single document. Source files are automatically deleted after combining. Metadata from the first transcript is preserved, and content is organized into sections.",
3251
+ description: "Combine multiple transcripts into a single document. You can provide absolute paths OR filenames/partial filenames. Source files are automatically deleted after combining. Metadata from the first transcript is preserved, and content is organized into sections.",
2964
3252
  inputSchema: {
2965
3253
  type: "object",
2966
3254
  properties: {
2967
3255
  transcriptPaths: {
2968
3256
  type: "array",
2969
3257
  items: { type: "string" },
2970
- description: "Array of transcript file paths to combine"
3258
+ description: 'Array of filenames, partial filenames, or absolute paths. Examples: ["meeting-1.md", "meeting-2.md"] or ["2026-01-29", "2026-01-30"]'
2971
3259
  },
2972
3260
  title: {
2973
3261
  type: "string",
@@ -2979,7 +3267,7 @@ const combineTranscriptsTool = {
2979
3267
  },
2980
3268
  contextDirectory: {
2981
3269
  type: "string",
2982
- description: "Path to the .protokoll context directory"
3270
+ description: "Optional: Path to the .protokoll context directory"
2983
3271
  }
2984
3272
  },
2985
3273
  required: ["transcriptPaths"]
@@ -2987,13 +3275,13 @@ const combineTranscriptsTool = {
2987
3275
  };
2988
3276
  const provideFeedbackTool = {
2989
3277
  name: "protokoll_provide_feedback",
2990
- description: 'Provide natural language feedback to correct a transcript. The feedback is processed by an agentic model that can: - Fix spelling and term errors - Add new terms, people, or companies to context - Change project assignment - Update the title Example: "YB should be Wibey" or "San Jay Grouper is actually Sanjay Gupta"',
3278
+ description: 'Provide natural language feedback to correct a transcript. You can provide either an absolute path OR just a filename/partial filename. The feedback is processed by an agentic model that can: - Fix spelling and term errors - Add new terms, people, or companies to context - Change project assignment - Update the title Example: "YB should be Wibey" or "San Jay Grouper is actually Sanjay Gupta"',
2991
3279
  inputSchema: {
2992
3280
  type: "object",
2993
3281
  properties: {
2994
3282
  transcriptPath: {
2995
3283
  type: "string",
2996
- description: "Absolute path to the transcript file"
3284
+ description: 'Filename, partial filename, or absolute path to the transcript. Examples: "meeting-notes.md", "2026-01-29", "/full/path/to/transcript.md"'
2997
3285
  },
2998
3286
  feedback: {
2999
3287
  type: "string",
@@ -3005,17 +3293,14 @@ const provideFeedbackTool = {
3005
3293
  },
3006
3294
  contextDirectory: {
3007
3295
  type: "string",
3008
- description: "Path to the .protokoll context directory"
3296
+ description: "Optional: Path to the .protokoll context directory"
3009
3297
  }
3010
3298
  },
3011
3299
  required: ["transcriptPath", "feedback"]
3012
3300
  }
3013
3301
  };
3014
3302
  async function handleReadTranscript(args) {
3015
- const transcriptPath = resolve(args.transcriptPath);
3016
- if (!await fileExists(transcriptPath)) {
3017
- throw new Error(`Transcript not found: ${transcriptPath}`);
3018
- }
3303
+ const transcriptPath = await findTranscript(args.transcriptPath, args.contextDirectory);
3019
3304
  const parsed = await parseTranscript(transcriptPath);
3020
3305
  return {
3021
3306
  filePath: transcriptPath,
@@ -3026,7 +3311,7 @@ async function handleReadTranscript(args) {
3026
3311
  };
3027
3312
  }
3028
3313
  async function handleListTranscripts(args) {
3029
- const directory = resolve(args.directory);
3314
+ const directory = args.directory ? resolve(args.directory) : await getConfiguredDirectory("outputDirectory", args.contextDirectory);
3030
3315
  if (!await fileExists(directory)) {
3031
3316
  throw new Error(`Directory not found: ${directory}`);
3032
3317
  }
@@ -3065,16 +3350,14 @@ async function handleListTranscripts(args) {
3065
3350
  };
3066
3351
  }
3067
3352
  async function handleEditTranscript(args) {
3068
- const transcriptPath = resolve(args.transcriptPath);
3069
- if (!await fileExists(transcriptPath)) {
3070
- throw new Error(`Transcript not found: ${transcriptPath}`);
3071
- }
3353
+ const transcriptPath = await findTranscript(args.transcriptPath, args.contextDirectory);
3072
3354
  if (!args.title && !args.projectId) {
3073
3355
  throw new Error("Must specify title and/or projectId");
3074
3356
  }
3075
3357
  const result = await editTranscript(transcriptPath, {
3076
3358
  title: args.title,
3077
- projectId: args.projectId
3359
+ projectId: args.projectId,
3360
+ contextDirectory: args.contextDirectory
3078
3361
  });
3079
3362
  await mkdir(dirname(result.outputPath), { recursive: true });
3080
3363
  await writeFile(result.outputPath, result.content, "utf-8");
@@ -3093,16 +3376,15 @@ async function handleCombineTranscripts(args) {
3093
3376
  if (args.transcriptPaths.length < 2) {
3094
3377
  throw new Error("At least 2 transcript files are required");
3095
3378
  }
3379
+ const resolvedPaths = [];
3096
3380
  for (const path of args.transcriptPaths) {
3097
- const resolved = resolve(path);
3098
- if (!await fileExists(resolved)) {
3099
- throw new Error(`Transcript not found: ${resolved}`);
3100
- }
3381
+ const resolved = await findTranscript(path, args.contextDirectory);
3382
+ resolvedPaths.push(resolved);
3101
3383
  }
3102
- const resolvedPaths = args.transcriptPaths.map((p) => resolve(p));
3103
3384
  const result = await combineTranscripts(resolvedPaths, {
3104
3385
  title: args.title,
3105
- projectId: args.projectId
3386
+ projectId: args.projectId,
3387
+ contextDirectory: args.contextDirectory
3106
3388
  });
3107
3389
  await mkdir(dirname(result.outputPath), { recursive: true });
3108
3390
  await writeFile(result.outputPath, result.content, "utf-8");
@@ -3123,10 +3405,7 @@ async function handleCombineTranscripts(args) {
3123
3405
  };
3124
3406
  }
3125
3407
  async function handleProvideFeedback(args) {
3126
- const transcriptPath = resolve(args.transcriptPath);
3127
- if (!await fileExists(transcriptPath)) {
3128
- throw new Error(`Transcript not found: ${transcriptPath}`);
3129
- }
3408
+ const transcriptPath = await findTranscript(args.transcriptPath, args.contextDirectory);
3130
3409
  const transcriptContent = await readFile(transcriptPath, "utf-8");
3131
3410
  const context = await create({
3132
3411
  startingDir: args.contextDirectory || dirname(transcriptPath)
@@ -3288,6 +3567,151 @@ async function handleToolCall(name, args) {
3288
3567
  }
3289
3568
  }
3290
3569
 
3570
+ let cachedRoots = null;
3571
+ function setRoots(roots) {
3572
+ cachedRoots = roots;
3573
+ }
3574
+ function getCachedRoots() {
3575
+ return cachedRoots;
3576
+ }
3577
+ function fileUriToPath(uri) {
3578
+ if (!uri.startsWith("file://")) {
3579
+ return null;
3580
+ }
3581
+ try {
3582
+ const url = new URL(uri);
3583
+ return decodeURIComponent(url.pathname);
3584
+ } catch {
3585
+ return null;
3586
+ }
3587
+ }
3588
+
3589
+ let serverConfig = {
3590
+ context: null,
3591
+ workspaceRoot: null,
3592
+ inputDirectory: null,
3593
+ outputDirectory: null,
3594
+ processedDirectory: null,
3595
+ initialized: false
3596
+ };
3597
+ async function initializeServerConfig(roots) {
3598
+ const workspaceRoot = roots.length > 0 ? fileUriToPath(roots[0].uri) : null;
3599
+ if (!workspaceRoot) {
3600
+ serverConfig = {
3601
+ context: null,
3602
+ workspaceRoot: process.cwd(),
3603
+ inputDirectory: resolve(process.cwd(), "./recordings"),
3604
+ outputDirectory: resolve(process.cwd(), "./notes"),
3605
+ processedDirectory: resolve(process.cwd(), "./processed"),
3606
+ initialized: true
3607
+ };
3608
+ return;
3609
+ }
3610
+ try {
3611
+ const context = await create({
3612
+ startingDir: workspaceRoot
3613
+ });
3614
+ const config = context.getConfig();
3615
+ serverConfig = {
3616
+ context,
3617
+ workspaceRoot,
3618
+ inputDirectory: resolveDirectory(config.inputDirectory, workspaceRoot, "./recordings"),
3619
+ outputDirectory: resolveDirectory(config.outputDirectory, workspaceRoot, "./notes"),
3620
+ processedDirectory: resolveDirectory(config.processedDirectory, workspaceRoot, "./processed"),
3621
+ initialized: true
3622
+ };
3623
+ } catch {
3624
+ serverConfig = {
3625
+ context: null,
3626
+ workspaceRoot,
3627
+ inputDirectory: resolve(workspaceRoot, "./recordings"),
3628
+ outputDirectory: resolve(workspaceRoot, "./notes"),
3629
+ processedDirectory: resolve(workspaceRoot, "./processed"),
3630
+ initialized: true
3631
+ };
3632
+ }
3633
+ }
3634
+ async function reloadServerConfig(roots) {
3635
+ await initializeServerConfig(roots);
3636
+ }
3637
+ function clearServerConfig() {
3638
+ serverConfig = {
3639
+ context: null,
3640
+ workspaceRoot: null,
3641
+ inputDirectory: null,
3642
+ outputDirectory: null,
3643
+ processedDirectory: null,
3644
+ initialized: false
3645
+ };
3646
+ }
3647
+ function getServerConfig() {
3648
+ if (!serverConfig.initialized) {
3649
+ throw new Error("Server configuration not initialized. Call initializeServerConfig() first.");
3650
+ }
3651
+ return {
3652
+ context: serverConfig.context,
3653
+ workspaceRoot: serverConfig.workspaceRoot,
3654
+ inputDirectory: serverConfig.inputDirectory,
3655
+ outputDirectory: serverConfig.outputDirectory,
3656
+ processedDirectory: serverConfig.processedDirectory,
3657
+ initialized: serverConfig.initialized
3658
+ };
3659
+ }
3660
+ function getContext() {
3661
+ return serverConfig.context;
3662
+ }
3663
+ function getWorkspaceRoot() {
3664
+ return serverConfig.workspaceRoot;
3665
+ }
3666
+ function getInputDirectory() {
3667
+ if (!serverConfig.initialized || !serverConfig.inputDirectory) {
3668
+ return resolve(process.cwd(), "./recordings");
3669
+ }
3670
+ return serverConfig.inputDirectory;
3671
+ }
3672
+ function getOutputDirectory() {
3673
+ if (!serverConfig.initialized || !serverConfig.outputDirectory) {
3674
+ return resolve(process.cwd(), "./notes");
3675
+ }
3676
+ return serverConfig.outputDirectory;
3677
+ }
3678
+ function getProcessedDirectory() {
3679
+ if (!serverConfig.initialized) {
3680
+ return null;
3681
+ }
3682
+ return serverConfig.processedDirectory;
3683
+ }
3684
+ function isInitialized() {
3685
+ return serverConfig.initialized;
3686
+ }
3687
+ function resolveDirectory(configValue, workspaceRoot, defaultRelative) {
3688
+ if (configValue) {
3689
+ if (configValue.startsWith("~")) {
3690
+ const homeDir = process.env.HOME || process.env.USERPROFILE || "";
3691
+ return resolve(homeDir, configValue.substring(1));
3692
+ }
3693
+ if (configValue.startsWith("/")) {
3694
+ return configValue;
3695
+ }
3696
+ return resolve(workspaceRoot, configValue);
3697
+ }
3698
+ return resolve(workspaceRoot, defaultRelative);
3699
+ }
3700
+
3701
+ const serverConfig$1 = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.defineProperty({
3702
+ __proto__: null,
3703
+ clearServerConfig,
3704
+ getContext,
3705
+ getInputDirectory,
3706
+ getOutputDirectory,
3707
+ getProcessedDirectory,
3708
+ getServerConfig,
3709
+ getWorkspaceRoot,
3710
+ initializeServerConfig,
3711
+ isInitialized,
3712
+ reloadServerConfig
3713
+ }, Symbol.toStringTag, { value: 'Module' }));
3714
+
3291
3715
  async function main() {
3292
3716
  const server = new Server(
3293
3717
  {
@@ -3308,6 +3732,10 @@ async function main() {
3308
3732
  }
3309
3733
  }
3310
3734
  );
3735
+ server.setRequestHandler(ListRootsRequestSchema, async () => {
3736
+ const roots = getCachedRoots() || [];
3737
+ return { roots };
3738
+ });
3311
3739
  server.setRequestHandler(ListToolsRequestSchema, async () => ({
3312
3740
  tools
3313
3741
  }));
@@ -3354,6 +3782,13 @@ async function main() {
3354
3782
  });
3355
3783
  const transport = new StdioServerTransport();
3356
3784
  await server.connect(transport);
3785
+ const workspaceRoot = process.env.WORKSPACE_ROOT || process.cwd();
3786
+ const initialRoots = [{
3787
+ uri: `file://${workspaceRoot}`,
3788
+ name: "Workspace"
3789
+ }];
3790
+ setRoots(initialRoots);
3791
+ await initializeServerConfig(initialRoots);
3357
3792
  await new Promise(() => {
3358
3793
  });
3359
3794
  }