isc-transforms-mcp 1.0.20 → 1.0.21

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.
@@ -2,7 +2,7 @@
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
3
  "$id": "sailpoint.isc.transforms.concat.schema.json",
4
4
  "title": "SailPoint ISC Transform Schema - concat",
5
- "description": "Strict schema derived from the SailPoint official Concatenation operation documentation. Concatenates an array of values (static strings or nested transform objects) into a single string output.",
5
+ "description": "Strict schema for the SailPoint ISC concat (Concatenation) transform. Joins an ordered array of values (static strings or nested transform outputs) into a single combined string. Key behaviors: (1) Items are joined in order with NO automatic separator — spaces, hyphens, or other delimiters must be added as explicit string entries in the array. (2) Each array entry can be a static string literal or a nested transform object whose output string is used. (3) A single-entry values array is valid JSON but semantically pointless — use the nested transform directly in that case.",
6
6
  "type": "object",
7
7
  "additionalProperties": false,
8
8
  "required": [
@@ -12,16 +12,18 @@
12
12
  ],
13
13
  "properties": {
14
14
  "type": {
15
- "const": "concat"
15
+ "const": "concat",
16
+ "description": "Transform operation type. Must be exactly 'concat'."
16
17
  },
17
18
  "name": {
18
19
  "type": "string",
19
- "minLength": 1
20
+ "minLength": 1,
21
+ "description": "Display name for this transform, shown in UI dropdowns and identity profile mappings."
20
22
  },
21
23
  "requiresPeriodicRefresh": {
22
24
  "type": "boolean",
23
25
  "default": false,
24
- "description": "Whether the transform logic should be reevaluated nightly as part of identity refresh. Default false."
26
+ "description": "If true, re-evaluates this transform during the nightly identity refresh cycle. Default is false."
25
27
  },
26
28
  "attributes": {
27
29
  "type": "object",
@@ -29,15 +31,17 @@
29
31
  "required": [
30
32
  "values"
31
33
  ],
34
+ "description": "Required container for concat operation attributes. The only valid attribute is 'values'.",
32
35
  "properties": {
33
36
  "values": {
34
37
  "type": "array",
35
- "minItems": 2,
36
- "description": "Array of items to join. Each item must be a static string or a nested transform object.",
38
+ "minItems": 1,
39
+ "description": "Required. Ordered array of items to join into a single output string. Items are concatenated in sequence with no automatic separator. Rules: (1) Each entry must be a static string literal or a nested transform object {type, attributes} whose output is used. (2) To include spaces, hyphens, or any other separator, add them as explicit string entries between the value entries. (3) A single-entry array is valid but pointless — the nested transform can be used directly. Examples: [firstName, ' ', lastName] → 'Jane Doe'; [jobTitle, ' - ', jobCode] → 'Engineer - ENG001'.",
37
40
  "items": {
38
- "oneOf": [
41
+ "anyOf": [
39
42
  {
40
- "type": "string"
43
+ "type": "string",
44
+ "description": "Static string literal included as-is in the output (e.g., ' ', ' - ', ' (Contractor)')."
41
45
  },
42
46
  {
43
47
  "$ref": "#/$defs/NestedTransform"
@@ -51,7 +55,7 @@
51
55
  "$defs": {
52
56
  "NestedTransform": {
53
57
  "type": "object",
54
- "description": "Nested transform object used inside other transforms (docs examples often omit 'name' on nested transforms). Operation-specific validation should be applied by the referenced operation schema.",
58
+ "description": "A nested transform object used as one element in the values array. Its output string is inserted at the corresponding position in the concatenated result. The 'name' field is optional in nested transforms per SailPoint docs.",
55
59
  "additionalProperties": false,
56
60
  "required": [
57
61
  "type",
@@ -59,18 +63,22 @@
59
63
  ],
60
64
  "properties": {
61
65
  "id": {
62
- "type": "string"
66
+ "type": "string",
67
+ "description": "Optional ID when referencing an existing saved transform."
63
68
  },
64
69
  "name": {
65
70
  "type": "string",
66
- "minLength": 1
71
+ "minLength": 1,
72
+ "description": "Optional display name for the nested transform."
67
73
  },
68
74
  "type": {
69
75
  "type": "string",
70
- "minLength": 1
76
+ "minLength": 1,
77
+ "description": "The operation type of the nested transform (e.g., 'accountAttribute', 'identityAttribute', 'static', 'lower', 'trim')."
71
78
  },
72
79
  "requiresPeriodicRefresh": {
73
- "type": "boolean"
80
+ "type": "boolean",
81
+ "description": "Whether this nested transform re-evaluates during nightly refresh."
74
82
  },
75
83
  "attributes": {
76
84
  "type": "object",
@@ -82,8 +90,54 @@
82
90
  },
83
91
  "examples": [
84
92
  {
93
+ "name": "Full Name from HR Source",
94
+ "type": "concat",
95
+ "attributes": {
96
+ "values": [
97
+ {
98
+ "type": "accountAttribute",
99
+ "attributes": {
100
+ "sourceName": "HR Source",
101
+ "attributeName": "FirstName"
102
+ }
103
+ },
104
+ " ",
105
+ {
106
+ "type": "accountAttribute",
107
+ "attributes": {
108
+ "sourceName": "HR Source",
109
+ "attributeName": "LastName"
110
+ }
111
+ }
112
+ ]
113
+ }
114
+ },
115
+ {
116
+ "name": "Job Title with Code",
117
+ "type": "concat",
118
+ "attributes": {
119
+ "values": [
120
+ {
121
+ "type": "accountAttribute",
122
+ "attributes": {
123
+ "sourceName": "HR Source",
124
+ "attributeName": "JobTitle"
125
+ }
126
+ },
127
+ " - ",
128
+ {
129
+ "type": "accountAttribute",
130
+ "attributes": {
131
+ "sourceName": "HR Source",
132
+ "attributeName": "JobCode"
133
+ }
134
+ }
135
+ ]
136
+ }
137
+ },
138
+ {
139
+ "name": "Contractor Display Name",
85
140
  "type": "concat",
86
- "name": "Test Concat Transform",
87
141
  "attributes": {
88
142
  "values": [
89
143
  {
@@ -104,6 +158,38 @@
104
158
  " (Contractor)"
105
159
  ]
106
160
  }
161
+ },
162
+ {
163
+ "name": "Lowercase Email from Identity Attributes",
164
+ "type": "concat",
165
+ "attributes": {
166
+ "values": [
167
+ {
168
+ "type": "lower",
169
+ "attributes": {
170
+ "input": {
171
+ "type": "identityAttribute",
172
+ "attributes": {
173
+ "name": "firstname"
174
+ }
175
+ }
176
+ }
177
+ },
178
+ ".",
179
+ {
180
+ "type": "lower",
181
+ "attributes": {
182
+ "input": {
183
+ "type": "identityAttribute",
184
+ "attributes": {
185
+ "name": "lastname"
186
+ }
187
+ }
188
+ }
189
+ },
190
+ "@example.com"
191
+ ]
192
+ }
107
193
  }
108
194
  ]
109
195
  }
@@ -18,7 +18,7 @@ const ALLOWED_ATTRS = {
18
18
  ]),
19
19
  base64Decode: new Set(["input"]),
20
20
  base64Encode: new Set(["input"]),
21
- concat: new Set(["values", "input"]),
21
+ concat: new Set(["values"]),
22
22
  conditional: "open", // dynamic variable keys allowed per docs
23
23
  dateCompare: new Set(["firstDate", "secondDate", "operator", "positiveCondition", "negativeCondition"]),
24
24
  dateFormat: new Set(["input", "inputFormat", "outputFormat"]),
@@ -1412,6 +1412,63 @@ function lintRfc5646(attrs) {
1412
1412
  return msgs;
1413
1413
  }
1414
1414
  // ---------------------------------------------------------------------------
1415
+ // lintConcat — concat (Concatenation)
1416
+ // Docs: https://developer.sailpoint.com/docs/extensibility/transforms/operations/concatenation
1417
+ // Joins an ordered array of strings / nested-transform outputs into one string.
1418
+ // No automatic separators — spaces, hyphens, etc. must be explicit array entries.
1419
+ // ---------------------------------------------------------------------------
1420
+ function lintConcat(attrs) {
1421
+ const msgs = [];
1422
+ const values = attrs?.values;
1423
+ // --- 1. values is the only attribute and is required ---
1424
+ if (values === undefined || values === null) {
1425
+ push(msgs, "error", "values is required for concat. Provide an ordered array of strings and/or nested transform objects " +
1426
+ "whose outputs will be joined into a single string.", "attributes.values");
1427
+ return msgs;
1428
+ }
1429
+ if (!Array.isArray(values)) {
1430
+ push(msgs, "error", "values must be an array of strings and/or nested transform objects.", "attributes.values");
1431
+ return msgs;
1432
+ }
1433
+ if (values.length === 0) {
1434
+ push(msgs, "error", "values array must not be empty. Provide at least one string or nested transform.", "attributes.values");
1435
+ return msgs;
1436
+ }
1437
+ // --- 2. Warn if only a single entry — concat is pointless with one value ---
1438
+ if (values.length === 1) {
1439
+ push(msgs, "warn", "values array has only one entry. concat is designed to join multiple values — " +
1440
+ "consider using the nested transform directly instead of wrapping it in a concat.", "attributes.values");
1441
+ }
1442
+ // --- 3. Validate each item: must be string or a nested transform object ---
1443
+ values.forEach((item, idx) => {
1444
+ if (item === null || item === undefined) {
1445
+ push(msgs, "warn", `values[${idx}] is null/undefined — this will produce the string "null"/"undefined" in the output. ` +
1446
+ "Remove it or replace with a static string or a nested transform.", `attributes.values[${idx}]`);
1447
+ }
1448
+ else if (typeof item === "string") {
1449
+ // Valid — static strings (including spaces, separators, literals) are expected
1450
+ }
1451
+ else if (isPlainObject(item)) {
1452
+ if (typeof item.type !== "string" || item.type.trim() === "") {
1453
+ push(msgs, "error", `values[${idx}] is an object but is missing a 'type' field — it does not look like a valid nested transform. ` +
1454
+ "Add a 'type' (e.g., 'accountAttribute', 'identityAttribute', 'static').", `attributes.values[${idx}]`);
1455
+ }
1456
+ }
1457
+ else {
1458
+ push(msgs, "error", `values[${idx}] must be a string or a nested transform object {type, attributes}. ` +
1459
+ `Got: ${typeof item}.`, `attributes.values[${idx}]`);
1460
+ }
1461
+ });
1462
+ // --- 4. Separator hint: inform if no explicit spacing string found between transform objects ---
1463
+ const allTransforms = values.every((v) => isPlainObject(v));
1464
+ if (allTransforms && values.length > 1) {
1465
+ push(msgs, "info", "concat does not insert any separator between values automatically. " +
1466
+ "If the output needs spaces, hyphens, or other delimiters, add them as explicit string entries " +
1467
+ "in the values array (e.g., [firstName, \" \", lastName]).", "attributes.values");
1468
+ }
1469
+ return msgs;
1470
+ }
1471
+ // ---------------------------------------------------------------------------
1415
1472
  // Main lintTransform export
1416
1473
  // ---------------------------------------------------------------------------
1417
1474
  export function lintTransform(input) {
@@ -1502,6 +1559,8 @@ export function lintTransform(input) {
1502
1559
  messages.push(...lintRandom(requestedType, attrs));
1503
1560
  if (requestedType === "rfc5646")
1504
1561
  messages.push(...lintRfc5646(attrs));
1562
+ if (requestedType === "concat")
1563
+ messages.push(...lintConcat(attrs));
1505
1564
  // --- Recursive nested transform lint ---
1506
1565
  // Recursively lint every nested transform found inside attributes.
1507
1566
  // We start from normalized.attributes (not the root) to avoid double-linting root.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "isc-transforms-mcp",
3
- "version": "1.0.20",
3
+ "version": "1.0.21",
4
4
  "type": "module",
5
5
  "description": "MCP server for SailPoint Identity Security Cloud (ISC) Transform authoring — scaffold, strict lint, catalog, and safe upsert to live tenants.",
6
6
  "author": {