feat: add create_from_mermaid MCP tool and frontend integration with test button

This commit is contained in:
francisco
2025-11-01 13:58:22 -03:00
parent c1cecf282c
commit f8c003db71
2 changed files with 127 additions and 0 deletions
+38
View File
@@ -7,6 +7,7 @@ import {
ExcalidrawElement ExcalidrawElement
} from '@excalidraw/excalidraw' } from '@excalidraw/excalidraw'
import '@excalidraw/excalidraw/index.css' import '@excalidraw/excalidraw/index.css'
import { convertMermaidToExcalidraw, DEFAULT_MERMAID_CONFIG } from './utils/mermaidConverter'
// Type definitions // Type definitions
interface ServerElement { interface ServerElement {
@@ -399,6 +400,42 @@ function App(): JSX.Element {
} }
} }
const handleMermaidTest = async (): Promise<void> => {
if (!excalidrawAPI) return
const testMermaid = `graph TD
A[Start] --> B{Decision}
B -->|Yes| C[Do Something]
B -->|No| D[Do Something Else]
C --> E[End]
D --> E`
try {
const result = await convertMermaidToExcalidraw(testMermaid, DEFAULT_MERMAID_CONFIG)
if (result.error) {
console.error('Mermaid conversion error:', result.error)
return
}
if (result.elements && result.elements.length > 0) {
const convertedElements = convertToExcalidrawElements(result.elements, { regenerateIds: false })
excalidrawAPI.updateScene({
elements: convertedElements,
captureUpdate: CaptureUpdateAction.IMMEDIATELY
})
if (result.files) {
excalidrawAPI.addFiles(Object.values(result.files))
}
console.log('Mermaid diagram converted successfully:', result.elements.length, 'elements')
}
} catch (error) {
console.error('Error converting Mermaid diagram:', error)
}
}
return ( return (
<div className="app"> <div className="app">
{/* Header */} {/* Header */}
@@ -437,6 +474,7 @@ function App(): JSX.Element {
</div> </div>
</div> </div>
<button className="btn-secondary" onClick={handleMermaidTest}>Test Mermaid</button>
<button className="btn-secondary" onClick={clearCanvas}>Clear Canvas</button> <button className="btn-secondary" onClick={clearCanvas}>Clear Canvas</button>
</div> </div>
</div> </div>
+89
View File
@@ -414,6 +414,41 @@ const tools: Tool[] = [
required: ['elementIds'] required: ['elementIds']
} }
}, },
{
name: 'create_from_mermaid',
description: 'Convert a Mermaid diagram to Excalidraw elements and render them on the canvas',
inputSchema: {
type: 'object',
properties: {
mermaidDiagram: {
type: 'string',
description: 'The Mermaid diagram definition (e.g., "graph TD; A-->B; B-->C;")'
},
config: {
type: 'object',
description: 'Optional Mermaid configuration',
properties: {
startOnLoad: { type: 'boolean' },
flowchart: {
type: 'object',
properties: {
curve: { type: 'string', enum: ['linear', 'basis'] }
}
},
themeVariables: {
type: 'object',
properties: {
fontSize: { type: 'string' }
}
},
maxEdges: { type: 'number' },
maxTextSize: { type: 'number' }
}
}
},
required: ['mermaidDiagram']
}
},
{ {
name: 'batch_create_elements', name: 'batch_create_elements',
description: 'Create multiple Excalidraw elements at once - ideal for complex diagrams', description: 'Create multiple Excalidraw elements at once - ideal for complex diagrams',
@@ -822,6 +857,60 @@ server.setRequestHandler(CallToolRequestSchema, async (request: CallToolRequest)
} }
} }
case 'create_from_mermaid': {
const params = z.object({
mermaidDiagram: z.string(),
config: z.object({
startOnLoad: z.boolean().optional(),
flowchart: z.object({
curve: z.enum(['linear', 'basis']).optional()
}).optional(),
themeVariables: z.object({
fontSize: z.string().optional()
}).optional(),
maxEdges: z.number().optional(),
maxTextSize: z.number().optional()
}).optional()
}).parse(args);
logger.info('Creating Excalidraw elements from Mermaid diagram via MCP', {
diagramLength: params.mermaidDiagram.length,
hasConfig: !!params.config
});
try {
// Send the Mermaid diagram to the frontend via the API
// The frontend will use mermaid-to-excalidraw to convert it
const response = await fetch(`${EXPRESS_SERVER_URL}/api/elements/from-mermaid`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
mermaidDiagram: params.mermaidDiagram,
config: params.config
})
});
if (!response.ok) {
throw new Error(`HTTP server error: ${response.status} ${response.statusText}`);
}
const result = await response.json() as ApiResponse;
logger.info('Mermaid diagram sent to frontend for conversion', {
success: result.success
});
return {
content: [{
type: 'text',
text: `Mermaid diagram sent for conversion!\n\n${JSON.stringify(result, null, 2)}\n\n⚠️ Note: The actual conversion happens in the frontend canvas with DOM access. Open the canvas at ${EXPRESS_SERVER_URL} to see the diagram rendered.`
}]
};
} catch (error) {
throw new Error(`Failed to process Mermaid diagram: ${(error as Error).message}`);
}
}
case 'batch_create_elements': { case 'batch_create_elements': {
const params = z.object({ elements: z.array(ElementSchema) }).parse(args); const params = z.object({ elements: z.array(ElementSchema) }).parse(args);
logger.info('Batch creating elements via MCP', { count: params.elements.length }); logger.info('Batch creating elements via MCP', { count: params.elements.length });