mirror of https://github.com/mingrammer/diagrams
parent
5db95a8843
commit
dc57280509
@ -0,0 +1,25 @@
|
|||||||
|
# Use a slim Python image
|
||||||
|
FROM python:3.11-slim
|
||||||
|
|
||||||
|
# Install system dependencies (Graphviz is required for diagrams)
|
||||||
|
RUN apt-get update && \
|
||||||
|
apt-get install -y --no-install-recommends \
|
||||||
|
graphviz \
|
||||||
|
git \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Set working directory
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy the current directory (which contains the diagrams library source and our server code)
|
||||||
|
COPY . /app
|
||||||
|
|
||||||
|
# Install the diagrams library from source (current dir) and other requirements
|
||||||
|
# We install 'mcp' explicitly as it might not be in the local repo's requirements
|
||||||
|
RUN pip install --no-cache-dir . mcp
|
||||||
|
|
||||||
|
# Create an output directory for persistence if volume is mounted
|
||||||
|
RUN mkdir -p /app/output
|
||||||
|
|
||||||
|
# Run the MCP server using absolute path
|
||||||
|
CMD ["python", "/app/src/server.py"]
|
||||||
@ -0,0 +1,112 @@
|
|||||||
|
# Project: Diagrams MCP Server
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
This project aims to implement a Model Context Protocol (MCP) server that exposes the capabilities of the [diagrams](https://diagrams.mingrammer.com/) Python library. The server will allow AI agents to dynamically discover available diagram nodes (AWS, Azure, Kubernetes, etc.) and generate architectural diagrams from Python code.
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
The solution will be containerized to ensure isolation and consistent dependencies (specifically Graphviz).
|
||||||
|
|
||||||
|
- **Runtime**: Python 3.9+
|
||||||
|
- **Container**: Docker (Debian-based to support Graphviz)
|
||||||
|
- **Communication**: Standard Input/Output (stdio) via the MCP protocol.
|
||||||
|
- **Libraries**:
|
||||||
|
- `diagrams`: For generating diagrams.
|
||||||
|
- `mcp`: Official Python SDK for the Model Context Protocol.
|
||||||
|
- `graphviz`: System dependency required by the `diagrams` library.
|
||||||
|
|
||||||
|
## File Structure
|
||||||
|
```text
|
||||||
|
.
|
||||||
|
├── Dockerfile
|
||||||
|
├── requirements.txt
|
||||||
|
├── server.py
|
||||||
|
└── src/
|
||||||
|
└── inspection.py # Helper for dynamic node discovery
|
||||||
|
```
|
||||||
|
|
||||||
|
## Implementation Details
|
||||||
|
|
||||||
|
### 1. Docker Environment (`Dockerfile`)
|
||||||
|
The environment must include Graphviz, which is a system-level dependency required for rendering.
|
||||||
|
|
||||||
|
* **Base Image**: `python:3.11-slim`
|
||||||
|
* **System Dependencies**: `graphviz` (via `apt-get install -y graphviz`)
|
||||||
|
* **Python Dependencies**: `diagrams`, `mcp`
|
||||||
|
|
||||||
|
### 2. MCP Server (`server.py`)
|
||||||
|
The server will define three main tools. It should use the `mcp.server.fastmcp` or `mcp.server` standard library to define the server.
|
||||||
|
|
||||||
|
#### Tool 1: `list_icons`
|
||||||
|
**Purpose**: Dynamically discovers all available diagram nodes across all providers (AWS, Azure, GCP, SaaS, etc.) so the AI knows what classes are available to import.
|
||||||
|
|
||||||
|
* **Logic**:
|
||||||
|
1. Recursively walk the `diagrams` package directory.
|
||||||
|
2. Import modules dynamically.
|
||||||
|
3. Inspect classes in each module.
|
||||||
|
4. Filter classes that inherit from `diagrams.Node` but are not the base `Node` class itself.
|
||||||
|
5. Organize into a hierarchy: `Provider -> Service -> Node`.
|
||||||
|
6. **Optimization**: Cache this result at startup as it won't change.
|
||||||
|
|
||||||
|
* **Parameters**:
|
||||||
|
- `provider_filter` (string, optional): If provided (e.g., "aws"), only return nodes for that provider.
|
||||||
|
|
||||||
|
* **Returns**: JSON structure:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"aws": {
|
||||||
|
"compute": ["EC2", "Lambda", ...],
|
||||||
|
"database": ["RDS", "DynamoDB", ...]
|
||||||
|
},
|
||||||
|
"k8s": { ... }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Tool 2: `generate_diagram`
|
||||||
|
**Purpose**: Executes Python code to generate a diagram image.
|
||||||
|
|
||||||
|
* **Logic**:
|
||||||
|
1. Accepts a string of Python code (DSL).
|
||||||
|
2. **Security**: The code is executed via `exec()`. Since this runs inside a Docker container, it provides a layer of isolation.
|
||||||
|
3. **Execution**:
|
||||||
|
- Set up a temporary directory.
|
||||||
|
- Change the working directory to this temp location.
|
||||||
|
- Execute the code.
|
||||||
|
- Find the generated output file (usually `.png`).
|
||||||
|
4. **Result**: Return the path to the generated image or the base64 encoded content (depending on client capability, but path is preferred if sharing volume). *For this implementation, return the path inside the container and ensure the container mounts a shared volume if persistence is needed.*
|
||||||
|
|
||||||
|
* **Parameters**:
|
||||||
|
- `code` (string, required): The Python code using `diagrams` DSL.
|
||||||
|
- `filename` (string, optional): Desired output filename.
|
||||||
|
|
||||||
|
* **Returns**:
|
||||||
|
- `status`: "success" or "error"
|
||||||
|
- `message`: Path to file or error message.
|
||||||
|
|
||||||
|
#### Tool 3: `get_diagram_examples`
|
||||||
|
**Purpose**: Provides example code snippets to help the AI understand the syntax.
|
||||||
|
|
||||||
|
* **Logic**: Return a dictionary of static examples for common patterns (Basic, Clustered, Cloud-specific).
|
||||||
|
|
||||||
|
* **Parameters**:
|
||||||
|
- `provider` (string, optional): specific provider example (e.g., "aws").
|
||||||
|
|
||||||
|
### 3. Dynamic Inspection Helper (`src/inspection.py`)
|
||||||
|
This module is crucial for `list_icons`. It must robustly handle imports without crashing the server if a specific provider has missing optional dependencies.
|
||||||
|
|
||||||
|
* Use `pkgutil.walk_packages` to iterate over `diagrams`.
|
||||||
|
* Use `importlib.import_module` to load found modules.
|
||||||
|
* Use `inspect.getmembers` to find classes.
|
||||||
|
* Check `issubclass(obj, diagrams.Node)`.
|
||||||
|
|
||||||
|
## Execution & Testing
|
||||||
|
To run the server:
|
||||||
|
```bash
|
||||||
|
# Build
|
||||||
|
docker build -t diagrams-mcp .
|
||||||
|
|
||||||
|
# Run (connected to stdin/stdout for MCP)
|
||||||
|
docker run -i --rm -v $(pwd)/output:/app/output diagrams-mcp
|
||||||
|
```
|
||||||
|
|
||||||
|
## Security Considerations
|
||||||
|
* **Arbitrary Code Execution**: The `generate_diagram` tool executes arbitrary Python code. This is by design but dangerous. The Docker container MUST be treated as untrusted and ephemeral. Do not mount sensitive host directories into the container.
|
||||||
@ -0,0 +1,136 @@
|
|||||||
|
# Diagrams MCP Server
|
||||||
|
|
||||||
|
This is a Model Context Protocol (MCP) server that exposes the capabilities of the [Diagrams](https://diagrams.mingrammer.com/) Python library. It allows AI agents to discover available diagram nodes (AWS, Azure, K8s, etc.) and generate architectural diagrams from Python code.
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
graph TD
|
||||||
|
subgraph Host ["Host System"]
|
||||||
|
Client[MCP Client]
|
||||||
|
HostFS[Workspace / Output]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph Container ["Docker Container"]
|
||||||
|
MCPServer["MCP Server (server.py)"]
|
||||||
|
NodeRegistry["(Node Registry)"]
|
||||||
|
|
||||||
|
subgraph Logic ["Core Logic"]
|
||||||
|
Inspector["src/inspection.py"]
|
||||||
|
Executor["generate_diagram"]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph Libs ["Dependencies"]
|
||||||
|
DiagramsLib["diagrams package"]
|
||||||
|
Graphviz["Graphviz Binary"]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
%% Startup Flow
|
||||||
|
MCPServer -- "Startup" --> Inspector
|
||||||
|
Inspector -- "Scans" --> DiagramsLib
|
||||||
|
Inspector -- "Populates" --> NodeRegistry
|
||||||
|
|
||||||
|
%% Tool Flows
|
||||||
|
Client -- "list_icons()" --> MCPServer
|
||||||
|
MCPServer -- "Query" --> NodeRegistry
|
||||||
|
|
||||||
|
Client -- "generate_diagram(code)" --> MCPServer
|
||||||
|
MCPServer -- "Pass Code" --> Executor
|
||||||
|
Executor -- "exec()" --> DiagramsLib
|
||||||
|
DiagramsLib -- "Render" --> Graphviz
|
||||||
|
|
||||||
|
%% Output
|
||||||
|
Graphviz -- "Generates PNG" --> Executor
|
||||||
|
Executor -- "Writes File (Volume Mount)" --> HostFS
|
||||||
|
```
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- **Dynamic Icon Discovery**: `list_icons` tool scans the `diagrams` library to find all available nodes (e.g., `EC2`, `Pod`, `BlobStorage`) organized by provider and service.
|
||||||
|
- **Diagram Generation**: `generate_diagram` tool accepts Python code (DSL) and renders it into an image (PNG).
|
||||||
|
- **Examples**: `get_diagram_examples` tool provides ready-to-use snippets for common patterns.
|
||||||
|
- **Sandboxed Execution**: Runs inside a Docker container to ensure isolation and consistent dependencies (Graphviz).
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- **Docker**: This server is designed to run as a Docker container to manage system dependencies like Graphviz.
|
||||||
|
|
||||||
|
## Build
|
||||||
|
|
||||||
|
Build the Docker image from the `mcp-server` directory:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd mcp-server
|
||||||
|
docker build -t diagrams-mcp:latest .
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
To use this server with an MCP client (like Gemini CLI or Claude Desktop), add the following configuration.
|
||||||
|
|
||||||
|
This configuration mounts the current project directory into the container, allowing the server to save the generated images directly to your workspace.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"mcpServers": {
|
||||||
|
"diagrams": {
|
||||||
|
"command": "sh",
|
||||||
|
"args": [
|
||||||
|
"-c",
|
||||||
|
"export PROJECT_PATH=\"$\(PROJECT_PATH:-$(pwd)\")\"; docker run -i --rm -v \"$PROJECT_PATH:$PROJECT_PATH\" -w \"$PROJECT_PATH\" diagrams-mcp:latest"
|
||||||
|
],
|
||||||
|
"env": {
|
||||||
|
"FASTMCP_LOG_LEVEL": "ERROR"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Explanation of the Command
|
||||||
|
|
||||||
|
- **`docker run -i --rm`**: Runs the container interactively (for stdin/stdout communication) and removes it after exit.
|
||||||
|
- **`-v "$PROJECT_PATH:$PROJECT_PATH"`**: Mounts the project root (where you invoke the agent) to the same path inside the container. This is crucial for the `generate_diagram` tool to write the output image file back to your host filesystem.
|
||||||
|
- **`-w "$PROJECT_PATH"`**: Sets the working directory inside the container to match the host, ensuring relative paths work as expected.
|
||||||
|
- **`diagrams-mcp:latest`**: The name of the image you built.
|
||||||
|
|
||||||
|
## Tools
|
||||||
|
|
||||||
|
### `list_icons`
|
||||||
|
Lists available icons/nodes from the diagrams package.
|
||||||
|
- **Inputs**: `provider_filter` (optional), `service_filter` (optional).
|
||||||
|
- **Example**: List all AWS compute nodes.
|
||||||
|
|
||||||
|
### `generate_diagram`
|
||||||
|
Generates a diagram from Python code.
|
||||||
|
- **Inputs**: `code` (Python DSL), `filename` (optional), `timeout` (default: 90s).
|
||||||
|
- **Example Code**:
|
||||||
|
```python
|
||||||
|
from diagrams import Diagram
|
||||||
|
from diagrams.aws.compute import EC2
|
||||||
|
|
||||||
|
with Diagram("Simple", show=False):
|
||||||
|
EC2("web")
|
||||||
|
```
|
||||||
|
|
||||||
|
### `get_diagram_examples`
|
||||||
|
Returns example code snippets.
|
||||||
|
- **Inputs**: `diagram_type` (e.g., "aws", "k8s").
|
||||||
|
|
||||||
|
## Development Structure
|
||||||
|
|
||||||
|
```text
|
||||||
|
mcp-server/
|
||||||
|
├── Dockerfile # Container definition (Python + Graphviz)
|
||||||
|
├── requirements.txt # Python deps
|
||||||
|
├── README.md # This file
|
||||||
|
└── src/
|
||||||
|
├── server.py # Main MCP server entrypoint
|
||||||
|
└── inspection.py # Helper for dynamic node discovery
|
||||||
|
```
|
||||||
|
|
||||||
|
## Autor
|
||||||
|
|
||||||
|
- **Autor**: Carlos Barbero
|
||||||
|
- **User**: carlosrgomes
|
||||||
@ -0,0 +1,2 @@
|
|||||||
|
mcp
|
||||||
|
graphviz
|
||||||
@ -0,0 +1,73 @@
|
|||||||
|
import pkgutil
|
||||||
|
import importlib
|
||||||
|
import inspect
|
||||||
|
import sys
|
||||||
|
from collections import defaultdict
|
||||||
|
import diagrams
|
||||||
|
from diagrams import Node
|
||||||
|
|
||||||
|
def get_all_nodes():
|
||||||
|
"""
|
||||||
|
Dynamically inspects the diagrams package and returns a dictionary of all available Nodes.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: A nested dictionary structure:
|
||||||
|
{
|
||||||
|
"provider": {
|
||||||
|
"service": ["NodeName1", "NodeName2", ...]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
# Initialize the structure
|
||||||
|
icons = defaultdict(lambda: defaultdict(list))
|
||||||
|
|
||||||
|
# We also keep a flat map for the execution context: Name -> Class
|
||||||
|
# This handles potential name collisions by favoring the last seen or explicit logic if needed.
|
||||||
|
node_registry = {}
|
||||||
|
|
||||||
|
# Iterate through all subpackages in diagrams (e.g., aws, azure, k8s)
|
||||||
|
# We look at the path of the diagrams package
|
||||||
|
path = diagrams.__path__
|
||||||
|
prefix = diagrams.__name__ + "."
|
||||||
|
|
||||||
|
for _, provider_name, ispkg in pkgutil.iter_modules(path, prefix):
|
||||||
|
if not ispkg:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# e.g., provider_name = "diagrams.aws"
|
||||||
|
short_provider = provider_name.split(".")[-1]
|
||||||
|
|
||||||
|
# Skip internal modules if any (base, etc are actually useful, but we focus on providers)
|
||||||
|
if short_provider in ['base', 'custom']:
|
||||||
|
# 'custom' and 'base' might be treated differently, but for now we scan them
|
||||||
|
pass
|
||||||
|
|
||||||
|
try:
|
||||||
|
provider_module = importlib.import_module(provider_name)
|
||||||
|
except ImportError:
|
||||||
|
# Skip providers that might have missing system deps or issues
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Now iterate modules within the provider (e.g., diagrams.aws.compute)
|
||||||
|
if hasattr(provider_module, "__path__"):
|
||||||
|
for _, service_name, _ in pkgutil.iter_modules(provider_module.__path__, provider_name + "."):
|
||||||
|
try:
|
||||||
|
service_module = importlib.import_module(service_name)
|
||||||
|
short_service = service_name.split(".")[-1]
|
||||||
|
|
||||||
|
# Inspect classes in this service module
|
||||||
|
for name, obj in inspect.getmembers(service_module, inspect.isclass):
|
||||||
|
# Must inherit from Node
|
||||||
|
if issubclass(obj, Node) and obj is not Node:
|
||||||
|
# Verify it belongs to this module (to avoid re-export noise)
|
||||||
|
# or at least is defined in the diagrams package
|
||||||
|
if obj.__module__.startswith("diagrams"):
|
||||||
|
icons[short_provider][short_service].append(name)
|
||||||
|
node_registry[name] = obj
|
||||||
|
|
||||||
|
except ImportError:
|
||||||
|
continue
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
|
||||||
|
return icons, node_registry
|
||||||
@ -0,0 +1,176 @@
|
|||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
import contextlib
|
||||||
|
import base64
|
||||||
|
from pathlib import Path
|
||||||
|
from mcp.server.fastmcp import FastMCP
|
||||||
|
from diagrams import Diagram, Cluster, Edge, Node
|
||||||
|
|
||||||
|
# Import our helper
|
||||||
|
from inspection import get_all_nodes
|
||||||
|
|
||||||
|
# Initialize FastMCP
|
||||||
|
mcp = FastMCP("diagrams-mcp")
|
||||||
|
|
||||||
|
# Pre-load nodes for quick access and for the execution context
|
||||||
|
print("Loading diagram nodes...", file=sys.stderr)
|
||||||
|
ALL_ICONS, NODE_REGISTRY = get_all_nodes()
|
||||||
|
print(f"Loaded {len(NODE_REGISTRY)} nodes.", file=sys.stderr)
|
||||||
|
|
||||||
|
@mcp.tool()
|
||||||
|
def list_icons(provider_filter: str = None, service_filter: str = None):
|
||||||
|
"""
|
||||||
|
List available icons from the diagrams package, with optional filtering.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
provider_filter: Filter icons by provider name (e.g., "aws", "gcp", "k8s")
|
||||||
|
service_filter: Filter icons by service name (e.g., "compute", "database")
|
||||||
|
"""
|
||||||
|
if not provider_filter:
|
||||||
|
# Return list of providers
|
||||||
|
return {"providers": list(ALL_ICONS.keys())}
|
||||||
|
|
||||||
|
if provider_filter not in ALL_ICONS:
|
||||||
|
return {"error": f"Provider '{provider_filter}' not found. Available: {list(ALL_ICONS.keys())}"}
|
||||||
|
|
||||||
|
provider_data = ALL_ICONS[provider_filter]
|
||||||
|
|
||||||
|
if not service_filter:
|
||||||
|
# Return all services for this provider
|
||||||
|
return provider_data
|
||||||
|
|
||||||
|
if service_filter not in provider_data:
|
||||||
|
return {"error": f"Service '{service_filter}' not found in '{provider_filter}'. Available: {list(provider_data.keys())}"}
|
||||||
|
|
||||||
|
return {service_filter: provider_data[service_filter]}
|
||||||
|
|
||||||
|
@mcp.tool()
|
||||||
|
def get_diagram_examples(diagram_type: str = "all"):
|
||||||
|
"""
|
||||||
|
Get example code for different types of diagrams.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
diagram_type: Type of diagram example to return (aws, k8s, flow, etc. or 'all')
|
||||||
|
"""
|
||||||
|
examples = {
|
||||||
|
"aws": """
|
||||||
|
from diagrams import Diagram
|
||||||
|
from diagrams.aws.compute import EC2
|
||||||
|
from diagrams.aws.database import RDS
|
||||||
|
from diagrams.aws.network import ELB
|
||||||
|
|
||||||
|
with Diagram("Web Service", show=False):
|
||||||
|
ELB("lb") >> EC2("web") >> RDS("userdb")
|
||||||
|
""",
|
||||||
|
"k8s": """
|
||||||
|
from diagrams import Diagram, Cluster
|
||||||
|
from diagrams.k8s.compute import Pod
|
||||||
|
from diagrams.k8s.network import Ingress, Service
|
||||||
|
|
||||||
|
with Diagram("K8s Cluster", show=False):
|
||||||
|
ingress = Ingress("domain.com")
|
||||||
|
|
||||||
|
with Cluster("App"):
|
||||||
|
svc = Service("svc")
|
||||||
|
pods = [Pod("pod1"), Pod("pod2")]
|
||||||
|
|
||||||
|
ingress >> svc >> pods
|
||||||
|
""",
|
||||||
|
"custom": """
|
||||||
|
from diagrams import Diagram
|
||||||
|
from diagrams.custom import Custom
|
||||||
|
|
||||||
|
with Diagram("Custom", show=False):
|
||||||
|
# Ensure you have the icon file locally if using Custom
|
||||||
|
Custom("Label", "./my-icon.png")
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
|
||||||
|
if diagram_type == "all":
|
||||||
|
return examples
|
||||||
|
|
||||||
|
return {diagram_type: examples.get(diagram_type, "No example found for this type.")}
|
||||||
|
|
||||||
|
@mcp.tool()
|
||||||
|
def generate_diagram(code: str, filename: str = None, timeout: int = 90):
|
||||||
|
"""
|
||||||
|
Generate a diagram from Python code using the diagrams package.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
code: Python code using the diagrams package DSL.
|
||||||
|
filename: Optional filename to save the diagram to.
|
||||||
|
timeout: Execution timeout in seconds.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Create a temporary directory for execution
|
||||||
|
with tempfile.TemporaryDirectory() as temp_dir:
|
||||||
|
original_cwd = os.getcwd()
|
||||||
|
os.chdir(temp_dir)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Prepare the execution context
|
||||||
|
# We inject Diagram, Cluster, Edge, and ALL discovered nodes (EC2, Pod, etc.)
|
||||||
|
# This allows the user to write code without heavy imports if they choose,
|
||||||
|
# though explicit imports are still better for clarity.
|
||||||
|
exec_globals = {
|
||||||
|
"Diagram": Diagram,
|
||||||
|
"Cluster": Cluster,
|
||||||
|
"Edge": Edge,
|
||||||
|
"Node": Node,
|
||||||
|
**NODE_REGISTRY
|
||||||
|
}
|
||||||
|
|
||||||
|
# Execute the code
|
||||||
|
# We wrap it in a try/except block within the exec to catch runtime errors
|
||||||
|
try:
|
||||||
|
exec(code, exec_globals)
|
||||||
|
except Exception as e:
|
||||||
|
return {"status": "error", "message": f"Runtime error: {str(e)}"}
|
||||||
|
|
||||||
|
# Find the generated file
|
||||||
|
# Diagrams generates files based on the name passed to Diagram() class
|
||||||
|
# We look for any .png file created in the temp dir
|
||||||
|
generated_files = list(Path(".").glob("*.png"))
|
||||||
|
|
||||||
|
if not generated_files:
|
||||||
|
return {"status": "error", "message": "No diagram image was generated. Did you call with Diagram(..., show=False)?"}
|
||||||
|
|
||||||
|
# Use the most recently modified file or the first one
|
||||||
|
generated_files.sort(key=lambda f: f.stat().st_mtime, reverse=True)
|
||||||
|
output_file = generated_files[0]
|
||||||
|
|
||||||
|
# If a filename was requested, we might want to rename it?
|
||||||
|
# For now, we return the path.
|
||||||
|
# In a real MCP setup, we might copy this to a mounted volume.
|
||||||
|
|
||||||
|
# Copy the generated file back to the original working directory
|
||||||
|
# This ensures that if the user mounted their project to the working directory,
|
||||||
|
# the file appears in their project.
|
||||||
|
import shutil
|
||||||
|
|
||||||
|
target_dir = Path(original_cwd)
|
||||||
|
target_filename = filename if filename else output_file.name
|
||||||
|
target_path = target_dir / target_filename
|
||||||
|
|
||||||
|
# Ensure extension
|
||||||
|
if not target_path.suffix:
|
||||||
|
target_path = target_path.with_suffix(".png")
|
||||||
|
|
||||||
|
shutil.copy2(output_file, target_path)
|
||||||
|
final_path = str(target_path)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"status": "success",
|
||||||
|
"path": final_path,
|
||||||
|
"filename": target_path.name
|
||||||
|
}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
return {"status": "error", "message": f"System error: {str(e)}"}
|
||||||
|
|
||||||
|
finally:
|
||||||
|
os.chdir(original_cwd)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
mcp.run()
|
||||||
Loading…
Reference in new issue