drafted 1.6.0 → 1.7.1
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 +38 -6
- package/package.json +1 -1
package/mcp/server.mjs
CHANGED
|
@@ -97,7 +97,7 @@ const server = new McpServer({
|
|
|
97
97
|
|
|
98
98
|
An org contains projects. Each project has a zoomable canvas with frames (HTML files) organized as /{layer}/{lane}/{filename}. Layers are predefined categories (wireframes, designs, brand-assets, etc.), lanes are groups within a layer, and frames are the individual design files.
|
|
99
99
|
|
|
100
|
-
WORKFLOW: project(action="list") → project(action="open") → ls / → read/write/edit. Projects span all orgs -- opening a project auto-switches org context. Every response includes a "project" field showing which project you're operating on -- always verify it matches your intent before writing.
|
|
100
|
+
WORKFLOW: project(action="list") → project(action="open") → ls / → read/write/edit. Projects span all orgs -- opening a project auto-switches org context. Every response includes a "project" field showing which project you're operating on -- always verify it matches your intent before writing. To switch orgs without opening a project (for wiki/skill work in an org with no projects), call get_org(action="switch", orgId=...).
|
|
101
101
|
|
|
102
102
|
SKILLS: Drafted has a skill library -- reusable agent instructions stored as SKILL.md files. When a user says "use the X skill", call skill(action="search") to find it, then skill(action="load") to get its instructions. Skills can cover anything: UX guidelines, copywriting rules, brand voice, coding standards, review checklists, etc.
|
|
103
103
|
|
|
@@ -793,7 +793,7 @@ tool('auth', 'Sign in to Drafted. `action=get_link` returns a verification URL i
|
|
|
793
793
|
|
|
794
794
|
// ── Project management tools (direct HTTP) ────────────────────────
|
|
795
795
|
|
|
796
|
-
tool('project', 'START HERE for project management. Dispatch by `action`: list (lists all projects across all orgs — always call first), open (switch the active project; required before reading/writing frames), create (new project, optionally from a template), update (change name/folder/description/layers), move (transfer to another org).
|
|
796
|
+
tool('project', 'START HERE for project management. Dispatch by `action`: list (lists all projects across all orgs — always call first), open (switch the active project; required before reading/writing frames), create (new project, optionally from a template), update (change name/folder/description/layers), move (transfer to another org). Opening a project auto-switches the org. To change orgs WITHOUT a project (for wiki/skill work in an empty org), use get_org(action="switch", orgId=...). **Skill gate:** projects with attached skills will REJECT all mutations (write, edit, mv, rm, shape, group, 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.', {
|
|
797
797
|
action: z.enum(['list', 'open', 'create', 'update', 'move']).describe('Operation to perform.'),
|
|
798
798
|
projectId: z.string().optional().describe('[open|update|move] project ID. Get IDs from action=list.'),
|
|
799
799
|
name: z.string().optional().describe('[create|update] project name'),
|
|
@@ -1157,8 +1157,29 @@ tool('layer', 'Manage layers in a project. Dispatch by `action`: add/update/remo
|
|
|
1157
1157
|
} catch (error) { return err(error); }
|
|
1158
1158
|
});
|
|
1159
1159
|
|
|
1160
|
-
tool('get_org', {
|
|
1160
|
+
tool('get_org', {
|
|
1161
|
+
action: z.enum(['get', 'switch']).optional().describe('Default: "get" returns active org and member info. Use "switch" with orgId to change the active org without opening a project.'),
|
|
1162
|
+
orgId: z.string().optional().describe('[switch] target org ID to switch to. Must be one of the orgs the user is a member of.'),
|
|
1163
|
+
}, async (args = {}) => {
|
|
1161
1164
|
try {
|
|
1165
|
+
const action = args.action || 'get';
|
|
1166
|
+
|
|
1167
|
+
if (action === 'switch') {
|
|
1168
|
+
if (!args.orgId) throw new Error('orgId is required for action=switch');
|
|
1169
|
+
await api('POST', '/auth/switch-org', { orgId: args.orgId });
|
|
1170
|
+
// Bust the per-session org cache so subsequent calls re-fetch /auth/me.
|
|
1171
|
+
const sess = getSessionState();
|
|
1172
|
+
sess.cachedOrgId = null;
|
|
1173
|
+
sess.cachedOrgIdTime = 0;
|
|
1174
|
+
// Clear active project too — projects are scoped to orgs, so the
|
|
1175
|
+
// previous one isn't valid in the new org.
|
|
1176
|
+
setMcpActiveProject(null, null);
|
|
1177
|
+
const me = await api('GET', '/auth/me');
|
|
1178
|
+
const orgs = (await api('GET', '/api/orgs')).orgs || [];
|
|
1179
|
+
const activeOrg = (orgs || []).map(o => ({ id: o.orgId || o.id, name: o.orgName || o.name })).find(o => o.id === me?.orgId) || null;
|
|
1180
|
+
return ok({ switched: true, activeOrg, note: 'Active org switched. Wiki and skill calls now target this org. Active project cleared — open a project (or stay org-scoped for wiki/skill).' });
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1162
1183
|
// Source of truth = THIS MCP session's bound org (what mutations will actually
|
|
1163
1184
|
// hit). Each session is independent on purpose — multiple agents can run in
|
|
1164
1185
|
// parallel against different orgs. /auth/me reads sessions.org_id directly.
|
|
@@ -1180,7 +1201,7 @@ tool('get_org', {}, async () => {
|
|
|
1180
1201
|
activeOrg,
|
|
1181
1202
|
orgs,
|
|
1182
1203
|
members: members.map(m => ({ id: m.userId, name: m.username, email: m.email, role: m.role })),
|
|
1183
|
-
note: "activeOrg is the org bound to THIS MCP session — what mutations will actually target. Browser tabs and other MCP sessions for the same user can be on different orgs.",
|
|
1204
|
+
note: "activeOrg is the org bound to THIS MCP session — what mutations will actually target. To switch orgs without creating a project, call get_org(action=\"switch\", orgId=\"...\"). Browser tabs and other MCP sessions for the same user can be on different orgs.",
|
|
1184
1205
|
});
|
|
1185
1206
|
} catch (error) { return err(error); }
|
|
1186
1207
|
});
|
|
@@ -1735,6 +1756,7 @@ tool('skill', 'Manage the Drafted skill library. Skills are reusable prompts/gui
|
|
|
1735
1756
|
limit: z.number().optional().describe('[search] max results (default 25, max 100)'),
|
|
1736
1757
|
skill: z.string().optional().describe('[load] skill ID (UUID) or slug'),
|
|
1737
1758
|
skillId: z.string().optional().describe('[update|remove|attach|detach|favorite|unfavorite|read_file|update_file] skill ID'),
|
|
1759
|
+
projectId: z.string().optional().describe('[list] project to list skills for (defaults to active project; falls back to org-attached skills if no project)'),
|
|
1738
1760
|
name: z.string().optional().describe('[add|update] skill name'),
|
|
1739
1761
|
description: z.string().optional().describe('[add|update] one-line description'),
|
|
1740
1762
|
content: z.string().optional().describe('[add|update] root SKILL.md content; [update_file] file content'),
|
|
@@ -1771,8 +1793,18 @@ tool('skill', 'Manage the Drafted skill library. Skills are reusable prompts/gui
|
|
|
1771
1793
|
return ok(result);
|
|
1772
1794
|
}
|
|
1773
1795
|
case 'list': {
|
|
1774
|
-
|
|
1775
|
-
|
|
1796
|
+
// Prefer the explicit projectId param; otherwise the active project;
|
|
1797
|
+
// otherwise fall back to org-attached skills so list works in
|
|
1798
|
+
// empty-org / wiki-only sessions where there's no project to bind to.
|
|
1799
|
+
const explicit = args.projectId;
|
|
1800
|
+
const active = getState().projectId;
|
|
1801
|
+
if (explicit || active) {
|
|
1802
|
+
const pid = explicit || active;
|
|
1803
|
+
return ok(await api('GET', `/api/projects/${pid}/skills`));
|
|
1804
|
+
}
|
|
1805
|
+
const orgId = await getCurrentOrgId();
|
|
1806
|
+
if (!orgId) throw new Error('No active org. Call get_org first or pass projectId.');
|
|
1807
|
+
return ok(await api('GET', `/api/orgs/${orgId}/skills`));
|
|
1776
1808
|
}
|
|
1777
1809
|
case 'add': {
|
|
1778
1810
|
const { name, description, content, tags, triggerPatterns } = args;
|