isc-transforms-mcp 1.0.0
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/JSONS/authoritative-operation-catalog.json +280 -0
- package/JSONS/sailpoint.isc.transforms.accountAttribute.schema.json +164 -0
- package/JSONS/sailpoint.isc.transforms.base64Decode.schema.json +37 -0
- package/JSONS/sailpoint.isc.transforms.base64Encode.schema.json +32 -0
- package/JSONS/sailpoint.isc.transforms.concat.schema.json +109 -0
- package/JSONS/sailpoint.isc.transforms.conditional.schema.json +161 -0
- package/JSONS/sailpoint.isc.transforms.dateCompare.schema.json +159 -0
- package/JSONS/sailpoint.isc.transforms.dateFormat.schema.json +101 -0
- package/JSONS/sailpoint.isc.transforms.dateMath.schema.json +119 -0
- package/JSONS/sailpoint.isc.transforms.decomposeDiacriticalMarks.schema.json +92 -0
- package/JSONS/sailpoint.isc.transforms.displayName.schema.json +42 -0
- package/JSONS/sailpoint.isc.transforms.e164phone.schema.json +107 -0
- package/JSONS/sailpoint.isc.transforms.firstValid.schema.json +129 -0
- package/JSONS/sailpoint.isc.transforms.generateRandomString.schema.json +94 -0
- package/JSONS/sailpoint.isc.transforms.getEndOfString.schema.json +118 -0
- package/JSONS/sailpoint.isc.transforms.getReferenceIdentityAttribute.schema.json +79 -0
- package/JSONS/sailpoint.isc.transforms.identityAttribute.schema.json +104 -0
- package/JSONS/sailpoint.isc.transforms.index.schema.json +48 -0
- package/JSONS/sailpoint.isc.transforms.indexOf.schema.json +90 -0
- package/JSONS/sailpoint.isc.transforms.iso3166.schema.json +103 -0
- package/JSONS/sailpoint.isc.transforms.join.schema.json +113 -0
- package/JSONS/sailpoint.isc.transforms.lastIndexOf.schema.json +90 -0
- package/JSONS/sailpoint.isc.transforms.leftPad.schema.json +96 -0
- package/JSONS/sailpoint.isc.transforms.lookup.schema.json +100 -0
- package/JSONS/sailpoint.isc.transforms.lower.schema.json +80 -0
- package/JSONS/sailpoint.isc.transforms.normalizeNames.schema.json +79 -0
- package/JSONS/sailpoint.isc.transforms.randomAlphaNumeric.schema.json +53 -0
- package/JSONS/sailpoint.isc.transforms.randomNumeric.schema.json +53 -0
- package/JSONS/sailpoint.isc.transforms.reference.schema.json +90 -0
- package/JSONS/sailpoint.isc.transforms.replace.schema.json +96 -0
- package/JSONS/sailpoint.isc.transforms.replaceAll.schema.json +96 -0
- package/JSONS/sailpoint.isc.transforms.rfc5646.schema.json +79 -0
- package/JSONS/sailpoint.isc.transforms.rightPad.schema.json +96 -0
- package/JSONS/sailpoint.isc.transforms.rule.schema.json +106 -0
- package/JSONS/sailpoint.isc.transforms.split.schema.json +103 -0
- package/JSONS/sailpoint.isc.transforms.static.schema.json +131 -0
- package/JSONS/sailpoint.isc.transforms.substring.schema.json +167 -0
- package/JSONS/sailpoint.isc.transforms.trim.schema.json +93 -0
- package/JSONS/sailpoint.isc.transforms.upper.schema.json +80 -0
- package/JSONS/sailpoint.isc.transforms.usernameGenerator.schema.json +106 -0
- package/JSONS/sailpoint.isc.transforms.uuid.schema.json +32 -0
- package/LICENSE +21 -0
- package/README.md +221 -0
- package/bin/isc-transforms-mcp.mjs +3 -0
- package/dist/allowlist.js +37 -0
- package/dist/config.js +67 -0
- package/dist/http/errors.js +19 -0
- package/dist/http/iscAuth.js +45 -0
- package/dist/http/iscClient.js +73 -0
- package/dist/index.js +613 -0
- package/dist/logger.js +9 -0
- package/dist/redact.js +28 -0
- package/dist/transforms/catalog.js +566 -0
- package/dist/transforms/explain.js +266 -0
- package/dist/transforms/generate.js +551 -0
- package/dist/transforms/index.js +9 -0
- package/dist/transforms/lint.js +839 -0
- package/dist/transforms/normalize.js +96 -0
- package/dist/transforms/patterns.js +295 -0
- package/dist/transforms/testcases.js +350 -0
- package/dist/transforms/validate.js +250 -0
- package/dist/util/diff.js +23 -0
- package/package.json +76 -0
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "sailpoint.isc.transforms.usernameGenerator.schema.json",
|
|
4
|
+
"title": "SailPoint ISC Transform Schema - usernameGenerator",
|
|
5
|
+
"description": "Strict schema derived from SailPoint official Username Generator operation documentation. This transform is intended for use within an account create profile; doc examples show the transform object embedded under a create profile attribute entry's 'transform' field and do not include a transform-level 'name'.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"additionalProperties": false,
|
|
8
|
+
"required": [
|
|
9
|
+
"type",
|
|
10
|
+
"attributes"
|
|
11
|
+
],
|
|
12
|
+
"properties": {
|
|
13
|
+
"name": {
|
|
14
|
+
"type": "string",
|
|
15
|
+
"minLength": 1,
|
|
16
|
+
"description": "Optional. In general transform syntax, only the root-level transform has a name; nested transforms omit it."
|
|
17
|
+
},
|
|
18
|
+
"type": {
|
|
19
|
+
"const": "usernameGenerator"
|
|
20
|
+
},
|
|
21
|
+
"attributes": {
|
|
22
|
+
"type": "object",
|
|
23
|
+
"description": "Username generator configuration plus optional dynamic variables (per docs).",
|
|
24
|
+
"required": [
|
|
25
|
+
"patterns"
|
|
26
|
+
],
|
|
27
|
+
"properties": {
|
|
28
|
+
"patterns": {
|
|
29
|
+
"type": "array",
|
|
30
|
+
"minItems": 1,
|
|
31
|
+
"items": {
|
|
32
|
+
"type": "string",
|
|
33
|
+
"minLength": 1
|
|
34
|
+
},
|
|
35
|
+
"description": "A JSON array of patterns for the generator to evaluate for uniqueness, in sequential order. Docs allow using 'uniqueCounter' as a reserved variable (shown as ${uniqueCounter} in examples). Docs also state that if a pattern contains uniqueCounter, it must be the last pattern."
|
|
36
|
+
},
|
|
37
|
+
"sourceCheck": {
|
|
38
|
+
"type": "boolean",
|
|
39
|
+
"default": false,
|
|
40
|
+
"description": "Whether the generator checks only the ISC database or queries the target system directly. true = check target system directly (only if the system supports getObject); false = check only the ISC database (default)."
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
"additionalProperties": {
|
|
44
|
+
"description": "Dynamic variable value used in patterns. Docs describe these as transforms.",
|
|
45
|
+
"$ref": "#/$defs/NestedTransform"
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
"$defs": {
|
|
50
|
+
"NestedTransform": {
|
|
51
|
+
"type": "object",
|
|
52
|
+
"description": "Nested transform object used as a dynamic variable (e.g., fn, ln, fi, mi). Docs examples for nested transforms typically omit 'name'.",
|
|
53
|
+
"additionalProperties": false,
|
|
54
|
+
"required": [
|
|
55
|
+
"type"
|
|
56
|
+
],
|
|
57
|
+
"properties": {
|
|
58
|
+
"id": {
|
|
59
|
+
"type": "string"
|
|
60
|
+
},
|
|
61
|
+
"name": {
|
|
62
|
+
"type": "string",
|
|
63
|
+
"minLength": 1
|
|
64
|
+
},
|
|
65
|
+
"type": {
|
|
66
|
+
"type": "string",
|
|
67
|
+
"minLength": 1
|
|
68
|
+
},
|
|
69
|
+
"requiresPeriodicRefresh": {
|
|
70
|
+
"type": "boolean"
|
|
71
|
+
},
|
|
72
|
+
"attributes": {
|
|
73
|
+
"type": [
|
|
74
|
+
"object",
|
|
75
|
+
"null"
|
|
76
|
+
],
|
|
77
|
+
"additionalProperties": true,
|
|
78
|
+
"description": "Operation-specific attributes for the nested transform (may be omitted for operations without attributes)."
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
"examples": [
|
|
84
|
+
{
|
|
85
|
+
"type": "usernameGenerator",
|
|
86
|
+
"attributes": {
|
|
87
|
+
"sourceCheck": true,
|
|
88
|
+
"patterns": [
|
|
89
|
+
"$fn.$ln${uniqueCounter}"
|
|
90
|
+
],
|
|
91
|
+
"fn": {
|
|
92
|
+
"type": "identityAttribute",
|
|
93
|
+
"attributes": {
|
|
94
|
+
"name": "firstname"
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
"ln": {
|
|
98
|
+
"type": "identityAttribute",
|
|
99
|
+
"attributes": {
|
|
100
|
+
"name": "lastname"
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
]
|
|
106
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "sailpoint.isc.transforms.uuid.schema.json",
|
|
4
|
+
"title": "SailPoint ISC Transform Schema - uuid",
|
|
5
|
+
"description": "Strict schema derived from SailPoint official UUID Generator (uuid) transform documentation. Creates a UUID as a 36-character string. This transform only uses top-level properties and has no attributes block.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"additionalProperties": false,
|
|
8
|
+
"required": [
|
|
9
|
+
"type",
|
|
10
|
+
"name"
|
|
11
|
+
],
|
|
12
|
+
"properties": {
|
|
13
|
+
"type": {
|
|
14
|
+
"const": "uuid"
|
|
15
|
+
},
|
|
16
|
+
"name": {
|
|
17
|
+
"type": "string",
|
|
18
|
+
"minLength": 1
|
|
19
|
+
},
|
|
20
|
+
"requiresPeriodicRefresh": {
|
|
21
|
+
"type": "boolean",
|
|
22
|
+
"default": false,
|
|
23
|
+
"description": "Whether the transform logic should be reevaluated every evening as part of the identity refresh process. Default is false."
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"examples": [
|
|
27
|
+
{
|
|
28
|
+
"type": "uuid",
|
|
29
|
+
"name": "UUID Generator Transform"
|
|
30
|
+
}
|
|
31
|
+
]
|
|
32
|
+
}
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Amit
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
# isc-transforms-mcp
|
|
2
|
+
|
|
3
|
+
> **SailPoint ISC transform authoring, right inside Claude.**
|
|
4
|
+
> Generate · Validate · Lint · Explain · Push to tenant — without leaving your AI assistant.
|
|
5
|
+
|
|
6
|
+
[](https://www.npmjs.com/package/isc-transforms-mcp)
|
|
7
|
+
[](LICENSE)
|
|
8
|
+
[](https://modelcontextprotocol.io)
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## What is this?
|
|
13
|
+
|
|
14
|
+
`isc-transforms-mcp` is a [Model Context Protocol](https://modelcontextprotocol.io) server that gives Claude a complete SailPoint ISC transform authoring toolkit. Instead of handwriting transform JSON, debugging schema errors in the UI, and cross-referencing the docs manually — you describe what you need in plain English and Claude does the rest.
|
|
15
|
+
|
|
16
|
+
**Free (Personal)** — 10 offline tools. No ISC tenant needed. Works entirely on your laptop.
|
|
17
|
+
**Enterprise** — All 14 tools, including live tenant operations (list, get, push, find references). See [Enterprise Plan](#-enterprise-plan).
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Demo
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
You: Generate a SailPoint transform that concatenates first name, a dot, and last name
|
|
25
|
+
to produce an email prefix. Then validate and lint it.
|
|
26
|
+
|
|
27
|
+
Claude: [calls isc_transforms_generate]
|
|
28
|
+
→ { "type": "concat", "name": "email-prefix", "attributes": { "values": [ ... ] } }
|
|
29
|
+
Confidence: high | Doc: https://developer.sailpoint.com/...
|
|
30
|
+
|
|
31
|
+
[calls isc_transforms_validate]
|
|
32
|
+
→ valid: true
|
|
33
|
+
|
|
34
|
+
[calls isc_transforms_lint]
|
|
35
|
+
→ ok: true | 0 errors | 0 warnings
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## Quick Start
|
|
41
|
+
|
|
42
|
+
### 1. Install
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
npm install -g isc-transforms-mcp
|
|
46
|
+
# or use without installing:
|
|
47
|
+
# npx isc-transforms-mcp
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### 2. Add to Claude Desktop
|
|
51
|
+
|
|
52
|
+
Open `%APPDATA%\Claude\claude_desktop_config.json` (Windows) or
|
|
53
|
+
`~/Library/Application Support/Claude/claude_desktop_config.json` (macOS).
|
|
54
|
+
|
|
55
|
+
```json
|
|
56
|
+
{
|
|
57
|
+
"mcpServers": {
|
|
58
|
+
"isc-transforms": {
|
|
59
|
+
"command": "npx",
|
|
60
|
+
"args": ["isc-transforms-mcp"]
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
> If you installed globally, you can also point directly to the binary:
|
|
67
|
+
> `"command": "isc-transforms-mcp"`
|
|
68
|
+
|
|
69
|
+
### 3. Restart Claude Desktop
|
|
70
|
+
|
|
71
|
+
Look for the 🔨 hammer icon at the bottom of the chat input — that confirms the tools loaded.
|
|
72
|
+
|
|
73
|
+
### 4. Try it
|
|
74
|
+
|
|
75
|
+
```
|
|
76
|
+
"Use isc_ping to check the server"
|
|
77
|
+
"Generate a SailPoint transform that falls back from work email to personal email"
|
|
78
|
+
"Validate this transform JSON: { ... }"
|
|
79
|
+
"What SailPoint transform pattern should I use for username generation?"
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## Free Tools (Phase 1 — No ISC Tenant Required)
|
|
85
|
+
|
|
86
|
+
All 10 tools below work completely offline. No credentials, no tenant, no internet connection needed.
|
|
87
|
+
|
|
88
|
+
### `isc_transforms_generate`
|
|
89
|
+
Converts a plain-English requirement into a SailPoint ISC transform JSON payload. Parses your description for operation keywords, attribute names, date formats, and fallback hints. Returns the transform JSON, confidence level, alternative operations, and a link to the official SailPoint docs for that operation type.
|
|
90
|
+
|
|
91
|
+
```
|
|
92
|
+
"Generate a transform that converts an EPOCH timestamp to ISO8601 date format"
|
|
93
|
+
"Create a username transform using first initial plus last name with a uniqueness counter"
|
|
94
|
+
"Fall back from department to costCenter if department is empty"
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### `isc_transforms_validate`
|
|
98
|
+
Two-stage JSON Schema validation powered by AJV and the official SailPoint JSON Schema pack. Stage 1 validates the root shape (name, type, attributes). Stage 2 validates against the operation-specific schema for all 44 operation types, including attribute requirements, allowed values, and nested transform shapes.
|
|
99
|
+
|
|
100
|
+
### `isc_transforms_lint`
|
|
101
|
+
27 semantic lint rules that go beyond what JSON Schema can check. Catches issues like multiple source references on `accountAttribute`, using `delimiter` instead of `separator` on `join`, invalid regex patterns, `requiresPeriodicRefresh` set as a string instead of boolean, missing `default` keys on `lookup` transforms, and 22 more. Errors include the doc URL for the affected operation so you know exactly what to fix.
|
|
102
|
+
|
|
103
|
+
### `isc_transforms_explain`
|
|
104
|
+
Takes a broken transform (or an ISC error message) and returns plain-English guidance plus an auto-corrected JSON where the fix is automatable. Handles 13 known error patterns including boolean coercion, deprecated attribute names, conditional operator restrictions, and missing required fields.
|
|
105
|
+
|
|
106
|
+
### `isc_transforms_suggestPattern`
|
|
107
|
+
Matches your description against 10 named nested-transform patterns and returns a complete working example. Patterns include: fallback email chain, conditional department → building code, username first-initial + last-name + uniqueCounter, EPOCH → ISO8601, normalize + lowercase name, country code → region lookup, email from first.last@domain, date compare for lifecycle state, E.164 phone normalisation, and split to extract domain from email.
|
|
108
|
+
|
|
109
|
+
### `isc_transforms_generateTestCases`
|
|
110
|
+
Generates 2–5 illustrative test cases for a transform: happy-path, null input, and edge cases. Each test case includes a description, sample input, expected output, and notes. Use these directly in the ISC transform tester.
|
|
111
|
+
|
|
112
|
+
### `isc_transforms_catalog`
|
|
113
|
+
Returns all 44+ supported SailPoint ISC transform operation types with: type key, human-readable title, required attributes, doc URL, schema coverage flag, and scaffold example. Essential reference when you are not sure which operation to use.
|
|
114
|
+
|
|
115
|
+
### `isc_transforms_getSchema`
|
|
116
|
+
Returns the full JSON Schema (Draft 2020-12) for any operation type — the exact schema used internally for validation. Useful when you want to understand precisely which attributes are required, optional, and what their constraints are.
|
|
117
|
+
|
|
118
|
+
### `isc_transforms_scaffold`
|
|
119
|
+
Generates a valid minimal starter JSON payload for any operation type. Good starting point before you fill in the actual values.
|
|
120
|
+
|
|
121
|
+
### `isc_ping`
|
|
122
|
+
Health check. Returns the server status, active license tier, and whether Phase 2 tools are available.
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## Enterprise Plan
|
|
127
|
+
|
|
128
|
+
The 4 tools below connect to a live ISC tenant and require an **Enterprise license key**.
|
|
129
|
+
|
|
130
|
+
| Tool | What it does |
|
|
131
|
+
|---|---|
|
|
132
|
+
| `isc_transforms_list` | `GET /v3/transforms` — fetch all transforms from your tenant |
|
|
133
|
+
| `isc_transforms_get` | `GET /v3/transforms/:id` — fetch a single transform |
|
|
134
|
+
| `isc_transforms_upsert` | Create or update with dry-run preview + JSON-Patch diff + lint before write |
|
|
135
|
+
| `isc_transforms_findReferences` | Scan identity profiles for every place a transform is referenced |
|
|
136
|
+
|
|
137
|
+
**Get Enterprise →** [YOUR-STORE-URL](https://YOUR-STORE-URL)
|
|
138
|
+
|
|
139
|
+
Once you have a license key, add it to your Claude Desktop config:
|
|
140
|
+
|
|
141
|
+
```json
|
|
142
|
+
{
|
|
143
|
+
"mcpServers": {
|
|
144
|
+
"isc-transforms": {
|
|
145
|
+
"command": "npx",
|
|
146
|
+
"args": ["isc-transforms-mcp"],
|
|
147
|
+
"env": {
|
|
148
|
+
"ISC_MCP_LICENSE_KEY": "ISC-XXXX-XXXX-XXXX-XXXX",
|
|
149
|
+
"ISC_TENANT": "your-tenant.identitynow.com",
|
|
150
|
+
"ISC_PAT_CLIENT_ID": "your-client-id",
|
|
151
|
+
"ISC_PAT_CLIENT_SECRET": "your-client-secret"
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### Hosted (Remote) Mode
|
|
159
|
+
|
|
160
|
+
Enterprise customers can also point Claude Desktop directly at the hosted server — no local install required:
|
|
161
|
+
|
|
162
|
+
```json
|
|
163
|
+
{
|
|
164
|
+
"mcpServers": {
|
|
165
|
+
"isc-transforms": {
|
|
166
|
+
"url": "https://YOUR-DOMAIN.COM/mcp",
|
|
167
|
+
"headers": {
|
|
168
|
+
"Authorization": "Bearer ISC-XXXX-XXXX-XXXX-XXXX"
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## Configuration Reference
|
|
178
|
+
|
|
179
|
+
All settings are optional for Personal use. Enterprise users need the `ISC_*` credentials for Phase 2 tools.
|
|
180
|
+
|
|
181
|
+
| Variable | Default | Description |
|
|
182
|
+
|---|---|---|
|
|
183
|
+
| `ISC_MCP_LICENSE_KEY` | — | Enterprise license key. Format: `ISC-XXXX-XXXX-XXXX-XXXX` |
|
|
184
|
+
| `ISC_TENANT` | — | Your tenant name. Expands to `https://{tenant}.api.identitynow.com` |
|
|
185
|
+
| `ISC_API_BASE_URL` | — | Explicit API base URL (alternative to `ISC_TENANT`) |
|
|
186
|
+
| `ISC_PAT_CLIENT_ID` | — | PAT client ID for authentication |
|
|
187
|
+
| `ISC_PAT_CLIENT_SECRET` | — | PAT client secret for authentication |
|
|
188
|
+
| `ISC_ACCESS_TOKEN` | — | Pre-minted bearer token (alternative to PAT) |
|
|
189
|
+
| `ISC_MCP_MODE` | `readonly` | Set to `write` to allow `isc_transforms_upsert` to apply changes |
|
|
190
|
+
| `ISC_MCP_DEBUG` | `false` | Enable verbose debug logging to stderr |
|
|
191
|
+
| `ISC_TIMEOUT_MS` | `30000` | HTTP request timeout in milliseconds |
|
|
192
|
+
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
## Supported Operations
|
|
196
|
+
|
|
197
|
+
All 44 SailPoint ISC transform operation types are fully supported for generation, validation, and linting:
|
|
198
|
+
|
|
199
|
+
`accountAttribute` · `base64Decode` · `base64Encode` · `concat` · `conditional` · `dateCompare` · `dateFormat` · `dateMath` · `decomposeDiacriticalMarks` · `displayName` · `e164phone` · `firstValid` · `generateRandomString`\* · `getEndOfString`\* · `getReferenceIdentityAttribute`\* · `identityAttribute` · `indexOf` · `iso3166` · `join` · `lastIndexOf` · `leftPad` · `lookup` · `lower` · `nameNormalizer` · `normalizeNames` · `randomAlphaNumeric` · `randomNumeric` · `reference` · `replace` · `replaceAll` · `rfc5646` · `rightPad` · `rule` · `split` · `static` · `substring` · `trim` · `upper` · `usernameGenerator` · `uuid`
|
|
200
|
+
|
|
201
|
+
\* Rule-backed operations (executed via the Cloud Services Deployment Utility).
|
|
202
|
+
|
|
203
|
+
---
|
|
204
|
+
|
|
205
|
+
## Running from Source
|
|
206
|
+
|
|
207
|
+
```bash
|
|
208
|
+
git clone https://github.com/simplifyauth/isc-transforms-mcp.git
|
|
209
|
+
cd isc-transforms-mcp
|
|
210
|
+
npm install
|
|
211
|
+
npm run build
|
|
212
|
+
npm start # stdio mode (Claude Desktop)
|
|
213
|
+
npm run serve # HTTP mode (hosted/remote)
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
---
|
|
217
|
+
|
|
218
|
+
## License
|
|
219
|
+
|
|
220
|
+
MIT — free to use, modify, and distribute.
|
|
221
|
+
Commercial hosting and enterprise features require a license key. See [Enterprise Plan](#-enterprise-plan).
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
const RULES = [
|
|
2
|
+
// Phase 1: discovery only (readonly)
|
|
3
|
+
{ method: "POST", pathPrefix: "/v3/search", modes: ["readonly", "write"] },
|
|
4
|
+
{ method: "GET", pathPrefix: "/v3/transforms", modes: ["readonly", "write"] },
|
|
5
|
+
{ method: "GET", pathPrefix: "/v3/identity-profiles", modes: ["readonly", "write"] },
|
|
6
|
+
{ method: "GET", pathPrefix: "/v3/workflow-library", modes: ["readonly", "write"] },
|
|
7
|
+
{ method: "GET", pathPrefix: "/v3/workflow-executions", modes: ["readonly", "write"] },
|
|
8
|
+
{ method: "POST", pathPrefix: "/v3/workflow-executions", modes: ["write"] },
|
|
9
|
+
{ method: "GET", pathPrefix: "/v3/workflows", modes: ["readonly", "write"] },
|
|
10
|
+
// Phase 2: Transforms write path
|
|
11
|
+
{ method: "POST", pathPrefix: "/v3/transforms", modes: ["write"] }, // create Docs: https://developer.sailpoint.com/docs/api/v3/create-transform/
|
|
12
|
+
{ method: "PUT", pathPrefix: "/v3/transforms", modes: ["write"] }, // update Docs: https://developer.sailpoint.com/docs/api/v3/update-transform/
|
|
13
|
+
{ method: "POST", pathPrefix: "/v3/identity-profiles", modes: ["write"] },
|
|
14
|
+
{ method: "PUT", pathPrefix: "/v3/identity-profiles", modes: ["write"] },
|
|
15
|
+
{ method: "PATCH", pathPrefix: "/v3/identity-profiles", modes: ["write"] }, // update via JSON Patch (Content-Type: application/json-patch+json) // Docs: https://developer.sailpoint.com/docs/api/v3/update-identity-profile/
|
|
16
|
+
{ method: "POST", pathPrefix: "/v3/workflows", modes: ["write"] },
|
|
17
|
+
{ method: "PUT", pathPrefix: "/v3/workflows", modes: ["write"] },
|
|
18
|
+
{ method: "PATCH", pathPrefix: "/v3/workflows", modes: ["write"] },
|
|
19
|
+
{ method: "DELETE", pathPrefix: "/v3/workflows", modes: ["write"] },
|
|
20
|
+
{ method: "POST", pathPrefix: "/v3/workflows/execute/external", modes: ["write"] },
|
|
21
|
+
// Phase Forms: Custom Forms (v2024)
|
|
22
|
+
// Docs: https://developer.sailpoint.com/docs/api/v2024/custom-forms/ (form definitions + form instances)
|
|
23
|
+
{ method: "GET", pathPrefix: "/v2024/form-definitions", modes: ["readonly", "write"] },
|
|
24
|
+
{ method: "POST", pathPrefix: "/v2024/form-definitions", modes: ["write"] }, // create-form-definition
|
|
25
|
+
{ method: "PATCH", pathPrefix: "/v2024/form-definitions", modes: ["write"] }, // patch-form-definition
|
|
26
|
+
{ method: "DELETE", pathPrefix: "/v2024/form-definitions", modes: ["write"] }, // delete-form-definition
|
|
27
|
+
{ method: "GET", pathPrefix: "/v2024/form-instances", modes: ["readonly", "write"] },
|
|
28
|
+
{ method: "PATCH", pathPrefix: "/v2024/form-instances", modes: ["write"] } // patch-form-instance
|
|
29
|
+
];
|
|
30
|
+
export function isAllowed(mode, method, path) {
|
|
31
|
+
return RULES.some(r => r.method === method &&
|
|
32
|
+
path.startsWith(r.pathPrefix) &&
|
|
33
|
+
r.modes.includes(mode));
|
|
34
|
+
}
|
|
35
|
+
export function getAllowlist() {
|
|
36
|
+
return RULES;
|
|
37
|
+
}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import "dotenv/config";
|
|
2
|
+
function mustBool(v, def) {
|
|
3
|
+
if (!v)
|
|
4
|
+
return def;
|
|
5
|
+
return ["1", "true", "yes", "on"].includes(v.toLowerCase());
|
|
6
|
+
}
|
|
7
|
+
function mustInt(v, def) {
|
|
8
|
+
if (!v)
|
|
9
|
+
return def;
|
|
10
|
+
const n = Number(v);
|
|
11
|
+
return Number.isFinite(n) ? n : def;
|
|
12
|
+
}
|
|
13
|
+
function stripTrailingSlash(u) {
|
|
14
|
+
return u.replace(/\/+$/, "");
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Validates the license key format: ISC-XXXX-XXXX-XXXX-XXXX
|
|
18
|
+
* where each X is an uppercase alphanumeric character (A-Z0-9).
|
|
19
|
+
* Full online activation is optional and handled separately at first use.
|
|
20
|
+
*/
|
|
21
|
+
export function isValidLicenseKey(key) {
|
|
22
|
+
if (!key)
|
|
23
|
+
return false;
|
|
24
|
+
return /^ISC-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}$/.test(key);
|
|
25
|
+
}
|
|
26
|
+
export function loadConfig() {
|
|
27
|
+
const tenant = process.env.ISC_TENANT?.trim();
|
|
28
|
+
const explicitBase = process.env.ISC_API_BASE_URL?.trim();
|
|
29
|
+
let apiBaseUrl = explicitBase || "";
|
|
30
|
+
if (!apiBaseUrl && tenant) {
|
|
31
|
+
apiBaseUrl = `https://${tenant}.api.identitynow.com`;
|
|
32
|
+
}
|
|
33
|
+
apiBaseUrl = stripTrailingSlash(apiBaseUrl || "");
|
|
34
|
+
const apiVersion = (process.env.ISC_API_VERSION?.trim() || "v3").replace(/^\/+/, "");
|
|
35
|
+
const modeRaw = (process.env.ISC_MCP_MODE?.trim() || "readonly");
|
|
36
|
+
if (modeRaw !== "readonly" && modeRaw !== "write") {
|
|
37
|
+
throw new Error("Config error: ISC_MCP_MODE must be 'readonly' or 'write'.");
|
|
38
|
+
}
|
|
39
|
+
const debug = mustBool(process.env.ISC_MCP_DEBUG, false);
|
|
40
|
+
const timeoutMs = mustInt(process.env.ISC_TIMEOUT_MS, 30000);
|
|
41
|
+
const accessToken = process.env.ISC_ACCESS_TOKEN?.trim();
|
|
42
|
+
const patClientId = process.env.ISC_PAT_CLIENT_ID?.trim();
|
|
43
|
+
const patClientSecret = process.env.ISC_PAT_CLIENT_SECRET?.trim();
|
|
44
|
+
// Offline mode: no ISC credentials — Phase 1 tools work, Phase 2 tools will return a clear error.
|
|
45
|
+
const offline = !accessToken && !(patClientId && patClientSecret);
|
|
46
|
+
if (offline && !apiBaseUrl) {
|
|
47
|
+
apiBaseUrl = "https://tenant.api.identitynow.com"; // placeholder, not used in offline mode
|
|
48
|
+
}
|
|
49
|
+
// License key — required for Phase 2 (enterprise) tools.
|
|
50
|
+
// Format: ISC-XXXX-XXXX-XXXX-XXXX (groups of 4 uppercase alphanumeric, prefix ISC-)
|
|
51
|
+
const licenseKey = process.env.ISC_MCP_LICENSE_KEY?.trim() || undefined;
|
|
52
|
+
const licenseTier = isValidLicenseKey(licenseKey) ? "enterprise" : "personal";
|
|
53
|
+
return {
|
|
54
|
+
tenant,
|
|
55
|
+
apiBaseUrl,
|
|
56
|
+
apiVersion,
|
|
57
|
+
mode: modeRaw,
|
|
58
|
+
debug,
|
|
59
|
+
timeoutMs,
|
|
60
|
+
accessToken,
|
|
61
|
+
patClientId,
|
|
62
|
+
patClientSecret,
|
|
63
|
+
offline,
|
|
64
|
+
licenseKey,
|
|
65
|
+
licenseTier,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export class HttpError extends Error {
|
|
2
|
+
status;
|
|
3
|
+
body;
|
|
4
|
+
constructor(message, status, body) {
|
|
5
|
+
super(message);
|
|
6
|
+
this.status = status;
|
|
7
|
+
this.body = body;
|
|
8
|
+
this.name = "HttpError";
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
export function toSafeError(e) {
|
|
12
|
+
if (e instanceof HttpError) {
|
|
13
|
+
return { name: e.name, message: e.message, status: e.status, body: e.body };
|
|
14
|
+
}
|
|
15
|
+
if (e instanceof Error) {
|
|
16
|
+
return { name: e.name, message: e.message };
|
|
17
|
+
}
|
|
18
|
+
return { name: "UnknownError", message: String(e) };
|
|
19
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { HttpError } from "./errors.js";
|
|
2
|
+
let cache = null;
|
|
3
|
+
function nowMs() {
|
|
4
|
+
return Date.now();
|
|
5
|
+
}
|
|
6
|
+
// Token URL example in v3 API intro: https://{tenant}.api.identitynow.com/oauth/token // Docs: https://developer.sailpoint.com/docs/api/v3/
|
|
7
|
+
export async function getBearerToken(cfg) {
|
|
8
|
+
// If user supplied a token, use it.
|
|
9
|
+
if (cfg.accessToken)
|
|
10
|
+
return cfg.accessToken;
|
|
11
|
+
if (!cfg.patClientId || !cfg.patClientSecret) {
|
|
12
|
+
throw new Error("Missing PAT credentials (ISC_PAT_CLIENT_ID / ISC_PAT_CLIENT_SECRET).");
|
|
13
|
+
}
|
|
14
|
+
// cache: refresh if expiring within 60s
|
|
15
|
+
if (cache && cache.expEpochMs - nowMs() > 60_000)
|
|
16
|
+
return cache.token;
|
|
17
|
+
const tokenUrl = `${cfg.apiBaseUrl}/oauth/token`;
|
|
18
|
+
const body = new URLSearchParams({
|
|
19
|
+
grant_type: "client_credentials",
|
|
20
|
+
client_id: cfg.patClientId,
|
|
21
|
+
client_secret: cfg.patClientSecret
|
|
22
|
+
});
|
|
23
|
+
const resp = await fetch(tokenUrl, {
|
|
24
|
+
method: "POST",
|
|
25
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
26
|
+
body,
|
|
27
|
+
signal: AbortSignal.timeout(cfg.timeoutMs)
|
|
28
|
+
});
|
|
29
|
+
const text = await resp.text();
|
|
30
|
+
let json = null;
|
|
31
|
+
try {
|
|
32
|
+
json = text ? JSON.parse(text) : null;
|
|
33
|
+
}
|
|
34
|
+
catch { /* ignore */ }
|
|
35
|
+
if (!resp.ok) {
|
|
36
|
+
throw new HttpError(`Token request failed (${resp.status})`, resp.status, json ?? text);
|
|
37
|
+
}
|
|
38
|
+
const token = json?.access_token;
|
|
39
|
+
const expiresIn = Number(json?.expires_in ?? 900);
|
|
40
|
+
if (!token) {
|
|
41
|
+
throw new Error("Token response missing access_token");
|
|
42
|
+
}
|
|
43
|
+
cache = { token, expEpochMs: nowMs() + expiresIn * 1000 };
|
|
44
|
+
return token;
|
|
45
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { isAllowed } from "../allowlist.js";
|
|
2
|
+
import { HttpError } from "./errors.js";
|
|
3
|
+
import { getBearerToken } from "./iscAuth.js";
|
|
4
|
+
function stripTrailingSlash(u) {
|
|
5
|
+
return u.replace(/\/+$/, "");
|
|
6
|
+
}
|
|
7
|
+
function stripLeadingSlash(p) {
|
|
8
|
+
return p.replace(/^\/+/, "");
|
|
9
|
+
}
|
|
10
|
+
export class IScClient {
|
|
11
|
+
cfg;
|
|
12
|
+
baseUrl; // e.g. https://{tenant}.api.identitynow.com
|
|
13
|
+
defaultVersion; // e.g. v3
|
|
14
|
+
baseApi; // e.g. https://{tenant}.api.identitynow.com/v3
|
|
15
|
+
constructor(cfg) {
|
|
16
|
+
this.cfg = cfg;
|
|
17
|
+
this.baseUrl = stripTrailingSlash(cfg.apiBaseUrl);
|
|
18
|
+
this.defaultVersion = stripLeadingSlash(cfg.apiVersion);
|
|
19
|
+
this.baseApi = `${this.baseUrl}/${this.defaultVersion}`;
|
|
20
|
+
}
|
|
21
|
+
/** Backwards-compatible (default version). */
|
|
22
|
+
getBaseApi() {
|
|
23
|
+
return this.baseApi;
|
|
24
|
+
}
|
|
25
|
+
/** Version-aware base. */
|
|
26
|
+
getBaseApiFor(version) {
|
|
27
|
+
return `${this.baseUrl}/${stripLeadingSlash(version)}`;
|
|
28
|
+
}
|
|
29
|
+
/** Default-version request (existing code uses this). */
|
|
30
|
+
async request(method, path, body, extraHeaders) {
|
|
31
|
+
return this.requestWithVersion(this.defaultVersion, method, path, body, extraHeaders);
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Version-aware request.
|
|
35
|
+
* Example:
|
|
36
|
+
* - version="v3", path="/workflows"
|
|
37
|
+
* - version="v2024", path="/form-definitions"
|
|
38
|
+
*/
|
|
39
|
+
async requestWithVersion(version, method, path, // "/transforms?limit=50" or "transforms?limit=50"
|
|
40
|
+
body, extraHeaders) {
|
|
41
|
+
const v = stripLeadingSlash(version);
|
|
42
|
+
const fullPath = `/${stripLeadingSlash(path)}`; // "/transforms?limit=50"
|
|
43
|
+
const allowPath = `/${v}${fullPath}`; // "/v3/transforms?limit=50"
|
|
44
|
+
if (!isAllowed(this.cfg.mode, method, allowPath)) {
|
|
45
|
+
throw new Error(`Blocked by allowlist: ${method} ${allowPath} (mode=${this.cfg.mode})`);
|
|
46
|
+
}
|
|
47
|
+
const token = await getBearerToken(this.cfg);
|
|
48
|
+
const url = `${this.baseUrl}/${v}${fullPath}`;
|
|
49
|
+
const resp = await fetch(url, {
|
|
50
|
+
method,
|
|
51
|
+
headers: {
|
|
52
|
+
Authorization: `Bearer ${token}`,
|
|
53
|
+
Accept: "application/json",
|
|
54
|
+
...(body ? { "Content-Type": "application/json" } : {}),
|
|
55
|
+
...(extraHeaders || {})
|
|
56
|
+
},
|
|
57
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
58
|
+
signal: AbortSignal.timeout(this.cfg.timeoutMs)
|
|
59
|
+
});
|
|
60
|
+
const text = await resp.text();
|
|
61
|
+
let json = null;
|
|
62
|
+
try {
|
|
63
|
+
json = text ? JSON.parse(text) : null;
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
/* ignore */
|
|
67
|
+
}
|
|
68
|
+
if (!resp.ok) {
|
|
69
|
+
throw new HttpError(`ISC API failed (${resp.status}) ${method} /${v}${fullPath}`, resp.status, json ?? text);
|
|
70
|
+
}
|
|
71
|
+
return (json ?? text);
|
|
72
|
+
}
|
|
73
|
+
}
|