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",
};
}
}