Add SSH MCP server and Gmail attachment download

Features:
- SSH MCP server with two tools:
  * ssh_execute: Run commands on remote hosts via SSH
  * ssh_file_upload: Upload files via SFTP
- Support for both password and SSH key authentication
- Auto-accept SSH host keys (AutoAddPolicy) for homelab use
- Gmail attachment download functionality
- Added download_attachment tool for Gmail API

Technical changes:
- Created mcp_servers/mcp_ssh.py with MCP-compliant text output
- Updated llm_interface.py to load SSH MCP server
- Added paramiko>=3.4.0 to requirements.txt
- Updated .env.example with SSH configuration template
- Enhanced gmail_client.py with download_attachment() method
- Added download_attachment tool handler in tools.py

SSH credentials configured via environment variables:
- PROXMOX_SSH_HOST, PROXMOX_SSH_USER, PROXMOX_SSH_PORT
- PROXMOX_SSH_PASSWORD (or) PROXMOX_SSH_KEY_FILE

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-02-24 12:32:05 -07:00
parent a9efdc0a01
commit 58de3e55dc
6 changed files with 489 additions and 10 deletions

View File

@@ -190,6 +190,33 @@ TOOL_DEFINITIONS = [
"required": ["message_id"],
},
},
{
"name": "download_attachment",
"description": "Download an email attachment from Gmail. Use get_email first to get attachment IDs.",
"input_schema": {
"type": "object",
"properties": {
"message_id": {
"type": "string",
"description": "Gmail message ID containing the attachment",
},
"attachment_id": {
"type": "string",
"description": "Attachment ID from the email (obtained from get_email)",
},
"filename": {
"type": "string",
"description": "Original filename of the attachment",
},
"output_dir": {
"type": "string",
"description": "Directory to save the file (default: 'downloads')",
"default": "downloads",
},
},
"required": ["message_id", "attachment_id", "filename"],
},
},
# Calendar tools
{
"name": "read_calendar",
@@ -411,6 +438,13 @@ def execute_tool(tool_name: str, tool_input: Dict[str, Any], healing_system: Any
message_id=tool_input["message_id"],
format_type=tool_input.get("format", "text"),
)
elif tool_name == "download_attachment":
result_str = _download_attachment(
message_id=tool_input["message_id"],
attachment_id=tool_input["attachment_id"],
filename=tool_input["filename"],
output_dir=tool_input.get("output_dir", "downloads"),
)
elif tool_name == "read_calendar":
result_str = _read_calendar(
days_ahead=tool_input.get("days_ahead", 7),
@@ -844,7 +878,10 @@ def _get_email(message_id: str, format_type: str = "text") -> str:
output.append(f"\n{email_data.get('body', '')}")
if email_data.get("attachments"):
output.append(f"\nAttachments: {', '.join(email_data['attachments'])}")
output.append("\nAttachments:")
for att in email_data["attachments"]:
att_info = f" - {att['filename']} ({att.get('size', 0)} bytes, ID: {att.get('attachment_id', 'N/A')})"
output.append(att_info)
full_output = "\n".join(output)
if len(full_output) > _MAX_TOOL_OUTPUT:
@@ -855,6 +892,36 @@ def _get_email(message_id: str, format_type: str = "text") -> str:
return f"Error getting email: {result.get('error', 'Unknown error')}"
def _download_attachment(
message_id: str,
attachment_id: str,
filename: str,
output_dir: str = "downloads",
) -> str:
"""Download an email attachment."""
gmail_client, _, _ = _initialize_google_clients()
if not gmail_client:
return "Error: Google not authorized. Run: python bot_runner.py --setup-google"
result = gmail_client.download_attachment(
message_id=message_id,
attachment_id=attachment_id,
filename=filename,
output_dir=output_dir,
)
if result["success"]:
return (
f"Downloaded attachment successfully:\n"
f" File: {result['filename']}\n"
f" Path: {result['file_path']}\n"
f" Size: {result['size']:,} bytes"
)
else:
return f"Error downloading attachment: {result.get('error', 'Unknown error')}"
def _read_calendar(
days_ahead: int = 7,
calendar_id: str = "primary",