Skip to main content
Glama
gregkop
by gregkop

sketchfab-download

Download 3D models from Sketchfab in formats like gltf, glb, usdz, or source. Specify the model ID and output path to save directly to your desired location.

Instructions

Download a 3D model from Sketchfab

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
formatNoPreferred format to download the model in (defaults to gltf if available)
modelIdYesThe unique ID of the Sketchfab model to download (must be downloadable)
outputPathNoLocal directory or file path to save the downloaded file (will use temp directory if not specified)

Implementation Reference

  • index.ts:421-583 (registration)
    Registration of the 'sketchfab-download' tool using server.tool(), which includes the description, input schema, and inline asynchronous handler function that performs the download.
    server.tool( "sketchfab-download", "Download a 3D model from Sketchfab", { modelId: z.string().describe("The unique ID of the Sketchfab model to download (must be downloadable)"), format: z.enum(["gltf", "glb", "usdz", "source"]).optional().describe("Preferred format to download the model in (defaults to gltf if available)"), outputPath: z.string().optional().describe("Local directory or file path to save the downloaded file (will use temp directory if not specified)"), }, async ({ modelId, format = "gltf", outputPath }) => { try { // Check if API key is available if (!apiKey) { return { content: [ { type: "text", text: "No Sketchfab API key provided. Please provide an API key using the --api-key parameter or set the SKETCHFAB_API_KEY environment variable.", }, ], }; } // Create API client const client = new SketchfabApiClient(apiKey); // Get model details const model = await client.getModel(modelId); // Check if model is downloadable if (!model.isDownloadable) { return { content: [ { type: "text", text: `Model "${model.name}" is not downloadable.`, }, ], }; } // Get download links const downloadLinks = await client.getModelDownloadLink(modelId); // Check if requested format is available const requestedFormat = format as keyof typeof downloadLinks; if (!downloadLinks[requestedFormat]) { // Find available formats const availableFormats = Object.keys(downloadLinks).filter( (key) => downloadLinks[key as keyof typeof downloadLinks] ); if (availableFormats.length === 0) { return { content: [ { type: "text", text: "No download formats available for this model.", }, ], }; } // Use the first available format const fallbackFormat = availableFormats[0] as keyof typeof downloadLinks; const fallbackLink = downloadLinks[fallbackFormat]!; // Download the model const modelData = await client.downloadModel(fallbackLink.url); // Determine filename and path const filename = `${model.name.replace(/[^a-zA-Z0-9]/g, "_")}_${modelId}.${fallbackFormat}`; const savePath = outputPath || path.join(os.tmpdir(), filename); const saveDir = path.dirname(savePath); // Check if the downloaded file is a ZIP archive if (isZipFile(modelData)) { // Create a directory for extraction const extractDir = path.join(saveDir, `${path.basename(savePath, path.extname(savePath))}_extracted`); // Extract the ZIP file const extractedFiles = extractZipFile(modelData, extractDir); // Save the original ZIP file as well fs.writeFileSync(savePath, modelData); return { content: [ { type: "text", text: `Downloaded model "${model.name}" in ${fallbackFormat} format (requested ${format} was not available).\nThe file was a ZIP archive and has been automatically extracted.\nOriginal ZIP saved to: ${savePath}\nExtracted files in: ${extractDir}\nExtracted ${extractedFiles.length} files.`, }, ], }; } else { // Write the file as is fs.writeFileSync(savePath, modelData); return { content: [ { type: "text", text: `Downloaded model "${model.name}" in ${fallbackFormat} format (requested ${format} was not available).\nSaved to: ${savePath}`, }, ], }; } } // Download the model in the requested format const downloadUrl = downloadLinks[requestedFormat]!.url; const modelData = await client.downloadModel(downloadUrl); // Determine filename and path const filename = `${model.name.replace(/[^a-zA-Z0-9]/g, "_")}_${modelId}.${format}`; const savePath = outputPath || path.join(os.tmpdir(), filename); const saveDir = path.dirname(savePath); // Check if the downloaded file is a ZIP archive if (isZipFile(modelData)) { // Create a directory for extraction const extractDir = path.join(saveDir, `${path.basename(savePath, path.extname(savePath))}_extracted`); // Extract the ZIP file const extractedFiles = extractZipFile(modelData, extractDir); // Save the original ZIP file as well fs.writeFileSync(savePath, modelData); return { content: [ { type: "text", text: `Downloaded model "${model.name}" in ${format} format.\nThe file was a ZIP archive and has been automatically extracted.\nOriginal ZIP saved to: ${savePath}\nExtracted files in: ${extractDir}\nExtracted ${extractedFiles.length} files.`, }, ], }; } else { // Write the file as is fs.writeFileSync(savePath, modelData); return { content: [ { type: "text", text: `Downloaded model "${model.name}" in ${format} format.\nSaved to: ${savePath}`, }, ], }; } } catch (error: unknown) { const errorMessage = error instanceof Error ? error.message : String(error); return { content: [ { type: "text", text: `Error downloading model: ${errorMessage}`, }, ], }; } } );
  • Zod schema defining input parameters for the tool: modelId (required), format (optional enum), outputPath (optional).
    { modelId: z.string().describe("The unique ID of the Sketchfab model to download (must be downloadable)"), format: z.enum(["gltf", "glb", "usdz", "source"]).optional().describe("Preferred format to download the model in (defaults to gltf if available)"), outputPath: z.string().optional().describe("Local directory or file path to save the downloaded file (will use temp directory if not specified)"), },
  • Inline async handler function that implements the core logic: API interactions via SketchfabApiClient, format selection/fallback, download, ZIP detection and extraction, file saving, and MCP content response.
    async ({ modelId, format = "gltf", outputPath }) => { try { // Check if API key is available if (!apiKey) { return { content: [ { type: "text", text: "No Sketchfab API key provided. Please provide an API key using the --api-key parameter or set the SKETCHFAB_API_KEY environment variable.", }, ], }; } // Create API client const client = new SketchfabApiClient(apiKey); // Get model details const model = await client.getModel(modelId); // Check if model is downloadable if (!model.isDownloadable) { return { content: [ { type: "text", text: `Model "${model.name}" is not downloadable.`, }, ], }; } // Get download links const downloadLinks = await client.getModelDownloadLink(modelId); // Check if requested format is available const requestedFormat = format as keyof typeof downloadLinks; if (!downloadLinks[requestedFormat]) { // Find available formats const availableFormats = Object.keys(downloadLinks).filter( (key) => downloadLinks[key as keyof typeof downloadLinks] ); if (availableFormats.length === 0) { return { content: [ { type: "text", text: "No download formats available for this model.", }, ], }; } // Use the first available format const fallbackFormat = availableFormats[0] as keyof typeof downloadLinks; const fallbackLink = downloadLinks[fallbackFormat]!; // Download the model const modelData = await client.downloadModel(fallbackLink.url); // Determine filename and path const filename = `${model.name.replace(/[^a-zA-Z0-9]/g, "_")}_${modelId}.${fallbackFormat}`; const savePath = outputPath || path.join(os.tmpdir(), filename); const saveDir = path.dirname(savePath); // Check if the downloaded file is a ZIP archive if (isZipFile(modelData)) { // Create a directory for extraction const extractDir = path.join(saveDir, `${path.basename(savePath, path.extname(savePath))}_extracted`); // Extract the ZIP file const extractedFiles = extractZipFile(modelData, extractDir); // Save the original ZIP file as well fs.writeFileSync(savePath, modelData); return { content: [ { type: "text", text: `Downloaded model "${model.name}" in ${fallbackFormat} format (requested ${format} was not available).\nThe file was a ZIP archive and has been automatically extracted.\nOriginal ZIP saved to: ${savePath}\nExtracted files in: ${extractDir}\nExtracted ${extractedFiles.length} files.`, }, ], }; } else { // Write the file as is fs.writeFileSync(savePath, modelData); return { content: [ { type: "text", text: `Downloaded model "${model.name}" in ${fallbackFormat} format (requested ${format} was not available).\nSaved to: ${savePath}`, }, ], }; } } // Download the model in the requested format const downloadUrl = downloadLinks[requestedFormat]!.url; const modelData = await client.downloadModel(downloadUrl); // Determine filename and path const filename = `${model.name.replace(/[^a-zA-Z0-9]/g, "_")}_${modelId}.${format}`; const savePath = outputPath || path.join(os.tmpdir(), filename); const saveDir = path.dirname(savePath); // Check if the downloaded file is a ZIP archive if (isZipFile(modelData)) { // Create a directory for extraction const extractDir = path.join(saveDir, `${path.basename(savePath, path.extname(savePath))}_extracted`); // Extract the ZIP file const extractedFiles = extractZipFile(modelData, extractDir); // Save the original ZIP file as well fs.writeFileSync(savePath, modelData); return { content: [ { type: "text", text: `Downloaded model "${model.name}" in ${format} format.\nThe file was a ZIP archive and has been automatically extracted.\nOriginal ZIP saved to: ${savePath}\nExtracted files in: ${extractDir}\nExtracted ${extractedFiles.length} files.`, }, ], }; } else { // Write the file as is fs.writeFileSync(savePath, modelData); return { content: [ { type: "text", text: `Downloaded model "${model.name}" in ${format} format.\nSaved to: ${savePath}`, }, ], }; } } catch (error: unknown) { const errorMessage = error instanceof Error ? error.message : String(error); return { content: [ { type: "text", text: `Error downloading model: ${errorMessage}`, }, ], }; } }
  • Supporting SketchfabApiClient class with methods getModel, getModelDownloadLink, and downloadModel used directly in the handler for model info, download links, and file retrieval.
    class SketchfabApiClient { private apiKey: string; private static API_BASE = "https://api.sketchfab.com/v3"; constructor(apiKey: string) { this.apiKey = apiKey; } private getAuthHeader() { return { Authorization: `Token ${this.apiKey}`, }; } async searchModels(options: { q?: string; tags?: string[]; categories?: string[]; downloadable?: boolean; count?: number; }): Promise<{ results: SketchfabModel[]; next?: string; previous?: string; }> { try { const { q, tags, categories, downloadable, count = 24 } = options; // Build query parameters const params: Record<string, any> = { type: "models" }; if (q) params.q = q; if (tags?.length) params.tags = tags; if (categories?.length) params.categories = categories; if (downloadable !== undefined) params.downloadable = downloadable; if (count) params.count = Math.min(count, 24); // API limit is 24 // Make API request const response = await axios.get(`${SketchfabApiClient.API_BASE}/search`, { params, headers: this.getAuthHeader(), }); return { results: response.data.results || [], next: response.data.next, previous: response.data.previous, }; } catch (error: unknown) { if (axios.isAxiosError(error) && error.response) { const status = error.response.status; if (status === 401) { throw new Error("Invalid Sketchfab API key"); } else if (status === 429) { throw new Error("Sketchfab API rate limit exceeded. Try again later."); } throw new Error(`Sketchfab API error (${status}): ${error.message}`); } throw error instanceof Error ? error : new Error(String(error)); } } async getModel(uid: string): Promise<SketchfabModel> { try { const response = await axios.get( `${SketchfabApiClient.API_BASE}/models/${uid}`, { headers: this.getAuthHeader(), } ); return response.data; } catch (error: unknown) { if (axios.isAxiosError(error) && error.response) { const status = error.response.status; if (status === 404) { throw new Error(`Model with UID ${uid} not found`); } else if (status === 401) { throw new Error("Invalid Sketchfab API key"); } throw new Error(`Sketchfab API error (${status}): ${error.message}`); } throw error instanceof Error ? error : new Error(String(error)); } } async getModelDownloadLink(uid: string): Promise<{ gltf?: { url: string; expires: number }; usdz?: { url: string; expires: number }; glb?: { url: string; expires: number }; source?: { url: string; expires: number }; }> { try { const response = await axios.get( `${SketchfabApiClient.API_BASE}/models/${uid}/download`, { headers: this.getAuthHeader(), } ); return response.data; } catch (error: unknown) { if (axios.isAxiosError(error) && error.response) { const status = error.response.status; if (status === 404) { throw new Error(`Model with UID ${uid} not found`); } else if (status === 401) { throw new Error("Invalid Sketchfab API key"); } else if (status === 400) { throw new Error("Model is not downloadable"); } else if (status === 403) { throw new Error("You do not have permission to download this model"); } throw new Error(`Sketchfab API error (${status}): ${error.message}`); } throw error instanceof Error ? error : new Error(String(error)); } } async downloadModel(downloadUrl: string): Promise<Buffer> { try { const response = await axios.get(downloadUrl, { responseType: "arraybuffer", timeout: 300000, // 5 minutes }); return Buffer.from(response.data); } catch (error: unknown) { if (axios.isAxiosError(error) && error.response) { const status = error.response.status; throw new Error(`Download error (${status}): ${error.message}`); } throw error instanceof Error ? error : new Error(String(error)); } } }
  • Utility functions isZipFile and extractZipFile for detecting and automatically extracting ZIP archives (common for Sketchfab downloads).
    function isZipFile(buffer: Buffer): boolean { // Check for ZIP file signature (PK..) return buffer.length >= 4 && buffer[0] === 0x50 && buffer[1] === 0x4B && (buffer[2] === 0x03 || buffer[2] === 0x05 || buffer[2] === 0x07) && (buffer[3] === 0x04 || buffer[3] === 0x06 || buffer[3] === 0x08); } // Utility function to extract a ZIP file function extractZipFile(zipBuffer: Buffer, outputDir: string): string[] { try { const zip = new AdmZip(zipBuffer); const zipEntries = zip.getEntries(); // Create output directory if it doesn't exist if (!fs.existsSync(outputDir)) { fs.mkdirSync(outputDir, { recursive: true }); } // Extract all files zip.extractAllTo(outputDir, true); // Return list of extracted files return zipEntries.map(entry => path.join(outputDir, entry.entryName)); } catch (error) { throw new Error(`Failed to extract ZIP file: ${error instanceof Error ? error.message : String(error)}`); } }

Other Tools

Related 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/gregkop/sketchfab-mcp-server'

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