drafted 1.11.35 → 1.11.37
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/mcp/server.mjs +22 -26
- package/package.json +1 -3
- package/src/shared/excalidraw.mjs +0 -61
package/mcp/server.mjs
CHANGED
|
@@ -19,7 +19,7 @@ import { z } from 'zod';
|
|
|
19
19
|
import { registerAppResource, RESOURCE_MIME_TYPE } from '@modelcontextprotocol/ext-apps/server';
|
|
20
20
|
import WebSocket from 'ws';
|
|
21
21
|
import { LAYERS } from '../src/shared/constants.mjs';
|
|
22
|
-
import { emptyExcalidrawScene,
|
|
22
|
+
import { emptyExcalidrawScene, stringifyExcalidrawScene } from '../src/shared/excalidraw.mjs';
|
|
23
23
|
import { createGateState, markSearched, g1Block, g2Block, g3Block, selectWithinBudget, wouldExceedBudget, budgetError, formatWikiIndex, PROJECT_CONTEXT_BUDGET_CHARS } from './gates.mjs';
|
|
24
24
|
import { loadPersistedProject, savePersistedProject } from './active-project-store.mjs';
|
|
25
25
|
|
|
@@ -701,12 +701,12 @@ function schedulePendingAuthPoll(delayMs = 2000) {
|
|
|
701
701
|
}
|
|
702
702
|
|
|
703
703
|
// Cookie for normal tool operations. Deliberately uses ONLY this instance's
|
|
704
|
-
//
|
|
705
|
-
// ~/.drafted/auth.json.
|
|
706
|
-
// (
|
|
707
|
-
//
|
|
708
|
-
//
|
|
709
|
-
//
|
|
704
|
+
// MCP requests carry a cloned child session (getState().sessionId) minted from the
|
|
705
|
+
// shared root login in ~/.drafted/auth.json. NOTE: org isolation is per-REQUEST via
|
|
706
|
+
// the X-Drafted-Org header (server: effectiveOrgId), NOT via the session — the old
|
|
707
|
+
// "clone per process so one agent's org switch doesn't move another's" model is
|
|
708
|
+
// DEPRECATED. The clone remains only to give the agent a stable surface identity
|
|
709
|
+
// (one playful tab name across reconnects/restarts — see cloneSession).
|
|
710
710
|
function getAuthHeaders() {
|
|
711
711
|
const sid = getState().sessionId;
|
|
712
712
|
const headers = {};
|
|
@@ -719,22 +719,21 @@ function getAuthHeaders() {
|
|
|
719
719
|
return headers;
|
|
720
720
|
}
|
|
721
721
|
|
|
722
|
-
// Mint
|
|
723
|
-
//
|
|
724
|
-
//
|
|
725
|
-
//
|
|
726
|
-
// agents. Returns true once a per-instance session is set.
|
|
722
|
+
// Mint (or reuse) a child session from the shared root login. The server dedups by
|
|
723
|
+
// (user, agentKey), so the SAME session comes back across reconnects/restarts → one
|
|
724
|
+
// stable surface + one playful tab name. Org isolation is per-request (X-Drafted-Org),
|
|
725
|
+
// NOT per-session, so reusing one child session across processes is safe.
|
|
727
726
|
async function cloneSession() {
|
|
728
727
|
if (getState().sessionId) return true;
|
|
729
728
|
const bootstrapId = getBootstrapSessionId();
|
|
730
729
|
if (!bootstrapId) return false;
|
|
731
730
|
|
|
732
731
|
try {
|
|
733
|
-
//
|
|
734
|
-
//
|
|
735
|
-
//
|
|
736
|
-
//
|
|
737
|
-
const agentKey = (process.env.DRAFTED_AGENT_NAME || '').trim();
|
|
732
|
+
// Named agents key on DRAFTED_AGENT_NAME; anonymous agents key on the root login
|
|
733
|
+
// id (stable per machine) so they ALSO keep one stable session/name across restarts
|
|
734
|
+
// instead of churning a fresh session every process. (Org is per-request, so a
|
|
735
|
+
// shared child session does NOT cause cross-agent org clobbering.)
|
|
736
|
+
const agentKey = (process.env.DRAFTED_AGENT_NAME || '').trim() || bootstrapId;
|
|
738
737
|
const res = await fetch(`${getServerUrl()}/auth/session/clone`, {
|
|
739
738
|
method: 'POST',
|
|
740
739
|
headers: { 'Content-Type': 'application/json', Cookie: `gc_session=${bootstrapId}` },
|
|
@@ -1316,8 +1315,8 @@ async function consumePendingDeviceCode() {
|
|
|
1316
1315
|
const data = await res.json();
|
|
1317
1316
|
if (data.status === 'approved' && data.sessionId) {
|
|
1318
1317
|
// data.sessionId is the shared ROOT login. Persist it as the machine-level
|
|
1319
|
-
// credential, then mint
|
|
1320
|
-
// the
|
|
1318
|
+
// credential, then mint a child session from it (mirrors the `auth login` tool)
|
|
1319
|
+
// so the agent gets a stable surface identity (see cloneSession).
|
|
1321
1320
|
persistAuthSession(data);
|
|
1322
1321
|
clearPendingDeviceCode();
|
|
1323
1322
|
getState().sessionId = null;
|
|
@@ -2047,7 +2046,6 @@ tool('frame', 'Frame CRUD in the ACTIVE PROJECT. Dispatch by `action`: read (by
|
|
|
2047
2046
|
content: z.string().optional().describe('[write] HTML/markdown/text for Drafted inline frames. [write_doc_content|append_doc_content] native Google Doc body text. Do not use action=write content to populate Google Doc/Slide frames.'),
|
|
2048
2047
|
excalidraw_data: z.any().optional().describe('[write_excalidraw] Excalidraw scene JSON object or JSON string. Defaults to an empty scene.'),
|
|
2049
2048
|
state: z.any().optional().describe('[set_state] App-frame state object (JSON) to persist for a deployed windowType:"app" frame — e.g. {specText:"..."} for the AS/NZS electrical app. The canvas hydrates the app from this on load (the host posts a "hydrate" message with it when the frame mounts), so you can deploy a generic app frame once and drive it with data afterwards. Max 64KB. Frame must be an app frame.'),
|
|
2050
|
-
mermaid: z.string().optional().describe('[write_excalidraw] Mermaid source to convert into an editable Excalidraw scene.'),
|
|
2051
2049
|
file_path: z.string().optional().describe('[write] absolute path to a local file to upload. Mutually exclusive with content/base64/googleType.'),
|
|
2052
2050
|
base64: z.string().optional().describe('[write] base64-encoded binary content. Mutually exclusive with content/file_path/googleType. Use with content_type when known.'),
|
|
2053
2051
|
content_type: z.string().optional().describe('[write + base64] MIME type for base64 binary content, e.g. image/png, application/pdf. Defaults from the path extension or application/octet-stream.'),
|
|
@@ -2439,13 +2437,12 @@ tool('frame', 'Frame CRUD in the ACTIVE PROJECT. Dispatch by `action`: read (by
|
|
|
2439
2437
|
});
|
|
2440
2438
|
}
|
|
2441
2439
|
case 'write_excalidraw': {
|
|
2442
|
-
const { path, excalidraw_data,
|
|
2440
|
+
const { path, excalidraw_data, width, height, color } = args;
|
|
2443
2441
|
if (!path) throw new Error('path required for action=write_excalidraw');
|
|
2444
|
-
if (excalidraw_data != null && mermaid) throw new Error('Provide only one of excalidraw_data or mermaid');
|
|
2445
2442
|
const parts = path.replace(/^\/+/, '').split('/');
|
|
2446
2443
|
if (parts.length !== 3) throw new Error('Path must be /{layer}/{lane}/{filename}');
|
|
2447
2444
|
const filename = parts[2].toLowerCase().endsWith('.excalidraw') ? parts[2] : parts[2] + '.excalidraw';
|
|
2448
|
-
const scene =
|
|
2445
|
+
const scene = excalidraw_data ?? emptyExcalidrawScene();
|
|
2449
2446
|
const body = { content: stringifyExcalidrawScene(scene) };
|
|
2450
2447
|
if (width) body.width = width;
|
|
2451
2448
|
if (height) body.height = height;
|
|
@@ -3463,13 +3460,12 @@ tool('wiki', 'Per-org wiki. Markdown pages with paths as hierarchy. You and othe
|
|
|
3463
3460
|
// `LINE+ID|content`). The server applies the ops via the same
|
|
3464
3461
|
// hashline algorithm — no client-side re-hashing, no algorithm drift.
|
|
3465
3462
|
case 'write_excalidraw': {
|
|
3466
|
-
const { path, excalidraw_data,
|
|
3463
|
+
const { path, excalidraw_data, width, height, color } = args;
|
|
3467
3464
|
if (!path) throw new Error('path required for action=write_excalidraw');
|
|
3468
|
-
if (excalidraw_data != null && mermaid) throw new Error('Provide only one of excalidraw_data or mermaid');
|
|
3469
3465
|
const parts = path.replace(/^\/+/, '').split('/');
|
|
3470
3466
|
if (parts.length !== 3) throw new Error('Path must be /{layer}/{lane}/{filename}');
|
|
3471
3467
|
const filename = parts[2].toLowerCase().endsWith('.excalidraw') ? parts[2] : parts[2] + '.excalidraw';
|
|
3472
|
-
const scene =
|
|
3468
|
+
const scene = excalidraw_data ?? emptyExcalidrawScene();
|
|
3473
3469
|
const body = { content: stringifyExcalidrawScene(scene) };
|
|
3474
3470
|
if (width) body.width = width;
|
|
3475
3471
|
if (height) body.height = height;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "drafted",
|
|
3
|
-
"version": "1.11.
|
|
3
|
+
"version": "1.11.37",
|
|
4
4
|
"description": "Drafted — visual thinking surface for humans and AI agents. Renders HTML, markdown, images, and code as frames on a zoomable canvas, with MCP tools for AI agents and real-time sync for humans.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -45,7 +45,6 @@
|
|
|
45
45
|
"@clerk/express": "^2.1.4",
|
|
46
46
|
"@clerk/mcp-tools": "^0.3.1",
|
|
47
47
|
"@excalidraw/excalidraw": "^0.18.1",
|
|
48
|
-
"@excalidraw/mermaid-to-excalidraw": "^2.2.2",
|
|
49
48
|
"@modelcontextprotocol/ext-apps": "^1.6.0",
|
|
50
49
|
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
51
50
|
"@sentry/browser": "^10.53.1",
|
|
@@ -59,7 +58,6 @@
|
|
|
59
58
|
"esbuild": "^0.28.0",
|
|
60
59
|
"exceljs": "^4.4.0",
|
|
61
60
|
"express": "^4.18.2",
|
|
62
|
-
"jsdom": "^27.0.1",
|
|
63
61
|
"jszip": "^3.10.1",
|
|
64
62
|
"lucide": "^1.16.0",
|
|
65
63
|
"mdast-util-from-markdown": "^2.0.3",
|
|
@@ -38,64 +38,3 @@ export function normalizeExcalidrawScene(input) {
|
|
|
38
38
|
export function stringifyExcalidrawScene(input) {
|
|
39
39
|
return JSON.stringify(normalizeExcalidrawScene(input), null, 2);
|
|
40
40
|
}
|
|
41
|
-
|
|
42
|
-
function restoreMermaidDom(previous, dom) {
|
|
43
|
-
for (const [key, value] of Object.entries(previous)) {
|
|
44
|
-
if (value === undefined) {
|
|
45
|
-
try { delete globalThis[key]; } catch {}
|
|
46
|
-
} else if (key === 'navigator') {
|
|
47
|
-
Object.defineProperty(globalThis, 'navigator', { value, configurable: true });
|
|
48
|
-
} else {
|
|
49
|
-
globalThis[key] = value;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
if (dom) dom.window.close();
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
export async function excalidrawSceneFromMermaid(mermaid) {
|
|
56
|
-
let dom;
|
|
57
|
-
const previous = {
|
|
58
|
-
window: globalThis.window,
|
|
59
|
-
document: globalThis.document,
|
|
60
|
-
DOMParser: globalThis.DOMParser,
|
|
61
|
-
XMLSerializer: globalThis.XMLSerializer,
|
|
62
|
-
Element: globalThis.Element,
|
|
63
|
-
SVGElement: globalThis.SVGElement,
|
|
64
|
-
navigator: globalThis.navigator,
|
|
65
|
-
CSSStyleSheet: globalThis.CSSStyleSheet,
|
|
66
|
-
};
|
|
67
|
-
try {
|
|
68
|
-
if (typeof document === 'undefined') {
|
|
69
|
-
const { JSDOM } = await import('jsdom');
|
|
70
|
-
dom = new JSDOM('<!doctype html><html><body></body></html>');
|
|
71
|
-
globalThis.window = dom.window;
|
|
72
|
-
globalThis.document = dom.window.document;
|
|
73
|
-
globalThis.DOMParser = dom.window.DOMParser;
|
|
74
|
-
globalThis.XMLSerializer = dom.window.XMLSerializer;
|
|
75
|
-
globalThis.Element = dom.window.Element;
|
|
76
|
-
globalThis.SVGElement = dom.window.SVGElement;
|
|
77
|
-
if (globalThis.SVGElement && !globalThis.SVGElement.prototype.getBBox) {
|
|
78
|
-
globalThis.SVGElement.prototype.getBBox = function () {
|
|
79
|
-
const text = this.textContent || '';
|
|
80
|
-
return { x: 0, y: 0, width: Math.max(24, text.length * 8), height: 20 };
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
|
-
Object.defineProperty(globalThis, 'navigator', { value: dom.window.navigator, configurable: true });
|
|
84
|
-
globalThis.CSSStyleSheet = class {
|
|
85
|
-
constructor() { this.cssRules = []; }
|
|
86
|
-
replaceSync() { this.cssRules = []; }
|
|
87
|
-
insertRule(rule) { this.cssRules.push({ cssText: rule }); }
|
|
88
|
-
};
|
|
89
|
-
}
|
|
90
|
-
const { parseMermaidToExcalidraw } = await import('@excalidraw/mermaid-to-excalidraw');
|
|
91
|
-
const result = await parseMermaidToExcalidraw(mermaid);
|
|
92
|
-
return {
|
|
93
|
-
...emptyExcalidrawScene(),
|
|
94
|
-
elements: result.elements || [],
|
|
95
|
-
files: result.files || {},
|
|
96
|
-
appState: { viewBackgroundColor: '#ffffff' },
|
|
97
|
-
};
|
|
98
|
-
} finally {
|
|
99
|
-
if (dom) restoreMermaidDom(previous, dom);
|
|
100
|
-
}
|
|
101
|
-
}
|