@svgrid/mcp 2.5.0 → 2.6.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.
@@ -12,7 +12,7 @@
12
12
  * The server holds one in-memory "current project" per session; `studio_get_config`
13
13
  * / `studio_generate_app` are the outputs.
14
14
  */
15
- import { createProject, parseProject, serializeProject, validateProject, addEntity, addScreen, addFreestandingScreen, addBlock, addComponentBlock, updateBlock, removeBlock, moveBlock, updateScreen, removeScreen, setScreenLayout, setEntityDataSource, setJob, setTenancy, setTheme, setAuth, setDataLayer, setDeployTarget, introspectDrizzle, introspectJson, flattenBlocks, blockPalette, UI_COMPONENT_REGISTRY, uiComponentSpec, studioThemes, emitStudioAppBundle, checkLicenseKey, entityDataSource, } from '@svgrid/enterprise/studio';
15
+ import { createProject, parseProject, serializeProject, validateProject, addEntity, addScreen, addFreestandingScreen, addBlock, addComponentBlock, updateBlock, removeBlock, moveBlock, updateScreen, removeScreen, setScreenLayout, setEntityForm, setFieldConditions, formPlan, suggestFormSections, setEntityDataSource, setJob, setTenancy, setTheme, setAuth, setDataLayer, setDeployTarget, introspectDrizzle, introspectJson, flattenBlocks, blockPalette, UI_COMPONENT_REGISTRY, uiComponentSpec, studioThemes, emitStudioAppBundle, checkLicenseKey, entityDataSource, } from '@svgrid/enterprise/studio';
16
16
  // ---- session state --------------------------------------------------------
17
17
  let project = null;
18
18
  function requireProject() {
@@ -223,6 +223,35 @@ export const projectTools = [
223
223
  required: ['screenId', 'layout'],
224
224
  },
225
225
  },
226
+ {
227
+ name: 'studio_set_form_layout',
228
+ description: 'Arrange an entity\'s create/edit form: column count and titled sections. `sections` is an array of { title?, description?, columns?: 1|2|3, fields: string[], visibleWhen?: PredicateExpr }; `fields` gives both the grouping and the order, and a field left out of every section still renders in a trailing untitled group. Omit `sections` and pass "suggest": true to have them proposed from the field names. The layout lives on the entity, so it renders the same in the edit panel, the generated app, and a server-rendered form.',
229
+ inputSchema: {
230
+ type: 'object',
231
+ properties: {
232
+ entity: { type: 'string' },
233
+ columns: { type: 'number', enum: [1, 2, 3] },
234
+ sections: { type: 'array', items: { type: 'object' }, description: 'The FormSection list. Replaces the current one.' },
235
+ suggest: { type: 'boolean', description: 'Propose sections from the field names instead of passing them.' },
236
+ },
237
+ required: ['entity'],
238
+ },
239
+ },
240
+ {
241
+ name: 'studio_set_field_conditions',
242
+ description: 'Make a form field value-driven: `visible`, `required`, and `disabled` conditions, each a PredicateExpr over the other fields, e.g. { "kind": "cmp", "column": "status", "op": "equals", "value": "cancelled" }. A field hidden by `visible` is skipped by validation and left out of the saved record; `required` REPLACES the field\'s static required flag (so it can make a required field optional too). Pass a condition as null to clear it, or omit every condition to clear all three. Conditions are data, so they generate into the app and re-run server-side.',
243
+ inputSchema: {
244
+ type: 'object',
245
+ properties: {
246
+ entity: { type: 'string' },
247
+ field: { type: 'string' },
248
+ visible: { type: ['object', 'null'], description: 'PredicateExpr, or null to clear.' },
249
+ required: { type: ['object', 'null'], description: 'PredicateExpr, or null to clear.' },
250
+ disabled: { type: ['object', 'null'], description: 'PredicateExpr, or null to clear.' },
251
+ },
252
+ required: ['entity', 'field'],
253
+ },
254
+ },
226
255
  {
227
256
  name: 'studio_set_entity_source',
228
257
  description: 'Bind an entity to a data source. `source` is an EntityDataSource, e.g. { "kind": "sql", "table": "customers", "dialect": "postgres" } | { "kind": "memory" } | { "kind": "pglite", "table": "..." } | { "kind": "supabase", ... } | { "kind": "rest", ... }.',
@@ -506,6 +535,59 @@ export function handleProjectTool(name, args) {
506
535
  project = setScreenLayout(p, screenId, layout);
507
536
  return confirm(`Screen ${screenId} now uses the ${layout} layout.`);
508
537
  }
538
+ case 'studio_set_form_layout': {
539
+ const p = requireProject();
540
+ const entity = String(args.entity ?? '');
541
+ const schema = p.entities.find((e) => e.name === entity);
542
+ if (!schema)
543
+ return fail(`No entity "${entity}".`);
544
+ const columns = args.columns === undefined ? schema.form?.columns : Number(args.columns);
545
+ if (columns !== undefined && ![1, 2, 3].includes(columns))
546
+ return fail('columns must be 1, 2, or 3.');
547
+ let sections = schema.form?.sections;
548
+ if (args.suggest) {
549
+ sections = suggestFormSections(schema);
550
+ if (!sections.length)
551
+ return fail(`Nothing to suggest for "${entity}" - too few form fields to be worth grouping.`);
552
+ }
553
+ else if (args.sections !== undefined) {
554
+ if (!Array.isArray(args.sections))
555
+ return fail('sections must be an array of FormSection objects.');
556
+ sections = args.sections;
557
+ }
558
+ // Plan before storing: a name that resolves to nothing (a typo, or a
559
+ // field since renamed) is dropped here rather than persisted into
560
+ // `studio.config.json` for a later reader to puzzle over. The reply
561
+ // reports the plan, so the agent sees what actually landed.
562
+ const plan = formPlan(schema, sections);
563
+ project = setEntityForm(p, entity, { columns, sections: plan.sections });
564
+ const placed = plan.sections.map((s) => `${s.title ?? '(untitled)'}: ${s.fields.join(', ') || '(empty)'}`);
565
+ return confirm(`"${entity}" form: ${columns ?? 2} columns, ${plan.sections.length} section(s).` +
566
+ (placed.length ? `\n${placed.join('\n')}` : '') +
567
+ (plan.unassigned.length ? `\nUnsectioned (render last): ${plan.unassigned.join(', ')}` : ''));
568
+ }
569
+ case 'studio_set_field_conditions': {
570
+ const p = requireProject();
571
+ const entity = String(args.entity ?? '');
572
+ const field = String(args.field ?? '');
573
+ const schema = p.entities.find((e) => e.name === entity);
574
+ if (!schema)
575
+ return fail(`No entity "${entity}".`);
576
+ if (!schema.fields.some((f) => f.field === field))
577
+ return fail(`No field "${field}" on "${entity}".`);
578
+ const current = schema.fields.find((f) => f.field === field).when ?? {};
579
+ const keys = ['visible', 'required', 'disabled'];
580
+ // Absent = leave as it was; null = clear it. Without that distinction an
581
+ // agent setting one condition would silently drop the other two.
582
+ const given = keys.filter((k) => args[k] !== undefined);
583
+ const when = given.length
584
+ ? Object.fromEntries(keys.map((k) => [k, args[k] === undefined ? current[k] : (args[k] || undefined)]))
585
+ : undefined;
586
+ const next = setFieldConditions(p, entity, field, when);
587
+ project = next;
588
+ const set = keys.filter((k) => next.entities.find((e) => e.name === entity).fields.find((f) => f.field === field).when?.[k]);
589
+ return confirm(set.length ? `"${entity}.${field}" is now conditional on: ${set.join(', ')}.` : `Cleared the conditions on "${entity}.${field}".`);
590
+ }
509
591
  case 'studio_set_entity_source': {
510
592
  const p = requireProject();
511
593
  const entity = String(args.entity ?? '');
package/package.json CHANGED
@@ -5,9 +5,9 @@
5
5
  "type": "commercial",
6
6
  "url": "https://svgrid.com/pricing"
7
7
  },
8
- "version": "2.5.0",
8
+ "version": "2.6.2",
9
9
  "description": "Model Context Protocol (MCP) server for SvGrid. Exposes example sources, docs, and API reference to AI assistants.",
10
- "license": "SEE LICENSE IN LICENSE",
10
+ "license": "MIT",
11
11
  "author": "jQWidgets <sales@jqwidgets.com>",
12
12
  "homepage": "https://svgrid.com/docs/help/mcp-server/",
13
13
  "repository": {
@@ -32,7 +32,7 @@
32
32
  "dependencies": {
33
33
  "@modelcontextprotocol/sdk": "^1.0.4",
34
34
  "zod": "^3.23.8",
35
- "@svgrid/enterprise": "^2.5.0"
35
+ "@svgrid/enterprise": "^2.6.3"
36
36
  },
37
37
  "devDependencies": {
38
38
  "@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": "Version-pinned Svelte 5 data grid APIs, 373 demo sources, and SvelteKit app scaffolding.",
6
- "version": "2.5.0",
6
+ "version": "2.6.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": "2.5.0",
18
+ "version": "2.6.1",
19
19
  "runtimeHint": "npx",
20
20
  "transport": {
21
21
  "type": "stdio"