Skip to content

Commit 0131f54

Browse files
committed
fixes
1 parent bb87e6f commit 0131f54

3 files changed

Lines changed: 49 additions & 25 deletions

File tree

core/attachment_storage.py

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
import os
1111
import uuid
1212
from pathlib import Path
13-
from typing import Optional, Dict
13+
from typing import NamedTuple, Optional, Dict
1414
from datetime import datetime, timedelta
1515

1616
logger = logging.getLogger(__name__)
@@ -21,10 +21,17 @@
2121
# Storage directory - configurable via WORKSPACE_ATTACHMENT_DIR env var
2222
# Uses absolute path to avoid creating tmp/ in arbitrary working directories (see #327)
2323
_default_dir = str(Path.home() / ".workspace-mcp" / "attachments")
24-
STORAGE_DIR = Path(os.getenv("WORKSPACE_ATTACHMENT_DIR", _default_dir))
24+
STORAGE_DIR = Path(os.getenv("WORKSPACE_ATTACHMENT_DIR", _default_dir)).expanduser().resolve()
2525
STORAGE_DIR.mkdir(parents=True, exist_ok=True)
2626

2727

28+
class SavedAttachment(NamedTuple):
29+
"""Result of saving an attachment: provides both the UUID and the absolute file path."""
30+
31+
file_id: str
32+
path: str
33+
34+
2835
class AttachmentStorage:
2936
"""Manages temporary storage of email attachments."""
3037

@@ -37,17 +44,17 @@ def save_attachment(
3744
base64_data: str,
3845
filename: Optional[str] = None,
3946
mime_type: Optional[str] = None,
40-
) -> str:
47+
) -> SavedAttachment:
4148
"""
42-
Save an attachment to local disk and return the absolute file path.
49+
Save an attachment to local disk.
4350
4451
Args:
4552
base64_data: Base64-encoded attachment data
4653
filename: Original filename (optional)
4754
mime_type: MIME type (optional)
4855
4956
Returns:
50-
Absolute file path where the attachment was saved
57+
SavedAttachment with file_id (UUID) and path (absolute file path)
5158
"""
5259
# Generate unique file ID for metadata tracking
5360
file_id = str(uuid.uuid4())
@@ -104,7 +111,7 @@ def save_attachment(
104111
"expires_at": expires_at,
105112
}
106113

107-
return str(file_path)
114+
return SavedAttachment(file_id=file_id, path=str(file_path))
108115

109116
def get_attachment_path(self, file_id: str) -> Optional[Path]:
110117
"""
@@ -213,7 +220,6 @@ def get_attachment_url(file_id: str) -> str:
213220
Returns:
214221
Full URL to access the attachment
215222
"""
216-
import os
217223
from core.config import WORKSPACE_MCP_PORT, WORKSPACE_MCP_BASE_URI
218224

219225
# Use external URL if set (for reverse proxy scenarios)

gdrive/drive_tools.py

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -348,41 +348,46 @@ async def get_drive_file_download_url(
348348
)
349349
return "\n".join(result_lines)
350350

351-
# Save file and generate URL
351+
# Save file to local disk and return file path
352352
try:
353353
storage = get_attachment_storage()
354354

355355
# Encode bytes to base64 (as expected by AttachmentStorage)
356356
base64_data = base64.urlsafe_b64encode(file_content_bytes).decode("utf-8")
357357

358-
# Save attachment
359-
saved_file_id = storage.save_attachment(
358+
# Save attachment to local disk
359+
result = storage.save_attachment(
360360
base64_data=base64_data,
361361
filename=output_filename,
362362
mime_type=output_mime_type,
363363
)
364364

365-
# Generate URL
366-
download_url = get_attachment_url(saved_file_id)
367-
368365
result_lines = [
369366
"File downloaded successfully!",
370367
f"File: {file_name}",
371368
f"File ID: {file_id}",
372369
f"Size: {size_kb:.1f} KB ({size_bytes} bytes)",
373370
f"MIME Type: {output_mime_type}",
374-
f"\n📎 Download URL: {download_url}",
375-
"\nThe file has been saved and is available at the URL above.",
376-
"The file will expire after 1 hour.",
377371
]
378372

373+
if get_transport_mode() == "stdio":
374+
result_lines.append(f"\n📎 Saved to: {result.path}")
375+
result_lines.append(
376+
"\nThe file has been saved to disk and can be accessed directly via the file path."
377+
)
378+
else:
379+
download_url = get_attachment_url(result.file_id)
380+
result_lines.append(f"\n📎 Download URL: {download_url}")
381+
result_lines.append(f"📂 Local path: {result.path}")
382+
result_lines.append("\nThe file will expire after 1 hour.")
383+
379384
if export_mime_type:
380385
result_lines.append(
381386
f"\nNote: Google native file exported to {output_mime_type} format."
382387
)
383388

384389
logger.info(
385-
f"[get_drive_file_download_url] Successfully saved {size_kb:.1f} KB file as {saved_file_id}"
390+
f"[get_drive_file_download_url] Successfully saved {size_kb:.1f} KB file to {result.path}"
386391
)
387392
return "\n".join(result_lines)
388393

gmail/gmail_tools.py

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -910,7 +910,8 @@ async def get_gmail_attachment_content(
910910

911911
# Save attachment to local disk and return file path
912912
try:
913-
from core.attachment_storage import get_attachment_storage
913+
from core.attachment_storage import get_attachment_storage, get_attachment_url
914+
from core.config import get_transport_mode
914915

915916
storage = get_attachment_storage()
916917

@@ -953,23 +954,35 @@ async def get_gmail_attachment_content(
953954
f"Could not fetch attachment metadata for {attachment_id}, using defaults"
954955
)
955956

956-
# Save attachment to local disk - returns absolute file path
957-
saved_path = storage.save_attachment(
957+
# Save attachment to local disk
958+
result = storage.save_attachment(
958959
base64_data=base64_data, filename=filename, mime_type=mime_type
959960
)
960961

961962
result_lines = [
962-
"Attachment downloaded and saved to local disk!",
963+
"Attachment downloaded successfully!",
963964
f"Message ID: {message_id}",
964965
f"Filename: {filename or 'unknown'}",
965966
f"Size: {size_kb:.1f} KB ({size_bytes} bytes)",
966-
f"\n📎 Saved to: {saved_path}",
967-
"\nThe file has been saved to disk and can be accessed directly via the file path.",
968-
"\nNote: Attachment IDs are ephemeral. Always use IDs from the most recent message fetch.",
969967
]
970968

969+
if get_transport_mode() == "stdio":
970+
result_lines.append(f"\n📎 Saved to: {result.path}")
971+
result_lines.append(
972+
"\nThe file has been saved to disk and can be accessed directly via the file path."
973+
)
974+
else:
975+
download_url = get_attachment_url(result.file_id)
976+
result_lines.append(f"\n📎 Download URL: {download_url}")
977+
result_lines.append(f"📂 Local path: {result.path}")
978+
result_lines.append("\nThe file will expire after 1 hour.")
979+
980+
result_lines.append(
981+
"\nNote: Attachment IDs are ephemeral. Always use IDs from the most recent message fetch."
982+
)
983+
971984
logger.info(
972-
f"[get_gmail_attachment_content] Successfully saved {size_kb:.1f} KB attachment to {saved_path}"
985+
f"[get_gmail_attachment_content] Successfully saved {size_kb:.1f} KB attachment to {result.path}"
973986
)
974987
return "\n".join(result_lines)
975988

0 commit comments

Comments
 (0)