@riotprompt/riotdoc 1.0.3-dev.0 → 1.0.4

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.
@@ -0,0 +1,1109 @@
1
+ #!/usr/bin/env node
2
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import { z } from "zod";
5
+ import { c as createWorkspace, b as loadVoice } from "./loader-DJHV70rz.js";
6
+ import { join, dirname, resolve } from "node:path";
7
+ import { mkdir, appendFile, readFile, writeFile } from "node:fs/promises";
8
+ import { l as loadDocument, c as loadOutline, a as loadObjectives } from "./loader-Cvfo7vBn.js";
9
+ import { parse } from "yaml";
10
+ import { readFileSync } from "node:fs";
11
+ import { fileURLToPath } from "node:url";
12
+ function formatTimestamp() {
13
+ return (/* @__PURE__ */ new Date()).toISOString();
14
+ }
15
+ async function executeCommand(args, context, commandFn, resultBuilder) {
16
+ const originalCwd = process.cwd();
17
+ const logs = [];
18
+ try {
19
+ if (args.path) {
20
+ process.chdir(args.path);
21
+ logs.push(`Changed to directory: ${args.path}`);
22
+ }
23
+ const result = await commandFn();
24
+ if (args.path) {
25
+ process.chdir(originalCwd);
26
+ }
27
+ const data = resultBuilder ? resultBuilder(result, args, originalCwd) : { result, path: args.path || originalCwd };
28
+ return {
29
+ success: true,
30
+ data,
31
+ message: args.dry_run ? "Dry run completed" : "Command completed successfully",
32
+ logs: logs.length > 0 ? logs : void 0
33
+ };
34
+ } catch (error) {
35
+ if (args.path && process.cwd() !== originalCwd) {
36
+ try {
37
+ process.chdir(originalCwd);
38
+ } catch {
39
+ }
40
+ }
41
+ return {
42
+ success: false,
43
+ error: error.message || "Command failed",
44
+ context: {
45
+ path: args.path || originalCwd,
46
+ command: error.command
47
+ },
48
+ logs: logs.length > 0 ? logs : void 0
49
+ };
50
+ }
51
+ }
52
+ async function executeCreate(args, context) {
53
+ return executeCommand(
54
+ args,
55
+ context,
56
+ async () => {
57
+ const { join: join2, resolve: resolve2 } = await import("node:path");
58
+ const basePath = args.base_path || process.cwd();
59
+ const workspacePath = resolve2(join2(basePath, args.name));
60
+ const title = args.title || args.name.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
61
+ await createWorkspace({
62
+ path: workspacePath,
63
+ id: args.name,
64
+ title,
65
+ type: args.type,
66
+ objectives: {
67
+ primaryGoal: args.primary_goal || "",
68
+ secondaryGoals: [],
69
+ keyTakeaways: []
70
+ }
71
+ });
72
+ return {
73
+ workspacePath,
74
+ name: args.name,
75
+ title,
76
+ type: args.type
77
+ };
78
+ },
79
+ (result) => ({
80
+ workspace: result.workspacePath,
81
+ name: result.name,
82
+ title: result.title,
83
+ type: result.type,
84
+ nextSteps: [
85
+ "Edit voice/tone.md to define your writing voice",
86
+ "Edit OBJECTIVES.md to refine your goals",
87
+ "Run: riotdoc_outline to generate outline",
88
+ "Run: riotdoc_draft to create first draft"
89
+ ]
90
+ })
91
+ );
92
+ }
93
+ const CheckpointCreateSchema = z.object({
94
+ path: z.string().optional().describe("Path to document directory"),
95
+ name: z.string().describe("Checkpoint name (kebab-case)"),
96
+ message: z.string().describe("Description of why checkpoint created"),
97
+ capturePrompt: z.boolean().optional().default(true).describe("Capture conversation context")
98
+ });
99
+ const CheckpointListSchema = z.object({
100
+ path: z.string().optional().describe("Path to document directory")
101
+ });
102
+ const CheckpointShowSchema = z.object({
103
+ path: z.string().optional().describe("Path to document directory"),
104
+ checkpoint: z.string().describe("Checkpoint name")
105
+ });
106
+ const CheckpointRestoreSchema = z.object({
107
+ path: z.string().optional().describe("Path to document directory"),
108
+ checkpoint: z.string().describe("Checkpoint name")
109
+ });
110
+ const HistoryShowSchema = z.object({
111
+ path: z.string().optional().describe("Path to document directory"),
112
+ since: z.string().optional().describe("Show events since this ISO timestamp"),
113
+ eventType: z.string().optional().describe("Filter by event type"),
114
+ limit: z.number().optional().describe("Maximum number of events to show")
115
+ });
116
+ async function logEvent(docPath, event) {
117
+ const historyDir = join(docPath, ".history");
118
+ await mkdir(historyDir, { recursive: true });
119
+ const timelinePath = join(historyDir, "timeline.jsonl");
120
+ const line = JSON.stringify(event) + "\n";
121
+ await appendFile(timelinePath, line);
122
+ }
123
+ ({
124
+ inputSchema: CheckpointCreateSchema.shape
125
+ });
126
+ ({
127
+ inputSchema: CheckpointListSchema.shape
128
+ });
129
+ ({
130
+ inputSchema: CheckpointShowSchema.shape
131
+ });
132
+ ({
133
+ inputSchema: CheckpointRestoreSchema.shape
134
+ });
135
+ ({
136
+ inputSchema: HistoryShowSchema.shape
137
+ });
138
+ const InsertSectionSchema = z.object({
139
+ path: z.string().optional().describe("Path to document directory"),
140
+ title: z.string().describe("Section title"),
141
+ position: z.number().optional().describe("Position to insert (1-based, optional)"),
142
+ after: z.string().optional().describe("Insert after this section title (optional)")
143
+ });
144
+ const RenameSectionSchema = z.object({
145
+ path: z.string().optional().describe("Path to document directory"),
146
+ oldTitle: z.string().describe("Current section title"),
147
+ newTitle: z.string().describe("New section title")
148
+ });
149
+ const DeleteSectionSchema = z.object({
150
+ path: z.string().optional().describe("Path to document directory"),
151
+ title: z.string().describe("Section title to delete")
152
+ });
153
+ const MoveSectionSchema = z.object({
154
+ path: z.string().optional().describe("Path to document directory"),
155
+ title: z.string().describe("Section title to move"),
156
+ position: z.number().describe("New position (1-based)")
157
+ });
158
+ function parseOutline(content) {
159
+ const lines = content.split("\n");
160
+ const sections = [];
161
+ for (const line of lines) {
162
+ if (line.match(/^##\s+/)) {
163
+ sections.push(line);
164
+ }
165
+ }
166
+ return sections;
167
+ }
168
+ async function insertSection(args) {
169
+ const docPath = args.path || process.cwd();
170
+ const outlinePath = join(docPath, "outline.md");
171
+ const content = await readFile(outlinePath, "utf-8");
172
+ const lines = content.split("\n");
173
+ let insertIndex;
174
+ if (args.after) {
175
+ const afterIndex = lines.findIndex(
176
+ (line) => line.toLowerCase().includes(args.after.toLowerCase())
177
+ );
178
+ if (afterIndex === -1) {
179
+ throw new Error(`Section not found: ${args.after}`);
180
+ }
181
+ insertIndex = afterIndex + 1;
182
+ } else if (args.position) {
183
+ const sections = parseOutline(content);
184
+ if (args.position < 1 || args.position > sections.length + 1) {
185
+ throw new Error(`Invalid position: ${args.position}. Must be between 1 and ${sections.length + 1}`);
186
+ }
187
+ let sectionCount = 0;
188
+ insertIndex = 0;
189
+ for (let i = 0; i < lines.length; i++) {
190
+ if (lines[i].match(/^##\s+/)) {
191
+ sectionCount++;
192
+ if (sectionCount === args.position) {
193
+ insertIndex = i;
194
+ break;
195
+ }
196
+ }
197
+ }
198
+ if (insertIndex === 0) {
199
+ insertIndex = lines.length;
200
+ }
201
+ } else {
202
+ insertIndex = lines.length;
203
+ }
204
+ const newSection = `## ${args.title}`;
205
+ lines.splice(insertIndex, 0, newSection, "");
206
+ await writeFile(outlinePath, lines.join("\n"));
207
+ await logEvent(docPath, {
208
+ timestamp: formatTimestamp(),
209
+ type: "outline_created",
210
+ data: {
211
+ action: "insert",
212
+ title: args.title,
213
+ position: insertIndex
214
+ }
215
+ });
216
+ return `✅ Section inserted: "${args.title}" at position ${insertIndex}`;
217
+ }
218
+ async function renameSection(args) {
219
+ const docPath = args.path || process.cwd();
220
+ const outlinePath = join(docPath, "outline.md");
221
+ const content = await readFile(outlinePath, "utf-8");
222
+ const lines = content.split("\n");
223
+ let found = false;
224
+ for (let i = 0; i < lines.length; i++) {
225
+ if (lines[i].match(/^##\s+/) && lines[i].toLowerCase().includes(args.oldTitle.toLowerCase())) {
226
+ lines[i] = `## ${args.newTitle}`;
227
+ found = true;
228
+ break;
229
+ }
230
+ }
231
+ if (!found) {
232
+ throw new Error(`Section not found: ${args.oldTitle}`);
233
+ }
234
+ await writeFile(outlinePath, lines.join("\n"));
235
+ await logEvent(docPath, {
236
+ timestamp: formatTimestamp(),
237
+ type: "outline_created",
238
+ data: {
239
+ action: "rename",
240
+ oldTitle: args.oldTitle,
241
+ newTitle: args.newTitle
242
+ }
243
+ });
244
+ return `✅ Section renamed: "${args.oldTitle}" → "${args.newTitle}"`;
245
+ }
246
+ async function deleteSection(args) {
247
+ const docPath = args.path || process.cwd();
248
+ const outlinePath = join(docPath, "outline.md");
249
+ const content = await readFile(outlinePath, "utf-8");
250
+ const lines = content.split("\n");
251
+ let found = false;
252
+ let deleteIndex = -1;
253
+ for (let i = 0; i < lines.length; i++) {
254
+ if (lines[i].match(/^##\s+/) && lines[i].toLowerCase().includes(args.title.toLowerCase())) {
255
+ deleteIndex = i;
256
+ found = true;
257
+ break;
258
+ }
259
+ }
260
+ if (!found) {
261
+ throw new Error(`Section not found: ${args.title}`);
262
+ }
263
+ lines.splice(deleteIndex, lines[deleteIndex + 1] === "" ? 2 : 1);
264
+ await writeFile(outlinePath, lines.join("\n"));
265
+ await logEvent(docPath, {
266
+ timestamp: formatTimestamp(),
267
+ type: "outline_created",
268
+ data: {
269
+ action: "delete",
270
+ title: args.title
271
+ }
272
+ });
273
+ return `✅ Section deleted: "${args.title}"`;
274
+ }
275
+ async function moveSection(args) {
276
+ const docPath = args.path || process.cwd();
277
+ const outlinePath = join(docPath, "outline.md");
278
+ const content = await readFile(outlinePath, "utf-8");
279
+ const lines = content.split("\n");
280
+ let sectionIndex = -1;
281
+ let sectionLine = "";
282
+ for (let i = 0; i < lines.length; i++) {
283
+ if (lines[i].match(/^##\s+/) && lines[i].toLowerCase().includes(args.title.toLowerCase())) {
284
+ sectionIndex = i;
285
+ sectionLine = lines[i];
286
+ break;
287
+ }
288
+ }
289
+ if (sectionIndex === -1) {
290
+ throw new Error(`Section not found: ${args.title}`);
291
+ }
292
+ lines.splice(sectionIndex, 1);
293
+ const sections = parseOutline(lines.join("\n"));
294
+ if (args.position < 1 || args.position > sections.length + 1) {
295
+ throw new Error(`Invalid position: ${args.position}. Must be between 1 and ${sections.length + 1}`);
296
+ }
297
+ let insertIndex = 0;
298
+ let sectionCount = 0;
299
+ for (let i = 0; i < lines.length; i++) {
300
+ if (lines[i].match(/^##\s+/)) {
301
+ sectionCount++;
302
+ if (sectionCount === args.position) {
303
+ insertIndex = i;
304
+ break;
305
+ }
306
+ }
307
+ }
308
+ if (insertIndex === 0 && args.position > sections.length) {
309
+ insertIndex = lines.length;
310
+ }
311
+ lines.splice(insertIndex, 0, sectionLine);
312
+ await writeFile(outlinePath, lines.join("\n"));
313
+ await logEvent(docPath, {
314
+ timestamp: formatTimestamp(),
315
+ type: "outline_created",
316
+ data: {
317
+ action: "move",
318
+ title: args.title,
319
+ newPosition: args.position
320
+ }
321
+ });
322
+ return `✅ Section moved: "${args.title}" to position ${args.position}`;
323
+ }
324
+ async function executeInsertSection(args, _context) {
325
+ try {
326
+ const validated = InsertSectionSchema.parse(args);
327
+ const result = await insertSection(validated);
328
+ return { success: true, data: { message: result } };
329
+ } catch (error) {
330
+ return { success: false, error: error.message };
331
+ }
332
+ }
333
+ async function executeRenameSection(args, _context) {
334
+ try {
335
+ const validated = RenameSectionSchema.parse(args);
336
+ const result = await renameSection(validated);
337
+ return { success: true, data: { message: result } };
338
+ } catch (error) {
339
+ return { success: false, error: error.message };
340
+ }
341
+ }
342
+ async function executeDeleteSection(args, _context) {
343
+ try {
344
+ const validated = DeleteSectionSchema.parse(args);
345
+ const result = await deleteSection(validated);
346
+ return { success: true, data: { message: result } };
347
+ } catch (error) {
348
+ return { success: false, error: error.message };
349
+ }
350
+ }
351
+ async function executeMoveSection(args, _context) {
352
+ try {
353
+ const validated = MoveSectionSchema.parse(args);
354
+ const result = await moveSection(validated);
355
+ return { success: true, data: { message: result } };
356
+ } catch (error) {
357
+ return { success: false, error: error.message };
358
+ }
359
+ }
360
+ ({
361
+ inputSchema: InsertSectionSchema.shape
362
+ });
363
+ ({
364
+ inputSchema: RenameSectionSchema.shape
365
+ });
366
+ ({
367
+ inputSchema: DeleteSectionSchema.shape
368
+ });
369
+ ({
370
+ inputSchema: MoveSectionSchema.shape
371
+ });
372
+ async function executeDraft(args, _context) {
373
+ const workspacePath = args.path || process.cwd();
374
+ return {
375
+ success: true,
376
+ data: {
377
+ action: "pending",
378
+ path: workspacePath,
379
+ note: "Draft creation implementation pending - requires AI integration",
380
+ assistanceLevel: args.assistance_level
381
+ },
382
+ message: "Draft command - implementation pending"
383
+ };
384
+ }
385
+ async function executeStatus(args, context) {
386
+ return executeCommand(
387
+ args,
388
+ context,
389
+ async () => {
390
+ const workspacePath = args.path || process.cwd();
391
+ const doc = await loadDocument(workspacePath);
392
+ if (!doc) {
393
+ throw new Error("Not a RiotDoc workspace");
394
+ }
395
+ return {
396
+ path: workspacePath,
397
+ title: doc.config.title,
398
+ type: doc.config.type,
399
+ status: doc.config.status,
400
+ createdAt: doc.config.createdAt.toISOString(),
401
+ updatedAt: doc.config.updatedAt.toISOString(),
402
+ targetWordCount: doc.config.targetWordCount,
403
+ audience: doc.config.audience,
404
+ draftCount: doc.drafts.length,
405
+ evidenceCount: doc.evidence.length
406
+ };
407
+ }
408
+ );
409
+ }
410
+ async function executeSpellcheck(args, _context) {
411
+ const workspacePath = args.path || process.cwd();
412
+ return {
413
+ success: true,
414
+ data: {
415
+ action: "pending",
416
+ path: workspacePath,
417
+ file: args.file,
418
+ note: "Spellcheck implementation pending"
419
+ },
420
+ message: "Spellcheck command - implementation pending"
421
+ };
422
+ }
423
+ async function executeCleanup(args, _context) {
424
+ const workspacePath = args.path || process.cwd();
425
+ return {
426
+ success: true,
427
+ data: {
428
+ action: "pending",
429
+ path: workspacePath,
430
+ keepDrafts: args.keep_drafts || 5,
431
+ dryRun: args.dry_run || false,
432
+ note: "Cleanup implementation pending"
433
+ },
434
+ message: "Cleanup command - implementation pending"
435
+ };
436
+ }
437
+ async function executeExport(args, _context) {
438
+ const workspacePath = args.path || process.cwd();
439
+ return {
440
+ success: true,
441
+ data: {
442
+ action: "pending",
443
+ path: workspacePath,
444
+ format: args.format,
445
+ draft: args.draft,
446
+ output: args.output,
447
+ note: "Export implementation pending"
448
+ },
449
+ message: "Export command - implementation pending"
450
+ };
451
+ }
452
+ async function executeRevise(args, _context) {
453
+ const workspacePath = args.path || process.cwd();
454
+ return {
455
+ success: true,
456
+ data: {
457
+ action: "pending",
458
+ path: workspacePath,
459
+ draft: args.draft,
460
+ feedback: args.feedback,
461
+ note: "Revise implementation pending"
462
+ },
463
+ message: "Revise command - implementation pending"
464
+ };
465
+ }
466
+ async function executeTool(toolName, args, context) {
467
+ try {
468
+ switch (toolName) {
469
+ case "riotdoc_create":
470
+ return await executeCreate(args, context);
471
+ case "riotdoc_outline_insert_section":
472
+ return await executeInsertSection(args, context);
473
+ case "riotdoc_outline_rename_section":
474
+ return await executeRenameSection(args, context);
475
+ case "riotdoc_outline_delete_section":
476
+ return await executeDeleteSection(args, context);
477
+ case "riotdoc_outline_move_section":
478
+ return await executeMoveSection(args, context);
479
+ case "riotdoc_draft":
480
+ return await executeDraft(args, context);
481
+ case "riotdoc_status":
482
+ return await executeStatus(args, context);
483
+ case "riotdoc_spellcheck":
484
+ return await executeSpellcheck(args, context);
485
+ case "riotdoc_cleanup":
486
+ return await executeCleanup(args, context);
487
+ case "riotdoc_export":
488
+ return await executeExport(args, context);
489
+ case "riotdoc_revise":
490
+ return await executeRevise(args, context);
491
+ default:
492
+ return {
493
+ success: false,
494
+ error: `Unknown tool: ${toolName}`
495
+ };
496
+ }
497
+ } catch (error) {
498
+ return {
499
+ success: false,
500
+ error: error.message || "Tool execution failed",
501
+ context: {
502
+ tool: toolName,
503
+ args
504
+ }
505
+ };
506
+ }
507
+ }
508
+ function parseRiotdocUri(uri) {
509
+ if (!uri.startsWith("riotdoc://")) {
510
+ throw new Error(`Invalid riotdoc URI: ${uri}`);
511
+ }
512
+ const withoutScheme = uri.slice("riotdoc://".length);
513
+ const [pathPart, queryPart] = withoutScheme.split("?");
514
+ const segments = pathPart.split("/").filter(Boolean);
515
+ if (segments.length === 0) {
516
+ throw new Error(`Invalid riotdoc URI: missing resource type`);
517
+ }
518
+ const type = segments[0];
519
+ const path = segments.slice(1).join("/") || void 0;
520
+ const query = {};
521
+ if (queryPart) {
522
+ const params = new URLSearchParams(queryPart);
523
+ for (const [key, value] of params) {
524
+ query[key] = value;
525
+ }
526
+ }
527
+ return {
528
+ scheme: "riotdoc",
529
+ type,
530
+ path,
531
+ query: Object.keys(query).length > 0 ? query : void 0
532
+ };
533
+ }
534
+ async function readConfigResource(uri) {
535
+ const directory = uri.path || process.cwd();
536
+ const configPath = join(directory, "riotdoc.yaml");
537
+ try {
538
+ const content = await readFile(configPath, "utf-8");
539
+ const config = parse(content);
540
+ return {
541
+ path: directory,
542
+ exists: true,
543
+ config
544
+ };
545
+ } catch {
546
+ return {
547
+ path: directory,
548
+ exists: false,
549
+ config: void 0
550
+ };
551
+ }
552
+ }
553
+ async function readStatusResource(uri) {
554
+ const directory = uri.path || process.cwd();
555
+ const doc = await loadDocument(directory);
556
+ if (!doc) {
557
+ throw new Error("Not a RiotDoc workspace");
558
+ }
559
+ return {
560
+ path: directory,
561
+ title: doc.config.title,
562
+ type: doc.config.type,
563
+ status: doc.config.status,
564
+ createdAt: doc.config.createdAt.toISOString(),
565
+ updatedAt: doc.config.updatedAt.toISOString(),
566
+ targetWordCount: doc.config.targetWordCount,
567
+ audience: doc.config.audience
568
+ };
569
+ }
570
+ async function readDocumentResource(uri) {
571
+ const directory = uri.path || process.cwd();
572
+ const doc = await loadDocument(directory);
573
+ if (!doc) {
574
+ throw new Error("Not a RiotDoc workspace");
575
+ }
576
+ return {
577
+ path: directory,
578
+ config: {
579
+ id: doc.config.id,
580
+ title: doc.config.title,
581
+ type: doc.config.type,
582
+ status: doc.config.status,
583
+ createdAt: doc.config.createdAt.toISOString(),
584
+ updatedAt: doc.config.updatedAt.toISOString(),
585
+ targetWordCount: doc.config.targetWordCount,
586
+ audience: doc.config.audience
587
+ },
588
+ voice: doc.voice,
589
+ objectives: doc.objectives,
590
+ outline: void 0,
591
+ // Loaded separately if needed
592
+ drafts: doc.drafts.map((d) => ({
593
+ number: d.number,
594
+ path: d.path,
595
+ createdAt: d.createdAt.toISOString(),
596
+ wordCount: d.wordCount
597
+ })),
598
+ evidence: doc.evidence.map((e) => ({
599
+ id: e.id,
600
+ path: e.path,
601
+ description: e.description,
602
+ type: e.type
603
+ }))
604
+ };
605
+ }
606
+ async function readOutlineResource(uri) {
607
+ const directory = uri.path || process.cwd();
608
+ try {
609
+ const content = await loadOutline(directory);
610
+ return {
611
+ path: directory,
612
+ content,
613
+ exists: true
614
+ };
615
+ } catch {
616
+ return {
617
+ path: directory,
618
+ content: "",
619
+ exists: false
620
+ };
621
+ }
622
+ }
623
+ async function readObjectivesResource(uri) {
624
+ const directory = uri.path || process.cwd();
625
+ const objectives = await loadObjectives(directory);
626
+ return {
627
+ path: directory,
628
+ primaryGoal: objectives.primaryGoal,
629
+ secondaryGoals: objectives.secondaryGoals,
630
+ keyTakeaways: objectives.keyTakeaways,
631
+ callToAction: objectives.callToAction,
632
+ emotionalArc: objectives.emotionalArc
633
+ };
634
+ }
635
+ async function readVoiceResource(uri) {
636
+ const directory = uri.path || process.cwd();
637
+ const voice = await loadVoice(directory);
638
+ return {
639
+ path: directory,
640
+ tone: voice.tone,
641
+ pointOfView: voice.pointOfView,
642
+ styleNotes: voice.styleNotes,
643
+ avoid: voice.avoid,
644
+ examplePhrases: voice.examplePhrases
645
+ };
646
+ }
647
+ async function readStyleReportResource(uri) {
648
+ const directory = uri.path || process.cwd();
649
+ return {
650
+ path: directory,
651
+ issues: [],
652
+ summary: {
653
+ errors: 0,
654
+ warnings: 0,
655
+ info: 0
656
+ }
657
+ };
658
+ }
659
+ function getResources() {
660
+ return [
661
+ {
662
+ uri: "riotdoc://config",
663
+ name: "Configuration",
664
+ description: "Loads riotdoc configuration from riotdoc.yaml. URI format: riotdoc://config[/path/to/workspace]. If no path is provided, uses current working directory. Returns: { path: string, exists: boolean, config: object }. The config object includes document metadata, type, status, and settings.",
665
+ mimeType: "application/json"
666
+ },
667
+ {
668
+ uri: "riotdoc://status",
669
+ name: "Document Status",
670
+ description: "Gets the current document status including title, type, dates, and progress. URI format: riotdoc://status[/path/to/workspace]. Returns: { path, title, type, status, createdAt, updatedAt, targetWordCount, audience }. Use this to check the state of a document before operations.",
671
+ mimeType: "application/json"
672
+ },
673
+ {
674
+ uri: "riotdoc://document",
675
+ name: "Complete Document",
676
+ description: "Loads complete document state including config, voice, objectives, drafts, and evidence. URI format: riotdoc://document[/path/to/workspace]. Returns comprehensive document information. Use this to get a full snapshot of the document workspace.",
677
+ mimeType: "application/json"
678
+ },
679
+ {
680
+ uri: "riotdoc://outline",
681
+ name: "Document Outline",
682
+ description: "Retrieves the document outline from OUTLINE.md. URI format: riotdoc://outline[/path/to/workspace]. Returns: { path, content, exists }. The outline provides the structural framework for the document.",
683
+ mimeType: "application/json"
684
+ },
685
+ {
686
+ uri: "riotdoc://objectives",
687
+ name: "Document Objectives",
688
+ description: "Loads document objectives from OBJECTIVES.md. URI format: riotdoc://objectives[/path/to/workspace]. Returns: { path, primaryGoal, secondaryGoals, keyTakeaways, callToAction, emotionalArc }. Objectives define what the document aims to achieve.",
689
+ mimeType: "application/json"
690
+ },
691
+ {
692
+ uri: "riotdoc://voice",
693
+ name: "Voice Configuration",
694
+ description: "Retrieves voice and tone configuration from voice/tone.md. URI format: riotdoc://voice[/path/to/workspace]. Returns: { path, tone, pointOfView, styleNotes, avoid, examplePhrases }. Voice configuration defines the writing style and tone.",
695
+ mimeType: "application/json"
696
+ },
697
+ {
698
+ uri: "riotdoc://style-report",
699
+ name: "Style Validation Report",
700
+ description: "Gets style validation results for the document. URI format: riotdoc://style-report[/path/to/workspace]. Returns: { path, issues: Array<{line, column, severity, message, rule}>, summary }. Use this to check for style violations and writing quality issues.",
701
+ mimeType: "application/json"
702
+ }
703
+ ];
704
+ }
705
+ async function readResource(uri) {
706
+ const parsed = parseRiotdocUri(uri);
707
+ switch (parsed.type) {
708
+ case "config":
709
+ return readConfigResource(parsed);
710
+ case "status":
711
+ return readStatusResource(parsed);
712
+ case "document":
713
+ return readDocumentResource(parsed);
714
+ case "outline":
715
+ return readOutlineResource(parsed);
716
+ case "objectives":
717
+ return readObjectivesResource(parsed);
718
+ case "voice":
719
+ return readVoiceResource(parsed);
720
+ case "style-report":
721
+ return readStyleReportResource(parsed);
722
+ default:
723
+ throw new Error(`Unknown resource type: ${parsed.type}`);
724
+ }
725
+ }
726
+ const __filename$1 = fileURLToPath(import.meta.url);
727
+ const __dirname$1 = dirname(__filename$1);
728
+ function getPromptsDir() {
729
+ const isBundled = __dirname$1.includes("/dist") || __dirname$1.endsWith("dist") || __filename$1.includes("dist/mcp-server.js") || __filename$1.includes("dist\\mcp-server.js");
730
+ if (isBundled) {
731
+ const promptsDir = resolve(__dirname$1, "mcp/prompts");
732
+ return promptsDir;
733
+ }
734
+ return __dirname$1;
735
+ }
736
+ function loadTemplate(name) {
737
+ const promptsDir = getPromptsDir();
738
+ const path = resolve(promptsDir, `${name}.md`);
739
+ try {
740
+ return readFileSync(path, "utf-8").trim();
741
+ } catch (error) {
742
+ throw new Error(`Failed to load prompt template "${name}" from ${path}: ${error}`);
743
+ }
744
+ }
745
+ function fillTemplate(template, args) {
746
+ return template.replace(/\${(\w+)}/g, (_, key) => {
747
+ return args[key] || `[${key}]`;
748
+ });
749
+ }
750
+ function getPrompts() {
751
+ return [
752
+ {
753
+ name: "create_document",
754
+ description: "Guided workflow for creating a new document workspace",
755
+ arguments: [
756
+ {
757
+ name: "name",
758
+ description: "Document workspace name",
759
+ required: true
760
+ },
761
+ {
762
+ name: "type",
763
+ description: "Document type (blog-post, podcast-script, technical-doc, newsletter, custom)",
764
+ required: true
765
+ },
766
+ {
767
+ name: "title",
768
+ description: "Document title",
769
+ required: false
770
+ },
771
+ {
772
+ name: "goal",
773
+ description: "Primary goal",
774
+ required: false
775
+ },
776
+ {
777
+ name: "audience",
778
+ description: "Target audience",
779
+ required: false
780
+ },
781
+ {
782
+ name: "base_path",
783
+ description: "Base path for workspace",
784
+ required: false
785
+ }
786
+ ]
787
+ },
788
+ {
789
+ name: "outline_document",
790
+ description: "Guided workflow for generating or refining document outline",
791
+ arguments: [
792
+ {
793
+ name: "path",
794
+ description: "Document workspace path",
795
+ required: false
796
+ }
797
+ ]
798
+ },
799
+ {
800
+ name: "draft_document",
801
+ description: "Guided workflow for creating document drafts with AI assistance",
802
+ arguments: [
803
+ {
804
+ name: "path",
805
+ description: "Document workspace path",
806
+ required: false
807
+ },
808
+ {
809
+ name: "level",
810
+ description: "Assistance level (generate, expand, revise, cleanup, spellcheck)",
811
+ required: false
812
+ }
813
+ ]
814
+ },
815
+ {
816
+ name: "review_document",
817
+ description: "Guided workflow for reviewing and providing feedback on drafts",
818
+ arguments: [
819
+ {
820
+ name: "path",
821
+ description: "Document workspace path",
822
+ required: false
823
+ },
824
+ {
825
+ name: "draft_number",
826
+ description: "Draft number to review",
827
+ required: false
828
+ }
829
+ ]
830
+ }
831
+ ];
832
+ }
833
+ async function getPrompt(name, args) {
834
+ const prompts = getPrompts();
835
+ if (!prompts.find((p) => p.name === name)) {
836
+ throw new Error(`Unknown prompt: ${name}`);
837
+ }
838
+ const template = loadTemplate(name);
839
+ const filledArgs = { ...args };
840
+ if (!filledArgs.path) filledArgs.path = "current directory";
841
+ if (!filledArgs.base_path) filledArgs.base_path = "current directory";
842
+ const content = fillTemplate(template, filledArgs);
843
+ return [
844
+ {
845
+ role: "user",
846
+ content: {
847
+ type: "text",
848
+ text: content
849
+ }
850
+ }
851
+ ];
852
+ }
853
+ function removeUndefinedValues(obj) {
854
+ if (obj === void 0) {
855
+ return void 0;
856
+ }
857
+ if (obj === null) {
858
+ return null;
859
+ }
860
+ if (Array.isArray(obj)) {
861
+ return obj.map(removeUndefinedValues).filter((item) => item !== void 0);
862
+ }
863
+ if (typeof obj === "object") {
864
+ const cleaned = {};
865
+ for (const [key, value] of Object.entries(obj)) {
866
+ const cleanedValue = removeUndefinedValues(value);
867
+ if (cleanedValue !== void 0) {
868
+ cleaned[key] = cleanedValue;
869
+ }
870
+ }
871
+ return cleaned;
872
+ }
873
+ return obj;
874
+ }
875
+ async function main() {
876
+ const server = new McpServer(
877
+ {
878
+ name: "riotdoc",
879
+ version: "1.0.0"
880
+ },
881
+ {
882
+ capabilities: {
883
+ tools: {},
884
+ resources: {
885
+ subscribe: false,
886
+ listChanged: false
887
+ },
888
+ prompts: {
889
+ listChanged: false
890
+ }
891
+ }
892
+ }
893
+ );
894
+ function registerTool(name, description, inputSchema) {
895
+ server.tool(
896
+ name,
897
+ description,
898
+ inputSchema,
899
+ async (args, { sendNotification, _meta }) => {
900
+ const context = {
901
+ workingDirectory: process.cwd(),
902
+ config: void 0,
903
+ logger: void 0,
904
+ sendNotification: async (notification) => {
905
+ if (notification.method === "notifications/progress" && _meta?.progressToken) {
906
+ const params = {
907
+ progressToken: _meta.progressToken,
908
+ progress: notification.params.progress
909
+ };
910
+ if (notification.params.total !== void 0) {
911
+ params.total = notification.params.total;
912
+ }
913
+ if (notification.params.message !== void 0) {
914
+ params.message = notification.params.message;
915
+ }
916
+ await sendNotification({
917
+ method: "notifications/progress",
918
+ params: removeUndefinedValues(params)
919
+ });
920
+ }
921
+ },
922
+ progressToken: _meta?.progressToken
923
+ };
924
+ const result = await executeTool(name, args, context);
925
+ if (result.success) {
926
+ const content = [];
927
+ if (result.logs && result.logs.length > 0) {
928
+ content.push({
929
+ type: "text",
930
+ text: "=== Command Output ===\n" + result.logs.join("\n") + "\n\n=== Result ==="
931
+ });
932
+ }
933
+ const cleanData = removeUndefinedValues(result.data);
934
+ content.push({
935
+ type: "text",
936
+ text: JSON.stringify(cleanData, null, 2)
937
+ });
938
+ return { content };
939
+ } else {
940
+ const errorParts = [];
941
+ if (result.logs && result.logs.length > 0) {
942
+ errorParts.push("=== Command Output ===");
943
+ errorParts.push(result.logs.join("\n"));
944
+ errorParts.push("\n=== Error ===");
945
+ }
946
+ errorParts.push(result.error || "Unknown error");
947
+ if (result.context && typeof result.context === "object") {
948
+ errorParts.push("\n=== Context ===");
949
+ for (const [key, value] of Object.entries(result.context)) {
950
+ if (value !== void 0 && value !== null) {
951
+ errorParts.push(`${key}: ${String(value)}`);
952
+ }
953
+ }
954
+ }
955
+ if (result.recovery && result.recovery.length > 0) {
956
+ errorParts.push("\n=== Recovery Steps ===");
957
+ errorParts.push(...result.recovery.map((step, i) => `${i + 1}. ${step}`));
958
+ }
959
+ return {
960
+ content: [{
961
+ type: "text",
962
+ text: errorParts.join("\n")
963
+ }],
964
+ isError: true
965
+ };
966
+ }
967
+ }
968
+ );
969
+ }
970
+ registerTool(
971
+ "riotdoc_create",
972
+ "Create a new document workspace with structured directories and configuration",
973
+ {
974
+ name: z.string(),
975
+ title: z.string().optional(),
976
+ type: z.enum(["blog-post", "podcast-script", "technical-doc", "newsletter", "custom"]),
977
+ base_path: z.string().optional(),
978
+ primary_goal: z.string().optional(),
979
+ audience: z.string().optional()
980
+ }
981
+ );
982
+ registerTool(
983
+ "riotdoc_outline",
984
+ "Generate or retrieve document outline",
985
+ {
986
+ path: z.string().optional(),
987
+ generate: z.boolean().optional()
988
+ }
989
+ );
990
+ registerTool(
991
+ "riotdoc_draft",
992
+ "Create a new draft or retrieve existing drafts",
993
+ {
994
+ path: z.string().optional(),
995
+ assistance_level: z.enum(["generate", "expand", "revise", "cleanup", "spellcheck"]).optional(),
996
+ draft_number: z.number().optional()
997
+ }
998
+ );
999
+ registerTool(
1000
+ "riotdoc_status",
1001
+ "Get document status and metadata",
1002
+ {
1003
+ path: z.string().optional()
1004
+ }
1005
+ );
1006
+ registerTool(
1007
+ "riotdoc_spellcheck",
1008
+ "Run spell checking on document content",
1009
+ {
1010
+ path: z.string().optional(),
1011
+ file: z.string().optional()
1012
+ }
1013
+ );
1014
+ registerTool(
1015
+ "riotdoc_cleanup",
1016
+ "Clean up document workspace by removing temporary files and old drafts",
1017
+ {
1018
+ path: z.string().optional(),
1019
+ keep_drafts: z.number().optional(),
1020
+ dry_run: z.boolean().optional()
1021
+ }
1022
+ );
1023
+ registerTool(
1024
+ "riotdoc_export",
1025
+ "Export document to various formats",
1026
+ {
1027
+ path: z.string().optional(),
1028
+ format: z.enum(["html", "pdf", "docx", "markdown"]),
1029
+ draft: z.number().optional(),
1030
+ output: z.string().optional()
1031
+ }
1032
+ );
1033
+ registerTool(
1034
+ "riotdoc_revise",
1035
+ "Add revision feedback to a draft",
1036
+ {
1037
+ path: z.string().optional(),
1038
+ draft: z.number().optional(),
1039
+ feedback: z.string()
1040
+ }
1041
+ );
1042
+ const resources = getResources();
1043
+ for (const resource of resources) {
1044
+ server.resource(
1045
+ resource.name,
1046
+ resource.uri,
1047
+ {
1048
+ description: resource.description || ""
1049
+ },
1050
+ async () => {
1051
+ const data = await readResource(resource.uri);
1052
+ return {
1053
+ contents: [{
1054
+ uri: resource.uri,
1055
+ mimeType: resource.mimeType || "application/json",
1056
+ text: JSON.stringify(data, null, 2)
1057
+ }]
1058
+ };
1059
+ }
1060
+ );
1061
+ }
1062
+ const prompts = getPrompts();
1063
+ for (const prompt of prompts) {
1064
+ const promptArgs = {};
1065
+ if (prompt.arguments) {
1066
+ for (const arg of prompt.arguments) {
1067
+ promptArgs[arg.name] = arg.required ? z.string() : z.string().optional();
1068
+ }
1069
+ }
1070
+ server.prompt(
1071
+ prompt.name,
1072
+ prompt.description,
1073
+ promptArgs,
1074
+ async (args, _extra) => {
1075
+ const argsRecord = {};
1076
+ for (const [key, value] of Object.entries(args)) {
1077
+ if (typeof value === "string") {
1078
+ argsRecord[key] = value;
1079
+ }
1080
+ }
1081
+ const messages = await getPrompt(prompt.name, argsRecord);
1082
+ return {
1083
+ messages: messages.map((msg) => {
1084
+ if (msg.content.type === "text") {
1085
+ return {
1086
+ role: msg.role,
1087
+ content: {
1088
+ type: "text",
1089
+ text: msg.content.text || ""
1090
+ }
1091
+ };
1092
+ }
1093
+ return msg;
1094
+ })
1095
+ };
1096
+ }
1097
+ );
1098
+ }
1099
+ const transport = new StdioServerTransport();
1100
+ await server.connect(transport);
1101
+ }
1102
+ main().catch((error) => {
1103
+ console.error("MCP Server error:", error);
1104
+ process.exit(1);
1105
+ });
1106
+ export {
1107
+ removeUndefinedValues
1108
+ };
1109
+ //# sourceMappingURL=mcp-server.js.map