@zindex-ai/mcp 0.39.2 → 0.39.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +14 -4
- 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: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${_}
|
|
2
|
+
import{fileURLToPath as Y}from"node:url";import{realpathSync as se}from"node:fs";import{McpServer as re}from"@modelcontextprotocol/sdk/server/mcp.js";import{StdioServerTransport as oe}from"@modelcontextprotocol/sdk/server/stdio.js";async function l(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 h=new Error(a.error);throw h.code="PLAN_LIMIT_EXCEEDED",h.planLimit=a,h}throw new Error(a.error??`HTTP ${i.status}`)}return a}var v=class{constructor(e){this.config=e}async createScene(e){return l(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 l(this.config,"GET",n)}catch{return null}}async applyOps(e){let{envelope:t}=e;return l(this.config,"POST",`/v1/scenes/${t.sceneId}/applyOps`,t)}async listRevisions(e){return l(this.config,"GET",`/v1/scenes/${e}/revisions`)}async diffScenes(e,t,n){let o=n!==void 0?`?from=${t}&to=${n}`:`?from=${t}`;return l(this.config,"GET",`/v1/scenes/${e}/diff${o}`)}async deleteScene(e){return l(this.config,"DELETE",`/v1/scenes/${e}?confirm=true`)}async undeleteScene(e){return l(this.config,"POST",`/v1/scenes/${e}/undelete`)}async listRecentlyDeleted(){return l(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),l(this.config,"PATCH",`/v1/scenes/${encodeURIComponent(e.sceneId)}/privacy`,t)}async publishScene(e){return l(this.config,"PATCH",`/v1/scenes/${encodeURIComponent(e)}/privacy`,{status:"public"})}async makeScenePrivate(e){return l(this.config,"PATCH",`/v1/scenes/${encodeURIComponent(e)}/privacy`,{status:"private"})}},w=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}),l(this.config,"POST",`/v1/scenes/${t.sceneId}/render`,r)}let o={scene:e.scene,format:e.target};return n!==void 0&&(o.theme=n),l(this.config,"POST","/v1/scenes/render",o)}},b=class{constructor(e){this.config=e}validateScene(e){return l(this.config,"POST","/v1/scenes/validate",e)}},_=class{constructor(e){this.config=e}async normalize(e){return l(this.config,"POST","/v1/scenes/normalize",e)}};var j="https://api.zindex.ai",T=class extends Error{constructor(){super("ZINDEX_API_KEY is required"),this.name="MissingApiKeyError"}};function S(){let s=process.env.ZINDEX_API_KEY?.trim()||null,e=(process.env.ZINDEX_API_URL?.trim()||j).replace(/\/$/,""),t={apiUrl:e,apiKey:s},n=s?"authenticated":"anonymous";return{sceneService:s?new v(t):null,renderService:new w(t),validator:new b(t),normalizeService:new _(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 P(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 g}from"zod";function L(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:g.string(),baseRevision:g.number(),transactionMode:g.enum(["allOrNothing","bestEffort"]).default("allOrNothing"),ops:g.array(g.unknown()),message:g.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 h=f(a);if(h)return h;throw a}})}import{z as Z}from"zod";function M(s,e){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:Z.any()},async({scene:t})=>{let n=await e.validateScene(t);return{content:[{type:"text",text:JSON.stringify(n)}]}})}import{z as y}from"zod";function z(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), 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",o,{sceneId:y.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:y.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:y.string().default("svg"),theme:y.string().optional().describe("Render theme: clean (default), dark, blueprint, sketch"),showRevision:y.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:y.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:h,diffFromRevision:K})=>{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 m=await e.getScene(r);if(!m)return{content:[{type:"text",text:JSON.stringify({error:`Scene '${r}' not found`})}],isError:!0};let E;try{E=await t.render({scene:m.scene,target:i,context:{sceneId:r,revision:m.revision,theme:a,showRevision:h!==!1,diffFromRevision:K}})}catch(D){let O=f(D);if(O)return O;throw D}let p=E.output,k=(await e.listRevisions(r).catch(()=>null))?.revisions?.length??m.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:E.diagnostics??[],sceneId:r,revision:m.revision,revisionCount:k,hint:k>1?`Rendered scene '${r}' at revision ${m.revision} (${k} revisions). Use dsp_diff_scene to compare revisions.`:`Rendered scene '${r}' at revision ${m.revision}. Use dsp_apply_ops to make incremental edits (each edit creates a new revision).`})}]}}let R=await t.render({scene:d,target:i,context:{theme:a}}),u=R.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:R.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 A}from"zod";function C(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:A.string(),revision:A.number().optional(),includeComputed:A.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 X}from"zod";function U(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).",{scene:X.any()},async({scene:t})=>{let n=await e.normalize(t);return{content:[{type:"text",text:JSON.stringify(n)}]}})}import{z as I}from"zod";function F(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:I.string(),from:I.number().describe("Starting revision number"),to:I.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 Q}from"zod";function G(s,e){s.tool("dsp_list_revisions","List all revisions of a scene with timestamps, messages, and change summaries. Returns a complete changelog.",{sceneId:Q.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 B}from"zod";function V(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:B.string(),confirm:B.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 ee}from"zod";function W(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:ee.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 q(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 N}from"zod";function H(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:N.string().min(1,"sceneId is required"),problem:N.string().min(1,"problem must be a non-empty description of what's wrong").max(500,"problem must be 500 characters or fewer"),anonymize:N.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 te}from"zod";function $(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:te.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 ne}from"zod";function J(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:ne.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 x="0.39.2",ae=`Zindex MCP server v${x}
|
|
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,
|
|
@@ -18,7 +18,7 @@ Configuration (via environment variables):
|
|
|
18
18
|
ZINDEX_API_URL Optional. Defaults to https://api.zindex.ai
|
|
19
19
|
|
|
20
20
|
Setup guide: https://zindex.ai/docs/getting-started/mcp-setup/
|
|
21
|
-
`,
|
|
21
|
+
`,ie=`
|
|
22
22
|
Zindex is agent-native diagram state infrastructure - a stateful diagram runtime for AI agents. Create architecture diagrams, ERDs, workflows, sequence diagrams, org charts, and topology maps from structured data, then patch, validate, diff, and render them as systems change. Not a whiteboard. Not a Mermaid clone. A database-like backend for diagrams with stable IDs, typed operations, semantic validation, immutable revisions, auto-layout, and SVG/PNG rendering. You describe diagrams as structured scenes and Zindex computes positions, routes edges, places labels, and renders to SVG/PNG.
|
|
23
23
|
|
|
24
24
|
## Two modes
|
|
@@ -49,6 +49,16 @@ Tell the user explicitly when you are in anonymous mode and that ZINDEX_API_KEY
|
|
|
49
49
|
|
|
50
50
|
If a write tool returns HTTP 403 with error "email_not_verified", surface the response message to the user and point them at https://zindex.ai/verify-email. Read tools and the public stateless tools are never gated.
|
|
51
51
|
|
|
52
|
+
## Plan limits (HTTP 403 PLAN_LIMIT_EXCEEDED)
|
|
53
|
+
|
|
54
|
+
The Zindex API enforces per-plan caps on persisted scenes, monthly diagram updates, monthly renders, and API keys. When a cap is reached, the affected tool returns a structured error result whose JSON content has shape:
|
|
55
|
+
|
|
56
|
+
{ "error": "Plan limit reached on the free plan: 50 of 50 persisted scenes. Free a slot by deleting an existing scene, or upgrade your plan at https://zindex.ai/pricing.", "code": "PLAN_LIMIT_EXCEEDED", "limit": "scenes" | "updatesPerMonth" | "rendersPerMonth" | "apiKeys", "current": <int>, "maximumAllowed": <int>, "plan": "free" | "builder" | "growth" | "enterprise", "upgradeUrl": "https://zindex.ai/pricing" }
|
|
57
|
+
|
|
58
|
+
The error string is self-contained - it includes the plan slug, the count, and the upgrade URL inline so you can relay it verbatim to the user. The structured fields let you branch programmatically: tell the user "you're at 50 of 50 persisted scenes on the free plan; delete one or upgrade at https://zindex.ai/pricing" rather than guessing. Do not retry the same call - the cap is sticky until the user frees capacity (delete a scene / revoke a key) or upgrades. Monthly counters reset at the start of each calendar month.
|
|
59
|
+
|
|
60
|
+
Affected tools: dsp_create_scene (scenes cap), dsp_apply_ops (updatesPerMonth cap), dsp_render_scene with sceneId (rendersPerMonth cap). The stateless dsp_render_scene with inline scene, dsp_validate_scene, and dsp_normalize_scene are public and never gated.
|
|
61
|
+
|
|
52
62
|
NEVER hand-edit the rendered SVG or PNG file after render. The rendered output is a throwaway projection - edits to it are lost on the next render, cannot be diffed, cannot be audited, and defeat the entire scene/revision model. If the render isn't what you want, apply ops to the scene (authenticated mode) or rebuild and re-render the scene literal (anonymous mode). The scene is the source of truth; the rendered file is disposable.
|
|
53
63
|
|
|
54
64
|
dsp_validate_scene and dsp_normalize_scene work in both modes for quick checks. They do not persist anything.
|
|
@@ -232,5 +242,5 @@ The anonymize parameter on dsp_submit_to_support is the agent-layer expression o
|
|
|
232
242
|
- Layout engine details: https://zindex.ai/docs/reference/layout-engine/
|
|
233
243
|
- Element types reference: https://zindex.ai/docs/reference/element-types/
|
|
234
244
|
- Privacy commitments: https://zindex.ai/privacy
|
|
235
|
-
`.trim();function
|
|
236
|
-
`),process.exit(0));let
|
|
245
|
+
`.trim();function ce(s){let e=s?.services??S(),{sceneService:t,renderService:n,validator:o,normalizeService:r,mode:d}=e,i=new re({name:"zindex",version:x},{instructions:ie}),a=0;return M(i,o),a++,U(i,r),a++,z(i,t,n),a++,d==="authenticated"&&t&&(P(i,t),a++,L(i,t),a++,C(i,t),a++,F(i,t),a++,G(i,t),a++,V(i,t),a++,W(i,t),a++,q(i,t),a++,H(i,t),a++,$(i,t),a++,J(i,t),a++),{server:i,services:e,toolCount:a}}function de(){let s=process.argv.slice(2);(s.includes("--help")||s.includes("-h"))&&(process.stdout.write(ae),process.exit(0)),(s.includes("--version")||s.includes("-v"))&&(process.stdout.write(`${x}
|
|
246
|
+
`),process.exit(0));let e=S(),{server:t,toolCount:n}=ce({services:e}),o=new oe;t.connect(o).then(()=>{let r=Y(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${x} running on stdio [connected to ${e.apiUrl}, ${d}]`),console.error(` Binary: ${r}`)})}function le(){let s=process.argv[1];if(s===void 0)return!1;let e=Y(import.meta.url);if(s===e)return!0;try{return se(s)===e}catch{return!1}}le()&&de();export{_ as HttpNormalizeService,w as HttpRenderService,v as HttpSceneService,b as HttpValidator,T as MissingApiKeyError,ce as createMcpServer,S 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.3",
|
|
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",
|