@tenonhq/dovetail-servicenow 0.0.14 → 0.0.16

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 CHANGED
@@ -195,6 +195,12 @@ npx dove-sn test-flow --sys-id <sys_id> --execute --confirm --inputs '{...}' #
195
195
  echo '{"rename":{"name":"New Name"},"patchStepInputs":[{"step":"Calculate SMS Send At","input":"send_rate","value":"5"}]}' > ops.json
196
196
  npx dove-sn edit-flow --sys-id <sys_id> --from-json ops.json # dry-run (diff)
197
197
  npx dove-sn edit-flow --sys-id <sys_id> --from-json ops.json --apply --update-set <id> # persist
198
+
199
+ # Edit a Custom Action Type's script and/or output variables, then republish (headless)
200
+ npx dove-sn edit-action --sys-id <action_type_sys_id> --scope <scope_sys_id> \
201
+ --patch-script "grabHashData::grabRecipients" # dry-run (diff)
202
+ npx dove-sn edit-action --sys-id <id> --scope <scope> --set-script ./script.js \
203
+ --merge-outputs ./output-var.json --apply --update-set <id> # persist + publish
198
204
  ```
199
205
 
200
206
  `copy-flow` calls the Designer's own `POST /processflow/flow/{id}/copy` — a
@@ -228,6 +234,17 @@ Step-input persistence via the snapshot POST is verified for action types but is
228
234
  and **warns** if a value didn't actually persist, so a no-op never reports as a
229
235
  silent success.
230
236
 
237
+ `edit-action` edits a **published Custom Action Type** headlessly: it GETs the
238
+ model, fetches the steps from `.../{id}/step_instances` (the model GET returns
239
+ `steps:null`), patches the script step input (`--patch-script "<find>::<replace>"`
240
+ or `--set-script <file>`) and/or merges output-variable definitions
241
+ (`--merge-outputs <json>`, matched by `name`), grafts the steps back, and POSTs
242
+ `/snapshot` to recompile — the same snapshot POST `publishActionType` uses, which
243
+ persists step input values back to `sys_variable_value`. It deliberately avoids
244
+ the Designer's model PUT, whose client-side step transform is unsafe to
245
+ hand-reconstruct. Dry-run (diff) by default; `--apply` republishes, and
246
+ `--update-set <id>` pins the capture into a chosen update set.
247
+
231
248
  `view-flow` reads `GET /api/now/processflow/flow/{id}` — the Designer's own model
232
249
  endpoint — and prints the ordered, nesting-aware action + flow-logic step graph
233
250
  plus the flow variables. This works for the integration user with plain basic
package/dist/cli.js CHANGED
@@ -75,6 +75,7 @@ const publishFlow_1 = require("./flowDesigner/publishFlow");
75
75
  const copyFlow_1 = require("./flowDesigner/copyFlow");
76
76
  const createFlow_1 = require("./flowDesigner/createFlow");
77
77
  const editFlow_1 = require("./flowDesigner/editFlow");
78
+ const editActionType_1 = require("./flowDesigner/editActionType");
78
79
  const testFlow_1 = require("./flowDesigner/testFlow");
79
80
  const flowDesigner_formatter_2 = require("./flowDesigner-formatter");
80
81
  function parseArgs(argv) {
@@ -595,6 +596,72 @@ async function runEditFlow(flags) {
595
596
  }
596
597
  return 0;
597
598
  }
599
+ /**
600
+ * dove-sn edit-action:
601
+ * --sys-id <sys_id> Required. sys_hub_action_type_definition sys_id.
602
+ * --scope <sys_id> Required. sysparm_transaction_scope (app scope sys_id).
603
+ * --patch-script "<find>::<replace>" Optional. Find/replace in the script step value.
604
+ * --set-script <path> Optional. Replace the script step value from a file.
605
+ * --merge-outputs <path> Optional. JSON file: an output-variable object/array to merge by name.
606
+ * --script-input <name> Optional. Input name holding the script (default: auto-detect).
607
+ * --update-set <sys_id> Optional. Capture the republish into this update set.
608
+ * --apply Optional. Republish (POST /snapshot). Omit for dry-run.
609
+ * --json Optional. Emit the structured EditActionTypeResult.
610
+ *
611
+ * Edits a published Custom Action Type's script and/or output variables and
612
+ * republishes through the snapshot POST. Dry-run (read-only) by default; --apply writes.
613
+ */
614
+ async function runEditAction(flags) {
615
+ var sysId = flags["sys-id"] || flags.sysId;
616
+ var scope = flags.scope || flags.scopeSysId;
617
+ if (!sysId || !scope) {
618
+ process.stderr.write("edit-action: --sys-id <sys_id> and --scope <sys_id> are required\n");
619
+ return 1;
620
+ }
621
+ var ops = {};
622
+ if (flags["patch-script"]) {
623
+ var parts = String(flags["patch-script"]).split("::");
624
+ if (parts.length !== 2) {
625
+ process.stderr.write("edit-action: --patch-script must be \"<find>::<replace>\"\n");
626
+ return 1;
627
+ }
628
+ ops.patchScript = { find: parts[0], replace: parts[1] };
629
+ }
630
+ if (flags["set-script"]) {
631
+ ops.setScript = fs.readFileSync(flags["set-script"], "utf8");
632
+ }
633
+ if (flags["merge-outputs"]) {
634
+ var parsedOutputs = JSON.parse(fs.readFileSync(flags["merge-outputs"], "utf8"));
635
+ ops.mergeOutputs = Array.isArray(parsedOutputs) ? parsedOutputs : [parsedOutputs];
636
+ }
637
+ if (flags["script-input"]) {
638
+ ops.scriptInputName = flags["script-input"];
639
+ }
640
+ var result = await (0, editActionType_1.editActionType)({
641
+ client: (0, client_1.createClient)({}),
642
+ sysId: sysId,
643
+ scopeSysId: scope,
644
+ ops: ops,
645
+ apply: flags.apply === "true",
646
+ updateSetSysId: flags["update-set"] || flags.updateSetSysId
647
+ });
648
+ if (flags.json === "true") {
649
+ process.stdout.write(JSON.stringify(result, null, 2) + "\n");
650
+ return 0;
651
+ }
652
+ process.stdout.write("[" + result.status + "] " + result.changes.length + " change(s)"
653
+ + (result.snapshotSysId ? " — snapshot " + result.snapshotSysId : "") + "\n");
654
+ for (var ci = 0; ci < result.changes.length; ci += 1) {
655
+ process.stdout.write(" + " + result.changes[ci] + "\n");
656
+ }
657
+ for (var wi = 0; wi < result.warnings.length; wi += 1) {
658
+ process.stdout.write(" ! " + result.warnings[wi] + "\n");
659
+ }
660
+ if (result.status === "preview" && result.scriptAfter !== undefined && result.scriptAfter !== result.scriptBefore) {
661
+ process.stdout.write("\n--- script after ---\n" + result.scriptAfter + "\n");
662
+ }
663
+ return 0;
664
+ }
598
665
  async function runMcp(flags) {
599
666
  if (flags.smoke === "true") {
600
667
  await (0, server_1.runSmoke)();
@@ -675,6 +742,9 @@ async function main() {
675
742
  if (parsed.command === "edit-flow") {
676
743
  return await runEditFlow(parsed.flags);
677
744
  }
745
+ if (parsed.command === "edit-action") {
746
+ return await runEditAction(parsed.flags);
747
+ }
678
748
  if (parsed.command === "create-view") {
679
749
  await runCreateView(parsed.flags);
680
750
  return 0;
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Edit a published Custom Action Type's script and/or output variables, then
3
+ * republish — entirely headless, basic auth, no Designer UI.
4
+ *
5
+ * Reuses the validated publish contract (see publishActionType):
6
+ * GET /api/now/processflow/action/action_types/{id} -> model (outputs[], steps:null)
7
+ * GET /api/now/processflow/action/action_types/{id}/step_instances -> { steps:[...] } (carries the script)
8
+ * POST /api/now/processflow/action/action_types/{id}/snapshot -> 201; compiles the snapshot AND
9
+ * persists step input values back to
10
+ * sys_variable_value
11
+ *
12
+ * The GET model returns `steps: null` (the Designer assembles them client-side),
13
+ * so we fetch them from `/step_instances`, patch the script input there,
14
+ * optionally merge output-variable definitions into `model.outputs`, graft the
15
+ * steps back onto the model, and POST `/snapshot`. Because the snapshot POST
16
+ * persists step input values, the patched script lands without a separate write.
17
+ *
18
+ * Why route through `/snapshot` rather than the Designer's model PUT: the PUT
19
+ * applies a client-side model transform (it strips ~14 step fields and reshapes
20
+ * inputs) that is unsafe to hand-reconstruct and risks corrupting the action.
21
+ * The snapshot POST is the path publishActionType already proved end to end.
22
+ *
23
+ * Full write-up: docs/servicenow-flow-designer-headless-authoring.md.
24
+ */
25
+ import type { ServiceNowClient } from "../client";
26
+ export interface EditActionTypeOps {
27
+ /** Replace every occurrence of `find` with `replace` inside the script step value. */
28
+ patchScript?: {
29
+ find: string;
30
+ replace: string;
31
+ };
32
+ /** Replace the script step value outright (wins over patchScript). */
33
+ setScript?: string;
34
+ /**
35
+ * Output-variable definition objects to merge into `model.outputs`, matched by
36
+ * `name` (replaced in place, else appended). Supply the modeled output JSON —
37
+ * e.g. an `array.object` with `children` — typically lifted from a Designer
38
+ * capture of the same shape.
39
+ */
40
+ mergeOutputs?: Array<Record<string, any>>;
41
+ /**
42
+ * The input `name` holding the script. Defaults to auto-detect: the first step
43
+ * input whose value reads like an action script.
44
+ */
45
+ scriptInputName?: string;
46
+ }
47
+ export interface EditActionTypeParams {
48
+ client: ServiceNowClient;
49
+ /** sys_id of the sys_hub_action_type_definition to edit. */
50
+ sysId: string;
51
+ /** Application scope sys_id, passed as sysparm_transaction_scope. */
52
+ scopeSysId: string;
53
+ ops: EditActionTypeOps;
54
+ /** When true, republish. When false/omitted, dry-run (no write). */
55
+ apply?: boolean;
56
+ /** Update set to capture the publish into (pins the REST session's active set first). */
57
+ updateSetSysId?: string;
58
+ }
59
+ export interface EditActionTypeResult {
60
+ status: "preview" | "published";
61
+ changes: Array<string>;
62
+ warnings: Array<string>;
63
+ scriptBefore?: string;
64
+ scriptAfter?: string;
65
+ outputsMerged: Array<string>;
66
+ /** HTTP status of the snapshot POST (201 on success); only set when applied. */
67
+ httpStatus?: number;
68
+ snapshotSysId?: string;
69
+ }
70
+ export declare function editActionType(params: EditActionTypeParams): Promise<EditActionTypeResult>;
@@ -0,0 +1,196 @@
1
+ "use strict";
2
+ /**
3
+ * Edit a published Custom Action Type's script and/or output variables, then
4
+ * republish — entirely headless, basic auth, no Designer UI.
5
+ *
6
+ * Reuses the validated publish contract (see publishActionType):
7
+ * GET /api/now/processflow/action/action_types/{id} -> model (outputs[], steps:null)
8
+ * GET /api/now/processflow/action/action_types/{id}/step_instances -> { steps:[...] } (carries the script)
9
+ * POST /api/now/processflow/action/action_types/{id}/snapshot -> 201; compiles the snapshot AND
10
+ * persists step input values back to
11
+ * sys_variable_value
12
+ *
13
+ * The GET model returns `steps: null` (the Designer assembles them client-side),
14
+ * so we fetch them from `/step_instances`, patch the script input there,
15
+ * optionally merge output-variable definitions into `model.outputs`, graft the
16
+ * steps back onto the model, and POST `/snapshot`. Because the snapshot POST
17
+ * persists step input values, the patched script lands without a separate write.
18
+ *
19
+ * Why route through `/snapshot` rather than the Designer's model PUT: the PUT
20
+ * applies a client-side model transform (it strips ~14 step fields and reshapes
21
+ * inputs) that is unsafe to hand-reconstruct and risks corrupting the action.
22
+ * The snapshot POST is the path publishActionType already proved end to end.
23
+ *
24
+ * Full write-up: docs/servicenow-flow-designer-headless-authoring.md.
25
+ */
26
+ Object.defineProperty(exports, "__esModule", { value: true });
27
+ exports.editActionType = editActionType;
28
+ function actionTypePath(sysId, scopeSysId, suffix) {
29
+ return "/api/now/processflow/action/action_types/" + encodeURIComponent(sysId)
30
+ + suffix
31
+ + "?sysparm_transaction_scope=" + encodeURIComponent(scopeSysId);
32
+ }
33
+ /** Normalize the `{ result: ... }` envelope the processflow endpoints sometimes use. */
34
+ function unwrap(data) {
35
+ if (data && typeof data === "object" && data.result && typeof data.result === "object") {
36
+ return data.result;
37
+ }
38
+ return data;
39
+ }
40
+ var SCRIPT_SIGNATURE = /function execute|inputs\.|outputs\.|new\s+[A-Za-z_$]/;
41
+ /** Locate the step input holding the action script (by name, else by signature). */
42
+ function findScriptInput(steps, inputName) {
43
+ for (var s = 0; s < steps.length; s += 1) {
44
+ var inputs = (steps[s] && steps[s].inputs) || [];
45
+ for (var i = 0; i < inputs.length; i += 1) {
46
+ var inp = inputs[i];
47
+ if (!inp) {
48
+ continue;
49
+ }
50
+ if (inputName) {
51
+ if (inp.name === inputName) {
52
+ return { input: inp };
53
+ }
54
+ }
55
+ else if (typeof inp.value === "string" && inp.value.length > 30 && SCRIPT_SIGNATURE.test(inp.value)) {
56
+ return { input: inp };
57
+ }
58
+ }
59
+ }
60
+ return null;
61
+ }
62
+ async function editActionType(params) {
63
+ var client = params.client;
64
+ var sysId = params.sysId;
65
+ var scopeSysId = params.scopeSysId;
66
+ var ops = params.ops || {};
67
+ if (!sysId) {
68
+ throw new Error("editActionType: sysId is required.");
69
+ }
70
+ if (!scopeSysId) {
71
+ throw new Error("editActionType: scopeSysId is required (sysparm_transaction_scope).");
72
+ }
73
+ if (!ops.patchScript && !ops.setScript && !(ops.mergeOutputs && ops.mergeOutputs.length > 0)) {
74
+ throw new Error("editActionType: no ops — supply patchScript, setScript, and/or mergeOutputs.");
75
+ }
76
+ var changes = [];
77
+ var warnings = [];
78
+ var outputsMerged = [];
79
+ // 1. GET the model (outputs[], steps:null).
80
+ var model = unwrap(await client.now.get(actionTypePath(sysId, scopeSysId, "")));
81
+ if (!model || typeof model !== "object") {
82
+ throw new Error("editActionType: unexpected GET model response for action type " + sysId);
83
+ }
84
+ // 2. GET the steps — the model's `steps` come back null.
85
+ var stepsResp = unwrap(await client.now.get(actionTypePath(sysId, scopeSysId, "/step_instances")));
86
+ var steps = stepsResp && Array.isArray(stepsResp.steps) ? stepsResp.steps : [];
87
+ if (steps.length === 0) {
88
+ throw new Error("editActionType: /step_instances returned no steps for action type " + sysId);
89
+ }
90
+ // 3. Patch the script step input.
91
+ var scriptBefore;
92
+ var scriptAfter;
93
+ if (ops.patchScript || ops.setScript) {
94
+ var hit = findScriptInput(steps, ops.scriptInputName);
95
+ if (!hit) {
96
+ warnings.push("script input not found" + (ops.scriptInputName ? " (name=" + ops.scriptInputName + ")" : " (auto-detect)"));
97
+ }
98
+ else {
99
+ scriptBefore = String(hit.input.value);
100
+ if (ops.setScript) {
101
+ scriptAfter = ops.setScript;
102
+ }
103
+ else if (ops.patchScript) {
104
+ if (scriptBefore.indexOf(ops.patchScript.find) === -1) {
105
+ warnings.push("patchScript.find not present in script: " + ops.patchScript.find);
106
+ scriptAfter = scriptBefore;
107
+ }
108
+ else {
109
+ scriptAfter = scriptBefore.split(ops.patchScript.find).join(ops.patchScript.replace);
110
+ }
111
+ }
112
+ if (scriptAfter !== undefined && scriptAfter !== scriptBefore) {
113
+ hit.input.value = scriptAfter;
114
+ changes.push("script input '" + hit.input.name + "' patched");
115
+ }
116
+ else {
117
+ warnings.push("script unchanged");
118
+ }
119
+ }
120
+ }
121
+ // 4. Merge output-variable definitions by name.
122
+ if (ops.mergeOutputs && ops.mergeOutputs.length > 0) {
123
+ if (!Array.isArray(model.outputs)) {
124
+ model.outputs = [];
125
+ }
126
+ for (var od = 0; od < ops.mergeOutputs.length; od += 1) {
127
+ var outDef = ops.mergeOutputs[od];
128
+ var outName = outDef && outDef.name;
129
+ if (!outName) {
130
+ warnings.push("mergeOutputs entry missing name — skipped");
131
+ continue;
132
+ }
133
+ var replaced = false;
134
+ for (var m = 0; m < model.outputs.length; m += 1) {
135
+ if (model.outputs[m] && model.outputs[m].name === outName) {
136
+ model.outputs[m] = outDef;
137
+ replaced = true;
138
+ break;
139
+ }
140
+ }
141
+ if (!replaced) {
142
+ model.outputs.push(outDef);
143
+ }
144
+ outputsMerged.push(outName);
145
+ changes.push("output '" + outName + "' " + (replaced ? "replaced" : "added"));
146
+ }
147
+ }
148
+ // 5. Graft steps onto the model (remap each step's `action` to this sysId, per
149
+ // the publish contract).
150
+ model.steps = steps.map(function (step) {
151
+ var copy = {};
152
+ for (var key in step) {
153
+ if (Object.prototype.hasOwnProperty.call(step, key)) {
154
+ copy[key] = step[key];
155
+ }
156
+ }
157
+ copy.action = sysId;
158
+ return copy;
159
+ });
160
+ if (!params.apply) {
161
+ return {
162
+ status: "preview",
163
+ changes: changes,
164
+ warnings: warnings,
165
+ scriptBefore: scriptBefore,
166
+ scriptAfter: scriptAfter,
167
+ outputsMerged: outputsMerged
168
+ };
169
+ }
170
+ // 6. Apply: optionally pin the update set, then POST /snapshot (compiles the
171
+ // snapshot and persists step input values).
172
+ if (params.updateSetSysId) {
173
+ await client.claude.changeUpdateSet({ sysId: params.updateSetSysId });
174
+ }
175
+ var snapResp = unwrap(await client.now.post(actionTypePath(sysId, scopeSysId, "/snapshot"), model));
176
+ var snapshotSysId;
177
+ if (snapResp && typeof snapResp === "object") {
178
+ var snap = snapResp.latest_snapshot || snapResp.master_snapshot || snapResp.snapshot;
179
+ if (snap && typeof snap === "object") {
180
+ snapshotSysId = typeof snap.sys_id === "string" ? snap.sys_id : undefined;
181
+ }
182
+ else if (typeof snap === "string") {
183
+ snapshotSysId = snap;
184
+ }
185
+ }
186
+ return {
187
+ status: "published",
188
+ changes: changes,
189
+ warnings: warnings,
190
+ scriptBefore: scriptBefore,
191
+ scriptAfter: scriptAfter,
192
+ outputsMerged: outputsMerged,
193
+ httpStatus: 201,
194
+ snapshotSysId: snapshotSysId
195
+ };
196
+ }
@@ -16,6 +16,8 @@ export { triggerPublication } from "./triggerPublication";
16
16
  export type { TriggerPublicationParams, TriggerPublicationResult } from "./triggerPublication";
17
17
  export { publishActionType } from "./publishActionType";
18
18
  export type { PublishActionTypeParams, PublishActionTypeResult } from "./publishActionType";
19
+ export { editActionType } from "./editActionType";
20
+ export type { EditActionTypeParams, EditActionTypeResult, EditActionTypeOps } from "./editActionType";
19
21
  export { readFlow } from "./readFlow";
20
22
  export type { ReadFlowParams, ReadFlowResult, FlowStep, FlowVariable } from "./readFlow";
21
23
  export { readActionType } from "./readActionType";
@@ -6,7 +6,7 @@
6
6
  * functions land in Phase 1.C/D.
7
7
  */
8
8
  Object.defineProperty(exports, "__esModule", { value: true });
9
- exports.WriteOrderError = exports.executeWritePlan = exports.topoSort = exports.SYSTEM_FIELDS_TO_STRIP = exports.assertSysId = exports.applyScope = exports.stripSystemFields = exports.generateSysId = exports.DEFAULT_RUN_FLOW_PATH = exports.testFlow = exports.editFlow = exports.buildPublishModel = exports.createFlow = exports.copyFlow = exports.publishFlow = exports.readActionType = exports.readFlow = exports.publishActionType = exports.triggerPublication = exports.cloneActionType = exports.cloneSubflow = exports.verifyArtifact = exports.listTemplates = void 0;
9
+ exports.WriteOrderError = exports.executeWritePlan = exports.topoSort = exports.SYSTEM_FIELDS_TO_STRIP = exports.assertSysId = exports.applyScope = exports.stripSystemFields = 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 = void 0;
10
10
  var listTemplates_1 = require("./listTemplates");
11
11
  Object.defineProperty(exports, "listTemplates", { enumerable: true, get: function () { return listTemplates_1.listTemplates; } });
12
12
  var verifyArtifact_1 = require("./verifyArtifact");
@@ -19,6 +19,8 @@ var triggerPublication_1 = require("./triggerPublication");
19
19
  Object.defineProperty(exports, "triggerPublication", { enumerable: true, get: function () { return triggerPublication_1.triggerPublication; } });
20
20
  var publishActionType_1 = require("./publishActionType");
21
21
  Object.defineProperty(exports, "publishActionType", { enumerable: true, get: function () { return publishActionType_1.publishActionType; } });
22
+ var editActionType_1 = require("./editActionType");
23
+ Object.defineProperty(exports, "editActionType", { enumerable: true, get: function () { return editActionType_1.editActionType; } });
22
24
  var readFlow_1 = require("./readFlow");
23
25
  Object.defineProperty(exports, "readFlow", { enumerable: true, get: function () { return readFlow_1.readFlow; } });
24
26
  var readActionType_1 = require("./readActionType");
package/dist/index.d.ts CHANGED
@@ -14,6 +14,6 @@ export { setFormLayout } from "./layout/formLayout";
14
14
  export { setRelatedLists } from "./layout/relatedLists";
15
15
  export { formatLayoutResult, formatCreateViewResult } from "./layout/formatter";
16
16
  export { sincPlugin } from "./plugin";
17
- export { listTemplates, verifyArtifact, cloneSubflow, cloneActionType, triggerPublication, publishActionType, readFlow, readActionType, publishFlow, copyFlow, createFlow, buildPublishModel, editFlow, testFlow, DEFAULT_RUN_FLOW_PATH, generateSysId, topoSort, executeWritePlan, WriteOrderError } from "./flowDesigner";
18
- export type { TemplateRef, ListTemplatesParams, FlowKind, VerifyExpect, VerifyFound, VerifyFailure, VerifyReport, VerifyArtifactParams, CloneSubflowParams, CloneSubflowResult, CloneActionTypeParams, CloneActionTypeResult, TriggerPublicationParams, TriggerPublicationResult, PublishActionTypeParams, PublishActionTypeResult, ReadFlowParams, ReadFlowResult, FlowStep, FlowVariable, ReadActionTypeParams, ReadActionTypeResult, ActionIo, PublishFlowParams, PublishFlowResult, CopyFlowParams, CopyFlowResult, CreateFlowParams, CreateFlowResult, EditFlowParams, EditFlowResult, EditFlowOps, StepInputPatch, TestFlowParams, TestFlowResult, WriteOp, WriteOpResult } from "./flowDesigner";
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
+ 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";
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.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.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");
@@ -33,6 +33,7 @@ Object.defineProperty(exports, "cloneSubflow", { enumerable: true, get: function
33
33
  Object.defineProperty(exports, "cloneActionType", { enumerable: true, get: function () { return flowDesigner_1.cloneActionType; } });
34
34
  Object.defineProperty(exports, "triggerPublication", { enumerable: true, get: function () { return flowDesigner_1.triggerPublication; } });
35
35
  Object.defineProperty(exports, "publishActionType", { enumerable: true, get: function () { return flowDesigner_1.publishActionType; } });
36
+ Object.defineProperty(exports, "editActionType", { enumerable: true, get: function () { return flowDesigner_1.editActionType; } });
36
37
  Object.defineProperty(exports, "readFlow", { enumerable: true, get: function () { return flowDesigner_1.readFlow; } });
37
38
  Object.defineProperty(exports, "readActionType", { enumerable: true, get: function () { return flowDesigner_1.readActionType; } });
38
39
  Object.defineProperty(exports, "publishFlow", { enumerable: true, get: function () { return flowDesigner_1.publishFlow; } });
@@ -7,6 +7,7 @@
7
7
  * a client is injected via RegistryDeps (used by tests).
8
8
  */
9
9
  import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
10
+ import type { ToolAnnotations } from "@modelcontextprotocol/sdk/types.js";
10
11
  import { z } from "zod";
11
12
  import type { ServiceNowClient } from "../client";
12
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"];
@@ -19,6 +20,7 @@ export interface ToolDescriptor {
19
20
  name: ToolName;
20
21
  description: string;
21
22
  shape: z.ZodRawShape;
23
+ annotations: ToolAnnotations;
22
24
  handler: (args: any) => Promise<any>;
23
25
  }
24
26
  export declare function buildDescriptors(deps?: RegistryDeps): Array<ToolDescriptor>;
@@ -39,6 +39,20 @@ exports.TOOL_NAMES = [
39
39
  "flow_test",
40
40
  "flow_edit"
41
41
  ];
42
+ // MCP tool annotations (spec 2025-11-25) — untrusted behavioural hints that let a
43
+ // host parallelize reads and gate writes; never a security boundary. Five presets
44
+ // cover every tool's safety profile:
45
+ // READ_ONLY — no writes; safe to auto-approve and run concurrently.
46
+ // WRITE_ADDITIVE_IDEMPOTENT — additive write, safe to repeat (idempotent upsert/create).
47
+ // WRITE_CREATE — additive write, NOT idempotent (each call mints a new record).
48
+ // WRITE_OVERWRITE — destructive but idempotent (prune/overwrite: same end state).
49
+ // WRITE_EXECUTE — destructive AND non-idempotent (runs a flow; repeating repeats the effect).
50
+ // openWorldHint is left at its spec default (true) — every tool reaches a ServiceNow instance.
51
+ var READ_ONLY = { readOnlyHint: true };
52
+ var WRITE_ADDITIVE_IDEMPOTENT = { readOnlyHint: false, destructiveHint: false, idempotentHint: true };
53
+ var WRITE_CREATE = { readOnlyHint: false, destructiveHint: false, idempotentHint: false };
54
+ var WRITE_OVERWRITE = { readOnlyHint: false, destructiveHint: true, idempotentHint: true };
55
+ var WRITE_EXECUTE = { readOnlyHint: false, destructiveHint: true, idempotentHint: false };
42
56
  function buildDescriptors(deps = {}) {
43
57
  function client() {
44
58
  return deps.client || (0, client_1.createClient)({});
@@ -46,6 +60,7 @@ function buildDescriptors(deps = {}) {
46
60
  return [
47
61
  {
48
62
  name: "create_view",
63
+ annotations: WRITE_ADDITIVE_IDEMPOTENT,
49
64
  description: "Create a ServiceNow custom view (sys_ui_view). Idempotent — an existing view of the "
50
65
  + "same name is returned unchanged. Every write is captured in the supplied update set.",
51
66
  shape: schemas_1.createViewSchema.shape,
@@ -55,6 +70,7 @@ function buildDescriptors(deps = {}) {
55
70
  },
56
71
  {
57
72
  name: "set_list_layout",
73
+ annotations: WRITE_OVERWRITE,
58
74
  description: "Declaratively set a ServiceNow list layout — which columns appear in a list, and their "
59
75
  + "order — for a table + view. Idempotent; prune (default true) removes columns not in "
60
76
  + "the spec; dryRun previews without writing. Writes are captured in the update set.",
@@ -65,6 +81,7 @@ function buildDescriptors(deps = {}) {
65
81
  },
66
82
  {
67
83
  name: "set_form_layout",
84
+ annotations: WRITE_OVERWRITE,
68
85
  description: "Declaratively set a ServiceNow form layout — sections and the fields within them — for "
69
86
  + "a table + view. The first section is the primary section (omit its caption). "
70
87
  + "Idempotent; prune (default true) removes sections/fields not in the spec; dryRun "
@@ -76,6 +93,7 @@ function buildDescriptors(deps = {}) {
76
93
  },
77
94
  {
78
95
  name: "set_related_lists",
96
+ annotations: WRITE_OVERWRITE,
79
97
  description: "Declaratively set which related lists appear on a ServiceNow form for a table + view. "
80
98
  + "Related-list ids are \"<table>.<field>\" or \"REL:<sys_relationship>\". Idempotent; "
81
99
  + "prune (default true); dryRun previews. Writes are captured in the update set.",
@@ -86,6 +104,7 @@ function buildDescriptors(deps = {}) {
86
104
  },
87
105
  {
88
106
  name: "add_choices_to_field",
107
+ annotations: WRITE_ADDITIVE_IDEMPOTENT,
89
108
  description: "Upsert sys_choice values for a ServiceNow table.column and (optionally) flip "
90
109
  + "sys_dictionary.choice so the field renders as a dropdown. Idempotent. Writes are "
91
110
  + "captured in the supplied update set.",
@@ -96,6 +115,7 @@ function buildDescriptors(deps = {}) {
96
115
  },
97
116
  {
98
117
  name: "flow_view",
118
+ annotations: READ_ONLY,
99
119
  description: "Read a ServiceNow Flow Designer flow or subflow's compiled step graph, headless. "
100
120
  + "Returns the ordered, nesting-aware list of action + flow-logic steps plus the flow "
101
121
  + "variables, via GET /api/now/processflow/flow/{sysId}. Read-only. Pass raw:true to "
@@ -108,6 +128,7 @@ function buildDescriptors(deps = {}) {
108
128
  },
109
129
  {
110
130
  name: "action_view",
131
+ annotations: READ_ONLY,
111
132
  description: "Read a ServiceNow Custom Action Type's compiled model (identity, inputs, outputs), "
112
133
  + "headless, via GET /api/now/processflow/action/action_types/{sysId}. Read-only. "
113
134
  + "sysId is the sys_hub_action_type_definition sys_id; scopeSysId is the application "
@@ -120,6 +141,7 @@ function buildDescriptors(deps = {}) {
120
141
  },
121
142
  {
122
143
  name: "flow_publish",
144
+ annotations: WRITE_OVERWRITE,
123
145
  description: "Publish (compile the snapshot of) a ServiceNow Flow Designer flow or subflow via "
124
146
  + "POST /api/now/processflow/flow/{sysId}/snapshot. This is a WRITE that recompiles the "
125
147
  + "flow's current design — use after editing in the Designer. sysId is the sys_hub_flow "
@@ -132,6 +154,7 @@ function buildDescriptors(deps = {}) {
132
154
  },
133
155
  {
134
156
  name: "flow_copy",
157
+ annotations: WRITE_CREATE,
135
158
  description: "Copy a ServiceNow flow/subflow via the Designer's Copy endpoint — a complete, faithful "
136
159
  + "clone created as an INACTIVE DRAFT in the target scope. sourceSysId is the sys_hub_flow "
137
160
  + "to copy; newName is the copy's name; scopeSysId defaults to the source's scope. Publish "
@@ -145,6 +168,7 @@ function buildDescriptors(deps = {}) {
145
168
  },
146
169
  {
147
170
  name: "flow_create",
171
+ annotations: WRITE_CREATE,
148
172
  description: "Create a NEW ServiceNow Flow Designer flow (sys_hub_flow, type=flow) from scratch and "
149
173
  + "PUBLISH it, headless. Mints a fresh flow via POST /processflow/flow, grafts the trigger + "
150
174
  + "action graph from an existing published template flow (templateSysId), then compiles the "
@@ -174,6 +198,7 @@ function buildDescriptors(deps = {}) {
174
198
  },
175
199
  {
176
200
  name: "flow_test",
201
+ annotations: WRITE_EXECUTE,
177
202
  description: "Test or run a ServiceNow flow/subflow. mode='validate' (default) is a safe, read-only "
178
203
  + "pre-flight — checks the flow is published and that supplied inputs match its declared "
179
204
  + "variables; it never runs the flow. mode='execute' actually runs it via the server-side "
@@ -194,6 +219,7 @@ function buildDescriptors(deps = {}) {
194
219
  },
195
220
  {
196
221
  name: "flow_edit",
222
+ annotations: WRITE_OVERWRITE,
197
223
  description: "Edit a ServiceNow flow/subflow in place. Supports rename (name/internalName), description, "
198
224
  + "and patchStepInputs (set named input values on steps by uiId or label). apply=false "
199
225
  + "(default) is a dry-run that returns the diff; apply=true persists the edit (a write). "
@@ -221,7 +247,7 @@ function registerAllTools(server, deps = {}) {
221
247
  }
222
248
  }
223
249
  function registerOne(server, desc) {
224
- server.registerTool(desc.name, { description: desc.description, inputSchema: desc.shape }, async function (args) {
250
+ server.registerTool(desc.name, { description: desc.description, inputSchema: desc.shape, annotations: desc.annotations }, async function (args) {
225
251
  try {
226
252
  var result = await desc.handler(args);
227
253
  return { content: [{ type: "text", text: JSON.stringify(result) }] };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tenonhq/dovetail-servicenow",
3
- "version": "0.0.14",
3
+ "version": "0.0.16",
4
4
  "engines": {
5
5
  "node": ">=22"
6
6
  },