Skip to main content
Glama
index.ts8.39 kB
#!/usr/bin/env node import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { CallToolRequestSchema, ListToolsRequestSchema, Tool, } from "@modelcontextprotocol/sdk/types.js"; import { exec } from "child_process"; import { promisify } from "util"; import { writeFile, readFile, unlink } from "fs/promises"; import { tmpdir } from "os"; import { join } from "path"; const execAsync = promisify(exec); // PlantUML JAR path - can be configured via environment variable const PLANTUML_JAR = join(__dirname, "..", "plantuml.jar"); interface GenerateDiagramArgs { source: string; format?: "png" | "svg" | "txt" | "utxt" | "eps" | "latex" | "pdf"; darkMode?: boolean; filename?: string; } interface CheckSyntaxArgs { source: string; } interface ExtractSourceArgs { filePath: string; } const tools: Tool[] = [ { name: "check_syntax", description: "Check PlantUML diagram syntax without generating images. Returns syntax errors if any.", inputSchema: { type: "object", properties: { source: { type: "string", description: "PlantUML source code to check", }, }, required: ["source"], }, }, { name: "extract_source", description: "Extract embedded PlantUML source from PNG or SVG metadata.", inputSchema: { type: "object", properties: { filePath: { type: "string", description: "Path to the PNG or SVG file", }, }, required: ["filePath"], }, }, { name: "get_plantuml_version", description: "Get PlantUML and Java version information.", inputSchema: { type: "object", properties: {}, }, }, { name: "generate_diagram", description: "Generate a diagram from PlantUML source code. Returns the diagram as base64-encoded data and optionally saves to a file.", inputSchema: { type: "object", properties: { source: { type: "string", description: "PlantUML source code for the diagram", }, format: { type: "string", enum: ["png", "svg", "txt", "utxt", "eps", "latex", "pdf"], description: "Output format for the diagram (default: png)", default: "png", }, darkMode: { type: "boolean", description: "Render diagram in dark mode", default: false, }, filename: { type: "string", description: "Optional output filename path to save the diagram to disk", }, }, required: ["source"], }, }, ]; async function generateDiagram(args: GenerateDiagramArgs): Promise<string> { const { source, format = "png", darkMode = false, filename } = args; // Create temporary files const tmpDir = tmpdir(); const inputFile = join(tmpDir, `plantuml-${Date.now()}.puml`); const outputExt = format === "txt" || format === "utxt" ? "atxt" : format; const outputFile = join(tmpDir, `plantuml-${Date.now()}.${outputExt}`); try { // Write source to temporary file await writeFile(inputFile, source, "utf-8"); // Build command const darkModeFlag = darkMode ? "--dark-mode" : ""; const formatFlag = `--${format}`; const command = `java -jar "${PLANTUML_JAR}" ${formatFlag} ${darkModeFlag} -pipe < "${inputFile}"`; // Execute PlantUML const { stdout, stderr } = await execAsync(command, { encoding: "buffer", maxBuffer: 10 * 1024 * 1024, // 10MB buffer }); if (stderr && stderr.length > 0) { const errorText = stderr.toString("utf-8"); if (errorText.includes("Error") || errorText.includes("Syntax")) { throw new Error(`PlantUML error: ${errorText}`); } } // Save to file if filename is provided if (filename) { await writeFile(filename, stdout); // Save the .puml source file alongside the generated image const pumlFilename = filename.replace(/\.[^.]+$/, '.puml'); await writeFile(pumlFilename, source, "utf-8"); } // Return base64-encoded output const base64Data = stdout.toString("base64"); return base64Data; } finally { // Cleanup temporary files try { await unlink(inputFile); } catch (e) { // Ignore cleanup errors } } } async function checkSyntax(args: CheckSyntaxArgs): Promise<string> { const { source } = args; const tmpDir = tmpdir(); const inputFile = join(tmpDir, `plantuml-check-${Date.now()}.puml`); try { await writeFile(inputFile, source, "utf-8"); const command = `java -jar "${PLANTUML_JAR}" --check-syntax "${inputFile}"`; try { const { stdout, stderr } = await execAsync(command); return "Syntax is valid"; } catch (error: any) { // PlantUML returns non-zero exit code on syntax errors const output = error.stdout || error.stderr || error.message; return `Syntax errors found:\n${output}`; } } finally { try { await unlink(inputFile); } catch (e) { // Ignore cleanup errors } } } async function extractSource(args: ExtractSourceArgs): Promise<string> { const { filePath } = args; try { const command = `java -jar "${PLANTUML_JAR}" --extract-source "${filePath}"`; const { stdout, stderr } = await execAsync(command); if (stderr && stderr.includes("Error")) { throw new Error(`Failed to extract source: ${stderr}`); } return stdout || "Source extracted successfully"; } catch (error: any) { throw new Error(`Failed to extract source: ${error.message}`); } } async function getVersion(): Promise<string> { try { const command = `java -jar "${PLANTUML_JAR}" --version`; const { stdout } = await execAsync(command); return stdout; } catch (error: any) { throw new Error(`Failed to get version: ${error.message}`); } } const server = new Server( { name: "plantuml-mcp-server", version: "1.0.0", }, { capabilities: { tools: {}, }, } ); server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools }; }); server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; try { switch (name) { case "generate_diagram": { const result = await generateDiagram(args as unknown as GenerateDiagramArgs); const diagramArgs = args as unknown as GenerateDiagramArgs; const format = diagramArgs.format || "png"; let message = `Diagram generated successfully in ${format} format.`; if (diagramArgs.filename) { const pumlFilename = diagramArgs.filename.replace(/\.[^.]+$/, '.puml'); message += `\n\nFiles saved:\n- Image: ${diagramArgs.filename}\n- Source: ${pumlFilename}`; } message += `\n\nBase64 data:\n${result}`; return { content: [ { type: "text", text: message, }, ], }; } case "check_syntax": { const result = await checkSyntax(args as unknown as CheckSyntaxArgs); return { content: [ { type: "text", text: result, }, ], }; } case "extract_source": { const result = await extractSource(args as unknown as ExtractSourceArgs); return { content: [ { type: "text", text: result, }, ], }; } case "get_plantuml_version": { const result = await getVersion(); return { content: [ { type: "text", text: result, }, ], }; } default: throw new Error(`Unknown tool: ${name}`); } } catch (error: any) { return { content: [ { type: "text", text: `Error: ${error.message}`, }, ], isError: true, }; } }); async function main() { const transport = new StdioServerTransport(); await server.connect(transport); console.error("PlantUML MCP Server running on stdio"); } main().catch((error) => { console.error("Fatal error:", error); process.exit(1); });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/junqing258/plantuml-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server