Skip to main content
Glama

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

TableJSON Schema
NameRequiredDescriptionDefault
attachment_urlYes
filenameYes
ticket_idYes

Output Schema

TableJSON Schema
NameRequiredDescriptionDefault
resultYes

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)
  • Registration call in server.py that attaches all attachment tools (including zendesk_download_attachment) to the MCP server instance.
    register_attachment_tools(mcp)
  • 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)
  • 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)
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Discloses handling of archives (unpacked), PDFs (text-extracted), images (base64-encoded), and mentions local cache organization. No contradictions with missing annotations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Three front-loaded sentences, no filler, every sentence adds value.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Handles main behavior, parameter sources, and type handling. Output schema exists so return details not needed. Could mention returned format briefly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

With 0% schema coverage, description compensates by explaining purpose of ticket_id (organize cache) and source of other parameters (list_attachments).

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Clearly states 'Download a Zendesk attachment and return its contents', specifying verb and resource, and is distinct from sibling tools like zendesk_list_attachments.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Tells agent to obtain attachment_url and filename from zendesk_list_attachments, providing clear prerequisite. Missing explicit when-not or alternatives.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/michaelrice/zendesk-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server