@riotprompt/riotdoc 1.0.1-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.
- package/.playwright-mcp/riotplan-cli-step.png +0 -0
- package/.playwright-mcp/riotplan-core-concepts.png +0 -0
- package/.playwright-mcp/riotplan-homepage.png +0 -0
- package/.playwright-mcp/riotplan-mcp-overview.png +0 -0
- package/.playwright-mcp/riotplan-mcp-tools.png +0 -0
- package/README.md +111 -8
- package/dist/__vite-browser-external-2Ng8QIWW.js +5 -0
- package/dist/__vite-browser-external-2Ng8QIWW.js.map +1 -0
- package/dist/cli.js +4 -116
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +152 -57
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/loader-Cvfo7vBn.js +153 -0
- package/dist/loader-Cvfo7vBn.js.map +1 -0
- package/dist/loader-DJHV70rz.js +389 -0
- package/dist/loader-DJHV70rz.js.map +1 -0
- package/dist/mcp-server.d.ts +19 -0
- package/dist/mcp-server.js +1109 -0
- package/dist/mcp-server.js.map +1 -0
- package/package.json +18 -11
- package/templates/blog-post-template.md +367 -0
- package/templates/blog-post-validation.md +278 -0
- package/templates/email-template.md +353 -0
- package/templates/podcast-script-template.md +496 -0
- package/templates/project-plan-template.md +513 -0
- package/templates/research-paper-template.md +497 -0
- package/dist/__vite-browser-external-l0sNRNKZ.js +0 -2
- package/dist/__vite-browser-external-l0sNRNKZ.js.map +0 -1
- package/dist/loader-B7ZeTPQg.js +0 -6358
- package/dist/loader-B7ZeTPQg.js.map +0 -1
|
@@ -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
|