Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4e410f1205 | ||
|
|
71b2a55231 |
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@sanjibdevnath/mcp-excalidraw-local",
|
||||
"version": "1.5.0",
|
||||
"version": "1.5.1",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@sanjibdevnath/mcp-excalidraw-local",
|
||||
"version": "1.5.0",
|
||||
"version": "1.5.1",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@sanjibdevnath/mcp-excalidraw-local",
|
||||
"version": "1.5.0",
|
||||
"version": "1.5.1",
|
||||
"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",
|
||||
|
||||
@@ -26,7 +26,7 @@ Before creating any elements, load the user's diagram preferences. These control
|
||||
| 1 (highest) | Session | In-memory (set via prompt during this conversation) | No — current session only |
|
||||
| 2 | Folder | `.claude/excalidraw-preferences.json` in the current project root | Yes — per-project |
|
||||
| 3 | Global | `~/.claude/skills/excalidraw-skill/preferences.json` | Yes — all projects |
|
||||
| 4 (lowest) | Hardcoded | Server defaults (fontFamily: 1, roughness: 0, fontSize: 20, strokeWidth: 2) | — |
|
||||
| 4 (lowest) | Hardcoded | Server defaults (fontFamily: 5, roughness: 0, fontSize: 20, strokeWidth: 2) | — |
|
||||
|
||||
### How to Load
|
||||
|
||||
@@ -45,11 +45,11 @@ If no preferences file exists at either location, **prompt the user before drawi
|
||||
|
||||
Ask these questions (use `AskUserQuestion` tool if available, otherwise ask inline):
|
||||
|
||||
1. **Font family** — Which font for all text?
|
||||
- Excalifont (hand-drawn) = 1
|
||||
1. **Font family** — Which font for all text? _(IDs from `src/font-families.json`)_
|
||||
- Excalifont (hand-drawn) = 5
|
||||
- Helvetica (sans-serif) = 2
|
||||
- Cascadia (monospace) = 3
|
||||
- Comic Shanns = 4
|
||||
- Comic Shanns = 8
|
||||
- Nunito = 6
|
||||
- Lilita One = 7
|
||||
|
||||
|
||||
@@ -1,16 +1,8 @@
|
||||
{
|
||||
"_comment": "Excalidraw MCP user preferences. Copy to preferences.json to activate.",
|
||||
"_fontReference": {
|
||||
"1": "Excalifont (hand-drawn)",
|
||||
"2": "Helvetica (sans-serif)",
|
||||
"3": "Cascadia (monospace)",
|
||||
"4": "Comic Shanns",
|
||||
"5": "Liberation Sans",
|
||||
"6": "Nunito",
|
||||
"7": "Lilita One"
|
||||
},
|
||||
"_fontReference": "See src/font-families.json for canonical font ID → name mapping.",
|
||||
"defaults": {
|
||||
"fontFamily": 1,
|
||||
"fontFamily": 5,
|
||||
"fontSize": 20,
|
||||
"roughness": 0,
|
||||
"strokeWidth": 2
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"fonts": [
|
||||
{ "id": 5, "name": "Excalifont", "label": "Excalifont (hand-drawn)", "aliases": ["excalifont", "hand-drawn"] },
|
||||
{ "id": 2, "name": "Helvetica", "label": "Helvetica (sans-serif)", "aliases": ["helvetica", "arial", "sans-serif"] },
|
||||
{ "id": 3, "name": "Cascadia", "label": "Cascadia (monospace)", "aliases": ["cascadia", "monospace", "courier"] },
|
||||
{ "id": 8, "name": "Comic Shanns", "label": "Comic Shanns", "aliases": ["comic shanns", "comic sans"] },
|
||||
{ "id": 6, "name": "Nunito", "label": "Nunito", "aliases": ["nunito"] },
|
||||
{ "id": 7, "name": "Lilita One", "label": "Lilita One", "aliases": ["lilita one"] },
|
||||
{ "id": 9, "name": "Liberation Sans", "label": "Liberation Sans", "aliases": ["liberation sans"], "legacy": true },
|
||||
{ "id": 1, "name": "Virgil", "label": "Virgil (legacy)", "aliases": ["virgil"], "legacy": true }
|
||||
],
|
||||
"defaultFontFamily": 5
|
||||
}
|
||||
+6
-4
@@ -27,7 +27,9 @@ import {
|
||||
ExcalidrawElementType,
|
||||
validateElement,
|
||||
normalizeFontFamily,
|
||||
files as globalFiles
|
||||
files as globalFiles,
|
||||
DEFAULT_FONT_FAMILY,
|
||||
FONT_FAMILY_DESCRIPTION,
|
||||
} from './types.js';
|
||||
import fetch from 'node-fetch';
|
||||
import { startCanvasServer, stopCanvasServer } from './server.js';
|
||||
@@ -75,7 +77,7 @@ interface ExcalidrawPreferences {
|
||||
}
|
||||
|
||||
const HARDCODED_DEFAULTS: ExcalidrawPreferences = {
|
||||
fontFamily: 1,
|
||||
fontFamily: DEFAULT_FONT_FAMILY,
|
||||
fontSize: 20,
|
||||
roughness: 0,
|
||||
strokeWidth: 2,
|
||||
@@ -459,7 +461,7 @@ const tools: Tool[] = [
|
||||
opacity: { type: 'number' },
|
||||
text: { type: 'string' },
|
||||
fontSize: { type: 'number' },
|
||||
fontFamily: { type: ['string', 'number'], description: 'Font family: 1=Excalifont (hand-drawn), 2=Helvetica (sans-serif), 3=Cascadia (monospace), 4=Comic Shanns, 5=Liberation Sans, 6=Nunito, 7=Lilita One. Accepts name strings too.' },
|
||||
fontFamily: { type: ['string', 'number'], description: FONT_FAMILY_DESCRIPTION },
|
||||
startElementId: { type: 'string', description: 'For arrows: ID of the element to bind the arrow start to. Arrow auto-routes to element edge.' },
|
||||
endElementId: { type: 'string', description: 'For arrows: ID of the element to bind the arrow end to. Arrow auto-routes to element edge.' },
|
||||
endArrowhead: { type: 'string', description: 'Arrowhead style at end: arrow, bar, dot, triangle, or null' },
|
||||
@@ -690,7 +692,7 @@ const tools: Tool[] = [
|
||||
opacity: { type: 'number' },
|
||||
text: { type: 'string' },
|
||||
fontSize: { type: 'number' },
|
||||
fontFamily: { type: ['string', 'number'], description: 'Font family: 1=Excalifont, 2=Helvetica, 3=Cascadia, 4=Comic Shanns, 5=Liberation Sans, 6=Nunito, 7=Lilita One. Accepts name strings too.' },
|
||||
fontFamily: { type: ['string', 'number'], description: FONT_FAMILY_DESCRIPTION },
|
||||
startElementId: { type: 'string', description: 'For arrows: ID of element to bind arrow start to' },
|
||||
endElementId: { type: 'string', description: 'For arrows: ID of element to bind arrow end to' },
|
||||
endArrowhead: { type: 'string', description: 'Arrowhead style at end: arrow, bar, dot, triangle, or null' },
|
||||
|
||||
+7
-10
@@ -13,6 +13,7 @@ import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as os from 'os';
|
||||
import { execSync } from 'child_process';
|
||||
import { FONT_FAMILIES, DEFAULT_FONT_FAMILY } from './types.js';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
@@ -184,14 +185,10 @@ async function phaseEnvironment(rl: readline.Interface): Promise<boolean> {
|
||||
|
||||
// ── Preference Setup ─────────────────────────────────────────
|
||||
|
||||
const FONT_OPTIONS: { value: number; label: string }[] = [
|
||||
{ value: 1, label: 'Excalifont (hand-drawn)' },
|
||||
{ value: 2, label: 'Helvetica (sans-serif)' },
|
||||
{ value: 3, label: 'Cascadia (monospace)' },
|
||||
{ value: 4, label: 'Comic Shanns' },
|
||||
{ value: 6, label: 'Nunito' },
|
||||
{ value: 7, label: 'Lilita One' },
|
||||
];
|
||||
// Derived from FONT_FAMILIES in types.ts — single source of truth
|
||||
const FONT_OPTIONS = FONT_FAMILIES
|
||||
.filter(f => !f.legacy)
|
||||
.map(f => ({ value: f.id, label: f.label }));
|
||||
|
||||
const ROUGHNESS_OPTIONS: { value: number; label: string }[] = [
|
||||
{ value: 0, label: 'Clean / professional' },
|
||||
@@ -245,12 +242,12 @@ async function phasePreferences(rl: readline.Interface, phaseLabel: string): Pro
|
||||
// Font
|
||||
process.stdout.write('\n Font family:\n');
|
||||
FONT_OPTIONS.forEach((f, i) => {
|
||||
const marker = f.value === 1 ? ' (default)' : '';
|
||||
const marker = f.value === DEFAULT_FONT_FAMILY ? ' (default)' : '';
|
||||
process.stdout.write(` ${CYAN}[${i + 1}]${RESET} ${f.label}${marker}\n`);
|
||||
});
|
||||
const fontAnswer = (await ask(rl, 'Choose [1]: ')).trim();
|
||||
const fontIdx = fontAnswer === '' ? 0 : parseInt(fontAnswer, 10) - 1;
|
||||
const fontFamily = (fontIdx >= 0 && fontIdx < FONT_OPTIONS.length) ? FONT_OPTIONS[fontIdx]!.value : 1;
|
||||
const fontFamily = (fontIdx >= 0 && fontIdx < FONT_OPTIONS.length) ? FONT_OPTIONS[fontIdx]!.value : DEFAULT_FONT_FAMILY;
|
||||
|
||||
// Roughness
|
||||
process.stdout.write('\n Diagram style:\n');
|
||||
|
||||
+30
-18
@@ -311,24 +311,36 @@ export interface ExcalidrawFile {
|
||||
// In-memory file storage (image files are too large for SQLite row storage)
|
||||
export const files = new Map<string, ExcalidrawFile>();
|
||||
|
||||
// Font family normalization: Excalidraw expects numeric IDs, but agents
|
||||
// often send string names. Map common names to their numeric equivalents.
|
||||
const FONT_FAMILY_MAP: Record<string, number> = {
|
||||
'virgil': 1,
|
||||
'hand-drawn': 1,
|
||||
'excalifont': 1,
|
||||
'helvetica': 2,
|
||||
'arial': 2,
|
||||
'sans-serif': 2,
|
||||
'cascadia': 3,
|
||||
'monospace': 3,
|
||||
'courier': 3,
|
||||
'comic shanns': 4,
|
||||
'comic sans': 4,
|
||||
'liberation sans': 5,
|
||||
'nunito': 6,
|
||||
'lilita one': 7,
|
||||
};
|
||||
// ── Font families — single source of truth ──────────────────────────────
|
||||
// IDs match the @excalidraw/excalidraw FONT_FAMILY constant.
|
||||
// The canonical data lives in font-families.json; every other file derives from it.
|
||||
import fontData from './font-families.json' with { type: 'json' };
|
||||
|
||||
export interface FontFamilyDef {
|
||||
id: number;
|
||||
name: string;
|
||||
label: string;
|
||||
aliases: string[];
|
||||
legacy?: boolean; // hidden from setup menus / tool docs
|
||||
}
|
||||
|
||||
export const FONT_FAMILIES: FontFamilyDef[] = fontData.fonts as FontFamilyDef[];
|
||||
|
||||
export const DEFAULT_FONT_FAMILY: number = fontData.defaultFontFamily;
|
||||
|
||||
// Derived: description string for MCP tool schemas
|
||||
export const FONT_FAMILY_DESCRIPTION =
|
||||
'Font family: ' +
|
||||
FONT_FAMILIES.filter(f => !f.legacy).map(f => `${f.id}=${f.name}`).join(', ') +
|
||||
'. Accepts name strings too.';
|
||||
|
||||
// Derived: string → number mapping for normalization
|
||||
const FONT_FAMILY_MAP: Record<string, number> = {};
|
||||
for (const font of FONT_FAMILIES) {
|
||||
for (const alias of font.aliases) {
|
||||
FONT_FAMILY_MAP[alias] = font.id;
|
||||
}
|
||||
}
|
||||
|
||||
export function normalizeFontFamily(value: string | number | undefined): number | undefined {
|
||||
if (value === undefined || value === null) return undefined;
|
||||
|
||||
Reference in New Issue
Block a user