@dforge-core/dforge-mcp 0.1.7 → 0.1.9
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/CHANGELOG.md +22 -0
- package/dist/server.js +333 -292
- package/package.json +4 -4
- package/resources/docs/dsl-reference.md +16 -3
- package/resources/schemas/entity.schema.json +10 -0
- package/resources/schemas/triggers.schema.json +1 -1
- package/skills/dforge-mcp-author/references/action-dsl.md +2 -1
- package/skills/dforge-mcp-author/references/data-views.md +23 -0
- package/skills/dforge-mcp-author/references/field-types.md +6 -4
- package/skills/dforge-mcp-author/references/validation-checklist.md +2 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dforge-core/dforge-mcp",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.9",
|
|
4
4
|
"description": "MCP server for dForge module authoring. Exposes scaffold/pack/install tools and schema resources so AI agents (Claude Code, Cursor, Zed) can create and ship dForge modules.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://github.com/iash44/dForge-core",
|
|
@@ -32,14 +32,14 @@
|
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
34
|
"@dforge-core/dforge-cli": "^0.2.7",
|
|
35
|
-
"@dforge-core/metadata": "^0.0.
|
|
35
|
+
"@dforge-core/metadata": "^0.0.8",
|
|
36
36
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
37
37
|
"zod": "^4.4.3"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
|
-
"@types/node": "^22.
|
|
40
|
+
"@types/node": "^22.20.1",
|
|
41
41
|
"tsup": "^8.5.1",
|
|
42
42
|
"typescript": "^6.0.3",
|
|
43
|
-
"vitest": "^3.2.
|
|
43
|
+
"vitest": "^3.2.7"
|
|
44
44
|
}
|
|
45
45
|
}
|
|
@@ -168,15 +168,28 @@ var po = insert('purchase_order', { supplier_id: params[supplier].supplier_id })
|
|
|
168
168
|
insert('po_line', { po_id: po.purchase_order_id, product_id: [product_id], qty: 1 })
|
|
169
169
|
```
|
|
170
170
|
|
|
171
|
-
#### `getRecord(entityCd,
|
|
172
|
-
Fetch a single record by
|
|
171
|
+
#### `getRecord(entityCd, key) → record`
|
|
172
|
+
Fetch a single record by key. **Throws** a localized "not found" error if no row
|
|
173
|
+
matches — the safe default for a lookup that should always resolve (e.g. an FK you
|
|
174
|
+
just read). Fields are readable by dot notation (`rec.name`) or `rec.get('name')`.
|
|
175
|
+
`key` is a scalar PK, or an object for a compound key: `getRecord('gl.tag', { tag_group: 'REGION', tag_code: 'EU' })`.
|
|
176
|
+
The result is a **read-only** snapshot — writing to it (`rec.set(...)`) throws; use
|
|
177
|
+
`update()` to persist. A null key value can't match, so it counts as not-found.
|
|
173
178
|
|
|
174
179
|
```dsl
|
|
175
180
|
var rec = getRecord('crm.customer', [customer_id])
|
|
176
|
-
if (rec == null) { error('Customer not found') }
|
|
177
181
|
info('Customer: ' + rec.name + ' — credit limit $' + rec.credit_limit)
|
|
178
182
|
```
|
|
179
183
|
|
|
184
|
+
#### `getRecordOrNull(entityCd, key) → record | null`
|
|
185
|
+
Same as `getRecord`, but returns `null` instead of throwing when the row is absent —
|
|
186
|
+
use it when a missing record is an expected outcome (optional lookup, upsert probe).
|
|
187
|
+
|
|
188
|
+
```dsl
|
|
189
|
+
var existing = getRecordOrNull('crm.customer', [customer_id])
|
|
190
|
+
if (existing == null) { insert('crm.customer', { customer_id: [customer_id], name: [name] }) }
|
|
191
|
+
```
|
|
192
|
+
|
|
180
193
|
#### `callProc(name, args?) → record`
|
|
181
194
|
Call a stored procedure with named args. Returns the proc's result row (or rows if it RETURNS TABLE).
|
|
182
195
|
|
|
@@ -472,6 +472,16 @@
|
|
|
472
472
|
"field": { "type": "string" }
|
|
473
473
|
},
|
|
474
474
|
"additionalProperties": false
|
|
475
|
+
},
|
|
476
|
+
"onDelete": {
|
|
477
|
+
"type": "string",
|
|
478
|
+
"enum": ["cascade", "setNull", "restrict", "noAction"],
|
|
479
|
+
"description": "FK ON DELETE behaviour. Default (omitted) = noAction (plain FK). 'cascade' deletes children with the parent; 'setNull' nulls the FK column (must be nullable)."
|
|
480
|
+
},
|
|
481
|
+
"onUpdate": {
|
|
482
|
+
"type": "string",
|
|
483
|
+
"enum": ["cascade", "setNull", "restrict", "noAction"],
|
|
484
|
+
"description": "FK ON UPDATE behaviour. Default (omitted) = noAction. Fires when a parent's key value changes — a no-op for immutable cuid PKs, but useful for entities keyed on a natural/mutable PK."
|
|
475
485
|
}
|
|
476
486
|
},
|
|
477
487
|
"additionalProperties": false
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
"async": {
|
|
51
51
|
"type": "boolean",
|
|
52
52
|
"default": false,
|
|
53
|
-
"description": "When true, the action runs in the background — the triggering transaction commits before the action starts. Recommended for slow actions (emails, external API calls). When false, the action runs
|
|
53
|
+
"description": "When true, the action runs in the background — the triggering transaction commits before the action starts. Recommended for slow actions (emails, external API calls); background failures are logged, not surfaced to the caller. When false, the action runs synchronously inside the same transaction as the triggering insert/update/delete: if the action fails (e.g. calls error()), the whole mutation rolls back and the error is returned to the caller. Keep async:false actions fast — they run on the request's transaction."
|
|
54
54
|
}
|
|
55
55
|
},
|
|
56
56
|
"additionalProperties": false
|
|
@@ -107,7 +107,8 @@ var note = params[note] // no quotes around param name in this DSL
|
|
|
107
107
|
|---|---|---|
|
|
108
108
|
| `insert('entity', { field: value, … })` | Inserted row object | Insert a new record. Returns the full row including auto-filled columns (PK, audit, number sequences). Access returned fields with `result.field_name`. |
|
|
109
109
|
| `query('SQL', { arg: value })` | Array of row objects | Execute parameterized SQL. Use `@argName` placeholders. Schema-qualified table names (`crm.contact`). Access row fields with `row.field_name` or `row['field_name']`. |
|
|
110
|
-
| `getRecord('entity',
|
|
110
|
+
| `getRecord('entity', key)` | Row object | Fetch a single record by key (qualified `module.entity` supported). `key` is a scalar PK or an object for a compound key (`{ col_a: 1, col_b: 'x' }`). Access fields with `rec.field_name` (or `rec.get('field_name')`). **Throws** a localized "not found" error if absent. |
|
|
111
|
+
| `getRecordOrNull('entity', key)` | Row object or `null` | Like `getRecord`, but returns `null` instead of throwing when the row is absent — use for an expected-absence lookup, guarded with `if (rec == null)`. |
|
|
111
112
|
| `preloadRef('fk_column')` | Row object | Load the referenced record for a FK column. Access its fields with `ref.get('field')`. |
|
|
112
113
|
|
|
113
114
|
### Messaging
|
|
@@ -58,6 +58,28 @@ Lives in: `ui/data_views.json`
|
|
|
58
58
|
}
|
|
59
59
|
```
|
|
60
60
|
|
|
61
|
+
## Critical rule — the entity needs a visible column
|
|
62
|
+
|
|
63
|
+
A grid-style view can only render an entity that has at least one **visible scalar column** — a field whose `flags` include `V` and whose `columnType` is **not** a set (`S`). Column visibility is entity-driven (the field's flags), **not** view-driven: the `columns` array inside `dataSources[]` is layout-only (reorder/pin/width among already-visible fields) — it can't make a non-`V` field appear.
|
|
64
|
+
|
|
65
|
+
If none of an entity's fields are visible (or only its set/child-collection columns are), the view renders the runtime empty state *"No visible columns configured for this entity."* — a broken screen the user only hits when they open the menu item. `dforge_module_validate` and `dforge_module_pack` now **reject** this before install.
|
|
66
|
+
|
|
67
|
+
```jsonc
|
|
68
|
+
// WRONG — grid over an entity whose fields are all hidden (no "V")
|
|
69
|
+
"fields": {
|
|
70
|
+
"code": { "fieldTypeCd": "text", "flags": "EM" }, // E=editable, M=… but no V
|
|
71
|
+
"lines": { "columnType": "S", "flags": "VEM" } // visible, but a SET — doesn't count for a grid
|
|
72
|
+
}
|
|
73
|
+
// RIGHT — at least one visible scalar field
|
|
74
|
+
"fields": {
|
|
75
|
+
"code": { "fieldTypeCd": "text", "flags": "VEM" } // V present, not a set → satisfies the grid
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
This applies to every column-rendering view type (grid, list, kanban, calendar, gallery, tree-grid, master-detail, and the default). The column-agnostic types — `diagram`, `matrix`, `library` — draw from `viewConfig`/relationships instead, so they're **exempt** from this rule.
|
|
80
|
+
|
|
81
|
+
See [flags.md](flags.md) for the flag string, [column-types.md](column-types.md) for `columnType` (`R`/`S`/`F`).
|
|
82
|
+
|
|
61
83
|
## View types
|
|
62
84
|
|
|
63
85
|
| `viewType` | Description | `viewConfig` | When to use |
|
|
@@ -243,3 +265,4 @@ Most views only have one source. Order is view-level and applies to the primary
|
|
|
243
265
|
- Object-array order like `[{ column_cd: "...", direction: "asc" }]` — **wrong shape for data views**. Use `string[]` with leading `-` for descending.
|
|
244
266
|
- `viewType` is **optional** — omitted or unknown values fall back to `grid` at runtime. Set it explicitly for any non-grid view (`kanban`, `calendar`, `matrix`, …); a `matrix` view also **requires** a `viewConfig` with `rowAxis`/`colAxis`/`cell`.
|
|
245
267
|
- Inventing view types like `"spreadsheet"` or `"table"` — **wrong**. Use `grid`.
|
|
268
|
+
- A grid-style view over an entity with **no visible scalar column** (no field flagged `V`, or only set columns) — **rejected** at validate/pack. Mark at least one non-set field visible. See the [critical rule above](#critical-rule--the-entity-needs-a-visible-column). `diagram`/`matrix`/`library` are exempt.
|
|
@@ -53,10 +53,12 @@ Source of truth: `server/database/system-modules/metadata/seed-data/field_types.
|
|
|
53
53
|
|
|
54
54
|
## Binary / file
|
|
55
55
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
| `
|
|
59
|
-
|
|
56
|
+
> **Stored as `jsonb`, not `bytea`.** Despite the `binary` base datatype, a `file`/`image` column holds a JSON metadata reference (`{ storagePath, fileName, … }`) — the bytes live in file storage. The field tools derive `jsonb`; do **not** set `dbDatatype: "bytea"`.
|
|
57
|
+
|
|
58
|
+
| `fieldTypeCd` | `baseDatatypeCd` | Description | Common `dbDatatype` | Params |
|
|
59
|
+
|---|---|---|---|---|
|
|
60
|
+
| `file` | `binary` | Arbitrary file upload. | `jsonb` | `accept`, `maxSize` |
|
|
61
|
+
| `image` | `binary` | Image upload with preview. | `jsonb` | `maxSize`, `dimensions` |
|
|
60
62
|
|
|
61
63
|
## Reference / lookup / set
|
|
62
64
|
|
|
@@ -72,6 +72,7 @@ For each entity:
|
|
|
72
72
|
- [ ] Each source has `entityCode` and `columns`
|
|
73
73
|
- [ ] `entityCode` points to an entity that exists
|
|
74
74
|
- [ ] Column codes in `columns` all exist on the entity
|
|
75
|
+
- [ ] The rendered entity has **at least one visible scalar column** (a field with `V` in `flags`, `columnType` not `S`) — a grid/list/kanban/etc. view over an entity whose fields are all hidden (or only sets) is **rejected** at validate/pack and would render "No visible columns configured for this entity." `diagram`/`matrix`/`library` are exempt
|
|
75
76
|
- [ ] If set, `viewType` is from the supported list (`grid`, `list`, `kanban`, `calendar`, `gallery`, `tree-grid`, `diagram`, `master-detail`, `library`, `matrix`)
|
|
76
77
|
- [ ] A `matrix` view has a `viewConfig` with `rowAxis`, `colAxis`, and `cell` (cell `entity` matches the primary `dataSources` entity; `rowKey`/`colKey` are real cell columns)
|
|
77
78
|
- [ ] Sort uses the view-def-root `order` key — a `string[]` like `["-created_date", "name"]` (leading `-` = descending), NOT `sort` / `[{column_cd, direction}]` (that object shape belongs to queries & reports, not data views)
|
|
@@ -172,6 +173,7 @@ If you see any of these, stop and investigate:
|
|
|
172
173
|
- Menus with `children: [...]` (array, not dict)
|
|
173
174
|
- Roles with `entityRights` (wrong key)
|
|
174
175
|
- Data views with root-level `entityCode` (should be inside `dataSources`)
|
|
176
|
+
- A grid/list/kanban/etc. view over an entity with no field flagged `V` (or only set columns visible) — rejected at validate/pack
|
|
175
177
|
- Formula columns without `baseDatatypeCd`
|
|
176
178
|
- DSL actions with JavaScript/Python syntax
|
|
177
179
|
- Seed data files without numbered prefixes
|