Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fac1c7a267 | ||
|
|
9311561227 |
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@sanjibdevnath/mcp-excalidraw-local",
|
||||
"version": "1.2.1",
|
||||
"version": "1.3.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@sanjibdevnath/mcp-excalidraw-local",
|
||||
"version": "1.2.1",
|
||||
"version": "1.3.0",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@sanjibdevnath/mcp-excalidraw-local",
|
||||
"version": "1.2.1",
|
||||
"version": "1.3.0",
|
||||
"description": "Fully local MCP server for Excalidraw with SQLite persistence, multi-tenancy, auto-sync, real-time canvas, and 32 tools",
|
||||
"main": "dist/index.js",
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: excalidraw-skill
|
||||
description: Programmatic canvas toolkit for creating, editing, and refining Excalidraw diagrams via MCP tools (32 tools) or REST API with real-time canvas sync, multi-tenant workspace isolation, SQLite persistence, project management, full-text search, and element version history. Use when an agent needs to draw or lay out diagrams on a live canvas, iteratively refine diagrams using screenshots, manage workspaces/tenants and projects, export/import .excalidraw files or PNG/SVG images, search elements, view change history, save/restore canvas snapshots, or perform element-level CRUD. Canvas server port is configurable via CANVAS_PORT env var (default 3000).
|
||||
description: MANDATORY prerequisite for ALL Excalidraw MCP tool usage. Read this skill BEFORE calling any Excalidraw tool (batch_create_elements, create_element, create_from_mermaid, update_element, etc.) — without this skill's sizing formulas, two-batch ordering (shapes first, arrows second), and write-check-review verification cycle, diagrams will have invisible arrows, truncated text, and overlapping elements. Use whenever the user asks to draw, create, visualize, sketch, or diagram anything — flowcharts, architecture diagrams, system designs, org charts, sequence flows, decision trees, network topologies, ER diagrams, mind maps, or any visual on Excalidraw canvas. Also covers diagram refinement, PNG/SVG export, project/workspace management, and all canvas interactions.
|
||||
---
|
||||
|
||||
# Excalidraw Skill
|
||||
|
||||
+121
@@ -57,6 +57,11 @@ interface AgentDef {
|
||||
mcpConfigPath?: string;
|
||||
mcpCliRemove?: string;
|
||||
mcpCliCommand?: string;
|
||||
instructionConfig?: {
|
||||
global: string;
|
||||
local: string;
|
||||
format: 'claude-md' | 'cursor-mdc';
|
||||
};
|
||||
}
|
||||
|
||||
function getAgents(): AgentDef[] {
|
||||
@@ -71,6 +76,11 @@ function getAgents(): AgentDef[] {
|
||||
},
|
||||
mcpConfigType: 'json-file',
|
||||
mcpConfigPath: path.join(home, '.cursor', 'mcp.json'),
|
||||
instructionConfig: {
|
||||
global: path.join(home, '.cursor', 'rules', 'excalidraw.mdc'),
|
||||
local: path.join(process.cwd(), '.cursor', 'rules', 'excalidraw.mdc'),
|
||||
format: 'cursor-mdc',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'Claude Code',
|
||||
@@ -82,6 +92,11 @@ function getAgents(): AgentDef[] {
|
||||
mcpConfigType: 'cli-command',
|
||||
mcpCliRemove: 'claude mcp remove excalidraw-canvas --scope user',
|
||||
mcpCliCommand: 'claude mcp add excalidraw-canvas --scope user -e CANVAS_PORT=3000 -- npx -y @sanjibdevnath/mcp-excalidraw-local@latest',
|
||||
instructionConfig: {
|
||||
global: path.join(home, '.claude', 'CLAUDE.md'),
|
||||
local: path.join(process.cwd(), 'CLAUDE.md'),
|
||||
format: 'claude-md',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'Codex CLI',
|
||||
@@ -226,6 +241,17 @@ async function phaseSkillInstall(rl: readline.Interface): Promise<void> {
|
||||
fs.mkdirSync(destDir, { recursive: true });
|
||||
copyDirSync(skillSource, destDir);
|
||||
ok(`Installed to ${destDir}`);
|
||||
|
||||
// Write instruction directive to ensure skill auto-triggers
|
||||
if (agent.instructionConfig) {
|
||||
const instrPath = agent.instructionConfig[scope];
|
||||
try {
|
||||
writeInstructionDirective(instrPath, agent.instructionConfig.format);
|
||||
ok(`Skill directive added to ${instrPath}`);
|
||||
} catch (instrErr) {
|
||||
warn(`Could not write directive to ${instrPath}: ${(instrErr as Error).message}`);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
fail(`Failed to install to ${destDir}: ${(err as Error).message}`);
|
||||
}
|
||||
@@ -245,6 +271,79 @@ function copyDirSync(src: string, dest: string): void {
|
||||
}
|
||||
}
|
||||
|
||||
// ── Instruction Directives ──────────────────────────────────
|
||||
|
||||
const DIRECTIVE_MARKER_START = '<!-- excalidraw-mcp:start -->';
|
||||
const DIRECTIVE_MARKER_END = '<!-- excalidraw-mcp:end -->';
|
||||
|
||||
const EXCALIDRAW_DIRECTIVE_BODY =
|
||||
'When Excalidraw MCP tools are available (batch_create_elements, create_element, ' +
|
||||
'create_from_mermaid, etc.), ALWAYS invoke the `/excalidraw-skill` skill FIRST ' +
|
||||
'before calling any Excalidraw tool. The skill contains essential sizing formulas, ' +
|
||||
'batch ordering rules (shapes first, arrows second), and a mandatory ' +
|
||||
'write-check-review verification cycle. Using Excalidraw tools without consulting ' +
|
||||
'this skill produces broken diagrams — invisible arrows, truncated text, ' +
|
||||
'overlapping elements.';
|
||||
|
||||
function buildClaudeMdSection(): string {
|
||||
return [
|
||||
DIRECTIVE_MARKER_START,
|
||||
'## Excalidraw Canvas — Skill Directive',
|
||||
'',
|
||||
EXCALIDRAW_DIRECTIVE_BODY,
|
||||
DIRECTIVE_MARKER_END,
|
||||
].join('\n');
|
||||
}
|
||||
|
||||
function buildCursorMdc(): string {
|
||||
return [
|
||||
'---',
|
||||
'description: Always consult excalidraw-skill before using Excalidraw MCP tools',
|
||||
'globs:',
|
||||
'alwaysApply: true',
|
||||
'---',
|
||||
'',
|
||||
'## Excalidraw Canvas — Skill Directive',
|
||||
'',
|
||||
EXCALIDRAW_DIRECTIVE_BODY,
|
||||
'',
|
||||
].join('\n');
|
||||
}
|
||||
|
||||
function writeInstructionDirective(filePath: string, format: 'claude-md' | 'cursor-mdc'): void {
|
||||
const dir = path.dirname(filePath);
|
||||
if (!fs.existsSync(dir)) {
|
||||
fs.mkdirSync(dir, { recursive: true });
|
||||
}
|
||||
|
||||
if (format === 'cursor-mdc') {
|
||||
// Cursor .mdc files are standalone — write/overwrite the whole file
|
||||
fs.writeFileSync(filePath, buildCursorMdc(), 'utf-8');
|
||||
return;
|
||||
}
|
||||
|
||||
// For claude-md: append or replace the marked section
|
||||
let content = '';
|
||||
if (fs.existsSync(filePath)) {
|
||||
content = fs.readFileSync(filePath, 'utf-8');
|
||||
}
|
||||
|
||||
const section = buildClaudeMdSection();
|
||||
const startIdx = content.indexOf(DIRECTIVE_MARKER_START);
|
||||
const endIdx = content.indexOf(DIRECTIVE_MARKER_END);
|
||||
|
||||
if (startIdx !== -1 && endIdx !== -1) {
|
||||
// Replace existing section
|
||||
content = content.slice(0, startIdx) + section + content.slice(endIdx + DIRECTIVE_MARKER_END.length);
|
||||
} else {
|
||||
// Append with spacing
|
||||
const trimmed = content.trimEnd();
|
||||
content = trimmed + (trimmed ? '\n\n' : '') + section + '\n';
|
||||
}
|
||||
|
||||
fs.writeFileSync(filePath, content, 'utf-8');
|
||||
}
|
||||
|
||||
// ── Phase 3: MCP Configuration ──────────────────────────────
|
||||
|
||||
async function phaseMcpConfig(rl: readline.Interface): Promise<void> {
|
||||
@@ -450,6 +549,17 @@ export async function runUpdate(): Promise<void> {
|
||||
copyDirSync(skillSource, inst.path);
|
||||
ok(`Updated ${inst.agent.name} (${inst.scope}) — ${inst.path}`);
|
||||
updated++;
|
||||
|
||||
// Update instruction directive
|
||||
if (inst.agent.instructionConfig) {
|
||||
const instrPath = inst.agent.instructionConfig[inst.scope];
|
||||
try {
|
||||
writeInstructionDirective(instrPath, inst.agent.instructionConfig.format);
|
||||
ok(`Skill directive updated in ${instrPath}`);
|
||||
} catch (instrErr) {
|
||||
warn(`Could not update directive in ${instrPath}: ${(instrErr as Error).message}`);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
fail(`Failed to update ${inst.path}: ${(err as Error).message}`);
|
||||
}
|
||||
@@ -484,6 +594,17 @@ export async function runUpdate(): Promise<void> {
|
||||
fs.mkdirSync(destDir, { recursive: true });
|
||||
copyDirSync(skillSource, destDir);
|
||||
ok(`Installed to ${destDir}`);
|
||||
|
||||
// Write instruction directive
|
||||
if (agent.instructionConfig) {
|
||||
const instrPath = agent.instructionConfig[scope];
|
||||
try {
|
||||
writeInstructionDirective(instrPath, agent.instructionConfig.format);
|
||||
ok(`Skill directive added to ${instrPath}`);
|
||||
} catch (instrErr) {
|
||||
warn(`Could not write directive to ${instrPath}: ${(instrErr as Error).message}`);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
fail(`Failed to install to ${destDir}: ${(err as Error).message}`);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user