feat: add create_from_mermaid MCP tool and frontend integration with test button
This commit is contained in:
@@ -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>
|
||||||
|
|||||||
@@ -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 });
|
||||||
|
|||||||
Reference in New Issue
Block a user