Skip to main content
Glama
cristip73

MCP Server for Asana

by cristip73

asana_download_attachment

Download Asana attachments to your local directory for offline access or backup.

Instructions

Download an attachment locally

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
attachment_gidYesThe attachment GID to download
output_dirNoDirectory to save the file (defaults to ~/downloads)

Implementation Reference

  • Tool handler switch case that destructures input parameters and delegates to AsanaClientWrapper.downloadAttachment method, returning JSON response.
    case "asana_download_attachment": {
      const { attachment_gid, output_dir } = args;
      const response = await asanaClient.downloadAttachment(attachment_gid, output_dir);
      return {
        content: [{ type: "text", text: JSON.stringify(response) }],
      };
    }
  • Core implementation that fetches attachment metadata, downloads the file from Asana's download_url using fetch, determines filename and MIME type, and saves to local directory (default ~/downloads).
    async downloadAttachment(attachmentId: string, outputDir?: string) {
      const fs = await import('fs');
      const path = await import('path');
      const os = await import('os');
      const { pipeline } = await import('stream/promises');
    
      outputDir = outputDir || path.join(os.homedir(), 'downloads');
    
      const attachment = await this.getAttachment(attachmentId);
      const downloadUrl = attachment.download_url || attachment.downloadUrl;
      if (!downloadUrl) {
        throw new Error('Attachment does not have a download_url');
      }
    
      await fs.promises.mkdir(outputDir, { recursive: true });
    
      const res = await fetch(downloadUrl);
      if (!res.ok || !res.body) {
        throw new Error(`Failed to download attachment: ${res.status}`);
      }
    
      let filename: string = attachment.name || attachment.gid;
      const contentType = res.headers.get('content-type') || attachment.mime_type;
      if (!path.extname(filename) && contentType) {
        filename += this.extensionForMime(contentType);
      }
    
      const filePath = path.join(outputDir, filename);
      const fileStream = fs.createWriteStream(filePath);
      await pipeline(res.body, fileStream);
    
      return { attachment_id: attachmentId, file_path: filePath, mime_type: contentType };
    }
  • Defines the Tool object with name, description, and inputSchema for validation in MCP.
    export const downloadAttachmentTool: Tool = {
      name: "asana_download_attachment",
      description: "Download an attachment locally",
      inputSchema: {
        type: "object",
        properties: {
          attachment_gid: {
            type: "string",
            description: "The attachment GID to download"
          },
          output_dir: {
            type: "string",
            description: "Directory to save the file (defaults to ~/downloads)"
          }
        },
        required: ["attachment_gid"]
      }
    };
  • Includes downloadAttachmentTool in the exported tools array for MCP tool registration.
      getAttachmentsForObjectTool,
      uploadAttachmentForObjectTool,
      downloadAttachmentTool
    ];
  • Validation case that checks parameters against the tool's schema.
        case 'asana_download_attachment':
          result = validateGid(params.attachment_gid, 'attachment_gid');
          if (!result.valid) errors.push(...result.errors);
          break;
      }
    
      return {
        valid: errors.length === 0,
        errors
      };
    }

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/cristip73/mcp-server-asana'

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