# Directory Structure
```
├── .gitignore
├── .python-version
├── main.py
├── mcp_client.py
├── mcp_server.py
├── pyproject.toml
├── README_MCP.md
├── README.md
├── requirements.txt
├── server.py
├── test.py
└── uv.lock
```
# Files
--------------------------------------------------------------------------------
/.python-version:
--------------------------------------------------------------------------------
```
1 | 3.10
2 | 
```
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
```
 1 | # Python-generated files
 2 | __pycache__/
 3 | *.py[oc]
 4 | build/
 5 | dist/
 6 | wheels/
 7 | *.egg-info
 8 | 
 9 | # Virtual environments
10 | .venv
11 | 
12 | temp_files/
```
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
```markdown
 1 | # Mermaid Diagram Generator Server
 2 | 
 3 | A simple Flask server that generates diagrams from Mermaid syntax using mermaid-cli.
 4 | 
 5 | ## Prerequisites
 6 | 
 7 | - Python 3.7+
 8 | - Node.js and npm (for mermaid-cli)
 9 | - mermaid-cli installed globally: `npm install -g @mermaid-js/mermaid-cli`
10 | 
11 | ## Installation
12 | 
13 | 1. Clone this repository
14 | 2. Install Python dependencies:
15 |    ```
16 |    pip install -r requirements.txt
17 |    ```
18 | 3. Ensure mermaid-cli is installed globally:
19 |    ```
20 |    npm install -g @mermaid-js/mermaid-cli
21 |    ```
22 | 
23 | ## Running the Server
24 | 
25 | Start the server with:
26 | 
27 | ```
28 | python server.py
29 | ```
30 | 
31 | By default, the server runs on `http://localhost:5000`.
32 | 
33 | ### Temporary Files
34 | 
35 | The server creates a local directory called `temp_files` in the project folder for storing temporary files. This approach:
36 | 
37 | - Avoids permission issues with system temp directories
38 | - Works better in virtual environments
39 | - Automatically cleans up files older than 30 minutes
40 | 
41 | ## API Usage
42 | 
43 | ### Web Interface
44 | 
45 | Open your browser and navigate to `http://localhost:5000` to use the web interface.
46 | 
47 | ### API Endpoint
48 | 
49 | Send a POST request to `/generate` with a JSON body containing your Mermaid diagram:
50 | 
51 | ```json
52 | {
53 |   "mermaid": "graph TD\nA[Client] --> B[Load Balancer]\nB --> C[Server1]\nB --> D[Server2]",
54 |   "theme": "default",  // optional: default, dark, forest, neutral
55 |   "background": "white" // optional: white, transparent
56 | }
57 | ```
58 | 
59 | The server will return a PNG image of the rendered diagram.
60 | 
61 | Example using curl:
62 | 
63 | ```bash
64 | curl -X POST http://localhost:5000/generate \
65 |   -H "Content-Type: application/json" \
66 |   -d '{"mermaid":"graph TD\nA[Client] --> B[Load Balancer]"}' \
67 |   --output diagram.png
68 | ```
69 | 
70 | ## Testing
71 | 
72 | Run the included test script to verify everything is working:
73 | 
74 | ```
75 | python test.py
76 | ```
77 | 
78 | This will generate a sample diagram and save it as `output_diagram.png`.
79 | 
80 | ## Troubleshooting
81 | 
82 | If you encounter errors:
83 | 
84 | 1. Ensure mermaid-cli (mmdc) is installed and accessible in your PATH
85 | 2. Check server logs for specific error messages
86 | 3. Make sure your Mermaid syntax is valid
87 | 4. Verify the `temp_files` directory exists and has appropriate permissions
88 | 
```
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
```
1 | flask==2.3.3
2 | Werkzeug==2.3.7
3 | requests==2.31.0
4 | mcp>=0.1.0 
```
--------------------------------------------------------------------------------
/main.py:
--------------------------------------------------------------------------------
```python
1 | def main():
2 |     print("Hello from test-mcp!")
3 | 
4 | 
5 | if __name__ == "__main__":
6 |     main()
7 | 
```
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
```toml
 1 | [project]
 2 | name = "test-mcp"
 3 | version = "0.1.0"
 4 | description = "Add your description here"
 5 | readme = "README.md"
 6 | requires-python = ">=3.10"
 7 | dependencies = [
 8 |     "mcp[cli]>=1.4.1",
 9 | ]
10 | 
```
--------------------------------------------------------------------------------
/test.py:
--------------------------------------------------------------------------------
```python
 1 | #!/usr/bin/env python3
 2 | import requests
 3 | import os
 4 | import sys
 5 | 
 6 | # Mermaid diagram text
 7 | mermaid_text = """
 8 | graph TD
 9 |     A[Client] --> B[Load Balancer]
10 |     B --> C[Server 1]
11 |     B --> D[Server 2]
12 |     B --> E[Server 3]
13 |     C --> F[Database]
14 |     D --> F
15 |     E --> F
16 | """
17 | 
18 | # Base URL of the server
19 | base_url = "http://localhost:5000"
20 | 
21 | def test_generate_diagram():
22 |     """Test generating a diagram via the API"""
23 |     
24 |     print("Testing diagram generation API...")
25 |     
26 |     # Prepare the request data
27 |     data = {
28 |         "mermaid": mermaid_text,
29 |         "theme": "default",
30 |         "background": "white"
31 |     }
32 |     
33 |     try:
34 |         # Send the request
35 |         print(f"Sending request to {base_url}/generate...")
36 |         response = requests.post(f"{base_url}/generate", json=data)
37 |         
38 |         # Check if the request was successful
39 |         if response.status_code == 200:
40 |             # Save the image to a file
41 |             output_file = "output_diagram.png"
42 |             with open(output_file, "wb") as f:
43 |                 f.write(response.content)
44 |             
45 |             print(f"Diagram generated successfully and saved to {output_file}")
46 |             file_size = os.path.getsize(output_file)
47 |             print(f"File size: {file_size} bytes")
48 |             
49 |             if file_size == 0:
50 |                 print("Warning: Generated file is empty!")
51 |                 return False
52 |                 
53 |             return True
54 |         else:
55 |             print(f"Error generating diagram. Status code: {response.status_code}")
56 |             print(f"Error message: {response.text}")
57 |             return False
58 |     except requests.exceptions.ConnectionError:
59 |         print(f"Error: Could not connect to server at {base_url}")
60 |         print("Make sure the server is running and accessible.")
61 |         return False
62 |     except Exception as e:
63 |         print(f"Unexpected error: {str(e)}")
64 |         return False
65 | 
66 | if __name__ == "__main__":
67 |     success = test_generate_diagram()
68 |     if not success:
69 |         sys.exit(1)  # Exit with error code if test failed 
```
--------------------------------------------------------------------------------
/README_MCP.md:
--------------------------------------------------------------------------------
```markdown
  1 | # Mermaid Diagram Generator for MCP
  2 | 
  3 | This project provides a Mermaid diagram generator tool for the Model Control Protocol (MCP) framework. It allows you to generate diagrams from Mermaid syntax through MCP tools and resources.
  4 | 
  5 | ## Features
  6 | 
  7 | - Generate diagrams from Mermaid syntax using MCP tools
  8 | - Multiple theme options (default, dark, forest, neutral)
  9 | - Background options (white, transparent)
 10 | - Access example diagrams through resources
 11 | - Automatic cleanup of temporary files
 12 | - Native image handling using MCP's `Image` class
 13 | 
 14 | ## Prerequisites
 15 | 
 16 | - Python 3.7+
 17 | - Node.js and npm (for mermaid-cli)
 18 | - mermaid-cli installed globally: `npm install -g @mermaid-js/mermaid-cli`
 19 | - MCP Python package: `pip install mcp`
 20 | 
 21 | ## Installation
 22 | 
 23 | 1. Clone this repository
 24 | 2. Install Python dependencies:
 25 |    ```
 26 |    pip install mcp
 27 |    ```
 28 | 3. Ensure mermaid-cli is installed globally:
 29 |    ```
 30 |    npm install -g @mermaid-js/mermaid-cli
 31 |    ```
 32 | 
 33 | ## Usage
 34 | 
 35 | ### Running the MCP Server
 36 | 
 37 | Start the MCP server with:
 38 | 
 39 | ```
 40 | python mcp_server.py
 41 | ```
 42 | 
 43 | By default, the server runs on port 7000.
 44 | 
 45 | ### Using the Client
 46 | 
 47 | The `mcp_client.py` file demonstrates how to use the MCP client to interact with the server:
 48 | 
 49 | ```python
 50 | from mcp_client import MermaidClient
 51 | 
 52 | # Create client
 53 | client = MermaidClient()
 54 | 
 55 | # Get example diagrams
 56 | examples = client.get_examples()
 57 | 
 58 | # Generate a diagram
 59 | client.generate_diagram(
 60 |     mermaid_code="""
 61 |     graph TD
 62 |         A[Client] --> B[Load Balancer]
 63 |         B --> C[Server]
 64 |     """,
 65 |     output_path="diagram.png",
 66 |     theme="dark",
 67 |     background="transparent"
 68 | )
 69 | ```
 70 | 
 71 | ### Running the Example Client
 72 | 
 73 | ```
 74 | python mcp_client.py
 75 | ```
 76 | 
 77 | This will generate example diagrams in the `output` directory.
 78 | 
 79 | ## MCP Server API
 80 | 
 81 | ### Tools
 82 | 
 83 | - **generate_mermaid_diagram**: Generates a PNG image from Mermaid code
 84 |   - Parameters:
 85 |     - `mermaid_code` (str): The Mermaid diagram code
 86 |     - `theme` (str, optional): Theme to use (default, dark, forest, neutral)
 87 |     - `background` (str, optional): Background color (white, transparent)
 88 |   - Returns: `Image` object containing the diagram
 89 | 
 90 | ### Resources
 91 | 
 92 | - **mermaid://examples**: Returns a dictionary of example Mermaid diagrams
 93 |   - Examples include: flowchart, sequence diagram, class diagram, state diagram
 94 | 
 95 | ## Image Handling
 96 | 
 97 | The server uses MCP's `Image` class from `FastMCP` to return images, providing:
 98 | 
 99 | - Direct handling of binary image data
100 | - Proper format specification (PNG)
101 | - Alt text for accessibility
102 | - Automatic serialization by the MCP framework
103 | 
104 | The client code demonstrates how to handle the `Image` object returned by the server and save it to a file. The MCP client automatically converts the `Image` object to a format that can be easily processed by client applications.
105 | 
106 | ## Temporary Files
107 | 
108 | The server creates a local directory called `temp_files` in the project folder for storing temporary files. This directory is cleaned up automatically to remove files older than 30 minutes.
109 | 
110 | ## Troubleshooting
111 | 
112 | If you encounter errors:
113 | 
114 | 1. Ensure mermaid-cli (mmdc) is installed and accessible in your PATH
115 | 2. Check server logs for specific error messages
116 | 3. Make sure your Mermaid syntax is valid
117 | 4. Verify the `temp_files` directory exists and has appropriate permissions 
```
--------------------------------------------------------------------------------
/mcp_client.py:
--------------------------------------------------------------------------------
```python
  1 | from mcp.client import MCPClient
  2 | import os
  3 | import logging
  4 | from typing import Optional
  5 | 
  6 | # Set up logging
  7 | logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
  8 | 
  9 | class MermaidClient:
 10 |     def __init__(self, server_url: str = "http://localhost:7000"):
 11 |         """
 12 |         Initialize the Mermaid client.
 13 |         
 14 |         Args:
 15 |             server_url: URL of the MCP server
 16 |         """
 17 |         self.client = MCPClient(server_url)
 18 |         logging.info(f"Connected to MCP server at {server_url}")
 19 |     
 20 |     def get_examples(self) -> dict:
 21 |         """
 22 |         Get example Mermaid diagrams.
 23 |         
 24 |         Returns:
 25 |             Dictionary of example diagrams
 26 |         """
 27 |         try:
 28 |             examples = self.client.get_resource("mermaid://examples")
 29 |             logging.info(f"Retrieved {len(examples)} example diagrams")
 30 |             return examples
 31 |         except Exception as e:
 32 |             logging.error(f"Error getting examples: {str(e)}")
 33 |             raise
 34 |     
 35 |     def generate_diagram(
 36 |         self, 
 37 |         mermaid_code: str, 
 38 |         output_path: str, 
 39 |         theme: Optional[str] = "default", 
 40 |         background: Optional[str] = "white"
 41 |     ) -> str:
 42 |         """
 43 |         Generate a diagram from Mermaid code and save it to a file.
 44 |         
 45 |         Args:
 46 |             mermaid_code: The Mermaid diagram code
 47 |             output_path: Path to save the image
 48 |             theme: The theme to use (default, dark, forest, neutral)
 49 |             background: The background color (white, transparent)
 50 |             
 51 |         Returns:
 52 |             Path to the saved image
 53 |         """
 54 |         try:
 55 |             logging.info(f"Generating diagram with theme '{theme}' and background '{background}'")
 56 |             
 57 |             # Call the MCP tool to generate the diagram
 58 |             # The result is now an Image object from FastMCP which contains raw image data
 59 |             image_result = self.client.call_tool(
 60 |                 "generate_mermaid_diagram", 
 61 |                 mermaid_code=mermaid_code, 
 62 |                 theme=theme, 
 63 |                 background=background
 64 |             )
 65 |             
 66 |             # The client already handles the Image object conversion, 
 67 |             # so we should get raw image data directly
 68 |             if hasattr(image_result, 'data') and isinstance(image_result.data, bytes):
 69 |                 # Handle Image object with direct data attribute
 70 |                 image_data = image_result.data
 71 |             elif isinstance(image_result, dict) and 'data' in image_result:
 72 |                 # Handle dict response with data field
 73 |                 image_data = image_result['data']
 74 |                 if not isinstance(image_data, bytes):
 75 |                     logging.warning("Converting data to bytes")
 76 |                     image_data = bytes(image_data)
 77 |             elif isinstance(image_result, bytes):
 78 |                 # Handle direct bytes response
 79 |                 image_data = image_result
 80 |             else:
 81 |                 # Log the actual type for debugging
 82 |                 logging.error(f"Unexpected result type: {type(image_result)}")
 83 |                 logging.error(f"Result content: {str(image_result)[:200]}...")
 84 |                 raise ValueError(f"Unexpected image format returned from server")
 85 |             
 86 |             # Save the image to a file
 87 |             with open(output_path, 'wb') as f:
 88 |                 f.write(image_data)
 89 |                 
 90 |             logging.info(f"Saved diagram to {output_path} ({len(image_data)} bytes)")
 91 |             return output_path
 92 |             
 93 |         except Exception as e:
 94 |             logging.error(f"Error generating diagram: {str(e)}")
 95 |             raise
 96 | 
 97 | def main():
 98 |     # Create the client
 99 |     client = MermaidClient()
100 |     
101 |     try:
102 |         # Get example diagrams
103 |         examples = client.get_examples()
104 |         
105 |         # Create output directory
106 |         os.makedirs("output", exist_ok=True)
107 |         
108 |         # Generate diagrams for each example
109 |         for name, mermaid_code in examples.items():
110 |             output_path = f"output/{name}_diagram.png"
111 |             client.generate_diagram(mermaid_code, output_path)
112 |             print(f"Generated {name} diagram: {output_path}")
113 |             
114 |         # Generate a custom diagram
115 |         custom_code = """
116 |         graph LR
117 |             A[Start] --> B{Is it working?}
118 |             B -->|Yes| C[Great!]
119 |             B -->|No| D[Debug]
120 |             D --> B
121 |         """
122 |         
123 |         client.generate_diagram(
124 |             custom_code,
125 |             "output/custom_diagram.png",
126 |             theme="dark",
127 |             background="transparent"
128 |         )
129 |         print("Generated custom diagram: output/custom_diagram.png")
130 |         
131 |     except Exception as e:
132 |         print(f"Error: {str(e)}")
133 | 
134 | if __name__ == "__main__":
135 |     main() 
```
--------------------------------------------------------------------------------
/mcp_server.py:
--------------------------------------------------------------------------------
```python
  1 | from mcp.server.fastmcp import FastMCP, Image
  2 | import base64
  3 | import subprocess
  4 | import os
  5 | import uuid
  6 | import logging
  7 | import sys
  8 | from typing import Optional
  9 | from mcp.types import ImageContent
 10 | 
 11 | # Set up logging
 12 | logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
 13 | 
 14 | # Create a local temp directory in the project folder
 15 | TEMP_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'temp_files')
 16 | os.makedirs(TEMP_DIR, exist_ok=True)
 17 | logging.info(f"Temporary directory at: {TEMP_DIR}")
 18 | 
 19 | # Create an MCP server
 20 | mcp = FastMCP("Mermaid Diagram Generator")
 21 | 
 22 | @mcp.tool()
 23 | def generate_mermaid_diagram(
 24 |     mermaid_code: str, 
 25 |     theme: Optional[str] = "default", 
 26 |     background: Optional[str] = "white"
 27 | ) -> Image:
 28 |     """
 29 |     Generate a diagram from Mermaid code and return it as an image.
 30 |     
 31 |     Args:
 32 |         mermaid_code: The Mermaid diagram code
 33 |         theme: The theme to use (default, dark, forest, neutral)
 34 |         background: The background color (white, transparent)
 35 |     
 36 |     Returns:
 37 |         An image of the generated diagram
 38 |     """
 39 |     logging.info(f"Generating diagram with theme '{theme}' and background '{background}'")
 40 |     logging.info(f"Mermaid code: {mermaid_code[:50]}...")
 41 |     
 42 |     try:
 43 |         # Create temporary files with unique names
 44 |         unique_id = str(uuid.uuid4())
 45 |         input_file = os.path.join(TEMP_DIR, f"{unique_id}.mmd")
 46 |         output_file = os.path.join(TEMP_DIR, f"{unique_id}.png")
 47 |         
 48 |         # Write mermaid text to the input file
 49 |         with open(input_file, 'w', encoding='utf-8') as f:
 50 |             f.write(mermaid_code)
 51 |         
 52 |         # Find mmdc executable
 53 |         mmdc_cmd = find_mmdc_executable()
 54 |         if not mmdc_cmd:
 55 |             raise Exception("mermaid-cli not found. Please ensure it's installed and in PATH")
 56 |         
 57 |         # Build the command
 58 |         cmd = [mmdc_cmd, '-i', input_file, '-o', output_file]
 59 |         
 60 |         # Add optional parameters
 61 |         if theme:
 62 |             cmd.extend(['-t', theme])
 63 |         if background:
 64 |             cmd.extend(['-b', background])
 65 |         
 66 |         logging.info(f"Running command: {' '.join(cmd)}")
 67 |         
 68 |         # Run the command
 69 |         result = subprocess.run(cmd, capture_output=True, text=True)
 70 |         
 71 |         if result.returncode != 0:
 72 |             logging.error(f"Error running mmdc: {result.stderr}")
 73 |             raise Exception(f"Error generating diagram: {result.stderr}")
 74 |         
 75 |         # Check if output file exists and has content
 76 |         if not os.path.exists(output_file):
 77 |             raise Exception("Failed to create diagram: output file not found")
 78 |         
 79 |         file_size = os.path.getsize(output_file)
 80 |         if file_size == 0:
 81 |             raise Exception("Generated image is empty")
 82 |         
 83 |         # Read the image file
 84 |         with open(output_file, 'rb') as f:
 85 |             image_data = f.read()
 86 |         
 87 |         # Create and return Image object
 88 |         # The Image class from FastMCP handles the image data directly
 89 |         return Image(data=image_data, format="png", alt_text=f"Mermaid diagram with theme {theme} and background {background}")
 90 |         
 91 |     except Exception as e:
 92 |         logging.exception("Error generating diagram")
 93 |         raise e
 94 |     
 95 |     finally:
 96 |         # Clean up temporary files
 97 |         try:
 98 |             if os.path.exists(input_file):
 99 |                 os.remove(input_file)
100 |             if os.path.exists(output_file):
101 |                 os.remove(output_file)
102 |         except Exception as cleanup_error:
103 |             logging.exception(f"Error during cleanup: {cleanup_error}")
104 | 
105 | def find_mmdc_executable():
106 |     """
107 |     Find the mmdc executable path.
108 |     
109 |     Returns:
110 |         Path to mmdc executable or None if not found
111 |     """
112 |     mmdc_paths = [
113 |         'mmdc',  # Default PATH
114 |         os.path.join(os.path.expanduser('~'), 'AppData', 'Roaming', 'npm', 'mmdc.cmd'),
115 |         os.path.join(os.path.expanduser('~'), 'AppData', 'Roaming', 'npm', 'mmdc'),
116 |         os.path.join('C:', 'Program Files', 'nodejs', 'node_modules', '@mermaid-js', 'mermaid-cli', 'bin', 'mmdc'),
117 |         os.path.join('C:', 'Program Files', 'nodejs', 'node_modules', '.bin', 'mmdc')
118 |     ]
119 |     
120 |     for path in mmdc_paths:
121 |         logging.info(f"Trying mmdc at: {path}")
122 |         try:
123 |             test_result = subprocess.run([path, '--version'], capture_output=True, text=True)
124 |             if test_result.returncode == 0:
125 |                 logging.info(f"Found working mmdc at: {path}")
126 |                 return path
127 |         except Exception as e:
128 |             logging.info(f"Could not use mmdc at {path}: {str(e)}")
129 |     
130 |     return None
131 | 
132 | @mcp.resource("mermaid://examples")
133 | def get_mermaid_examples() -> dict:
134 |     """Get example Mermaid diagrams"""
135 |     return {
136 |         "flowchart": """
137 | graph TD
138 |     A[Client] --> B[Load Balancer]
139 |     B --> C[Server 1]
140 |     B --> D[Server 2]
141 |     B --> E[Server 3]
142 |     C --> F[Database]
143 |     D --> F
144 |     E --> F
145 | """,
146 |         "sequence": """
147 | sequenceDiagram
148 |     participant Browser
149 |     participant API
150 |     participant Database
151 |     
152 |     Browser->>API: GET /data
153 |     API->>Database: SELECT * FROM data
154 |     Database-->>API: Return data
155 |     API-->>Browser: Return JSON
156 | """,
157 |         "class": """
158 | classDiagram
159 |     class Animal {
160 |         +String name
161 |         +move()
162 |     }
163 |     class Dog {
164 |         +bark()
165 |     }
166 |     class Bird {
167 |         +fly()
168 |     }
169 |     Animal <|-- Dog
170 |     Animal <|-- Bird
171 | """,
172 |         "state": """
173 | stateDiagram-v2
174 |     [*] --> Idle
175 |     Idle --> Processing: Start
176 |     Processing --> Completed
177 |     Processing --> Error
178 |     Completed --> [*]
179 |     Error --> Idle: Retry
180 | """
181 |     }
182 | 
183 | # Cleanup function for old temp files
184 | def cleanup_old_files(max_age_minutes=30):
185 |     """Remove files older than max_age_minutes from the temp directory"""
186 |     import time
187 |     try:
188 |         current_time = time.time()
189 |         for filename in os.listdir(TEMP_DIR):
190 |             file_path = os.path.join(TEMP_DIR, filename)
191 |             if os.path.isfile(file_path):
192 |                 # Check file age
193 |                 file_age_minutes = (current_time - os.path.getmtime(file_path)) / 60
194 |                 if file_age_minutes > max_age_minutes:
195 |                     os.remove(file_path)
196 |                     logging.info(f"Removed old file: {file_path}")
197 |     except Exception as e:
198 |         logging.exception(f"Error cleaning up old files: {e}")
199 | 
200 | if __name__ == "__main__":
201 |     # Clean up old files before starting
202 |     cleanup_old_files()
203 |     
204 |     # Start the MCP server
205 |     mcp.run() 
```
--------------------------------------------------------------------------------
/server.py:
--------------------------------------------------------------------------------
```python
  1 | # server.py
  2 | from flask import Flask, request, send_file, Response
  3 | import subprocess
  4 | import tempfile
  5 | import os
  6 | import uuid
  7 | import logging
  8 | import shutil
  9 | import sys
 10 | 
 11 | app = Flask(__name__)
 12 | logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
 13 | 
 14 | # Create a local temp directory in the project folder
 15 | TEMP_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'temp_files')
 16 | os.makedirs(TEMP_DIR, exist_ok=True)
 17 | logging.info(f"Temporary directory at: {TEMP_DIR}")
 18 | logging.info(f"Python executable: {sys.executable}")
 19 | logging.info(f"Current directory: {os.getcwd()}")
 20 | 
 21 | @app.route('/generate', methods=['POST'])
 22 | def generate_diagram():
 23 |     input_file = None
 24 |     output_file = None
 25 |     
 26 |     try:
 27 |         # Get mermaid text from the request
 28 |         if not request.is_json:
 29 |             return Response("Request must be JSON", status=400)
 30 |         
 31 |         data = request.get_json()
 32 |         if 'mermaid' not in data:
 33 |             return Response("Missing 'mermaid' field in JSON", status=400)
 34 |         
 35 |         mermaid_text = data['mermaid']
 36 |         logging.info(f"Received mermaid text: {mermaid_text[:50]}...")
 37 |         
 38 |         # Create temporary files with unique names in our local temp directory
 39 |         unique_id = str(uuid.uuid4())
 40 |         input_file = os.path.join(TEMP_DIR, f"{unique_id}.mmd")
 41 |         output_file = os.path.join(TEMP_DIR, f"{unique_id}.png")
 42 |         
 43 |         logging.info(f"Input file: {input_file}")
 44 |         logging.info(f"Output file: {output_file}")
 45 |         
 46 |         # Write mermaid text to the input file
 47 |         with open(input_file, 'w', encoding='utf-8') as f:
 48 |             f.write(mermaid_text)
 49 |         
 50 |         logging.info(f"Wrote mermaid text to input file, size: {os.path.getsize(input_file)} bytes")
 51 |         
 52 |         # Run mmdc command
 53 |         # Try to find mmdc in common locations if direct command fails
 54 |         mmdc_paths = [
 55 |             'mmdc',  # Default PATH
 56 |             os.path.join(os.path.expanduser('~'), 'AppData', 'Roaming', 'npm', 'mmdc.cmd'),
 57 |             os.path.join(os.path.expanduser('~'), 'AppData', 'Roaming', 'npm', 'mmdc'),
 58 |             os.path.join('C:', 'Program Files', 'nodejs', 'node_modules', '@mermaid-js', 'mermaid-cli', 'bin', 'mmdc'),
 59 |             os.path.join('C:', 'Program Files', 'nodejs', 'node_modules', '.bin', 'mmdc')
 60 |         ]
 61 |         
 62 |         # Try each path
 63 |         mmdc_cmd = None
 64 |         for path in mmdc_paths:
 65 |             logging.info(f"Trying mmdc at: {path}")
 66 |             try:
 67 |                 # Just test if the command exists
 68 |                 test_result = subprocess.run([path, '--version'], 
 69 |                                            capture_output=True, 
 70 |                                            text=True)
 71 |                 if test_result.returncode == 0:
 72 |                     mmdc_cmd = path
 73 |                     logging.info(f"Found working mmdc at: {path}")
 74 |                     break
 75 |             except Exception as e:
 76 |                 logging.info(f"Could not use mmdc at {path}: {str(e)}")
 77 |         
 78 |         if not mmdc_cmd:
 79 |             logging.error("Could not find mmdc executable in any location")
 80 |             return Response("mermaid-cli not found. Please ensure it's installed and in PATH", status=500)
 81 |         
 82 |         cmd = [mmdc_cmd, '-i', input_file, '-o', output_file]
 83 |         
 84 |         # Add optional parameters if provided
 85 |         if 'theme' in data:
 86 |             cmd.extend(['-t', data['theme']])
 87 |         if 'background' in data:
 88 |             cmd.extend(['-b', data['background']])
 89 |         
 90 |         logging.info(f"Running command: {' '.join(cmd)}")
 91 |         
 92 |         try:
 93 |             result = subprocess.run(cmd, capture_output=True, text=True)
 94 |             logging.info(f"Command exit code: {result.returncode}")
 95 |             logging.info(f"Command stdout: {result.stdout}")
 96 |             logging.info(f"Command stderr: {result.stderr}")
 97 |             
 98 |             if result.returncode != 0:
 99 |                 logging.error(f"Error running mmdc: {result.stderr}")
100 |                 return Response(f"Error generating diagram: {result.stderr}", status=500)
101 |         except Exception as cmd_error:
102 |             logging.exception(f"Exception running command: {str(cmd_error)}")
103 |             return Response(f"Error executing mmdc command: {str(cmd_error)}", status=500)
104 |         
105 |         # Check if output file exists
106 |         if not os.path.exists(output_file):
107 |             logging.error(f"Output file not created: {output_file}")
108 |             return Response("Failed to create diagram: output file not found", status=500)
109 |         
110 |         file_size = os.path.getsize(output_file)
111 |         logging.info(f"Output file size: {file_size} bytes")
112 |         
113 |         if file_size == 0:
114 |             logging.error("Generated image has zero size")
115 |             return Response("Generated image is empty", status=500)
116 |             
117 |         # Return the generated image
118 |         return send_file(output_file, mimetype='image/png', as_attachment=False)
119 |     
120 |     except Exception as e:
121 |         logging.exception("Error processing request")
122 |         return Response(f"Server error: {str(e)}", status=500)
123 |     
124 |     finally:
125 |         # Clean up temporary files
126 |         try:
127 |             if input_file and os.path.exists(input_file):
128 |                 os.remove(input_file)
129 |                 logging.info(f"Cleaned up input file: {input_file}")
130 |             # We don't delete the output file here as send_file needs it
131 |             # The cleanup function below will handle old files
132 |         except Exception as cleanup_error:
133 |             logging.exception(f"Error during cleanup: {cleanup_error}")
134 | 
135 | # Cleanup function to remove old temp files
136 | def cleanup_old_files(max_age_minutes=30):
137 |     """Remove files older than max_age_minutes from the temp directory"""
138 |     try:
139 |         current_time = time.time()
140 |         for filename in os.listdir(TEMP_DIR):
141 |             file_path = os.path.join(TEMP_DIR, filename)
142 |             if os.path.isfile(file_path):
143 |                 # Check file age
144 |                 file_age_minutes = (current_time - os.path.getmtime(file_path)) / 60
145 |                 if file_age_minutes > max_age_minutes:
146 |                     os.remove(file_path)
147 |                     logging.info(f"Removed old file: {file_path}")
148 |     except Exception as e:
149 |         logging.exception(f"Error cleaning up old files: {e}")
150 | 
151 | @app.route('/', methods=['GET'])
152 | def index():
153 |     return """
154 |     <html>
155 |         <head>
156 |             <title>Mermaid Diagram Generator</title>
157 |             <style>
158 |                 body { font-family: Arial, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; }
159 |                 textarea { width: 100%; height: 200px; margin-bottom: 10px; }
160 |                 button { padding: 10px 15px; background-color: #4CAF50; color: white; border: none; cursor: pointer; }
161 |                 #result { margin-top: 20px; }
162 |                 pre { background-color: #f5f5f5; padding: 10px; border-radius: 5px; }
163 |             </style>
164 |         </head>
165 |         <body>
166 |             <h1>Mermaid Diagram Generator</h1>
167 |             <p>Enter your Mermaid diagram text below:</p>
168 |             <textarea id="mermaidText">graph TD
169 | A[Client] --> B[Load Balancer]
170 | B --> C[Server1]
171 | B --> D[Server2]</textarea>
172 |             <div>
173 |                 <label for="theme">Theme:</label>
174 |                 <select id="theme">
175 |                     <option value="default">Default</option>
176 |                     <option value="dark">Dark</option>
177 |                     <option value="forest">Forest</option>
178 |                     <option value="neutral">Neutral</option>
179 |                 </select>
180 |                 <label for="background">Background:</label>
181 |                 <select id="background">
182 |                     <option value="white">White</option>
183 |                     <option value="transparent">Transparent</option>
184 |                 </select>
185 |             </div>
186 |             <button onclick="generateDiagram()">Generate Diagram</button>
187 |             <div id="result"></div>
188 |             
189 |             <script>
190 |                 function generateDiagram() {
191 |                     const mermaidText = document.getElementById('mermaidText').value;
192 |                     const theme = document.getElementById('theme').value;
193 |                     const background = document.getElementById('background').value;
194 |                     const resultDiv = document.getElementById('result');
195 |                     
196 |                     resultDiv.innerHTML = 'Processing...';
197 |                     
198 |                     fetch('/generate', {
199 |                         method: 'POST',
200 |                         headers: {
201 |                             'Content-Type': 'application/json'
202 |                         },
203 |                         body: JSON.stringify({
204 |                             mermaid: mermaidText,
205 |                             theme: theme,
206 |                             background: background
207 |                         })
208 |                     })
209 |                     .then(response => {
210 |                         if (!response.ok) {
211 |                             return response.text().then(text => { throw new Error(text) });
212 |                         }
213 |                         return response.blob();
214 |                     })
215 |                     .then(blob => {
216 |                         const url = URL.createObjectURL(blob);
217 |                         resultDiv.innerHTML = `<h3>Generated Diagram:</h3><img src="${url}" alt="Generated Diagram">`;
218 |                     })
219 |                     .catch(error => {
220 |                         resultDiv.innerHTML = `<h3>Error:</h3><pre>${error.message}</pre>`;
221 |                     });
222 |                 }
223 |             </script>
224 |         </body>
225 |     </html>
226 |     """
227 | 
228 | if __name__ == '__main__':
229 |     # Import here to avoid issues when importing the app in other modules
230 |     import time
231 |     
232 |     # Schedule cleanup of old files on startup
233 |     cleanup_old_files()
234 |     
235 |     app.run(host='0.0.0.0', port=5000, debug=True)
236 | 
```