isc-transforms-mcp 1.0.13 → 1.0.15

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.identityAttribute.schema.json",
4
4
  "title": "SailPoint ISC Transform Schema - identityAttribute",
5
- "description": "Strict schema derived from SailPoint official Identity Attribute operation documentation. Retrieves the value of a user's identity attribute by its system (camel-cased) name.",
5
+ "description": "Strict schema for the SailPoint ISC identityAttribute transform. Retrieves the value of an identity attribute by its camelCase system name. IMPORTANT LIMITATION: This transform is NOT intended for use within another identity profile attribute's calculation. Due to multi-threaded identity processing, the referenced attribute may not yet exist or may hold stale data at evaluation time. Intended use cases are provisioning policies and entitlement request forms. For identity profile attribute mappings that need source account data, use accountAttribute instead.",
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": "identityAttribute"
15
+ "const": "identityAttribute",
16
+ "description": "Transform operation type. Must be exactly 'identityAttribute'."
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 provisioning policy mappings."
20
22
  },
21
23
  "requiresPeriodicRefresh": {
22
24
  "type": "boolean",
23
25
  "default": false,
24
- "description": "Whether the transform logic should be reevaluated every evening 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,20 @@
29
31
  "required": [
30
32
  "name"
31
33
  ],
34
+ "description": "Required container for identityAttribute operation attributes.",
32
35
  "properties": {
33
36
  "name": {
34
37
  "type": "string",
35
38
  "minLength": 1,
36
- "description": "System (camel-cased) name of the identity attribute to retrieve (e.g., 'email', 'uid')."
39
+ "description": "The camelCase system name of the identity attribute to retrieve. This is the internal attribute name, not the display label. Examples: 'uid' (SailPoint User Name), 'email', 'identificationNumber' (Employee Number), 'manager', 'department', 'displayName'. Find the system name in Admin UI → Identity Profiles → Identity Attributes."
37
40
  },
38
41
  "input": {
39
- "description": "Optional explicit input data. If omitted, the transform uses input from the source+attribute combination configured in the UI. Modeled as a nested transform object.",
40
- "oneOf": [
42
+ "description": "Optional explicit input data for this transform. If omitted, the transform uses the source and attribute combination configured in the identity profile UI. When provided, must be a nested transform object or a static string value.",
43
+ "anyOf": [
44
+ {
45
+ "type": "string",
46
+ "description": "Static string input value."
47
+ },
41
48
  {
42
49
  "$ref": "#/$defs/NestedTransform"
43
50
  }
@@ -49,7 +56,7 @@
49
56
  "$defs": {
50
57
  "NestedTransform": {
51
58
  "type": "object",
52
- "description": "Nested transform object used as explicit input (name optional in docs).",
59
+ "description": "A nested transform object used as explicit input to this transform. Provides the input data that feeds the identityAttribute lookup. The 'name' field is optional in nested transforms per SailPoint docs.",
53
60
  "additionalProperties": false,
54
61
  "required": [
55
62
  "type",
@@ -57,18 +64,22 @@
57
64
  ],
58
65
  "properties": {
59
66
  "id": {
60
- "type": "string"
67
+ "type": "string",
68
+ "description": "Optional ID when referencing an existing saved transform."
61
69
  },
62
70
  "name": {
63
71
  "type": "string",
64
- "minLength": 1
72
+ "minLength": 1,
73
+ "description": "Optional display name for the nested transform."
65
74
  },
66
75
  "type": {
67
76
  "type": "string",
68
- "minLength": 1
77
+ "minLength": 1,
78
+ "description": "The operation type of the nested transform (e.g., 'accountAttribute', 'static')."
69
79
  },
70
80
  "requiresPeriodicRefresh": {
71
- "type": "boolean"
81
+ "type": "boolean",
82
+ "description": "Whether this nested transform re-evaluates during nightly refresh."
72
83
  },
73
84
  "attributes": {
74
85
  "type": "object",
@@ -80,25 +91,39 @@
80
91
  },
81
92
  "examples": [
82
93
  {
83
- "attributes": {
84
- "name": "email"
85
- },
94
+ "name": "Get SailPoint User Name",
86
95
  "type": "identityAttribute",
87
- "name": "Identity Attribute Transform"
88
- },
89
- {
90
96
  "attributes": {
91
97
  "name": "uid"
92
- },
93
- "type": "identityAttribute",
94
- "name": "Identity Attribute Transform"
98
+ }
95
99
  },
96
100
  {
101
+ "name": "Get Employee Number",
102
+ "type": "identityAttribute",
97
103
  "attributes": {
98
104
  "name": "identificationNumber"
99
- },
105
+ }
106
+ },
107
+ {
108
+ "name": "Get Manager",
100
109
  "type": "identityAttribute",
101
- "name": "Identity Attribute Transform"
110
+ "attributes": {
111
+ "name": "manager"
112
+ }
113
+ },
114
+ {
115
+ "name": "Get Email with Explicit Input",
116
+ "type": "identityAttribute",
117
+ "attributes": {
118
+ "name": "email",
119
+ "input": {
120
+ "type": "accountAttribute",
121
+ "attributes": {
122
+ "sourceName": "HR Source",
123
+ "attributeName": "workEmail"
124
+ }
125
+ }
126
+ }
102
127
  }
103
128
  ]
104
129
  }
@@ -2,7 +2,7 @@
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
3
  "$id": "sailpoint.isc.transforms.static.schema.json",
4
4
  "title": "SailPoint ISC Transform Schema - static",
5
- "description": "Strict schema derived from SailPoint official Static operation documentation. Returns a fixed string value. Docs also allow Velocity Template Language (VTL) in the value, and allow dynamic variables in attributes to be referenced within the VTL.",
5
+ "description": "Strict schema for the SailPoint ISC Static transform. Returns a fixed string value or evaluates a Velocity Template Language (VTL) expression. The 'value' attribute holds either a literal string (e.g. 'Contractor') or a VTL template (e.g. \"#if($workerType=='Employee')Full-Time#{else}Contingent#end\"). Additional sibling keys in the attributes object are dynamic VTL variables each can be a static string or a nested transform whose output feeds the VTL expression. IMPORTANT: Attribute ordering matters — all dynamic variable attributes must be evaluated before the value expression runs. Reference variables using $varName or ${varName} (formal reference) or $!varName / $!{varName} (quiet reference, suppresses null output).",
6
6
  "type": "object",
7
7
  "additionalProperties": false,
8
8
  "required": [
@@ -12,34 +12,37 @@
12
12
  ],
13
13
  "properties": {
14
14
  "type": {
15
- "const": "static"
15
+ "const": "static",
16
+ "description": "Transform operation type. Must be exactly 'static'."
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 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. Set to true if the VTL expression uses dynamic variables that may change over time. Default is false."
25
27
  },
26
28
  "attributes": {
27
29
  "type": "object",
28
- "description": "Static value plus optional dynamic variables (per docs).",
30
+ "description": "Required container. Must include 'value' (the fixed string or VTL expression). Any additional keys are dynamic VTL variables each key name maps to a static string or a nested transform. Use $keyName or ${keyName} in value to reference them. Attribute ordering in the identity profile matters: all variable attributes must be placed before the static transform.",
29
31
  "required": [
30
32
  "value"
31
33
  ],
32
34
  "properties": {
33
35
  "value": {
34
36
  "type": "string",
35
- "description": "Static string value or Velocity template."
37
+ "description": "The output of this transform. Either a fixed string literal (e.g. 'Contractor') or a VTL expression referencing dynamic variables defined as sibling keys in attributes (e.g. \"#if($workerType=='Employee')Full-Time#{else}Contingent#end\" or \"$first.$last@example.com\"). Supports: $varName (simple), ${varName} (formal), $!varName (quiet — outputs empty string if null), $!{varName} (quiet formal). VTL conditionals: #if($var=='value')result#{else}alternative#end."
36
38
  }
37
39
  },
38
40
  "additionalProperties": {
39
- "description": "Dynamic variables referenced in Velocity template. Can be string literals or nested transforms.",
41
+ "description": "Dynamic VTL variable. The key becomes the variable name referenced as $keyName in value. The value can be a static string (e.g. 'HR') or a nested transform object (e.g. accountAttribute, identityAttribute) whose output feeds the VTL expression.",
40
42
  "anyOf": [
41
43
  {
42
- "type": "string"
44
+ "type": "string",
45
+ "description": "Static string value for this VTL variable."
43
46
  },
44
47
  {
45
48
  "$ref": "#/$defs/NestedTransform"
@@ -51,50 +54,52 @@
51
54
  "$defs": {
52
55
  "NestedTransform": {
53
56
  "type": "object",
54
- "description": "Nested transform object used as a dynamic variable. Docs examples often omit 'name'.",
57
+ "description": "A nested transform object used as a dynamic VTL variable value. The output of this nested transform is substituted for the variable reference in value. Common types: accountAttribute, identityAttribute, getReferenceIdentityAttribute. The 'name' field is optional in nested transforms per SailPoint docs.",
55
58
  "additionalProperties": false,
56
59
  "required": [
57
- "type"
60
+ "type",
61
+ "attributes"
58
62
  ],
59
63
  "properties": {
60
64
  "id": {
61
- "type": "string"
65
+ "type": "string",
66
+ "description": "Optional ID when referencing an existing saved transform."
62
67
  },
63
68
  "name": {
64
69
  "type": "string",
65
- "minLength": 1
70
+ "minLength": 1,
71
+ "description": "Optional display name for the nested transform."
66
72
  },
67
73
  "type": {
68
74
  "type": "string",
69
- "minLength": 1
75
+ "minLength": 1,
76
+ "description": "The operation type of the nested transform (e.g., 'accountAttribute', 'identityAttribute', 'getReferenceIdentityAttribute')."
70
77
  },
71
78
  "requiresPeriodicRefresh": {
72
- "type": "boolean"
79
+ "type": "boolean",
80
+ "description": "Whether this nested transform re-evaluates during nightly refresh."
73
81
  },
74
82
  "attributes": {
75
- "type": [
76
- "object",
77
- "null"
78
- ],
83
+ "type": "object",
79
84
  "additionalProperties": true,
80
- "description": "Operation-specific attributes for the nested transform (may be omitted for operations without attributes)."
85
+ "description": "Operation-specific attributes for the nested transform."
81
86
  }
82
87
  }
83
88
  }
84
89
  },
85
90
  "examples": [
86
91
  {
92
+ "name": "Fixed String",
87
93
  "type": "static",
88
- "name": "Static Transform",
89
94
  "attributes": {
90
95
  "value": "Contractor"
91
96
  }
92
97
  },
93
98
  {
99
+ "name": "Email From Identity Attributes",
94
100
  "type": "static",
95
- "name": "Static Transform (Template)",
96
101
  "attributes": {
97
- "value": "${first}.${last}@example.com",
102
+ "value": "$first.$last@example.com",
98
103
  "first": {
99
104
  "type": "identityAttribute",
100
105
  "attributes": {
@@ -108,6 +113,36 @@
108
113
  }
109
114
  }
110
115
  }
116
+ },
117
+ {
118
+ "name": "Worker Type Conditional",
119
+ "type": "static",
120
+ "attributes": {
121
+ "value": "#if($workerType=='Employee')Full-Time#{else}Contingent#end",
122
+ "workerType": {
123
+ "type": "accountAttribute",
124
+ "attributes": {
125
+ "sourceName": "HR Source",
126
+ "attributeName": "workerType"
127
+ }
128
+ }
129
+ }
130
+ },
131
+ {
132
+ "name": "Manager Email Quiet Reference",
133
+ "type": "static",
134
+ "attributes": {
135
+ "value": "$!{managerEmail}",
136
+ "managerEmail": {
137
+ "type": "getReferenceIdentityAttribute",
138
+ "attributes": {
139
+ "name": "Cloud Services Deployment Utility",
140
+ "operation": "getReferenceIdentityAttribute",
141
+ "uid": "manager",
142
+ "attributeName": "email"
143
+ }
144
+ }
145
+ }
111
146
  }
112
147
  ]
113
148
  }
@@ -1021,20 +1021,92 @@ function lintSubstring(attrs) {
1021
1021
  return msgs;
1022
1022
  }
1023
1023
  // ---------------------------------------------------------------------------
1024
- // 24. static — value must be a string
1024
+ // 24. static — value (fixed string or VTL), dynamic VTL variable cross-check
1025
+ // Docs: https://developer.sailpoint.com/docs/extensibility/transforms/operations/static
1025
1026
  // ---------------------------------------------------------------------------
1026
1027
  function lintStatic(attrs) {
1027
1028
  const msgs = [];
1028
- // Presence check is handled by checkRequired (spec.requiredAttributes).
1029
- // Here we only validate the type when value is present.
1029
+ // 1. value type check must be a string (fixed literal or VTL expression)
1030
1030
  if (attrs?.value !== undefined && attrs?.value !== null && typeof attrs.value !== "string") {
1031
- push(msgs, "error", "value must be a string.", "attributes.value");
1031
+ push(msgs, "error", "value must be a string — either a fixed literal (e.g. 'Contractor') or a VTL expression " +
1032
+ "(e.g. \"#if($workerType=='Employee')Full-Time#{else}Contingent#end\").", "attributes.value");
1033
+ return msgs; // cannot do VTL analysis without a string value
1034
+ }
1035
+ const value = attrs?.value;
1036
+ // 2. Empty value warning
1037
+ if (typeof value === "string" && value.trim() === "") {
1038
+ push(msgs, "warn", "value is an empty string — this will produce an empty (null-equivalent) attribute. " +
1039
+ "Provide a non-empty fixed string or VTL expression.", "attributes.value");
1040
+ }
1041
+ if (typeof value === "string" && value.length > 0) {
1042
+ // 3. Extract VTL variable references ($varName, ${varName}, $!varName, $!{varName})
1043
+ const vtlRefs = new Set();
1044
+ const refPattern = /\$!?\{?([A-Za-z_][A-Za-z0-9_]*)\}?/g;
1045
+ let m;
1046
+ while ((m = refPattern.exec(value)) !== null) {
1047
+ vtlRefs.add(m[1]);
1048
+ }
1049
+ if (vtlRefs.size > 0) {
1050
+ // 4. Warn for each VTL reference that has no matching dynamic variable in attributes
1051
+ for (const varName of vtlRefs) {
1052
+ if (!Object.prototype.hasOwnProperty.call(attrs, varName)) {
1053
+ push(msgs, "warn", `VTL references $${varName} in value but no dynamic variable '${varName}' is defined in attributes. ` +
1054
+ `Add a '${varName}' key to attributes with a static string or nested transform that supplies the value.`, `attributes.${varName}`);
1055
+ }
1056
+ }
1057
+ // 5. Ordering hint — VTL variables must be positioned before the value attribute in the identity profile
1058
+ push(msgs, "info", "VTL variables detected in value. Attribute ordering matters in identity profiles: " +
1059
+ "all dynamic variable attributes must be mapped and evaluated before the static transform's value expression runs. " +
1060
+ "Review the attribute mapping order in your identity profile.", "attributes.value");
1061
+ }
1062
+ // 6. Warn for dynamic variables defined in attributes but never referenced in value (likely a typo or leftover)
1063
+ const reservedKeys = new Set(["value"]);
1064
+ for (const key of Object.keys(attrs ?? {})) {
1065
+ if (reservedKeys.has(key))
1066
+ continue;
1067
+ if (!vtlRefs.has(key)) {
1068
+ push(msgs, "warn", `Dynamic variable '${key}' is defined in attributes but not referenced as $${key} in value. ` +
1069
+ `If intentional, remove it to keep the transform clean; otherwise check for a typo.`, `attributes.${key}`);
1070
+ }
1071
+ }
1072
+ }
1073
+ return msgs;
1074
+ }
1075
+ // ---------------------------------------------------------------------------
1076
+ // 25. identityAttribute — name format + critical use-case limitation
1077
+ // Docs: https://developer.sailpoint.com/docs/extensibility/transforms/operations/identity-attribute
1078
+ // ---------------------------------------------------------------------------
1079
+ function lintIdentityAttribute(attrs) {
1080
+ const msgs = [];
1081
+ // 1. name: must be a camelCase system name — no spaces, ideally no hyphens
1082
+ const name = attrs?.name;
1083
+ if (typeof name === "string" && name.trim().length > 0) {
1084
+ if (/\s/.test(name)) {
1085
+ push(msgs, "error", `attributes.name '${name}' contains whitespace. Identity attribute system names are camelCase with no spaces ` +
1086
+ "(e.g., 'uid', 'email', 'identificationNumber'). Check the identity profile attribute's system name in the Admin UI.", "attributes.name");
1087
+ }
1088
+ else if (/-/.test(name)) {
1089
+ push(msgs, "warn", `attributes.name '${name}' contains a hyphen. Identity attribute system names are camelCase ` +
1090
+ "(e.g., 'identificationNumber', not 'identification-number'). Verify this is the exact system name shown in the Admin UI.", "attributes.name");
1091
+ }
1092
+ }
1093
+ // 2. Critical use-case limitation — identityAttribute is NOT safe inside identity profile attribute calculations
1094
+ push(msgs, "warn", "identityAttribute is NOT intended for use within another identity profile attribute's calculation. " +
1095
+ "Due to multi-threaded identity processing, the referenced attribute may not yet exist or may hold stale data at evaluation time. " +
1096
+ "Intended use: provisioning policies and entitlement request forms. " +
1097
+ "If you need an account attribute value inside an identity profile mapping, use accountAttribute instead.", "type");
1098
+ // 3. input: must be a nested transform object if provided
1099
+ if (attrs?.input !== undefined) {
1100
+ const inp = attrs.input;
1101
+ if (!(isPlainObject(inp) && typeof inp.type === "string")) {
1102
+ push(msgs, "warn", "input must be a nested transform object {type, attributes} providing explicit input data. " +
1103
+ "If omitted, the transform uses the source+attribute combination configured in the identity profile UI.", "attributes.input");
1104
+ }
1032
1105
  }
1033
- // Any other keys in attributes are valid dynamic VTL variable definitions — no unknown-attribute errors.
1034
1106
  return msgs;
1035
1107
  }
1036
1108
  // ---------------------------------------------------------------------------
1037
- // 25. indexOf / lastIndexOf
1109
+ // 27. indexOf / lastIndexOf
1038
1110
  // ---------------------------------------------------------------------------
1039
1111
  function lintIndexOf(t, attrs) {
1040
1112
  const msgs = [];
@@ -1047,7 +1119,7 @@ function lintIndexOf(t, attrs) {
1047
1119
  return msgs;
1048
1120
  }
1049
1121
  // ---------------------------------------------------------------------------
1050
- // 26. randomAlphaNumeric / randomNumeric — length
1122
+ // 28. randomAlphaNumeric / randomNumeric — length
1051
1123
  // ---------------------------------------------------------------------------
1052
1124
  function lintRandom(t, attrs) {
1053
1125
  const msgs = [];
@@ -1063,7 +1135,7 @@ function lintRandom(t, attrs) {
1063
1135
  return msgs;
1064
1136
  }
1065
1137
  // ---------------------------------------------------------------------------
1066
- // 27. rfc5646 — format type check
1138
+ // 29. rfc5646 — format type check
1067
1139
  // ---------------------------------------------------------------------------
1068
1140
  function lintRfc5646(attrs) {
1069
1141
  const msgs = [];
@@ -1155,6 +1227,8 @@ export function lintTransform(input) {
1155
1227
  messages.push(...lintSubstring(attrs));
1156
1228
  if (requestedType === "static")
1157
1229
  messages.push(...lintStatic(attrs));
1230
+ if (requestedType === "identityAttribute")
1231
+ messages.push(...lintIdentityAttribute(attrs));
1158
1232
  if (requestedType === "indexOf" || requestedType === "lastIndexOf")
1159
1233
  messages.push(...lintIndexOf(requestedType, attrs));
1160
1234
  if (requestedType === "randomAlphaNumeric" || requestedType === "randomNumeric")
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "isc-transforms-mcp",
3
- "version": "1.0.13",
3
+ "version": "1.0.15",
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": {