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.
Files changed (63) hide show
  1. package/JSONS/authoritative-operation-catalog.json +280 -0
  2. package/JSONS/sailpoint.isc.transforms.accountAttribute.schema.json +164 -0
  3. package/JSONS/sailpoint.isc.transforms.base64Decode.schema.json +37 -0
  4. package/JSONS/sailpoint.isc.transforms.base64Encode.schema.json +32 -0
  5. package/JSONS/sailpoint.isc.transforms.concat.schema.json +109 -0
  6. package/JSONS/sailpoint.isc.transforms.conditional.schema.json +161 -0
  7. package/JSONS/sailpoint.isc.transforms.dateCompare.schema.json +159 -0
  8. package/JSONS/sailpoint.isc.transforms.dateFormat.schema.json +101 -0
  9. package/JSONS/sailpoint.isc.transforms.dateMath.schema.json +119 -0
  10. package/JSONS/sailpoint.isc.transforms.decomposeDiacriticalMarks.schema.json +92 -0
  11. package/JSONS/sailpoint.isc.transforms.displayName.schema.json +42 -0
  12. package/JSONS/sailpoint.isc.transforms.e164phone.schema.json +107 -0
  13. package/JSONS/sailpoint.isc.transforms.firstValid.schema.json +129 -0
  14. package/JSONS/sailpoint.isc.transforms.generateRandomString.schema.json +94 -0
  15. package/JSONS/sailpoint.isc.transforms.getEndOfString.schema.json +118 -0
  16. package/JSONS/sailpoint.isc.transforms.getReferenceIdentityAttribute.schema.json +79 -0
  17. package/JSONS/sailpoint.isc.transforms.identityAttribute.schema.json +104 -0
  18. package/JSONS/sailpoint.isc.transforms.index.schema.json +48 -0
  19. package/JSONS/sailpoint.isc.transforms.indexOf.schema.json +90 -0
  20. package/JSONS/sailpoint.isc.transforms.iso3166.schema.json +103 -0
  21. package/JSONS/sailpoint.isc.transforms.join.schema.json +113 -0
  22. package/JSONS/sailpoint.isc.transforms.lastIndexOf.schema.json +90 -0
  23. package/JSONS/sailpoint.isc.transforms.leftPad.schema.json +96 -0
  24. package/JSONS/sailpoint.isc.transforms.lookup.schema.json +100 -0
  25. package/JSONS/sailpoint.isc.transforms.lower.schema.json +80 -0
  26. package/JSONS/sailpoint.isc.transforms.normalizeNames.schema.json +79 -0
  27. package/JSONS/sailpoint.isc.transforms.randomAlphaNumeric.schema.json +53 -0
  28. package/JSONS/sailpoint.isc.transforms.randomNumeric.schema.json +53 -0
  29. package/JSONS/sailpoint.isc.transforms.reference.schema.json +90 -0
  30. package/JSONS/sailpoint.isc.transforms.replace.schema.json +96 -0
  31. package/JSONS/sailpoint.isc.transforms.replaceAll.schema.json +96 -0
  32. package/JSONS/sailpoint.isc.transforms.rfc5646.schema.json +79 -0
  33. package/JSONS/sailpoint.isc.transforms.rightPad.schema.json +96 -0
  34. package/JSONS/sailpoint.isc.transforms.rule.schema.json +106 -0
  35. package/JSONS/sailpoint.isc.transforms.split.schema.json +103 -0
  36. package/JSONS/sailpoint.isc.transforms.static.schema.json +131 -0
  37. package/JSONS/sailpoint.isc.transforms.substring.schema.json +167 -0
  38. package/JSONS/sailpoint.isc.transforms.trim.schema.json +93 -0
  39. package/JSONS/sailpoint.isc.transforms.upper.schema.json +80 -0
  40. package/JSONS/sailpoint.isc.transforms.usernameGenerator.schema.json +106 -0
  41. package/JSONS/sailpoint.isc.transforms.uuid.schema.json +32 -0
  42. package/LICENSE +21 -0
  43. package/README.md +221 -0
  44. package/bin/isc-transforms-mcp.mjs +3 -0
  45. package/dist/allowlist.js +37 -0
  46. package/dist/config.js +67 -0
  47. package/dist/http/errors.js +19 -0
  48. package/dist/http/iscAuth.js +45 -0
  49. package/dist/http/iscClient.js +73 -0
  50. package/dist/index.js +613 -0
  51. package/dist/logger.js +9 -0
  52. package/dist/redact.js +28 -0
  53. package/dist/transforms/catalog.js +566 -0
  54. package/dist/transforms/explain.js +266 -0
  55. package/dist/transforms/generate.js +551 -0
  56. package/dist/transforms/index.js +9 -0
  57. package/dist/transforms/lint.js +839 -0
  58. package/dist/transforms/normalize.js +96 -0
  59. package/dist/transforms/patterns.js +295 -0
  60. package/dist/transforms/testcases.js +350 -0
  61. package/dist/transforms/validate.js +250 -0
  62. package/dist/util/diff.js +23 -0
  63. 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
+ [![npm version](https://img.shields.io/npm/v/isc-transforms-mcp)](https://www.npmjs.com/package/isc-transforms-mcp)
7
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
8
+ [![MCP compatible](https://img.shields.io/badge/MCP-compatible-blue)](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,3 @@
1
+ #!/usr/bin/env node
2
+ // Entry-point shim for `npx isc-transforms-mcp` or global installs.
3
+ import "../dist/index.js";
@@ -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
+ }