@zindex-ai/mcp 0.40.7 → 0.40.8

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
@@ -88,8 +88,8 @@ For the full feature catalog see [Supported Diagram Classes](https://zindex.ai/d
88
88
 
89
89
  ## Documentation
90
90
 
91
+ - [Canonical agent front door](https://zindex.ai/) (with `Accept: text/markdown`) - the single recommended entry point
91
92
  - [MCP Setup](https://zindex.ai/docs/getting-started/mcp-setup/)
92
- - [Agent Usage Guide](https://zindex.ai/docs/getting-started/agent-usage/)
93
93
  - [Examples](https://zindex.ai/examples/) - executable workflow recipes (ER from migrations, PR architecture diff, compliance flow, etc.)
94
94
  - [MCP Tools Reference](https://zindex.ai/docs/reference/mcp-tools/)
95
95
  - [Supported Diagram Classes](https://zindex.ai/docs/reference/diagram-classes/)
package/dist/index.js CHANGED
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env node
2
- import{fileURLToPath as L}from"node:url";import{readFileSync as ie,realpathSync as ce}from"node:fs";import{dirname as de,resolve as le}from"node:path";import{McpServer as ue}from"@modelcontextprotocol/sdk/server/mcp.js";import{StdioServerTransport as pe}from"@modelcontextprotocol/sdk/server/stdio.js";async function u(s,e,t,n){let o=`${s.apiUrl}${t}`,r={"Content-Type":"application/json"};s.apiKey&&(r.Authorization=`Bearer ${s.apiKey}`);let d={method:e,headers:r};n&&(d.body=JSON.stringify(n));let i=await fetch(o,d);if(i.status===204)return{ok:!0};let a=await i.json();if(!i.ok&&!a.applied){if(i.status===409)return a;if(i.status===403&&a?.code==="PLAN_LIMIT_EXCEEDED"){let p=new Error(a.error);throw p.code="PLAN_LIMIT_EXCEEDED",p.planLimit=a,p}let g=a.error??`HTTP ${i.status}`,m=Array.isArray(a?.diagnostics)?a.diagnostics:void 0,k=m&&m.length>0?`${g}
2
+ import{fileURLToPath as L}from"node:url";import{readFileSync as ie,realpathSync as ce}from"node:fs";import{dirname as de,resolve as le}from"node:path";import{McpServer as ue}from"@modelcontextprotocol/sdk/server/mcp.js";import{StdioServerTransport as pe}from"@modelcontextprotocol/sdk/server/stdio.js";async function u(s,e,t,n){let o=`${s.apiUrl}${t}`,r={"Content-Type":"application/json"};s.apiKey&&(r.Authorization=`Bearer ${s.apiKey}`);let d={method:e,headers:r};n&&(d.body=JSON.stringify(n));let i=await fetch(o,d);if(i.status===204)return{ok:!0};let a=await i.json();if(!i.ok&&!a.applied){if(i.status===409)return a;if(i.status===403&&a?.code==="PLAN_LIMIT_EXCEEDED"){let p=new Error(a.error);throw p.code="PLAN_LIMIT_EXCEEDED",p.planLimit=a,p}let f=a.error??`HTTP ${i.status}`,m=Array.isArray(a?.diagnostics)?a.diagnostics:void 0,k=m&&m.length>0?`${f}
3
3
 
4
4
  Diagnostics:
5
- ${JSON.stringify(m,null,2)}`:g,l=new Error(k);throw l.status=i.status,l.code=a?.code,l.diagnostics=m,l.apiError=a,l}return a}var w=class{constructor(e){this.config=e}async createScene(e){return u(this.config,"POST","/v1/scenes",e.scene)}async getScene(e,t){let n=t!==void 0?`/v1/scenes/${e}?revision=${t}`:`/v1/scenes/${e}`;try{return await u(this.config,"GET",n)}catch{return null}}async applyOps(e){let{envelope:t}=e;return u(this.config,"POST",`/v1/scenes/${t.sceneId}/applyOps`,t)}async listRevisions(e){return u(this.config,"GET",`/v1/scenes/${e}/revisions`)}async diffScenes(e,t,n){let o=n!==void 0?`?from=${t}&to=${n}`:`?from=${t}`;return u(this.config,"GET",`/v1/scenes/${e}/diff${o}`)}async deleteScene(e){return u(this.config,"DELETE",`/v1/scenes/${e}?confirm=true`)}async undeleteScene(e){return u(this.config,"POST",`/v1/scenes/${e}/undelete`)}async listRecentlyDeleted(){return u(this.config,"GET","/v1/scenes/_recently-deleted")}async submitToSupport(e){let t={status:"support"},n={};return e.problem&&(n.description=e.problem),e.anonymize===!0&&(n.anonymize=!0),Object.keys(n).length>0&&(t.support=n),u(this.config,"PATCH",`/v1/scenes/${encodeURIComponent(e.sceneId)}/privacy`,t)}async publishScene(e){return u(this.config,"PATCH",`/v1/scenes/${encodeURIComponent(e)}/privacy`,{status:"public"})}async makeScenePrivate(e){return u(this.config,"PATCH",`/v1/scenes/${encodeURIComponent(e)}/privacy`,{status:"private"})}},b=class{constructor(e){this.config=e}async render(e){let t=e.context,n=t?.theme;if(t?.sceneId&&typeof t.revision=="number"){let r={format:e.target};return n!==void 0&&(r.theme=n),t.showRevision!==void 0&&(r.showRevision=t.showRevision),typeof t.diffFromRevision=="number"&&(r.diff={fromRevision:t.diffFromRevision}),u(this.config,"POST",`/v1/scenes/${t.sceneId}/render`,r)}let o={scene:e.scene,format:e.target};return n!==void 0&&(o.theme=n),u(this.config,"POST","/v1/scenes/render",o)}},_=class{constructor(e){this.config=e}validateScene(e){return u(this.config,"POST","/v1/scenes/validate",e)}},S=class{constructor(e){this.config=e}async normalize(e){return u(this.config,"POST","/v1/scenes/normalize",e)}};var Q="https://api.zindex.ai",O=class extends Error{constructor(){super("ZINDEX_API_KEY is required"),this.name="MissingApiKeyError"}};function T(){let s=process.env.ZINDEX_API_KEY?.trim()||null,e=(process.env.ZINDEX_API_URL?.trim()||Q).replace(/\/$/,""),t={apiUrl:e,apiKey:s},n=s?"authenticated":"anonymous";return{sceneService:s?new w(t):null,renderService:new b(t),validator:new _(t),normalizeService:new S(t),apiUrl:e,mode:n}}import{z as c}from"zod";function f(s){let e=s;return!e||e.code!=="PLAN_LIMIT_EXCEEDED"||!e.planLimit?null:{content:[{type:"text",text:JSON.stringify(e.planLimit)}],isError:!0}}function C(s,e){s.tool("dsp_create_scene","Create a new persisted diagram scene with revision tracking. Returns sceneId (use for all subsequent operations) and revision 1. This is the recommended first step - persisted scenes support incremental edits (dsp_apply_ops), revision history (dsp_list_revisions), visual diff (dsp_diff_scene), and clean renders (dsp_render_scene). ALWAYS declare scene-level diagramFamily (architecture, workflow, entityRelationship, sequence, network, orgchart, uiflow) - it gates family-specific behaviour the engine and downstream tooling rely on; omitting it triggers a MISSING_DIAGRAM_FAMILY info diagnostic. Nodes support auto-layout (omit layout field, set layoutStrategy on the scene), textStyle wrapping, and position shorthand.",{scene:c.object({schemaVersion:c.string(),diagramFamily:c.enum(["architecture","workflow","entityRelationship","sequence","network","orgchart","uiflow"]).optional().describe("ALWAYS declare on every scene. Gates family-specific behaviour the engine and downstream tooling rely on (sequence fan-in exemption, ER auto-promote-fk-labels, workflow BPMN conventions, family-namespaced node types). Omitting it triggers a MISSING_DIAGRAM_FAMILY info diagnostic."),layoutStrategy:c.object({algorithm:c.enum(["hierarchical","flow","grid","forceDirected"]),direction:c.enum(["TB","BT","LR","RL"]).optional()}).passthrough().optional().describe("Auto-layout strategy. When set, nodes can omit their layout field and the engine computes positions from the graph structure."),scene:c.object({id:c.string(),title:c.string().optional(),units:c.string().default("px"),canvas:c.object({width:c.number(),height:c.number(),background:c.string().default("#ffffff")})}).passthrough(),elements:c.array(c.unknown()).default([]),styles:c.record(c.any()).optional(),assets:c.record(c.any()).optional(),constraints:c.array(c.unknown()).optional()}).passthrough()},async({scene:t})=>{try{let n=await e.createScene({scene:t});return{content:[{type:"text",text:JSON.stringify({sceneId:n.sceneId,revision:n.revision})}]}}catch(n){if(n.name==="SceneAlreadyExistsError")return{content:[{type:"text",text:JSON.stringify({error:`Scene '${t.scene.id}' already exists. Use dsp_apply_ops to update an existing scene, or choose a different scene ID.`,sceneId:t.scene.id})}],isError:!0};let o=f(n);if(o)return o;throw n}})}import{z as y}from"zod";function z(s){let e=s;if(!e||!Array.isArray(e.diagnostics)||e.diagnostics.length===0)return null;let t=(e.message??"").split(`
6
- `)[0]??"";return{content:[{type:"text",text:JSON.stringify({error:t,status:e.status,code:e.code,diagnostics:e.diagnostics})}],isError:!0}}function F(s,e){s.tool("dsp_apply_ops","Edit a persisted scene incrementally. Each call creates a new immutable revision. Include a 'message' field (like a git commit message) to describe the change. Use baseRevision to detect conflicts (409 = re-fetch and re-plan). setText op accepts textStyle for styling updates. Supports 17 operation types including updateScene for modifying scene-level metadata (title, palette, styles, layoutStrategy, diagramFamily, canvas) on an existing scene without recreating it.",{sceneId:y.string(),baseRevision:y.number(),transactionMode:y.enum(["allOrNothing","bestEffort"]).default("allOrNothing"),ops:y.array(y.unknown()),message:y.string().optional().describe("Optional revision message describing this operation batch (like a git commit message)")},async({sceneId:t,baseRevision:n,transactionMode:o,ops:r,message:d})=>{let i={schemaVersion:"0.1",sceneId:t,baseRevision:n,transactionMode:o,ops:r,message:d};try{let a=await e.applyOps({envelope:i});return{content:[{type:"text",text:JSON.stringify({applied:a.applied,revision:a.revision,changedElementIds:a.changedElementIds,diagnostics:a.diagnostics})}]}}catch(a){let g=f(a);if(g)return g;let m=z(a);if(m)return m;throw a}})}import{z as te}from"zod";var ee=/^[a-zA-Z0-9_-]{1,128}$/,x=`Expected envelope shape:
5
+ ${JSON.stringify(m,null,2)}`:f,l=new Error(k);throw l.status=i.status,l.code=a?.code,l.diagnostics=m,l.apiError=a,l}return a}var w=class{constructor(e){this.config=e}async createScene(e){return u(this.config,"POST","/v1/scenes",e.scene)}async getScene(e,t){let n=t!==void 0?`/v1/scenes/${e}?revision=${t}`:`/v1/scenes/${e}`;try{return await u(this.config,"GET",n)}catch{return null}}async applyOps(e){let{envelope:t}=e;return u(this.config,"POST",`/v1/scenes/${t.sceneId}/applyOps`,t)}async listRevisions(e){return u(this.config,"GET",`/v1/scenes/${e}/revisions`)}async diffScenes(e,t,n){let o=n!==void 0?`?from=${t}&to=${n}`:`?from=${t}`;return u(this.config,"GET",`/v1/scenes/${e}/diff${o}`)}async deleteScene(e){return u(this.config,"DELETE",`/v1/scenes/${e}?confirm=true`)}async undeleteScene(e){return u(this.config,"POST",`/v1/scenes/${e}/undelete`)}async listRecentlyDeleted(){return u(this.config,"GET","/v1/scenes/_recently-deleted")}async submitToSupport(e){let t={status:"support"},n={};return e.problem&&(n.description=e.problem),e.anonymize===!0&&(n.anonymize=!0),Object.keys(n).length>0&&(t.support=n),u(this.config,"PATCH",`/v1/scenes/${encodeURIComponent(e.sceneId)}/privacy`,t)}async publishScene(e){return u(this.config,"PATCH",`/v1/scenes/${encodeURIComponent(e)}/privacy`,{status:"public"})}async makeScenePrivate(e){return u(this.config,"PATCH",`/v1/scenes/${encodeURIComponent(e)}/privacy`,{status:"private"})}},b=class{constructor(e){this.config=e}async render(e){let t=e.context,n=t?.theme;if(t?.sceneId&&typeof t.revision=="number"){let r={format:e.target};return n!==void 0&&(r.theme=n),t.showRevision!==void 0&&(r.showRevision=t.showRevision),typeof t.diffFromRevision=="number"&&(r.diff={fromRevision:t.diffFromRevision}),u(this.config,"POST",`/v1/scenes/${t.sceneId}/render`,r)}let o={scene:e.scene,format:e.target};return n!==void 0&&(o.theme=n),u(this.config,"POST","/v1/scenes/render",o)}},_=class{constructor(e){this.config=e}validateScene(e){return u(this.config,"POST","/v1/scenes/validate",e)}},S=class{constructor(e){this.config=e}async normalize(e){return u(this.config,"POST","/v1/scenes/normalize",e)}};var Q="https://api.zindex.ai",O=class extends Error{constructor(){super("ZINDEX_API_KEY is required"),this.name="MissingApiKeyError"}};function T(){let s=process.env.ZINDEX_API_KEY?.trim()||null,e=(process.env.ZINDEX_API_URL?.trim()||Q).replace(/\/$/,""),t={apiUrl:e,apiKey:s},n=s?"authenticated":"anonymous";return{sceneService:s?new w(t):null,renderService:new b(t),validator:new _(t),normalizeService:new S(t),apiUrl:e,mode:n}}import{z as c}from"zod";function g(s){let e=s;return!e||e.code!=="PLAN_LIMIT_EXCEEDED"||!e.planLimit?null:{content:[{type:"text",text:JSON.stringify(e.planLimit)}],isError:!0}}function C(s,e){s.tool("dsp_create_scene","Create a new persisted diagram scene with revision tracking. Returns sceneId (use for all subsequent operations) and revision 1. This is the recommended first step - persisted scenes support incremental edits (dsp_apply_ops), revision history (dsp_list_revisions), visual diff (dsp_diff_scene), and clean renders (dsp_render_scene). ALWAYS declare scene-level diagramFamily (architecture, workflow, entityRelationship, sequence, network, orgchart, uiflow) - it gates family-specific behaviour the engine and downstream tooling rely on; omitting it triggers a MISSING_DIAGRAM_FAMILY info diagnostic. Nodes support auto-layout (omit layout field, set layoutStrategy on the scene), textStyle wrapping, and position shorthand.",{scene:c.object({schemaVersion:c.string(),diagramFamily:c.enum(["architecture","workflow","entityRelationship","sequence","network","orgchart","uiflow"]).optional().describe("ALWAYS declare on every scene. Gates family-specific behaviour the engine and downstream tooling rely on (sequence fan-in exemption, ER auto-promote-fk-labels, workflow BPMN conventions, family-namespaced node types). Omitting it triggers a MISSING_DIAGRAM_FAMILY info diagnostic."),layoutStrategy:c.object({algorithm:c.enum(["hierarchical","flow","grid","forceDirected"]),direction:c.enum(["TB","BT","LR","RL"]).optional()}).passthrough().optional().describe("Auto-layout strategy. When set, nodes can omit their layout field and the engine computes positions from the graph structure."),scene:c.object({id:c.string(),title:c.string().optional(),units:c.string().default("px"),canvas:c.object({width:c.number(),height:c.number(),background:c.string().default("#ffffff")})}).passthrough(),elements:c.array(c.unknown()).default([]),styles:c.record(c.any()).optional(),assets:c.record(c.any()).optional(),constraints:c.array(c.unknown()).optional()}).passthrough()},async({scene:t})=>{try{let n=await e.createScene({scene:t});return{content:[{type:"text",text:JSON.stringify({sceneId:n.sceneId,revision:n.revision})}]}}catch(n){if(n.name==="SceneAlreadyExistsError")return{content:[{type:"text",text:JSON.stringify({error:`Scene '${t.scene.id}' already exists. Use dsp_apply_ops to update an existing scene, or choose a different scene ID.`,sceneId:t.scene.id})}],isError:!0};let o=g(n);if(o)return o;throw n}})}import{z as y}from"zod";function z(s){let e=s;if(!e||!Array.isArray(e.diagnostics)||e.diagnostics.length===0)return null;let t=(e.message??"").split(`
6
+ `)[0]??"";return{content:[{type:"text",text:JSON.stringify({error:t,status:e.status,code:e.code,diagnostics:e.diagnostics})}],isError:!0}}function F(s,e){s.tool("dsp_apply_ops","Edit a persisted scene incrementally. Each call creates a new immutable revision. Include a 'message' field (like a git commit message) to describe the change. Use baseRevision to detect conflicts (409 = re-fetch and re-plan). setText op accepts textStyle for styling updates. Supports 17 operation types including updateScene for modifying scene-level metadata (title, palette, styles, layoutStrategy, diagramFamily, canvas) on an existing scene without recreating it.",{sceneId:y.string(),baseRevision:y.number(),transactionMode:y.enum(["allOrNothing","bestEffort"]).default("allOrNothing"),ops:y.array(y.unknown()),message:y.string().optional().describe("Optional revision message describing this operation batch (like a git commit message)")},async({sceneId:t,baseRevision:n,transactionMode:o,ops:r,message:d})=>{let i={schemaVersion:"0.1",sceneId:t,baseRevision:n,transactionMode:o,ops:r,message:d};try{let a=await e.applyOps({envelope:i});return{content:[{type:"text",text:JSON.stringify({applied:a.applied,revision:a.revision,changedElementIds:a.changedElementIds,diagnostics:a.diagnostics})}]}}catch(a){let f=g(a);if(f)return f;let m=z(a);if(m)return m;throw a}})}import{z as te}from"zod";var ee=/^[a-zA-Z0-9_-]{1,128}$/,x=`Expected envelope shape:
7
7
  {
8
8
  "schemaVersion": "0.1",
9
9
  "scene": { "id": "<sceneId>", "title": "...", "units": "px",
@@ -36,7 +36,7 @@ Common mistakes:
36
36
  - DO NOT pass a string of JSON; pass a parsed object.
37
37
 
38
38
  Returns { ok, diagnostics: [{ code, severity, path, message }] }.
39
- On 'ok: false' the diagnostics array contains structured per-rule details - programmatically iterate them rather than parsing the message string.`,{scene:te.any()},async({scene:t})=>{let n=E(t,"dsp_validate_scene"),o=await e.validateScene(n);return{content:[{type:"text",text:JSON.stringify(o)}]}})}import{z as v}from"zod";function B(s,e,t){let n=e===null,o=n?"Render a scene to SVG or PNG (anonymous mode). Pass the inline scene as `scene` - the platform validates, normalizes, lays out, and renders in one call. Stateless: no scene IDs, no revisions, no diff, no watermark. For persistence (sceneIds, revisions, diff via dsp_diff_scene, watermark on every render), set ZINDEX_API_KEY and use the sceneId form (free key at https://zindex.ai/signup). Check `diagnostics` for: TEXT_OVERFLOW (resize element), CANVAS_AUTO_EXTENDED (declared canvas was auto-extended to fit content; minimum, not a hard cap), NODE_OVERLAP_DETECTED (two node rects overlap; reposition or switch to layoutStrategy), MISSING_DIAGRAM_FAMILY (declare diagramFamily on every scene), MISSING_GLYPH (label contains a codepoint Inter doesn't have; rendered as visible tofu placeholder \u25A1 instead of dropped silently), AUTOGROUP_FAMILY_MISMATCH / AUTOGROUP_SUPPRESSED_TRIVIAL / AUTOGROUP_SKIPPED_ABSOLUTE (scene.extensions.autoGroup was set but skipped - see code for the reason).":"Render a scene to SVG or PNG. Two input forms: pass `sceneId` to render a persisted scene at its current revision (with watermark and diff support, the recommended workflow after dsp_create_scene + dsp_apply_ops); or pass `scene` to render an inline scene without persisting (stateless, no watermark, no revision tracking). Persisted renders include the `Rev N - date` watermark and accept `diffFromRevision` for a coloured visual diff. Check the `diagnostics` array for TEXT_OVERFLOW (resize element), CANVAS_AUTO_EXTENDED (info; canvas was auto-extended), EDGE_LABEL_SUPPRESSED_REDUNDANT (info; an edge label matching a column in either endpoint's extensions.columns was auto-promoted to a column-row anchor, matching the dbdiagram.io / DBeaver convention - the FK edge now terminates at the named column row; set edge.style.forceLabel=true to keep the label, or set endpoint.column directly to anchor without auto-inference), EDGE_LABEL_SUPPRESSED_FANIN (info; 2+ edges share an exact label string AND a target endpoint AND come from the same conceptual source unit (same source, same parent frame with full coverage, OR a common single-step predecessor), so the platform renders the label once on the lowest-id edge per group and drops it from the others; set edge.style.forceLabel=true to keep a label on a specific edge, or rename one of the duplicates), EDGE_LABEL_FANIN_BUNDLED (info; 2+ edges share an exact label string AND a target endpoint but the source nodes are structurally distinct - geometry merges into one trunk so the convergence is visible, but every label and arrowhead is preserved because each edge represents a distinct hop with its own meaning; typical case is parallel CRUD handlers writing the same SQL operation to a shared database), EDGE_OBSTACLE_GRAZED (info; data has grazedEdgeIds and clearance - one or more edges were routed through the degraded-clearance fallback because the router could not find a route that cleared every non-source/non-target node by 16 px; recovery: widen scene.canvas, move the obstacle node, distinguish/merge the affected edges, or set style.forceLabel=true if intentional), EDGE_COLUMN_NOT_FOUND (warning; endpoint.column references a column not declared in the entity - falls back to face-centre anchoring), LABEL_DUPLICATION_DETECTED (rename one of the duplicated labels), LAYOUT_ABSOLUTE_AT_ORIGIN (info; 1+ nodes declare layout.mode='absolute' with default coordinates {x:0, y:0} - data has affectedElementIds, allAtOrigin, and layoutStrategyPresent; remove the layout block on those nodes and rely on layoutStrategy at scene level), LAYOUT_STRATEGY_OVERRIDDEN (info; data has strategyAlgorithm - scene declares layoutStrategy AND every node has explicit layout.mode='absolute' coords, so the strategy is silently ignored; pick one: REMOVE per-element layout blocks to use auto-layout, OR REMOVE layoutStrategy if pinning every node by hand), NODE_OVERLAP_DETECTED (warning; data has nodeA, nodeB, and overlap{x,y,width,height} - two leaf-node bounding boxes overlap by more than 2 px after layout completes; the renderer paints both rects so the lower one is obscured by the upper, surfacing as labels reading through other shapes or edges terminating inside a covered node; reposition one of the named nodes via updateNode, or remove the per-node absolute layout blocks and set scene.layoutStrategy for auto-placement), MISSING_DIAGRAM_FAMILY (info; the scene omits diagramFamily, which gates family-specific behaviour the engine and downstream tooling rely on; data has allowedValues; declare diagramFamily on every scene via dsp_create_scene or updateScene), MISSING_GLYPH (info; data has codepoint, weight, hex - a label contained a codepoint Inter doesn't include, typically math/logic symbols like \u2227 \u2228 \u2261 \u2200 \u2203 \u2208; rendered as visible tofu placeholder \u25A1 U+25A1 instead of being silently dropped; rewrite the label or accept the marker), AUTOGROUP_FAMILY_MISMATCH (info; scene.extensions.autoGroup was set but diagramFamily isn't 'architecture' - extension is architecture-only in v1, ignored), AUTOGROUP_SUPPRESSED_TRIVIAL (info; scene.extensions.autoGroup was set but fewer than 2 nodeType groups have 2+ members - single band is pointless visual chrome), and AUTOGROUP_SKIPPED_ABSOLUTE (info; scene.extensions.autoGroup was set but every node uses absolute layout - auto-grouping requires the layered planner to pack same-type nodes contiguously; remove per-node layout blocks and rely on layoutStrategy).";s.tool("dsp_render_scene",o,{sceneId:v.string().optional().describe(n?"Persisted-scene ID. NOT AVAILABLE in anonymous mode - this server has no ZINDEX_API_KEY set. Either set ZINDEX_API_KEY (free key at https://zindex.ai/signup) and restart the host, or pass `scene` instead for an inline render.":"Persisted-scene ID returned by dsp_create_scene. Renders the scene at its current revision with watermark + diff support. Use this for the recommended persist-first workflow."),scene:v.any().optional().describe("Inline scene document (the same shape dsp_validate_scene accepts). Use for stateless one-off renders without persisting. Available in both anonymous and authenticated modes; in authenticated mode prefer `sceneId` so you get the watermark and diff."),format:v.string().default("svg"),theme:v.string().optional().describe("Render theme: clean (default), dark, blueprint, sketch"),showRevision:v.boolean().optional().describe('Persisted-scene only. Show the full watermark: "scene-id | Rev N - date" in the bottom-right and "zindex.ai" in the bottom-left. Default true. Set to false for completely clean output - the watermark is binary (full metadata or nothing).'),diffFromRevision:v.number().optional().describe("Persisted-scene only. Render visual diff from this revision to current. Added=green, removed=red, modified=amber.")},async({sceneId:r,scene:d,format:i,theme:a,showRevision:g,diffFromRevision:m})=>{if(!r&&!d)return{content:[{type:"text",text:JSON.stringify({error:"Either `sceneId` (persisted scene) or `scene` (inline scene) must be provided."})}],isError:!0};if(r&&d)return{content:[{type:"text",text:JSON.stringify({error:"Provide `sceneId` OR `scene`, not both. Use `sceneId` for a persisted scene, `scene` for an inline render."})}],isError:!0};if(r&&n)return{content:[{type:"text",text:JSON.stringify({error:"Rendering a persisted scene by sceneId requires an API key. This MCP server is in anonymous mode (no ZINDEX_API_KEY set). Either set ZINDEX_API_KEY (free key at https://zindex.ai/signup) and restart the MCP host, or pass an inline `scene` to use the public render endpoint without authentication.",mode:"anonymous",hint:"dsp_validate_scene and dsp_normalize_scene also work in anonymous mode and accept the same inline scene shape."})}],isError:!0};if(r){let p=await e.getScene(r);if(!p)return{content:[{type:"text",text:JSON.stringify({error:`Scene '${r}' not found`})}],isError:!0};let I;try{I=await t.render({scene:p.scene,target:i,context:{sceneId:r,revision:p.revision,theme:a,showRevision:g!==!1,diffFromRevision:m}})}catch(M){let U=f(M);if(U)return U;throw M}let h=I.output,N=(await e.listRevisions(r).catch(()=>null))?.revisions?.length??p.revision;return{content:[{type:"text",text:JSON.stringify({mimeType:h.mimeType,...h.content?{content:h.content}:{},...h.contentBase64?{contentBase64:h.contentBase64}:{},...h.width?{width:h.width}:{},...h.height?{height:h.height}:{},...h.fileName?{fileName:h.fileName}:{},diagnostics:I.diagnostics??[],sceneId:r,revision:p.revision,revisionCount:N,hint:N>1?`Rendered scene '${r}' at revision ${p.revision} (${N} revisions). Use dsp_diff_scene to compare revisions.`:`Rendered scene '${r}' at revision ${p.revision}. Use dsp_apply_ops to make incremental edits (each edit creates a new revision).`})}]}}let k=await t.render({scene:d,target:i,context:{theme:a}}),l=k.output;return{content:[{type:"text",text:JSON.stringify({mimeType:l.mimeType,...l.content?{content:l.content}:{},...l.contentBase64?{contentBase64:l.contentBase64}:{},...l.width?{width:l.width}:{},...l.height?{height:l.height}:{},...l.fileName?{fileName:l.fileName}:{},diagnostics:k.diagnostics??[],mode:"stateless",hint:n?"Stateless render in anonymous mode (no API key). For persistence (sceneIds, revisions, diff, watermark), set ZINDEX_API_KEY (free at https://zindex.ai/signup) and use dsp_create_scene + dsp_apply_ops + dsp_render_scene with sceneId.":"Stateless render: no scene ID, no revision tracking, no watermark. For persistence, use dsp_create_scene + dsp_apply_ops + dsp_render_scene with sceneId."})}]}})}import{z as R}from"zod";function V(s,e){s.tool("dsp_get_scene","Retrieve a persisted scene by ID. Pass revision to fetch a specific historical version. By default returns the scene WITHOUT the computed layout section (bounds, edge paths) to save tokens - pass includeComputed: true only when you need layout data. Always read the latest scene before editing with dsp_apply_ops.",{sceneId:R.string(),revision:R.number().optional(),includeComputed:R.boolean().optional().default(!1)},async({sceneId:t,revision:n,includeComputed:o})=>{let r=await e.getScene(t,n);if(!r){let i=n?`Scene '${t}' revision ${n} not found`:`Scene '${t}' not found`;return{content:[{type:"text",text:JSON.stringify({error:i})}],isError:!0}}let d=o?r.scene:{...r.scene,computed:{layout:{},edgePaths:{},diagnostics:[]}};return{content:[{type:"text",text:JSON.stringify({sceneId:r.sceneId,revision:r.revision,scene:d})}]}})}import{z as ne}from"zod";function j(s,e){s.tool("dsp_normalize_scene",`Normalize a scene document - applies defaults, resolves layout, computes positions, and auto-extends scene.canvas to fit the computed bbox if necessary (emits a CANVAS_AUTO_EXTENDED info diagnostic when this happens; declared canvas dimensions are treated as a minimum, not a hard cap). Returns the result without persisting it. For production diagrams, create a persisted scene with dsp_create_scene instead (persistence gives you revision tracking and incremental edits).
39
+ On 'ok: false' the diagnostics array contains structured per-rule details - programmatically iterate them rather than parsing the message string.`,{scene:te.any()},async({scene:t})=>{let n=E(t,"dsp_validate_scene"),o=await e.validateScene(n);return{content:[{type:"text",text:JSON.stringify(o)}]}})}import{z as v}from"zod";function B(s,e,t){let n=e===null,o=n?"Render a scene to SVG or PNG (anonymous mode). Pass the inline scene as `scene` - the platform validates, normalizes, lays out, and renders in one call. Stateless: no scene IDs, no revisions, no diff, no watermark. For persistence (sceneIds, revisions, diff via dsp_diff_scene, watermark on every render), set ZINDEX_API_KEY and use the sceneId form (free key at https://zindex.ai/signup). Check `diagnostics` for: TEXT_OVERFLOW (resize element), CANVAS_AUTO_EXTENDED (declared canvas was auto-extended to fit content; minimum, not a hard cap), NODE_OVERLAP_DETECTED (two node rects overlap; reposition or switch to layoutStrategy), MISSING_DIAGRAM_FAMILY (declare diagramFamily on every scene), MISSING_GLYPH (label contains a codepoint Inter doesn't have; rendered as visible tofu placeholder \u25A1 instead of dropped silently), AUTOGROUP_FAMILY_MISMATCH / AUTOGROUP_SUPPRESSED_TRIVIAL / AUTOGROUP_SKIPPED_ABSOLUTE (scene.extensions.autoGroup was set but skipped - see code for the reason).":"Render a scene to SVG or PNG. Two input forms: pass `sceneId` to render a persisted scene at its current revision (with watermark and diff support, the recommended workflow after dsp_create_scene + dsp_apply_ops); or pass `scene` to render an inline scene without persisting (stateless, no watermark, no revision tracking). Persisted renders include the `Rev N - date` watermark and accept `diffFromRevision` for a coloured visual diff. Check the `diagnostics` array for TEXT_OVERFLOW (resize element), CANVAS_AUTO_EXTENDED (info; canvas was auto-extended), EDGE_LABEL_SUPPRESSED_REDUNDANT (info; an edge label matching a column in either endpoint's extensions.columns was auto-promoted to a column-row anchor, matching the dbdiagram.io / DBeaver convention - the FK edge now terminates at the named column row; set edge.style.forceLabel=true to keep the label, or set endpoint.column directly to anchor without auto-inference), EDGE_LABEL_SUPPRESSED_FANIN (info; 2+ edges share an exact label string AND a target endpoint AND come from the same conceptual source unit (same source, same parent frame with full coverage, OR a common single-step predecessor), so the platform renders the label once on the lowest-id edge per group and drops it from the others; set edge.style.forceLabel=true to keep a label on a specific edge, or rename one of the duplicates), EDGE_LABEL_FANIN_BUNDLED (info; 2+ edges share an exact label string AND a target endpoint but the source nodes are structurally distinct - geometry merges into one trunk so the convergence is visible, but every label and arrowhead is preserved because each edge represents a distinct hop with its own meaning; typical case is parallel CRUD handlers writing the same SQL operation to a shared database), EDGE_OBSTACLE_GRAZED (info; data has grazedEdgeIds and clearance - one or more edges were routed through the degraded-clearance fallback because the router could not find a route that cleared every non-source/non-target node by 16 px; recovery: widen scene.canvas, move the obstacle node, distinguish/merge the affected edges, or set style.forceLabel=true if intentional), EDGE_COLUMN_NOT_FOUND (warning; endpoint.column references a column not declared in the entity - falls back to face-centre anchoring), LABEL_DUPLICATION_DETECTED (rename one of the duplicated labels), LAYOUT_ABSOLUTE_AT_ORIGIN (info; 1+ nodes declare layout.mode='absolute' with default coordinates {x:0, y:0} - data has affectedElementIds, allAtOrigin, and layoutStrategyPresent; remove the layout block on those nodes and rely on layoutStrategy at scene level), LAYOUT_STRATEGY_OVERRIDDEN (info; data has strategyAlgorithm - scene declares layoutStrategy AND every node has explicit layout.mode='absolute' coords, so the strategy is silently ignored; pick one: REMOVE per-element layout blocks to use auto-layout, OR REMOVE layoutStrategy if pinning every node by hand), NODE_OVERLAP_DETECTED (warning; data has nodeA, nodeB, and overlap{x,y,width,height} - two leaf-node bounding boxes overlap by more than 2 px after layout completes; the renderer paints both rects so the lower one is obscured by the upper, surfacing as labels reading through other shapes or edges terminating inside a covered node; reposition one of the named nodes via updateNode, or remove the per-node absolute layout blocks and set scene.layoutStrategy for auto-placement), MISSING_DIAGRAM_FAMILY (info; the scene omits diagramFamily, which gates family-specific behaviour the engine and downstream tooling rely on; data has allowedValues; declare diagramFamily on every scene via dsp_create_scene or updateScene), MISSING_GLYPH (info; data has codepoint, weight, hex - a label contained a codepoint Inter doesn't include, typically math/logic symbols like \u2227 \u2228 \u2261 \u2200 \u2203 \u2208; rendered as visible tofu placeholder \u25A1 U+25A1 instead of being silently dropped; rewrite the label or accept the marker), AUTOGROUP_FAMILY_MISMATCH (info; scene.extensions.autoGroup was set but diagramFamily isn't 'architecture' - extension is architecture-only in v1, ignored), AUTOGROUP_SUPPRESSED_TRIVIAL (info; scene.extensions.autoGroup was set but fewer than 2 nodeType groups have 2+ members - single band is pointless visual chrome), and AUTOGROUP_SKIPPED_ABSOLUTE (info; scene.extensions.autoGroup was set but every node uses absolute layout - auto-grouping requires the layered planner to pack same-type nodes contiguously; remove per-node layout blocks and rely on layoutStrategy).";s.tool("dsp_render_scene",o,{sceneId:v.string().optional().describe(n?"Persisted-scene ID. NOT AVAILABLE in anonymous mode - this server has no ZINDEX_API_KEY set. Either set ZINDEX_API_KEY (free key at https://zindex.ai/signup) and restart the host, or pass `scene` instead for an inline render.":"Persisted-scene ID returned by dsp_create_scene. Renders the scene at its current revision with watermark + diff support. Use this for the recommended persist-first workflow."),scene:v.any().optional().describe("Inline scene document (the same shape dsp_validate_scene accepts). Use for stateless one-off renders without persisting. Available in both anonymous and authenticated modes; in authenticated mode prefer `sceneId` so you get the watermark and diff."),format:v.string().default("svg"),theme:v.string().optional().describe("Render theme: clean (default), dark, blueprint, sketch"),showRevision:v.boolean().optional().describe('Persisted-scene only. Show the full watermark: "scene-id | Rev N - date" in the bottom-right and "zindex.ai" in the bottom-left. Default true. Set to false for completely clean output - the watermark is binary (full metadata or nothing).'),diffFromRevision:v.number().optional().describe("Persisted-scene only. Render visual diff from this revision to current. Added=green, removed=red, modified=amber.")},async({sceneId:r,scene:d,format:i,theme:a,showRevision:f,diffFromRevision:m})=>{if(!r&&!d)return{content:[{type:"text",text:JSON.stringify({error:"Either `sceneId` (persisted scene) or `scene` (inline scene) must be provided."})}],isError:!0};if(r&&d)return{content:[{type:"text",text:JSON.stringify({error:"Provide `sceneId` OR `scene`, not both. Use `sceneId` for a persisted scene, `scene` for an inline render."})}],isError:!0};if(r&&n)return{content:[{type:"text",text:JSON.stringify({error:"Rendering a persisted scene by sceneId requires an API key. This MCP server is in anonymous mode (no ZINDEX_API_KEY set). Either set ZINDEX_API_KEY (free key at https://zindex.ai/signup) and restart the MCP host, or pass an inline `scene` to use the public render endpoint without authentication.",mode:"anonymous",hint:"dsp_validate_scene and dsp_normalize_scene also work in anonymous mode and accept the same inline scene shape."})}],isError:!0};if(r){let p=await e.getScene(r);if(!p)return{content:[{type:"text",text:JSON.stringify({error:`Scene '${r}' not found`})}],isError:!0};let I;try{I=await t.render({scene:p.scene,target:i,context:{sceneId:r,revision:p.revision,theme:a,showRevision:f!==!1,diffFromRevision:m}})}catch(M){let U=g(M);if(U)return U;throw M}let h=I.output,N=(await e.listRevisions(r).catch(()=>null))?.revisions?.length??p.revision;return{content:[{type:"text",text:JSON.stringify({mimeType:h.mimeType,...h.content?{content:h.content}:{},...h.contentBase64?{contentBase64:h.contentBase64}:{},...h.width?{width:h.width}:{},...h.height?{height:h.height}:{},...h.fileName?{fileName:h.fileName}:{},diagnostics:I.diagnostics??[],sceneId:r,revision:p.revision,revisionCount:N,hint:N>1?`Rendered scene '${r}' at revision ${p.revision} (${N} revisions). Use dsp_diff_scene to compare revisions.`:`Rendered scene '${r}' at revision ${p.revision}. Use dsp_apply_ops to make incremental edits (each edit creates a new revision).`})}]}}let k=await t.render({scene:d,target:i,context:{theme:a}}),l=k.output;return{content:[{type:"text",text:JSON.stringify({mimeType:l.mimeType,...l.content?{content:l.content}:{},...l.contentBase64?{contentBase64:l.contentBase64}:{},...l.width?{width:l.width}:{},...l.height?{height:l.height}:{},...l.fileName?{fileName:l.fileName}:{},diagnostics:k.diagnostics??[],mode:"stateless",hint:n?"Stateless render in anonymous mode (no API key). For persistence (sceneIds, revisions, diff, watermark), set ZINDEX_API_KEY (free at https://zindex.ai/signup) and use dsp_create_scene + dsp_apply_ops + dsp_render_scene with sceneId.":"Stateless render: no scene ID, no revision tracking, no watermark. For persistence, use dsp_create_scene + dsp_apply_ops + dsp_render_scene with sceneId."})}]}})}import{z as R}from"zod";function V(s,e){s.tool("dsp_get_scene","Retrieve a persisted scene by ID. Pass revision to fetch a specific historical version. By default returns the scene WITHOUT the computed layout section (bounds, edge paths) to save tokens - pass includeComputed: true only when you need layout data. Always read the latest scene before editing with dsp_apply_ops.",{sceneId:R.string(),revision:R.number().optional(),includeComputed:R.boolean().optional().default(!1)},async({sceneId:t,revision:n,includeComputed:o})=>{let r=await e.getScene(t,n);if(!r){let i=n?`Scene '${t}' revision ${n} not found`:`Scene '${t}' not found`;return{content:[{type:"text",text:JSON.stringify({error:i})}],isError:!0}}let d=o?r.scene:{...r.scene,computed:{layout:{},edgePaths:{},diagnostics:[]}};return{content:[{type:"text",text:JSON.stringify({sceneId:r.sceneId,revision:r.revision,scene:d})}]}})}import{z as ne}from"zod";function j(s,e){s.tool("dsp_normalize_scene",`Normalize a scene document - applies defaults, resolves layout, computes positions, and auto-extends scene.canvas to fit the computed bbox if necessary (emits a CANVAS_AUTO_EXTENDED info diagnostic when this happens; declared canvas dimensions are treated as a minimum, not a hard cap). Returns the result without persisting it. For production diagrams, create a persisted scene with dsp_create_scene instead (persistence gives you revision tracking and incremental edits).
40
40
 
41
41
  The 'scene' argument must be a COMPLETE scene document with this shape:
42
42
 
@@ -52,7 +52,7 @@ Common mistakes:
52
52
  - DO NOT wrap in another envelope ({scene: {...}}); the 'scene' field at this tool's input level IS the envelope.
53
53
  - DO NOT pass a string of JSON; pass a parsed object.
54
54
 
55
- Returns { scene, computed, diagnostics } where 'scene' is the normalized document with defaults filled in.`,{scene:ne.any()},async({scene:t})=>{let n=E(t,"dsp_normalize_scene"),o=await e.normalize(n);return{content:[{type:"text",text:JSON.stringify(o)}]}})}import{z as D}from"zod";function $(s,e){s.tool("dsp_diff_scene","Diff two revisions of a scene. Returns added, removed, and modified element IDs with summary counts. Use this to see what changed between any two revisions.",{sceneId:D.string(),from:D.number().describe("Starting revision number"),to:D.number().optional().describe("Ending revision number (defaults to current/latest)")},async({sceneId:t,from:n,to:o})=>{try{let r=await e.diffScenes(t,n,o);return{content:[{type:"text",text:JSON.stringify(r)}]}}catch(r){return{content:[{type:"text",text:JSON.stringify({error:r?.message??"Diff failed"})}],isError:!0}}})}import{z as se}from"zod";function W(s,e){s.tool("dsp_list_revisions","List all revisions of a scene with timestamps, messages, and change summaries. Returns a complete changelog.",{sceneId:se.string()},async({sceneId:t})=>{try{let n=await e.listRevisions(t);return{content:[{type:"text",text:JSON.stringify(n)}]}}catch(n){return{content:[{type:"text",text:JSON.stringify({error:n?.message??"List revisions failed"})}],isError:!0}}})}import{z as q}from"zod";function H(s,e){s.tool("dsp_delete_scene","Soft-delete a persisted scene. The scene is reversible for 24 hours via dsp_undelete_scene; after that the scene is permanently removed by a scheduled cron. CONFIRM WITH THE HUMAN BEFORE CALLING. Tell them which scene you're about to delete (by id and any known title) and wait for their explicit confirmation. Never delete scenes as part of a cleanup pass, batch operation, or 'let me start over' reset without per-scene human confirmation. The server enforces a rate limit of 10 deletions per workspace per minute.",{sceneId:q.string(),confirm:q.boolean().describe("Must be `true`. Set this only after the human has confirmed they want this scene deleted.")},async({sceneId:t,confirm:n})=>{if(n!==!0)return{content:[{type:"text",text:JSON.stringify({error:"dsp_delete_scene requires confirm: true. Confirm with the human first, then call again with confirm: true."})}],isError:!0};try{return await e.deleteScene(t),{content:[{type:"text",text:JSON.stringify({deleted:!0,sceneId:t,soft:!0,note:"Scene is soft-deleted. It is recoverable for 24 hours via dsp_undelete_scene. After that it is permanently removed."})}]}}catch(o){return{content:[{type:"text",text:JSON.stringify({error:o?.message??"Delete failed"})}],isError:!0}}})}import{z as oe}from"zod";function J(s,e){s.tool("dsp_undelete_scene","Restore a soft-deleted scene during the 24-hour grace window. Use this if you realize you deleted the wrong scene. After 24 hours the scene has been hard-deleted by a scheduled cron and cannot be restored - this tool returns an error in that case. Confirm with the human before calling to make sure they want the restore.",{sceneId:oe.string()},async({sceneId:t})=>{try{let n=await e.undeleteScene(t);return{content:[{type:"text",text:JSON.stringify({restored:n?.restored===!0,sceneId:n?.sceneId??t,revision:n?.revision})}]}}catch(n){return{content:[{type:"text",text:JSON.stringify({error:n?.message??"Undelete failed. The scene either never existed or its 24-hour grace window has expired."})}],isError:!0}}})}function Y(s,e){s.tool("dsp_list_recently_deleted","List scenes in the caller's workspace that were soft-deleted within the past 24 hours and are still recoverable. Returns sceneId, title (when available), deletedAt timestamp, and expiresAt (when the scene will be permanently removed by the scheduled cron). Use this when a human asks to restore a previously-deleted scene but doesn't remember the id - read the returned list back to the human, then call dsp_undelete_scene on the one they confirm. Returns an empty list when no scenes are recoverable.",{},async()=>{try{let t=await e.listRecentlyDeleted();return{content:[{type:"text",text:JSON.stringify(t??{items:[]})}]}}catch(t){return{content:[{type:"text",text:JSON.stringify({error:t?.message??"Failed to list recently-deleted scenes"})}],isError:!0}}})}import{z as P}from"zod";function K(s,e){s.tool("dsp_submit_to_support","Submit a persisted scene to Zindex support when you cannot resolve a rendering, validation, or layout issue alone. Sets the scene's privacy state to 'support' so Zindex admins can view it; the customer can withdraw at any time from the playground. Pass `anonymize: true` (recommended when acting without explicit user confirmation) to replace labels + IDs with same-width random characters before admins see the scene - visual structure is preserved so support can still diagnose layout issues without seeing the customer's actual content. Confirm with the human before calling: this exposes their scene (anonymized or raw) to Zindex support.",{sceneId:P.string().min(1,"sceneId is required"),problem:P.string().min(1,"problem must be a non-empty description of what's wrong").max(500,"problem must be 500 characters or fewer"),anonymize:P.boolean().optional().describe("When true, the platform replaces labels + IDs with same-width random characters before admins see the scene. Default false - matches the playground modal's opt-in semantics. Set true when submitting without explicit human confirmation.")},async({sceneId:t,problem:n,anonymize:o})=>{try{let r=await e.submitToSupport({sceneId:t,problem:n,anonymize:o===!0});return{content:[{type:"text",text:JSON.stringify({sceneId:r?.sceneId??t,privacyStatus:r?.privacyStatus??"support",supportSubmittedAt:r?.supportSubmittedAt??null,supportAnonymized:r?.supportAnonymized===!0,auditUrl:`https://zindex.ai/playground?id=${encodeURIComponent(t)}`})}]}}catch(r){let d=r?.message??"Submit-to-support failed.";return{content:[{type:"text",text:JSON.stringify({error:d,hint:d.includes("not found")?"Confirm the sceneId is correct + the scene is owned by the API key's workspace.":void 0})}],isError:!0}}})}import{z as re}from"zod";function Z(s,e){s.tool("dsp_publish_scene","Make a persisted scene publicly addressable at https://zindex.ai/s/<sceneId>. Anyone with the URL - including search engines and AI crawlers - can view the rendered scene and its source JSON. ALWAYS confirm with the human BEFORE calling: this exposes their scene to the public Internet. The scene's source JSON is fetchable without authentication. Use dsp_make_scene_private to revert (the public URL stops resolving immediately; the scene itself stays in the workspace). Returns the new privacy status + the public URL + the madePublicAt timestamp.",{sceneId:re.string().min(1,"sceneId is required")},async({sceneId:t})=>{try{let n=await e.publishScene(t),o=n?.sceneId??t;return{content:[{type:"text",text:JSON.stringify({sceneId:o,privacyStatus:n?.privacyStatus??"public",madePublicAt:n?.madePublicAt??null,publicUrl:`https://zindex.ai/s/${encodeURIComponent(o)}`})}]}}catch(n){let o=n?.message??"Publish failed.";return{content:[{type:"text",text:JSON.stringify({error:o,hint:/support/i.test(o)?"The API rejects support -> public transitions. Have the customer withdraw the support submission from the playground first, then re-call dsp_publish_scene.":/not found/i.test(o)?"Confirm the sceneId is correct + the scene is owned by the API key's workspace.":void 0})}],isError:!0}}})}import{z as ae}from"zod";function X(s,e){s.tool("dsp_make_scene_private","Make a persisted scene private again. The public URL (https://zindex.ai/s/<sceneId>) stops resolving immediately - the public read endpoint sets Cache-Control: no-store so revocation propagates without a TTL wait. Also withdraws a scene from Zindex support if it was previously submitted via dsp_submit_to_support (private is the canonical 'undo' for both public and support). The scene document itself is unchanged. No confirmation gate - this strictly reduces exposure.",{sceneId:ae.string().min(1,"sceneId is required")},async({sceneId:t})=>{try{let n=await e.makeScenePrivate(t);return{content:[{type:"text",text:JSON.stringify({sceneId:n?.sceneId??t,privacyStatus:n?.privacyStatus??"private"})}]}}catch(n){let o=n?.message??"Make-private failed.";return{content:[{type:"text",text:JSON.stringify({error:o,hint:/not found/i.test(o)?"Confirm the sceneId is correct + the scene is owned by the API key's workspace.":void 0})}],isError:!0}}})}var A=JSON.parse(ie(le(de(L(import.meta.url)),"..","package.json"),"utf8")).version,he=`Zindex MCP server v${A}
55
+ Returns { scene, computed, diagnostics } where 'scene' is the normalized document with defaults filled in.`,{scene:ne.any()},async({scene:t})=>{let n=E(t,"dsp_normalize_scene"),o=await e.normalize(n);return{content:[{type:"text",text:JSON.stringify(o)}]}})}import{z as D}from"zod";function $(s,e){s.tool("dsp_diff_scene","Diff two revisions of a scene. Returns added, removed, and modified element IDs with summary counts. Use this to see what changed between any two revisions.",{sceneId:D.string(),from:D.number().describe("Starting revision number"),to:D.number().optional().describe("Ending revision number (defaults to current/latest)")},async({sceneId:t,from:n,to:o})=>{try{let r=await e.diffScenes(t,n,o);return{content:[{type:"text",text:JSON.stringify(r)}]}}catch(r){return{content:[{type:"text",text:JSON.stringify({error:r?.message??"Diff failed"})}],isError:!0}}})}import{z as se}from"zod";function q(s,e){s.tool("dsp_list_revisions","List all revisions of a scene with timestamps, messages, and change summaries. Returns a complete changelog.",{sceneId:se.string()},async({sceneId:t})=>{try{let n=await e.listRevisions(t);return{content:[{type:"text",text:JSON.stringify(n)}]}}catch(n){return{content:[{type:"text",text:JSON.stringify({error:n?.message??"List revisions failed"})}],isError:!0}}})}import{z as W}from"zod";function H(s,e){s.tool("dsp_delete_scene","Soft-delete a persisted scene. The scene is reversible for 24 hours via dsp_undelete_scene; after that the scene is permanently removed by a scheduled cron. CONFIRM WITH THE HUMAN BEFORE CALLING. Tell them which scene you're about to delete (by id and any known title) and wait for their explicit confirmation. Never delete scenes as part of a cleanup pass, batch operation, or 'let me start over' reset without per-scene human confirmation. The server enforces a rate limit of 10 deletions per workspace per minute.",{sceneId:W.string(),confirm:W.boolean().describe("Must be `true`. Set this only after the human has confirmed they want this scene deleted.")},async({sceneId:t,confirm:n})=>{if(n!==!0)return{content:[{type:"text",text:JSON.stringify({error:"dsp_delete_scene requires confirm: true. Confirm with the human first, then call again with confirm: true."})}],isError:!0};try{return await e.deleteScene(t),{content:[{type:"text",text:JSON.stringify({deleted:!0,sceneId:t,soft:!0,note:"Scene is soft-deleted. It is recoverable for 24 hours via dsp_undelete_scene. After that it is permanently removed."})}]}}catch(o){return{content:[{type:"text",text:JSON.stringify({error:o?.message??"Delete failed"})}],isError:!0}}})}import{z as oe}from"zod";function J(s,e){s.tool("dsp_undelete_scene","Restore a soft-deleted scene during the 24-hour grace window. Use this if you realize you deleted the wrong scene. After 24 hours the scene has been hard-deleted by a scheduled cron and cannot be restored - this tool returns an error in that case. Confirm with the human before calling to make sure they want the restore.",{sceneId:oe.string()},async({sceneId:t})=>{try{let n=await e.undeleteScene(t);return{content:[{type:"text",text:JSON.stringify({restored:n?.restored===!0,sceneId:n?.sceneId??t,revision:n?.revision})}]}}catch(n){return{content:[{type:"text",text:JSON.stringify({error:n?.message??"Undelete failed. The scene either never existed or its 24-hour grace window has expired."})}],isError:!0}}})}function Y(s,e){s.tool("dsp_list_recently_deleted","List scenes in the caller's workspace that were soft-deleted within the past 24 hours and are still recoverable. Returns sceneId, title (when available), deletedAt timestamp, and expiresAt (when the scene will be permanently removed by the scheduled cron). Use this when a human asks to restore a previously-deleted scene but doesn't remember the id - read the returned list back to the human, then call dsp_undelete_scene on the one they confirm. Returns an empty list when no scenes are recoverable.",{},async()=>{try{let t=await e.listRecentlyDeleted();return{content:[{type:"text",text:JSON.stringify(t??{items:[]})}]}}catch(t){return{content:[{type:"text",text:JSON.stringify({error:t?.message??"Failed to list recently-deleted scenes"})}],isError:!0}}})}import{z as P}from"zod";function K(s,e){s.tool("dsp_submit_to_support","Submit a persisted scene to Zindex support when you cannot resolve a rendering, validation, or layout issue alone. Sets the scene's privacy state to 'support' so Zindex admins can view it; the customer can withdraw at any time from the playground. Pass `anonymize: true` (recommended when acting without explicit user confirmation) to replace labels + IDs with same-width random characters before admins see the scene - visual structure is preserved so support can still diagnose layout issues without seeing the customer's actual content. Confirm with the human before calling: this exposes their scene (anonymized or raw) to Zindex support.",{sceneId:P.string().min(1,"sceneId is required"),problem:P.string().min(1,"problem must be a non-empty description of what's wrong").max(500,"problem must be 500 characters or fewer"),anonymize:P.boolean().optional().describe("When true, the platform replaces labels + IDs with same-width random characters before admins see the scene. Default false - matches the playground modal's opt-in semantics. Set true when submitting without explicit human confirmation.")},async({sceneId:t,problem:n,anonymize:o})=>{try{let r=await e.submitToSupport({sceneId:t,problem:n,anonymize:o===!0});return{content:[{type:"text",text:JSON.stringify({sceneId:r?.sceneId??t,privacyStatus:r?.privacyStatus??"support",supportSubmittedAt:r?.supportSubmittedAt??null,supportAnonymized:r?.supportAnonymized===!0,auditUrl:`https://zindex.ai/playground?id=${encodeURIComponent(t)}`})}]}}catch(r){let d=r?.message??"Submit-to-support failed.";return{content:[{type:"text",text:JSON.stringify({error:d,hint:d.includes("not found")?"Confirm the sceneId is correct + the scene is owned by the API key's workspace.":void 0})}],isError:!0}}})}import{z as re}from"zod";function Z(s,e){s.tool("dsp_publish_scene","Make a persisted scene publicly addressable at https://zindex.ai/s/<sceneId>. Anyone with the URL - including search engines and AI crawlers - can view the rendered scene and its source JSON. ALWAYS confirm with the human BEFORE calling: this exposes their scene to the public Internet. The scene's source JSON is fetchable without authentication. Use dsp_make_scene_private to revert (the public URL stops resolving immediately; the scene itself stays in the workspace). Returns the new privacy status + the public URL + the madePublicAt timestamp.",{sceneId:re.string().min(1,"sceneId is required")},async({sceneId:t})=>{try{let n=await e.publishScene(t),o=n?.sceneId??t;return{content:[{type:"text",text:JSON.stringify({sceneId:o,privacyStatus:n?.privacyStatus??"public",madePublicAt:n?.madePublicAt??null,publicUrl:`https://zindex.ai/s/${encodeURIComponent(o)}`})}]}}catch(n){let o=n?.message??"Publish failed.";return{content:[{type:"text",text:JSON.stringify({error:o,hint:/support/i.test(o)?"The API rejects support -> public transitions. Have the customer withdraw the support submission from the playground first, then re-call dsp_publish_scene.":/not found/i.test(o)?"Confirm the sceneId is correct + the scene is owned by the API key's workspace.":void 0})}],isError:!0}}})}import{z as ae}from"zod";function X(s,e){s.tool("dsp_make_scene_private","Make a persisted scene private again. The public URL (https://zindex.ai/s/<sceneId>) stops resolving immediately - the public read endpoint sets Cache-Control: no-store so revocation propagates without a TTL wait. Also withdraws a scene from Zindex support if it was previously submitted via dsp_submit_to_support (private is the canonical 'undo' for both public and support). The scene document itself is unchanged. No confirmation gate - this strictly reduces exposure.",{sceneId:ae.string().min(1,"sceneId is required")},async({sceneId:t})=>{try{let n=await e.makeScenePrivate(t);return{content:[{type:"text",text:JSON.stringify({sceneId:n?.sceneId??t,privacyStatus:n?.privacyStatus??"private"})}]}}catch(n){let o=n?.message??"Make-private failed.";return{content:[{type:"text",text:JSON.stringify({error:o,hint:/not found/i.test(o)?"Confirm the sceneId is correct + the scene is owned by the API key's workspace.":void 0})}],isError:!0}}})}var A=JSON.parse(ie(le(de(L(import.meta.url)),"..","package.json"),"utf8")).version,he=`Zindex MCP server v${A}
56
56
 
57
57
  A thin MCP client for the Zindex API. Exposes tools for the full DSP
58
58
  protocol - scene lifecycle (create, apply ops, render), validation,
@@ -293,12 +293,11 @@ The anonymize parameter on dsp_submit_to_support is the agent-layer expression o
293
293
  ## References
294
294
 
295
295
  - Canonical agent front door (Accept: text/markdown): https://zindex.ai/
296
- - Agent usage guide (long form): https://zindex.ai/docs/getting-started/agent-usage/
297
296
  - Examples library (executable patterns + agent resources): https://zindex.ai/examples/
298
297
  - Examples manifest (machine-readable, for programmatic discovery): https://zindex.ai/examples/index.json
299
- - Per-example agent resources: every example at /examples/<slug> exposes /examples/<slug>.scene.json (canonical scene), /examples/<slug>.ops.json (typed-operation envelope that builds the scene), /examples/<slug>.workflow.json (agent workflow recipe with steps + MCP/HTTP hints), /examples/<slug>.diff.json (sample dsp_diff_scene response demonstrating revision evolution), /examples/<slug>.github-actions.yml (runnable GitHub Actions workflow for the CI/CD recipe), /examples/<slug>.svg (rendered diagram), and /examples/<slug>.md (markdown summary). Two showcase examples (er-diagram-from-migrations, pr-architecture-diff) additionally expose /examples/<slug>.before.svg for the side-by-side revision-diff display. Fetch these directly rather than scraping HTML.
298
+ - Per-example agent resources: every example at /examples/<slug> exposes /examples/<slug>.scene.json (canonical scene), /examples/<slug>.ops.json (typed-operation envelope that builds the scene), /examples/<slug>.workflow.json (agent workflow recipe with steps + MCP/HTTP hints), /examples/<slug>.diff.json (sample dsp_diff_scene response demonstrating revision evolution), /examples/<slug>.github-actions.yml (runnable GitHub Actions workflow for the CI/CD recipe), /examples/<slug>.svg (rendered diagram), and /examples/<slug>.md (markdown summary). Three showcase examples (er-diagram-from-migrations, pr-architecture-diff, request-flow-from-handler) additionally expose /examples/<slug>.before.svg for the side-by-side revision-diff display. Fetch these directly rather than scraping HTML.
300
299
  - Layout engine details: https://zindex.ai/docs/reference/layout-engine/
301
300
  - Element types reference: https://zindex.ai/docs/reference/element-types/
302
301
  - Privacy commitments: https://zindex.ai/privacy
303
- `.trim();function ge(s){let e=s?.services??T(),{sceneService:t,renderService:n,validator:o,normalizeService:r,mode:d}=e,i=new ue({name:"zindex",version:A},{instructions:me}),a=0;return G(i,o),a++,j(i,r),a++,B(i,t,n),a++,d==="authenticated"&&t&&(C(i,t),a++,F(i,t),a++,V(i,t),a++,$(i,t),a++,W(i,t),a++,H(i,t),a++,J(i,t),a++,Y(i,t),a++,K(i,t),a++,Z(i,t),a++,X(i,t),a++),{server:i,services:e,toolCount:a}}function fe(){let s=process.argv.slice(2);(s.includes("--help")||s.includes("-h"))&&(process.stdout.write(he),process.exit(0)),(s.includes("--version")||s.includes("-v"))&&(process.stdout.write(`${A}
304
- `),process.exit(0));let e=T(),{server:t,toolCount:n}=ge({services:e}),o=new pe;t.connect(o).then(()=>{let r=L(import.meta.url),d=e.mode==="authenticated"?`authenticated mode (${n} tools)`:`anonymous mode (${n} public tools: dsp_validate_scene, dsp_normalize_scene, dsp_render_scene). For persisted scenes (sceneIds, revisions, diff, full MCP tool set) set ZINDEX_API_KEY - free key at https://zindex.ai/signup`;console.error(`Zindex MCP server v${A} running on stdio [connected to ${e.apiUrl}, ${d}]`),console.error(` Binary: ${r}`)})}function ye(){let s=process.argv[1];if(s===void 0)return!1;let e=L(import.meta.url);if(s===e)return!0;try{return ce(s)===e}catch{return!1}}ye()&&fe();export{S as HttpNormalizeService,b as HttpRenderService,w as HttpSceneService,_ as HttpValidator,O as MissingApiKeyError,ge as createMcpServer,T as createServices};
302
+ `.trim();function fe(s){let e=s?.services??T(),{sceneService:t,renderService:n,validator:o,normalizeService:r,mode:d}=e,i=new ue({name:"zindex",version:A},{instructions:me}),a=0;return G(i,o),a++,j(i,r),a++,B(i,t,n),a++,d==="authenticated"&&t&&(C(i,t),a++,F(i,t),a++,V(i,t),a++,$(i,t),a++,q(i,t),a++,H(i,t),a++,J(i,t),a++,Y(i,t),a++,K(i,t),a++,Z(i,t),a++,X(i,t),a++),{server:i,services:e,toolCount:a}}function ge(){let s=process.argv.slice(2);(s.includes("--help")||s.includes("-h"))&&(process.stdout.write(he),process.exit(0)),(s.includes("--version")||s.includes("-v"))&&(process.stdout.write(`${A}
303
+ `),process.exit(0));let e=T(),{server:t,toolCount:n}=fe({services:e}),o=new pe;t.connect(o).then(()=>{let r=L(import.meta.url),d=e.mode==="authenticated"?`authenticated mode (${n} tools)`:`anonymous mode (${n} public tools: dsp_validate_scene, dsp_normalize_scene, dsp_render_scene). For persisted scenes (sceneIds, revisions, diff, full MCP tool set) set ZINDEX_API_KEY - free key at https://zindex.ai/signup`;console.error(`Zindex MCP server v${A} running on stdio [connected to ${e.apiUrl}, ${d}]`),console.error(` Binary: ${r}`)})}function ye(){let s=process.argv[1];if(s===void 0)return!1;let e=L(import.meta.url);if(s===e)return!0;try{return ce(s)===e}catch{return!1}}ye()&&ge();export{S as HttpNormalizeService,b as HttpRenderService,w as HttpSceneService,_ as HttpValidator,O as MissingApiKeyError,fe as createMcpServer,T as createServices};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zindex-ai/mcp",
3
- "version": "0.40.7",
3
+ "version": "0.40.8",
4
4
  "description": "MCP server for Zindex - agent-native diagram state infrastructure. A thin HTTP client exposing tools (create, patch, validate, normalize, diff, render, list-revisions, get-scene, delete-scene, undelete-scene, list-recently-deleted, submit-to-support, publish-scene, make-scene-private) backed by the Zindex API. ZINDEX_API_KEY is optional: with a key the server runs in authenticated mode (full MCP tool set, persisted scenes with revisions and diff); without a key it runs in anonymous mode and exposes the public-endpoint tools (dsp_validate_scene, dsp_normalize_scene, dsp_render_scene with inline scenes). Free key at https://zindex.ai/signup.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",