@zindex-ai/mcp 0.39.0 → 0.39.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -0
- package/dist/index.js +38 -5
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -67,6 +67,9 @@ Both work without an API key. Running `npx @zindex-ai/mcp` with no key starts th
|
|
|
67
67
|
| `dsp_delete_scene` | Soft-delete a persisted scene. Recoverable for 24 hours via `dsp_undelete_scene`. Requires `confirm: true`. The agent must confirm with the human before calling. |
|
|
68
68
|
| `dsp_undelete_scene` | Restore a soft-deleted scene during the 24-hour grace window. |
|
|
69
69
|
| `dsp_list_recently_deleted` | Enumerate scenes the caller has soft-deleted within the past 24 hours. Used to look up the sceneId when restoring a scene from a previous session. |
|
|
70
|
+
| `dsp_submit_to_support` | Submit a persisted scene to Zindex support so admins can view it (sets privacy to `support`). Optional `anonymize` flag replaces labels + IDs with same-width random characters. The agent must confirm with the human before calling. |
|
|
71
|
+
| `dsp_publish_scene` | Make a persisted scene publicly addressable at `https://zindex.ai/s/<sceneId>` (sets privacy to `public`). Anyone with the URL - including search engines and AI crawlers - can view the rendered scene and its source JSON. The agent must confirm with the human before calling. |
|
|
72
|
+
| `dsp_make_scene_private` | Revert a scene to `private`. The public URL stops resolving immediately. Also the canonical "undo" for `dsp_submit_to_support`. |
|
|
70
73
|
|
|
71
74
|
The MCP server is self-describing - it emits onboarding instructions and core workflow rules via the MCP `instructions` field on connect. Compatible hosts pass this to the connecting agent automatically, so no per-agent configuration is required beyond the API key.
|
|
72
75
|
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{fileURLToPath as G}from"node:url";import{realpathSync as H}from"node:fs";import{McpServer as j}from"@modelcontextprotocol/sdk/server/mcp.js";import{StdioServerTransport as J}from"@modelcontextprotocol/sdk/server/stdio.js";async function l(o,t,e,n){let c=`${o.apiUrl}${e}`,s={"Content-Type":"application/json"};o.apiKey&&(s.Authorization=`Bearer ${o.apiKey}`);let d={method:t,headers:s};n&&(d.body=JSON.stringify(n));let r=await fetch(c,d);if(r.status===204)return{ok:!0};let a=await r.json();if(!r.ok&&!a.applied){if(r.status===409)return a;throw new Error(a.error??`HTTP ${r.status}`)}return a}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 c=n!==void 0?`?from=${e}&to=${n}`:`?from=${e}`;return l(this.config,"GET",`/v1/scenes/${t}/diff${c}`)}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")}},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 s={format:t.target};return n!==void 0&&(s.theme=n),e.showRevision!==void 0&&(s.showRevision=e.showRevision),typeof e.diffFromRevision=="number"&&(s.diff={fromRevision:e.diffFromRevision}),l(this.config,"POST",`/v1/scenes/${e.sceneId}/render`,s)}let c={scene:t.scene,format:t.target};return n!==void 0&&(c.theme=n),l(this.config,"POST","/v1/scenes/render",c)}},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 V="https://api.zindex.ai",x=class extends Error{constructor(){super("ZINDEX_API_KEY is required"),this.name="MissingApiKeyError"}};function b(){let o=process.env.ZINDEX_API_KEY?.trim()||null,t=(process.env.ZINDEX_API_URL?.trim()||V).replace(/\/$/,""),e={apiUrl:t,apiKey:o},n=o?"authenticated":"anonymous";return{sceneService:o?new g(e):null,renderService:new y(e),validator:new v(e),normalizeService:new w(e),apiUrl:t,mode:n}}import{z as i}from"zod";function N(o,t){o.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:i.object({schemaVersion:i.string(),diagramFamily:i.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:i.object({algorithm:i.enum(["hierarchical","flow","grid","forceDirected"]),direction:i.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:i.object({id:i.string(),title:i.string().optional(),units:i.string().default("px"),canvas:i.object({width:i.number(),height:i.number(),background:i.string().default("#ffffff")})}).passthrough(),elements:i.array(i.unknown()).default([]),styles:i.record(i.any()).optional(),assets:i.record(i.any()).optional(),constraints:i.array(i.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 I(o,t){o.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:c,ops:s,message:d})=>{let r={schemaVersion:"0.1",sceneId:e,baseRevision:n,transactionMode:c,ops:s,message:d},a=await t.applyOps({envelope:r});return{content:[{type:"text",text:JSON.stringify({applied:a.applied,revision:a.revision,changedElementIds:a.changedElementIds,diagnostics:a.diagnostics})}]}})}import{z as W}from"zod";function R(o,t){o.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:W.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(o,t,e){let n=t===null,c=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).";o.tool("dsp_render_scene",c,{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:s,scene:d,format:r,theme:a,showRevision:B,diffFromRevision:q})=>{if(!s&&!d)return{content:[{type:"text",text:JSON.stringify({error:"Either `sceneId` (persisted scene) or `scene` (inline scene) must be provided."})}],isError:!0};if(s&&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(s&&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(s){let p=await t.getScene(s);if(!p)return{content:[{type:"text",text:JSON.stringify({error:`Scene '${s}' not found`})}],isError:!0};let A=await e.render({scene:p.scene,target:r,context:{sceneId:s,revision:p.revision,theme:a,showRevision:B!==!1,diffFromRevision:q}}),h=A.output,S=(await t.listRevisions(s).catch(()=>null))?.revisions?.length??p.revision;return{content:[{type:"text",text:JSON.stringify({mimeType:h.mimeType,...h.content?{content:h.content}:{},...h.contentBase64?{contentBase64:h.contentBase64}:{},...h.width?{width:h.width}:{},...h.height?{height:h.height}:{},...h.fileName?{fileName:h.fileName}:{},diagnostics:A.diagnostics??[],sceneId:s,revision:p.revision,revisionCount:S,hint:S>1?`Rendered scene '${s}' at revision ${p.revision} (${S} revisions). Use dsp_diff_scene to compare revisions.`:`Rendered scene '${s}' at revision ${p.revision}. Use dsp_apply_ops to make incremental edits (each edit creates a new revision).`})}]}}let T=await e.render({scene:d,target:r,context:{theme:a}}),u=T.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:T.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 O(o,t){o.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:c})=>{let s=await t.getScene(e,n);if(!s){let r=n?`Scene '${e}' revision ${n} not found`:`Scene '${e}' not found`;return{content:[{type:"text",text:JSON.stringify({error:r})}],isError:!0}}let d=c?s.scene:{...s.scene,computed:{layout:{},edgePaths:{},diagnostics:[]}};return{content:[{type:"text",text:JSON.stringify({sceneId:s.sceneId,revision:s.revision,scene:d})}]}})}import{z as K}from"zod";function L(o,t){o.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 P(o,t){o.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:c})=>{try{let s=await t.diffScenes(e,n,c);return{content:[{type:"text",text:JSON.stringify(s)}]}}catch(s){return{content:[{type:"text",text:JSON.stringify({error:s?.message??"Diff failed"})}],isError:!0}}})}import{z as $}from"zod";function M(o,t){o.tool("dsp_list_revisions","List all revisions of a scene with timestamps, messages, and change summaries. Returns a complete changelog.",{sceneId:$.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 z(o,t){o.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(c){return{content:[{type:"text",text:JSON.stringify({error:c?.message??"Delete failed"})}],isError:!0}}})}import{z as Y}from"zod";function U(o,t){o.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:Y.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 F(o,t){o.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}}})}var _="0.39.0",X=`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:$,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${_}
|
|
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,14 +18,14 @@ 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
|
+
`,oe=`
|
|
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
|
|
25
25
|
|
|
26
26
|
This MCP server runs in one of two modes depending on whether ZINDEX_API_KEY is set in the host config:
|
|
27
27
|
|
|
28
|
-
- **Authenticated mode** (key set): the full MCP tool set is registered - dsp_create_scene, dsp_apply_ops, dsp_get_scene, dsp_diff_scene, dsp_list_revisions, dsp_delete_scene, dsp_undelete_scene, dsp_list_recently_deleted alongside the public-endpoint tools (dsp_validate_scene, dsp_normalize_scene, dsp_render_scene). This is the recommended setup.
|
|
28
|
+
- **Authenticated mode** (key set): the full MCP tool set is registered - dsp_create_scene, dsp_apply_ops, dsp_get_scene, dsp_diff_scene, dsp_list_revisions, dsp_delete_scene, dsp_undelete_scene, dsp_list_recently_deleted, dsp_submit_to_support, dsp_publish_scene, dsp_make_scene_private alongside the public-endpoint tools (dsp_validate_scene, dsp_normalize_scene, dsp_render_scene). This is the recommended setup.
|
|
29
29
|
- **Anonymous mode** (no key): only the public-endpoint tools - dsp_validate_scene, dsp_normalize_scene, and dsp_render_scene (inline-scene form only) - are registered. Use this to demo the engine, render one-off diagrams, or feasibility-test the platform before the user has a key. Tell the user to create a free key at https://zindex.ai/signup to unlock persistence (sceneIds, revisions, diff, the full tool set).
|
|
30
30
|
|
|
31
31
|
Call tools/list at connect time to know which mode you are in. The presence of dsp_create_scene means authenticated mode; if it is absent (and you only see dsp_validate_scene, dsp_normalize_scene, dsp_render_scene), you are in anonymous mode.
|
|
@@ -47,6 +47,8 @@ This gives you: revision history, incremental edits without regenerating the ful
|
|
|
47
47
|
|
|
48
48
|
Tell the user explicitly when you are in anonymous mode and that ZINDEX_API_KEY (free key at https://zindex.ai/signup) unlocks persisted scenes, sceneIds, revisions, visual diff, and the full MCP tool set. Do not pretend you have persistence when you do not - the user needs to know they have to set the key to come back to the same scene later.
|
|
49
49
|
|
|
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
|
+
|
|
50
52
|
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.
|
|
51
53
|
|
|
52
54
|
dsp_validate_scene and dsp_normalize_scene work in both modes for quick checks. They do not persist anything.
|
|
@@ -190,6 +192,36 @@ If you realize you deleted the wrong scene, call dsp_undelete_scene immediately
|
|
|
190
192
|
|
|
191
193
|
If the human asks to restore a scene from an earlier session but does not remember the sceneId, call dsp_list_recently_deleted to enumerate scenes still within the 24-hour grace window. Read the titles back to the human, then call dsp_undelete_scene on the one they confirm. Scenes outside the 24-hour window have been hard-deleted by the scheduled cron and cannot be recovered.
|
|
192
194
|
|
|
195
|
+
## Submitting to Zindex support
|
|
196
|
+
|
|
197
|
+
dsp_submit_to_support sets a persisted scene's privacy state to 'support' so Zindex admins can view it to diagnose a rendering, validation, or layout issue. Use it ONLY after you have tried to resolve the issue with the tools available - normalize, validate, applyOps, render with theme variation, etc. - and the issue persists.
|
|
198
|
+
|
|
199
|
+
BEFORE calling dsp_submit_to_support:
|
|
200
|
+
1. Confirm with the human that they want to expose this scene to Zindex support. Repeat back which scene (by id and any known title) you're about to submit.
|
|
201
|
+
2. Wait for their explicit confirmation, then call the tool.
|
|
202
|
+
|
|
203
|
+
The \`anonymize\` parameter controls whether labels + IDs are replaced with same-width random characters before admins see the scene. Visual structure is preserved either way - admins can diagnose layout / routing / clipping issues without seeing your actual content. Default is false to match the playground's opt-in semantics. **When acting without the human present, default to anonymize: true** - it is the right safety choice when there is no human in the loop to consent to raw labels being visible. The human can withdraw the submission at any time from the playground; if they want raw labels exposed they will re-submit with anonymize off.
|
|
204
|
+
|
|
205
|
+
After the tool returns, give the human the auditUrl (a deep link to the playground for that scene) so they can monitor the submission state + withdraw if they change their mind. Every admin view is recorded in the customer's audit log.
|
|
206
|
+
|
|
207
|
+
## Publishing a scene publicly
|
|
208
|
+
|
|
209
|
+
dsp_publish_scene makes a persisted scene reachable at https://zindex.ai/s/<sceneId> for ANYONE with the URL - search engines, AI crawlers, and any human who guesses or is sent the link. The scene's rendered SVG is visible on the page and the source JSON is fetchable without authentication via GET /v1/public/scenes/:id. This is the only MCP tool that exposes customer content to the public Internet.
|
|
210
|
+
|
|
211
|
+
BEFORE calling dsp_publish_scene:
|
|
212
|
+
1. Confirm with the human that they want this scene exposed publicly. Repeat back which scene (by id and any known title) you're about to publish AND that the source JSON will be fetchable without authentication.
|
|
213
|
+
2. Wait for their explicit confirmation, then call the tool.
|
|
214
|
+
|
|
215
|
+
The response includes the publicUrl. Share it back with the human so they can verify what visitors see + share the link.
|
|
216
|
+
|
|
217
|
+
dsp_make_scene_private reverts the scene: the public URL stops resolving immediately (the public read endpoint sets Cache-Control: no-store, so revocation propagates without a TTL wait). The scene document itself is unchanged - the owner can re-publish later. No confirmation gate; making a scene private strictly reduces exposure. dsp_make_scene_private is also the canonical "undo" for a dsp_submit_to_support call - calling it on a scene currently shared with support withdraws the submission and returns the scene to private.
|
|
218
|
+
|
|
219
|
+
The API rejects cross-state support <-> public transitions with a 400; the customer (or agent) must go via private first. The tool surfaces the API error verbatim with a hint pointing at the path.
|
|
220
|
+
|
|
221
|
+
Visitors to a public scene see an in-page "Report this scene" link. Reports go to the Zindex moderation queue. If a scene violates our hosted-content policies, Zindex can disable its public URL and email the owner the reason verbatim. The scene itself stays in the workspace and the owner can re-publish if they address the issue. See https://zindex.ai/terms (Section 5: Publicly-hosted scenes) for the full policy.
|
|
222
|
+
|
|
223
|
+
The anonymize parameter on dsp_submit_to_support is the agent-layer expression of Zindex's privacy posture: scene content is encrypted at rest, there are no LLM subprocessors in the stack, anonymize-before-submit is offered by default when no human is present, and deletion is atomic. Full policy at https://zindex.ai/privacy.
|
|
224
|
+
|
|
193
225
|
## References
|
|
194
226
|
|
|
195
227
|
- Canonical agent front door (Accept: text/markdown): https://zindex.ai/
|
|
@@ -199,5 +231,6 @@ If the human asks to restore a scene from an earlier session but does not rememb
|
|
|
199
231
|
- Per-example agent resources: every example at /examples/<slug> exposes /examples/<slug>.scene.json (canonical scene), /examples/<slug>.ops.json (typed-operation envelope that builds the scene), /examples/<slug>.workflow.json (agent workflow recipe with steps + MCP/HTTP hints), /examples/<slug>.diff.json (sample dsp_diff_scene response demonstrating revision evolution), /examples/<slug>.github-actions.yml (runnable GitHub Actions workflow for the CI/CD recipe), /examples/<slug>.svg (rendered diagram), and /examples/<slug>.md (markdown summary). Two showcase examples (er-diagram-from-migrations, pr-architecture-diff) additionally expose /examples/<slug>.before.svg for the side-by-side revision-diff display. Fetch these directly rather than scraping HTML.
|
|
200
232
|
- Layout engine details: https://zindex.ai/docs/reference/layout-engine/
|
|
201
233
|
- Element types reference: https://zindex.ai/docs/reference/element-types/
|
|
202
|
-
|
|
203
|
-
|
|
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++,M(a,e),i++,z(a,e),i++,U(a,e),i++,F(a,e),i++,G(a,e),i++,B(a,e),i++,q(a,e),i++,V(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
|
+
`),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,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zindex-ai/mcp",
|
|
3
|
-
"version": "0.39.
|
|
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) 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.",
|
|
3
|
+
"version": "0.39.1",
|
|
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",
|
|
7
7
|
"bin": {
|