@svgrid/mcp 3.0.0 → 3.0.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/LICENSE +40 -40
- package/README.md +2 -2
- package/dist/core-tools.d.ts +2 -2
- package/dist/core-tools.js +13 -3
- package/dist/data.d.ts +6 -0
- package/dist/data.js +2124 -622
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/preview.d.ts +1 -1
- package/dist/preview.js +1 -1
- package/dist/validate.d.ts +9 -0
- package/dist/validate.js +246 -0
- package/package.json +2 -2
- package/server.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* SvGrid MCP server (stdio).
|
|
4
4
|
*
|
|
5
5
|
* Gives an MCP-capable client accurate, version-pinned answers about SvGrid -
|
|
6
|
-
* the real exported API, the shipped docs,
|
|
6
|
+
* the real exported API, the shipped docs, 400+ runnable demos - and, uniquely,
|
|
7
7
|
* a way to CHECK generated code against that surface before a user ever sees
|
|
8
8
|
* it.
|
|
9
9
|
*
|
package/dist/index.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* SvGrid MCP server (stdio).
|
|
4
4
|
*
|
|
5
5
|
* Gives an MCP-capable client accurate, version-pinned answers about SvGrid -
|
|
6
|
-
* the real exported API, the shipped docs,
|
|
6
|
+
* the real exported API, the shipped docs, 400+ runnable demos - and, uniquely,
|
|
7
7
|
* a way to CHECK generated code against that surface before a user ever sees
|
|
8
8
|
* it.
|
|
9
9
|
*
|
package/dist/preview.d.ts
CHANGED
|
@@ -8,7 +8,7 @@ export declare const UI_RESOURCE_URI = "ui://svgrid/preview.html";
|
|
|
8
8
|
* start failing in a way nobody is watching for. `tools/mcp-tools.test.ts` checks these
|
|
9
9
|
* against the workspace so they cannot quietly rot either.
|
|
10
10
|
*/
|
|
11
|
-
export declare const PREVIEW_GRID_WC_VERSION = "
|
|
11
|
+
export declare const PREVIEW_GRID_WC_VERSION = "3.0.1";
|
|
12
12
|
export declare function previewResource(): {
|
|
13
13
|
uri: string;
|
|
14
14
|
name: string;
|
package/dist/preview.js
CHANGED
|
@@ -27,7 +27,7 @@ const UI_RESOURCE_META_KEY = 'ui/resourceUri';
|
|
|
27
27
|
* start failing in a way nobody is watching for. `tools/mcp-tools.test.ts` checks these
|
|
28
28
|
* against the workspace so they cannot quietly rot either.
|
|
29
29
|
*/
|
|
30
|
-
export const PREVIEW_GRID_WC_VERSION = '
|
|
30
|
+
export const PREVIEW_GRID_WC_VERSION = '3.0.1';
|
|
31
31
|
const EXT_APPS_VERSION = '1.7.5';
|
|
32
32
|
const CDN = 'https://cdn.jsdelivr.net/npm';
|
|
33
33
|
/**
|
package/dist/validate.d.ts
CHANGED
|
@@ -64,6 +64,15 @@ export type ApiSurface = {
|
|
|
64
64
|
};
|
|
65
65
|
readonly props: readonly TypeMember[];
|
|
66
66
|
readonly columnDef: readonly TypeMember[];
|
|
67
|
+
/** The chart spec's vocabulary; absent on a surface built before it existed. */
|
|
68
|
+
readonly chartSpec?: {
|
|
69
|
+
readonly keys: readonly string[];
|
|
70
|
+
readonly seriesKeys: readonly string[];
|
|
71
|
+
readonly types: readonly string[];
|
|
72
|
+
readonly seriesTypes: readonly string[];
|
|
73
|
+
readonly overlays: readonly string[];
|
|
74
|
+
readonly overlayPatterns: readonly string[];
|
|
75
|
+
};
|
|
67
76
|
/** Methods on the free `SvGridApi`. */
|
|
68
77
|
readonly apiMethods: readonly string[];
|
|
69
78
|
/** Methods `installEnterprise(api)` adds on top. */
|
package/dist/validate.js
CHANGED
|
@@ -1006,6 +1006,251 @@ export function applyFixes(source, diagnostics) {
|
|
|
1006
1006
|
function escapeRegExp(value) {
|
|
1007
1007
|
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
1008
1008
|
}
|
|
1009
|
+
/**
|
|
1010
|
+
* Parse an object literal the way a spec is usually written by hand: quoted
|
|
1011
|
+
* or bare keys, single or double quotes, trailing commas, numbers, booleans,
|
|
1012
|
+
* null, nested objects and arrays. Anything else (an identifier, a call, a
|
|
1013
|
+
* template literal, a spread, an arrow, a comment) means the value is not
|
|
1014
|
+
* static, and the whole literal is left alone: the check is only ever run on
|
|
1015
|
+
* what can be read completely, so it never guesses.
|
|
1016
|
+
*/
|
|
1017
|
+
function parseStaticLiteral(src, start) {
|
|
1018
|
+
let i = start;
|
|
1019
|
+
const keyOffsets = new Map();
|
|
1020
|
+
const fail = () => { throw new Error('not static'); };
|
|
1021
|
+
const ws = () => { while (i < src.length && /\s/.test(src[i]))
|
|
1022
|
+
i++; };
|
|
1023
|
+
const value = (path) => {
|
|
1024
|
+
ws();
|
|
1025
|
+
const c = src[i];
|
|
1026
|
+
if (c === '{')
|
|
1027
|
+
return object(path);
|
|
1028
|
+
if (c === '[')
|
|
1029
|
+
return array(path);
|
|
1030
|
+
if (c === '"' || c === "'")
|
|
1031
|
+
return string();
|
|
1032
|
+
if (c === '-' || (c >= '0' && c <= '9'))
|
|
1033
|
+
return number();
|
|
1034
|
+
if (src.startsWith('true', i)) {
|
|
1035
|
+
i += 4;
|
|
1036
|
+
return true;
|
|
1037
|
+
}
|
|
1038
|
+
if (src.startsWith('false', i)) {
|
|
1039
|
+
i += 5;
|
|
1040
|
+
return false;
|
|
1041
|
+
}
|
|
1042
|
+
if (src.startsWith('null', i)) {
|
|
1043
|
+
i += 4;
|
|
1044
|
+
return null;
|
|
1045
|
+
}
|
|
1046
|
+
return fail();
|
|
1047
|
+
};
|
|
1048
|
+
const string = () => {
|
|
1049
|
+
const q = src[i];
|
|
1050
|
+
let j = i + 1;
|
|
1051
|
+
let out = '';
|
|
1052
|
+
while (j < src.length && src[j] !== q) {
|
|
1053
|
+
if (src[j] === '\\') {
|
|
1054
|
+
out += src[j + 1];
|
|
1055
|
+
j += 2;
|
|
1056
|
+
continue;
|
|
1057
|
+
}
|
|
1058
|
+
if (src[j] === '\n')
|
|
1059
|
+
fail();
|
|
1060
|
+
out += src[j];
|
|
1061
|
+
j++;
|
|
1062
|
+
}
|
|
1063
|
+
if (src[j] !== q)
|
|
1064
|
+
fail();
|
|
1065
|
+
i = j + 1;
|
|
1066
|
+
return out;
|
|
1067
|
+
};
|
|
1068
|
+
const number = () => {
|
|
1069
|
+
const m = /^-?\d+(?:\.\d+)?(?:e[+-]?\d+)?/i.exec(src.slice(i, i + 40));
|
|
1070
|
+
if (!m)
|
|
1071
|
+
fail();
|
|
1072
|
+
i += m[0].length;
|
|
1073
|
+
return Number(m[0]);
|
|
1074
|
+
};
|
|
1075
|
+
const array = (path) => {
|
|
1076
|
+
i++;
|
|
1077
|
+
const out = [];
|
|
1078
|
+
for (;;) {
|
|
1079
|
+
ws();
|
|
1080
|
+
if (src[i] === ']') {
|
|
1081
|
+
i++;
|
|
1082
|
+
return out;
|
|
1083
|
+
}
|
|
1084
|
+
out.push(value(`${path}[${out.length}]`));
|
|
1085
|
+
ws();
|
|
1086
|
+
if (src[i] === ',') {
|
|
1087
|
+
i++;
|
|
1088
|
+
continue;
|
|
1089
|
+
}
|
|
1090
|
+
if (src[i] === ']') {
|
|
1091
|
+
i++;
|
|
1092
|
+
return out;
|
|
1093
|
+
}
|
|
1094
|
+
fail();
|
|
1095
|
+
}
|
|
1096
|
+
};
|
|
1097
|
+
const object = (path) => {
|
|
1098
|
+
i++;
|
|
1099
|
+
const out = {};
|
|
1100
|
+
for (;;) {
|
|
1101
|
+
ws();
|
|
1102
|
+
if (src[i] === '}') {
|
|
1103
|
+
i++;
|
|
1104
|
+
return out;
|
|
1105
|
+
}
|
|
1106
|
+
const keyAt = i;
|
|
1107
|
+
let key;
|
|
1108
|
+
if (src[i] === '"' || src[i] === "'")
|
|
1109
|
+
key = string();
|
|
1110
|
+
else {
|
|
1111
|
+
const m = /^[A-Za-z_$][\w$]*/.exec(src.slice(i, i + 80));
|
|
1112
|
+
if (!m)
|
|
1113
|
+
fail();
|
|
1114
|
+
key = m[0];
|
|
1115
|
+
i += key.length;
|
|
1116
|
+
}
|
|
1117
|
+
ws();
|
|
1118
|
+
if (src[i] !== ':')
|
|
1119
|
+
fail();
|
|
1120
|
+
i++;
|
|
1121
|
+
const p = path ? `${path}.${key}` : key;
|
|
1122
|
+
keyOffsets.set(p, keyAt);
|
|
1123
|
+
out[key] = value(p);
|
|
1124
|
+
ws();
|
|
1125
|
+
if (src[i] === ',') {
|
|
1126
|
+
i++;
|
|
1127
|
+
continue;
|
|
1128
|
+
}
|
|
1129
|
+
if (src[i] === '}') {
|
|
1130
|
+
i++;
|
|
1131
|
+
return out;
|
|
1132
|
+
}
|
|
1133
|
+
fail();
|
|
1134
|
+
}
|
|
1135
|
+
};
|
|
1136
|
+
try {
|
|
1137
|
+
return { value: object(''), keyOffsets };
|
|
1138
|
+
}
|
|
1139
|
+
catch {
|
|
1140
|
+
return null;
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
/** The offset of the `{` that closes back onto `}` at `close`, or -1. */
|
|
1144
|
+
function openingBrace(src, close) {
|
|
1145
|
+
let depth = 0;
|
|
1146
|
+
for (let i = close; i >= 0; i--) {
|
|
1147
|
+
if (src[i] === '}')
|
|
1148
|
+
depth++;
|
|
1149
|
+
else if (src[i] === '{') {
|
|
1150
|
+
depth--;
|
|
1151
|
+
if (depth === 0)
|
|
1152
|
+
return i;
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
return -1;
|
|
1156
|
+
}
|
|
1157
|
+
/**
|
|
1158
|
+
* Chart specs written as static literals: a `ChartSpec`-typed const, a
|
|
1159
|
+
* `satisfies ChartSpec` expression, or `spec={{ ... }}` on a chart tag.
|
|
1160
|
+
* Only a literal the parser reads completely is checked; one with a variable
|
|
1161
|
+
* or a call in it is skipped, so a finding is never a guess.
|
|
1162
|
+
*/
|
|
1163
|
+
function checkChartSpecs(ctx) {
|
|
1164
|
+
const vocab = ctx.surface.chartSpec;
|
|
1165
|
+
if (!vocab)
|
|
1166
|
+
return;
|
|
1167
|
+
const raw = ctx.raw;
|
|
1168
|
+
const starts = [];
|
|
1169
|
+
for (const m of raw.matchAll(/:\s*ChartSpec\s*=\s*\{/g))
|
|
1170
|
+
starts.push(m.index + m[0].length - 1);
|
|
1171
|
+
for (const m of raw.matchAll(/\}\s*satisfies\s+ChartSpec\b/g)) {
|
|
1172
|
+
const open = openingBrace(raw, m.index);
|
|
1173
|
+
if (open >= 0)
|
|
1174
|
+
starts.push(open);
|
|
1175
|
+
}
|
|
1176
|
+
for (const m of raw.matchAll(/<(?:SvChart|SvGridChart)\b[^>]*?\sspec=\{\{/g))
|
|
1177
|
+
starts.push(m.index + m[0].length - 1);
|
|
1178
|
+
const seen = new Set();
|
|
1179
|
+
for (const start of starts) {
|
|
1180
|
+
if (seen.has(start))
|
|
1181
|
+
continue;
|
|
1182
|
+
seen.add(start);
|
|
1183
|
+
const lit = parseStaticLiteral(raw, start);
|
|
1184
|
+
if (!lit || typeof lit.value !== 'object' || lit.value === null)
|
|
1185
|
+
continue;
|
|
1186
|
+
const spec = lit.value;
|
|
1187
|
+
const lineOf = (path) => lineAt(raw, lit.keyOffsets.get(path) ?? start);
|
|
1188
|
+
const see = 'help/charts/api';
|
|
1189
|
+
for (const key of Object.keys(spec)) {
|
|
1190
|
+
if (vocab.keys.includes(key))
|
|
1191
|
+
continue;
|
|
1192
|
+
const guess = nearest(key, vocab.keys);
|
|
1193
|
+
push(ctx, {
|
|
1194
|
+
rule: 'svgrid/chart-unknown-key',
|
|
1195
|
+
severity: 'warning',
|
|
1196
|
+
line: lineOf(key),
|
|
1197
|
+
message: guess ? `ChartSpec has no field "${key}"; did you mean "${guess}"?` : `ChartSpec has no field "${key}"; it is ignored`,
|
|
1198
|
+
...(guess ? { fix: `rename "${key}" to "${guess}"`, rename: { from: key, to: guess } } : {}),
|
|
1199
|
+
see,
|
|
1200
|
+
});
|
|
1201
|
+
}
|
|
1202
|
+
const type = spec.type;
|
|
1203
|
+
if (typeof type === 'string' && !vocab.types.includes(type)) {
|
|
1204
|
+
const guess = nearest(type, vocab.types);
|
|
1205
|
+
push(ctx, {
|
|
1206
|
+
rule: 'svgrid/chart-unknown-type',
|
|
1207
|
+
severity: 'error',
|
|
1208
|
+
line: lineOf('type'),
|
|
1209
|
+
message: guess ? `"${type}" is not a chart type; did you mean "${guess}"?` : `"${type}" is not a chart type`,
|
|
1210
|
+
...(guess ? { fix: `use type: '${guess}'`, rename: { from: type, to: guess } } : {}),
|
|
1211
|
+
see,
|
|
1212
|
+
});
|
|
1213
|
+
}
|
|
1214
|
+
const categories = Array.isArray(spec.categories) ? spec.categories : null;
|
|
1215
|
+
const freeLength = typeof type === 'string' && ['scatter', 'gauge', 'treemap', 'sankey', 'calendar', 'sunburst', 'chord', 'histogram'].includes(type);
|
|
1216
|
+
if (Array.isArray(spec.series)) {
|
|
1217
|
+
spec.series.forEach((s, i) => {
|
|
1218
|
+
if (typeof s !== 'object' || s === null || Array.isArray(s))
|
|
1219
|
+
return;
|
|
1220
|
+
const sr = s;
|
|
1221
|
+
const p = `series[${i}]`;
|
|
1222
|
+
for (const key of Object.keys(sr)) {
|
|
1223
|
+
if (vocab.seriesKeys.includes(key))
|
|
1224
|
+
continue;
|
|
1225
|
+
const guess = nearest(key, vocab.seriesKeys);
|
|
1226
|
+
push(ctx, {
|
|
1227
|
+
rule: 'svgrid/chart-unknown-series-key',
|
|
1228
|
+
severity: 'warning',
|
|
1229
|
+
line: lineOf(`${p}.${key}`),
|
|
1230
|
+
message: guess ? `a chart series has no field "${key}"; did you mean "${guess}"?` : `a chart series has no field "${key}"; it is ignored`,
|
|
1231
|
+
...(guess ? { fix: `rename "${key}" to "${guess}"`, rename: { from: key, to: guess } } : {}),
|
|
1232
|
+
see,
|
|
1233
|
+
});
|
|
1234
|
+
}
|
|
1235
|
+
if (categories && !freeLength && Array.isArray(sr.values) && sr.values.length !== categories.length && !(Array.isArray(sr.points) && sr.values.length === 0)) {
|
|
1236
|
+
push(ctx, {
|
|
1237
|
+
rule: 'svgrid/chart-series-length',
|
|
1238
|
+
severity: 'error',
|
|
1239
|
+
line: lineOf(`${p}.values`),
|
|
1240
|
+
message: `series "${String(sr.label ?? i)}" has ${sr.values.length} values for ${categories.length} categories; they run in parallel`,
|
|
1241
|
+
see,
|
|
1242
|
+
});
|
|
1243
|
+
}
|
|
1244
|
+
if (sr.axis !== undefined && sr.axis !== 'left' && sr.axis !== 'right') {
|
|
1245
|
+
push(ctx, { rule: 'svgrid/chart-axis', severity: 'error', line: lineOf(`${p}.axis`), message: `a series axis is 'left' or 'right', not "${String(sr.axis)}"`, see });
|
|
1246
|
+
}
|
|
1247
|
+
if (typeof sr.overlay === 'string' && !vocab.overlays.includes(sr.overlay) && !vocab.overlayPatterns.some((re) => new RegExp(re).test(sr.overlay))) {
|
|
1248
|
+
push(ctx, { rule: 'svgrid/chart-overlay', severity: 'error', line: lineOf(`${p}.overlay`), message: `"${sr.overlay}" is not an overlay; expected ${vocab.overlays.join(', ')} or one of ${vocab.overlayPatterns.map((r) => r.replace(/^\^|\$/g, '').replace(/\\d\+\(\?:\\\.\\d\+\)\?/g, 'N')).join(', ')}`, see });
|
|
1249
|
+
}
|
|
1250
|
+
});
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1009
1254
|
export function checkStatic(source, surface, filename = 'Component.svelte') {
|
|
1010
1255
|
const ctx = {
|
|
1011
1256
|
raw: source,
|
|
@@ -1025,6 +1270,7 @@ export function checkStatic(source, surface, filename = 'Component.svelte') {
|
|
|
1025
1270
|
checkKnownTraps(ctx);
|
|
1026
1271
|
checkEnterpriseUsage(ctx);
|
|
1027
1272
|
checkFeatures(ctx);
|
|
1273
|
+
checkChartSpecs(ctx);
|
|
1028
1274
|
const rank = { error: 0, warning: 1, info: 2 };
|
|
1029
1275
|
const unique = new Map();
|
|
1030
1276
|
for (const d of ctx.out)
|
package/package.json
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"type": "commercial",
|
|
6
6
|
"url": "https://svgrid.com/pricing"
|
|
7
7
|
},
|
|
8
|
-
"version": "3.0.
|
|
8
|
+
"version": "3.0.2",
|
|
9
9
|
"description": "Model Context Protocol server for SvGrid, the Svelte 5 data grid: checks the code your AI writes against the real API surface, plus version-pinned docs, API reference and 373 demo sources.",
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"author": "jQWidgets <sales@jqwidgets.com>",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"@modelcontextprotocol/sdk": "^1.0.4",
|
|
34
34
|
"svelte": "^5.55.5",
|
|
35
35
|
"zod": "^3.23.8",
|
|
36
|
-
"@svgrid/enterprise": "^
|
|
36
|
+
"@svgrid/enterprise": "^3.0.2"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
39
|
"@types/node": "^22.10.7",
|
package/server.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"name": "com.svgrid/svgrid",
|
|
4
4
|
"title": "SvGrid",
|
|
5
5
|
"description": "Checks AI-written SvGrid code against the real API, plus version-pinned Svelte 5 grid docs.",
|
|
6
|
-
"version": "3.0.
|
|
6
|
+
"version": "3.0.1",
|
|
7
7
|
"websiteUrl": "https://svgrid.com/docs/help/mcp-server/",
|
|
8
8
|
"repository": {
|
|
9
9
|
"url": "https://github.com/sv-grid/sv-grid",
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"registryType": "npm",
|
|
16
16
|
"registryBaseUrl": "https://registry.npmjs.org",
|
|
17
17
|
"identifier": "@svgrid/mcp",
|
|
18
|
-
"version": "3.0.
|
|
18
|
+
"version": "3.0.1",
|
|
19
19
|
"runtimeHint": "npx",
|
|
20
20
|
"transport": {
|
|
21
21
|
"type": "stdio"
|