drafted 1.7.18 → 1.7.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/install-mcp.sh +42 -0
- package/mcp/server.mjs +97 -6
- package/package.json +1 -1
package/install-mcp.sh
CHANGED
|
@@ -586,6 +586,7 @@ struct DraftedUpdaterApp: App {
|
|
|
586
586
|
var body: some Scene {
|
|
587
587
|
MenuBarExtra {
|
|
588
588
|
Button("Update Drafted") { Actions.updateDrafted() }
|
|
589
|
+
Button("View Logs") { Actions.viewLogs() }
|
|
589
590
|
Button("Open Drafted") { Actions.openDrafted() }
|
|
590
591
|
Divider()
|
|
591
592
|
Button("Quit") { NSApp.terminate(nil) }
|
|
@@ -735,6 +736,47 @@ struct Actions {
|
|
|
735
736
|
}
|
|
736
737
|
}
|
|
737
738
|
|
|
739
|
+
static func viewLogs() {
|
|
740
|
+
let fm = FileManager.default
|
|
741
|
+
let home = fm.homeDirectoryForCurrentUser
|
|
742
|
+
let fallback = home.appendingPathComponent(".drafted")
|
|
743
|
+
try? fm.createDirectory(at: fallback, withIntermediateDirectories: true)
|
|
744
|
+
let readme = fallback.appendingPathComponent("logs-readme.txt")
|
|
745
|
+
if !fm.fileExists(atPath: readme.path) {
|
|
746
|
+
let text = """
|
|
747
|
+
Drafted MCP logs are written by the host app that launched drafted-mcp.
|
|
748
|
+
|
|
749
|
+
Most useful locations:
|
|
750
|
+
- Drafted MCP client errors: ~/.drafted/mcp-client.log
|
|
751
|
+
- Claude Desktop: ~/Library/Logs/Claude/mcp-server-drafted.log
|
|
752
|
+
- Claude Code: ~/.claude/projects (session transcripts) or ~/.claude/logs when present
|
|
753
|
+
- Drafted installer/updater: ~/.drafted
|
|
754
|
+
|
|
755
|
+
If a tool returns `fetch failed`, open the Claude log or transcript from the same run and look for `mcp-server-drafted` or `Drafted MCP`.
|
|
756
|
+
"""
|
|
757
|
+
try? text.write(to: readme, atomically: true, encoding: .utf8)
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
let candidates = [
|
|
761
|
+
home.appendingPathComponent(".drafted/mcp-client.log"),
|
|
762
|
+
home.appendingPathComponent("Library/Logs/Claude/mcp-server-drafted.log"),
|
|
763
|
+
home.appendingPathComponent("Library/Logs/Claude"),
|
|
764
|
+
home.appendingPathComponent(".claude/logs"),
|
|
765
|
+
home.appendingPathComponent(".claude/projects"),
|
|
766
|
+
fallback,
|
|
767
|
+
]
|
|
768
|
+
for url in candidates where fm.fileExists(atPath: url.path) {
|
|
769
|
+
var isDirectory: ObjCBool = false
|
|
770
|
+
fm.fileExists(atPath: url.path, isDirectory: &isDirectory)
|
|
771
|
+
if isDirectory.boolValue {
|
|
772
|
+
NSWorkspace.shared.open(url)
|
|
773
|
+
} else {
|
|
774
|
+
NSWorkspace.shared.activateFileViewerSelecting([url])
|
|
775
|
+
}
|
|
776
|
+
return
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
|
|
738
780
|
static func openDrafted() {
|
|
739
781
|
NSWorkspace.shared.open(URL(string: "https://drafted.live")!)
|
|
740
782
|
}
|
package/mcp/server.mjs
CHANGED
|
@@ -10,9 +10,9 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
|
10
10
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
11
11
|
import { execFile } from 'child_process';
|
|
12
12
|
import { createHash } from 'node:crypto';
|
|
13
|
-
import { readFileSync, existsSync, realpathSync, writeFileSync, mkdirSync, unlinkSync } from 'fs';
|
|
13
|
+
import { readFileSync, existsSync, realpathSync, writeFileSync, mkdirSync, unlinkSync, appendFileSync } from 'fs';
|
|
14
14
|
import { join, dirname, basename, extname, resolve } from 'path';
|
|
15
|
-
import { homedir } from 'os';
|
|
15
|
+
import { homedir, platform, release as osRelease, arch as osArch } from 'os';
|
|
16
16
|
import { fileURLToPath } from 'url';
|
|
17
17
|
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
18
18
|
import { z } from 'zod';
|
|
@@ -202,9 +202,15 @@ function tool(name, descOrSchema, schemaOrHandler, handler) {
|
|
|
202
202
|
}
|
|
203
203
|
return server.registerTool(name, config, async (...args) => {
|
|
204
204
|
const state = getState();
|
|
205
|
+
const previousTool = state.currentTool;
|
|
206
|
+
state.currentTool = name;
|
|
205
207
|
trackUmamiEvent(UMAMI_EVENTS.MCP_TOOL_CALLED, { tool: name, projectId: state.projectId || undefined, source: 'mcp' });
|
|
206
|
-
reportInstallationEvent(UMAMI_EVENTS.DRAFTED_MCP_REQUEST);
|
|
207
|
-
|
|
208
|
+
reportInstallationEvent(UMAMI_EVENTS.DRAFTED_MCP_REQUEST, { tool: name });
|
|
209
|
+
try {
|
|
210
|
+
return await cb(...args);
|
|
211
|
+
} finally {
|
|
212
|
+
state.currentTool = previousTool;
|
|
213
|
+
}
|
|
208
214
|
});
|
|
209
215
|
}
|
|
210
216
|
|
|
@@ -301,7 +307,25 @@ function getInstallInfo() {
|
|
|
301
307
|
}
|
|
302
308
|
}
|
|
303
309
|
|
|
304
|
-
|
|
310
|
+
|
|
311
|
+
function osFamily() {
|
|
312
|
+
const p = platform();
|
|
313
|
+
if (p === 'darwin') return 'macos';
|
|
314
|
+
if (p === 'win32') return 'windows';
|
|
315
|
+
if (p === 'linux') return 'linux';
|
|
316
|
+
return 'unknown';
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function normalizedArch() {
|
|
320
|
+
const a = osArch();
|
|
321
|
+
return ['x64', 'arm64', 'arm', 'ia32'].includes(a) ? a : 'unknown';
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function mcpMode() {
|
|
325
|
+
return process.argv.includes('--http') ? 'http' : 'stdio';
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
function reportInstallationEvent(event, extra = {}) {
|
|
305
329
|
const info = getInstallInfo();
|
|
306
330
|
if (!info) return;
|
|
307
331
|
fetch(`${getServerUrl()}/api/installations/report`, {
|
|
@@ -312,12 +336,78 @@ function reportInstallationEvent(event) {
|
|
|
312
336
|
event,
|
|
313
337
|
schemaVersion: 1,
|
|
314
338
|
cliVersion: PACKAGE_VERSION,
|
|
315
|
-
|
|
339
|
+
osFamily: osFamily(),
|
|
340
|
+
osVersion: osRelease().slice(0, 60),
|
|
341
|
+
arch: normalizedArch(),
|
|
342
|
+
nodeVersion: process.version,
|
|
343
|
+
mcpMode: mcpMode(),
|
|
316
344
|
source: 'mcp',
|
|
345
|
+
...extra,
|
|
317
346
|
}),
|
|
318
347
|
}).catch(() => {});
|
|
319
348
|
}
|
|
320
349
|
|
|
350
|
+
function classifyMcpError(error) {
|
|
351
|
+
const msg = String(error?.message || error || '').toLowerCase();
|
|
352
|
+
const causeCode = String(error?.cause?.code || error?.code || '').toLowerCase();
|
|
353
|
+
if (causeCode) return causeCode.slice(0, 80);
|
|
354
|
+
if (msg.includes('fetch failed')) return 'fetch_failed';
|
|
355
|
+
if (msg.includes('failed to fetch')) return 'fetch_failed';
|
|
356
|
+
if (msg.includes('network')) return 'network_error';
|
|
357
|
+
if (msg.includes('timeout') || msg.includes('timed out')) return 'timeout';
|
|
358
|
+
const httpMatch = msg.match(/http\s+(\d{3})/);
|
|
359
|
+
if (httpMatch) return `http_${httpMatch[1]}`;
|
|
360
|
+
if (msg.includes('unauthorized') || msg.includes('401')) return 'auth_401';
|
|
361
|
+
if (msg.includes('forbidden') || msg.includes('403')) return 'auth_403';
|
|
362
|
+
return 'tool_error';
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
function scrubLogValue(value) {
|
|
366
|
+
if (typeof value !== 'string') return value;
|
|
367
|
+
return value
|
|
368
|
+
.replace(/([?&](?:token|code|dci|session|auth|password|secret)=)[^&\s]+/gi, '$1[Filtered]')
|
|
369
|
+
.replace(/(Bearer\s+)[A-Za-z0-9._~+\/-]+=*/gi, '$1[Filtered]')
|
|
370
|
+
.replace(/(gc_session=)[^;\s]+/gi, '$1[Filtered]')
|
|
371
|
+
.slice(0, 500);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
function serverOriginForLog() {
|
|
375
|
+
try { return new URL(getServerUrl()).origin; } catch { return 'invalid_server_url'; }
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
function writeMcpClientLog(tool, error) {
|
|
379
|
+
const entry = {
|
|
380
|
+
ts: new Date().toISOString(),
|
|
381
|
+
level: 'error',
|
|
382
|
+
component: 'drafted-mcp-client',
|
|
383
|
+
version: PACKAGE_VERSION,
|
|
384
|
+
tool: String(tool || 'unknown').slice(0, 80),
|
|
385
|
+
errorCode: classifyMcpError(error),
|
|
386
|
+
message: scrubLogValue(error?.message || String(error)),
|
|
387
|
+
causeCode: scrubLogValue(error?.cause?.code || error?.code || ''),
|
|
388
|
+
causeMessage: scrubLogValue(error?.cause?.message || ''),
|
|
389
|
+
server: serverOriginForLog(),
|
|
390
|
+
osFamily: osFamily(),
|
|
391
|
+
nodeVersion: process.version,
|
|
392
|
+
mcpMode: mcpMode(),
|
|
393
|
+
};
|
|
394
|
+
const line = `[Drafted MCP] ${JSON.stringify(entry)}\n`;
|
|
395
|
+
try { console.error(line.trimEnd()); } catch { /* ignore */ }
|
|
396
|
+
try {
|
|
397
|
+
const logPath = join(homedir(), '.drafted', 'mcp-client.log');
|
|
398
|
+
mkdirSync(dirname(logPath), { recursive: true });
|
|
399
|
+
appendFileSync(logPath, line, { mode: 0o600 });
|
|
400
|
+
} catch { /* best effort */ }
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
function reportMcpToolError(tool, error) {
|
|
404
|
+
writeMcpClientLog(tool, error);
|
|
405
|
+
reportInstallationEvent(UMAMI_EVENTS.DRAFTED_MCP_ERROR, {
|
|
406
|
+
tool: String(tool || 'unknown').slice(0, 80),
|
|
407
|
+
errorCode: classifyMcpError(error),
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
|
|
321
411
|
function wikiBrowserUrl(path = '') {
|
|
322
412
|
const normalized = normalizeWikiPath(path || '');
|
|
323
413
|
if (!normalized) return `${getServerUrl()}/wiki`;
|
|
@@ -702,6 +792,7 @@ if (!globalThis.__draftedAgentWsBootstrapped) {
|
|
|
702
792
|
}
|
|
703
793
|
|
|
704
794
|
function err(error) {
|
|
795
|
+
reportMcpToolError(getState().currentTool, error);
|
|
705
796
|
return { content: [{ type: 'text', text: error.message || String(error) }], isError: true };
|
|
706
797
|
}
|
|
707
798
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "drafted",
|
|
3
|
-
"version": "1.7.
|
|
3
|
+
"version": "1.7.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": [
|