diff --git a/.env.example b/.env.example deleted file mode 100644 index ed3b70c..0000000 --- a/.env.example +++ /dev/null @@ -1,8 +0,0 @@ -# Logging level (debug, info, warn, error) -LOG_LEVEL=info - -# Enable debug mode (true/false) -DEBUG=false - -# Default theme (light/dark) -DEFAULT_THEME=light \ No newline at end of file diff --git a/.gitignore b/.gitignore index 5693cd7..7fa36dc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ node_modules .env package-lock.json +.cursor \ No newline at end of file diff --git a/README.md b/README.md index 9d5e221..24b6e95 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,29 @@ # Excalidraw MCP Server: Powerful Drawing API for LLM Integration +> **📣 NEWS: We're excited to announce npx support!** You can now run Excalidraw MCP directly using `npx excalidraw-mcp` without installation. See Quick Start below for details. + 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. +## Quick Start + +You can run the Excalidraw MCP server directly using npx: + +```bash +npx excalidraw-mcp +``` + +### 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 @@ -71,13 +93,24 @@ To use this server with the Claude Desktop application, add the following config ```json { "mcpServers": { - "excalidraw": { - "command": "node", - "args": ["src/index.js"], - "env": { - "LOG_LEVEL": "info", - "DEBUG": "false" - } + "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"] } } } @@ -171,4 +204,20 @@ Here are some practical examples of how to use the Excalidraw MCP server: ## License -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. \ No newline at end of file +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. + +## Development + +Clone the repository and install dependencies: + +```bash +git clone +cd excalidraw-mcp +npm install +``` + +Start the development server: + +```bash +npm run dev +``` \ No newline at end of file diff --git a/claude_desktop_config.json b/claude_desktop_config.json index 4c03725..5b4dc4d 100644 --- a/claude_desktop_config.json +++ b/claude_desktop_config.json @@ -1,14 +1,8 @@ { "mcpServers": { - "excalidraw": { - "command": "node", - "args": [ - "src/index.js" - ], - "env": { - "LOG_LEVEL": "info", - "DEBUG": "false" - } + "mcp_excalidraw": { + "command": "npx", + "args": ["-y", "excalidraw-mcp"] } } -} \ No newline at end of file +} \ No newline at end of file diff --git a/package.json b/package.json index f9c10df..0afd752 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,8 @@ "lint": "eslint src/**/*.js", "build": "tsc", "docker:build": "docker build -t mcp/excalidraw .", - "docker:run": "docker run -p 3000:3000 mcp/excalidraw" + "docker:run": "docker run -p 3000:3000 mcp/excalidraw", + "prepare": "node -e \"try { require('fs').chmodSync('./src/cli.js', '755') } catch(e) { console.log(e) }\"" }, "dependencies": { "@modelcontextprotocol/sdk": "latest", @@ -37,5 +38,16 @@ "drawing" ], "author": "", - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=16.0.0" + }, + "publishConfig": { + "access": "public" + }, + "files": [ + "src/**/*", + "README.md", + "LICENSE" + ] } \ No newline at end of file diff --git a/src/cli.js b/src/cli.js new file mode 100644 index 0000000..a8e4f09 --- /dev/null +++ b/src/cli.js @@ -0,0 +1,103 @@ +#!/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) { + console.error('Error starting MCP server:', error); + process.exit(1); + } +} + +function showHelp() { + console.log(` + Excalidraw MCP Server + + Usage: + npx excalidraw-mcp [options] + + Options: + -p, --port Port to run the server on (default: 3000) + -h, --host Host to bind the server to (default: localhost) + -m, --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 + `); +} + +main().catch(error => { + console.error('Fatal error:', error); + process.exit(1); +}); \ No newline at end of file diff --git a/src/index.js b/src/index.js index 9e37753..0495d21 100644 --- a/src/index.js +++ b/src/index.js @@ -685,23 +685,65 @@ server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools }; }); -// Start server with STDIO transport +// Start server with transport based on mode async function runServer() { try { - const transport = new StdioServerTransport(); + logger.info('Starting Excalidraw MCP server...'); + + const transportMode = process.env.MCP_TRANSPORT_MODE || 'stdio'; + let transport; + + if (transportMode === 'http') { + const port = parseInt(process.env.PORT || '3000', 10); + const host = process.env.HOST || 'localhost'; + + logger.info(`Starting HTTP server on ${host}:${port}`); + // Here you would create an HTTP transport + // This is a placeholder - actual HTTP transport implementation would need to be added + transport = new StdioServerTransport(); // Fallback to stdio for now + } else { + // Default to stdio transport + transport = new StdioServerTransport(); + } + + // Add a debug message before connecting + logger.debug('Connecting to transport...'); + await server.connect(transport); - logger.info('Excalidraw MCP server running on stdio'); + logger.info(`Excalidraw MCP server running on ${transportMode}`); + + // Keep the process running + process.stdin.resume(); } catch (error) { logger.error('Error starting server:', error); + console.error('Failed to start MCP server:', error.message, error.stack); process.exit(1); } } -runServer(); +// Add global error handlers +process.on('uncaughtException', (error) => { + logger.error('Uncaught exception:', error); + console.error('UNCAUGHT EXCEPTION:', error.message, error.stack); + // Don't exit immediately to allow logging + setTimeout(() => process.exit(1), 1000); +}); + +process.on('unhandledRejection', (reason, promise) => { + logger.error('Unhandled promise rejection:', reason); + console.error('UNHANDLED REJECTION:', reason); + // Don't exit immediately to allow logging + setTimeout(() => process.exit(1), 1000); +}); + +// Only run the server directly if this file is executed directly (not imported) +if (import.meta.url === `file://${process.argv[1]}`) { + runServer(); +} // For testing and debugging purposes if (process.env.DEBUG === 'true') { logger.debug('Debug mode enabled'); } -export default server; \ No newline at end of file +export default runServer; \ No newline at end of file diff --git a/src/test.js b/src/test.js new file mode 100644 index 0000000..29431dc --- /dev/null +++ b/src/test.js @@ -0,0 +1,9 @@ +import { Server } from '@modelcontextprotocol/sdk/server/index.js'; +import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; + +console.log('MCP SDK imports successful'); +console.log('Server:', Server); +console.log('StdioServerTransport:', StdioServerTransport); + +// Exit gracefully +process.exit(0); \ No newline at end of file