@zindex-ai/mcp 0.39.1 → 0.39.2
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/dist/index.js +3 -3
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{fileURLToPath as W}from"node:url";import{realpathSync as ee}from"node:fs";import{McpServer as te}from"@modelcontextprotocol/sdk/server/mcp.js";import{StdioServerTransport as ne}from"@modelcontextprotocol/sdk/server/stdio.js";async function l(s,t,e,n){let r=`${s.apiUrl}${e}`,o={"Content-Type":"application/json"};s.apiKey&&(o.Authorization=`Bearer ${s.apiKey}`);let d={method:t,headers:o};n&&(d.body=JSON.stringify(n));let a=await fetch(r,d);if(a.status===204)return{ok:!0};let i=await a.json();if(!a.ok&&!i.applied){if(a.status===409)return i;throw new Error(i.error??`HTTP ${a.status}`)}return i}var g=class{constructor(t){this.config=t}async createScene(t){return l(this.config,"POST","/v1/scenes",t.scene)}async getScene(t,e){let n=e!==void 0?`/v1/scenes/${t}?revision=${e}`:`/v1/scenes/${t}`;try{return await l(this.config,"GET",n)}catch{return null}}async applyOps(t){let{envelope:e}=t;return l(this.config,"POST",`/v1/scenes/${e.sceneId}/applyOps`,e)}async listRevisions(t){return l(this.config,"GET",`/v1/scenes/${t}/revisions`)}async diffScenes(t,e,n){let r=n!==void 0?`?from=${e}&to=${n}`:`?from=${e}`;return l(this.config,"GET",`/v1/scenes/${t}/diff${r}`)}async deleteScene(t){return l(this.config,"DELETE",`/v1/scenes/${t}?confirm=true`)}async undeleteScene(t){return l(this.config,"POST",`/v1/scenes/${t}/undelete`)}async listRecentlyDeleted(){return l(this.config,"GET","/v1/scenes/_recently-deleted")}async submitToSupport(t){let e={status:"support"},n={};return t.problem&&(n.description=t.problem),t.anonymize===!0&&(n.anonymize=!0),Object.keys(n).length>0&&(e.support=n),l(this.config,"PATCH",`/v1/scenes/${encodeURIComponent(t.sceneId)}/privacy`,e)}async publishScene(t){return l(this.config,"PATCH",`/v1/scenes/${encodeURIComponent(t)}/privacy`,{status:"public"})}async makeScenePrivate(t){return l(this.config,"PATCH",`/v1/scenes/${encodeURIComponent(t)}/privacy`,{status:"private"})}},y=class{constructor(t){this.config=t}async render(t){let e=t.context,n=e?.theme;if(e?.sceneId&&typeof e.revision=="number"){let o={format:t.target};return n!==void 0&&(o.theme=n),e.showRevision!==void 0&&(o.showRevision=e.showRevision),typeof e.diffFromRevision=="number"&&(o.diff={fromRevision:e.diffFromRevision}),l(this.config,"POST",`/v1/scenes/${e.sceneId}/render`,o)}let r={scene:t.scene,format:t.target};return n!==void 0&&(r.theme=n),l(this.config,"POST","/v1/scenes/render",r)}},v=class{constructor(t){this.config=t}validateScene(t){return l(this.config,"POST","/v1/scenes/validate",t)}},w=class{constructor(t){this.config=t}async normalize(t){return l(this.config,"POST","/v1/scenes/normalize",t)}};var Y="https://api.zindex.ai",x=class extends Error{constructor(){super("ZINDEX_API_KEY is required"),this.name="MissingApiKeyError"}};function b(){let s=process.env.ZINDEX_API_KEY?.trim()||null,t=(process.env.ZINDEX_API_URL?.trim()||Y).replace(/\/$/,""),e={apiUrl:t,apiKey:s},n=s?"authenticated":"anonymous";return{sceneService:s?new g(e):null,renderService:new y(e),validator:new v(e),normalizeService:new w(e),apiUrl:t,mode:n}}import{z as c}from"zod";function N(s,t){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:e})=>{try{let n=await t.createScene({scene:e});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 '${e.scene.id}' already exists. Use dsp_apply_ops to update an existing scene, or choose a different scene ID.`,sceneId:e.scene.id})}],isError:!0};throw n}})}import{z as m}from"zod";function R(s,t){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:m.string(),baseRevision:m.number(),transactionMode:m.enum(["allOrNothing","bestEffort"]).default("allOrNothing"),ops:m.array(m.unknown()),message:m.string().optional().describe("Optional revision message describing this operation batch (like a git commit message)")},async({sceneId:e,baseRevision:n,transactionMode:r,ops:o,message:d})=>{let a={schemaVersion:"0.1",sceneId:e,baseRevision:n,transactionMode:r,ops:o,message:d},i=await t.applyOps({envelope:a});return{content:[{type:"text",text:JSON.stringify({applied:i.applied,revision:i.revision,changedElementIds:i.changedElementIds,diagnostics:i.diagnostics})}]}})}import{z as J}from"zod";function O(s,t){s.tool("dsp_validate_scene","Validate a scene document for structural and semantic correctness. Use this to check your scene before creating it with dsp_create_scene. Does not persist anything.",{scene:J.any()},async({scene:e})=>{let n=await t.validateScene(e);return{content:[{type:"text",text:JSON.stringify(n)}]}})}import{z as f}from"zod";function D(s,t,e){let n=t===null,r=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), MISSING_DIAGRAM_FAMILY (declare diagramFamily on every scene).":"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), and 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).";s.tool("dsp_render_scene",r,{sceneId:f.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:f.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:f.string().default("svg"),theme:f.string().optional().describe("Render theme: clean (default), dark, blueprint, sketch"),showRevision:f.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:f.number().optional().describe("Persisted-scene only. Render visual diff from this revision to current. Added=green, removed=red, modified=amber.")},async({sceneId:o,scene:d,format:a,theme:i,showRevision:$,diffFromRevision:H})=>{if(!o&&!d)return{content:[{type:"text",text:JSON.stringify({error:"Either `sceneId` (persisted scene) or `scene` (inline scene) must be provided."})}],isError:!0};if(o&&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(o&&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(o){let h=await t.getScene(o);if(!h)return{content:[{type:"text",text:JSON.stringify({error:`Scene '${o}' not found`})}],isError:!0};let I=await e.render({scene:h.scene,target:a,context:{sceneId:o,revision:h.revision,theme:i,showRevision:$!==!1,diffFromRevision:H}}),p=I.output,S=(await t.listRevisions(o).catch(()=>null))?.revisions?.length??h.revision;return{content:[{type:"text",text:JSON.stringify({mimeType:p.mimeType,...p.content?{content:p.content}:{},...p.contentBase64?{contentBase64:p.contentBase64}:{},...p.width?{width:p.width}:{},...p.height?{height:p.height}:{},...p.fileName?{fileName:p.fileName}:{},diagnostics:I.diagnostics??[],sceneId:o,revision:h.revision,revisionCount:S,hint:S>1?`Rendered scene '${o}' at revision ${h.revision} (${S} revisions). Use dsp_diff_scene to compare revisions.`:`Rendered scene '${o}' at revision ${h.revision}. Use dsp_apply_ops to make incremental edits (each edit creates a new revision).`})}]}}let A=await e.render({scene:d,target:a,context:{theme:i}}),u=A.output;return{content:[{type:"text",text:JSON.stringify({mimeType:u.mimeType,...u.content?{content:u.content}:{},...u.contentBase64?{contentBase64:u.contentBase64}:{},...u.width?{width:u.width}:{},...u.height?{height:u.height}:{},...u.fileName?{fileName:u.fileName}:{},diagnostics:A.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 E}from"zod";function P(s,t){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:E.string(),revision:E.number().optional(),includeComputed:E.boolean().optional().default(!1)},async({sceneId:e,revision:n,includeComputed:r})=>{let o=await t.getScene(e,n);if(!o){let a=n?`Scene '${e}' revision ${n} not found`:`Scene '${e}' not found`;return{content:[{type:"text",text:JSON.stringify({error:a})}],isError:!0}}let d=r?o.scene:{...o.scene,computed:{layout:{},edgePaths:{},diagnostics:[]}};return{content:[{type:"text",text:JSON.stringify({sceneId:o.sceneId,revision:o.revision,scene:d})}]}})}import{z as K}from"zod";function L(s,t){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).",{scene:K.any()},async({scene:e})=>{let n=await t.normalize(e);return{content:[{type:"text",text:JSON.stringify(n)}]}})}import{z as k}from"zod";function M(s,t){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:k.string(),from:k.number().describe("Starting revision number"),to:k.number().optional().describe("Ending revision number (defaults to current/latest)")},async({sceneId:e,from:n,to:r})=>{try{let o=await t.diffScenes(e,n,r);return{content:[{type:"text",text:JSON.stringify(o)}]}}catch(o){return{content:[{type:"text",text:JSON.stringify({error:o?.message??"Diff failed"})}],isError:!0}}})}import{z as j}from"zod";function z(s,t){s.tool("dsp_list_revisions","List all revisions of a scene with timestamps, messages, and change summaries. Returns a complete changelog.",{sceneId:j.string()},async({sceneId:e})=>{try{let n=await t.listRevisions(e);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 C}from"zod";function U(s,t){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:C.string(),confirm:C.boolean().describe("Must be `true`. Set this only after the human has confirmed they want this scene deleted.")},async({sceneId:e,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 t.deleteScene(e),{content:[{type:"text",text:JSON.stringify({deleted:!0,sceneId:e,soft:!0,note:"Scene is soft-deleted. It is recoverable for 24 hours via dsp_undelete_scene. After that it is permanently removed."})}]}}catch(r){return{content:[{type:"text",text:JSON.stringify({error:r?.message??"Delete failed"})}],isError:!0}}})}import{z as Z}from"zod";function F(s,t){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:Z.string()},async({sceneId:e})=>{try{let n=await t.undeleteScene(e);return{content:[{type:"text",text:JSON.stringify({restored:n?.restored===!0,sceneId:n?.sceneId??e,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 G(s,t){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 e=await t.listRecentlyDeleted();return{content:[{type:"text",text:JSON.stringify(e??{items:[]})}]}}catch(e){return{content:[{type:"text",text:JSON.stringify({error:e?.message??"Failed to list recently-deleted scenes"})}],isError:!0}}})}import{z as T}from"zod";function B(s,t){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:T.string().min(1,"sceneId is required"),problem:T.string().min(1,"problem must be a non-empty description of what's wrong").max(500,"problem must be 500 characters or fewer"),anonymize:T.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:e,problem:n,anonymize:r})=>{try{let o=await t.submitToSupport({sceneId:e,problem:n,anonymize:r===!0});return{content:[{type:"text",text:JSON.stringify({sceneId:o?.sceneId??e,privacyStatus:o?.privacyStatus??"support",supportSubmittedAt:o?.supportSubmittedAt??null,supportAnonymized:o?.supportAnonymized===!0,auditUrl:`https://zindex.ai/playground?id=${encodeURIComponent(e)}`})}]}}catch(o){let d=o?.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 X}from"zod";function q(s,t){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:X.string().min(1,"sceneId is required")},async({sceneId:e})=>{try{let n=await t.publishScene(e),r=n?.sceneId??e;return{content:[{type:"text",text:JSON.stringify({sceneId:r,privacyStatus:n?.privacyStatus??"public",madePublicAt:n?.madePublicAt??null,publicUrl:`https://zindex.ai/s/${encodeURIComponent(r)}`})}]}}catch(n){let r=n?.message??"Publish failed.";return{content:[{type:"text",text:JSON.stringify({error:r,hint:/support/i.test(r)?"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(r)?"Confirm the sceneId is correct + the scene is owned by the API key's workspace.":void 0})}],isError:!0}}})}import{z as Q}from"zod";function V(s,t){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:Q.string().min(1,"sceneId is required")},async({sceneId:e})=>{try{let n=await t.makeScenePrivate(e);return{content:[{type:"text",text:JSON.stringify({sceneId:n?.sceneId??e,privacyStatus:n?.privacyStatus??"private"})}]}}catch(n){let r=n?.message??"Make-private failed.";return{content:[{type:"text",text:JSON.stringify({error:r,hint:/not found/i.test(r)?"Confirm the sceneId is correct + the scene is owned by the API key's workspace.":void 0})}],isError:!0}}})}var _="0.39.1",se=`Zindex MCP server v${_}
|
|
2
|
+
import{fileURLToPath as W}from"node:url";import{realpathSync as ee}from"node:fs";import{McpServer as te}from"@modelcontextprotocol/sdk/server/mcp.js";import{StdioServerTransport as ne}from"@modelcontextprotocol/sdk/server/stdio.js";async function l(s,t,e,n){let r=`${s.apiUrl}${e}`,o={"Content-Type":"application/json"};s.apiKey&&(o.Authorization=`Bearer ${s.apiKey}`);let d={method:t,headers:o};n&&(d.body=JSON.stringify(n));let a=await fetch(r,d);if(a.status===204)return{ok:!0};let i=await a.json();if(!a.ok&&!i.applied){if(a.status===409)return i;throw new Error(i.error??`HTTP ${a.status}`)}return i}var g=class{constructor(t){this.config=t}async createScene(t){return l(this.config,"POST","/v1/scenes",t.scene)}async getScene(t,e){let n=e!==void 0?`/v1/scenes/${t}?revision=${e}`:`/v1/scenes/${t}`;try{return await l(this.config,"GET",n)}catch{return null}}async applyOps(t){let{envelope:e}=t;return l(this.config,"POST",`/v1/scenes/${e.sceneId}/applyOps`,e)}async listRevisions(t){return l(this.config,"GET",`/v1/scenes/${t}/revisions`)}async diffScenes(t,e,n){let r=n!==void 0?`?from=${e}&to=${n}`:`?from=${e}`;return l(this.config,"GET",`/v1/scenes/${t}/diff${r}`)}async deleteScene(t){return l(this.config,"DELETE",`/v1/scenes/${t}?confirm=true`)}async undeleteScene(t){return l(this.config,"POST",`/v1/scenes/${t}/undelete`)}async listRecentlyDeleted(){return l(this.config,"GET","/v1/scenes/_recently-deleted")}async submitToSupport(t){let e={status:"support"},n={};return t.problem&&(n.description=t.problem),t.anonymize===!0&&(n.anonymize=!0),Object.keys(n).length>0&&(e.support=n),l(this.config,"PATCH",`/v1/scenes/${encodeURIComponent(t.sceneId)}/privacy`,e)}async publishScene(t){return l(this.config,"PATCH",`/v1/scenes/${encodeURIComponent(t)}/privacy`,{status:"public"})}async makeScenePrivate(t){return l(this.config,"PATCH",`/v1/scenes/${encodeURIComponent(t)}/privacy`,{status:"private"})}},y=class{constructor(t){this.config=t}async render(t){let e=t.context,n=e?.theme;if(e?.sceneId&&typeof e.revision=="number"){let o={format:t.target};return n!==void 0&&(o.theme=n),e.showRevision!==void 0&&(o.showRevision=e.showRevision),typeof e.diffFromRevision=="number"&&(o.diff={fromRevision:e.diffFromRevision}),l(this.config,"POST",`/v1/scenes/${e.sceneId}/render`,o)}let r={scene:t.scene,format:t.target};return n!==void 0&&(r.theme=n),l(this.config,"POST","/v1/scenes/render",r)}},v=class{constructor(t){this.config=t}validateScene(t){return l(this.config,"POST","/v1/scenes/validate",t)}},w=class{constructor(t){this.config=t}async normalize(t){return l(this.config,"POST","/v1/scenes/normalize",t)}};var Y="https://api.zindex.ai",x=class extends Error{constructor(){super("ZINDEX_API_KEY is required"),this.name="MissingApiKeyError"}};function b(){let s=process.env.ZINDEX_API_KEY?.trim()||null,t=(process.env.ZINDEX_API_URL?.trim()||Y).replace(/\/$/,""),e={apiUrl:t,apiKey:s},n=s?"authenticated":"anonymous";return{sceneService:s?new g(e):null,renderService:new y(e),validator:new v(e),normalizeService:new w(e),apiUrl:t,mode:n}}import{z as c}from"zod";function N(s,t){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:e})=>{try{let n=await t.createScene({scene:e});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 '${e.scene.id}' already exists. Use dsp_apply_ops to update an existing scene, or choose a different scene ID.`,sceneId:e.scene.id})}],isError:!0};throw n}})}import{z as m}from"zod";function R(s,t){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:m.string(),baseRevision:m.number(),transactionMode:m.enum(["allOrNothing","bestEffort"]).default("allOrNothing"),ops:m.array(m.unknown()),message:m.string().optional().describe("Optional revision message describing this operation batch (like a git commit message)")},async({sceneId:e,baseRevision:n,transactionMode:r,ops:o,message:d})=>{let a={schemaVersion:"0.1",sceneId:e,baseRevision:n,transactionMode:r,ops:o,message:d},i=await t.applyOps({envelope:a});return{content:[{type:"text",text:JSON.stringify({applied:i.applied,revision:i.revision,changedElementIds:i.changedElementIds,diagnostics:i.diagnostics})}]}})}import{z as J}from"zod";function O(s,t){s.tool("dsp_validate_scene","Validate a scene document for structural and semantic correctness. Use this to check your scene before creating it with dsp_create_scene. Does not persist anything.",{scene:J.any()},async({scene:e})=>{let n=await t.validateScene(e);return{content:[{type:"text",text:JSON.stringify(n)}]}})}import{z as f}from"zod";function D(s,t,e){let n=t===null,r=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), MISSING_DIAGRAM_FAMILY (declare diagramFamily on every scene).":"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), and 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).";s.tool("dsp_render_scene",r,{sceneId:f.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:f.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:f.string().default("svg"),theme:f.string().optional().describe("Render theme: clean (default), dark, blueprint, sketch"),showRevision:f.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:f.number().optional().describe("Persisted-scene only. Render visual diff from this revision to current. Added=green, removed=red, modified=amber.")},async({sceneId:o,scene:d,format:a,theme:i,showRevision:H,diffFromRevision:$})=>{if(!o&&!d)return{content:[{type:"text",text:JSON.stringify({error:"Either `sceneId` (persisted scene) or `scene` (inline scene) must be provided."})}],isError:!0};if(o&&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(o&&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(o){let h=await t.getScene(o);if(!h)return{content:[{type:"text",text:JSON.stringify({error:`Scene '${o}' not found`})}],isError:!0};let I=await e.render({scene:h.scene,target:a,context:{sceneId:o,revision:h.revision,theme:i,showRevision:H!==!1,diffFromRevision:$}}),p=I.output,S=(await t.listRevisions(o).catch(()=>null))?.revisions?.length??h.revision;return{content:[{type:"text",text:JSON.stringify({mimeType:p.mimeType,...p.content?{content:p.content}:{},...p.contentBase64?{contentBase64:p.contentBase64}:{},...p.width?{width:p.width}:{},...p.height?{height:p.height}:{},...p.fileName?{fileName:p.fileName}:{},diagnostics:I.diagnostics??[],sceneId:o,revision:h.revision,revisionCount:S,hint:S>1?`Rendered scene '${o}' at revision ${h.revision} (${S} revisions). Use dsp_diff_scene to compare revisions.`:`Rendered scene '${o}' at revision ${h.revision}. Use dsp_apply_ops to make incremental edits (each edit creates a new revision).`})}]}}let A=await e.render({scene:d,target:a,context:{theme:i}}),u=A.output;return{content:[{type:"text",text:JSON.stringify({mimeType:u.mimeType,...u.content?{content:u.content}:{},...u.contentBase64?{contentBase64:u.contentBase64}:{},...u.width?{width:u.width}:{},...u.height?{height:u.height}:{},...u.fileName?{fileName:u.fileName}:{},diagnostics:A.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 E}from"zod";function P(s,t){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:E.string(),revision:E.number().optional(),includeComputed:E.boolean().optional().default(!1)},async({sceneId:e,revision:n,includeComputed:r})=>{let o=await t.getScene(e,n);if(!o){let a=n?`Scene '${e}' revision ${n} not found`:`Scene '${e}' not found`;return{content:[{type:"text",text:JSON.stringify({error:a})}],isError:!0}}let d=r?o.scene:{...o.scene,computed:{layout:{},edgePaths:{},diagnostics:[]}};return{content:[{type:"text",text:JSON.stringify({sceneId:o.sceneId,revision:o.revision,scene:d})}]}})}import{z as K}from"zod";function L(s,t){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).",{scene:K.any()},async({scene:e})=>{let n=await t.normalize(e);return{content:[{type:"text",text:JSON.stringify(n)}]}})}import{z as k}from"zod";function z(s,t){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:k.string(),from:k.number().describe("Starting revision number"),to:k.number().optional().describe("Ending revision number (defaults to current/latest)")},async({sceneId:e,from:n,to:r})=>{try{let o=await t.diffScenes(e,n,r);return{content:[{type:"text",text:JSON.stringify(o)}]}}catch(o){return{content:[{type:"text",text:JSON.stringify({error:o?.message??"Diff failed"})}],isError:!0}}})}import{z as j}from"zod";function M(s,t){s.tool("dsp_list_revisions","List all revisions of a scene with timestamps, messages, and change summaries. Returns a complete changelog.",{sceneId:j.string()},async({sceneId:e})=>{try{let n=await t.listRevisions(e);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 C}from"zod";function U(s,t){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:C.string(),confirm:C.boolean().describe("Must be `true`. Set this only after the human has confirmed they want this scene deleted.")},async({sceneId:e,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 t.deleteScene(e),{content:[{type:"text",text:JSON.stringify({deleted:!0,sceneId:e,soft:!0,note:"Scene is soft-deleted. It is recoverable for 24 hours via dsp_undelete_scene. After that it is permanently removed."})}]}}catch(r){return{content:[{type:"text",text:JSON.stringify({error:r?.message??"Delete failed"})}],isError:!0}}})}import{z as Z}from"zod";function F(s,t){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:Z.string()},async({sceneId:e})=>{try{let n=await t.undeleteScene(e);return{content:[{type:"text",text:JSON.stringify({restored:n?.restored===!0,sceneId:n?.sceneId??e,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 G(s,t){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 e=await t.listRecentlyDeleted();return{content:[{type:"text",text:JSON.stringify(e??{items:[]})}]}}catch(e){return{content:[{type:"text",text:JSON.stringify({error:e?.message??"Failed to list recently-deleted scenes"})}],isError:!0}}})}import{z as T}from"zod";function B(s,t){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:T.string().min(1,"sceneId is required"),problem:T.string().min(1,"problem must be a non-empty description of what's wrong").max(500,"problem must be 500 characters or fewer"),anonymize:T.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:e,problem:n,anonymize:r})=>{try{let o=await t.submitToSupport({sceneId:e,problem:n,anonymize:r===!0});return{content:[{type:"text",text:JSON.stringify({sceneId:o?.sceneId??e,privacyStatus:o?.privacyStatus??"support",supportSubmittedAt:o?.supportSubmittedAt??null,supportAnonymized:o?.supportAnonymized===!0,auditUrl:`https://zindex.ai/playground?id=${encodeURIComponent(e)}`})}]}}catch(o){let d=o?.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 X}from"zod";function V(s,t){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:X.string().min(1,"sceneId is required")},async({sceneId:e})=>{try{let n=await t.publishScene(e),r=n?.sceneId??e;return{content:[{type:"text",text:JSON.stringify({sceneId:r,privacyStatus:n?.privacyStatus??"public",madePublicAt:n?.madePublicAt??null,publicUrl:`https://zindex.ai/s/${encodeURIComponent(r)}`})}]}}catch(n){let r=n?.message??"Publish failed.";return{content:[{type:"text",text:JSON.stringify({error:r,hint:/support/i.test(r)?"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(r)?"Confirm the sceneId is correct + the scene is owned by the API key's workspace.":void 0})}],isError:!0}}})}import{z as Q}from"zod";function q(s,t){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:Q.string().min(1,"sceneId is required")},async({sceneId:e})=>{try{let n=await t.makeScenePrivate(e);return{content:[{type:"text",text:JSON.stringify({sceneId:n?.sceneId??e,privacyStatus:n?.privacyStatus??"private"})}]}}catch(n){let r=n?.message??"Make-private failed.";return{content:[{type:"text",text:JSON.stringify({error:r,hint:/not found/i.test(r)?"Confirm the sceneId is correct + the scene is owned by the API key's workspace.":void 0})}],isError:!0}}})}var _="0.39.2",se=`Zindex MCP server v${_}
|
|
3
3
|
|
|
4
4
|
A thin MCP client for the Zindex API. Exposes tools for the full DSP
|
|
5
5
|
protocol - scene lifecycle (create, apply ops, render), validation,
|
|
@@ -73,7 +73,7 @@ Frames (pools, lanes, containers) also auto-size from their children - omit layo
|
|
|
73
73
|
- dsp_get_scene excludes the computed section (layout bounds, edge paths) by default to save tokens. Pass includeComputed: true only when you need computed layout data.
|
|
74
74
|
- Pass theme to dsp_render_scene: "clean" (default) | "dark" | "blueprint" | "sketch". When you set scene.canvas.background to a dark color (e.g. \`#06080d\`, \`#1a1a22\`) and DO NOT pass theme, the renderer auto-selects "dark" so strokes / labels stay legible against the dark canvas - no need to remember theme: "dark" alongside a dark canvas. Explicit theme: "clean" always wins. scene.palette.background does NOT trigger auto-selection - set canvas.background to express dark-mode intent.
|
|
75
75
|
- KEEP WATERMARKS ENABLED during iteration. The default showRevision: true stamps the scene ID and revision onto the render so you and the user can tell which version of the diagram is being viewed. Only pass showRevision: false when the user explicitly asks for a clean final deliverable, and ideally only on the last render after all edits are complete. Silently stripping the watermark destroys the audit trail.
|
|
76
|
-
- ALWAYS check the diagnostics array in the render response. TEXT_OVERFLOW warnings include { elementId, requiredHeight, availableHeight } - resize the element. CANVAS_AUTO_EXTENDED info diagnostics include { declaredWidth, declaredHeight, width, height, extendedDimensions } and signal that scene.canvas was grown to fit the computed layout (declared canvas dimensions are a minimum, not a hard cap; the engine never silently clips). If you want the declared size to be authoritative for downstream consumers, persist the new dimensions back to your scene via updateScene. CANVAS_CONTENT_OFFSET info diagnostics include { axis, canvasWidth, canvasHeight, contentBounds, horizontalOffsetPx, verticalOffsetPx } - the bounding-box centre of laid-out content deviates more than 10% from the canvas centre on the named axis (e.g. content stranded on the left half of a wide canvas). Fires only when at least one node carries user-supplied coordinates; auto-laid-out scenes are excluded because the engine's centring pass already balances them. Recovery in preference order: (1) switch to layoutStrategy and omit layout from elements so the engine auto-centres; (2) shrink scene.canvas via updateScene to match the actual content bounds; (3) recentre the absolute coordinates by adding (canvasDim - contentDim) / 2 - contentMin to each axis on every element. EDGE_LABEL_SUPPRESSED_REDUNDANT info diagnostics include { suppressedEdgeIds, suppressedLabels } - an edge label that exactly matches a column declared in either endpoint's extensions.columns was auto-promoted to a column-row anchor (the FK edge now terminates at the named column row inside the entity, matching the dbdiagram.io / DBeaver convention; the label is dropped because the column position carries the FK identity). Set edge.style.forceLabel=true via updateEdge to keep a label visible on a specific edge. Agents can also set endpoint.column directly: { from: { elementId: "orders", column: "user_id" }, to: { elementId: "users", column: "id" } }. EDGE_LABEL_SUPPRESSED_FANIN info diagnostics include { suppressedEdgeIds, groups: [{ targetId, label, electedEdgeId, suppressedEdgeIds }] } - 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). The platform renders the label once on the lowest-id edge per group, drops it from the others, AND merges the geometry into one trunk. This is structurally distinct from EDGE_LABEL_SUPPRESSED_REDUNDANT: there's no underlying column - the convergence itself is the redundancy. Set edge.style.forceLabel=true via updateEdge to keep a label on a specific edge, or rename one of the duplicates if the convergence wasn't intentional. Sequence diagrams are exempt because same-label messages are temporally distinct events. EDGE_LABEL_FANIN_BUNDLED info diagnostics include { bundledEdgeIds, groups: [{ targetId, label, electedEdgeId, bundledEdgeIds }] } - 2+ edges share an exact label string AND a target endpoint, but the source nodes are structurally distinct (no shared parent frame, no common single-step predecessor). Typical case is parallel CRUD handlers from different routes writing the same SQL operation type to a shared database cylinder. The geometry merges into one trunk per group so the convergence is visible, but every label and arrowhead is preserved because each edge represents a distinct hop with its own meaning. No fix needed - the diagnostic is purely informational; if you want N edges from distinct sources collapsed into one labelled trunk, place the sources in a shared frame whose children list contains exactly that source set, or wire them through a common single-step predecessor node. EDGE_OBSTACLE_GRAZED info diagnostics include { grazedEdgeIds, clearance } - one or more edges were routed through the degraded-clearance fallback because the router could not find an L/step-path or corridor-graph route that cleared every non-source/non-target node by 16 px. The geometry is still drawn (a graze beats no edge) but the result may visually clip a third-party node. Recovery in preference order: widen scene.canvas, move the obstacle node out of the routing corridor, distinguish or merge the affected edges (a labelled trunk often reads cleaner than N parallel grazing edges), or set style.forceLabel=true on the affected edges if the close routing is intentional. Common trigger: a note or service node positioned in a lane that fan-out edges need to cross when the canvas-side escape corridor is narrower than (N edges \xD7 16 px clearance). LABEL_DUPLICATION_DETECTED warnings include { labelText, sharedEndpoint: { kind, elementId }, edgeIds } and now only fire for the shared-SOURCE case (e.g. one node emitting 3+ edges with identical labels to different targets); shared-target convergence is reported separately via EDGE_LABEL_SUPPRESSED_FANIN or EDGE_LABEL_FANIN_BUNDLED above and does NOT trigger this diagnostic. Distinguish the source-shared duplicates (e.g. "read user" vs "read order") via updateEdge so the diagram is unambiguous, or merge the edges if they represent the same flow. LAYOUT_ABSOLUTE_AT_ORIGIN info diagnostics include { affectedElementIds, allAtOrigin, layoutStrategyPresent } - 1+ nodes declare layout.mode="absolute" with default coordinates {x:0, y:0}, which is incoherent input. When allAtOrigin=true the engine fell back to auto-layout for the whole scene (your layout blocks did nothing); when allAtOrigin=false the named nodes were pinned at (0,0) and likely overlap other elements. layoutStrategyPresent indicates whether the scene also declared layoutStrategy at the document root - when true with allAtOrigin=true, the engine ran the strategy via the all-zero recovery path. The fix is to remove the layout block on those nodes and rely on layoutStrategy at scene level for clean auto-layout - do not author layout.mode="absolute" with placeholder zero coordinates. LAYOUT_STRATEGY_OVERRIDDEN info diagnostics include { strategyAlgorithm } - the scene declares layoutStrategy at the document root AND every node has explicit layout.mode="absolute" coordinates, so the strategy is silently ignored (the engine pins each node to its declared coords and never invokes the strategy). Common cause: agent set layoutStrategy intending auto-layout, then generated explicit absolute layout blocks per element. Pick one: REMOVE the per-element layout blocks to use auto-layout via layoutStrategy, OR REMOVE layoutStrategy if you intend to pin every node by hand. The two are mutually exclusive at the all-elements level. MISSING_DIAGRAM_FAMILY info diagnostics include { allowedValues } - the scene omits the diagramFamily field. Always declare it at the scene-document root (architecture, workflow, entityRelationship, sequence, network, orgchart, uiflow): it gates family-specific behaviour the engine and downstream tooling rely on. Set it via dsp_create_scene at creation time or via updateScene on an existing scene. OUTER_FIELD_NESTED info diagnostics include { nestedFields } - one or more document-root fields (layoutStrategy, diagramFamily, palette) were authored inside scene.scene instead of at the outer document root. The normalizer accepted the nested form as a fallback, but the canonical placement (per scene.schema.json) is the document root alongside elements and schemaVersion. Move the field via updateScene so subsequent reads match the schema.
|
|
76
|
+
- ALWAYS check the diagnostics array in the render response. TEXT_OVERFLOW warnings include { elementId, requiredHeight, availableHeight } - resize the element. CANVAS_AUTO_EXTENDED info diagnostics include { declaredWidth, declaredHeight, width, height, extendedDimensions } and signal that scene.canvas was grown to fit the computed layout (declared canvas dimensions are a minimum, not a hard cap; the engine never silently clips). If you want the declared size to be authoritative for downstream consumers, persist the new dimensions back to your scene via updateScene. CANVAS_AUTO_TIGHTENED info diagnostics include { axis, declaredWidth-or-declaredHeight, width-or-height, contentWidth, contentHeight } and signal that the engine reduced the declared canvas BELOW the agent's value on the named axis. Fires once per tightened axis when every node was auto-laid-out AND the declared dimension exceeded the comfortable centred-content size by more than 30%, capping that axis at content x 1.4 to avoid excessive whitespace. Persist via updateScene, or supply absolute coordinates on any node to opt the scene out of tightening if a larger canvas was intentional. CANVAS_CONTENT_OFFSET info diagnostics include { axis, canvasWidth, canvasHeight, contentBounds, horizontalOffsetPx, verticalOffsetPx } - the bounding-box centre of laid-out content deviates more than 10% from the canvas centre on the named axis (e.g. content stranded on the left half of a wide canvas). Fires only when at least one node carries user-supplied coordinates; auto-laid-out scenes are excluded because the engine's centring pass already balances them. Recovery in preference order: (1) switch to layoutStrategy and omit layout from elements so the engine auto-centres; (2) shrink scene.canvas via updateScene to match the actual content bounds; (3) recentre the absolute coordinates by adding (canvasDim - contentDim) / 2 - contentMin to each axis on every element. EDGE_LABEL_SUPPRESSED_REDUNDANT info diagnostics include { suppressedEdgeIds, suppressedLabels } - an edge label that exactly matches a column declared in either endpoint's extensions.columns was auto-promoted to a column-row anchor (the FK edge now terminates at the named column row inside the entity, matching the dbdiagram.io / DBeaver convention; the label is dropped because the column position carries the FK identity). Set edge.style.forceLabel=true via updateEdge to keep a label visible on a specific edge. Agents can also set endpoint.column directly: { from: { elementId: "orders", column: "user_id" }, to: { elementId: "users", column: "id" } }. EDGE_LABEL_SUPPRESSED_FANIN info diagnostics include { suppressedEdgeIds, groups: [{ targetId, label, electedEdgeId, suppressedEdgeIds }] } - 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). The platform renders the label once on the lowest-id edge per group, drops it from the others, AND merges the geometry into one trunk. This is structurally distinct from EDGE_LABEL_SUPPRESSED_REDUNDANT: there's no underlying column - the convergence itself is the redundancy. Set edge.style.forceLabel=true via updateEdge to keep a label on a specific edge, or rename one of the duplicates if the convergence wasn't intentional. Sequence diagrams are exempt because same-label messages are temporally distinct events. EDGE_LABEL_FANIN_BUNDLED info diagnostics include { bundledEdgeIds, groups: [{ targetId, label, electedEdgeId, bundledEdgeIds }] } - 2+ edges share an exact label string AND a target endpoint, but the source nodes are structurally distinct (no shared parent frame, no common single-step predecessor). Typical case is parallel CRUD handlers from different routes writing the same SQL operation type to a shared database cylinder. The geometry merges into one trunk per group so the convergence is visible, but every label and arrowhead is preserved because each edge represents a distinct hop with its own meaning. No fix needed - the diagnostic is purely informational; if you want N edges from distinct sources collapsed into one labelled trunk, place the sources in a shared frame whose children list contains exactly that source set, or wire them through a common single-step predecessor node. EDGE_OBSTACLE_GRAZED info diagnostics include { grazedEdgeIds, clearance } - one or more edges were routed through the degraded-clearance fallback because the router could not find an L/step-path or corridor-graph route that cleared every non-source/non-target node by 16 px. The geometry is still drawn (a graze beats no edge) but the result may visually clip a third-party node. Recovery in preference order: widen scene.canvas, move the obstacle node out of the routing corridor, distinguish or merge the affected edges (a labelled trunk often reads cleaner than N parallel grazing edges), or set style.forceLabel=true on the affected edges if the close routing is intentional. Common trigger: a note or service node positioned in a lane that fan-out edges need to cross when the canvas-side escape corridor is narrower than (N edges \xD7 16 px clearance). LABEL_DUPLICATION_DETECTED warnings include { labelText, sharedEndpoint: { kind, elementId }, edgeIds } and now only fire for the shared-SOURCE case (e.g. one node emitting 3+ edges with identical labels to different targets); shared-target convergence is reported separately via EDGE_LABEL_SUPPRESSED_FANIN or EDGE_LABEL_FANIN_BUNDLED above and does NOT trigger this diagnostic. Distinguish the source-shared duplicates (e.g. "read user" vs "read order") via updateEdge so the diagram is unambiguous, or merge the edges if they represent the same flow. LAYOUT_ABSOLUTE_AT_ORIGIN info diagnostics include { affectedElementIds, allAtOrigin, layoutStrategyPresent } - 1+ nodes declare layout.mode="absolute" with default coordinates {x:0, y:0}, which is incoherent input. When allAtOrigin=true the engine fell back to auto-layout for the whole scene (your layout blocks did nothing); when allAtOrigin=false the named nodes were pinned at (0,0) and likely overlap other elements. layoutStrategyPresent indicates whether the scene also declared layoutStrategy at the document root - when true with allAtOrigin=true, the engine ran the strategy via the all-zero recovery path. The fix is to remove the layout block on those nodes and rely on layoutStrategy at scene level for clean auto-layout - do not author layout.mode="absolute" with placeholder zero coordinates. LAYOUT_STRATEGY_OVERRIDDEN info diagnostics include { strategyAlgorithm } - the scene declares layoutStrategy at the document root AND every node has explicit layout.mode="absolute" coordinates, so the strategy is silently ignored (the engine pins each node to its declared coords and never invokes the strategy). Common cause: agent set layoutStrategy intending auto-layout, then generated explicit absolute layout blocks per element. Pick one: REMOVE the per-element layout blocks to use auto-layout via layoutStrategy, OR REMOVE layoutStrategy if you intend to pin every node by hand. The two are mutually exclusive at the all-elements level. MISSING_DIAGRAM_FAMILY info diagnostics include { allowedValues } - the scene omits the diagramFamily field. Always declare it at the scene-document root (architecture, workflow, entityRelationship, sequence, network, orgchart, uiflow): it gates family-specific behaviour the engine and downstream tooling rely on. Set it via dsp_create_scene at creation time or via updateScene on an existing scene. OUTER_FIELD_NESTED info diagnostics include { nestedFields } - one or more document-root fields (layoutStrategy, diagramFamily, palette) were authored inside scene.scene instead of at the outer document root. The normalizer accepted the nested form as a fallback, but the canonical placement (per scene.schema.json) is the document root alongside elements and schemaVersion. Move the field via updateScene so subsequent reads match the schema.
|
|
77
77
|
- Use dsp_list_revisions for changelog, dsp_diff_scene to compare revisions, and dsp_render_scene with diffFromRevision for visual diff (added=green, removed=red, modified=amber).
|
|
78
78
|
|
|
79
79
|
## Scene structure
|
|
@@ -232,5 +232,5 @@ The anonymize parameter on dsp_submit_to_support is the agent-layer expression o
|
|
|
232
232
|
- Layout engine details: https://zindex.ai/docs/reference/layout-engine/
|
|
233
233
|
- Element types reference: https://zindex.ai/docs/reference/element-types/
|
|
234
234
|
- Privacy commitments: https://zindex.ai/privacy
|
|
235
|
-
`.trim();function re(s){let t=s?.services??b(),{sceneService:e,renderService:n,validator:r,normalizeService:o,mode:d}=t,a=new te({name:"zindex",version:_},{instructions:oe}),i=0;return O(a,r),i++,L(a,o),i++,D(a,e,n),i++,d==="authenticated"&&e&&(N(a,e),i++,R(a,e),i++,P(a,e),i++,
|
|
235
|
+
`.trim();function re(s){let t=s?.services??b(),{sceneService:e,renderService:n,validator:r,normalizeService:o,mode:d}=t,a=new te({name:"zindex",version:_},{instructions:oe}),i=0;return O(a,r),i++,L(a,o),i++,D(a,e,n),i++,d==="authenticated"&&e&&(N(a,e),i++,R(a,e),i++,P(a,e),i++,z(a,e),i++,M(a,e),i++,U(a,e),i++,F(a,e),i++,G(a,e),i++,B(a,e),i++,V(a,e),i++,q(a,e),i++),{server:a,services:t,toolCount:i}}function ae(){let s=process.argv.slice(2);(s.includes("--help")||s.includes("-h"))&&(process.stdout.write(se),process.exit(0)),(s.includes("--version")||s.includes("-v"))&&(process.stdout.write(`${_}
|
|
236
236
|
`),process.exit(0));let t=b(),{server:e,toolCount:n}=re({services:t}),r=new ne;e.connect(r).then(()=>{let o=W(import.meta.url),d=t.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${_} running on stdio [connected to ${t.apiUrl}, ${d}]`),console.error(` Binary: ${o}`)})}function ie(){let s=process.argv[1];if(s===void 0)return!1;let t=W(import.meta.url);if(s===t)return!0;try{return ee(s)===t}catch{return!1}}ie()&&ae();export{w as HttpNormalizeService,y as HttpRenderService,g as HttpSceneService,v as HttpValidator,x as MissingApiKeyError,re as createMcpServer,b as createServices};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zindex-ai/mcp",
|
|
3
|
-
"version": "0.39.
|
|
3
|
+
"version": "0.39.2",
|
|
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",
|