@nowline/mcp 0.8.0 → 0.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/diagnostics.d.ts +26 -0
- package/dist/diagnostics.d.ts.map +1 -1
- package/dist/diagnostics.js +55 -0
- package/dist/diagnostics.js.map +1 -1
- package/dist/generated/ui-bundle.js +2 -2
- package/dist/prompts.d.ts.map +1 -1
- package/dist/prompts.js +3 -0
- package/dist/prompts.js.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +245 -142
- package/dist/server.js.map +1 -1
- package/package.json +10 -9
- package/src/diagnostics.ts +63 -0
- package/src/generated/ui-bundle.ts +2 -2
- package/src/prompts.ts +3 -0
- package/src/server.ts +248 -148
package/src/prompts.ts
CHANGED
|
@@ -10,6 +10,7 @@ export function registerPrompts(server: McpServer): void {
|
|
|
10
10
|
server.registerPrompt(
|
|
11
11
|
'create-roadmap',
|
|
12
12
|
{
|
|
13
|
+
title: 'Create Roadmap from Description',
|
|
13
14
|
description:
|
|
14
15
|
'Generate a new .nowline roadmap from a plain-English description. Composes the DSL reference and example files so the LLM has full context.',
|
|
15
16
|
argsSchema: {
|
|
@@ -60,6 +61,7 @@ export function registerPrompts(server: McpServer): void {
|
|
|
60
61
|
server.registerPrompt(
|
|
61
62
|
'fix-diagnostics',
|
|
62
63
|
{
|
|
64
|
+
title: 'Fix Roadmap Diagnostics',
|
|
63
65
|
description:
|
|
64
66
|
'Fix validation errors in a .nowline file. Describe the validate→fix→re-validate loop keyed on NL.E#### diagnostic codes.',
|
|
65
67
|
argsSchema: {
|
|
@@ -108,6 +110,7 @@ export function registerPrompts(server: McpServer): void {
|
|
|
108
110
|
server.registerPrompt(
|
|
109
111
|
'convert-to-nowline',
|
|
110
112
|
{
|
|
113
|
+
title: 'Convert to Nowline',
|
|
111
114
|
description:
|
|
112
115
|
'Convert a gantt/timeline from another format (Mermaid gantt, MS Project, Excel, Google Sheets, CSV) into Nowline DSL. Uses the conversion guide resource for format-specific rules.',
|
|
113
116
|
argsSchema: {
|
package/src/server.ts
CHANGED
|
@@ -33,7 +33,10 @@ import {
|
|
|
33
33
|
collectMcpLayoutInsights,
|
|
34
34
|
DEFAULT_RENDER_WIDTH,
|
|
35
35
|
diagnosticsErrorBlock,
|
|
36
|
+
handleToolError,
|
|
37
|
+
InputRequiredError,
|
|
36
38
|
LAYOUT_INSIGHT_HINT,
|
|
39
|
+
PathOutsideRootError,
|
|
37
40
|
REVIEW_MAX_WIDTH,
|
|
38
41
|
toolDescriptionWithSyntax,
|
|
39
42
|
} from './diagnostics.js';
|
|
@@ -120,13 +123,33 @@ function leanPreviewBlock(payload: PreviewPayload) {
|
|
|
120
123
|
};
|
|
121
124
|
}
|
|
122
125
|
|
|
126
|
+
// ---- Tool annotation presets (Anthropic Software Directory Policy § 5.E) ---
|
|
127
|
+
|
|
128
|
+
function readOnlyTool(title: string) {
|
|
129
|
+
return {
|
|
130
|
+
title,
|
|
131
|
+
readOnlyHint: true as const,
|
|
132
|
+
idempotentHint: true as const,
|
|
133
|
+
openWorldHint: false as const,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function mutatingTool(title: string, opts: { destructiveHint: boolean; idempotentHint?: boolean }) {
|
|
138
|
+
return {
|
|
139
|
+
title,
|
|
140
|
+
destructiveHint: opts.destructiveHint,
|
|
141
|
+
...(opts.idempotentHint ? { idempotentHint: true as const } : {}),
|
|
142
|
+
openWorldHint: false as const,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
123
146
|
// ---- Server factory ---------------------------------------------------------
|
|
124
147
|
|
|
125
148
|
function resolveAndGuard(filePath: string, allowedRoot: string): string {
|
|
126
149
|
const abs = path.resolve(allowedRoot, filePath);
|
|
127
150
|
const guard = path.resolve(allowedRoot);
|
|
128
151
|
if (!abs.startsWith(guard + path.sep) && abs !== guard) {
|
|
129
|
-
throw new
|
|
152
|
+
throw new PathOutsideRootError(filePath, allowedRoot);
|
|
130
153
|
}
|
|
131
154
|
return abs;
|
|
132
155
|
}
|
|
@@ -187,7 +210,7 @@ async function sourceAndPath(
|
|
|
187
210
|
const source = args.source ?? (await fs.readFile(abs, 'utf-8'));
|
|
188
211
|
return { source, filePath: abs };
|
|
189
212
|
}
|
|
190
|
-
throw new
|
|
213
|
+
throw new InputRequiredError('At least one of `source` or `path` is required.');
|
|
191
214
|
}
|
|
192
215
|
|
|
193
216
|
// ---- Server factory ---------------------------------------------------------
|
|
@@ -226,6 +249,7 @@ export function createMcpServer(opts: McpServerOptions = {}): McpServer {
|
|
|
226
249
|
'nowline-reference',
|
|
227
250
|
'nowline://reference',
|
|
228
251
|
{
|
|
252
|
+
title: 'Roadmap Reference',
|
|
229
253
|
description:
|
|
230
254
|
'Full DSL reference (nowline.5 man page): syntax, directives, and examples.',
|
|
231
255
|
mimeType: 'text/plain',
|
|
@@ -241,6 +265,7 @@ export function createMcpServer(opts: McpServerOptions = {}): McpServer {
|
|
|
241
265
|
'nowline-examples',
|
|
242
266
|
'nowline://examples',
|
|
243
267
|
{
|
|
268
|
+
title: 'Roadmap Examples',
|
|
244
269
|
description: 'Canonical example .nowline files from the official examples/ directory.',
|
|
245
270
|
mimeType: 'text/plain',
|
|
246
271
|
},
|
|
@@ -257,6 +282,7 @@ export function createMcpServer(opts: McpServerOptions = {}): McpServer {
|
|
|
257
282
|
'nowline-conversions',
|
|
258
283
|
'nowline://conversions',
|
|
259
284
|
{
|
|
285
|
+
title: 'Conversion Guide',
|
|
260
286
|
description:
|
|
261
287
|
'LLM-mediated conversion guide: how to translate Mermaid gantt, MS Project, Excel, Google Sheets timeline, and generic CSV into Nowline DSL.',
|
|
262
288
|
mimeType: 'text/plain',
|
|
@@ -277,6 +303,7 @@ export function createMcpServer(opts: McpServerOptions = {}): McpServer {
|
|
|
277
303
|
'nowline-preview',
|
|
278
304
|
PREVIEW_UI_URI,
|
|
279
305
|
{
|
|
306
|
+
title: 'Roadmap Preview',
|
|
280
307
|
description:
|
|
281
308
|
'Interactive in-chat roadmap preview (MCP Apps). Hydrates via ontoolresult.',
|
|
282
309
|
},
|
|
@@ -311,33 +338,41 @@ export function createMcpServer(opts: McpServerOptions = {}): McpServer {
|
|
|
311
338
|
.describe('Absolute or relative path to a .nowline file to validate.'),
|
|
312
339
|
}),
|
|
313
340
|
outputSchema: ValidateOutputSchema,
|
|
314
|
-
annotations:
|
|
341
|
+
annotations: readOnlyTool('Validate Roadmap'),
|
|
315
342
|
},
|
|
316
343
|
async (args) => {
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
...(insights.length > 0
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
344
|
+
try {
|
|
345
|
+
const { source, filePath } = await sourceAndPath(args, allowedRoot);
|
|
346
|
+
const doc = await buildDocument(source);
|
|
347
|
+
const diagnostics = collectMcpDiagnostics(doc, filePath);
|
|
348
|
+
const ok = diagnostics.every((d) => d.severity !== 'error');
|
|
349
|
+
const insights = ok
|
|
350
|
+
? await collectMcpLayoutInsights({
|
|
351
|
+
source,
|
|
352
|
+
filePath,
|
|
353
|
+
today: todayUtc(),
|
|
354
|
+
locale: 'en-US',
|
|
355
|
+
readFile: createNodeHostEnv(filePath).readSource,
|
|
356
|
+
doc,
|
|
357
|
+
})
|
|
358
|
+
: [];
|
|
359
|
+
const structured = {
|
|
360
|
+
ok,
|
|
361
|
+
diagnostics,
|
|
362
|
+
...(insights.length > 0 ? { insights } : {}),
|
|
363
|
+
};
|
|
364
|
+
return {
|
|
365
|
+
content: [
|
|
366
|
+
{ type: 'text', text: JSON.stringify(structured, null, 2) },
|
|
367
|
+
...(insights.length > 0
|
|
368
|
+
? [{ type: 'text' as const, text: LAYOUT_INSIGHT_HINT }]
|
|
369
|
+
: []),
|
|
370
|
+
],
|
|
371
|
+
structuredContent: structured,
|
|
372
|
+
};
|
|
373
|
+
} catch (err) {
|
|
374
|
+
return handleToolError(err, args.path);
|
|
375
|
+
}
|
|
341
376
|
},
|
|
342
377
|
);
|
|
343
378
|
|
|
@@ -353,16 +388,20 @@ export function createMcpServer(opts: McpServerOptions = {}): McpServer {
|
|
|
353
388
|
.describe('Absolute or relative path to the .nowline file to read.'),
|
|
354
389
|
}),
|
|
355
390
|
outputSchema: ReadOutputSchema,
|
|
356
|
-
annotations:
|
|
391
|
+
annotations: readOnlyTool('Read Roadmap'),
|
|
357
392
|
},
|
|
358
393
|
async (args) => {
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
394
|
+
try {
|
|
395
|
+
const abs = resolveAndGuard(args.path, allowedRoot);
|
|
396
|
+
const source = await fs.readFile(abs, 'utf-8');
|
|
397
|
+
const structured = { path: abs, source };
|
|
398
|
+
return {
|
|
399
|
+
content: [{ type: 'text', text: JSON.stringify(structured, null, 2) }],
|
|
400
|
+
structuredContent: structured,
|
|
401
|
+
};
|
|
402
|
+
} catch (err) {
|
|
403
|
+
return handleToolError(err, args.path);
|
|
404
|
+
}
|
|
366
405
|
},
|
|
367
406
|
);
|
|
368
407
|
|
|
@@ -380,19 +419,26 @@ export function createMcpServer(opts: McpServerOptions = {}): McpServer {
|
|
|
380
419
|
}),
|
|
381
420
|
outputSchema: CreateOutputSchema,
|
|
382
421
|
// Overwrites silently → destructive; same source always produces same file → idempotent.
|
|
383
|
-
annotations:
|
|
422
|
+
annotations: mutatingTool('Create Roadmap', {
|
|
423
|
+
destructiveHint: true,
|
|
424
|
+
idempotentHint: true,
|
|
425
|
+
}),
|
|
384
426
|
},
|
|
385
427
|
async (args) => {
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
428
|
+
try {
|
|
429
|
+
const abs = resolveAndGuard(args.path, allowedRoot);
|
|
430
|
+
const blocked = await diagnosticsErrorBlock(args.source, abs);
|
|
431
|
+
if (!blocked.ok) return blocked.response;
|
|
432
|
+
await fs.mkdir(path.dirname(abs), { recursive: true });
|
|
433
|
+
await fs.writeFile(abs, args.source, 'utf-8');
|
|
434
|
+
const structured = { ok: true, path: abs };
|
|
435
|
+
return {
|
|
436
|
+
content: [{ type: 'text', text: JSON.stringify(structured, null, 2) }],
|
|
437
|
+
structuredContent: structured,
|
|
438
|
+
};
|
|
439
|
+
} catch (err) {
|
|
440
|
+
return handleToolError(err, args.path);
|
|
441
|
+
}
|
|
396
442
|
},
|
|
397
443
|
);
|
|
398
444
|
|
|
@@ -411,18 +457,25 @@ export function createMcpServer(opts: McpServerOptions = {}): McpServer {
|
|
|
411
457
|
source: z.string().describe('The new .nowline source text.'),
|
|
412
458
|
}),
|
|
413
459
|
outputSchema: UpdateOutputSchema,
|
|
414
|
-
annotations:
|
|
460
|
+
annotations: mutatingTool('Update Roadmap', {
|
|
461
|
+
destructiveHint: true,
|
|
462
|
+
idempotentHint: true,
|
|
463
|
+
}),
|
|
415
464
|
},
|
|
416
465
|
async (args) => {
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
466
|
+
try {
|
|
467
|
+
const abs = resolveAndGuard(args.path, allowedRoot);
|
|
468
|
+
const blocked = await diagnosticsErrorBlock(args.source, abs);
|
|
469
|
+
if (!blocked.ok) return blocked.response;
|
|
470
|
+
await fs.writeFile(abs, args.source, 'utf-8');
|
|
471
|
+
const structured = { ok: true, path: abs };
|
|
472
|
+
return {
|
|
473
|
+
content: [{ type: 'text', text: JSON.stringify(structured, null, 2) }],
|
|
474
|
+
structuredContent: structured,
|
|
475
|
+
};
|
|
476
|
+
} catch (err) {
|
|
477
|
+
return handleToolError(err, args.path);
|
|
478
|
+
}
|
|
426
479
|
},
|
|
427
480
|
);
|
|
428
481
|
|
|
@@ -438,16 +491,20 @@ export function createMcpServer(opts: McpServerOptions = {}): McpServer {
|
|
|
438
491
|
.describe('Absolute or relative path of the .nowline file to delete.'),
|
|
439
492
|
}),
|
|
440
493
|
outputSchema: DeleteOutputSchema,
|
|
441
|
-
annotations: { destructiveHint: true },
|
|
494
|
+
annotations: mutatingTool('Delete Roadmap', { destructiveHint: true }),
|
|
442
495
|
},
|
|
443
496
|
async (args) => {
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
497
|
+
try {
|
|
498
|
+
const abs = resolveAndGuard(args.path, allowedRoot);
|
|
499
|
+
await fs.unlink(abs);
|
|
500
|
+
const structured = { path: abs };
|
|
501
|
+
return {
|
|
502
|
+
content: [{ type: 'text', text: JSON.stringify(structured, null, 2) }],
|
|
503
|
+
structuredContent: structured,
|
|
504
|
+
};
|
|
505
|
+
} catch (err) {
|
|
506
|
+
return handleToolError(err, args.path);
|
|
507
|
+
}
|
|
451
508
|
},
|
|
452
509
|
);
|
|
453
510
|
|
|
@@ -470,17 +527,23 @@ export function createMcpServer(opts: McpServerOptions = {}): McpServer {
|
|
|
470
527
|
.describe('Whether to scan subdirectories. Defaults to false.'),
|
|
471
528
|
}),
|
|
472
529
|
outputSchema: ListOutputSchema,
|
|
473
|
-
annotations:
|
|
530
|
+
annotations: readOnlyTool('List Roadmaps'),
|
|
474
531
|
},
|
|
475
532
|
async (args) => {
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
533
|
+
try {
|
|
534
|
+
const dir = args.directory
|
|
535
|
+
? resolveAndGuard(args.directory, allowedRoot)
|
|
536
|
+
: allowedRoot;
|
|
537
|
+
const recursive = args.recursive ?? false;
|
|
538
|
+
const paths = await listNowlineFiles(dir, recursive);
|
|
539
|
+
const structured = { paths };
|
|
540
|
+
return {
|
|
541
|
+
content: [{ type: 'text', text: JSON.stringify(structured, null, 2) }],
|
|
542
|
+
structuredContent: structured,
|
|
543
|
+
};
|
|
544
|
+
} catch (err) {
|
|
545
|
+
return handleToolError(err, args.directory);
|
|
546
|
+
}
|
|
484
547
|
},
|
|
485
548
|
);
|
|
486
549
|
|
|
@@ -549,14 +612,24 @@ export function createMcpServer(opts: McpServerOptions = {}): McpServer {
|
|
|
549
612
|
),
|
|
550
613
|
}),
|
|
551
614
|
outputSchema: RenderOutputSchema,
|
|
552
|
-
annotations:
|
|
615
|
+
annotations: readOnlyTool('Render Roadmap'),
|
|
553
616
|
_meta: {
|
|
554
617
|
ui: { resourceUri: PREVIEW_UI_URI },
|
|
555
618
|
'openai/outputTemplate': PREVIEW_UI_URI,
|
|
556
619
|
},
|
|
557
620
|
},
|
|
558
621
|
async (args) => {
|
|
559
|
-
|
|
622
|
+
// Only the filesystem touchpoints (reading the input, writing the
|
|
623
|
+
// output) map to structured NL.MCP.* errors; a kernel render fault
|
|
624
|
+
// bubbles unchanged so it is never mislabeled as a path/IO failure.
|
|
625
|
+
let source: string;
|
|
626
|
+
let filePath: string;
|
|
627
|
+
try {
|
|
628
|
+
({ source, filePath } = await sourceAndPath(args, allowedRoot));
|
|
629
|
+
} catch (err) {
|
|
630
|
+
return handleToolError(err, args.path);
|
|
631
|
+
}
|
|
632
|
+
|
|
560
633
|
const blocked = await diagnosticsErrorBlock(source, filePath);
|
|
561
634
|
if (!blocked.ok) return blocked.response;
|
|
562
635
|
|
|
@@ -615,25 +688,29 @@ export function createMcpServer(opts: McpServerOptions = {}): McpServer {
|
|
|
615
688
|
const insightsField = insights.length > 0 ? { insights } : {};
|
|
616
689
|
|
|
617
690
|
if (args.output) {
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
691
|
+
try {
|
|
692
|
+
const outAbs = resolveAndGuard(args.output, allowedRoot);
|
|
693
|
+
await fs.mkdir(path.dirname(outAbs), { recursive: true });
|
|
694
|
+
await fs.writeFile(outAbs, bytes);
|
|
695
|
+
const structured = {
|
|
696
|
+
format,
|
|
697
|
+
path: outAbs,
|
|
698
|
+
bytes: bytes.byteLength,
|
|
699
|
+
shareUrl,
|
|
700
|
+
...insightsField,
|
|
701
|
+
};
|
|
702
|
+
return {
|
|
703
|
+
content: [
|
|
704
|
+
...(appActive ? [leanPreviewBlock(previewPayload)] : []),
|
|
705
|
+
{ type: 'text' as const, text: JSON.stringify(structured, null, 2) },
|
|
706
|
+
...insightHintBlocks,
|
|
707
|
+
...reviewBlocks,
|
|
708
|
+
],
|
|
709
|
+
structuredContent: structured,
|
|
710
|
+
};
|
|
711
|
+
} catch (err) {
|
|
712
|
+
return handleToolError(err, args.output);
|
|
713
|
+
}
|
|
637
714
|
}
|
|
638
715
|
|
|
639
716
|
if (appActive) {
|
|
@@ -653,7 +730,12 @@ export function createMcpServer(opts: McpServerOptions = {}): McpServer {
|
|
|
653
730
|
}
|
|
654
731
|
|
|
655
732
|
if (format === 'png') {
|
|
656
|
-
const structured = {
|
|
733
|
+
const structured = {
|
|
734
|
+
format,
|
|
735
|
+
bytes: bytes.byteLength,
|
|
736
|
+
shareUrl,
|
|
737
|
+
...insightsField,
|
|
738
|
+
};
|
|
657
739
|
return {
|
|
658
740
|
content: [
|
|
659
741
|
{
|
|
@@ -733,10 +815,20 @@ export function createMcpServer(opts: McpServerOptions = {}): McpServer {
|
|
|
733
815
|
),
|
|
734
816
|
}),
|
|
735
817
|
outputSchema: ExportOutputSchema,
|
|
736
|
-
annotations:
|
|
818
|
+
annotations: readOnlyTool('Export Roadmap'),
|
|
737
819
|
},
|
|
738
820
|
async (args) => {
|
|
739
|
-
|
|
821
|
+
// Only the filesystem touchpoints (reading the input, writing the
|
|
822
|
+
// output) map to structured NL.MCP.* errors; a kernel export fault
|
|
823
|
+
// bubbles unchanged so it is never mislabeled as a path/IO failure.
|
|
824
|
+
let source: string;
|
|
825
|
+
let filePath: string;
|
|
826
|
+
try {
|
|
827
|
+
({ source, filePath } = await sourceAndPath(args, allowedRoot));
|
|
828
|
+
} catch (err) {
|
|
829
|
+
return handleToolError(err, args.path);
|
|
830
|
+
}
|
|
831
|
+
|
|
740
832
|
const blocked = await diagnosticsErrorBlock(source, filePath);
|
|
741
833
|
if (!blocked.ok) return blocked.response;
|
|
742
834
|
|
|
@@ -767,14 +859,18 @@ export function createMcpServer(opts: McpServerOptions = {}): McpServer {
|
|
|
767
859
|
const isBinary = BINARY_FORMATS.has(format);
|
|
768
860
|
|
|
769
861
|
if (args.output) {
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
862
|
+
try {
|
|
863
|
+
const outAbs = resolveAndGuard(args.output, allowedRoot);
|
|
864
|
+
await fs.mkdir(path.dirname(outAbs), { recursive: true });
|
|
865
|
+
await fs.writeFile(outAbs, bytes);
|
|
866
|
+
const structured = { format, path: outAbs, bytes: bytes.byteLength, shareUrl };
|
|
867
|
+
return {
|
|
868
|
+
content: [{ type: 'text', text: JSON.stringify(structured, null, 2) }],
|
|
869
|
+
structuredContent: structured,
|
|
870
|
+
};
|
|
871
|
+
} catch (err) {
|
|
872
|
+
return handleToolError(err, args.output);
|
|
873
|
+
}
|
|
778
874
|
}
|
|
779
875
|
|
|
780
876
|
if (isBinary) {
|
|
@@ -828,47 +924,51 @@ export function createMcpServer(opts: McpServerOptions = {}): McpServer {
|
|
|
828
924
|
),
|
|
829
925
|
}),
|
|
830
926
|
outputSchema: ConvertOutputSchema,
|
|
831
|
-
annotations:
|
|
927
|
+
annotations: readOnlyTool('Convert Roadmap'),
|
|
832
928
|
},
|
|
833
929
|
async (args) => {
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
930
|
+
try {
|
|
931
|
+
if (args.to === 'json') {
|
|
932
|
+
const { source, filePath } = await sourceAndPath(args, allowedRoot);
|
|
933
|
+
const host = createNodeHostEnv(filePath);
|
|
934
|
+
const jsonBytes = await exportDocument(
|
|
935
|
+
source,
|
|
936
|
+
'json',
|
|
937
|
+
{
|
|
938
|
+
sourcePath: filePath,
|
|
939
|
+
today: todayUtc(),
|
|
940
|
+
locale: 'en-US',
|
|
941
|
+
theme: 'light',
|
|
942
|
+
},
|
|
943
|
+
host,
|
|
944
|
+
);
|
|
945
|
+
const result = new TextDecoder('utf-8').decode(jsonBytes);
|
|
946
|
+
const structured = { to: 'json' as const, result };
|
|
947
|
+
return {
|
|
948
|
+
content: [{ type: 'text', text: result }],
|
|
949
|
+
structuredContent: structured,
|
|
950
|
+
};
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
// to: 'nowline' — input is a JSON AST string
|
|
954
|
+
const jsonSource =
|
|
955
|
+
args.source ??
|
|
956
|
+
(args.path
|
|
957
|
+
? await fs.readFile(resolveAndGuard(args.path, allowedRoot), 'utf-8')
|
|
958
|
+
: null);
|
|
959
|
+
if (!jsonSource) {
|
|
960
|
+
throw new InputRequiredError('At least one of `source` or `path` is required.');
|
|
961
|
+
}
|
|
962
|
+
const { ast } = parseNowlineJson(jsonSource, args.path ?? 'input.json');
|
|
963
|
+
const result = printNowlineFile(ast);
|
|
964
|
+
const structured = { to: 'nowline' as const, result };
|
|
850
965
|
return {
|
|
851
966
|
content: [{ type: 'text', text: result }],
|
|
852
967
|
structuredContent: structured,
|
|
853
968
|
};
|
|
969
|
+
} catch (err) {
|
|
970
|
+
return handleToolError(err, args.path);
|
|
854
971
|
}
|
|
855
|
-
|
|
856
|
-
// to: 'nowline' — input is a JSON AST string
|
|
857
|
-
const jsonSource =
|
|
858
|
-
args.source ??
|
|
859
|
-
(args.path
|
|
860
|
-
? await fs.readFile(resolveAndGuard(args.path, allowedRoot), 'utf-8')
|
|
861
|
-
: null);
|
|
862
|
-
if (!jsonSource) {
|
|
863
|
-
throw new Error('At least one of `source` or `path` is required.');
|
|
864
|
-
}
|
|
865
|
-
const { ast } = parseNowlineJson(jsonSource, args.path ?? 'input.json');
|
|
866
|
-
const result = printNowlineFile(ast);
|
|
867
|
-
const structured = { to: 'nowline' as const, result };
|
|
868
|
-
return {
|
|
869
|
-
content: [{ type: 'text', text: result }],
|
|
870
|
-
structuredContent: structured,
|
|
871
|
-
};
|
|
872
972
|
},
|
|
873
973
|
);
|
|
874
974
|
|
|
@@ -881,7 +981,7 @@ export function createMcpServer(opts: McpServerOptions = {}): McpServer {
|
|
|
881
981
|
'Return all supported themes, icons, locales, export formats, and template names in a single response.',
|
|
882
982
|
inputSchema: z.object({}),
|
|
883
983
|
outputSchema: CapabilitiesOutputSchema,
|
|
884
|
-
annotations:
|
|
984
|
+
annotations: readOnlyTool('View Roadmap Capabilities'),
|
|
885
985
|
},
|
|
886
986
|
async () => {
|
|
887
987
|
const structured = {
|
|
@@ -906,7 +1006,7 @@ export function createMcpServer(opts: McpServerOptions = {}): McpServer {
|
|
|
906
1006
|
description: 'List supported color themes: light, dark, grayscale.',
|
|
907
1007
|
inputSchema: z.object({}),
|
|
908
1008
|
outputSchema: ListItemsOutputSchema,
|
|
909
|
-
annotations:
|
|
1009
|
+
annotations: readOnlyTool('List Themes'),
|
|
910
1010
|
},
|
|
911
1011
|
async () => {
|
|
912
1012
|
const structured = { items: [...CAPABILITIES.themes] };
|
|
@@ -926,7 +1026,7 @@ export function createMcpServer(opts: McpServerOptions = {}): McpServer {
|
|
|
926
1026
|
'List built-in capacity-icon names usable in the `capacity-icon:` style property.',
|
|
927
1027
|
inputSchema: z.object({}),
|
|
928
1028
|
outputSchema: ListItemsOutputSchema,
|
|
929
|
-
annotations:
|
|
1029
|
+
annotations: readOnlyTool('List Icons'),
|
|
930
1030
|
},
|
|
931
1031
|
async () => {
|
|
932
1032
|
const structured = { items: [...CAPABILITIES.icons] };
|
|
@@ -945,7 +1045,7 @@ export function createMcpServer(opts: McpServerOptions = {}): McpServer {
|
|
|
945
1045
|
description: 'List supported BCP-47 locale tags.',
|
|
946
1046
|
inputSchema: z.object({}),
|
|
947
1047
|
outputSchema: ListItemsOutputSchema,
|
|
948
|
-
annotations:
|
|
1048
|
+
annotations: readOnlyTool('List Locales'),
|
|
949
1049
|
},
|
|
950
1050
|
async () => {
|
|
951
1051
|
const structured = { items: [...CAPABILITIES.locales] };
|
|
@@ -965,7 +1065,7 @@ export function createMcpServer(opts: McpServerOptions = {}): McpServer {
|
|
|
965
1065
|
'List all supported export formats (svg, png, pdf, html, mermaid, xlsx, msproj, json).',
|
|
966
1066
|
inputSchema: z.object({}),
|
|
967
1067
|
outputSchema: ListItemsOutputSchema,
|
|
968
|
-
annotations:
|
|
1068
|
+
annotations: readOnlyTool('List Export Formats'),
|
|
969
1069
|
},
|
|
970
1070
|
async () => {
|
|
971
1071
|
const structured = { items: [...CAPABILITIES.formats] };
|
|
@@ -984,7 +1084,7 @@ export function createMcpServer(opts: McpServerOptions = {}): McpServer {
|
|
|
984
1084
|
description: 'List built-in template names usable with `nowline --init --template`.',
|
|
985
1085
|
inputSchema: z.object({}),
|
|
986
1086
|
outputSchema: ListItemsOutputSchema,
|
|
987
|
-
annotations:
|
|
1087
|
+
annotations: readOnlyTool('List Templates'),
|
|
988
1088
|
},
|
|
989
1089
|
async () => {
|
|
990
1090
|
const structured = { items: [...CAPABILITIES.templates] };
|
|
@@ -1009,7 +1109,7 @@ export function createMcpServer(opts: McpServerOptions = {}): McpServer {
|
|
|
1009
1109
|
.describe('Reference format. Defaults to condensed.'),
|
|
1010
1110
|
}),
|
|
1011
1111
|
outputSchema: ReferenceOutputSchema,
|
|
1012
|
-
annotations:
|
|
1112
|
+
annotations: readOnlyTool('View Roadmap Reference'),
|
|
1013
1113
|
},
|
|
1014
1114
|
async (args) => {
|
|
1015
1115
|
const format = args.format ?? 'condensed';
|
|
@@ -1034,7 +1134,7 @@ export function createMcpServer(opts: McpServerOptions = {}): McpServer {
|
|
|
1034
1134
|
.describe('Example name. Omit for the catalog plus minimal inline.'),
|
|
1035
1135
|
}),
|
|
1036
1136
|
outputSchema: ExamplesOutputSchema,
|
|
1037
|
-
annotations:
|
|
1137
|
+
annotations: readOnlyTool('View Roadmap Examples'),
|
|
1038
1138
|
},
|
|
1039
1139
|
async (args) => {
|
|
1040
1140
|
const exampleNames = EXAMPLES.map((e) => exampleShortName(e.name));
|
|
@@ -1085,7 +1185,7 @@ export function createMcpServer(opts: McpServerOptions = {}): McpServer {
|
|
|
1085
1185
|
'Return the structured Nowline DSL key vocabulary (directive keys, entity types, item properties).',
|
|
1086
1186
|
inputSchema: z.object({}),
|
|
1087
1187
|
outputSchema: SchemaOutputSchema,
|
|
1088
|
-
annotations:
|
|
1188
|
+
annotations: readOnlyTool('View Roadmap Schema'),
|
|
1089
1189
|
},
|
|
1090
1190
|
async () => {
|
|
1091
1191
|
const structured = {
|