drafted 1.12.6 → 1.12.8
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 +57 -11
- package/package.json +1 -1
package/mcp/server.mjs
CHANGED
|
@@ -200,7 +200,9 @@ IMPORTANT: Any URL containing /f/{uuid} is a Drafted frame link — ALWAYS use r
|
|
|
200
200
|
}, {
|
|
201
201
|
// Initialize instructions: the agent-identity contract, so an agent learns its own
|
|
202
202
|
// tab name + the right way to read it WITHOUT having to "think to" call a tool.
|
|
203
|
-
instructions: `SESSION IDENTITY — read this first: you run as a NAMED session tab visible to the user on their Drafted surface. Your session has a human-readable name (a Greek term, e.g. "Nous") — that name is how the user matches YOUR window to the tab they see, so identify yourself by it when it matters which agent you are. Read it from get_org (response field "session.name") or from the whoami tool; call whoami to refresh after a reconnect
|
|
203
|
+
instructions: `SESSION IDENTITY — read this first: you run as a NAMED session tab visible to the user on their Drafted surface. Your session has a human-readable name (a Greek term, e.g. "Nous") — that name is how the user matches YOUR window to the tab they see, so identify yourself by it when it matters which agent you are. Read it from get_org (response field "session.name") or from the whoami tool; call whoami to refresh after a reconnect.
|
|
204
|
+
|
|
205
|
+
DUAL REGISTRATION IS NORMAL: a separate "Drafted" remote connector (https://drafted.live/mcp, managed by claude.ai) may appear alongside this local stdio server in MCP listings. It is NOT a duplicate and NOT broken — it serves claude.ai web/mobile/Cowork, where a local stdio process cannot run. Never advise removing it; a "needs authentication" state on it is fixed by signing in from claude.ai and does not affect this stdio session.`,
|
|
204
206
|
});
|
|
205
207
|
|
|
206
208
|
const layerKeys = Object.keys(LAYERS);
|
|
@@ -804,7 +806,7 @@ async function cloneSession() {
|
|
|
804
806
|
// instead of churning a fresh session every process. (Org is per-request, so a
|
|
805
807
|
// shared child session does NOT cause cross-agent org clobbering.)
|
|
806
808
|
const agentKey = (process.env.DRAFTED_AGENT_NAME || '').trim() || bootstrapId;
|
|
807
|
-
const res = await
|
|
809
|
+
const res = await serverFetch(`${getServerUrl()}/auth/session/clone`, {
|
|
808
810
|
method: 'POST',
|
|
809
811
|
headers: { 'Content-Type': 'application/json', Cookie: `gc_session=${bootstrapId}` },
|
|
810
812
|
body: JSON.stringify({ agentKey }),
|
|
@@ -836,7 +838,7 @@ async function restoreBoundOrg(clonedOrgId) {
|
|
|
836
838
|
const want = sess.boundOrgId;
|
|
837
839
|
if (!want || want === clonedOrgId) return;
|
|
838
840
|
try {
|
|
839
|
-
const res = await
|
|
841
|
+
const res = await serverFetch(`${getServerUrl()}/auth/switch-org`, {
|
|
840
842
|
method: 'POST',
|
|
841
843
|
headers: { 'Content-Type': 'application/json', ...getAuthHeaders() },
|
|
842
844
|
body: JSON.stringify({ orgId: want }),
|
|
@@ -866,6 +868,37 @@ const MIME_MAP = {
|
|
|
866
868
|
};
|
|
867
869
|
function mimeFromExt(ext) { return MIME_MAP[ext?.toLowerCase()] || 'application/octet-stream'; }
|
|
868
870
|
|
|
871
|
+
// TLS-trust failures from antivirus/corporate HTTPS interception (Norton,
|
|
872
|
+
// Zscaler, ...) surface from undici as a bare "fetch failed" — agents then tell
|
|
873
|
+
// users "network error / you're offline" for a day (real incident, 2026-07-02).
|
|
874
|
+
// Node does not use the OS certificate store, so the interceptor's re-signed
|
|
875
|
+
// chain is untrusted. Translate the code into the actual fix.
|
|
876
|
+
const TLS_TRUST_CODES = new Set([
|
|
877
|
+
'UNABLE_TO_VERIFY_LEAF_SIGNATURE', 'SELF_SIGNED_CERT_IN_CHAIN',
|
|
878
|
+
'DEPTH_ZERO_SELF_SIGNED_CERT', 'UNABLE_TO_GET_ISSUER_CERT_LOCALLY',
|
|
879
|
+
'UNABLE_TO_GET_ISSUER_CERT', 'CERT_UNTRUSTED',
|
|
880
|
+
]);
|
|
881
|
+
function enrichFetchError(error, url) {
|
|
882
|
+
const code = String(error?.cause?.code || error?.code || '').toUpperCase();
|
|
883
|
+
if (!TLS_TRUST_CODES.has(code)) return error;
|
|
884
|
+
const e = new Error(
|
|
885
|
+
`TLS interception detected reaching ${url} (${code}): antivirus or a corporate proxy ` +
|
|
886
|
+
`(Norton, Zscaler, Netskope, ...) is re-signing HTTPS, and Node does not use the OS ` +
|
|
887
|
+
`certificate store, so it rejects the interceptor's certificate. This is NOT a Drafted ` +
|
|
888
|
+
`outage and NOT a sign-out. Fixes, best first: (1) exclude drafted.live and *.drafted.live ` +
|
|
889
|
+
`from the interceptor's HTTPS/SSL scanning; (2) export the interceptor's root certificate ` +
|
|
890
|
+
`to a PEM file and set NODE_EXTRA_CA_CERTS=<path> in this MCP server's env; (3) on Node ` +
|
|
891
|
+
`>= 22.15, set NODE_OPTIONS=--use-system-ca so Node trusts the OS store. NEVER set ` +
|
|
892
|
+
`NODE_TLS_REJECT_UNAUTHORIZED=0 (disables all TLS verification).`
|
|
893
|
+
);
|
|
894
|
+
e.cause = error;
|
|
895
|
+
return e;
|
|
896
|
+
}
|
|
897
|
+
async function serverFetch(url, opts) {
|
|
898
|
+
try { return await fetch(url, opts); }
|
|
899
|
+
catch (e) { throw enrichFetchError(e, url); }
|
|
900
|
+
}
|
|
901
|
+
|
|
869
902
|
async function api(method, path, body, extraHeaders = {}, _retried = false) {
|
|
870
903
|
await ensureSession();
|
|
871
904
|
const pid = getState().projectId;
|
|
@@ -897,7 +930,7 @@ async function api(method, path, body, extraHeaders = {}, _retried = false) {
|
|
|
897
930
|
opts.body = JSON.stringify(body);
|
|
898
931
|
}
|
|
899
932
|
|
|
900
|
-
const res = await
|
|
933
|
+
const res = await serverFetch(url, opts);
|
|
901
934
|
const text = await res.text();
|
|
902
935
|
|
|
903
936
|
// Session expired after server restart, or a browser approval just completed
|
|
@@ -1397,7 +1430,7 @@ async function consumePendingDeviceCode() {
|
|
|
1397
1430
|
if (!pending?.deviceCode) return false;
|
|
1398
1431
|
|
|
1399
1432
|
try {
|
|
1400
|
-
const res = await
|
|
1433
|
+
const res = await serverFetch(`${getServerUrl()}/auth/device/token`, {
|
|
1401
1434
|
method: 'POST',
|
|
1402
1435
|
headers: { 'Content-Type': 'application/json' },
|
|
1403
1436
|
body: JSON.stringify({ deviceCode: pending.deviceCode }),
|
|
@@ -1433,7 +1466,7 @@ if (!isRemote) tool('auth', 'Sign in to Drafted. `action=get_link` returns a ver
|
|
|
1433
1466
|
}, async ({ action }) => {
|
|
1434
1467
|
try {
|
|
1435
1468
|
if (action === 'get_link') {
|
|
1436
|
-
const codeRes = await
|
|
1469
|
+
const codeRes = await serverFetch(`${getServerUrl()}/auth/device/code`, { method: 'POST' });
|
|
1437
1470
|
if (!codeRes.ok) throw new Error(`Failed to start device authorization (HTTP ${codeRes.status})`);
|
|
1438
1471
|
const data = await codeRes.json();
|
|
1439
1472
|
persistPendingDeviceCode(data);
|
|
@@ -1447,7 +1480,7 @@ if (!isRemote) tool('auth', 'Sign in to Drafted. `action=get_link` returns a ver
|
|
|
1447
1480
|
const existing = getState().sessionId || getBootstrapSessionId();
|
|
1448
1481
|
if (existing) {
|
|
1449
1482
|
try {
|
|
1450
|
-
const res = await
|
|
1483
|
+
const res = await serverFetch(`${getServerUrl()}/auth/me`, {
|
|
1451
1484
|
headers: { Cookie: `gc_session=${existing}` },
|
|
1452
1485
|
});
|
|
1453
1486
|
if (res.ok) {
|
|
@@ -1465,7 +1498,7 @@ if (!isRemote) tool('auth', 'Sign in to Drafted. `action=get_link` returns a ver
|
|
|
1465
1498
|
({ deviceCode, verificationUrl, expiresIn } = pending);
|
|
1466
1499
|
reusingPending = true;
|
|
1467
1500
|
} else {
|
|
1468
|
-
const codeRes = await
|
|
1501
|
+
const codeRes = await serverFetch(`${getServerUrl()}/auth/device/code`, { method: 'POST' });
|
|
1469
1502
|
if (!codeRes.ok) throw new Error(`Failed to start device authorization (HTTP ${codeRes.status})`);
|
|
1470
1503
|
({ deviceCode, verificationUrl, expiresIn } = await codeRes.json());
|
|
1471
1504
|
}
|
|
@@ -1489,7 +1522,7 @@ if (!isRemote) tool('auth', 'Sign in to Drafted. `action=get_link` returns a ver
|
|
|
1489
1522
|
const deadline = Date.now() + (expiresIn * 1000);
|
|
1490
1523
|
while (Date.now() < deadline) {
|
|
1491
1524
|
await new Promise(r => setTimeout(r, 4000));
|
|
1492
|
-
const res = await
|
|
1525
|
+
const res = await serverFetch(`${getServerUrl()}/auth/device/token`, {
|
|
1493
1526
|
method: 'POST',
|
|
1494
1527
|
headers: { 'Content-Type': 'application/json' },
|
|
1495
1528
|
body: JSON.stringify({ deviceCode }),
|
|
@@ -1540,11 +1573,20 @@ async function sessionSurfaceBlock() {
|
|
|
1540
1573
|
// No WS ack yet — best-effort identity from /auth/me so callers still get a userId/org.
|
|
1541
1574
|
const cookieSid = sessionId || getBootstrapSessionId();
|
|
1542
1575
|
let me = null;
|
|
1576
|
+
let unreachable = false;
|
|
1577
|
+
let unreachableWhy = null;
|
|
1543
1578
|
if (cookieSid) {
|
|
1544
1579
|
try {
|
|
1545
|
-
const res = await
|
|
1580
|
+
const res = await serverFetch(`${getServerUrl()}/auth/me`, { headers: { Cookie: `gc_session=${cookieSid}` } });
|
|
1546
1581
|
if (res.ok) me = await res.json();
|
|
1547
|
-
} catch {
|
|
1582
|
+
} catch (e) {
|
|
1583
|
+
// Transport failure, NOT a sign-out. Conflating the two made agents tell
|
|
1584
|
+
// users "you're signed out" during a network blip and start needless
|
|
1585
|
+
// re-logins — surface the distinction instead. serverFetch already
|
|
1586
|
+
// translates TLS-interception codes into the actual fix.
|
|
1587
|
+
unreachable = true;
|
|
1588
|
+
if (String(e?.message || '').startsWith('TLS interception')) unreachableWhy = e.message;
|
|
1589
|
+
}
|
|
1548
1590
|
}
|
|
1549
1591
|
return {
|
|
1550
1592
|
sessionId: cookieSid,
|
|
@@ -1556,6 +1598,10 @@ async function sessionSurfaceBlock() {
|
|
|
1556
1598
|
color: null,
|
|
1557
1599
|
surfaced: false,
|
|
1558
1600
|
alive: false,
|
|
1601
|
+
...(unreachable ? {
|
|
1602
|
+
serverUnreachable: true,
|
|
1603
|
+
note: unreachableWhy || `Could not reach ${getServerUrl()} — identity UNKNOWN, not signed out. This is a network/transport failure: do not tell the user they are logged out and do not start a new login; retry when connectivity is back.`,
|
|
1604
|
+
} : {}),
|
|
1559
1605
|
};
|
|
1560
1606
|
}
|
|
1561
1607
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "drafted",
|
|
3
|
-
"version": "1.12.
|
|
3
|
+
"version": "1.12.8",
|
|
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": [
|