sad-mcp 0.1.11 → 0.1.13

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/dist/drive.d.ts CHANGED
@@ -7,5 +7,6 @@ export interface DriveFile {
7
7
  modifiedTime?: string;
8
8
  }
9
9
  export declare function listAllFiles(): Promise<DriveFile[]>;
10
+ export declare function isGoogleWorkspaceFile(file: DriveFile): boolean;
10
11
  export declare function downloadFile(file: DriveFile): Promise<Buffer>;
11
12
  export declare function categorizeFile(file: DriveFile): string;
package/dist/drive.js CHANGED
@@ -80,6 +80,15 @@ export async function listAllFiles() {
80
80
  saveCacheIndex(index);
81
81
  return files;
82
82
  }
83
+ // Google Workspace mimeTypes that need export (not direct download)
84
+ const EXPORT_MIME_MAP = {
85
+ "application/vnd.google-apps.presentation": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
86
+ "application/vnd.google-apps.document": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
87
+ "application/vnd.google-apps.spreadsheet": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
88
+ };
89
+ export function isGoogleWorkspaceFile(file) {
90
+ return file.mimeType in EXPORT_MIME_MAP;
91
+ }
83
92
  export async function downloadFile(file) {
84
93
  const index = loadCacheIndex();
85
94
  const cached = index.files[file.id];
@@ -88,9 +97,18 @@ export async function downloadFile(file) {
88
97
  return readFileSync(cached.localPath);
89
98
  }
90
99
  const drive = await getDrive();
91
- // Download the file
92
- const res = await drive.files.get({ fileId: file.id, alt: "media" }, { responseType: "arraybuffer" });
93
- const buffer = Buffer.from(res.data);
100
+ let buffer;
101
+ // Google Workspace files need export, not direct download
102
+ const exportMime = EXPORT_MIME_MAP[file.mimeType];
103
+ if (exportMime) {
104
+ const res = await drive.files.export({ fileId: file.id, mimeType: exportMime }, { responseType: "arraybuffer" });
105
+ buffer = Buffer.from(res.data);
106
+ }
107
+ else {
108
+ // Regular file — direct download
109
+ const res = await drive.files.get({ fileId: file.id, alt: "media" }, { responseType: "arraybuffer" });
110
+ buffer = Buffer.from(res.data);
111
+ }
94
112
  // Cache locally
95
113
  mkdirSync(CACHE_DIR, { recursive: true });
96
114
  const sanitizedName = file.path.replace(/[/\\:*?"<>|]/g, "_");
@@ -1,3 +1,3 @@
1
- import type { DriveFile } from "./drive.js";
1
+ import { type DriveFile } from "./drive.js";
2
2
  export declare function extractText(file: DriveFile, buffer: Buffer): Promise<string>;
3
3
  export declare function isExtractable(file: DriveFile): boolean;
@@ -1,5 +1,14 @@
1
1
  import officeparser from "officeparser";
2
2
  import pdf from "pdf-parse";
3
+ import { mkdirSync } from "fs";
4
+ import { join } from "path";
5
+ import { homedir } from "os";
6
+ import { isGoogleWorkspaceFile } from "./drive.js";
7
+ // officeparser defaults to a relative "officeParserTemp" dir which resolves to
8
+ // C:\Windows\System32 when Claude Desktop launches the MCP server → EPERM.
9
+ // Use a safe temp location under the user's home instead.
10
+ const OFFICE_TEMP_DIR = join(homedir(), ".sad-mcp", "office-temp");
11
+ mkdirSync(OFFICE_TEMP_DIR, { recursive: true });
3
12
  // pdf-parse uses console.log('Warning: ...') internally, which writes to stdout
4
13
  // and corrupts the MCP JSON-RPC transport. Redirect console.log to stderr during parsing.
5
14
  function withSilentStdout(fn) {
@@ -18,8 +27,15 @@ function withSilentStdout(fn) {
18
27
  throw e;
19
28
  }
20
29
  }
30
+ // Map Google Workspace mimeTypes to their exported Office equivalents
31
+ const GOOGLE_MIME_TO_OFFICE = {
32
+ "application/vnd.google-apps.presentation": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
33
+ "application/vnd.google-apps.document": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
34
+ "application/vnd.google-apps.spreadsheet": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
35
+ };
21
36
  export async function extractText(file, buffer) {
22
- const mimeType = file.mimeType;
37
+ // After export, Google Workspace files arrive in Office format — use that mimeType
38
+ const mimeType = GOOGLE_MIME_TO_OFFICE[file.mimeType] || file.mimeType;
23
39
  const name = file.name.toLowerCase();
24
40
  // Plain text files — return as-is
25
41
  if (mimeType === "text/plain" ||
@@ -42,7 +58,7 @@ export async function extractText(file, buffer) {
42
58
  if (mimeType === "application/vnd.openxmlformats-officedocument.presentationml.presentation" ||
43
59
  name.endsWith(".pptx")) {
44
60
  try {
45
- const text = await officeparser.parseOfficeAsync(buffer);
61
+ const text = await officeparser.parseOfficeAsync(buffer, { tempFilesLocation: OFFICE_TEMP_DIR });
46
62
  return text;
47
63
  }
48
64
  catch (err) {
@@ -53,7 +69,7 @@ export async function extractText(file, buffer) {
53
69
  if (mimeType === "application/vnd.openxmlformats-officedocument.wordprocessingml.document" ||
54
70
  name.endsWith(".docx")) {
55
71
  try {
56
- const text = await officeparser.parseOfficeAsync(buffer);
72
+ const text = await officeparser.parseOfficeAsync(buffer, { tempFilesLocation: OFFICE_TEMP_DIR });
57
73
  return text;
58
74
  }
59
75
  catch (err) {
@@ -64,7 +80,7 @@ export async function extractText(file, buffer) {
64
80
  if (mimeType === "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" ||
65
81
  name.endsWith(".xlsx")) {
66
82
  try {
67
- const text = await officeparser.parseOfficeAsync(buffer);
83
+ const text = await officeparser.parseOfficeAsync(buffer, { tempFilesLocation: OFFICE_TEMP_DIR });
68
84
  return text;
69
85
  }
70
86
  catch (err) {
@@ -81,5 +97,7 @@ export function isExtractable(file) {
81
97
  name.endsWith(".pdf") ||
82
98
  name.endsWith(".pptx") ||
83
99
  name.endsWith(".docx") ||
84
- name.endsWith(".xlsx"));
100
+ name.endsWith(".xlsx") ||
101
+ isGoogleWorkspaceFile(file) // Google Slides, Docs, Sheets — exported then extracted
102
+ );
85
103
  }
package/dist/tools.js CHANGED
@@ -25,8 +25,8 @@ async function ensureTextCache() {
25
25
  textCache.set(file.id, { file, text });
26
26
  saveTextEntry(file.id, { modifiedTime: file.modifiedTime, text });
27
27
  }
28
- catch {
29
- // Skip files that fail to download/extract
28
+ catch (err) {
29
+ console.error(`[sad-mcp] Failed to process "${file.name}": ${err instanceof Error ? err.message : String(err)}`);
30
30
  }
31
31
  }
32
32
  }
@@ -201,8 +201,10 @@ export function registerToolHandlers(server) {
201
201
  saveTextEntry(matchedFile.id, { modifiedTime: matchedFile.modifiedTime, text });
202
202
  bestMatch = { file: matchedFile, text };
203
203
  }
204
- catch {
205
- const errText = `Found file "${matchedFile.name}" but could not extract its text content. It may be an image-heavy presentation. Try searching for a transcript of the same lecture instead (e.g., search for the lecture name in transcripts).`;
204
+ catch (err) {
205
+ const errorDetail = err instanceof Error ? err.message : String(err);
206
+ console.error(`[sad-mcp] get_material failed for "${matchedFile.name}": ${errorDetail}`);
207
+ const errText = `Found file "${matchedFile.name}" but could not extract its text content (${errorDetail}). Try searching for a transcript of the same lecture instead.`;
206
208
  trackToolCall(name, toolArgs, { success: false, responseChars: errText.length }, Date.now() - startTime);
207
209
  return { content: [{ type: "text", text: errText }] };
208
210
  }
package/dist/tracking.js CHANGED
@@ -4,7 +4,7 @@ import { homedir } from "os";
4
4
  import { randomUUID } from "crypto";
5
5
  const CONFIG_DIR = join(homedir(), ".sad-mcp");
6
6
  const ANON_ID_PATH = join(CONFIG_DIR, "anonymous-id.txt");
7
- const VERSION = "0.1.11";
7
+ const VERSION = "0.1.13";
8
8
  const WEBHOOK_URL = "https://script.google.com/macros/s/AKfycbxGraOdki3CUMz6Ch9u17qt_9P01nTAsWeZZN_wrOL9mRUosNriXZmBdEG5RTS2cCjr/exec";
9
9
  // Session ID — unique per server process lifetime
10
10
  const sessionId = randomUUID().slice(0, 8);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sad-mcp",
3
- "version": "0.1.11",
3
+ "version": "0.1.13",
4
4
  "description": "MCP server for Software Analysis and Design course materials at BGU",
5
5
  "type": "module",
6
6
  "bin": {