drafted 1.11.18 → 1.11.19
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 +41 -19
- package/package.json +1 -1
package/mcp/server.mjs
CHANGED
|
@@ -981,25 +981,6 @@ function setMcpActiveProject(projectId, meta = null) {
|
|
|
981
981
|
if (meta?.orgId) sess.boundOrgId = meta.orgId;
|
|
982
982
|
}
|
|
983
983
|
|
|
984
|
-
// Clear the active project if its orgId no longer matches the current org.
|
|
985
|
-
// Catches the silent drift where org switches (via get_org switch, browser
|
|
986
|
-
// tab, parallel agent, etc.) leave the MCP session pointing at a project
|
|
987
|
-
// the API can no longer see — which surfaces as confusing "Project not
|
|
988
|
-
// found" errors on otherwise-valid tool calls.
|
|
989
|
-
async function ensureProjectMatchesOrg() {
|
|
990
|
-
const s = getState();
|
|
991
|
-
const meta = s.projectMeta;
|
|
992
|
-
if (!s.projectId || !meta?.orgId) return;
|
|
993
|
-
const currentOrgId = await getCurrentOrgId();
|
|
994
|
-
if (currentOrgId && currentOrgId !== meta.orgId) {
|
|
995
|
-
s.projectId = null;
|
|
996
|
-
s.projectMeta = null;
|
|
997
|
-
const sess = getSessionState();
|
|
998
|
-
sess.activeProjectId = null;
|
|
999
|
-
sess.activeProjectMeta = null;
|
|
1000
|
-
}
|
|
1001
|
-
}
|
|
1002
|
-
|
|
1003
984
|
// Returns { id, slug, name, orgId } for the project this MCP session most
|
|
1004
985
|
// recently opened — what frame mutations will actually target. Echoed on
|
|
1005
986
|
// every mutation so silent cross-project drift is visible.
|
|
@@ -1101,6 +1082,36 @@ async function getCurrentOrgContext() {
|
|
|
1101
1082
|
} catch { return null; }
|
|
1102
1083
|
}
|
|
1103
1084
|
|
|
1085
|
+
// Project-less mutations (wiki, skills) have no resource to derive the org from.
|
|
1086
|
+
// If the agent never bound an org this session (no project opened, no get_org
|
|
1087
|
+
// switch) and passed no explicit org, refuse to GUESS when the user belongs to
|
|
1088
|
+
// more than one org — otherwise the write silently lands in whatever org the
|
|
1089
|
+
// session happened to inherit (the "wiki write into the wrong org" failure).
|
|
1090
|
+
// Single-org users are unambiguous and proceed untouched. (DRAFT-36 Phase 4.)
|
|
1091
|
+
async function requireBoundOrgForProjectlessMutation(explicitOrg) {
|
|
1092
|
+
if (explicitOrg) return;
|
|
1093
|
+
const sess = getSessionState();
|
|
1094
|
+
if (sess.boundOrgId) return; // bound via project open / get_org switch
|
|
1095
|
+
if (getState().projectId) return; // an active project implies its org
|
|
1096
|
+
let count = sess.cachedOrgCount;
|
|
1097
|
+
if (count == null) {
|
|
1098
|
+
try {
|
|
1099
|
+
const d = await api('GET', '/api/orgs');
|
|
1100
|
+
count = (d.orgs || d || []).length;
|
|
1101
|
+
sess.cachedOrgCount = count;
|
|
1102
|
+
} catch { return; } // can't determine membership — don't block a legit write
|
|
1103
|
+
}
|
|
1104
|
+
if (count > 1) {
|
|
1105
|
+
throw new Error(
|
|
1106
|
+
`Refusing to guess the org: no project is open and no org was bound this ` +
|
|
1107
|
+
`session, but you belong to ${count} orgs — a project-less wiki/skill write ` +
|
|
1108
|
+
`would land in whichever org the session inherited. Call ` +
|
|
1109
|
+
`get_org(action="switch", orgId=...) or project(action="open") first ` +
|
|
1110
|
+
`(skill calls may also pass org=...).`
|
|
1111
|
+
);
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1104
1115
|
async function getOrgSkills(orgId) {
|
|
1105
1116
|
if (!orgId) return [];
|
|
1106
1117
|
// No cache: gate freshness > the ~5ms HTTP roundtrip. Otherwise an attach
|
|
@@ -2886,6 +2897,11 @@ tool('skill', 'Manage the Drafted skill library. Skills are reusable prompts/gui
|
|
|
2886
2897
|
}, async (args) => {
|
|
2887
2898
|
try {
|
|
2888
2899
|
const { action } = args;
|
|
2900
|
+
// Org-creating/editing skill actions on the project-less surface must have a
|
|
2901
|
+
// bound org (or explicit org=) when the user is multi-org. (DRAFT-36 Phase 4.)
|
|
2902
|
+
if (['add', 'update', 'remove', 'push', 'fork'].includes(action)) {
|
|
2903
|
+
await requireBoundOrgForProjectlessMutation(args.org);
|
|
2904
|
+
}
|
|
2889
2905
|
switch (action) {
|
|
2890
2906
|
case 'search': {
|
|
2891
2907
|
const { query, tags, scope = 'all', limit, offset, compact } = args;
|
|
@@ -3120,6 +3136,12 @@ tool('wiki', 'Per-org wiki. Markdown pages with paths as hierarchy. You and othe
|
|
|
3120
3136
|
try {
|
|
3121
3137
|
const { action } = args;
|
|
3122
3138
|
|
|
3139
|
+
// Project-less wiki mutations must have a bound org when the user is
|
|
3140
|
+
// multi-org, so a write never silently lands in an inherited org. (Ph4.)
|
|
3141
|
+
if (['write', 'edit', 'mv', 'rm', 'bulk-write'].includes(action)) {
|
|
3142
|
+
await requireBoundOrgForProjectlessMutation(undefined);
|
|
3143
|
+
}
|
|
3144
|
+
|
|
3123
3145
|
// Resolve org context once for the wiki tool. Each MCP session is bound
|
|
3124
3146
|
// to one org (parallel agents on different orgs is supported by design).
|
|
3125
3147
|
const orgCtx = await getCurrentOrgContext();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "drafted",
|
|
3
|
-
"version": "1.11.
|
|
3
|
+
"version": "1.11.19",
|
|
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": [
|