@dforge-core/dforge-mcp 0.2.32 → 0.2.34
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 +44 -0
- package/dist/server.js +219 -147
- package/package.json +46 -48
- package/resources/docs/conventions.md +21 -1
- package/resources/schemas/deps.schema.json +7 -2
- package/resources/schemas/domains.schema.json +1 -1
- package/resources/schemas/entity.schema.json +10 -13
- package/resources/schemas/registers.schema.json +415 -0
- package/resources/schemas/reports.schema.json +69 -4
- package/resources/schemas/seed-data.schema.json +12 -1
- package/resources/schemas/settings.schema.json +24 -1
- package/resources/schemas/traits.schema.json +26 -1
- package/resources/schemas/translations.schema.json +2 -2
- package/skills/dforge-mcp-author/examples/column-security/README.md +2 -3
- package/skills/dforge-mcp-author/references/conventions.md +4 -3
- package/skills/dforge-mcp-author/references/data-views.md +1 -1
- package/skills/dforge-mcp-author/references/manifest.md +21 -3
- package/skills/dforge-mcp-author/references/reports.md +6 -2
- package/skills/dforge-mcp-author/references/security.md +3 -2
- package/skills/dforge-mcp-author/references/translations.md +3 -3
- package/skills/dforge-module-build/SKILL.md +4 -0
- package/skills/dforge-module-ship/SKILL.md +5 -0
package/package.json
CHANGED
|
@@ -1,49 +1,47 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
}
|
|
49
|
-
}
|
|
2
|
+
"name": "@dforge-core/dforge-mcp",
|
|
3
|
+
"version": "0.2.34",
|
|
4
|
+
"description": "MCP server for dForge module authoring. Exposes 35 scaffold/patch/validate/pack/install tools and schema resources so AI agents (Claude Code, Cursor, Zed) can create and ship dForge modules.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"homepage": "https://github.com/iash44/dForge-core",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/dforge-core/dforge-mcp.git"
|
|
10
|
+
},
|
|
11
|
+
"bin": {
|
|
12
|
+
"dforge-mcp": "dist/server.js",
|
|
13
|
+
"dforge-install-skills": "scripts/install-skills.mjs"
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist/",
|
|
17
|
+
"docs/",
|
|
18
|
+
"resources/",
|
|
19
|
+
"skills/",
|
|
20
|
+
"scripts/install-skills.mjs",
|
|
21
|
+
"README.md",
|
|
22
|
+
"CHANGELOG.md"
|
|
23
|
+
],
|
|
24
|
+
"main": "dist/server.js",
|
|
25
|
+
"engines": {
|
|
26
|
+
"node": ">=18"
|
|
27
|
+
},
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"@dforge-core/dforge-cli": "^0.2.27",
|
|
30
|
+
"@dforge-core/metadata": "^0.0.34",
|
|
31
|
+
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
32
|
+
"zod": "^4.4.3"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@types/node": "^26.2.0",
|
|
36
|
+
"tsup": "^8.5.1",
|
|
37
|
+
"typescript": "^6.0.3",
|
|
38
|
+
"vitest": "^4.1.10"
|
|
39
|
+
},
|
|
40
|
+
"scripts": {
|
|
41
|
+
"build": "tsup",
|
|
42
|
+
"sync-schemas": "node scripts/vendor-schemas.cjs",
|
|
43
|
+
"typecheck": "tsc --noEmit",
|
|
44
|
+
"test": "vitest run",
|
|
45
|
+
"install-skills": "node scripts/install-skills.mjs"
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -71,6 +71,8 @@ Use the CRM sample module (`modules/crm/`) as a reference implementation.
|
|
|
71
71
|
├── /print_templates/ # HTML/CSS print templates (optional)
|
|
72
72
|
│ ├── invoice.html
|
|
73
73
|
│ └── invoice.css
|
|
74
|
+
├── /migrations/ # Upgrade data scripts, {version}.sql (optional)
|
|
75
|
+
│ └── 0.2.0.sql
|
|
74
76
|
└── /files/ # Module static files (optional) — README, docs
|
|
75
77
|
└── README.md
|
|
76
78
|
```
|
|
@@ -512,7 +514,7 @@ Two halves, both required:
|
|
|
512
514
|
|
|
513
515
|
**`"default"` is the one exempt name** and means "no view" (full column set). It is what the ~100 shipped folder entries write, what `FolderExporter` writes back for a NULL `view_name`, and what the runtime auto-creates (column-less) for an entity reached without a folder binding — so rejecting it would fail every install. A module that really declares a view named `default` still gets it bound.
|
|
514
516
|
|
|
515
|
-
Don't confuse the
|
|
517
|
+
Don't confuse the two things called "view": an **entity view** (`views`, this section) is column security; a **data view** (`ui/data_views.json`, referenced by a menu's `dataViewCode`) is a grid/kanban/calendar. `folders.json`'s `viewName` binds the first. An entity is never backed by a SQL view — `isView` / `viewSql` are rejected; use a data view, a report or a stored procedure instead.
|
|
516
518
|
|
|
517
519
|
## Column Domains (`domains.json`)
|
|
518
520
|
|
|
@@ -964,6 +966,24 @@ Use semantic versioning: `MAJOR.MINOR.PATCH`
|
|
|
964
966
|
1. **`version`**: Overall module version (includes all changes)
|
|
965
967
|
2. **`dbSchemaVersion`**: Database schema version (only bumped for schema changes)
|
|
966
968
|
|
|
969
|
+
### Upgrade Migrations (`migrations/`)
|
|
970
|
+
|
|
971
|
+
A table or column change needs no script: the installer generates the DDL. A script is for the DATA a new version reshapes — filling a new column from an old one, moving rows to a new table, dropping a table the new version no longer declares (the platform never drops one on its own).
|
|
972
|
+
|
|
973
|
+
```
|
|
974
|
+
migrations/
|
|
975
|
+
└── 0.11.0.sql # runs on an upgrade from below 0.11.0 to 0.11.0 or later
|
|
976
|
+
```
|
|
977
|
+
|
|
978
|
+
- **Name** — the module `version` the file upgrades to, `MAJOR.MINOR.PATCH.sql`. Keyed by `version`, not `dbSchemaVersion`. A misnamed file, or one newer than the manifest `version`, fails the package at load.
|
|
979
|
+
- **When** — on an upgrade only: every file above the installed version and no higher than the package's, in ascending order. A fresh install or a same-version reinstall runs none.
|
|
980
|
+
- **Where** — inside the install transaction, after the new tables and columns exist and before `NOT NULL` is applied or a register's `totals` cache is rebuilt. A table the new version removed is still there to read. A failing script rolls the upgrade back and the module stays on the old version; refuse unconvertible data with `RAISE EXCEPTION '…'`, which the operator sees.
|
|
981
|
+
- **Ids** — `"dForge".next_id()` mints a row id in SQL. Declare `"admin": ">=1.21.0"` in `dependencies`; that is also what guarantees a platform that runs migrations.
|
|
982
|
+
- **Locks** — a script writing a posted document or its register journal wraps the writes in `SELECT set_config('registry.posting', 'true', true);` … `'false'`, so the post locks stand aside. Period locks do not; reopen a closed period and close it again.
|
|
983
|
+
- A `kind: "pack"` module cannot carry migrations.
|
|
984
|
+
|
|
985
|
+
Reference: `modules/gl/migrations/0.11.0.sql` in dForge-core.
|
|
986
|
+
|
|
967
987
|
---
|
|
968
988
|
|
|
969
989
|
## Installation & Reinstallation
|
|
@@ -18,11 +18,16 @@
|
|
|
18
18
|
},
|
|
19
19
|
"entities": {
|
|
20
20
|
"type": "object",
|
|
21
|
-
"description": "Consumed provider entities, keyed by entity code (e.g. 'party').",
|
|
22
|
-
"minProperties": 1,
|
|
21
|
+
"description": "Consumed provider entities, keyed by entity code (e.g. 'party'). At least one, except against a system module (admin, metadata, workspace, sys-billing), where {} declares a pure platform-version gate for a feature that introduced no entity.",
|
|
23
22
|
"additionalProperties": { "$ref": "#/definitions/entityContract" }
|
|
24
23
|
}
|
|
25
24
|
},
|
|
25
|
+
"if": {
|
|
26
|
+
"not": { "properties": { "module": { "enum": ["admin", "metadata", "workspace", "sys-billing"] } }, "required": ["module"] }
|
|
27
|
+
},
|
|
28
|
+
"then": {
|
|
29
|
+
"properties": { "entities": { "minProperties": 1 } }
|
|
30
|
+
},
|
|
26
31
|
"definitions": {
|
|
27
32
|
"entityContract": {
|
|
28
33
|
"type": "object",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
3
|
"$id": "https://dforge.dev/schemas/domains.schema.json",
|
|
4
4
|
"title": "dForge Column Domains",
|
|
5
|
-
"description": "domains.json — reusable, named semantic types. A domain bundles a base datatype, a control, sizing and a shared option list under one code; columns reference it via \"domain\": \"module_cd.domain_cd\" instead of restating the definition. Structural fields are materialized onto each column at install; options stay on the domain and resolve at metadata-load time, so a shared list is authored and translated exactly once.",
|
|
5
|
+
"description": "domains.json — reusable, named semantic types. A domain bundles a base datatype, a control, sizing and a shared option list under one code; columns reference it via \"domain\": \"domain_cd\" (this module's own) or \"module_cd.domain_cd\" (another module's) instead of restating the definition. Structural fields are materialized onto each column at install; options stay on the domain and resolve at metadata-load time, so a shared list is authored and translated exactly once.",
|
|
6
6
|
"type": "object",
|
|
7
7
|
"additionalProperties": {
|
|
8
8
|
"$ref": "#/$defs/domain"
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
3
|
"$id": "https://dforge.dev/schemas/entity.schema.json",
|
|
4
4
|
"title": "dForge Entity Definition",
|
|
5
|
-
"description": "Defines an entity (table
|
|
5
|
+
"description": "Defines an entity (a table) for a dForge module",
|
|
6
6
|
"type": "object",
|
|
7
7
|
"anyOf": [
|
|
8
8
|
{ "required": ["fields"] },
|
|
@@ -24,16 +24,7 @@
|
|
|
24
24
|
},
|
|
25
25
|
"dbObject": {
|
|
26
26
|
"type": "string",
|
|
27
|
-
"description": "Database table
|
|
28
|
-
},
|
|
29
|
-
"isView": {
|
|
30
|
-
"type": "boolean",
|
|
31
|
-
"description": "When true, entity maps to a SQL view (read-only, no DDL generation). Default: false (table-backed).",
|
|
32
|
-
"default": false
|
|
33
|
-
},
|
|
34
|
-
"viewSql": {
|
|
35
|
-
"type": "string",
|
|
36
|
-
"description": "SQL SELECT statement for view-backed entities (isView=true). Executed as CREATE OR REPLACE VIEW during module install."
|
|
27
|
+
"description": "Database table name (defaults to entity code)"
|
|
37
28
|
},
|
|
38
29
|
"toString": {
|
|
39
30
|
"description": "Display pattern using column placeholders, e.g. \"{first_name} {last_name}\" — the caption of a record wherever it is referenced. Every `{placeholder}` must name a field of the entity (trait columns included) and not a set; pack and install fail otherwise. Omitted, the install generates one from the fields and warns. Not declared on extension entities (with `extends`). No `type` constraint here because `toString` collides with Object.prototype.toString in JSON validators — they read the inherited function and reject it, even when the property is absent from the JSON."
|
|
@@ -151,7 +142,7 @@
|
|
|
151
142
|
},
|
|
152
143
|
"views": {
|
|
153
144
|
"type": "object",
|
|
154
|
-
"description": "Entity views keyed by view name — column-level security. Unrelated to
|
|
145
|
+
"description": "Entity views keyed by view name — column-level security. Unrelated to ui/data_views.json (grids, kanban, calendars). A folder binds one view per entity via ui/folders.json (entities.<code>.viewName); users working in that folder see ONLY the columns the view lists. Entities without views show the entity's full column set; a folder naming a view that isn't declared here FAILS the install, since resolving it to nothing would show every column \u2014 the opposite of what naming a view asks for. A view can only be declared by the entity's own module: an extension ('extends') declaring 'views' is rejected, because a view lists the complete column set and an extension knows only the columns it adds.",
|
|
155
146
|
"propertyNames": {
|
|
156
147
|
"pattern": "^[a-zA-Z_][a-zA-Z0-9_-]*$",
|
|
157
148
|
"maxLength": 100
|
|
@@ -250,12 +241,18 @@
|
|
|
250
241
|
"lineEntity": { "type": "string", "description": "The line entity, defined in this module. Declare together with lineField and lockedLineFields, or none of the three for a document without lines." },
|
|
251
242
|
"lineField": { "type": "string", "description": "The line's foreign-key column(s) to the header, comma-separated in the header's key order for a composite key." },
|
|
252
243
|
"stateField": { "type": "string", "description": "The bool column that records the posted state, when it is not the registry column itself. Written only by the register engine." },
|
|
244
|
+
"register": { "type": "string", "description": "The register this column posts to, as 'module_cd.register_cd' (declared in that module's registers.json). Binding to one replaces restating what the register is: the declaration supplies the state entity, dimensions, resources, journal, allowNegative and autoCreate, and this column keeps only what legitimately differs per writer — direction/sign, 'map' and 'dateField'." },
|
|
245
|
+
"map": { "type": "object", "additionalProperties": { "type": "string" }, "description": "Register dimension or resource name -> this document's column carrying it. Every resource the register declares must be mapped (install refuses otherwise); a dimension left out binds to a column of the same name. Only meaningful with 'register'. On an accumulation (A) column a value may be a one-hop reference path, '<reference column>.<target column>', read through the reference at posting time (off the line when the binding posts from lines)." },
|
|
246
|
+
"headerMap": { "type": "object", "additionalProperties": { "type": "string" }, "description": "Register dimension or resource name -> this DOCUMENT's column carrying it, for a binding that posts from LINES. A line binding reads every other value off the line, and a dimension that belongs to the document (a dealer, an organisation, a date) cannot be put on the line: a G column is a same-row expression or an aggregate over a child set, so it cannot read a parent. Explicit rather than a fallback, because 'not on the line, try the header' turns a typo into a silent read of an unrelated header column with the same name. Install refuses: a name on neither table, a field also named in 'map', a column also read off the line by 'map', a column that is part of the line's key, a column 'lockedFields' does not freeze (the posting is keyed by it, so it has to hold still while the document is posted), and a RESOURCE unless the register is 'info' (a quantity from the document would be added once per line; an information register's records replace rather than add). A value may be a one-hop reference path off the document, whose reference key column 'lockedFields' must then freeze." },
|
|
247
|
+
"dateField": { "type": "string", "description": "This document's date column, supplying the date the register stamps on each journal row. Required once the register declares a 'journalDate' — one writer that supplied none would make every balance read as of a date quietly short, across all writers. On an accumulation (A) column it may be a one-hop reference path, '<reference column>.<target column>'." },
|
|
248
|
+
"skipPostingWhenEmpty": { "type": "array", "items": { "type": "string" }, "description": "THE EXCEPTION, not the normal way to model this — read the guide before declaring it. Register dimensions (by the register's names) whose empty value means THIS POSTING records nothing: the line, or the document when the binding posts from the document. One posting is skipped, never the register and never the document. When that is all the document would have recorded it is left unposted to this register, and the flag is per binding, so its other registers post as usual. An empty dimension NOT listed refuses the posting, naming the row and the dimension. It leaves a document fully posted while some of its lines produced no movement, which usually means the document is modelling two kinds of line in one table: since 'lineEntity' is declared per BINDING, prefer giving each register its own line table (goods lines post to stock, service lines do not) so that every line of each table posts. Declare this only when the distinction is not structural but lives in the data — a batch or serial number filled on some rows and not others — so there is no second table to put those rows in. Accumulation (A) columns only. Formerly skipWhenNull: a package using the old name is refused at install, while a tenant's already-stored params keep being read under it." },
|
|
253
249
|
"periodEntity": { "type": "string", "description": "Period entity this registry closes against — one of this module's, or 'module.entity' from a dependency." },
|
|
254
250
|
"periodDateField": { "type": "string", "description": "Header date column that resolves the period." },
|
|
255
251
|
"periodKeyField": { "type": "string", "default": "period_key" },
|
|
256
252
|
"periodClosedField": { "type": "string", "default": "closed" }
|
|
257
253
|
},
|
|
258
254
|
"dependencies": {
|
|
255
|
+
"headerMap": ["lineEntity"],
|
|
259
256
|
"lineEntity": ["lineField", "lockedLineFields"],
|
|
260
257
|
"lineField": ["lineEntity"],
|
|
261
258
|
"lockedLineFields": ["lineEntity"],
|
|
@@ -398,7 +395,7 @@
|
|
|
398
395
|
},
|
|
399
396
|
"domain": {
|
|
400
397
|
"type": "string",
|
|
401
|
-
"description": "Reference to a reusable column domain
|
|
398
|
+
"description": "Reference to a reusable column domain. A bare 'domain_cd' means this module's own domain, then a tenant-created one, and never another module's, even one with the same code. 'module_cd.domain_cd' names a domain of any installed module; declare that module as a dependency. The domain supplies dbDatatype, baseDatatypeCd, fieldTypeCd, maxLen, precision and params; declaring any of those alongside 'domain' is rejected at install. The column keeps its own description, flags, orderNum, isNullable and isPk."
|
|
402
399
|
},
|
|
403
400
|
"refFilter": {
|
|
404
401
|
"description": "Filter criteria for reference lookups. Condition values support @[field] placeholders resolved against the current record at lookup time (e.g. {\"c\":\"channel\",\"o\":\"eq\",\"v\":\"@[channel]\"} narrows options to rows matching the record's own channel), and @[ref].[field] nav placeholders that read through a Reference column, as a formula does (e.g. @[purchase_order].[supplier_id] on an order line narrows to the order's supplier, also in the New dialog of the order's Lines set). A condition whose placeholder is unset is dropped rather than matching nothing."
|