drafted 1.14.12 → 1.14.14
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 +28 -9
- package/package.json +1 -1
package/mcp/server.mjs
CHANGED
|
@@ -1450,12 +1450,23 @@ async function requireBoundOrgForProjectlessMutation(explicitOrg) {
|
|
|
1450
1450
|
return;
|
|
1451
1451
|
}
|
|
1452
1452
|
if (projectlessMutationNeedsOrg({ orgCount: orgs.length })) {
|
|
1453
|
+
// Actionable, not a dead end: name the orgs and the ONE call that binds this session,
|
|
1454
|
+
// so the agent recovers itself instead of stalling on the human. Project-less wiki and
|
|
1455
|
+
// skill work is a first-class flow — no project required, ever. The only thing refused
|
|
1456
|
+
// is GUESSING which org, which is what silently misfiled a page into the user's
|
|
1457
|
+
// default org. (Listing the orgs is safe: they're the caller's own memberships.)
|
|
1458
|
+
const names = orgs.map(o => o.name || o.id).filter(Boolean);
|
|
1453
1459
|
throw new Error(
|
|
1454
|
-
`
|
|
1455
|
-
|
|
1456
|
-
`
|
|
1457
|
-
`call (org
|
|
1458
|
-
`
|
|
1460
|
+
`Which org? A project-less wiki/skill write needs one, and you belong to ${orgs.length}: ` +
|
|
1461
|
+
`${names.join(', ')}. Don't guess — an unaddressed write lands in whichever org this ` +
|
|
1462
|
+
`session inherited, which is how a page meant for one org ends up in another.\n` +
|
|
1463
|
+
`Recover with ONE call: get_org(action="use", org="<name>"). That binds this session's ` +
|
|
1464
|
+
`working org — every later project-less wiki/skill write then just works, with no project ` +
|
|
1465
|
+
`and no org= to repeat. Prefer it over passing org= per call, which pays this toll on ` +
|
|
1466
|
+
`every write.\n` +
|
|
1467
|
+
`(Alternatives: org="<name>" on this single call; or project(action="open") if the work ` +
|
|
1468
|
+
`belongs to a project — the org derives from it.)\n` +
|
|
1469
|
+
`Pick from the conversation if the org is clear from context; ask the user only if it isn't.`
|
|
1459
1470
|
);
|
|
1460
1471
|
}
|
|
1461
1472
|
}
|
|
@@ -1853,6 +1864,12 @@ async function sessionSurfaceBlock() {
|
|
|
1853
1864
|
color: agentSurface.color,
|
|
1854
1865
|
surfaced: true,
|
|
1855
1866
|
alive: !!agentSurface.alive,
|
|
1867
|
+
// `alive` was ambiguous: agents couldn't tell whether it meant "your writes are
|
|
1868
|
+
// no-ops" (act differently) or "the user's canvas tab is closed" (ignore). It's the
|
|
1869
|
+
// latter — say so, so nobody changes behavior over it.
|
|
1870
|
+
aliveMeaning: agentSurface.alive
|
|
1871
|
+
? 'A canvas/desktop surface is live for this session — focus and presence calls will be seen.'
|
|
1872
|
+
: 'No canvas surface is currently open for this session. Frame, wiki, and skill writes all persist normally — only focus/presence have nothing to draw on. Do not change what you write because of this.',
|
|
1856
1873
|
};
|
|
1857
1874
|
}
|
|
1858
1875
|
// No WS ack yet — best-effort identity from /auth/me so callers still get a userId/org.
|
|
@@ -2661,7 +2678,7 @@ tool('frame', 'Frame CRUD in the ACTIVE PROJECT. Dispatch by `action`: read (by
|
|
|
2661
2678
|
path: z.string().optional().describe('[read] /{layer}/{lane}/{filename}, frame URL, or UUID. [write|edit|anchor] /{layer}/{lane}/{filename}.'),
|
|
2662
2679
|
lines: z.string().optional().describe('[read] line range (e.g. "1-50"). Omit to read all.'),
|
|
2663
2680
|
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.'),
|
|
2664
|
-
excalidraw_data: z.any().optional().describe('[write_excalidraw] Excalidraw scene JSON object or JSON string. Defaults to an empty scene.'),
|
|
2681
|
+
excalidraw_data: z.any().optional().describe('[write_excalidraw] Excalidraw scene JSON object or JSON string. Defaults to an empty scene. WRITE LEAN SCENES: your MCP client caps tool-call arguments (a big scene is rejected before Drafted ever sees it), so emit only the fields that carry meaning — id, type, x, y, width, height, angle, text/label, strokeColor, backgroundColor, fillStyle, strokeWidth, and the binding ids for arrows. Omit every field the editor can default (version, versionNonce, seed, updated, groupIds, boundElements when empty, roundness, opacity at 100, frameId when null). The editor fills them on open; shipping them can triple the payload for no gain. For a big diagram, write it in passes: create the scene, then frame(action="write_excalidraw") again with the next batch of elements.'),
|
|
2665
2682
|
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.'),
|
|
2666
2683
|
file_path: z.string().optional().describe('[write] absolute path to a local file to upload. Mutually exclusive with content/base64/googleType.'),
|
|
2667
2684
|
base64: z.string().optional().describe('[write] base64-encoded binary content. Mutually exclusive with content/file_path/googleType. Use with content_type when known.'),
|
|
@@ -3168,8 +3185,9 @@ tool('ls', 'List contents of the ACTIVE PROJECT. Use ls / after project(action="
|
|
|
3168
3185
|
recursive: z.boolean().optional().describe('List contents of subdirectories. When true, forces summary mode (metadata only, no full content) to keep results under the 25k token cap.'),
|
|
3169
3186
|
summary: z.boolean().optional().describe('Include size, updatedAt, title for frames'),
|
|
3170
3187
|
pattern: z.string().optional().describe('Glob pattern to filter filenames (e.g. "*.html")'),
|
|
3188
|
+
head: z.number().optional().describe('Include the first N lines of EACH frame inline (max 50). Survey a whole lane in one call instead of one frame(action="read") per frame — read only the frames the heads show you actually need.'),
|
|
3171
3189
|
limit: z.number().optional().default(500).describe('Max entries to return (default 500, max 2000). Use pattern or path to scope further.'),
|
|
3172
|
-
}, async ({ path, recursive, summary, pattern, limit }) => {
|
|
3190
|
+
}, async ({ path, recursive, summary, pattern, head, limit }) => {
|
|
3173
3191
|
try {
|
|
3174
3192
|
const params = new URLSearchParams();
|
|
3175
3193
|
params.set('path', path);
|
|
@@ -3184,6 +3202,7 @@ tool('ls', 'List contents of the ACTIVE PROJECT. Use ls / after project(action="
|
|
|
3184
3202
|
} else if (summary) {
|
|
3185
3203
|
params.set('summary', 'true');
|
|
3186
3204
|
}
|
|
3205
|
+
if (head) params.set('head', String(head));
|
|
3187
3206
|
if (pattern) params.set('pattern', pattern);
|
|
3188
3207
|
const result = await api('GET', `/api/fs/?${params.toString()}`);
|
|
3189
3208
|
|
|
@@ -3548,7 +3567,7 @@ tool('skill', 'Manage the Drafted skill library. Skills are reusable prompts/gui
|
|
|
3548
3567
|
path: z.string().optional().describe('[read_file|update_file] relative path inside skill directory (e.g. "examples/react.md")'),
|
|
3549
3568
|
offset: z.number().optional().describe('[search|list|export] 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'),
|
|
3550
3569
|
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.'),
|
|
3551
|
-
org: z.string().optional().describe('[add] org (id or name) the skill is born in — defaults to the open project\'s org; [list|search|load] scope to this org; [fork|push|update|export|import] resolve/fork into this org. Per-request only — nothing is switched.'),
|
|
3570
|
+
org: z.string().optional().describe('[add] org (id or name) the skill is born in — defaults to the open project\'s org; [list|search|load] scope to this org; [fork|push|update|export|import] resolve/fork into this org. Per-request only — nothing is switched. NO PROJECT IS NEEDED: for project-less skill work either pass org= here, or bind the session once with get_org(action="use", org="<name>"). A multi-org caller that addresses neither is refused rather than guessed.'),
|
|
3552
3571
|
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"]'),
|
|
3553
3572
|
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. [import] OKF bundle files inline — skills/<slug>/SKILL.md dirs and Skill/Playbook/SOP/Procedure-typed .md with name + description frontmatter become org skills. Caps: 500 files, 512KB/file, 5MB total.'),
|
|
3554
3573
|
dir: z.string().optional().describe('[push|export|import] local directory. push: source tree to push instead of files[]; walked locally (heavy dirs, .skillinstall/, and .skillignore pre-filtered), server re-enforces; the dir\'s .gitignore is auto-updated to exclude .skillinstall/ (the rebuildable bundle). export: write the OKF bundle files here (default ./okf-skills-<org>). import: read the bundle from here (alternative to files[]).'),
|
|
@@ -3846,7 +3865,7 @@ tool('wiki', 'Per-org wiki. Markdown pages with paths as hierarchy. You and othe
|
|
|
3846
3865
|
action: z.enum(['ls', 'recent', 'read', 'search', 'links', 'log', 'health', 'write', 'edit', 'mv', 'rm', 'cite', 'source-register', 'source-list', 'source-get', 'bulk-write', 'export', 'import']).describe('Operation to perform. export: the whole wiki as an OKF v0.1 bundle (local dir on stdio, download URL on remote, or format="files" for paged inline files). import: ingest an OKF bundle (files[] or local dir; dryRun supported).'),
|
|
3847
3866
|
path: z.string().optional().describe('[ls|read|links|cite] wiki path. For ls: default / (root). For read: required. For links/cite: required unless pageId given. Reading `index.md` (any level) returns the SYNTHESIZED OKF directory listing.'),
|
|
3848
3867
|
pageId: z.string().optional().describe('[read|edit|mv|rm|links] page UUID (from read/search). UUID-first: addresses the page directly, org auto-derives — no org needed and no path lookup. Preferred over path for an existing page.'),
|
|
3849
|
-
org: z.string().optional().describe('Org slug or id to scope this call to (per-request only — nothing is switched).
|
|
3868
|
+
org: z.string().optional().describe('Org slug or id to scope this call to (per-request only — nothing is switched). NO PROJECT IS NEEDED for wiki work: to write project-less, either pass org= here, or bind the session once with get_org(action="use", org="<name>") and then omit it. Multi-org callers MUST address the org one of those two ways — an unaddressed write is refused rather than guessed (it would land in whichever org the session inherited). [write] the org the page is created in. [search] restrict to this org (default: ALL your orgs). [ls|recent|read|links|log|health|edit|mv|rm|bulk-write] target this org\'s wiki instead of the open project\'s org. Ignored when a pageId is given (the page self-derives its org).'),
|
|
3850
3869
|
recursive: z.boolean().optional().describe('[ls] list recursively with depth indicators'),
|
|
3851
3870
|
limit: z.number().optional().describe('[recent|search|export] max results (recent default 10, search default 25, export files default 100)'),
|
|
3852
3871
|
offset: z.number().optional().describe('[export] pagination offset for format="files"'),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "drafted",
|
|
3
|
-
"version": "1.14.
|
|
3
|
+
"version": "1.14.14",
|
|
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": [
|