zendesk_download_attachment
Downloads a Zendesk attachment using its URL and filename, returning the contents. Archives are unpacked, PDFs extracted, and images base64-encoded.
Instructions
Download a Zendesk attachment and return its contents. ticket_id is required to organize the local cache. Obtain attachment_url and filename from zendesk_list_attachments. Archives are unpacked, PDFs are text-extracted, images are base64-encoded.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| attachment_url | Yes | ||
| filename | Yes | ||
| ticket_id | Yes |
Output Schema
| Name | Required | Description | Default |
|---|---|---|---|
| result | Yes |
Implementation Reference
- Core handler function that downloads an attachment from Zendesk, caches it locally, and returns contents based on file type (text, archive, PDF, image, or binary).
def _download_attachment_data(attachment_url: str, filename: str, ticket_id: int) -> str: cfg = load_config() token = cfg.get("oauth_token", "") cache_dir = attachment_cache_dir(ticket_id) cache_dir.mkdir(parents=True, exist_ok=True) # Strip path components from filename to prevent directory traversal safe_filename = Path(filename).name dest = cache_dir / safe_filename try: response = httpx.get(attachment_url, headers={"Authorization": f"Bearer {token}"}, follow_redirects=True) response.raise_for_status() dest.write_bytes(response.content) except Exception as e: return json.dumps({"type": "error", "message": f"Download failed: {e}", "cached_path": str(dest)}) suffix = Path(filename).suffix.lower() if suffix in _TEXT_EXTENSIONS: try: text = dest.read_text(errors="replace") return json.dumps({"type": "text", "content": text, "cached_path": str(dest)}) except Exception as e: return json.dumps({"type": "error", "message": str(e), "cached_path": str(dest)}) if suffix == ".zip": return _handle_zip(dest) if suffix in {".tar", ".gz", ".tgz"} or filename.endswith(".tar.gz"): return _handle_tar(dest) if suffix == ".pdf": return _handle_pdf(dest) if suffix in _IMAGE_EXTENSIONS: return _handle_image(dest) return json.dumps({ "type": "binary", "message": "Binary file — content not returned. Use cached_path to access it.", "cached_path": str(dest), "size_bytes": dest.stat().st_size, }) - MCP tool registration decorator that exposes zendesk_download_attachment as a FastMCP tool, delegating to _download_attachment_data.
@mcp.tool() def zendesk_download_attachment(attachment_url: str, filename: str, ticket_id: int) -> str: """Download a Zendesk attachment and return its contents. ticket_id is required to organize the local cache. Obtain attachment_url and filename from zendesk_list_attachments. Archives are unpacked, PDFs are text-extracted, images are base64-encoded.""" return _download_attachment_data(attachment_url, filename, ticket_id) - src/zendesk_mcp/server.py:24-24 (registration)Registration call in server.py that attaches all attachment tools (including zendesk_download_attachment) to the MCP server instance.
register_attachment_tools(mcp) - src/zendesk_mcp/tools/attachments.py:158-167 (registration)The register_attachment_tools function that registers both zendesk_list_attachments and zendesk_download_attachment as MCP tools via the @mcp.tool() decorator.
def register_attachment_tools(mcp) -> None: @mcp.tool() def zendesk_list_attachments(ticket_id: int) -> str: """List all attachments across all comments for a Zendesk ticket. Returns filename, content type, size, and download URL for each. Use zendesk_download_attachment to fetch file contents.""" return _list_attachments_data(ticket_id) @mcp.tool() def zendesk_download_attachment(attachment_url: str, filename: str, ticket_id: int) -> str: """Download a Zendesk attachment and return its contents. ticket_id is required to organize the local cache. Obtain attachment_url and filename from zendesk_list_attachments. Archives are unpacked, PDFs are text-extracted, images are base64-encoded.""" return _download_attachment_data(attachment_url, filename, ticket_id) - src/zendesk_mcp/config.py:24-27 (helper)Helper function that resolves the base attachment cache directory from config and appends the ticket_id subdirectory.
def attachment_cache_dir(ticket_id: int, config_file: Path | None = None) -> Path: cfg = load_config(config_file) base = cfg.get("attachment_cache_dir", "~/.cache/zendesk-mcp/attachments") return Path(base).expanduser() / str(ticket_id)