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 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
- return cb(...args);
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
- function reportInstallationEvent(event) {
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
- mcpMode: process.argv.includes('--http') ? 'http' : 'stdio',
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.18",
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": [