drafted 1.11.1 → 1.11.3
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 +65 -7
- package/package.json +2 -1
package/mcp/server.mjs
CHANGED
|
@@ -567,12 +567,13 @@ function reportMcpToolError(tool, error) {
|
|
|
567
567
|
|
|
568
568
|
function wikiPageUrl(path = '', org = '') {
|
|
569
569
|
const normalized = normalizeWikiPath(path || '');
|
|
570
|
-
//
|
|
571
|
-
//
|
|
572
|
-
// route resolves
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
570
|
+
// Canonical org-scoped URL: /o/<org>/wiki/<path>. Portable across the viewer's
|
|
571
|
+
// orgs (wiki paths collide across orgs). `org` is the org id; the
|
|
572
|
+
// /o/<org>/wiki route resolves it id-first then slug. Falls back to bare /wiki
|
|
573
|
+
// (which 302s to the canonical form) when org is unknown.
|
|
574
|
+
const base = org ? `${getServerUrl()}/o/${encodeURIComponent(org)}/wiki` : `${getServerUrl()}/wiki`;
|
|
575
|
+
if (!normalized) return base;
|
|
576
|
+
return `${base}/${normalized.split('/').map(encodeURIComponent).join('/')}`;
|
|
576
577
|
}
|
|
577
578
|
|
|
578
579
|
function getBootstrapSessionId() {
|
|
@@ -1902,7 +1903,7 @@ tool('get_org', {
|
|
|
1902
1903
|
// ── Filesystem tools (direct HTTP to /api/fs) ─────────────────────
|
|
1903
1904
|
|
|
1904
1905
|
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`.', {
|
|
1905
|
-
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', '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.'),
|
|
1906
|
+
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.'),
|
|
1906
1907
|
path: z.string().optional().describe('[read] /{layer}/{lane}/{filename}, frame URL, or UUID. [write|edit|anchor] /{layer}/{lane}/{filename}.'),
|
|
1907
1908
|
lines: z.string().optional().describe('[read] line range (e.g. "1-50"). Omit to read all.'),
|
|
1908
1909
|
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.'),
|
|
@@ -1915,6 +1916,24 @@ tool('frame', 'Frame CRUD in the ACTIVE PROJECT. Dispatch by `action`: read (by
|
|
|
1915
1916
|
title: z.string().optional().describe('[write + googleType] Title for a new native file. Defaults to filename from path.'),
|
|
1916
1917
|
url: z.string().optional().describe('[write + googleType] Existing Google file URL to attach.'),
|
|
1917
1918
|
googleId: z.string().optional().describe('[write + googleType] Existing Google file ID to attach. [Sheet/Doc/Slide actions] Native Google file ID to use when not resolving from path/frame.'),
|
|
1919
|
+
officeType: z.enum(['xlsx']).optional().describe('[create_office] native Drafted-owned Office format to author. Currently xlsx (real .xlsx bytes — download is the file itself, no conversion).'),
|
|
1920
|
+
rows: z.array(z.array(z.union([z.string(), z.number(), z.boolean(), z.null()]))).optional().describe('[create_office] initial rows for a single-sheet xlsx (2D array of cell values).'),
|
|
1921
|
+
sheets: z.array(z.object({ name: z.string().optional(), rows: z.array(z.array(z.union([z.string(), z.number(), z.boolean(), z.null()]))).optional() })).optional().describe('[create_office] multiple sheets: [{ name, rows }].'),
|
|
1922
|
+
sheet: z.string().optional().describe('[read_office] limit the read to one sheet by name.'),
|
|
1923
|
+
maxCells: z.number().optional().describe('[read_office] cap non-empty cells returned (default 5000).'),
|
|
1924
|
+
ops: z.array(z.object({
|
|
1925
|
+
type: z.string(),
|
|
1926
|
+
sheet: z.string().optional(),
|
|
1927
|
+
ref: z.string().optional(),
|
|
1928
|
+
value: z.union([z.string(), z.number(), z.boolean(), z.null()]).optional(),
|
|
1929
|
+
formula: z.string().optional(),
|
|
1930
|
+
cells: z.array(z.object({ ref: z.string(), value: z.union([z.string(), z.number(), z.boolean(), z.null()]).optional(), formula: z.string().optional() })).optional(),
|
|
1931
|
+
range: z.string().optional(),
|
|
1932
|
+
name: z.string().optional(),
|
|
1933
|
+
from: z.string().optional(),
|
|
1934
|
+
to: z.string().optional(),
|
|
1935
|
+
rows: z.array(z.array(z.union([z.string(), z.number(), z.boolean(), z.null()]))).optional(),
|
|
1936
|
+
})).optional().describe('[edit_office] xlsx edit ops (resolved against stable cell/sheet addresses): {type:"setCell",sheet?,ref,value|formula} | {type:"setCells",sheet?,cells:[{ref,value|formula}]} | {type:"clearRange",sheet?,range} | {type:"addSheet",name,rows?} | {type:"renameSheet",from,to} | {type:"deleteSheet",name}.'),
|
|
1918
1937
|
format: z.enum(['plain_text', 'markdown']).optional().describe('[write_doc_content|append_doc_content] Source format hint. Currently plain_text and minimal markdown are accepted as text.'),
|
|
1919
1938
|
mode: z.enum(['replace', 'append', 'clear']).optional().describe('[Doc/Slide content actions] Optional mode hint for clients; prefer the explicit write/append/clear action names.'),
|
|
1920
1939
|
slides: z.array(z.object({
|
|
@@ -1991,6 +2010,19 @@ tool('frame', 'Frame CRUD in the ACTIVE PROJECT. Dispatch by `action`: read (by
|
|
|
1991
2010
|
}
|
|
1992
2011
|
return body;
|
|
1993
2012
|
};
|
|
2013
|
+
// Resolve a frame path / URL / UUID to a frame id for the native Office routes.
|
|
2014
|
+
const resolveOfficeFrameId = async (path) => {
|
|
2015
|
+
if (!path) throw new Error('Provide path to an office frame, frame URL, or frame ID');
|
|
2016
|
+
const frameUrlMatch = path.match(/\/f\/([a-f0-9-]{36})/);
|
|
2017
|
+
const uuidMatch = path.match(/^[a-f0-9-]{36}$/);
|
|
2018
|
+
if (frameUrlMatch?.[1] || uuidMatch) return frameUrlMatch?.[1] || path;
|
|
2019
|
+
const parts = path.replace(/^\/+/, '').split('/');
|
|
2020
|
+
if (parts.length !== 3) throw new Error('Path must be /{layer}/{lane}/{filename}, a frame URL, or a frame ID');
|
|
2021
|
+
const frame = await api('GET', `/api/fs/${parts[0]}/${parts[1]}/${parts[2]}`);
|
|
2022
|
+
const id = frame?.id || frame?.frame?.id;
|
|
2023
|
+
if (!id) throw new Error('Office frame not found for path');
|
|
2024
|
+
return id;
|
|
2025
|
+
};
|
|
1994
2026
|
switch (action) {
|
|
1995
2027
|
case 'read': {
|
|
1996
2028
|
const { path, lines } = args;
|
|
@@ -2132,6 +2164,32 @@ tool('frame', 'Frame CRUD in the ACTIVE PROJECT. Dispatch by `action`: read (by
|
|
|
2132
2164
|
const result = await api('POST', endpoint, body);
|
|
2133
2165
|
return ok(withProject(result));
|
|
2134
2166
|
}
|
|
2167
|
+
case 'create_office': {
|
|
2168
|
+
// Author a native Drafted-owned Office file (real bytes; download is the
|
|
2169
|
+
// file itself). Populate via edit_office; read structure via read_office.
|
|
2170
|
+
const { path, title, officeType = 'xlsx', rows, sheets } = args;
|
|
2171
|
+
let layer = 'copy';
|
|
2172
|
+
let lane = 'default';
|
|
2173
|
+
let label = title;
|
|
2174
|
+
if (path) {
|
|
2175
|
+
const p = path.replace(/^\/+/, '').split('/');
|
|
2176
|
+
if (p.length === 3) { layer = p[0]; lane = p[1]; label = p[2]; }
|
|
2177
|
+
}
|
|
2178
|
+
if (!label) throw new Error('Provide title (filename) or a /{layer}/{lane}/{filename} path for the new office file');
|
|
2179
|
+
return ok(await api('POST', '/api/office/create', { projectId: getState().projectId, layer, lane, label, format: officeType, rows, sheets }));
|
|
2180
|
+
}
|
|
2181
|
+
case 'read_office': {
|
|
2182
|
+
const id = await resolveOfficeFrameId(args.path);
|
|
2183
|
+
const q = [];
|
|
2184
|
+
if (args.sheet) q.push('sheet=' + encodeURIComponent(args.sheet));
|
|
2185
|
+
if (args.maxCells) q.push('maxCells=' + Number(args.maxCells));
|
|
2186
|
+
return ok(await api('GET', `/api/office/${id}${q.length ? '?' + q.join('&') : ''}`));
|
|
2187
|
+
}
|
|
2188
|
+
case 'edit_office': {
|
|
2189
|
+
if (!Array.isArray(args.ops) || !args.ops.length) throw new Error('ops (non-empty array) required for action=edit_office');
|
|
2190
|
+
const id = await resolveOfficeFrameId(args.path);
|
|
2191
|
+
return ok(await api('POST', `/api/office/${id}/edit`, { ops: args.ops }));
|
|
2192
|
+
}
|
|
2135
2193
|
case 'write': {
|
|
2136
2194
|
const { path, content, file_path, base64, content_type, autoSize, width, height, color, googleType, title, url, googleId } = args;
|
|
2137
2195
|
if (!path) throw new Error('path required for action=write');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "drafted",
|
|
3
|
-
"version": "1.11.
|
|
3
|
+
"version": "1.11.3",
|
|
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": [
|
|
@@ -54,6 +54,7 @@
|
|
|
54
54
|
"dotenv": "^17.3.1",
|
|
55
55
|
"drizzle-orm": "^0.45.1",
|
|
56
56
|
"esbuild": "^0.28.0",
|
|
57
|
+
"exceljs": "^4.4.0",
|
|
57
58
|
"express": "^4.18.2",
|
|
58
59
|
"jsdom": "^27.0.1",
|
|
59
60
|
"lucide": "^1.16.0",
|