drafted 1.11.15 → 1.11.17
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 +39 -7
- package/package.json +1 -1
package/mcp/server.mjs
CHANGED
|
@@ -135,7 +135,7 @@ const LOCAL_ONLY_PARAMS = ['file_path'];
|
|
|
135
135
|
// tool wrapper before the handler runs — so the stdio contract, the Zod runtime
|
|
136
136
|
// validation, and every handler stay byte-for-byte unchanged.
|
|
137
137
|
const REMOTE_JSON_STRING_PARAMS = {
|
|
138
|
-
frame: ['excalidraw_data', 'requests'],
|
|
138
|
+
frame: ['excalidraw_data', 'requests', 'state'],
|
|
139
139
|
wiki: ['frontmatter', 'pages'],
|
|
140
140
|
project: ['layers'],
|
|
141
141
|
template: ['layers'],
|
|
@@ -367,7 +367,7 @@ function tool(name, descOrSchema, schemaOrHandler, handler) {
|
|
|
367
367
|
const v = args[0][k];
|
|
368
368
|
if (typeof v === 'string' && v.trim()) {
|
|
369
369
|
try { args[0][k] = JSON.parse(v); }
|
|
370
|
-
catch { return err(new Error(`Param "${k}" must be a JSON-encoded ${['excalidraw_data', 'frontmatter', 'target', 'output'].includes(k) ? 'object' : 'array'} string; JSON.parse failed.`)); }
|
|
370
|
+
catch { return err(new Error(`Param "${k}" must be a JSON-encoded ${['excalidraw_data', 'frontmatter', 'target', 'output', 'state'].includes(k) ? 'object' : 'array'} string; JSON.parse failed.`)); }
|
|
371
371
|
}
|
|
372
372
|
}
|
|
373
373
|
}
|
|
@@ -1935,12 +1935,13 @@ tool('get_org', {
|
|
|
1935
1935
|
|
|
1936
1936
|
// ── Filesystem tools (direct HTTP to /api/fs) ─────────────────────
|
|
1937
1937
|
|
|
1938
|
-
tool('frame', 'Frame CRUD in the ACTIVE PROJECT. Dispatch by `action`: read (by path, frame URL, or UUID), write (new frame or overwrite), Google Sheet actions (`get_sheet`, `read_sheet_values`, `write_sheet_values`, `append_sheet_rows`, `clear_sheet_range`, `update_sheet`), Google Doc actions (`get_doc`, `read_doc_content`, `write_doc_content`, `append_doc_content`, `clear_doc_content`, `update_doc`), Google Slide actions (`get_slide`, `read_slide_content`, `write_slide_content`, `append_slides`, `clear_slides`, `update_slide`), write_excalidraw (native editable Excalidraw diagram), edit (hashline ops), mv (rename/move), anchor (mark as required-read for the layer), search (match frame names). Use project(action="open") first. For listing use `ls`, for deletion use `rm`.\n\n**Google Workspace native content:** Create or attach Google Docs/Sheets/Slides with `frame(action="write", googleType=...)`. After creating, immediately populate the native file using the matching write action in the same tool — do NOT leave it empty and do NOT tell the user you cannot write to it. For Sheets: `write_sheet_values` or `append_sheet_rows` (pass `path` or `googleId` from the create response). For Docs: `write_doc_content`/`append_doc_content`. For Slides: `write_slide_content`/`append_slides`. Read with `read_sheet_values`/`read_doc_content`/`read_slide_content`. Do NOT use inline `frame.write(content)` or hashline `frame.edit` to populate Google Workspace frames.\n\n**Write — content, binary, or Google Workspace frame:** ' + (isRemote ? 'Provide exactly one of `content` (HTML/markdown/text), `base64` (base64-encoded binary with optional `content_type`), or `googleType` (`google-doc`, `google-sheet`, `google-slide`).' : 'Provide exactly one of `content` (HTML/markdown/text), `file_path` (absolute local file), `base64` (base64-encoded binary with optional `content_type`), or `googleType` (`google-doc`, `google-sheet`, `google-slide`).') + ' Call get_org first; when `googleDrive.connected` is true, strongly prefer Google Workspace frames for docs, sheets, and slides in that org. For inline content, filename extension matters: use `.html` for complete HTML documents and `.md` for Markdown. Never place a full HTML document in a `.md` or extensionless frame. For a new Google file, pass `googleType` and optional `title`; for an existing Google file, pass `googleType` plus `url` or `googleId`. ' + (isRemote ? 'For binary frames (images, PDFs, videos), pass `base64` with the binary bytes.' : 'For binary frames (images, PDFs, videos), use `file_path` when the file is local to the MCP host, or `base64` when the caller already has binary bytes.') + '\n\n**Write — dimensions:** By default, frames use the layer\'s default size (e.g. 1440×900 for designs, 1440×3000 for wireframes). Often too large for small content. Use `autoSize: true` to measure HTML content and size to fit, or pass explicit `width`/`height`.', {
|
|
1939
|
-
action: z.enum(['read', 'write', 'write_sheet_values', 'read_sheet_values', 'append_sheet_rows', 'clear_sheet_range', 'get_sheet', 'update_sheet', 'get_doc', 'read_doc_content', 'write_doc_content', 'append_doc_content', 'clear_doc_content', 'update_doc', 'get_slide', 'read_slide_content', 'write_slide_content', 'append_slides', 'clear_slides', 'update_slide', 'create_office', 'read_office', 'edit_office', 'write_excalidraw', 'edit', 'mv', 'anchor', 'search', 'versions', 'read_version', 'restore_version']).describe('Operation to perform. Use native Doc/Slide actions for Google Docs/Slides; do not use inline write/edit for native Workspace content.'),
|
|
1938
|
+
tool('frame', 'Frame CRUD in the ACTIVE PROJECT. Dispatch by `action`: read (by path, frame URL, or UUID), write (new frame or overwrite), set_state / get_state (persist or read a deployed windowType:"app" frame\'s hydration state — push e.g. {specText} to drive a generic app frame with data after deploy; the canvas hydrates the app from it on load), Google Sheet actions (`get_sheet`, `read_sheet_values`, `write_sheet_values`, `append_sheet_rows`, `clear_sheet_range`, `update_sheet`), Google Doc actions (`get_doc`, `read_doc_content`, `write_doc_content`, `append_doc_content`, `clear_doc_content`, `update_doc`), Google Slide actions (`get_slide`, `read_slide_content`, `write_slide_content`, `append_slides`, `clear_slides`, `update_slide`), write_excalidraw (native editable Excalidraw diagram), edit (hashline ops), mv (rename/move), anchor (mark as required-read for the layer), search (match frame names). Use project(action="open") first. For listing use `ls`, for deletion use `rm`.\n\n**Google Workspace native content:** Create or attach Google Docs/Sheets/Slides with `frame(action="write", googleType=...)`. After creating, immediately populate the native file using the matching write action in the same tool — do NOT leave it empty and do NOT tell the user you cannot write to it. For Sheets: `write_sheet_values` or `append_sheet_rows` (pass `path` or `googleId` from the create response). For Docs: `write_doc_content`/`append_doc_content`. For Slides: `write_slide_content`/`append_slides`. Read with `read_sheet_values`/`read_doc_content`/`read_slide_content`. Do NOT use inline `frame.write(content)` or hashline `frame.edit` to populate Google Workspace frames.\n\n**Write — content, binary, or Google Workspace frame:** ' + (isRemote ? 'Provide exactly one of `content` (HTML/markdown/text), `base64` (base64-encoded binary with optional `content_type`), or `googleType` (`google-doc`, `google-sheet`, `google-slide`).' : 'Provide exactly one of `content` (HTML/markdown/text), `file_path` (absolute local file), `base64` (base64-encoded binary with optional `content_type`), or `googleType` (`google-doc`, `google-sheet`, `google-slide`).') + ' Call get_org first; when `googleDrive.connected` is true, strongly prefer Google Workspace frames for docs, sheets, and slides in that org. For inline content, filename extension matters: use `.html` for complete HTML documents and `.md` for Markdown. Never place a full HTML document in a `.md` or extensionless frame. For a new Google file, pass `googleType` and optional `title`; for an existing Google file, pass `googleType` plus `url` or `googleId`. ' + (isRemote ? 'For binary frames (images, PDFs, videos), pass `base64` with the binary bytes.' : 'For binary frames (images, PDFs, videos), use `file_path` when the file is local to the MCP host, or `base64` when the caller already has binary bytes.') + '\n\n**Write — dimensions:** By default, frames use the layer\'s default size (e.g. 1440×900 for designs, 1440×3000 for wireframes). Often too large for small content. Use `autoSize: true` to measure HTML content and size to fit, or pass explicit `width`/`height`.', {
|
|
1939
|
+
action: z.enum(['read', 'write', 'set_state', 'get_state', 'write_sheet_values', 'read_sheet_values', 'append_sheet_rows', 'clear_sheet_range', 'get_sheet', 'update_sheet', 'get_doc', 'read_doc_content', 'write_doc_content', 'append_doc_content', 'clear_doc_content', 'update_doc', 'get_slide', 'read_slide_content', 'write_slide_content', 'append_slides', 'clear_slides', 'update_slide', 'create_office', 'read_office', 'edit_office', 'write_excalidraw', 'edit', 'mv', 'anchor', 'search', 'versions', 'read_version', 'restore_version']).describe('Operation to perform. Use native Doc/Slide actions for Google Docs/Slides; do not use inline write/edit for native Workspace content.'),
|
|
1940
1940
|
path: z.string().optional().describe('[read] /{layer}/{lane}/{filename}, frame URL, or UUID. [write|edit|anchor] /{layer}/{lane}/{filename}.'),
|
|
1941
1941
|
lines: z.string().optional().describe('[read] line range (e.g. "1-50"). Omit to read all.'),
|
|
1942
1942
|
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.'),
|
|
1943
1943
|
excalidraw_data: z.any().optional().describe('[write_excalidraw] Excalidraw scene JSON object or JSON string. Defaults to an empty scene.'),
|
|
1944
|
+
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.'),
|
|
1944
1945
|
mermaid: z.string().optional().describe('[write_excalidraw] Mermaid source to convert into an editable Excalidraw scene.'),
|
|
1945
1946
|
file_path: z.string().optional().describe('[write] absolute path to a local file to upload. Mutually exclusive with content/base64/googleType.'),
|
|
1946
1947
|
base64: z.string().optional().describe('[write] base64-encoded binary content. Mutually exclusive with content/file_path/googleType. Use with content_type when known.'),
|
|
@@ -2112,6 +2113,32 @@ tool('frame', 'Frame CRUD in the ACTIVE PROJECT. Dispatch by `action`: read (by
|
|
|
2112
2113
|
_meta: { frameHtml: result.content },
|
|
2113
2114
|
});
|
|
2114
2115
|
}
|
|
2116
|
+
case 'set_state':
|
|
2117
|
+
case 'get_state': {
|
|
2118
|
+
// Read/write the persisted appState of a deployed windowType:'app' frame
|
|
2119
|
+
// (designs.metadata.appState via /api/file/:id/state). The canvas hydrates
|
|
2120
|
+
// the app from this on load — so a generic app frame can be driven with
|
|
2121
|
+
// data after deploy (e.g. push {specText} to the AS/NZS electrical app).
|
|
2122
|
+
const { path } = args;
|
|
2123
|
+
if (!path) throw new Error(`path required for action=${action}`);
|
|
2124
|
+
const urlMatch = path.match(/\/f\/([a-f0-9-]{36})/);
|
|
2125
|
+
const uuidMatch = path.match(/^[a-f0-9-]{36}$/);
|
|
2126
|
+
let frameId = urlMatch?.[1] || (uuidMatch ? path : null);
|
|
2127
|
+
if (!frameId) {
|
|
2128
|
+
const parts = path.replace(/^\/+/, '').split('/');
|
|
2129
|
+
if (parts.length !== 3) throw new Error('Path must be /{layer}/{lane}/{filename}, a frame URL, or a frame ID');
|
|
2130
|
+
const f = await api('GET', `/api/fs/${parts[0]}/${parts[1]}/${parts[2]}`);
|
|
2131
|
+
frameId = f.id;
|
|
2132
|
+
if (!frameId) throw new Error(`Could not resolve a frame id for ${path}`);
|
|
2133
|
+
}
|
|
2134
|
+
if (action === 'get_state') {
|
|
2135
|
+
const r = await api('GET', `/api/file/${frameId}/state`);
|
|
2136
|
+
return ok(JSON.stringify({ frameId, state: r.state ?? null }, null, 2));
|
|
2137
|
+
}
|
|
2138
|
+
if (args.state === undefined) throw new Error('state required for action=set_state — the app-frame state object to persist; the app hydrates from it on load');
|
|
2139
|
+
await api('POST', `/api/file/${frameId}/state`, { state: args.state });
|
|
2140
|
+
return ok(`App state persisted to frame ${frameId}. The app hydrates from it on next load — open or refresh the frame to see it render with the new state.`);
|
|
2141
|
+
}
|
|
2115
2142
|
case 'get_sheet':
|
|
2116
2143
|
case 'read_sheet_values':
|
|
2117
2144
|
case 'write_sheet_values':
|
|
@@ -2822,7 +2849,6 @@ tool('skill', 'Manage the Drafted skill library. Skills are reusable prompts/gui
|
|
|
2822
2849
|
tags: z.array(z.string()).optional().describe('[search] filter by tags; [add|update] tag list'),
|
|
2823
2850
|
scope: z.enum(['all', 'org', 'global']).optional().describe('[search|list] library scope (default: all for search; when provided to list, lists the library instead of project/org attachments)'),
|
|
2824
2851
|
limit: z.number().optional().describe('[search|list] max results per page (default 25, max 100)'),
|
|
2825
|
-
offset: z.number().optional().describe('[search|list] skip N results for pagination (default 0). Response reports totalAvailable/returned/truncated.'),
|
|
2826
2852
|
compact: z.boolean().optional().describe('[search|list] return only {slug,name,tags} per skill instead of full summaries — for browsing large catalogs within token budget'),
|
|
2827
2853
|
skill: z.string().optional().describe('[load|history] skill ID (UUID) or slug'),
|
|
2828
2854
|
version: z.number().optional().describe('[history] fetch this version number\'s full snapshot (content included); omit for the reverse-chron version list'),
|
|
@@ -2833,6 +2859,8 @@ tool('skill', 'Manage the Drafted skill library. Skills are reusable prompts/gui
|
|
|
2833
2859
|
content: z.string().optional().describe('[add|update] root SKILL.md content; [update_file] file content'),
|
|
2834
2860
|
triggerPatterns: z.array(z.string()).optional().describe('[add|update] patterns that suggest this skill'),
|
|
2835
2861
|
path: z.string().optional().describe('[read_file|update_file] relative path inside skill directory (e.g. "examples/react.md")'),
|
|
2862
|
+
offset: z.number().optional().describe('[search|list] skip N results for pagination; [read_file] start reading at this byte offset (default 0) — for large files (e.g. a >90KB app-frame bundle) read in chunks using the returned nextOffset until truncated=false'),
|
|
2863
|
+
maxBytes: z.number().optional().describe('[read_file] return at most this many bytes from offset (default: whole remaining file). Response reports totalSize/offset/truncated/nextOffset.'),
|
|
2836
2864
|
org: z.string().optional().describe('[fork|push|update] resolve/fork into this Drafted org (id or name); scopes the request without switching the session'),
|
|
2837
2865
|
setup: z.array(z.string()).optional().describe('[add|update] setup command(s) (in order) run on materialize to build a source-only skill, e.g. ["npm ci","npm run build"]'),
|
|
2838
2866
|
files: z.array(z.object({ path: z.string(), content: z.string() })).optional().describe('[push] source files to push (path + UTF-8 content); server strips artifacts + enforces caps'),
|
|
@@ -3020,9 +3048,13 @@ tool('skill', 'Manage the Drafted skill library. Skills are reusable prompts/gui
|
|
|
3020
3048
|
return ok(await api('DELETE', `/api/skills/favorites/${skillId}`));
|
|
3021
3049
|
}
|
|
3022
3050
|
case 'read_file': {
|
|
3023
|
-
const { skillId, path } = args;
|
|
3051
|
+
const { skillId, path, offset, maxBytes } = args;
|
|
3024
3052
|
if (!skillId || !path) throw new Error('skillId and path required for action=read_file');
|
|
3025
|
-
|
|
3053
|
+
const params = new URLSearchParams();
|
|
3054
|
+
if (offset != null) params.set('offset', String(offset));
|
|
3055
|
+
if (maxBytes != null) params.set('maxBytes', String(maxBytes));
|
|
3056
|
+
const qs = params.toString();
|
|
3057
|
+
return ok(await api('GET', `/api/skills/${skillId}/files/${path}${qs ? `?${qs}` : ''}`));
|
|
3026
3058
|
}
|
|
3027
3059
|
case 'update_file': {
|
|
3028
3060
|
const { skillId, path, content } = args;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "drafted",
|
|
3
|
-
"version": "1.11.
|
|
3
|
+
"version": "1.11.17",
|
|
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": [
|