Compare commits

...

4 Commits

Author SHA1 Message Date
dayuan.jiang
6ed05ed24d chore: always bundle latest draw.io version in Electron builds
Remove pinned v29.3.5 tag so the build always clones the latest draw.io.
This adds the Animated GIF export and other new features to the Electron app.

Closes #770
2026-04-06 10:13:40 +09:00
Dayuan Jiang
31819f413c fix: add 10MB body size limit to MCP HTTP endpoints (#791)
All three POST handlers (/api/state, /api/restore, /api/history-svg)
now use a shared readBody() helper that enforces a 10MB limit and
returns 413 if exceeded, preventing memory exhaustion from oversized
requests.

Bumps @next-ai-drawio/mcp-server to 0.1.19.
2026-04-06 09:14:20 +09:00
Dayuan Jiang
41c410c2ba fix: bind MCP server HTTP to 127.0.0.1 only (#787)
The embedded HTTP sidecar was using server.listen(port) without a host
argument, which defaults to 0.0.0.0 (all interfaces). This exposed the
server to the local network. Now explicitly binds to 127.0.0.1.

Also excludes release/ from tsconfig to fix pre-existing TS errors.

Bumps @next-ai-drawio/mcp-server to 0.1.18.
2026-04-06 09:04:38 +09:00
Dayuan Jiang
f593901fee fix: zoom reset on drag and IndexedDB version conflict (#776)
- Fix zoom resetting when dragging items (#775): removed useEffect that
  called load() on every autosave-triggered chartXML change, which reset
  the viewport. Moved diagram restore logic to onDrawioLoad where it
  only fires on remount.

- Fix IndexedDB VersionError: template-storage.ts shared the same DB
  name as session-storage.ts but at version 2, causing session storage
  to fail with "requested version (1) < existing version (2)". Give
  templates their own DB ("next-ai-drawio-templates").
2026-04-03 16:44:42 +09:00
6 changed files with 38 additions and 56 deletions

View File

@@ -37,7 +37,7 @@ jobs:
- name: Download draw.io static files for offline use
run: |
rm -rf public/drawio
git clone --depth 1 --branch v29.3.5 https://github.com/jgraph/drawio.git /tmp/drawio
git clone --depth 1 https://github.com/jgraph/drawio.git /tmp/drawio
mkdir -p public/drawio
cp -r /tmp/drawio/src/main/webapp/* public/drawio/
rm -rf public/drawio/WEB-INF
@@ -70,7 +70,7 @@ jobs:
shell: bash
run: |
rm -rf public/drawio
git clone --depth 1 --branch v29.3.5 https://github.com/jgraph/drawio.git /tmp/drawio
git clone --depth 1 https://github.com/jgraph/drawio.git /tmp/drawio
mkdir -p public/drawio
cp -r /tmp/drawio/src/main/webapp/* public/drawio/
rm -rf public/drawio/WEB-INF

View File

@@ -65,6 +65,10 @@ export function DiagramProvider({ children }: { children: React.ReactNode }) {
if (hasCalledOnLoadRef.current) return
hasCalledOnLoadRef.current = true
setIsDrawioReady(true)
// Restore diagram after remount (e.g., theme/UI change)
if (drawioRef.current && isRealDiagram(chartXMLRef.current)) {
drawioRef.current.load({ xml: chartXMLRef.current })
}
}
const resetDrawioReady = () => {
@@ -77,24 +81,6 @@ export function DiagramProvider({ children }: { children: React.ReactNode }) {
chartXMLRef.current = chartXML
}, [chartXML])
// Restore diagram when DrawIO becomes ready after remount (e.g., theme/UI change)
// Also restore when chartXML changes while DrawIO is ready (e.g., session loaded after iframe ready)
const lastRestoredXmlRef = useRef<string>("")
useEffect(() => {
if (!isDrawioReady || !drawioRef.current) return
// Only load if we have a real diagram and it's different from what we already loaded
if (
isRealDiagram(chartXML) &&
chartXML !== lastRestoredXmlRef.current
) {
lastRestoredXmlRef.current = chartXML
drawioRef.current.load({ xml: chartXML })
} else if (!isRealDiagram(chartXML)) {
// Reset when diagram is cleared so a future restore can re-load the same XML.
lastRestoredXmlRef.current = ""
}
}, [isDrawioReady, chartXML])
// Track if we're expecting an export for file save (stores raw export data)
const saveResolverRef = useRef<{
resolver: ((data: string) => void) | null

View File

@@ -2,8 +2,8 @@ import { type DBSchema, type IDBPDatabase, openDB } from "idb"
import { nanoid } from "nanoid"
// Constants
const DB_NAME = "next-ai-drawio"
const DB_VERSION = 2
const DB_NAME = "next-ai-drawio-templates"
const DB_VERSION = 1
const STORE_NAME = "templates"
// Types
@@ -34,14 +34,6 @@ export type TemplateCreateInput = Pick<Template, "prompt"> &
>
interface TemplateDB extends DBSchema {
sessions: {
key: string
value: {
id: string
[key: string]: unknown
}
indexes: { "by-updated": number }
}
templates: {
key: string
value: Template
@@ -98,14 +90,6 @@ async function getDB(): Promise<IDBPDatabase<TemplateDB>> {
dbPromise = openDB<TemplateDB>(DB_NAME, DB_VERSION, {
upgrade(db, oldVersion) {
if (oldVersion < 1) {
if (!db.objectStoreNames.contains("sessions")) {
const sessionStore = db.createObjectStore("sessions", {
keyPath: "id",
})
sessionStore.createIndex("by-updated", "updatedAt")
}
}
if (oldVersion < 2) {
if (!db.objectStoreNames.contains(STORE_NAME)) {
const templateStore = db.createObjectStore(STORE_NAME, {
keyPath: "id",

View File

@@ -1,6 +1,6 @@
{
"name": "@next-ai-drawio/mcp-server",
"version": "0.1.17",
"version": "0.1.19",
"description": "MCP server for Next AI Draw.io - AI-powered diagram generation with real-time browser preview",
"type": "module",
"main": "dist/index.js",

View File

@@ -4,6 +4,29 @@
*/
import http from "node:http"
const MAX_BODY_BYTES = 10 * 1024 * 1024 // 10 MiB
function readBody(
req: http.IncomingMessage,
res: http.ServerResponse,
cb: (body: string) => void,
): void {
let body = ""
let size = 0
req.on("data", (chunk: Buffer) => {
size += chunk.length
if (size > MAX_BODY_BYTES) {
res.writeHead(413, { "Content-Type": "application/json" })
res.end(JSON.stringify({ error: "Payload too large" }))
req.destroy()
return
}
body += chunk
})
req.on("end", () => cb(body))
}
import {
addHistory,
clearHistory,
@@ -155,7 +178,7 @@ export function startHttpServer(port = 6002): Promise<number> {
}
})
server.listen(port, () => {
server.listen(port, "127.0.0.1", () => {
serverPort = port
log.info(`HTTP server running on http://localhost:${port}`)
resolve(port)
@@ -266,11 +289,7 @@ function handleStateApi(
}),
)
} else if (req.method === "POST") {
let body = ""
req.on("data", (chunk) => {
body += chunk
})
req.on("end", () => {
readBody(req, res, (body) => {
try {
const data = JSON.parse(body)
const { sessionId } = data
@@ -347,11 +366,7 @@ function handleRestoreApi(
return
}
let body = ""
req.on("data", (chunk) => {
body += chunk
})
req.on("end", () => {
readBody(req, res, (body) => {
try {
const { sessionId, index } = JSON.parse(body)
if (!sessionId || index === undefined) {
@@ -393,11 +408,7 @@ function handleHistorySvgApi(
return
}
let body = ""
req.on("data", (chunk) => {
body += chunk
})
req.on("end", () => {
readBody(req, res, (body) => {
try {
const { sessionId, svg } = JSON.parse(body)
if (!sessionId || !svg) {

View File

@@ -35,6 +35,7 @@
"packages",
"electron",
"electron-standalone",
"dist-electron"
"dist-electron",
"release"
]
}