@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.
- package/dist/branding.d.ts +20 -0
- package/dist/branding.d.ts.map +1 -0
- package/dist/branding.js +27 -0
- package/dist/branding.js.map +1 -0
- package/dist/diagnostics.d.ts +59 -0
- package/dist/diagnostics.d.ts.map +1 -0
- package/dist/diagnostics.js +117 -0
- package/dist/diagnostics.js.map +1 -0
- package/dist/generated/ui-bundle.d.ts +2 -0
- package/dist/generated/ui-bundle.d.ts.map +1 -1
- package/dist/generated/ui-bundle.js +4 -3
- package/dist/generated/ui-bundle.js.map +1 -1
- package/dist/reference-cheatsheet.d.ts +2 -0
- package/dist/reference-cheatsheet.d.ts.map +1 -0
- package/dist/reference-cheatsheet.js +47 -0
- package/dist/reference-cheatsheet.js.map +1 -0
- package/dist/schema-vocab.d.ts +6 -0
- package/dist/schema-vocab.d.ts.map +1 -0
- package/dist/schema-vocab.js +55 -0
- package/dist/schema-vocab.js.map +1 -0
- package/dist/schemas.d.ts +52 -0
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +24 -1
- package/dist/schemas.js.map +1 -1
- package/dist/server.d.ts +2 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +312 -165
- package/dist/server.js.map +1 -1
- package/dist/ui/entry.js +148 -110
- package/dist/ui/entry.js.map +1 -1
- package/dist/ui/payload.d.ts +30 -0
- package/dist/ui/payload.d.ts.map +1 -0
- package/dist/ui/payload.js +49 -0
- package/dist/ui/payload.js.map +1 -0
- package/package.json +11 -7
- package/src/branding.ts +26 -0
- package/src/diagnostics.ts +185 -0
- package/src/generated/ui-bundle.ts +5 -3
- package/src/reference-cheatsheet.ts +47 -0
- package/src/schema-vocab.ts +55 -0
- package/src/schemas.ts +28 -1
- package/src/server.ts +419 -200
- package/src/ui/entry.ts +167 -122
- 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
|
+
}
|