@tenonhq/dovetail-servicenow 0.0.17 → 0.0.19
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/README.md +25 -0
- package/dist/cli.js +96 -0
- package/dist/client.js +11 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +12 -1
- package/dist/mcp/registry.d.ts +1 -1
- package/dist/mcp/registry.js +37 -1
- package/dist/mcp/schemas.d.ts +92 -0
- package/dist/mcp/schemas.js +23 -1
- package/dist/table/buildColumnXml.d.ts +29 -0
- package/dist/table/buildColumnXml.js +86 -0
- package/dist/table/buildTableSave.d.ts +75 -0
- package/dist/table/buildTableSave.js +162 -0
- package/dist/table/createTable.d.ts +97 -0
- package/dist/table/createTable.js +220 -0
- package/dist/table/formSession.d.ts +62 -0
- package/dist/table/formSession.js +191 -0
- package/dist/table/index.d.ts +13 -0
- package/dist/table/index.js +31 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -221,6 +221,31 @@ live). The
|
|
|
221
221
|
POST silently no-ops (stays draft). Sequence reverse-engineered from Workflow
|
|
222
222
|
Studio HARs and validated live (2026-06-02, tenonworkstudio). Exit `0` published
|
|
223
223
|
or dry-run; `2` created-but-not-published.
|
|
224
|
+
### Create a table (with columns)
|
|
225
|
+
|
|
226
|
+
```bash
|
|
227
|
+
npx dove-sn create-table \
|
|
228
|
+
--name x_cadso_core_error --label Error --scope x_cadso_core \
|
|
229
|
+
--columns "Key:string:255, Severity:choice:50, First Seen On:datetime, Occurence Count:integer:5" \
|
|
230
|
+
--number-prefix ERR --user-role x_cadso_core.user --update-set <sys_id> --dry-run --json
|
|
231
|
+
npx dove-sn create-table --from-json ./table.json # full spec from a file
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
`create-table` mints a **brand-new** table (`sys_db_object`) **with its columns**.
|
|
235
|
+
A table create is a privileged platform op — a REST / Dovetail `createRecord`
|
|
236
|
+
insert into `sys_db_object` **orphans** the table (a metadata row with no physical
|
|
237
|
+
table, no ACLs, no scope wiring). So this replays the Studio form save: a single
|
|
238
|
+
`POST /sys_db_object.do` (form-login session → `g_ck`) whose body embeds every
|
|
239
|
+
column as a `sys_dictionary` list-edit XML blob (one `<record operation="add">`
|
|
240
|
+
per column) plus the Application-Navigator module — yielding the real 36-record
|
|
241
|
+
graph + the physical table + seeded ACLs. Friendly column types are mapped to
|
|
242
|
+
ServiceNow internal types (`string` → `string_full_utf8`, Studio's real String
|
|
243
|
+
type). `--dry-run` returns the plan + the column XML + the projected graph with no
|
|
244
|
+
session and no writes; the pure transforms are unit-tested. **The live write path
|
|
245
|
+
is pending a validated-live spike** — prefer `--dry-run`, and always verify the
|
|
246
|
+
`sys_update_xml` rows landed in the intended update set. Ground truth (the HAR
|
|
247
|
+
dissection) lives in the CTO repo's create-table docs.
|
|
248
|
+
|
|
224
249
|
`test-flow` defaults to **validate** — a safe pre-flight (published? inputs match
|
|
225
250
|
declared variables?) that never runs the flow; `--execute --confirm` runs it via
|
|
226
251
|
the server-side FlowAPI runner (deploy `resources/runFlow.md` first).
|
package/dist/cli.js
CHANGED
|
@@ -57,6 +57,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
57
57
|
})();
|
|
58
58
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
59
59
|
const fs = __importStar(require("fs"));
|
|
60
|
+
const path = __importStar(require("path"));
|
|
60
61
|
const loadEnv_1 = require("./loadEnv");
|
|
61
62
|
const client_1 = require("./client");
|
|
62
63
|
const choices_1 = require("./choices");
|
|
@@ -77,6 +78,7 @@ const createFlow_1 = require("./flowDesigner/createFlow");
|
|
|
77
78
|
const editFlow_1 = require("./flowDesigner/editFlow");
|
|
78
79
|
const editActionType_1 = require("./flowDesigner/editActionType");
|
|
79
80
|
const testFlow_1 = require("./flowDesigner/testFlow");
|
|
81
|
+
const table_1 = require("./table");
|
|
80
82
|
const flowDesigner_formatter_2 = require("./flowDesigner-formatter");
|
|
81
83
|
function parseArgs(argv) {
|
|
82
84
|
var command = argv[0] || "";
|
|
@@ -699,6 +701,11 @@ function printHelp() {
|
|
|
699
701
|
" (--name <n> --template <sys_id> --scope <sys_id>\n" +
|
|
700
702
|
" [--trigger-table <t>] [--trigger-condition <q>] [--log-message <m>]\n" +
|
|
701
703
|
" [--internal-name <n>] [--description <d>] [--dry-run] [--json])\n" +
|
|
704
|
+
" create-table Create a NEW table (sys_db_object) WITH columns, via the Studio form save\n" +
|
|
705
|
+
" (--name <x_scope_t> --label <l> --scope <s>\n" +
|
|
706
|
+
" --columns \"Label:type:max, ...\" OR --from-json <spec.json>\n" +
|
|
707
|
+
" [--extends <t>] [--number-prefix <p>] [--user-role <r>]\n" +
|
|
708
|
+
" [--no-acls] [--no-menu] [--update-set <sys_id>] [--dry-run] [--json])\n" +
|
|
702
709
|
" test-flow Validate (default) or run a flow/subflow\n" +
|
|
703
710
|
" (--sys-id <sys_id> [--execute --confirm] [--inputs <json>] [--json])\n" +
|
|
704
711
|
" edit-flow Patch a flow/subflow (rename, description, step inputs)\n" +
|
|
@@ -708,6 +715,92 @@ function printHelp() {
|
|
|
708
715
|
" --env <path> Load credentials from a specific .env file (also --env-file,\n" +
|
|
709
716
|
" or the DOVETAIL_ENV_FILE env var). Default: .env in the cwd.\n");
|
|
710
717
|
}
|
|
718
|
+
/** Parse inline `--columns "Label:type:max, Other:choice, ..."` into ColumnSpec[]. */
|
|
719
|
+
function parseColumnsInline(input) {
|
|
720
|
+
var out = [];
|
|
721
|
+
if (!input)
|
|
722
|
+
return out;
|
|
723
|
+
var parts = input.split(",");
|
|
724
|
+
for (var i = 0; i < parts.length; i += 1) {
|
|
725
|
+
var piece = parts[i].trim();
|
|
726
|
+
if (!piece)
|
|
727
|
+
continue;
|
|
728
|
+
var seg = piece.split(":");
|
|
729
|
+
var label = (seg[0] || "").trim();
|
|
730
|
+
var type = (seg[1] || "string").trim();
|
|
731
|
+
var max = (seg[2] || "").trim();
|
|
732
|
+
if (!label)
|
|
733
|
+
continue;
|
|
734
|
+
var col = { label: label, type: type };
|
|
735
|
+
if (max)
|
|
736
|
+
col.max_length = max;
|
|
737
|
+
out.push(col);
|
|
738
|
+
}
|
|
739
|
+
return out;
|
|
740
|
+
}
|
|
741
|
+
/**
|
|
742
|
+
* dove-sn create-table:
|
|
743
|
+
* --name x_cadso_core_error --label Error --scope x_cadso_core
|
|
744
|
+
* --columns "Key:string:255, Severity:choice:50, Occurence Count:integer:5"
|
|
745
|
+
* [--extends sys_metadata] [--number-prefix ERR] [--user-role x_cadso_core.user]
|
|
746
|
+
* [--no-acls] [--no-menu] [--update-set <sys_id>] [--save-action <sys_id>]
|
|
747
|
+
* [--from-json <spec.json>] [--dry-run] [--json]
|
|
748
|
+
*/
|
|
749
|
+
async function runCreateTable(flags) {
|
|
750
|
+
var spec = {};
|
|
751
|
+
if (flags["from-json"]) {
|
|
752
|
+
spec = JSON.parse(fs.readFileSync(path.resolve(flags["from-json"]), "utf8"));
|
|
753
|
+
}
|
|
754
|
+
var name = flags.name || spec.name;
|
|
755
|
+
var label = flags.label || spec.label;
|
|
756
|
+
var scope = flags.scope || spec.scope;
|
|
757
|
+
var columns = flags.columns ? parseColumnsInline(flags.columns) : (spec.columns || []);
|
|
758
|
+
if (!name || !label || !scope || columns.length === 0) {
|
|
759
|
+
process.stderr.write("create-table: --name, --label, --scope and --columns (or --from-json) are required\n");
|
|
760
|
+
return 1;
|
|
761
|
+
}
|
|
762
|
+
var params = {
|
|
763
|
+
client: (0, client_1.createClient)({}),
|
|
764
|
+
name: name,
|
|
765
|
+
label: label,
|
|
766
|
+
scope: scope,
|
|
767
|
+
columns: columns
|
|
768
|
+
};
|
|
769
|
+
var ext = flags.extends || spec.extendsTable;
|
|
770
|
+
if (ext)
|
|
771
|
+
params.extendsTable = ext;
|
|
772
|
+
var prefix = flags["number-prefix"] || spec.numberPrefix;
|
|
773
|
+
if (prefix)
|
|
774
|
+
params.numberPrefix = prefix;
|
|
775
|
+
var role = flags["user-role"] || spec.userRole;
|
|
776
|
+
if (role)
|
|
777
|
+
params.userRole = role;
|
|
778
|
+
if (flags["no-acls"] === "true" || spec.createAccessControls === false)
|
|
779
|
+
params.createAccessControls = false;
|
|
780
|
+
if (flags["no-menu"] === "true" || spec.showInMenu === false)
|
|
781
|
+
params.showInMenu = false;
|
|
782
|
+
var us = flags["update-set"] || spec.updateSetSysId;
|
|
783
|
+
if (us)
|
|
784
|
+
params.updateSetSysId = us;
|
|
785
|
+
var sa = flags["save-action"] || spec.saveActionSysId;
|
|
786
|
+
if (sa)
|
|
787
|
+
params.saveActionSysId = sa;
|
|
788
|
+
if (flags["dry-run"] === "true" || spec.dryRun === true)
|
|
789
|
+
params.dryRun = true;
|
|
790
|
+
var result = await (0, table_1.createTable)(params);
|
|
791
|
+
if (flags.json === "true") {
|
|
792
|
+
process.stdout.write(JSON.stringify(result, null, 2) + "\n");
|
|
793
|
+
}
|
|
794
|
+
else {
|
|
795
|
+
process.stdout.write("[" + result.status + "] " + result.name + " (" + result.label + ") scope=" + result.scopeSysId
|
|
796
|
+
+ " — " + result.columns + " columns, projected graph " + result.graph.total + " records"
|
|
797
|
+
+ (result.tableSysId ? " — sys_id " + result.tableSysId : "")
|
|
798
|
+
+ "\n" + result.note + "\n");
|
|
799
|
+
}
|
|
800
|
+
if (result.status === "failed")
|
|
801
|
+
return 2;
|
|
802
|
+
return 0;
|
|
803
|
+
}
|
|
711
804
|
async function main() {
|
|
712
805
|
var parsed = parseArgs(process.argv.slice(2));
|
|
713
806
|
// Load credentials before any command runs. `--env`/`--env-file` (or the
|
|
@@ -736,6 +829,9 @@ async function main() {
|
|
|
736
829
|
if (parsed.command === "create-flow") {
|
|
737
830
|
return await runCreateFlow(parsed.flags);
|
|
738
831
|
}
|
|
832
|
+
if (parsed.command === "create-table") {
|
|
833
|
+
return await runCreateTable(parsed.flags);
|
|
834
|
+
}
|
|
739
835
|
if (parsed.command === "test-flow") {
|
|
740
836
|
return await runTestFlow(parsed.flags);
|
|
741
837
|
}
|
package/dist/client.js
CHANGED
|
@@ -278,6 +278,17 @@ function createClient(config = {}) {
|
|
|
278
278
|
},
|
|
279
279
|
claude: {
|
|
280
280
|
createRecord: async function (params) {
|
|
281
|
+
// Guard: a bare record insert into sys_db_object creates only an
|
|
282
|
+
// orphaned metadata row — no physical table and no ACLs. Table creation
|
|
283
|
+
// is a privileged platform operation that must run through the proper
|
|
284
|
+
// lifecycle, never a generic createRecord call.
|
|
285
|
+
if (params && params.table === "sys_db_object") {
|
|
286
|
+
throw new Error("Refusing to insert sys_db_object via createRecord: a bare insert " +
|
|
287
|
+
"orphans the table (no physical table or ACLs are created). " +
|
|
288
|
+
"Create tables via the dove-sn create-table capability (in build), " +
|
|
289
|
+
"or in Studio then run `dove refresh -s <scope>` to materialise " +
|
|
290
|
+
"the new records locally.");
|
|
291
|
+
}
|
|
281
292
|
var data = await dovetailRequest("POST", "createRecord", params, null, "claude.createRecord(" + params.table + ")");
|
|
282
293
|
return data.result || data;
|
|
283
294
|
},
|
package/dist/index.d.ts
CHANGED
|
@@ -17,3 +17,5 @@ export { sincPlugin } from "./plugin";
|
|
|
17
17
|
export { listTemplates, verifyArtifact, cloneSubflow, cloneActionType, triggerPublication, publishActionType, editActionType, readFlow, readActionType, publishFlow, copyFlow, createFlow, buildPublishModel, editFlow, testFlow, DEFAULT_RUN_FLOW_PATH, generateSysId, topoSort, executeWritePlan, WriteOrderError } from "./flowDesigner";
|
|
18
18
|
export type { TemplateRef, ListTemplatesParams, FlowKind, VerifyExpect, VerifyFound, VerifyFailure, VerifyReport, VerifyArtifactParams, CloneSubflowParams, CloneSubflowResult, CloneActionTypeParams, CloneActionTypeResult, TriggerPublicationParams, TriggerPublicationResult, PublishActionTypeParams, PublishActionTypeResult, EditActionTypeParams, EditActionTypeResult, EditActionTypeOps, ReadFlowParams, ReadFlowResult, FlowStep, FlowVariable, ReadActionTypeParams, ReadActionTypeResult, ActionIo, PublishFlowParams, PublishFlowResult, CopyFlowParams, CopyFlowResult, CreateFlowParams, CreateFlowResult, EditFlowParams, EditFlowResult, EditFlowOps, StepInputPatch, TestFlowParams, TestFlowResult, WriteOp, WriteOpResult } from "./flowDesigner";
|
|
19
19
|
export type { ServiceNowClientConfig, ChoiceValue, ChoiceType, AddChoicesParams, AddChoicesResult, ChoiceActionResult, DictionaryRecord, UpdateSetRecord, LayoutAction, LayoutRecordResult, LayoutResult, CreateViewParams, CreateViewResult, FormSectionSpec, SetFormLayoutParams, SetListLayoutParams, SetRelatedListsParams } from "./types";
|
|
20
|
+
export { createTable, projectTableGraph, buildColumnXml, normalizeColumns, resolveType, applyTableSaveOverlay, defaultAccessFlags, TYPE_MAP, DEFAULT_SUPER_CLASS, DEFAULT_SAVE_ACTION } from "./table";
|
|
21
|
+
export type { CreateTableParams, CreateTableResult, TableGraph, NormalizedColumn, ColumnSpec, AccessFlags, OverlaySpec } from "./table";
|
package/dist/index.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* REST API so every change lands in the target update set and scope.
|
|
7
7
|
*/
|
|
8
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
-
exports.WriteOrderError = exports.executeWritePlan = exports.topoSort = exports.generateSysId = exports.DEFAULT_RUN_FLOW_PATH = exports.testFlow = exports.editFlow = exports.buildPublishModel = exports.createFlow = exports.copyFlow = exports.publishFlow = exports.readActionType = exports.readFlow = exports.editActionType = exports.publishActionType = exports.triggerPublication = exports.cloneActionType = exports.cloneSubflow = exports.verifyArtifact = exports.listTemplates = exports.sincPlugin = exports.formatCreateViewResult = exports.formatLayoutResult = exports.setRelatedLists = exports.setFormLayout = exports.setListLayout = exports.createView = exports.formatAddChoicesResult = exports.addChoicesToField = exports.createClient = void 0;
|
|
9
|
+
exports.DEFAULT_SAVE_ACTION = exports.DEFAULT_SUPER_CLASS = exports.TYPE_MAP = exports.defaultAccessFlags = exports.applyTableSaveOverlay = exports.resolveType = exports.normalizeColumns = exports.buildColumnXml = exports.projectTableGraph = exports.createTable = exports.WriteOrderError = exports.executeWritePlan = exports.topoSort = exports.generateSysId = exports.DEFAULT_RUN_FLOW_PATH = exports.testFlow = exports.editFlow = exports.buildPublishModel = exports.createFlow = exports.copyFlow = exports.publishFlow = exports.readActionType = exports.readFlow = exports.editActionType = exports.publishActionType = exports.triggerPublication = exports.cloneActionType = exports.cloneSubflow = exports.verifyArtifact = exports.listTemplates = exports.sincPlugin = exports.formatCreateViewResult = exports.formatLayoutResult = exports.setRelatedLists = exports.setFormLayout = exports.setListLayout = exports.createView = exports.formatAddChoicesResult = exports.addChoicesToField = exports.createClient = void 0;
|
|
10
10
|
var client_1 = require("./client");
|
|
11
11
|
Object.defineProperty(exports, "createClient", { enumerable: true, get: function () { return client_1.createClient; } });
|
|
12
12
|
var choices_1 = require("./choices");
|
|
@@ -47,3 +47,14 @@ Object.defineProperty(exports, "generateSysId", { enumerable: true, get: functio
|
|
|
47
47
|
Object.defineProperty(exports, "topoSort", { enumerable: true, get: function () { return flowDesigner_1.topoSort; } });
|
|
48
48
|
Object.defineProperty(exports, "executeWritePlan", { enumerable: true, get: function () { return flowDesigner_1.executeWritePlan; } });
|
|
49
49
|
Object.defineProperty(exports, "WriteOrderError", { enumerable: true, get: function () { return flowDesigner_1.WriteOrderError; } });
|
|
50
|
+
var table_1 = require("./table");
|
|
51
|
+
Object.defineProperty(exports, "createTable", { enumerable: true, get: function () { return table_1.createTable; } });
|
|
52
|
+
Object.defineProperty(exports, "projectTableGraph", { enumerable: true, get: function () { return table_1.projectTableGraph; } });
|
|
53
|
+
Object.defineProperty(exports, "buildColumnXml", { enumerable: true, get: function () { return table_1.buildColumnXml; } });
|
|
54
|
+
Object.defineProperty(exports, "normalizeColumns", { enumerable: true, get: function () { return table_1.normalizeColumns; } });
|
|
55
|
+
Object.defineProperty(exports, "resolveType", { enumerable: true, get: function () { return table_1.resolveType; } });
|
|
56
|
+
Object.defineProperty(exports, "applyTableSaveOverlay", { enumerable: true, get: function () { return table_1.applyTableSaveOverlay; } });
|
|
57
|
+
Object.defineProperty(exports, "defaultAccessFlags", { enumerable: true, get: function () { return table_1.defaultAccessFlags; } });
|
|
58
|
+
Object.defineProperty(exports, "TYPE_MAP", { enumerable: true, get: function () { return table_1.TYPE_MAP; } });
|
|
59
|
+
Object.defineProperty(exports, "DEFAULT_SUPER_CLASS", { enumerable: true, get: function () { return table_1.DEFAULT_SUPER_CLASS; } });
|
|
60
|
+
Object.defineProperty(exports, "DEFAULT_SAVE_ACTION", { enumerable: true, get: function () { return table_1.DEFAULT_SAVE_ACTION; } });
|
package/dist/mcp/registry.d.ts
CHANGED
|
@@ -10,7 +10,7 @@ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
|
10
10
|
import { z } from "zod";
|
|
11
11
|
import type { ToolAnnotations } from "@tenonhq/dovetail-mcp-kit";
|
|
12
12
|
import type { ServiceNowClient } from "../client";
|
|
13
|
-
export declare var TOOL_NAMES: readonly ["create_view", "set_list_layout", "set_form_layout", "set_related_lists", "add_choices_to_field", "flow_view", "action_view", "flow_publish", "flow_copy", "flow_create", "flow_test", "flow_edit"];
|
|
13
|
+
export declare var TOOL_NAMES: readonly ["create_view", "set_list_layout", "set_form_layout", "set_related_lists", "add_choices_to_field", "flow_view", "action_view", "flow_publish", "flow_copy", "flow_create", "flow_test", "flow_edit", "create_table"];
|
|
14
14
|
export type ToolName = typeof TOOL_NAMES[number];
|
|
15
15
|
export interface RegistryDeps {
|
|
16
16
|
/** Optional client injection for tests; defaults to createClient({}). */
|
package/dist/mcp/registry.js
CHANGED
|
@@ -25,6 +25,7 @@ const copyFlow_1 = require("../flowDesigner/copyFlow");
|
|
|
25
25
|
const createFlow_1 = require("../flowDesigner/createFlow");
|
|
26
26
|
const editFlow_1 = require("../flowDesigner/editFlow");
|
|
27
27
|
const testFlow_1 = require("../flowDesigner/testFlow");
|
|
28
|
+
const table_1 = require("../table");
|
|
28
29
|
const schemas_1 = require("./schemas");
|
|
29
30
|
exports.TOOL_NAMES = [
|
|
30
31
|
"create_view",
|
|
@@ -38,7 +39,8 @@ exports.TOOL_NAMES = [
|
|
|
38
39
|
"flow_copy",
|
|
39
40
|
"flow_create",
|
|
40
41
|
"flow_test",
|
|
41
|
-
"flow_edit"
|
|
42
|
+
"flow_edit",
|
|
43
|
+
"create_table"
|
|
42
44
|
];
|
|
43
45
|
// Annotation presets (READ_ONLY / WRITE_ADDITIVE_IDEMPOTENT / WRITE_CREATE /
|
|
44
46
|
// WRITE_OVERWRITE / WRITE_EXECUTE) come from @tenonhq/dovetail-mcp-kit.
|
|
@@ -227,6 +229,40 @@ function buildDescriptors(deps = {}) {
|
|
|
227
229
|
updateSetSysId: p.updateSetSysId
|
|
228
230
|
});
|
|
229
231
|
}
|
|
232
|
+
},
|
|
233
|
+
{
|
|
234
|
+
name: "create_table",
|
|
235
|
+
annotations: dovetail_mcp_kit_1.WRITE_CREATE,
|
|
236
|
+
description: "Create a NEW ServiceNow table (sys_db_object) WITH its columns, headless and faithfully. "
|
|
237
|
+
+ "A table create is a privileged platform op — a REST/createRecord insert ORPHANS the table "
|
|
238
|
+
+ "(metadata row, no physical table, no ACLs). This replays the Studio form save "
|
|
239
|
+
+ "(POST /sys_db_object.do) so the real 36-record graph + the physical table + seeded ACLs "
|
|
240
|
+
+ "are created. name (x_scope_*), label, scope, and columns[] are required; extendsTable "
|
|
241
|
+
+ "defaults to sys_metadata; friendly column types are mapped to internal types "
|
|
242
|
+
+ "(string -> string_full_utf8). dryRun:true returns the plan + the column XML + the projected "
|
|
243
|
+
+ "graph with no session and no writes. NOTE: the live write path is pending a validated-live "
|
|
244
|
+
+ "spike — prefer dryRun until confirmed, and always verify the sys_update_xml landed in the "
|
|
245
|
+
+ "intended update set.",
|
|
246
|
+
shape: schemas_1.createTableSchema.shape,
|
|
247
|
+
handler: async function (args) {
|
|
248
|
+
var p = schemas_1.createTableSchema.parse(args);
|
|
249
|
+
return (0, table_1.createTable)({
|
|
250
|
+
client: client(),
|
|
251
|
+
name: p.name,
|
|
252
|
+
label: p.label,
|
|
253
|
+
scope: p.scope,
|
|
254
|
+
columns: p.columns,
|
|
255
|
+
extendsTable: p.extendsTable,
|
|
256
|
+
numberPrefix: p.numberPrefix,
|
|
257
|
+
userRole: p.userRole,
|
|
258
|
+
createAccessControls: p.createAccessControls,
|
|
259
|
+
access: p.access,
|
|
260
|
+
showInMenu: p.showInMenu,
|
|
261
|
+
updateSetSysId: p.updateSetSysId,
|
|
262
|
+
saveActionSysId: p.saveActionSysId,
|
|
263
|
+
dryRun: p.dryRun
|
|
264
|
+
});
|
|
265
|
+
}
|
|
230
266
|
}
|
|
231
267
|
];
|
|
232
268
|
}
|
package/dist/mcp/schemas.d.ts
CHANGED
|
@@ -382,3 +382,95 @@ export declare var editFlowSchema: z.ZodObject<{
|
|
|
382
382
|
updateSetSysId?: string | undefined;
|
|
383
383
|
apply?: boolean | undefined;
|
|
384
384
|
}>;
|
|
385
|
+
export declare var columnSpecSchema: z.ZodObject<{
|
|
386
|
+
label: z.ZodString;
|
|
387
|
+
type: z.ZodString;
|
|
388
|
+
name: z.ZodOptional<z.ZodString>;
|
|
389
|
+
max_length: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodNumber]>>;
|
|
390
|
+
reference: z.ZodOptional<z.ZodString>;
|
|
391
|
+
}, "strip", z.ZodTypeAny, {
|
|
392
|
+
type: string;
|
|
393
|
+
label: string;
|
|
394
|
+
name?: string | undefined;
|
|
395
|
+
max_length?: string | number | undefined;
|
|
396
|
+
reference?: string | undefined;
|
|
397
|
+
}, {
|
|
398
|
+
type: string;
|
|
399
|
+
label: string;
|
|
400
|
+
name?: string | undefined;
|
|
401
|
+
max_length?: string | number | undefined;
|
|
402
|
+
reference?: string | undefined;
|
|
403
|
+
}>;
|
|
404
|
+
export declare var createTableSchema: z.ZodObject<{
|
|
405
|
+
name: z.ZodString;
|
|
406
|
+
label: z.ZodString;
|
|
407
|
+
scope: z.ZodString;
|
|
408
|
+
columns: z.ZodArray<z.ZodObject<{
|
|
409
|
+
label: z.ZodString;
|
|
410
|
+
type: z.ZodString;
|
|
411
|
+
name: z.ZodOptional<z.ZodString>;
|
|
412
|
+
max_length: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodNumber]>>;
|
|
413
|
+
reference: z.ZodOptional<z.ZodString>;
|
|
414
|
+
}, "strip", z.ZodTypeAny, {
|
|
415
|
+
type: string;
|
|
416
|
+
label: string;
|
|
417
|
+
name?: string | undefined;
|
|
418
|
+
max_length?: string | number | undefined;
|
|
419
|
+
reference?: string | undefined;
|
|
420
|
+
}, {
|
|
421
|
+
type: string;
|
|
422
|
+
label: string;
|
|
423
|
+
name?: string | undefined;
|
|
424
|
+
max_length?: string | number | undefined;
|
|
425
|
+
reference?: string | undefined;
|
|
426
|
+
}>, "many">;
|
|
427
|
+
extendsTable: z.ZodOptional<z.ZodString>;
|
|
428
|
+
numberPrefix: z.ZodOptional<z.ZodString>;
|
|
429
|
+
userRole: z.ZodOptional<z.ZodString>;
|
|
430
|
+
createAccessControls: z.ZodOptional<z.ZodBoolean>;
|
|
431
|
+
access: z.ZodOptional<z.ZodString>;
|
|
432
|
+
showInMenu: z.ZodOptional<z.ZodBoolean>;
|
|
433
|
+
updateSetSysId: z.ZodOptional<z.ZodString>;
|
|
434
|
+
saveActionSysId: z.ZodOptional<z.ZodString>;
|
|
435
|
+
dryRun: z.ZodOptional<z.ZodBoolean>;
|
|
436
|
+
}, "strip", z.ZodTypeAny, {
|
|
437
|
+
name: string;
|
|
438
|
+
columns: {
|
|
439
|
+
type: string;
|
|
440
|
+
label: string;
|
|
441
|
+
name?: string | undefined;
|
|
442
|
+
max_length?: string | number | undefined;
|
|
443
|
+
reference?: string | undefined;
|
|
444
|
+
}[];
|
|
445
|
+
label: string;
|
|
446
|
+
scope: string;
|
|
447
|
+
dryRun?: boolean | undefined;
|
|
448
|
+
updateSetSysId?: string | undefined;
|
|
449
|
+
extendsTable?: string | undefined;
|
|
450
|
+
numberPrefix?: string | undefined;
|
|
451
|
+
userRole?: string | undefined;
|
|
452
|
+
createAccessControls?: boolean | undefined;
|
|
453
|
+
access?: string | undefined;
|
|
454
|
+
showInMenu?: boolean | undefined;
|
|
455
|
+
saveActionSysId?: string | undefined;
|
|
456
|
+
}, {
|
|
457
|
+
name: string;
|
|
458
|
+
columns: {
|
|
459
|
+
type: string;
|
|
460
|
+
label: string;
|
|
461
|
+
name?: string | undefined;
|
|
462
|
+
max_length?: string | number | undefined;
|
|
463
|
+
reference?: string | undefined;
|
|
464
|
+
}[];
|
|
465
|
+
label: string;
|
|
466
|
+
scope: string;
|
|
467
|
+
dryRun?: boolean | undefined;
|
|
468
|
+
updateSetSysId?: string | undefined;
|
|
469
|
+
extendsTable?: string | undefined;
|
|
470
|
+
numberPrefix?: string | undefined;
|
|
471
|
+
userRole?: string | undefined;
|
|
472
|
+
createAccessControls?: boolean | undefined;
|
|
473
|
+
access?: string | undefined;
|
|
474
|
+
showInMenu?: boolean | undefined;
|
|
475
|
+
saveActionSysId?: string | undefined;
|
|
476
|
+
}>;
|
package/dist/mcp/schemas.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* own file so registry.ts stays focused on wiring.
|
|
5
5
|
*/
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
-
exports.editFlowSchema = exports.stepInputPatchSchema = exports.testFlowSchema = exports.createFlowSchema = exports.copyFlowSchema = exports.publishFlowSchema = exports.viewActionSchema = exports.viewFlowSchema = exports.addChoicesToFieldSchema = exports.choiceValueSchema = exports.setRelatedListsSchema = exports.setFormLayoutSchema = exports.formSectionSchema = exports.setListLayoutSchema = exports.createViewSchema = void 0;
|
|
7
|
+
exports.createTableSchema = exports.columnSpecSchema = exports.editFlowSchema = exports.stepInputPatchSchema = exports.testFlowSchema = exports.createFlowSchema = exports.copyFlowSchema = exports.publishFlowSchema = exports.viewActionSchema = exports.viewFlowSchema = exports.addChoicesToFieldSchema = exports.choiceValueSchema = exports.setRelatedListsSchema = exports.setFormLayoutSchema = exports.formSectionSchema = exports.setListLayoutSchema = exports.createViewSchema = void 0;
|
|
8
8
|
const zod_1 = require("zod");
|
|
9
9
|
exports.createViewSchema = zod_1.z.object({
|
|
10
10
|
name: zod_1.z.string().min(1),
|
|
@@ -113,3 +113,25 @@ exports.editFlowSchema = zod_1.z.object({
|
|
|
113
113
|
scopeSysId: zod_1.z.string().optional(),
|
|
114
114
|
updateSetSysId: zod_1.z.string().optional()
|
|
115
115
|
});
|
|
116
|
+
exports.columnSpecSchema = zod_1.z.object({
|
|
117
|
+
label: zod_1.z.string().min(1),
|
|
118
|
+
type: zod_1.z.string().min(1),
|
|
119
|
+
name: zod_1.z.string().optional(),
|
|
120
|
+
max_length: zod_1.z.union([zod_1.z.string(), zod_1.z.number()]).optional(),
|
|
121
|
+
reference: zod_1.z.string().optional()
|
|
122
|
+
});
|
|
123
|
+
exports.createTableSchema = zod_1.z.object({
|
|
124
|
+
name: zod_1.z.string().min(1),
|
|
125
|
+
label: zod_1.z.string().min(1),
|
|
126
|
+
scope: zod_1.z.string().min(1),
|
|
127
|
+
columns: zod_1.z.array(exports.columnSpecSchema).min(1),
|
|
128
|
+
extendsTable: zod_1.z.string().optional(),
|
|
129
|
+
numberPrefix: zod_1.z.string().optional(),
|
|
130
|
+
userRole: zod_1.z.string().optional(),
|
|
131
|
+
createAccessControls: zod_1.z.boolean().optional(),
|
|
132
|
+
access: zod_1.z.string().optional(),
|
|
133
|
+
showInMenu: zod_1.z.boolean().optional(),
|
|
134
|
+
updateSetSysId: zod_1.z.string().optional(),
|
|
135
|
+
saveActionSysId: zod_1.z.string().optional(),
|
|
136
|
+
dryRun: zod_1.z.boolean().optional()
|
|
137
|
+
});
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build the embedded list-edit column XML that rides inside the sys_db_object.do
|
|
3
|
+
* table-save POST. This is the ONE place ServiceNow's Studio form encodes new
|
|
4
|
+
* columns: a `<record_update table="sys_dictionary">` envelope with one
|
|
5
|
+
* `<record operation="add">` per column. Ground truth: a real Studio table-create
|
|
6
|
+
* HAR (`x_cadso_core_error`, tenonworkstudio, 2026-06-13) — see
|
|
7
|
+
* CTO docs/servicenow-create-table-har-analysis.md.
|
|
8
|
+
*
|
|
9
|
+
* Pure + deterministic (callers pass the per-column sys_ids) so it is unit-tested
|
|
10
|
+
* without a network. ES6 only, no optional chaining.
|
|
11
|
+
*/
|
|
12
|
+
/** A normalized column ready to render. `type` is the ServiceNow internal_type. */
|
|
13
|
+
export interface NormalizedColumn {
|
|
14
|
+
/** Display label (Studio derives `element` from this server-side). */
|
|
15
|
+
label: string;
|
|
16
|
+
/** sys_dictionary.internal_type, e.g. "string_full_utf8", "choice", "integer". */
|
|
17
|
+
type: string;
|
|
18
|
+
/** max_length as a string; "" when the type carries none (e.g. glide_date_time). */
|
|
19
|
+
maxLength: string;
|
|
20
|
+
/** Reference target table for type "reference"; "" otherwise (rendered as NULL). */
|
|
21
|
+
reference: string;
|
|
22
|
+
}
|
|
23
|
+
/** Minimal XML-attr/text escaping — the values cross an XML boundary. */
|
|
24
|
+
export declare function xmlEscape(s: string): string;
|
|
25
|
+
/**
|
|
26
|
+
* Build the full `<record_update>` column blob. `sysIds[i]` is the fresh
|
|
27
|
+
* client-generated sys_id for column i (length must match `columns`).
|
|
28
|
+
*/
|
|
29
|
+
export declare function buildColumnXml(columns: Array<NormalizedColumn>, sysIds: Array<string>): string;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Build the embedded list-edit column XML that rides inside the sys_db_object.do
|
|
4
|
+
* table-save POST. This is the ONE place ServiceNow's Studio form encodes new
|
|
5
|
+
* columns: a `<record_update table="sys_dictionary">` envelope with one
|
|
6
|
+
* `<record operation="add">` per column. Ground truth: a real Studio table-create
|
|
7
|
+
* HAR (`x_cadso_core_error`, tenonworkstudio, 2026-06-13) — see
|
|
8
|
+
* CTO docs/servicenow-create-table-har-analysis.md.
|
|
9
|
+
*
|
|
10
|
+
* Pure + deterministic (callers pass the per-column sys_ids) so it is unit-tested
|
|
11
|
+
* without a network. ES6 only, no optional chaining.
|
|
12
|
+
*/
|
|
13
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
+
exports.xmlEscape = xmlEscape;
|
|
15
|
+
exports.buildColumnXml = buildColumnXml;
|
|
16
|
+
/**
|
|
17
|
+
* The 14 per-record fields Studio submits, in the HAR's exact order. Most are
|
|
18
|
+
* static (server fills them); only column_label, internal_type, reference and
|
|
19
|
+
* max_length carry capability data.
|
|
20
|
+
*/
|
|
21
|
+
function renderRecord(col, sysId) {
|
|
22
|
+
var hasMax = col.maxLength !== "";
|
|
23
|
+
var ref = col.reference ? col.reference : "NULL";
|
|
24
|
+
var parts = [];
|
|
25
|
+
parts.push('<record sys_id="' + sysId + '" operation="add">');
|
|
26
|
+
// column_label — the only field the user really types; element is derived from it.
|
|
27
|
+
parts.push(field("column_label", { modified: true, dsp_set: true, value: "", display: col.label }));
|
|
28
|
+
parts.push(field("element", { value: "" }));
|
|
29
|
+
parts.push(field("internal_type", { modified: true, value_set: true, value: col.type }));
|
|
30
|
+
parts.push(field("reference", { value: ref }));
|
|
31
|
+
if (hasMax) {
|
|
32
|
+
parts.push(field("max_length", { modified: true, dsp_set: true, value: "", display: col.maxLength }));
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
parts.push(field("max_length", { value: "" }));
|
|
36
|
+
}
|
|
37
|
+
parts.push(field("default_value", { value: "" }));
|
|
38
|
+
parts.push(field("display", { value: "false" }));
|
|
39
|
+
parts.push(field("sys_updated_on", { value: "" }));
|
|
40
|
+
parts.push(field("sys_updated_by", { value: "" }));
|
|
41
|
+
parts.push(field("attributes", { value: "" }));
|
|
42
|
+
parts.push(field("read_only", { value: "false" }));
|
|
43
|
+
parts.push(field("sys_created_on", { value: "" }));
|
|
44
|
+
parts.push(field("sys_created_by", { value: "" }));
|
|
45
|
+
parts.push(field("name", { value: "" }));
|
|
46
|
+
parts.push("</record>");
|
|
47
|
+
return parts.join("");
|
|
48
|
+
}
|
|
49
|
+
function field(name, opts) {
|
|
50
|
+
var modified = opts.modified === true ? "true" : "false";
|
|
51
|
+
var valueSet = opts.value_set === true ? "true" : "false";
|
|
52
|
+
var dspSet = opts.dsp_set === true ? "true" : "false";
|
|
53
|
+
var inner = "<value>" + xmlEscape(opts.value) + "</value>";
|
|
54
|
+
if (opts.dsp_set === true) {
|
|
55
|
+
inner += "<display_value>" + xmlEscape(opts.display === undefined ? "" : opts.display) + "</display_value>";
|
|
56
|
+
}
|
|
57
|
+
return '<field name="' + name + '" modified="' + modified
|
|
58
|
+
+ '" value_set="' + valueSet + '" dsp_set="' + dspSet + '">' + inner + "</field>";
|
|
59
|
+
}
|
|
60
|
+
/** Minimal XML-attr/text escaping — the values cross an XML boundary. */
|
|
61
|
+
function xmlEscape(s) {
|
|
62
|
+
return String(s)
|
|
63
|
+
.replace(/&/g, "&")
|
|
64
|
+
.replace(/</g, "<")
|
|
65
|
+
.replace(/>/g, ">")
|
|
66
|
+
.replace(/"/g, """);
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Build the full `<record_update>` column blob. `sysIds[i]` is the fresh
|
|
70
|
+
* client-generated sys_id for column i (length must match `columns`).
|
|
71
|
+
*/
|
|
72
|
+
function buildColumnXml(columns, sysIds) {
|
|
73
|
+
if (!Array.isArray(columns)) {
|
|
74
|
+
throw new Error("buildColumnXml: columns must be an array.");
|
|
75
|
+
}
|
|
76
|
+
if (!Array.isArray(sysIds) || sysIds.length !== columns.length) {
|
|
77
|
+
throw new Error("buildColumnXml: need exactly one sys_id per column (got "
|
|
78
|
+
+ (Array.isArray(sysIds) ? sysIds.length : "none") + " for " + columns.length + " columns).");
|
|
79
|
+
}
|
|
80
|
+
var open = '<record_update table="sys_dictionary" field="null" query="nameONE IN^element!=NULL^ORDERBYelement">';
|
|
81
|
+
var body = "";
|
|
82
|
+
for (var i = 0; i < columns.length; i += 1) {
|
|
83
|
+
body += renderRecord(columns[i], sysIds[i]);
|
|
84
|
+
}
|
|
85
|
+
return open + body + "</record_update>";
|
|
86
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure transforms for the table-save form POST: normalize a friendly column spec
|
|
3
|
+
* into ServiceNow internal types, and overlay the capability's values onto the
|
|
4
|
+
* fields harvested from the live new-record form.
|
|
5
|
+
*
|
|
6
|
+
* The transport (GET the new-record form, harvest fields, POST) lives in
|
|
7
|
+
* createTable.ts; everything here is deterministic and unit-tested. ES6 only.
|
|
8
|
+
*/
|
|
9
|
+
import { NormalizedColumn } from "./buildColumnXml";
|
|
10
|
+
/** Friendly input column (what a caller / MCP tool passes). */
|
|
11
|
+
export interface ColumnSpec {
|
|
12
|
+
name?: string;
|
|
13
|
+
label: string;
|
|
14
|
+
type: string;
|
|
15
|
+
max_length?: string | number;
|
|
16
|
+
reference?: string;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Friendly column type -> ServiceNow internal_type. Studio's default for a
|
|
20
|
+
* "String" is `string_full_utf8`, NOT plain `string` (confirmed in the HAR), so
|
|
21
|
+
* a caller saying "string" gets the type the platform actually writes.
|
|
22
|
+
*/
|
|
23
|
+
export declare var TYPE_MAP: Record<string, string>;
|
|
24
|
+
/** Resolve a friendly-or-internal type string to the internal_type to write. */
|
|
25
|
+
export declare function resolveType(type: string): string;
|
|
26
|
+
/** Validate + normalize the friendly column list. Throws on the broken cases. */
|
|
27
|
+
export declare function normalizeColumns(cols: Array<ColumnSpec>): Array<NormalizedColumn>;
|
|
28
|
+
/** Per-operation access flags; all default true (matches Studio's create defaults). */
|
|
29
|
+
export interface AccessFlags {
|
|
30
|
+
read: boolean;
|
|
31
|
+
create: boolean;
|
|
32
|
+
update: boolean;
|
|
33
|
+
delete: boolean;
|
|
34
|
+
ws: boolean;
|
|
35
|
+
configuration: boolean;
|
|
36
|
+
alter: boolean;
|
|
37
|
+
actions: boolean;
|
|
38
|
+
client_scripts: boolean;
|
|
39
|
+
}
|
|
40
|
+
export declare function defaultAccessFlags(): AccessFlags;
|
|
41
|
+
/** Everything the overlay needs that the new-record form can't supply. */
|
|
42
|
+
export interface OverlaySpec {
|
|
43
|
+
name: string;
|
|
44
|
+
label: string;
|
|
45
|
+
tableSysId: string;
|
|
46
|
+
saveActionSysId: string;
|
|
47
|
+
superClassSysId: string;
|
|
48
|
+
superClassLabel: string;
|
|
49
|
+
scopeSysId: string;
|
|
50
|
+
scopeLabel: string;
|
|
51
|
+
numberPrefix: string;
|
|
52
|
+
userRoleSysId: string;
|
|
53
|
+
userRoleLabel: string;
|
|
54
|
+
createAccessControls: boolean;
|
|
55
|
+
access: string;
|
|
56
|
+
accessFlags: AccessFlags;
|
|
57
|
+
/** Application sys_id for the nav module; "" disables Show-in-menu. */
|
|
58
|
+
selectedApplicationSysId: string;
|
|
59
|
+
menuName: string;
|
|
60
|
+
/** The discovered `ni.java...ListEditFormatterAction[sys_db_object.REL:<relId>]` key. */
|
|
61
|
+
listEditKey: string;
|
|
62
|
+
columnXml: string;
|
|
63
|
+
}
|
|
64
|
+
/** Build the `...ShowInMenuFormatterAction[<part>]` form key. */
|
|
65
|
+
export declare function showInMenuKey(part: string): string;
|
|
66
|
+
/** Build the `...ListEditFormatterAction[sys_db_object.REL:<relId>]` form key. */
|
|
67
|
+
export declare function listEditKey(relId: string): string;
|
|
68
|
+
/**
|
|
69
|
+
* Overlay the capability's values onto the base field map harvested from the live
|
|
70
|
+
* new-record form. Returns a NEW object (base is not mutated). Sets exactly the
|
|
71
|
+
* keys Studio mutates for a table create; everything else (sysparm_ck,
|
|
72
|
+
* sysparm_encoded_record, the dynamic `<sysid>_text` field, all sys_original.*
|
|
73
|
+
* defaults) is preserved from the harvested form.
|
|
74
|
+
*/
|
|
75
|
+
export declare function applyTableSaveOverlay(base: Record<string, string>, o: OverlaySpec): Record<string, string>;
|