@svgrid/mcp
Advanced tools
@@ -15,3 +15,3 @@ /** | ||
| */ | ||
| 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'; | ||
| 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'; | ||
| // ---- session state -------------------------------------------------------- | ||
@@ -228,2 +228,31 @@ let project = null; | ||
| { | ||
| name: 'studio_set_form_layout', | ||
| 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.', | ||
| inputSchema: { | ||
| type: 'object', | ||
| properties: { | ||
| entity: { type: 'string' }, | ||
| columns: { type: 'number', enum: [1, 2, 3] }, | ||
| sections: { type: 'array', items: { type: 'object' }, description: 'The FormSection list. Replaces the current one.' }, | ||
| suggest: { type: 'boolean', description: 'Propose sections from the field names instead of passing them.' }, | ||
| }, | ||
| required: ['entity'], | ||
| }, | ||
| }, | ||
| { | ||
| name: 'studio_set_field_conditions', | ||
| 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.', | ||
| inputSchema: { | ||
| type: 'object', | ||
| properties: { | ||
| entity: { type: 'string' }, | ||
| field: { type: 'string' }, | ||
| visible: { type: ['object', 'null'], description: 'PredicateExpr, or null to clear.' }, | ||
| required: { type: ['object', 'null'], description: 'PredicateExpr, or null to clear.' }, | ||
| disabled: { type: ['object', 'null'], description: 'PredicateExpr, or null to clear.' }, | ||
| }, | ||
| required: ['entity', 'field'], | ||
| }, | ||
| }, | ||
| { | ||
| name: 'studio_set_entity_source', | ||
@@ -511,2 +540,55 @@ 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", ... }.', | ||
| } | ||
| case 'studio_set_form_layout': { | ||
| const p = requireProject(); | ||
| const entity = String(args.entity ?? ''); | ||
| const schema = p.entities.find((e) => e.name === entity); | ||
| if (!schema) | ||
| return fail(`No entity "${entity}".`); | ||
| const columns = args.columns === undefined ? schema.form?.columns : Number(args.columns); | ||
| if (columns !== undefined && ![1, 2, 3].includes(columns)) | ||
| return fail('columns must be 1, 2, or 3.'); | ||
| let sections = schema.form?.sections; | ||
| if (args.suggest) { | ||
| sections = suggestFormSections(schema); | ||
| if (!sections.length) | ||
| return fail(`Nothing to suggest for "${entity}" - too few form fields to be worth grouping.`); | ||
| } | ||
| else if (args.sections !== undefined) { | ||
| if (!Array.isArray(args.sections)) | ||
| return fail('sections must be an array of FormSection objects.'); | ||
| sections = args.sections; | ||
| } | ||
| // Plan before storing: a name that resolves to nothing (a typo, or a | ||
| // field since renamed) is dropped here rather than persisted into | ||
| // `studio.config.json` for a later reader to puzzle over. The reply | ||
| // reports the plan, so the agent sees what actually landed. | ||
| const plan = formPlan(schema, sections); | ||
| project = setEntityForm(p, entity, { columns, sections: plan.sections }); | ||
| const placed = plan.sections.map((s) => `${s.title ?? '(untitled)'}: ${s.fields.join(', ') || '(empty)'}`); | ||
| return confirm(`"${entity}" form: ${columns ?? 2} columns, ${plan.sections.length} section(s).` + | ||
| (placed.length ? `\n${placed.join('\n')}` : '') + | ||
| (plan.unassigned.length ? `\nUnsectioned (render last): ${plan.unassigned.join(', ')}` : '')); | ||
| } | ||
| case 'studio_set_field_conditions': { | ||
| const p = requireProject(); | ||
| const entity = String(args.entity ?? ''); | ||
| const field = String(args.field ?? ''); | ||
| const schema = p.entities.find((e) => e.name === entity); | ||
| if (!schema) | ||
| return fail(`No entity "${entity}".`); | ||
| if (!schema.fields.some((f) => f.field === field)) | ||
| return fail(`No field "${field}" on "${entity}".`); | ||
| const current = schema.fields.find((f) => f.field === field).when ?? {}; | ||
| const keys = ['visible', 'required', 'disabled']; | ||
| // Absent = leave as it was; null = clear it. Without that distinction an | ||
| // agent setting one condition would silently drop the other two. | ||
| const given = keys.filter((k) => args[k] !== undefined); | ||
| const when = given.length | ||
| ? Object.fromEntries(keys.map((k) => [k, args[k] === undefined ? current[k] : (args[k] || undefined)])) | ||
| : undefined; | ||
| const next = setFieldConditions(p, entity, field, when); | ||
| project = next; | ||
| const set = keys.filter((k) => next.entities.find((e) => e.name === entity).fields.find((f) => f.field === field).when?.[k]); | ||
| return confirm(set.length ? `"${entity}.${field}" is now conditional on: ${set.join(', ')}.` : `Cleared the conditions on "${entity}.${field}".`); | ||
| } | ||
| case 'studio_set_entity_source': { | ||
@@ -513,0 +595,0 @@ const p = requireProject(); |
+2
-2
@@ -8,3 +8,3 @@ { | ||
| }, | ||
| "version": "2.5.0", | ||
| "version": "2.6.1", | ||
| "description": "Model Context Protocol (MCP) server for SvGrid. Exposes example sources, docs, and API reference to AI assistants.", | ||
@@ -36,3 +36,3 @@ "license": "SEE LICENSE IN LICENSE", | ||
| "zod": "^3.23.8", | ||
| "@svgrid/enterprise": "^2.5.0" | ||
| "@svgrid/enterprise": "^2.6.1" | ||
| }, | ||
@@ -39,0 +39,0 @@ "devDependencies": { |
+2
-2
@@ -6,3 +6,3 @@ { | ||
| "description": "Version-pinned Svelte 5 data grid APIs, 373 demo sources, and SvelteKit app scaffolding.", | ||
| "version": "2.5.0", | ||
| "version": "2.6.1", | ||
| "websiteUrl": "https://svgrid.com/docs/help/mcp-server/", | ||
@@ -19,3 +19,3 @@ "repository": { | ||
| "identifier": "@svgrid/mcp", | ||
| "version": "2.5.0", | ||
| "version": "2.6.1", | ||
| "runtimeHint": "npx", | ||
@@ -22,0 +22,0 @@ "transport": { |
Sorry, the diff of this file is too big to display
6531182
0.43%2049
4.33%Updated