@nowline/mcp 0.7.0 → 0.8.0

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.
Files changed (44) hide show
  1. package/dist/branding.d.ts +20 -0
  2. package/dist/branding.d.ts.map +1 -0
  3. package/dist/branding.js +27 -0
  4. package/dist/branding.js.map +1 -0
  5. package/dist/diagnostics.d.ts +59 -0
  6. package/dist/diagnostics.d.ts.map +1 -0
  7. package/dist/diagnostics.js +117 -0
  8. package/dist/diagnostics.js.map +1 -0
  9. package/dist/generated/ui-bundle.d.ts +2 -0
  10. package/dist/generated/ui-bundle.d.ts.map +1 -1
  11. package/dist/generated/ui-bundle.js +4 -3
  12. package/dist/generated/ui-bundle.js.map +1 -1
  13. package/dist/reference-cheatsheet.d.ts +2 -0
  14. package/dist/reference-cheatsheet.d.ts.map +1 -0
  15. package/dist/reference-cheatsheet.js +47 -0
  16. package/dist/reference-cheatsheet.js.map +1 -0
  17. package/dist/schema-vocab.d.ts +6 -0
  18. package/dist/schema-vocab.d.ts.map +1 -0
  19. package/dist/schema-vocab.js +55 -0
  20. package/dist/schema-vocab.js.map +1 -0
  21. package/dist/schemas.d.ts +52 -0
  22. package/dist/schemas.d.ts.map +1 -1
  23. package/dist/schemas.js +24 -1
  24. package/dist/schemas.js.map +1 -1
  25. package/dist/server.d.ts +2 -0
  26. package/dist/server.d.ts.map +1 -1
  27. package/dist/server.js +312 -165
  28. package/dist/server.js.map +1 -1
  29. package/dist/ui/entry.js +148 -110
  30. package/dist/ui/entry.js.map +1 -1
  31. package/dist/ui/payload.d.ts +30 -0
  32. package/dist/ui/payload.d.ts.map +1 -0
  33. package/dist/ui/payload.js +49 -0
  34. package/dist/ui/payload.js.map +1 -0
  35. package/package.json +11 -7
  36. package/src/branding.ts +26 -0
  37. package/src/diagnostics.ts +185 -0
  38. package/src/generated/ui-bundle.ts +5 -3
  39. package/src/reference-cheatsheet.ts +47 -0
  40. package/src/schema-vocab.ts +55 -0
  41. package/src/schemas.ts +28 -1
  42. package/src/server.ts +419 -200
  43. package/src/ui/entry.ts +167 -122
  44. package/src/ui/payload.ts +63 -0
@@ -0,0 +1,185 @@
1
+ // MCP diagnostic helpers — maps Langium/core diagnostics to the MCP tool shape.
2
+
3
+ import {
4
+ collectDocumentDiagnostics,
5
+ createNowlineServices,
6
+ extractSuggestion,
7
+ type NowlineFile,
8
+ resolveDiagnosticCode,
9
+ resolveIncludes,
10
+ } from '@nowline/core';
11
+ import { collectLayoutInsights, type LayoutInsight, layoutRoadmap } from '@nowline/layout';
12
+ import { URI } from 'langium';
13
+
14
+ export interface McpDiagnostic {
15
+ file: string;
16
+ line: number;
17
+ column: number;
18
+ severity: 'error' | 'warning' | 'info';
19
+ code: string;
20
+ message: string;
21
+ suggestion?: string;
22
+ }
23
+
24
+ export interface McpInsight {
25
+ severity: 'info' | 'warning';
26
+ code: string;
27
+ message: string;
28
+ entityId?: string;
29
+ }
30
+
31
+ export function layoutInsightToMcp(insight: LayoutInsight): McpInsight {
32
+ return {
33
+ severity: insight.severity,
34
+ code: insight.code,
35
+ message: insight.message,
36
+ entityId: insight.entityId,
37
+ };
38
+ }
39
+
40
+ let cachedServices: ReturnType<typeof createNowlineServices> | undefined;
41
+ let docCounter = 0;
42
+
43
+ export function getMcpServices() {
44
+ if (!cachedServices) cachedServices = createNowlineServices();
45
+ return cachedServices;
46
+ }
47
+
48
+ export async function buildDocument(source: string) {
49
+ const services = getMcpServices();
50
+ const uri = URI.parse(`memory:///mcp-${++docCounter}.nowline`);
51
+ const doc = services.shared.workspace.LangiumDocumentFactory.fromString<NowlineFile>(
52
+ source,
53
+ uri,
54
+ );
55
+ await services.shared.workspace.DocumentBuilder.build([doc], { validation: true });
56
+ return doc;
57
+ }
58
+
59
+ export function collectMcpDiagnostics(
60
+ doc: Awaited<ReturnType<typeof buildDocument>>,
61
+ filePath: string,
62
+ ): McpDiagnostic[] {
63
+ const raw = collectDocumentDiagnostics(doc);
64
+ const out: McpDiagnostic[] = [];
65
+ for (const d of raw) {
66
+ if (d.origin === 'lexer' || d.origin === 'parser') {
67
+ out.push({
68
+ file: filePath,
69
+ line: 1,
70
+ column: 1,
71
+ severity: 'error',
72
+ code: d.origin === 'lexer' ? 'lexing-error' : 'parsing-error',
73
+ message: d.error.message,
74
+ });
75
+ } else {
76
+ const diag = d.diagnostic;
77
+ const range = diag.range;
78
+ const severity =
79
+ diag.severity === 2 ? 'warning' : diag.severity === 3 ? 'info' : 'error';
80
+ out.push({
81
+ file: filePath,
82
+ line: (range?.start.line ?? 0) + 1,
83
+ column: (range?.start.character ?? 0) + 1,
84
+ severity,
85
+ code: resolveDiagnosticCode(diag),
86
+ message: diag.message,
87
+ suggestion: extractSuggestion(diag.message),
88
+ });
89
+ }
90
+ }
91
+ return out;
92
+ }
93
+
94
+ export function diagnosticsErrorResponse(filePath: string, diagnostics: McpDiagnostic[]) {
95
+ return {
96
+ content: [
97
+ {
98
+ type: 'text' as const,
99
+ text: JSON.stringify({ ok: false, path: filePath, diagnostics }, null, 2),
100
+ },
101
+ ],
102
+ isError: true as const,
103
+ };
104
+ }
105
+
106
+ export async function diagnosticsErrorBlock(
107
+ source: string,
108
+ filePath: string,
109
+ ): Promise<
110
+ | { ok: true; doc: Awaited<ReturnType<typeof buildDocument>> }
111
+ | { ok: false; response: ReturnType<typeof diagnosticsErrorResponse> }
112
+ > {
113
+ const doc = await buildDocument(source);
114
+ const diagnostics = collectMcpDiagnostics(doc, filePath);
115
+ if (diagnostics.some((d) => d.severity === 'error')) {
116
+ return { ok: false, response: diagnosticsErrorResponse(filePath, diagnostics) };
117
+ }
118
+ return { ok: true, doc };
119
+ }
120
+
121
+ export interface LayoutInsightInputs {
122
+ source: string;
123
+ filePath: string;
124
+ today?: Date;
125
+ theme?: 'light' | 'dark' | 'grayscale';
126
+ width?: number;
127
+ locale?: string;
128
+ readFile?: (absPath: string) => Promise<string>;
129
+ /** Pre-built document to reuse instead of re-parsing `source`. The
130
+ * caller is responsible for passing a doc parsed from the same
131
+ * `source` (e.g. the one from `diagnosticsErrorBlock`). */
132
+ doc?: Awaited<ReturnType<typeof buildDocument>>;
133
+ }
134
+
135
+ export async function collectMcpLayoutInsights(inputs: LayoutInsightInputs): Promise<McpInsight[]> {
136
+ const doc = inputs.doc ?? (await buildDocument(inputs.source));
137
+ const diagnostics = collectMcpDiagnostics(doc, inputs.filePath);
138
+ if (diagnostics.some((d) => d.severity === 'error')) {
139
+ return [];
140
+ }
141
+
142
+ const services = getMcpServices();
143
+ const file = doc.parseResult.value;
144
+ const resolved = await resolveIncludes(file, inputs.filePath, {
145
+ services: services.Nowline,
146
+ readFile: inputs.readFile,
147
+ });
148
+ if (resolved.diagnostics.some((d) => d.severity === 'error')) {
149
+ return [];
150
+ }
151
+
152
+ const layout = layoutRoadmap(file, resolved, {
153
+ today: inputs.today,
154
+ theme: inputs.theme ?? 'light',
155
+ width: inputs.width,
156
+ locale: inputs.locale ?? 'en-US',
157
+ });
158
+
159
+ return collectLayoutInsights(layout, {
160
+ today: inputs.today,
161
+ locale: inputs.locale ?? 'en-US',
162
+ }).map(layoutInsightToMcp);
163
+ }
164
+
165
+ export const LAYOUT_INSIGHT_HINT =
166
+ 'These are layout consequences, not errors — the roadmap rendered. ' +
167
+ 'To see the visual result, call `render` with `review:true`.';
168
+
169
+ export const REVIEW_MAX_WIDTH = 1024;
170
+
171
+ export const DEFAULT_RENDER_WIDTH = 1280;
172
+
173
+ export const DSL_SYNTAX_POINTER =
174
+ 'Source is `.nowline` DSL (not JSON/YAML); call the `reference`/`examples` tools for full syntax.';
175
+
176
+ export const DSL_SYNTAX_EXAMPLE = `nowline v1
177
+
178
+ roadmap r "Title" start:2026-01-05 scale:2w
179
+
180
+ swimlane eng "Engineering"
181
+ item build "Build" duration:3w`;
182
+
183
+ export function toolDescriptionWithSyntax(base: string): string {
184
+ return `${base}\n\nExample:\n${DSL_SYNTAX_EXAMPLE}\n\n${DSL_SYNTAX_POINTER}`;
185
+ }