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:
@@ -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
|
||||||
Vendored
-241
@@ -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>
|
|
||||||
Vendored
-241
@@ -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>
|
|
||||||
@@ -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
@@ -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);
|
|
||||||
});
|
|
||||||
@@ -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);
|
|
||||||
Reference in New Issue
Block a user