isc-transforms-mcp 1.0.16 → 1.0.18

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.replace.schema.json",
4
4
  "title": "SailPoint ISC Transform Schema - replace",
5
- "description": "Strict schema derived from SailPoint official Replace operation documentation. Find and replace all instances of a single regex pattern in the incoming value.",
5
+ "description": "Strict schema for the SailPoint ISC replace transform. Finds all occurrences of a single Java regular expression pattern in the input string and replaces every match with the replacement string. Unlike replaceAll, this operates on a single regex+replacement pair. Key behaviors: (1) ALL occurrences are replaced, not just the first. (2) Use an empty replacement string to delete all matched text. (3) Capture groups in the regex can be referenced as $1, $2, etc. in the replacement. (4) Use bracket notation for literal special characters (e.g., '[.]' for a literal dot). (5) input is optional — if omitted the transform uses the source+attribute configured in the identity profile UI.",
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": "replace"
15
+ "const": "replace",
16
+ "description": "Transform operation type. Must be exactly 'replace'."
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. Default is false."
25
27
  },
26
28
  "attributes": {
27
29
  "type": "object",
@@ -30,19 +32,24 @@
30
32
  "regex",
31
33
  "replacement"
32
34
  ],
35
+ "description": "Required container for replace operation attributes.",
33
36
  "properties": {
34
37
  "regex": {
35
38
  "type": "string",
36
39
  "minLength": 1,
37
- "description": "Regular expression pattern to replace."
40
+ "description": "Required. The Java regular expression pattern to search for. ALL occurrences of the pattern in the input are replaced — not just the first match. Pattern syntax notes: (1) Standard Java regex — character classes, quantifiers, anchors, and capture groups are all supported. (2) Use bracket notation to match literal special regex characters: '[.]' for a literal dot, '[+]' for a literal plus, '[-]' for a literal hyphen. (3) Capture groups with parentheses (e.g., '([A-Za-z]+)') can be back-referenced in the replacement field as $1, $2, etc. (4) The pattern is case-sensitive by default — use character classes (e.g., '[Aa]') or inline flag '(?i)' to match case-insensitively."
38
41
  },
39
42
  "replacement": {
40
43
  "type": "string",
41
- "description": "Replacement string."
44
+ "description": "Required. The string that replaces every occurrence of the matched pattern. Replacement notes: (1) Use an empty string \"\" to delete all matched text. (2) Use $0 to insert the entire matched text, $1 for the first capture group, $2 for the second, etc. (3) To include a literal dollar sign, use \\$."
42
45
  },
43
46
  "input": {
44
- "description": "Optional explicit input passed into the transform (nested transform). If omitted, uses UI-configured input.",
45
- "oneOf": [
47
+ "description": "Optional explicit input providing the string to apply the regex replacement to. 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.",
48
+ "anyOf": [
49
+ {
50
+ "type": "string",
51
+ "description": "Static string value to apply the regex replacement to."
52
+ },
46
53
  {
47
54
  "$ref": "#/$defs/NestedTransform"
48
55
  }
@@ -54,7 +61,7 @@
54
61
  "$defs": {
55
62
  "NestedTransform": {
56
63
  "type": "object",
57
- "description": "Nested transform object used as explicit input (name optional in docs).",
64
+ "description": "A nested transform object that provides the string input for the replace operation. Its output string is the value the regex replacement is applied to. The 'name' field is optional in nested transforms per SailPoint docs.",
58
65
  "additionalProperties": false,
59
66
  "required": [
60
67
  "type",
@@ -62,18 +69,22 @@
62
69
  ],
63
70
  "properties": {
64
71
  "id": {
65
- "type": "string"
72
+ "type": "string",
73
+ "description": "Optional ID when referencing an existing saved transform."
66
74
  },
67
75
  "name": {
68
76
  "type": "string",
69
- "minLength": 1
77
+ "minLength": 1,
78
+ "description": "Optional display name for the nested transform."
70
79
  },
71
80
  "type": {
72
81
  "type": "string",
73
- "minLength": 1
82
+ "minLength": 1,
83
+ "description": "The operation type of the nested transform (e.g., 'accountAttribute', 'identityAttribute', 'trim')."
74
84
  },
75
85
  "requiresPeriodicRefresh": {
76
- "type": "boolean"
86
+ "type": "boolean",
87
+ "description": "Whether this nested transform re-evaluates during nightly refresh."
77
88
  },
78
89
  "attributes": {
79
90
  "type": "object",
@@ -85,12 +96,49 @@
85
96
  },
86
97
  "examples": [
87
98
  {
99
+ "name": "Replace Whitespace with Underscore",
88
100
  "type": "replace",
89
- "name": "Replace Transform",
90
101
  "attributes": {
91
102
  "regex": "\\s+",
92
103
  "replacement": "_"
93
104
  }
105
+ },
106
+ {
107
+ "name": "Remove Non-Alphanumeric Characters",
108
+ "type": "replace",
109
+ "attributes": {
110
+ "regex": "[^A-Za-z0-9]",
111
+ "replacement": ""
112
+ }
113
+ },
114
+ {
115
+ "name": "Extract Domain from Email (Capture Group)",
116
+ "type": "replace",
117
+ "attributes": {
118
+ "regex": "^[^@]+@(.+)$",
119
+ "replacement": "$1",
120
+ "input": {
121
+ "type": "identityAttribute",
122
+ "attributes": {
123
+ "name": "email"
124
+ }
125
+ }
126
+ }
127
+ },
128
+ {
129
+ "name": "Replace Literal Dots with Hyphens",
130
+ "type": "replace",
131
+ "attributes": {
132
+ "regex": "[.]",
133
+ "replacement": "-",
134
+ "input": {
135
+ "type": "accountAttribute",
136
+ "attributes": {
137
+ "sourceName": "HR Source",
138
+ "attributeName": "displayName"
139
+ }
140
+ }
141
+ }
94
142
  }
95
143
  ]
96
144
  }
@@ -2,7 +2,7 @@
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
3
  "$id": "sailpoint.isc.transforms.replaceAll.schema.json",
4
4
  "title": "SailPoint ISC Transform Schema - replaceAll",
5
- "description": "Strict schema derived from SailPoint official Replace All operation documentation. Performs find-and-replace for every (pattern -> replacement) entry in a table. Patterns are interpreted as regular expressions.",
5
+ "description": "Strict schema for the SailPoint ISC replaceAll transform. Performs multiple find-and-replace operations in a single pass against the input string. Each key in the table is a Java regular expression pattern; its value is the replacement string. All patterns are applied simultaneously against the original input — not sequentially. Pattern matching is case-sensitive by default. Use an empty string value to delete all text matching a pattern. Unlike the single replace transform, replaceAll can handle multiple distinct patterns in one operation.",
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": "replaceAll"
15
+ "const": "replaceAll",
16
+ "description": "Transform operation type. Must be exactly 'replaceAll'."
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. Default is false."
25
27
  },
26
28
  "attributes": {
27
29
  "type": "object",
@@ -29,18 +31,24 @@
29
31
  "required": [
30
32
  "table"
31
33
  ],
34
+ "description": "Required container for replaceAll operation attributes.",
32
35
  "properties": {
33
36
  "table": {
34
37
  "type": "object",
35
- "description": "Map of regex patterns to replacement strings.",
38
+ "description": "Required map of Java regex patterns (keys) to replacement strings (values). Each key is compiled as a Java regular expression and matched against the input string — all occurrences of each matching pattern are replaced with the corresponding value. Rules: (1) All values must be static strings; use an empty string \"\" to delete all matched text. (2) Pattern matching is case-sensitive — use character classes (e.g., '[Aa]') or separate entries for case variants. (3) All patterns apply simultaneously against the original input string in a single pass. (4) For literal special regex characters, use bracket notation (e.g., '[.]' for a literal dot, '[+]' for a literal plus).",
36
39
  "minProperties": 1,
37
40
  "additionalProperties": {
38
- "type": "string"
41
+ "type": "string",
42
+ "description": "Replacement string. All occurrences of the matching regex pattern are replaced with this value. Use an empty string to delete matched text."
39
43
  }
40
44
  },
41
45
  "input": {
42
- "description": "Optional explicit input passed into the transform (nested transform). If omitted, uses UI-configured input.",
43
- "oneOf": [
46
+ "description": "Optional explicit input that provides the string to apply pattern replacements to. 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.",
47
+ "anyOf": [
48
+ {
49
+ "type": "string",
50
+ "description": "Static string value to apply replacements to."
51
+ },
44
52
  {
45
53
  "$ref": "#/$defs/NestedTransform"
46
54
  }
@@ -52,7 +60,7 @@
52
60
  "$defs": {
53
61
  "NestedTransform": {
54
62
  "type": "object",
55
- "description": "Nested transform object used as explicit input (name optional in docs).",
63
+ "description": "A nested transform object that provides the string input for the replaceAll operation. Its output string is the value that pattern replacements are applied to. The 'name' field is optional in nested transforms per SailPoint docs.",
56
64
  "additionalProperties": false,
57
65
  "required": [
58
66
  "type",
@@ -60,18 +68,22 @@
60
68
  ],
61
69
  "properties": {
62
70
  "id": {
63
- "type": "string"
71
+ "type": "string",
72
+ "description": "Optional ID when referencing an existing saved transform."
64
73
  },
65
74
  "name": {
66
75
  "type": "string",
67
- "minLength": 1
76
+ "minLength": 1,
77
+ "description": "Optional display name for the nested transform."
68
78
  },
69
79
  "type": {
70
80
  "type": "string",
71
- "minLength": 1
81
+ "minLength": 1,
82
+ "description": "The operation type of the nested transform (e.g., 'accountAttribute', 'identityAttribute', 'trim')."
72
83
  },
73
84
  "requiresPeriodicRefresh": {
74
- "type": "boolean"
85
+ "type": "boolean",
86
+ "description": "Whether this nested transform re-evaluates during nightly refresh."
75
87
  },
76
88
  "attributes": {
77
89
  "type": "object",
@@ -83,12 +95,47 @@
83
95
  },
84
96
  "examples": [
85
97
  {
98
+ "name": "Remove Special Characters",
99
+ "type": "replaceAll",
100
+ "attributes": {
101
+ "table": {
102
+ "[^A-Za-z0-9]": ""
103
+ }
104
+ }
105
+ },
106
+ {
107
+ "name": "Normalize Whitespace and Dots",
86
108
  "type": "replaceAll",
87
- "name": "Replace All Transform",
88
109
  "attributes": {
89
110
  "table": {
90
111
  "\\s+": "_",
91
- "[^A-Za-z0-9_]+": ""
112
+ "[.]": "-"
113
+ }
114
+ }
115
+ },
116
+ {
117
+ "name": "Remove Vowels",
118
+ "type": "replaceAll",
119
+ "attributes": {
120
+ "table": {
121
+ "[aeiouAEIOU]": ""
122
+ }
123
+ }
124
+ },
125
+ {
126
+ "name": "Sanitize Username from Account Attribute",
127
+ "type": "replaceAll",
128
+ "attributes": {
129
+ "table": {
130
+ "\\s+": ".",
131
+ "[^A-Za-z0-9.]": ""
132
+ },
133
+ "input": {
134
+ "type": "accountAttribute",
135
+ "attributes": {
136
+ "sourceName": "HR Source",
137
+ "attributeName": "displayName"
138
+ }
92
139
  }
93
140
  }
94
141
  }
@@ -428,46 +428,108 @@ function lintFirstValid(attrs) {
428
428
  return msgs;
429
429
  }
430
430
  // ---------------------------------------------------------------------------
431
- // 8. replace — validate regex compiles
431
+ // 8. replace — regex compile, all-instances info, backreference hint
432
+ // Docs: https://developer.sailpoint.com/docs/extensibility/transforms/operations/replace
432
433
  // ---------------------------------------------------------------------------
433
434
  function lintReplace(attrs) {
434
435
  const msgs = [];
436
+ // 1. regex: must be a non-empty string and a valid compilable regex
435
437
  if (attrs?.regex !== undefined) {
436
438
  if (typeof attrs.regex !== "string") {
437
439
  push(msgs, "error", "regex must be a string.", "attributes.regex");
438
440
  }
441
+ else if (attrs.regex.trim() === "") {
442
+ push(msgs, "error", "regex must not be empty.", "attributes.regex");
443
+ }
439
444
  else {
440
- // Try to compile the regex catch syntax errors early before ISC does
445
+ // Compile checksurface syntax errors before ISC does
441
446
  try {
442
447
  new RegExp(attrs.regex);
443
448
  }
444
449
  catch (e) {
445
- push(msgs, "error", `regex '${attrs.regex}' is not a valid regular expression: ${e?.message ?? e}.`, "attributes.regex");
450
+ push(msgs, "error", `regex '${attrs.regex}' is not a valid regular expression: ${e?.message ?? String(e)}. ` +
451
+ "Use bracket notation for literal special characters (e.g., '[.]' for a literal dot, '[-]' for a literal hyphen).", "attributes.regex");
446
452
  }
453
+ // All-instances info — users often expect first-match-only behaviour
454
+ push(msgs, "info", "replace replaces ALL occurrences of the pattern in the input string, not just the first match. " +
455
+ "To target only a specific occurrence, use a more precise regex that anchors to the position you want.", "attributes.regex");
447
456
  }
448
457
  }
449
- if (attrs?.replacement !== undefined && typeof attrs.replacement !== "string") {
450
- push(msgs, "error", "replacement must be a string.", "attributes.replacement");
458
+ // 2. replacement: must be a string; empty string is valid and deletes all matches
459
+ if (attrs?.replacement !== undefined) {
460
+ if (typeof attrs.replacement !== "string") {
461
+ push(msgs, "error", "replacement must be a string. Use an empty string \"\" to delete all text matched by the regex.", "attributes.replacement");
462
+ }
463
+ else if (/\$\d+/.test(attrs.replacement)) {
464
+ // Backreference detected — confirm the regex has the matching capture group
465
+ push(msgs, "info", "replacement contains a backreference (e.g., '$1'). Ensure the regex contains a matching capture group (e.g., '(.+)'). " +
466
+ "'$0' refers to the entire match; '$1' refers to the first capture group, '$2' to the second, etc.", "attributes.replacement");
467
+ }
451
468
  }
452
- if (attrs?.input === undefined) {
453
- push(msgs, "warn", "replace typically expects an attributes.input (nested transform or attribute reference).", "attributes.input");
469
+ // 3. input: optional per docs (omit to use UI-configured source+attribute).
470
+ // Validate type only when present.
471
+ if (attrs?.input !== undefined) {
472
+ const inp = attrs.input;
473
+ if (!(typeof inp === "string" || (isPlainObject(inp) && typeof inp.type === "string"))) {
474
+ push(msgs, "warn", "input must be a nested transform object {type, attributes} providing the string to apply the regex to, or a static string. " +
475
+ "If omitted, the transform uses the source+attribute combination configured in the identity profile UI.", "attributes.input");
476
+ }
454
477
  }
455
478
  return msgs;
456
479
  }
457
480
  // ---------------------------------------------------------------------------
458
- // 8. replaceAll — table validation
481
+ // 8. replaceAll — regex key validation, value type, case-sensitivity info
482
+ // Docs: https://developer.sailpoint.com/docs/extensibility/transforms/operations/replace-all
459
483
  // ---------------------------------------------------------------------------
460
484
  function lintReplaceAll(attrs) {
461
485
  const msgs = [];
462
486
  if (attrs?.table !== undefined) {
463
487
  if (!isPlainObject(attrs.table) || Array.isArray(attrs.table)) {
464
- push(msgs, "error", "table must be an object map of key value string pairs.", "attributes.table");
488
+ push(msgs, "error", "table must be an object map where keys are regex patterns and values are replacement strings " +
489
+ "(e.g., {\"[aeiou]\": \"\", \"\\\\s+\": \"_\"}). Keys are interpreted as standard Java regular expressions.", "attributes.table");
465
490
  }
466
491
  else {
467
- const badEntries = Object.entries(attrs.table).filter(([, v]) => typeof v !== "string");
468
- if (badEntries.length) {
469
- push(msgs, "error", `All table values must be strings. Non-string entries: ${badEntries.map(([k]) => k).join(", ")}.`, "attributes.table");
492
+ const entries = Object.entries(attrs.table);
493
+ // 1. Empty table
494
+ if (entries.length === 0) {
495
+ push(msgs, "warn", "replaceAll table is empty. Add at least one regex pattern key and its replacement string value.", "attributes.table");
470
496
  }
497
+ else {
498
+ // 2. All values must be strings (replacement text; empty string is valid — deletes all matches)
499
+ const badVals = entries.filter(([, v]) => typeof v !== "string");
500
+ if (badVals.length) {
501
+ push(msgs, "error", `All table values must be strings (the replacement text). Non-string entries: ${badVals.map(([k]) => `'${k}'`).join(", ")}. ` +
502
+ "Use an empty string \"\" as the value to delete all text that matches the pattern.", "attributes.table");
503
+ }
504
+ // 3. Validate each key as a compilable Java-compatible regex (JS RegExp is a close proxy)
505
+ for (const [key] of entries) {
506
+ try {
507
+ new RegExp(key);
508
+ }
509
+ catch (e) {
510
+ push(msgs, "error", `table key '${key}' is not a valid regex pattern: ${e?.message ?? String(e)}. ` +
511
+ "All table keys are interpreted as Java regular expressions. " +
512
+ "Use bracket notation to match literal special characters (e.g., '[.]' for a literal dot, '[+]' for a literal plus).", "attributes.table");
513
+ }
514
+ }
515
+ // 4. Case-sensitivity info — comparisons are case-sensitive by default
516
+ push(msgs, "info", "replaceAll pattern matching is case-sensitive by default. " +
517
+ "To match both cases, use a character class (e.g., '[Aa]bc' matches 'Abc' or 'abc') or include separate table entries for each case variant.", "attributes.table");
518
+ // 5. Simultaneous replacement info — all patterns apply in one pass
519
+ if (entries.length > 1) {
520
+ push(msgs, "info", "replaceAll applies all pattern replacements simultaneously in a single pass. " +
521
+ "Each pattern matches against the original input — not against output already modified by a previous pattern. " +
522
+ "Order of entries does not affect which text is matched.", "attributes.table");
523
+ }
524
+ }
525
+ }
526
+ }
527
+ // 6. input: validate type if provided
528
+ if (attrs?.input !== undefined) {
529
+ const inp = attrs.input;
530
+ if (!(typeof inp === "string" || (isPlainObject(inp) && typeof inp.type === "string"))) {
531
+ push(msgs, "warn", "input must be a nested transform object {type, attributes} providing the string to apply replacements to, or a static string. " +
532
+ "If omitted, the transform uses the source+attribute combination configured in the identity profile UI.", "attributes.input");
471
533
  }
472
534
  }
473
535
  return msgs;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "isc-transforms-mcp",
3
- "version": "1.0.16",
3
+ "version": "1.0.18",
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": {