Skip to main content
Glama
gregkop
by gregkop

sketchfab-download

Download 3D models from Sketchfab by providing a model ID and preferred format (gltf, glb, usdz, or source), saving files locally for use in projects.

Instructions

Download a 3D model from Sketchfab

Input Schema

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

Implementation Reference

  • The async handler function implementing the core logic for the 'sketchfab-download' tool. It checks for API key, fetches model info, retrieves download URL for the specified format (fallback if unavailable), downloads the binary data, detects if ZIP and extracts it using adm-zip, saves files/ZIP to outputPath or system temp dir, and returns markdown text with file paths.
    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 input schema defining parameters for the sketchfab-download tool: modelId (string, required), format (enum ["gltf","glb","usdz","source"], optional, defaults to gltf), outputPath (string, 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)"), },
  • index.ts:421-423 (registration)
    MCP server.tool registration for the 'sketchfab-download' tool, specifying the tool name and description.
    server.tool( "sketchfab-download", "Download a 3D model from Sketchfab",
  • SketchfabApiClient class providing essential API interactions for the download tool, including getModelDownloadLink (157-188) to fetch temporary download URLs and downloadModel (190-205) to fetch the binary data.
    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)); } } }

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