@svgrid/mcp 3.0.1 → 3.0.3

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/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, 375 runnable demos - and, uniquely,
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, 375 runnable demos - and, uniquely,
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 = "3.0.0";
11
+ export declare const PREVIEW_GRID_WC_VERSION = "3.0.3";
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 = '3.0.0';
30
+ export const PREVIEW_GRID_WC_VERSION = '3.0.3';
31
31
  const EXT_APPS_VERSION = '1.7.5';
32
32
  const CDN = 'https://cdn.jsdelivr.net/npm';
33
33
  /**
package/dist/prompts.js CHANGED
@@ -7,6 +7,14 @@ export const PROMPTS = [
7
7
  { name: 'description', description: 'The data and behaviour you want, in plain language.', required: true },
8
8
  ],
9
9
  },
10
+ {
11
+ name: 'build_sheet',
12
+ title: 'Build a spreadsheet',
13
+ description: 'Scaffold an Excel-style spreadsheet on the SvSheet shell (@svgrid/enterprise) for a described workbook: sheets, formulas, formats, rules, protection.',
14
+ arguments: [
15
+ { name: 'description', description: 'The sheets, the cells and formulas, and what the user may do, in plain language.', required: true },
16
+ ],
17
+ },
10
18
  {
11
19
  name: 'explain_api',
12
20
  title: 'Explain a SvGrid API',
@@ -41,6 +49,28 @@ export function getPrompt(name, args) {
41
49
  'and touch the grid rather than just read it.'),
42
50
  };
43
51
  }
52
+ if (name === 'build_sheet') {
53
+ return {
54
+ description: 'Build a verified spreadsheet on SvSheet',
55
+ ...messages(`Build a spreadsheet on the SvSheet shell for: ${arg('description')}\n\n` +
56
+ 'SvSheet is in `@svgrid/enterprise`, a component that owns a workbook and a document; ' +
57
+ 'confirm the package is installed before importing it. Work in this order:\n' +
58
+ '1. Call `svgrid_search` for "spreadsheet shell" and for each part this needs ' +
59
+ '(formulas, data validation, conditional formatting, comments, protection, xlsx). ' +
60
+ 'Do not rely on memory of the API.\n' +
61
+ '2. Call `svgrid_get` on the closest spreadsheet demo (ids 452 to 466 and 484 to 494) and follow ' +
62
+ 'its structure: `createWorkbook` for the cells as raw text with formulas as `=...` strings, ' +
63
+ '`createSheetDocument` for everything that is not a cell, `<SvSheet document={doc}>`.\n' +
64
+ '3. Write the component. Rectangles are `[minRow, minCol, maxRow, maxCol]`, 0-based; ' +
65
+ 'a write from outside goes through `cmd.setCellValue` in `onAction` or is followed by ' +
66
+ '`refresh()`; addresses in `formats` for another sheet are qualified (`Orders!F2`).\n' +
67
+ '4. Call `svgrid_check_code` on what you wrote and use the `fixed` source it returns, ' +
68
+ 'repeating until it is clean, BEFORE showing me the code.\n' +
69
+ '5. Say which formulas the engine evaluates and which it does not, from the search ' +
70
+ 'results rather than from memory: the library is Excel-shaped but not complete, and ' +
71
+ 'dynamic arrays spill while the spill operator (`D2#`) and array constants do not parse.'),
72
+ };
73
+ }
44
74
  if (name === 'explain_api') {
45
75
  return {
46
76
  description: 'Explain a SvGrid API from the shipped docs',
@@ -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
@@ -194,7 +194,7 @@ const PROP_RENAMES = {
194
194
  };
195
195
  const PROP_RENAME_NOTES = {
196
196
  defaultColDef: 'SvGrid has no shared column default; set the key on each column, or map over your columns to add it.',
197
- rowModelType: 'SvGrid always renders from `data`. For server-side paging/sorting, build the rows with `createServerDataSource` and feed its result into `data`.',
197
+ rowModelType: 'SvGrid has no row-model switch. Build the rows with `createServerDataSource` and pass the controller as `rowModel` - add `mode: "infinite"` for block-loaded scrolling. Server-side grouping, tree data, pivot, transactions and selection across unloaded rows are `createServerRowModel` in @svgrid/enterprise, on the same contract and the same prop.',
198
198
  theme: 'Themes are stylesheets, not a prop: import one, e.g. `import "@svgrid/grid/themes/shadcn.css"`.',
199
199
  };
200
200
  const COLUMN_RENAMES = {
@@ -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.1",
8
+ "version": "3.0.3",
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": "^3.0.0"
36
+ "@svgrid/enterprise": "^3.0.3"
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.1",
6
+ "version": "3.0.3",
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.1",
18
+ "version": "3.0.3",
19
19
  "runtimeHint": "npx",
20
20
  "transport": {
21
21
  "type": "stdio"