Update README.md and remove unused files for Excalidraw MCP Canvas

- Revise README.md to reflect the new project name and features, emphasizing real-time diagramming and AI integration.
- Remove outdated public HTML files and CLI script as they are no longer needed.
- Streamline installation and setup instructions for better clarity.
- Enhance architecture overview and key features sections to provide a comprehensive understanding of the system.
This commit is contained in:
ycsahara
2025-07-11 19:31:22 +00:00
parent 11309c731b
commit cee3b775bf
6 changed files with 282 additions and 1448 deletions
+282 -233
View File
@@ -1,280 +1,329 @@
# Excalidraw MCP Server: Powerful Drawing API for LLM Integration # Excalidraw MCP Canvas: Live Visual Diagramming with AI Integration
> **📣 NEWS: Version 1.0.0 is now published to npm!** You can run Excalidraw MCP directly using `npx excalidraw-mcp` without installation. No setup required - just run and enjoy! A comprehensive system that combines **Excalidraw's powerful drawing capabilities** with **Model Context Protocol (MCP)** integration, enabling AI agents to create and manipulate diagrams in real-time on a live canvas.
A comprehensive Model Context Protocol (MCP) server that enables seamless interaction with Excalidraw diagrams and drawings. This server provides LLMs (Large Language Models) with the ability to create, modify, query, and manipulate Excalidraw drawings through a structured, developer-friendly API. ## 🚀 What This System Does
## Quick Start - **🎨 Live Canvas**: Real-time Excalidraw canvas accessible via web browser
- **🤖 AI Integration**: MCP server allows AI agents (like Claude) to create visual diagrams
- **⚡ Real-time Sync**: Elements created via MCP API appear instantly on the canvas
- **🔄 WebSocket Updates**: Live synchronization across multiple connected clients
- **🏗️ Production Ready**: Clean, minimal UI suitable for end users
You can run the Excalidraw MCP server directly using npx without installing anything: ## 🏛️ Architecture Overview
```
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
│ AI Agent │───▶│ MCP Server │───▶│ Canvas Server │
│ (Claude) │ │ (src/index.js) │ │ (src/server.js) │
└─────────────────┘ └──────────────────┘ └─────────────────┘
┌─────────────────┐
│ Frontend │
│ (React + WS) │
└─────────────────┘
```
## 🌟 Key Features
### **Real-time Canvas Integration**
- Elements created via MCP appear instantly on the live canvas
- WebSocket-based real-time synchronization
- Multi-client support with live updates
### **Production-Ready Interface**
- Clean, minimal UI with connection status
- Simple "Clear Canvas" functionality
- No development clutter or debug information
### **Comprehensive MCP API**
- **Element Creation**: rectangles, ellipses, diamonds, arrows, text, lines
- **Element Management**: update, delete, query with filters
- **Batch Operations**: create multiple elements in one call
- **Advanced Features**: grouping, alignment, distribution, locking
### **Robust Architecture**
- Express.js backend with REST API + WebSocket
- React frontend with official Excalidraw package
- Dual-path element loading for reliability
- Auto-reconnection and error handling
## 📦 Installation & Setup
### **Prerequisites**
- Node.js 16+
- npm or yarn
### **1. Clone and Install**
```bash ```bash
npx excalidraw-mcp
```
If you prefer to install it globally:
```bash
npm install -g excalidraw-mcp
excalidraw-mcp
```
### Options
The following command-line options are available:
```
-d, --debug Enable debug logging
-?, --help Show this help message
```
> **Note:** The following options are currently only fully functional in the Docker version:
> ```
> -p, --port <port> Port to run the server on (default: 3000)
> -h, --host <host> Host to bind the server to (default: localhost)
> -m, --mode <mode> Transport mode: 'stdio' or 'http' (default: stdio)
> ```
### Examples
Run with default options:
```bash
npx excalidraw-mcp
```
Enable debug logging:
```bash
npx excalidraw-mcp --debug
```
## Features
- **Full Excalidraw Element Control**: Create, update, delete, and query any Excalidraw element
- **Advanced Element Manipulation**: Group, align, distribute, lock, and unlock elements
- **Resource Management**: Access and modify scene information, libraries, themes, and elements
- **Easy Integration**: Works with Claude Desktop and other LLM platforms
- **Docker Support**: Simple deployment with containerization options
## API Tools Reference
### Element Creation and Modification
* **create_element**
* Create a new Excalidraw element (rectangle, ellipse, diamond, etc.)
* Required inputs: `type`, `x`, `y` coordinates
* Optional inputs: dimensions, colors, styling properties
* **update_element**
* Update an existing Excalidraw element by ID
* Required input: `id` of the element to update
* Optional inputs: any element property to modify
* **delete_element**
* Delete an Excalidraw element
* Required input: `id` of the element to delete
* **query_elements**
* Query elements with optional filtering
* Optional inputs: `type` to filter by element type, `filter` object with key-value pairs
### Resource Management
* **get_resource**
* Get a specific resource like scene information or all elements
* Required input: `resource` type (scene, library, theme, elements)
### Element Organization
* **group_elements**
* Group multiple elements together
* Required input: `elementIds` array of element IDs to group
* **ungroup_elements**
* Ungroup a group of elements
* Required input: `groupId` of the group to ungroup
* **align_elements**
* Align multiple elements based on specified alignment
* Required inputs: `elementIds` array and `alignment` (left, center, right, top, middle, bottom)
* **distribute_elements**
* Distribute elements evenly across space
* Required inputs: `elementIds` array and `direction` (horizontal or vertical)
* **lock_elements**
* Lock elements to prevent modification
* Required input: `elementIds` array of elements to lock
* **unlock_elements**
* Unlock elements to allow modification
* Required input: `elementIds` array of elements to unlock
## Integration with Claude Desktop
To use this server with the Claude Desktop application, add the following configuration to the "mcpServers" section of your `claude_desktop_config.json`:
```json
{
"mcpServers": {
"mcp_excalidraw": {
"command": "npx",
"args": ["-y", "excalidraw-mcp"]
}
}
}
```
## Integration with Cursor
To use this server with the Cursor application, add the following configuration to the "mcpServers" section of your `.cursor/mcp.json`:
```json
{
"mcpServers": {
"mcp_excalidraw": {
"command": "npx",
"args": ["-y", "excalidraw-mcp"]
}
}
}
```
## Integration with Cursor
To use this server with Cursor, create a `.cursor/mcp.json` file in your workspace with the following configuration:
```json
{
"mcpServers": {
"mcp_excalidraw": {
"command": "npx",
"args": ["-y", "excalidraw-mcp"]
}
}
}
```
Make sure to:
1. Replace `/path/to/your/directory` with the actual absolute path to your mcp_excalidraw installation
2. Create the `.cursor` directory if it doesn't exist
3. Ensure the path to `index.js` is correct and the file exists
### Docker Integration
```json
{
"mcpServers": {
"excalidraw": {
"command": "docker",
"args": ["run", "-i", "--rm", "mcp/excalidraw"],
"env": {
"LOG_LEVEL": "info",
"DEBUG": "false"
}
}
}
}
```
## Installation Guide
### NPM Installation
```bash
# Install globally
npm install -g excalidraw-mcp
# Run the server
excalidraw-mcp
```
### Local Development Setup
```bash
# Clone the repository
git clone <repository-url> git clone <repository-url>
cd excalidraw-mcp cd mcp_excalidraw
# Install dependencies
npm install npm install
# Start the server
npm start
``` ```
### Docker Installation ### **2. Build the Frontend**
```bash ```bash
# Build the Docker image npm run build
docker build -t mcp/excalidraw .
# Run the container
docker run -i --rm mcp/excalidraw
``` ```
## Configuration Options ### **3. Start the System**
The server can be configured using the following environment variables: #### **Option A: Production Mode**
```bash
# Start canvas server (serves frontend + API)
npm run canvas
```
- `LOG_LEVEL` - Set the logging level (default: "info") #### **Option B: Development Mode**
- `DEBUG` - Enable debug mode (default: "false") ```bash
- `DEFAULT_THEME` - Set the default theme (default: "light") # Start both canvas server and Vite dev server
npm run dev
```
## Usage Examples ### **4. Access the Canvas**
Open your browser and navigate to:
```
http://localhost:3000
```
Here are some practical examples of how to use the Excalidraw MCP server: ## 🔧 Available Scripts
### Creating a Rectangle Element | Script | Description |
|--------|-------------|
| `npm start` | Start MCP server (`src/index.js`) |
| `npm run canvas` | Start canvas server (`src/server.js`) |
| `npm run build` | Build frontend for production |
| `npm run dev` | Start canvas + Vite dev server |
| `npm run production` | Build + start in production mode |
```json ## 🎯 Usage Guide
### **For End Users**
1. Open the canvas at `http://localhost:3000`
2. Check connection status (should show "Connected")
3. AI agents can now create diagrams that appear in real-time
4. Use "Clear Canvas" to remove all elements
### **For AI Agents (via MCP)**
The MCP server provides these tools for creating visual diagrams:
#### **Basic Element Creation**
```javascript
// Create a rectangle
{ {
"type": "rectangle", "type": "rectangle",
"x": 100, "x": 100,
"y": 100, "y": 100,
"width": 200, "width": 200,
"height": 100, "height": 100,
"backgroundColor": "#ffffff", "backgroundColor": "#e3f2fd",
"strokeColor": "#000000", "strokeColor": "#1976d2",
"strokeWidth": 2, "strokeWidth": 2
"roughness": 1
} }
``` ```
### Querying Specific Elements #### **Create Text Elements**
```javascript
{
"type": "text",
"x": 150,
"y": 125,
"text": "Process Step",
"fontSize": 16,
"strokeColor": "#333333"
}
```
#### **Create Arrows & Lines**
```javascript
{
"type": "arrow",
"x": 300,
"y": 130,
"width": 100,
"height": 0,
"strokeColor": "#666666",
"strokeWidth": 2
}
```
#### **Batch Creation for Complex Diagrams**
```javascript
{
"elements": [
{
"type": "rectangle",
"x": 100,
"y": 100,
"width": 120,
"height": 60,
"backgroundColor": "#fff3e0",
"strokeColor": "#ff9800"
},
{
"type": "text",
"x": 130,
"y": 125,
"text": "Start",
"fontSize": 16
}
]
}
```
## 🔌 Integration with Claude Desktop
Add this configuration to your `claude_desktop_config.json`:
```json ```json
{ {
"type": "rectangle", "mcpServers": {
"filter": { "excalidraw_canvas": {
"strokeColor": "#000000" "command": "node",
"args": ["/path/to/mcp_excalidraw/src/index.js"],
}
} }
} }
``` ```
### Grouping Multiple Elements ## Integration with Cursor
Add this configuration to your `claude_desktop_config.json`:
```json ```json
{ {
"elementIds": ["elem1", "elem2", "elem3"] "mcpServers": {
"mcp_excalidraw": {
"command": "node",
"args": ["/path/to/mcp_excalidraw/src/index.js"]
}
}
} }
``` ```
## License **Important**: Replace `/path/to/mcp_excalidraw` with the actual absolute path to your installation.
This Excalidraw MCP server is licensed under the MIT License. You are free to use, modify, and distribute the software, subject to the terms and conditions of the MIT License. For more details, please see the LICENSE file in the project repository. ## 🛠️ Environment Variables
## Development | Variable | Default | Description |
|----------|---------|-------------|
| `EXPRESS_SERVER_URL` | `http://localhost:3000` | Canvas server URL for MCP sync |
| `ENABLE_CANVAS_SYNC` | `true` | Enable/disable canvas synchronization |
| `DEBUG` | `false` | Enable debug logging |
| `PORT` | `3000` | Canvas server port |
| `HOST` | `localhost` | Canvas server host |
Clone the repository and install dependencies: ## 📊 API Endpoints
```bash The canvas server provides these REST endpoints:
git clone <repository-url>
cd excalidraw-mcp | Method | Endpoint | Description |
npm install |--------|----------|-------------|
| `GET` | `/api/elements` | Get all elements |
| `POST` | `/api/elements` | Create new element |
| `PUT` | `/api/elements/:id` | Update element |
| `DELETE` | `/api/elements/:id` | Delete element |
| `POST` | `/api/elements/batch` | Create multiple elements |
| `GET` | `/health` | Server health check |
## 🎨 MCP Tools Available
### **Element Management**
- `create_element` - Create any type of Excalidraw element
- `update_element` - Modify existing elements
- `delete_element` - Remove elements
- `query_elements` - Search elements with filters
### **Batch Operations**
- `batch_create_elements` - Create complex diagrams in one call
### **Element Organization**
- `group_elements` - Group multiple elements
- `ungroup_elements` - Ungroup element groups
- `align_elements` - Align elements (left, center, right, top, middle, bottom)
- `distribute_elements` - Distribute elements evenly
- `lock_elements` / `unlock_elements` - Lock/unlock elements
### **Resource Access**
- `get_resource` - Access scene, library, theme, or elements data
## 🏗️ Development Architecture
### **Frontend** (`frontend/src/`)
- **React + Vite**: Modern build system
- **Official Excalidraw**: `@excalidraw/excalidraw` package
- **WebSocket Client**: Real-time element sync
- **Clean UI**: Production-ready interface
### **Canvas Server** (`src/server.js`)
- **Express.js**: REST API + static file serving
- **WebSocket**: Real-time client communication
- **Element Storage**: In-memory with persistence options
- **CORS**: Cross-origin support
### **MCP Server** (`src/index.js`)
- **MCP Protocol**: Standard Model Context Protocol
- **Canvas Sync**: HTTP requests to canvas server
- **Element Management**: Full CRUD operations
- **Batch Support**: Complex diagram creation
## 🐛 Troubleshooting
### **Canvas Not Loading**
- Ensure `npm run build` completed successfully
- Check that `dist/index.html` exists
- Verify canvas server is running on port 3000
### **Elements Not Syncing**
- Confirm MCP server is running (`npm start`)
- Check `ENABLE_CANVAS_SYNC=true` in environment
- Verify canvas server is accessible at `EXPRESS_SERVER_URL`
### **WebSocket Connection Issues**
- Check browser console for WebSocket errors
- Ensure no firewall blocking WebSocket connections
- Try refreshing the browser page
### **Build Errors**
- Delete `node_modules` and run `npm install`
- Check Node.js version (requires 16+)
- Ensure all dependencies are installed
## 📋 Project Structure
```
mcp_excalidraw/
├── frontend/
│ ├── src/
│ │ ├── App.jsx # Main React component
│ │ └── main.jsx # React entry point
│ └── index.html # HTML template
├── src/
│ ├── index.js # MCP server
│ ├── server.js # Canvas server (Express + WebSocket)
│ ├── types.js # Shared types and utilities
│ └── utils/
│ └── logger.js # Logging utility
├── dist/ # Built frontend (generated)
├── vite.config.js # Vite build configuration
├── package.json # Dependencies and scripts
└── README.md # This file
``` ```
Start the development server: ## 🤝 Contributing
```bash 1. Fork the repository
npm run dev 2. Create a feature branch (`git checkout -b feature/amazing-feature`)
``` 3. Commit your changes (`git commit -m 'Add amazing feature'`)
4. Push to the branch (`git push origin feature/amazing-feature`)
5. Open a Pull Request
## 📝 License
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
## 🙏 Acknowledgments
- **Excalidraw Team** - For the amazing drawing library
- **MCP Community** - For the Model Context Protocol specification
-241
View File
@@ -1,241 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Excalidraw POC - Backend API Integration</title>
<style>
body {
margin: 0;
padding: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;
background-color: #f5f5f5;
}
.header {
background: #fff;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
padding: 10px 20px;
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
gap: 10px;
}
.header h1 {
margin: 0;
color: #333;
font-size: 24px;
}
.controls {
display: flex;
gap: 10px;
flex-wrap: wrap;
}
button {
padding: 8px 16px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
transition: background-color 0.2s;
}
.btn-primary {
background-color: #007bff;
color: white;
}
.btn-primary:hover {
background-color: #0056b3;
}
.btn-secondary {
background-color: #6c757d;
color: white;
}
.btn-secondary:hover {
background-color: #545b62;
}
.btn-success {
background-color: #28a745;
color: white;
}
.btn-success:hover {
background-color: #218838;
}
.btn-danger {
background-color: #dc3545;
color: white;
}
.btn-danger:hover {
background-color: #c82333;
}
.status {
display: flex;
align-items: center;
gap: 8px;
font-size: 14px;
}
.status-dot {
width: 8px;
height: 8px;
border-radius: 50%;
}
.status-connected {
background-color: #28a745;
}
.status-disconnected {
background-color: #dc3545;
}
.canvas-container {
height: calc(100vh - 80px);
width: 100%;
position: relative;
}
.api-panel {
position: fixed;
right: 20px;
top: 100px;
background: white;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
padding: 20px;
width: 300px;
max-height: 400px;
overflow-y: auto;
z-index: 1000;
}
.api-panel h3 {
margin: 0 0 15px 0;
color: #333;
font-size: 18px;
}
.api-form {
display: flex;
flex-direction: column;
gap: 10px;
}
.form-group {
display: flex;
flex-direction: column;
gap: 5px;
}
label {
font-weight: 500;
color: #555;
font-size: 14px;
}
input, select {
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
}
.form-row {
display: flex;
gap: 10px;
}
.form-row .form-group {
flex: 1;
}
.toggle-panel {
position: fixed;
right: 20px;
top: 60px;
background: #007bff;
color: white;
border: none;
border-radius: 4px;
padding: 10px;
cursor: pointer;
font-size: 14px;
z-index: 1001;
}
.api-panel.hidden {
display: none;
}
.notification {
position: fixed;
top: 20px;
right: 20px;
padding: 12px 16px;
border-radius: 4px;
color: white;
font-size: 14px;
z-index: 1002;
animation: slideIn 0.3s ease;
}
.notification.success {
background-color: #28a745;
}
.notification.error {
background-color: #dc3545;
}
@keyframes slideIn {
from {
transform: translateX(100%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
.element-count {
font-size: 14px;
color: #666;
}
.loading {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
font-size: 16px;
color: #666;
}
.loading-content {
text-align: center;
}
.loading-content div:first-child {
margin-bottom: 10px;
}
</style>
<script type="module" crossorigin src="/assets/main-8eVhstul.js"></script>
<link rel="stylesheet" crossorigin href="/assets/main-B9Rh8YyQ.css">
</head>
<body>
<div id="root"></div>
</body>
</html>
-241
View File
@@ -1,241 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Excalidraw POC - Backend API Integration</title>
<style>
body {
margin: 0;
padding: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;
background-color: #f5f5f5;
}
.header {
background: #fff;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
padding: 10px 20px;
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
gap: 10px;
}
.header h1 {
margin: 0;
color: #333;
font-size: 24px;
}
.controls {
display: flex;
gap: 10px;
flex-wrap: wrap;
}
button {
padding: 8px 16px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
transition: background-color 0.2s;
}
.btn-primary {
background-color: #007bff;
color: white;
}
.btn-primary:hover {
background-color: #0056b3;
}
.btn-secondary {
background-color: #6c757d;
color: white;
}
.btn-secondary:hover {
background-color: #545b62;
}
.btn-success {
background-color: #28a745;
color: white;
}
.btn-success:hover {
background-color: #218838;
}
.btn-danger {
background-color: #dc3545;
color: white;
}
.btn-danger:hover {
background-color: #c82333;
}
.status {
display: flex;
align-items: center;
gap: 8px;
font-size: 14px;
}
.status-dot {
width: 8px;
height: 8px;
border-radius: 50%;
}
.status-connected {
background-color: #28a745;
}
.status-disconnected {
background-color: #dc3545;
}
.canvas-container {
height: calc(100vh - 80px);
width: 100%;
position: relative;
}
.api-panel {
position: fixed;
right: 20px;
top: 100px;
background: white;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
padding: 20px;
width: 300px;
max-height: 400px;
overflow-y: auto;
z-index: 1000;
}
.api-panel h3 {
margin: 0 0 15px 0;
color: #333;
font-size: 18px;
}
.api-form {
display: flex;
flex-direction: column;
gap: 10px;
}
.form-group {
display: flex;
flex-direction: column;
gap: 5px;
}
label {
font-weight: 500;
color: #555;
font-size: 14px;
}
input, select {
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
}
.form-row {
display: flex;
gap: 10px;
}
.form-row .form-group {
flex: 1;
}
.toggle-panel {
position: fixed;
right: 20px;
top: 60px;
background: #007bff;
color: white;
border: none;
border-radius: 4px;
padding: 10px;
cursor: pointer;
font-size: 14px;
z-index: 1001;
}
.api-panel.hidden {
display: none;
}
.notification {
position: fixed;
top: 20px;
right: 20px;
padding: 12px 16px;
border-radius: 4px;
color: white;
font-size: 14px;
z-index: 1002;
animation: slideIn 0.3s ease;
}
.notification.success {
background-color: #28a745;
}
.notification.error {
background-color: #dc3545;
}
@keyframes slideIn {
from {
transform: translateX(100%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
.element-count {
font-size: 14px;
color: #666;
}
.loading {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
font-size: 16px;
color: #666;
}
.loading-content {
text-align: center;
}
.loading-content div:first-child {
margin-bottom: 10px;
}
</style>
<script type="module" crossorigin src="/assets/main-8eVhstul.js"></script>
<link rel="stylesheet" crossorigin href="/assets/main-B9Rh8YyQ.css">
</head>
<body>
<div id="root"></div>
</body>
</html>
-621
View File
@@ -1,621 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Excalidraw POC - Backend API Integration</title>
<style>
body {
margin: 0;
padding: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;
background-color: #f5f5f5;
}
.header {
background: #fff;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
padding: 10px 20px;
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
gap: 10px;
}
.header h1 {
margin: 0;
color: #333;
font-size: 24px;
}
.controls {
display: flex;
gap: 10px;
flex-wrap: wrap;
}
button {
padding: 8px 16px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
transition: background-color 0.2s;
}
.btn-primary {
background-color: #007bff;
color: white;
}
.btn-primary:hover {
background-color: #0056b3;
}
.btn-secondary {
background-color: #6c757d;
color: white;
}
.btn-secondary:hover {
background-color: #545b62;
}
.btn-success {
background-color: #28a745;
color: white;
}
.btn-success:hover {
background-color: #218838;
}
.btn-danger {
background-color: #dc3545;
color: white;
}
.btn-danger:hover {
background-color: #c82333;
}
.status {
display: flex;
align-items: center;
gap: 8px;
font-size: 14px;
}
.status-dot {
width: 8px;
height: 8px;
border-radius: 50%;
}
.status-connected {
background-color: #28a745;
}
.status-disconnected {
background-color: #dc3545;
}
.canvas-container {
height: calc(100vh - 80px);
width: 100%;
position: relative;
}
.api-panel {
position: fixed;
right: 20px;
top: 100px;
background: white;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
padding: 20px;
width: 300px;
max-height: 400px;
overflow-y: auto;
z-index: 1000;
}
.api-panel h3 {
margin: 0 0 15px 0;
color: #333;
font-size: 18px;
}
.api-form {
display: flex;
flex-direction: column;
gap: 10px;
}
.form-group {
display: flex;
flex-direction: column;
gap: 5px;
}
label {
font-weight: 500;
color: #555;
font-size: 14px;
}
input, select {
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
}
.form-row {
display: flex;
gap: 10px;
}
.form-row .form-group {
flex: 1;
}
.toggle-panel {
position: fixed;
right: 20px;
top: 60px;
background: #007bff;
color: white;
border: none;
border-radius: 4px;
padding: 10px;
cursor: pointer;
font-size: 14px;
z-index: 1001;
}
.api-panel.hidden {
display: none;
}
.notification {
position: fixed;
top: 20px;
right: 20px;
padding: 12px 16px;
border-radius: 4px;
color: white;
font-size: 14px;
z-index: 1002;
animation: slideIn 0.3s ease;
}
.notification.success {
background-color: #28a745;
}
.notification.error {
background-color: #dc3545;
}
@keyframes slideIn {
from {
transform: translateX(100%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
.element-count {
font-size: 14px;
color: #666;
}
</style>
</head>
<body>
<div class="header">
<h1>Excalidraw POC - Backend API Integration</h1>
<div class="controls">
<div class="status">
<div class="status-dot" id="statusDot"></div>
<span id="statusText">Connecting...</span>
</div>
<div class="element-count">
Elements: <span id="elementCount">0</span>
</div>
<button class="btn-secondary" onclick="clearCanvas()">Clear Canvas</button>
<button class="btn-primary" onclick="createSampleElements()">Create Sample Elements</button>
</div>
</div>
<button class="toggle-panel" onclick="toggleApiPanel()">API Panel</button>
<div class="api-panel" id="apiPanel">
<h3>Create Element via API</h3>
<form class="api-form" onsubmit="createElementFromForm(event)">
<div class="form-group">
<label for="elementType">Element Type:</label>
<select id="elementType" required>
<option value="rectangle">Rectangle</option>
<option value="ellipse">Ellipse</option>
<option value="diamond">Diamond</option>
<option value="text">Text</option>
<option value="arrow">Arrow</option>
<option value="line">Line</option>
</select>
</div>
<div class="form-row">
<div class="form-group">
<label for="x">X Position:</label>
<input type="number" id="x" value="100" required>
</div>
<div class="form-group">
<label for="y">Y Position:</label>
<input type="number" id="y" value="100" required>
</div>
</div>
<div class="form-row">
<div class="form-group">
<label for="width">Width:</label>
<input type="number" id="width" value="100">
</div>
<div class="form-group">
<label for="height">Height:</label>
<input type="number" id="height" value="100">
</div>
</div>
<div class="form-group">
<label for="text">Text (for text elements):</label>
<input type="text" id="text" placeholder="Enter text content">
</div>
<div class="form-row">
<div class="form-group">
<label for="backgroundColor">Background Color:</label>
<input type="color" id="backgroundColor" value="#ffffff">
</div>
<div class="form-group">
<label for="strokeColor">Stroke Color:</label>
<input type="color" id="strokeColor" value="#000000">
</div>
</div>
<div class="form-group">
<label for="strokeWidth">Stroke Width:</label>
<input type="number" id="strokeWidth" value="2" min="1" max="10">
</div>
<button type="submit" class="btn-success">Create Element</button>
</form>
</div>
<div class="canvas-container">
<div id="excalidraw-container">
<div style="display: flex; justify-content: center; align-items: center; height: 100%; font-size: 16px; color: #666;">
<div style="text-align: center;">
<div style="margin-bottom: 10px;"></div>
<div>Loading Excalidraw...</div>
</div>
</div>
</div>
</div>
<script>
// Global variables
let excalidrawAPI = null;
let websocket = null;
let isConnected = false;
let apiPanelVisible = true;
// Initialize Excalidraw when the page loads
window.addEventListener('load', async () => {
try {
// Load Excalidraw from CDN
const { Excalidraw, convertToExcalidrawElements } = await import('https://esm.sh/@excalidraw/excalidraw@0.17.0');
// Make convertToExcalidrawElements available globally
window.convertToExcalidrawElements = convertToExcalidrawElements;
// Initialize Excalidraw
const excalidrawContainer = document.getElementById('excalidraw-container');
// Create Excalidraw instance
const excalidrawComponent = React.createElement(Excalidraw, {
excalidrawAPI: (api) => {
excalidrawAPI = api;
console.log('Excalidraw API initialized');
},
onChange: (excalidrawElements, appState, files) => {
updateElementCount(excalidrawElements.length);
},
initialData: {
elements: [],
appState: {
theme: 'light',
viewBackgroundColor: '#ffffff'
}
}
});
// Render Excalidraw
const root = ReactDOM.createRoot(excalidrawContainer);
root.render(excalidrawComponent);
// Initialize WebSocket connection after Excalidraw loads
connectWebSocket();
} catch (error) {
console.error('Error loading Excalidraw:', error);
document.getElementById('excalidraw-container').innerHTML =
'<div style="padding: 20px; text-align: center; color: #666;">' +
'<h3>Error loading Excalidraw</h3>' +
'<p>Please check the console for more details.</p>' +
'</div>';
}
});
// WebSocket connection
function connectWebSocket() {
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
const wsUrl = `${protocol}//${window.location.host}`;
websocket = new WebSocket(wsUrl);
websocket.onopen = () => {
console.log('WebSocket connected');
isConnected = true;
updateConnectionStatus();
};
websocket.onmessage = (event) => {
const data = JSON.parse(event.data);
handleWebSocketMessage(data);
};
websocket.onclose = () => {
console.log('WebSocket disconnected');
isConnected = false;
updateConnectionStatus();
// Reconnect after 5 seconds
setTimeout(connectWebSocket, 5000);
};
websocket.onerror = (error) => {
console.error('WebSocket error:', error);
isConnected = false;
updateConnectionStatus();
};
}
// Handle WebSocket messages
function handleWebSocketMessage(data) {
if (!excalidrawAPI || !window.convertToExcalidrawElements) return;
const currentElements = excalidrawAPI.getSceneElements();
switch (data.type) {
case 'initial_elements':
if (data.elements && data.elements.length > 0) {
const convertedElements = window.convertToExcalidrawElements(data.elements);
excalidrawAPI.updateScene({ elements: convertedElements });
}
break;
case 'element_created':
const newElement = window.convertToExcalidrawElements([data.element]);
excalidrawAPI.updateScene({
elements: [...currentElements, ...newElement]
});
showNotification('Element created successfully!', 'success');
break;
case 'element_updated':
const updatedElements = currentElements.map(el =>
el.id === data.element.id ? window.convertToExcalidrawElements([data.element])[0] : el
);
excalidrawAPI.updateScene({ elements: updatedElements });
showNotification('Element updated successfully!', 'success');
break;
case 'element_deleted':
const filteredElements = currentElements.filter(el => el.id !== data.elementId);
excalidrawAPI.updateScene({ elements: filteredElements });
showNotification('Element deleted successfully!', 'success');
break;
case 'elements_batch_created':
const batchElements = window.convertToExcalidrawElements(data.elements);
excalidrawAPI.updateScene({
elements: [...currentElements, ...batchElements]
});
showNotification(`${data.elements.length} elements created!`, 'success');
break;
}
}
// Update connection status
function updateConnectionStatus() {
const statusDot = document.getElementById('statusDot');
const statusText = document.getElementById('statusText');
if (isConnected) {
statusDot.className = 'status-dot status-connected';
statusText.textContent = 'Connected';
} else {
statusDot.className = 'status-dot status-disconnected';
statusText.textContent = 'Disconnected';
}
}
// Update element count
function updateElementCount(count) {
const elementCountElement = document.getElementById('elementCount');
elementCountElement.textContent = count;
}
// Show notification
function showNotification(message, type = 'success') {
const notification = document.createElement('div');
notification.className = `notification ${type}`;
notification.textContent = message;
document.body.appendChild(notification);
setTimeout(() => {
notification.remove();
}, 3000);
}
// API Functions
async function createElementFromForm(event) {
event.preventDefault();
const elementData = {
type: document.getElementById('elementType').value,
x: parseInt(document.getElementById('x').value),
y: parseInt(document.getElementById('y').value),
width: parseInt(document.getElementById('width').value) || undefined,
height: parseInt(document.getElementById('height').value) || undefined,
backgroundColor: document.getElementById('backgroundColor').value,
strokeColor: document.getElementById('strokeColor').value,
strokeWidth: parseInt(document.getElementById('strokeWidth').value)
};
const textValue = document.getElementById('text').value;
if (textValue) {
elementData.text = textValue;
}
// Remove undefined values
Object.keys(elementData).forEach(key => {
if (elementData[key] === undefined) {
delete elementData[key];
}
});
try {
const response = await fetch('/api/elements', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(elementData)
});
const result = await response.json();
if (result.success) {
showNotification('Element created via API!', 'success');
// Reset form
document.getElementById('x').value = Math.floor(Math.random() * 400) + 50;
document.getElementById('y').value = Math.floor(Math.random() * 300) + 50;
} else {
showNotification(`Error: ${result.error}`, 'error');
}
} catch (error) {
console.error('Error creating element:', error);
showNotification('Failed to create element', 'error');
}
}
// Create sample elements
async function createSampleElements() {
const sampleElements = [
{
type: 'rectangle',
x: 50,
y: 50,
width: 150,
height: 100,
backgroundColor: '#ffeaa7',
strokeColor: '#2d3436'
},
{
type: 'ellipse',
x: 250,
y: 50,
width: 120,
height: 120,
backgroundColor: '#74b9ff',
strokeColor: '#0984e3'
},
{
type: 'diamond',
x: 50,
y: 200,
width: 100,
height: 100,
backgroundColor: '#fd79a8',
strokeColor: '#e84393'
},
{
type: 'text',
x: 250,
y: 220,
text: 'Hello from API!',
fontSize: 20,
strokeColor: '#2d3436'
}
];
try {
const response = await fetch('/api/elements/batch', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ elements: sampleElements })
});
const result = await response.json();
if (result.success) {
showNotification(`${result.count} sample elements created!`, 'success');
} else {
showNotification(`Error: ${result.error}`, 'error');
}
} catch (error) {
console.error('Error creating sample elements:', error);
showNotification('Failed to create sample elements', 'error');
}
}
// Clear canvas
function clearCanvas() {
if (excalidrawAPI) {
excalidrawAPI.updateScene({ elements: [] });
showNotification('Canvas cleared!', 'success');
}
}
// Toggle API panel
function toggleApiPanel() {
const apiPanel = document.getElementById('apiPanel');
apiPanelVisible = !apiPanelVisible;
if (apiPanelVisible) {
apiPanel.classList.remove('hidden');
} else {
apiPanel.classList.add('hidden');
}
}
// Export functions to global scope
window.createElementFromForm = createElementFromForm;
window.createSampleElements = createSampleElements;
window.clearCanvas = clearCanvas;
window.toggleApiPanel = toggleApiPanel;
</script>
<!-- React dependencies -->
<script crossorigin src="https://unpkg.com/react@18.2.0/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18.2.0/umd/react-dom.production.min.js"></script>
</body>
</html>
-103
View File
@@ -1,103 +0,0 @@
#!/usr/bin/env node
import { parseArgs } from 'node:util';
import { resolve } from 'path';
import dotenv from 'dotenv';
import logger from './utils/logger.js';
// Load environment variables
dotenv.config();
async function main() {
try {
// Parse command line arguments
const options = {
port: {
type: 'string',
short: 'p',
default: process.env.PORT || '3000'
},
host: {
type: 'string',
short: 'h',
default: process.env.HOST || 'localhost'
},
mode: {
type: 'string',
short: 'm',
default: 'stdio'
},
debug: {
type: 'boolean',
short: 'd',
default: false
},
help: {
type: 'boolean',
short: '?',
default: false
}
};
const { values, positionals } = parseArgs({
options,
allowPositionals: true,
strict: false
});
// Show help if requested
if (values.help) {
showHelp();
process.exit(0);
}
// Set debug mode if requested
if (values.debug) {
process.env.DEBUG = 'true';
logger.level = 'debug';
logger.debug('Debug mode enabled');
}
// Set the mode for server transport
process.env.MCP_TRANSPORT_MODE = values.mode;
// Set port and host
process.env.PORT = values.port;
process.env.HOST = values.host;
// Import and run server
const { default: runServer } = await import('./index.js');
await runServer();
} catch (error) {
process.stderr.write(`Error starting MCP server: ${error}\n`);
process.exit(1);
}
}
function showHelp() {
process.stderr.write(`
Excalidraw MCP Server
Usage:
npx excalidraw-mcp [options]
Options:
-p, --port <port> Port to run the server on (default: 3000)
-h, --host <host> Host to bind the server to (default: localhost)
-m, --mode <mode> Transport mode: 'stdio' or 'http' (default: stdio)
-d, --debug Enable debug logging
-?, --help Show this help message
Examples:
npx excalidraw-mcp
npx excalidraw-mcp --port 4000
npx excalidraw-mcp --mode http
npx excalidraw-mcp --debug
\n`);
}
main().catch(error => {
process.stderr.write(`Fatal error: ${error}\n`);
process.exit(1);
});
-9
View File
@@ -1,9 +0,0 @@
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
process.stderr.write('MCP SDK imports successful\n');
process.stderr.write(`Server: ${Server}\n`);
process.stderr.write(`StdioServerTransport: ${StdioServerTransport}\n`);
// Exit gracefully
process.exit(0);