isc-transforms-mcp 1.0.1 → 1.0.3

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/README.md CHANGED
@@ -134,7 +134,7 @@ The 4 tools below connect to a live ISC tenant and require an **Enterprise licen
134
134
  | `isc_transforms_upsert` | Create or update with dry-run preview + JSON-Patch diff + lint before write |
135
135
  | `isc_transforms_findReferences` | Scan identity profiles for every place a transform is referenced |
136
136
 
137
- **Enterprise plan coming soon** — join the waitlist by opening an [issue](https://github.com/simplifyauth/isc-transforms-mcp/issues) or emailing coalfieldexpress@gmail.com.
137
+ **Enterprise plan coming soon** — [join the waitlist](https://docs.google.com/forms/d/e/1FAIpQLScrSaxD8sev0NuX0t5RXo5B9M0qIz8FQW5Wps2WJAF7fqbs4w/viewform) to be notified when it launches.
138
138
 
139
139
  Once you have a license key, add it to your Claude Desktop config:
140
140
 
package/dist/index.js CHANGED
@@ -338,6 +338,58 @@ export async function buildMcpServer(cfg) {
338
338
  });
339
339
  return { content: [{ type: "text", text: asText({ count: items.length, types: listTransformTypes(), items }) }] };
340
340
  });
341
+ // ── 7b. Full operation catalog (all ops + schemas in one call) ───────────
342
+ server.registerTool(tn("isc.transforms.operationCatalog"), {
343
+ title: "Full Transform Operation Catalog",
344
+ description: "Returns EVERYTHING the LLM needs to build any SailPoint ISC transform — all 39 operation types " +
345
+ "in a single response. For each operation: type key, title, required attributes (with types), " +
346
+ "optional attributes, attribute constraints, doc URL, scaffold example, and JSON Schema. " +
347
+ "Call this FIRST before building any transform. Use it to decide which operation(s) to use, " +
348
+ "understand what attributes are required, and see a working scaffold to start from. " +
349
+ "This eliminates the need to call catalog, getSchema, and scaffold separately. " +
350
+ "OFFLINE — no ISC tenant required.",
351
+ inputSchema: z.object({
352
+ operation_types: z
353
+ .array(z.string())
354
+ .optional()
355
+ .describe("Optional list of specific operation types to return (e.g. ['dateCompare','dateMath','conditional']). " +
356
+ "Omit to return all 39 operations."),
357
+ }),
358
+ }, async ({ operation_types }) => {
359
+ const allTypes = listTransformTypes();
360
+ const requested = operation_types && operation_types.length > 0
361
+ ? operation_types
362
+ .map((t) => toCanonicalType(t) ?? t)
363
+ .filter((t) => allTypes.includes(t))
364
+ : allTypes;
365
+ const items = requested.map((t) => {
366
+ const s = TRANSFORM_CATALOG[t];
367
+ const schema = getOperationSchema(t);
368
+ return {
369
+ type: s.type,
370
+ title: s.title,
371
+ doc_url: s.docUrl,
372
+ required_attributes: s.requiredAttributes ?? [],
373
+ attributes_optional: Boolean(s.attributesOptional),
374
+ is_rule_backed: Boolean(s.injectedAttributes),
375
+ scaffold: s.scaffold(`my-${t}-transform`),
376
+ json_schema: schema ?? null,
377
+ };
378
+ });
379
+ return {
380
+ content: [{
381
+ type: "text",
382
+ text: asText({
383
+ instruction: "Use this catalog to select the right operation type(s) for the requirement. " +
384
+ "Check required_attributes to know what you must supply. " +
385
+ "Use scaffold as your starting JSON shape. " +
386
+ "Validate and lint the result after building.",
387
+ total_operations: items.length,
388
+ operations: items,
389
+ }),
390
+ }],
391
+ };
392
+ });
341
393
  // ── 8. Get JSON Schema for an operation ──────────────────────────────────
342
394
  server.registerTool(tn("isc.transforms.getSchema"), {
343
395
  title: "Get Operation JSON Schema",
@@ -835,5 +835,51 @@ export function lintTransform(input) {
835
835
  messages.push(...lintRandom(requestedType, attrs));
836
836
  if (requestedType === "rfc5646")
837
837
  messages.push(...lintRfc5646(attrs));
838
+ // --- Recursive nested transform lint ---
839
+ // Recursively lint every nested transform found inside attributes.
840
+ // We start from normalized.attributes (not the root) to avoid double-linting root.
841
+ if (normalized.attributes && typeof normalized.attributes === "object") {
842
+ messages.push(...lintNestedTransforms(normalized.attributes, "attributes"));
843
+ }
838
844
  return { normalized, messages };
839
845
  }
846
+ /**
847
+ * Recursively walks a subtree (starting from a transform's attributes object)
848
+ * and lints every nested object that carries a 'type' field — i.e. nested transforms.
849
+ * Reports errors with a path prefix so the user knows exactly where the problem is.
850
+ */
851
+ function lintNestedTransforms(subtree, path) {
852
+ if (!subtree || typeof subtree !== "object")
853
+ return [];
854
+ const msgs = [];
855
+ if (Array.isArray(subtree)) {
856
+ subtree.forEach((item, idx) => {
857
+ const itemPath = `${path}[${idx}]`;
858
+ if (item && typeof item === "object" && typeof item.type === "string") {
859
+ // lintTransform already recurses into the nested transform's own attributes,
860
+ // so we only call it here — no additional recursion needed.
861
+ const result = lintTransform(item);
862
+ result.messages.forEach((m) => msgs.push({ ...m, path: `${itemPath}${m.path ? "." + m.path : ""}` }));
863
+ }
864
+ else if (item && typeof item === "object") {
865
+ msgs.push(...lintNestedTransforms(item, itemPath));
866
+ }
867
+ });
868
+ return msgs;
869
+ }
870
+ for (const [key, value] of Object.entries(subtree)) {
871
+ const childPath = `${path}.${key}`;
872
+ if (value && typeof value === "object" && typeof value.type === "string") {
873
+ // Nested transform object — lintTransform handles further recursion internally.
874
+ const result = lintTransform(value);
875
+ result.messages.forEach((m) => msgs.push({ ...m, path: `${childPath}${m.path ? "." + m.path : ""}` }));
876
+ }
877
+ else if (Array.isArray(value)) {
878
+ msgs.push(...lintNestedTransforms(value, childPath));
879
+ }
880
+ else if (value && typeof value === "object") {
881
+ msgs.push(...lintNestedTransforms(value, childPath));
882
+ }
883
+ }
884
+ return msgs;
885
+ }
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "isc-transforms-mcp",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
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": {
7
7
  "name": "Amit",
8
- "email": "coalfieldexpress@gmail.com"
8
+ "email": "amit@simplifyauth.com"
9
9
  },
10
10
  "license": "MIT",
11
11
  "keywords": [