Skip to main content
Glama
create-asset.ts•8.37 kB
import { z } from "zod"; import * as fs from "fs"; import { api } from "../api"; import { MCP_CONFIG } from "../config"; import { createAssetSchema, FileType, FileSource, Metadata_AttributeType } from "../utils"; import { UploadPrimitives, detectFileTypeFromMimeType, NodeFileData } from "../models/upload-primitives"; // Input schema - adapted for MCP (supports both filePath and base64) export const createAssetInputSchema = createAssetSchema; export type CreateAssetInput = z.infer<typeof createAssetInputSchema>; export interface CreateAssetResult { success: boolean; data?: { asset: { fileId: string; contractId: string; title: string; description?: string; location?: string; editions?: number; shareWithCommunity?: boolean; }; message: string; progress?: { stage: string; percentage?: number; }; }; error?: string; } // Helper function to detect MIME type from file extension function detectMimeType(fileName: string): string { const extension = fileName.split(".").pop()?.toLowerCase(); if (!extension) return "application/octet-stream"; const mimeTypes: Record<string, string> = { jpg: "image/jpeg", jpeg: "image/jpeg", png: "image/png", gif: "image/gif", webp: "image/webp", svg: "image/svg+xml", mp4: "video/mp4", webm: "video/webm", mov: "video/quicktime", avi: "video/x-msvideo", mp3: "audio/mpeg", wav: "audio/wav", ogg: "audio/ogg", pdf: "application/pdf", txt: "text/plain", }; return mimeTypes[extension] || "application/octet-stream"; } // Helper function to create Node.js File-like object from file path function createNodeFileFromPath(filePath: string): NodeFileData { if (!fs.existsSync(filePath)) { throw new Error(`File does not exist: ${filePath}`); } const buffer = fs.readFileSync(filePath); const stats = fs.statSync(filePath); const fileName = filePath.split("/").pop() || "unknown"; const mimeType = detectMimeType(fileName); return { buffer, name: fileName, size: buffer.length, type: mimeType, lastModified: stats.mtime.getTime(), slice(start: number, end: number) { const sliceBuffer = this.buffer.subarray(start, end); return { arrayBuffer(): Promise<ArrayBuffer> { const arrayBuffer = sliceBuffer.buffer.slice( sliceBuffer.byteOffset, sliceBuffer.byteOffset + sliceBuffer.byteLength, ); return Promise.resolve(arrayBuffer as ArrayBuffer); }, }; }, }; } // Helper function to create Node.js File-like object from base64 data function createNodeFileFromData(fileData: string, fileName: string, mimeType: string): NodeFileData { try { // Remove data URL prefix if present (e.g., "data:image/png;base64,") const base64Data = fileData.replace(/^data:[^;]+;base64,/, ""); const buffer = Buffer.from(base64Data, "base64"); if (buffer.length === 0) { throw new Error("Invalid base64 data: resulted in empty buffer"); } return { buffer, name: fileName, size: buffer.length, type: mimeType, lastModified: Date.now(), slice(start: number, end: number) { const sliceBuffer = this.buffer.subarray(start, end); return { arrayBuffer(): Promise<ArrayBuffer> { const arrayBuffer = sliceBuffer.buffer.slice( sliceBuffer.byteOffset, sliceBuffer.byteOffset + sliceBuffer.byteLength, ); return Promise.resolve(arrayBuffer as ArrayBuffer); }, }; }, }; } catch (error) { throw new Error( `Failed to create file from base64 data: ${error instanceof Error ? error.message : "Unknown error"}`, ); } } export async function createAsset(params: CreateAssetInput): Promise<CreateAssetResult> { try { // Validate input using our schema const validatedParams = createAssetInputSchema.parse(params); // Determine how to create the file - either from disk or base64 data let file: NodeFileData; if (validatedParams.fileData && validatedParams.fileName && validatedParams.mimeType) { // Option 1: Create file from base64 data (for Claude Desktop) file = createNodeFileFromData(validatedParams.fileData, validatedParams.fileName, validatedParams.mimeType); } else if (validatedParams.filePath) { // Option 2: Create file from disk path if (!fs.existsSync(validatedParams.filePath)) { return { success: false, error: `File not found: ${validatedParams.filePath}`, }; } file = createNodeFileFromPath(validatedParams.filePath); } else { return { success: false, error: "Either filePath or (fileData + fileName + mimeType) must be provided", }; } const fileType = detectFileTypeFromMimeType(file.type); if (fileType === FileType.Unknown) { return { success: false, error: `Unsupported file type: ${file.type}`, }; } // Create upload primitives instance const uploader = new UploadPrimitives({ disableThumbnail: false, isPrivate: false, }); // Stage 1: Prepare upload const fileMetadata = JSON.stringify({ lastModified: file.lastModified, size: file.size, type: file.type, name: file.name, }); await uploader.prepareUpload({ file, metadata: fileMetadata, fileType, source: FileSource.Upload, deviceId: MCP_CONFIG.DEVICE_ID, }); if (!uploader.fileId) { throw new Error("Failed to prepare file upload"); } // Stage 2: Upload all chunks await uploader.uploadAllChunks(); // Stage 3: Finalize upload await uploader.finalizeUpload(); // Stage 4: Start minting const mintResponse = await api.assets.startMinting({ fileId: uploader.fileId, editions: validatedParams.editions, contractId: validatedParams.contractId, shareWithCommunity: validatedParams.shareWithCommunity ?? false, isEncrypted: false, // Not supporting encryption in MCP for now metadata: { attributes: [ { key: "title", value: validatedParams.title, type: Metadata_AttributeType.STRING, }, ...(validatedParams.description ? [ { key: "description", value: validatedParams.description, type: Metadata_AttributeType.STRING, }, ] : []), ...(validatedParams.location ? [ { key: "location", value: validatedParams.location, type: Metadata_AttributeType.STRING, }, ] : []), { key: "appName", value: "Uranium MCP", type: Metadata_AttributeType.STRING, }, { key: "appVersion", value: "1.0.0", type: Metadata_AttributeType.STRING, }, ], }, }); if (mintResponse.status !== "ok") { return { success: false, error: mintResponse.errorCode || "Failed to start minting", }; } return { success: true, data: { asset: { fileId: uploader.fileId, contractId: validatedParams.contractId, title: validatedParams.title, description: validatedParams.description, location: validatedParams.location, editions: validatedParams.editions, shareWithCommunity: validatedParams.shareWithCommunity, }, message: `Asset "${validatedParams.title}" created successfully and is being minted!`, progress: { stage: "Minting NFT...", percentage: 100, }, }, }; } catch (error) { if (error instanceof z.ZodError) { const errorMessages = error.issues.map((err) => err.message).join(", "); return { success: false, error: `Validation error: ${errorMessages}`, }; } return { success: false, error: error instanceof Error ? error.message : "Unknown error occurred", }; } }

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/xkelxmc/uranium-mcp'

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