drafted 1.14.8 → 1.14.10
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 +29 -66
- package/package.json +1 -1
package/mcp/server.mjs
CHANGED
|
@@ -267,9 +267,7 @@ const TOOL_ANNOTATIONS = {
|
|
|
267
267
|
// Assets
|
|
268
268
|
asset: { title: 'Assets', readOnlyHint: false, destructiveHint: false, openWorldHint: false, description: 'Manage project assets (CSS/JS/images/fonts referenced by frames). `action=upload` or `action=list`.' },
|
|
269
269
|
|
|
270
|
-
//
|
|
271
|
-
shape: { title: 'Add shape', readOnlyHint: false, destructiveHint: false, openWorldHint: false },
|
|
272
|
-
group: { title: 'Create group', readOnlyHint: false, destructiveHint: false, openWorldHint: false },
|
|
270
|
+
// Connectors / layout
|
|
273
271
|
connector: { title: 'Connectors', readOnlyHint: false, destructiveHint: true, openWorldHint: false, description: 'Connect or disconnect frames with arrows on the surface. `action=connect` or `action=disconnect`.' },
|
|
274
272
|
layout: { title: 'Auto-layout', readOnlyHint: false, destructiveHint: false, openWorldHint: false },
|
|
275
273
|
|
|
@@ -307,8 +305,6 @@ function isMutatingToolCall(name, args = {}) {
|
|
|
307
305
|
case 'minion':
|
|
308
306
|
return ['create', 'update', 'enable', 'disable', 'delete', 'test_start', 'test_say', 'test_resolve'].includes(action);
|
|
309
307
|
case 'rm':
|
|
310
|
-
case 'shape':
|
|
311
|
-
case 'group':
|
|
312
308
|
case 'connector':
|
|
313
309
|
case 'layout':
|
|
314
310
|
return true;
|
|
@@ -1082,6 +1078,15 @@ function shapeSkillCatalog(result, { limit, offset = 0, compact = false } = {})
|
|
|
1082
1078
|
return result;
|
|
1083
1079
|
}
|
|
1084
1080
|
|
|
1081
|
+
// Semantic frame URL (/o/<org>/<project>/<layer>/<lane>/<label>) from a row that
|
|
1082
|
+
// carries slugs (e.g. /api/search hits). Returns null when a slug is missing so the
|
|
1083
|
+
// caller can fall back to the /f/<uuid> resolver.
|
|
1084
|
+
function semanticFrameUrl({ orgSlug, projectSlug, layer, lane, label }) {
|
|
1085
|
+
if (!orgSlug || !projectSlug || !label) return null;
|
|
1086
|
+
const e = encodeURIComponent;
|
|
1087
|
+
return `${getServerUrl()}/o/${e(orgSlug)}/${e(projectSlug)}/${e(layer || 'designs')}/${e(lane || 'default')}/${e(label)}`;
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1085
1090
|
// Build the structuredContent shape that the frame-preview widget reads.
|
|
1086
1091
|
// Tools that produce or return a frame (read/write/edit) call this so the
|
|
1087
1092
|
// model and widget see the same metadata view.
|
|
@@ -1097,7 +1102,9 @@ function frameStructuredContent(result, project = null) {
|
|
|
1097
1102
|
lane: result.lane,
|
|
1098
1103
|
width: result.width,
|
|
1099
1104
|
height: result.height,
|
|
1100
|
-
|
|
1105
|
+
// Server emits the semantic /o/<org>/<project>/<layer>/... URL; /f/<uuid> is the
|
|
1106
|
+
// fallback (still resolved + redirected server-side).
|
|
1107
|
+
frameUrl: result.frameUrl || (result.id ? `${getServerUrl()}/f/${result.id}` : undefined),
|
|
1101
1108
|
project, // {id, slug, name, orgId} of the active project — visible in client UI
|
|
1102
1109
|
};
|
|
1103
1110
|
}
|
|
@@ -1824,7 +1831,7 @@ tool('health', {}, async () => {
|
|
|
1824
1831
|
|
|
1825
1832
|
// ── Project management tools (direct HTTP) ────────────────────────
|
|
1826
1833
|
|
|
1827
|
-
tool('project', 'START HERE for project management. Dispatch by `action`: list (lists all projects across all orgs — always call first), open (bind this agent session to a project; required before reading/writing frames — the org derives from the project), create (new project; org= names where it is born), update (change name/folder/description/layers), move (transfer to another org), export (the project as an OKF v0.1 bundle — <layer>/<lane>/<file>.md concepts, index.md/log.md synthesized), import (ingest an OKF bundle: concepts become markdown document frames, links between them become connectors; dryRun supported). There is no org switching: for project-less work (wiki/skills) pass org=... on the call. **Skill gate:** projects with attached skills will REJECT all mutations (write, edit, mv, rm,
|
|
1834
|
+
tool('project', 'START HERE for project management. Dispatch by `action`: list (lists all projects across all orgs — always call first), open (bind this agent session to a project; required before reading/writing frames — the org derives from the project), create (new project; org= names where it is born), update (change name/folder/description/layers), move (transfer to another org), export (the project as an OKF v0.1 bundle — <layer>/<lane>/<file>.md concepts, index.md/log.md synthesized), import (ingest an OKF bundle: concepts become markdown document frames, links between them become connectors; dryRun supported). There is no org switching: for project-less work (wiki/skills) pass org=... on the call. **Skill gate:** projects with attached skills will REJECT all mutations (write, edit, mv, rm, connector, layout, layer, asset upload) until you have loaded each attached skill via skill(action="load"). Skills tell you HOW to do the work — they\'re not optional. Open returns the attached skill list and auto-inlines content for projects with ≤3 skills.', {
|
|
1828
1835
|
action: z.enum(['list', 'open', 'create', 'update', 'move', 'export', 'import']).describe('Operation to perform.'),
|
|
1829
1836
|
projectId: z.string().optional().describe('[open|update|move|export|import] project ID. Get IDs from action=list. For export/import: defaults to the bound project.'),
|
|
1830
1837
|
name: z.string().optional().describe('[create|update] project name'),
|
|
@@ -1884,17 +1891,21 @@ tool('project', 'START HERE for project management. Dispatch by `action`: list (
|
|
|
1884
1891
|
joinAgentWsRoom(projectId);
|
|
1885
1892
|
const base = getServerUrl();
|
|
1886
1893
|
let projectSlug = projectId;
|
|
1887
|
-
let
|
|
1894
|
+
let orgSlug = null;
|
|
1895
|
+
let projectMeta = { id: projectId, slug: null, name: null, orgId: null, orgSlug: null };
|
|
1888
1896
|
try {
|
|
1889
1897
|
const data = await api('GET', '/api/projects');
|
|
1890
1898
|
const proj = (data.projects || []).find(p => p.id === projectId);
|
|
1891
1899
|
if (proj) {
|
|
1892
1900
|
projectSlug = proj.slug || projectId;
|
|
1893
|
-
|
|
1901
|
+
orgSlug = proj.orgSlug || null;
|
|
1902
|
+
projectMeta = { id: proj.id, slug: proj.slug || null, name: proj.name || null, orgId: proj.orgId || null, orgSlug };
|
|
1894
1903
|
}
|
|
1895
1904
|
} catch { /* fall back to projectId */ }
|
|
1896
1905
|
setMcpActiveProject(projectId, projectMeta);
|
|
1897
|
-
|
|
1906
|
+
// Semantic /o/<org-slug>/<project-slug> when the org slug is known; otherwise
|
|
1907
|
+
// the /project/<slug> resolver redirects to it.
|
|
1908
|
+
const url = orgSlug ? `${base}/o/${encodeURIComponent(orgSlug)}/${encodeURIComponent(projectSlug)}` : `${base}/project/${projectSlug}`;
|
|
1898
1909
|
// Surfacing to the user is the focus mechanism's job now (agent-active ping ->
|
|
1899
1910
|
// desktop window / notification+glow for browser tabs) — this used to also force
|
|
1900
1911
|
// `exec('open <url>')` on the MCP host machine, an unsolicited GUI action that both
|
|
@@ -2157,7 +2168,7 @@ tool('focus', {
|
|
|
2157
2168
|
} catch (error) { return err(error); }
|
|
2158
2169
|
});
|
|
2159
2170
|
|
|
2160
|
-
tool('screenshot', 'Render a PNG via headless browser. `scope=frame` captures a single frame (default 1440×900, fullPage). `scope=canvas` captures a region of the project surface (default 1600×1200, typically the "plans" layer where
|
|
2171
|
+
tool('screenshot', 'Render a PNG via headless browser. `scope=frame` captures a single frame (default 1440×900, fullPage). `scope=canvas` captures a region of the project surface (default 1600×1200, typically the "plans" layer where frames live).', {
|
|
2161
2172
|
scope: z.enum(['frame', 'canvas']).describe('What to capture.'),
|
|
2162
2173
|
target: z.string().optional().describe('[scope=frame] frame URL, UUID, or /{layer}/{lane}/{filename} path.'),
|
|
2163
2174
|
slug: z.string().optional().describe('[scope=canvas] project slug. Defaults to the currently active project.'),
|
|
@@ -3043,7 +3054,7 @@ tool('frame', 'Frame CRUD in the ACTIVE PROJECT. Dispatch by `action`: read (by
|
|
|
3043
3054
|
path: `/${r.layer}/${r.lane}/${r.label}`,
|
|
3044
3055
|
project: r.projectName,
|
|
3045
3056
|
projectId: r.projectId,
|
|
3046
|
-
frameUrl: `${getServerUrl()}/f/${r.id}`,
|
|
3057
|
+
frameUrl: semanticFrameUrl(r) || `${getServerUrl()}/f/${r.id}`,
|
|
3047
3058
|
contentType: r.contentType,
|
|
3048
3059
|
updatedAt: r.updatedAt,
|
|
3049
3060
|
})),
|
|
@@ -3116,7 +3127,11 @@ tool('ls', 'List contents of the ACTIVE PROJECT. Use ls / after project(action="
|
|
|
3116
3127
|
const projectId = getState().projectId;
|
|
3117
3128
|
const structuredContent = {
|
|
3118
3129
|
project: result?.project || result?.projectName,
|
|
3119
|
-
canvasUrl: result?.
|
|
3130
|
+
canvasUrl: result?.project?.slug
|
|
3131
|
+
? (result.project.orgSlug
|
|
3132
|
+
? `${getServerUrl()}/o/${encodeURIComponent(result.project.orgSlug)}/${encodeURIComponent(result.project.slug)}`
|
|
3133
|
+
: `${getServerUrl()}/project/${encodeURIComponent(result.project.slug)}`)
|
|
3134
|
+
: undefined,
|
|
3120
3135
|
byLayer,
|
|
3121
3136
|
truncated: result?.truncated || false,
|
|
3122
3137
|
totalAvailable: result?.totalAvailable,
|
|
@@ -3293,56 +3308,6 @@ tool('asset', 'Manage supporting files (CSS, JS, images, fonts) in the ACTIVE PR
|
|
|
3293
3308
|
} catch (error) { return err(error); }
|
|
3294
3309
|
});
|
|
3295
3310
|
|
|
3296
|
-
// ── Shape tool ───────────────────────────────────────────────────────
|
|
3297
|
-
|
|
3298
|
-
tool('shape', 'Create a flowchart shape on the surface. Shapes are lightweight text nodes — use them for flowcharts, process diagrams, and decision trees. Connect shapes with connector(action="connect"), then call layout to auto-arrange.', {
|
|
3299
|
-
text: z.string().describe('Text to display inside the shape. Supports multi-line with \\n.'),
|
|
3300
|
-
shape: z.enum(['rectangle', 'diamond', 'oval', 'pill']).optional().default('rectangle').describe('Shape type: rectangle (process/action), diamond (decision), oval (start/end), pill (rounded step)'),
|
|
3301
|
-
layer: z.string().optional().describe('Layer to place the shape in (default: plans)'),
|
|
3302
|
-
lane: z.string().optional().describe('Lane within the layer (default: default)'),
|
|
3303
|
-
color: z.string().optional().describe('Border color (CSS color string)'),
|
|
3304
|
-
fill: z.string().optional().describe('Background fill color (CSS color string)'),
|
|
3305
|
-
textColor: z.string().optional().describe('Text color (CSS color string)'),
|
|
3306
|
-
group: z.string().optional().describe('Group ID to assign this shape to (for swim lane clustering). Use the ID returned by the group tool.'),
|
|
3307
|
-
width: z.number().optional().describe('Explicit width in pixels (overrides auto-sizing from text)'),
|
|
3308
|
-
height: z.number().optional().describe('Explicit height in pixels (overrides auto-sizing from text)'),
|
|
3309
|
-
layoutIgnore: z.boolean().optional().describe('Exclude this shape from auto-layout. Use for annotations, labels, or manually positioned elements.'),
|
|
3310
|
-
}, async ({ text, shape, layer, lane, color, fill, textColor, group, width, height, layoutIgnore }) => {
|
|
3311
|
-
try {
|
|
3312
|
-
const body = { text, shape };
|
|
3313
|
-
if (layer) body.layer = layer;
|
|
3314
|
-
if (lane) body.lane = lane;
|
|
3315
|
-
if (color) body.color = color;
|
|
3316
|
-
if (fill) body.fill = fill;
|
|
3317
|
-
if (textColor) body.textColor = textColor;
|
|
3318
|
-
if (group) body.group = group;
|
|
3319
|
-
if (width) body.width = width;
|
|
3320
|
-
if (height) body.height = height;
|
|
3321
|
-
if (layoutIgnore) body.layoutIgnore = true;
|
|
3322
|
-
const result = await api('POST', '/api/fs/shape', body);
|
|
3323
|
-
return ok(result);
|
|
3324
|
-
} catch (error) { return err(error); }
|
|
3325
|
-
});
|
|
3326
|
-
|
|
3327
|
-
tool('group', 'Create a group (swim lane / region) on the surface. Groups are labeled, colored background areas that visually contain shapes. Assign shapes to a group using the group parameter on the shape tool. Use layout with groups=true to auto-cluster.', {
|
|
3328
|
-
label: z.string().describe('Group label text (displayed in the header bar)'),
|
|
3329
|
-
color: z.string().optional().describe('Header bar and border color (CSS color string)'),
|
|
3330
|
-
fill: z.string().optional().describe('Background fill color (CSS color string). Defaults to a tinted version of color.'),
|
|
3331
|
-
layer: z.string().optional().describe('Layer to place the group in (default: plans)'),
|
|
3332
|
-
lane: z.string().optional().describe('Lane within the layer (default: default)'),
|
|
3333
|
-
order: z.number().optional().describe('Sort order for group positioning (lower = first). Default: 0'),
|
|
3334
|
-
}, async ({ label, color, fill, layer, lane, order }) => {
|
|
3335
|
-
try {
|
|
3336
|
-
const body = { label };
|
|
3337
|
-
if (color) body.color = color;
|
|
3338
|
-
if (fill) body.fill = fill;
|
|
3339
|
-
if (layer) body.layer = layer;
|
|
3340
|
-
if (lane) body.lane = lane;
|
|
3341
|
-
if (order != null) body.order = order;
|
|
3342
|
-
const result = await api('POST', '/api/fs/group', body);
|
|
3343
|
-
return ok(result);
|
|
3344
|
-
} catch (error) { return err(error); }
|
|
3345
|
-
});
|
|
3346
3311
|
// ── Connector tools ───────────────────────────────────────────────
|
|
3347
3312
|
|
|
3348
3313
|
tool('connector', 'Create or remove connectors (arrows) between frames on the surface. `action=connect` adds an arrow from source to target. `action=disconnect` removes one — pass either connectorId directly or source+target to find and delete.', {
|
|
@@ -3393,11 +3358,9 @@ tool('connector', 'Create or remove connectors (arrows) between frames on the su
|
|
|
3393
3358
|
|
|
3394
3359
|
tool('layout', 'Auto-arrange frames using graph layout algorithm. Positions connected frames as a directed graph.', {
|
|
3395
3360
|
direction: z.enum(['TB', 'LR', 'BT', 'RL']).optional().default('TB').describe('Layout direction: TB (top-bottom), LR (left-right), BT (bottom-top), RL (right-left)'),
|
|
3396
|
-
|
|
3397
|
-
}, async ({ direction, groups }) => {
|
|
3361
|
+
}, async ({ direction }) => {
|
|
3398
3362
|
try {
|
|
3399
3363
|
const body = { direction };
|
|
3400
|
-
if (groups) body.groups = true;
|
|
3401
3364
|
const result = await api('POST', '/api/layout', body);
|
|
3402
3365
|
return ok(result);
|
|
3403
3366
|
} catch (error) { return err(error); }
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "drafted",
|
|
3
|
-
"version": "1.14.
|
|
3
|
+
"version": "1.14.10",
|
|
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": [
|