@meshy-ai/meshy-mcp-server 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/.env.example +14 -0
  2. package/LICENSE +21 -0
  3. package/README.md +108 -0
  4. package/dist/constants.d.ts +123 -0
  5. package/dist/constants.js +169 -0
  6. package/dist/index.d.ts +8 -0
  7. package/dist/index.js +130 -0
  8. package/dist/instructions.d.ts +6 -0
  9. package/dist/instructions.js +90 -0
  10. package/dist/schemas/balance.d.ts +11 -0
  11. package/dist/schemas/balance.js +8 -0
  12. package/dist/schemas/common.d.ts +38 -0
  13. package/dist/schemas/common.js +52 -0
  14. package/dist/schemas/generation.d.ts +219 -0
  15. package/dist/schemas/generation.js +217 -0
  16. package/dist/schemas/image.d.ts +55 -0
  17. package/dist/schemas/image.js +46 -0
  18. package/dist/schemas/output.d.ts +75 -0
  19. package/dist/schemas/output.js +41 -0
  20. package/dist/schemas/postprocessing.d.ts +135 -0
  21. package/dist/schemas/postprocessing.js +123 -0
  22. package/dist/schemas/printing.d.ts +63 -0
  23. package/dist/schemas/printing.js +54 -0
  24. package/dist/schemas/tasks.d.ts +123 -0
  25. package/dist/schemas/tasks.js +85 -0
  26. package/dist/services/error-handler.d.ts +32 -0
  27. package/dist/services/error-handler.js +141 -0
  28. package/dist/services/file-utils.d.ts +15 -0
  29. package/dist/services/file-utils.js +55 -0
  30. package/dist/services/meshy-client.d.ts +54 -0
  31. package/dist/services/meshy-client.js +172 -0
  32. package/dist/services/output-manager.d.ts +52 -0
  33. package/dist/services/output-manager.js +284 -0
  34. package/dist/tools/balance.d.ts +9 -0
  35. package/dist/tools/balance.js +61 -0
  36. package/dist/tools/generation.d.ts +9 -0
  37. package/dist/tools/generation.js +419 -0
  38. package/dist/tools/image.d.ts +9 -0
  39. package/dist/tools/image.js +154 -0
  40. package/dist/tools/postprocessing.d.ts +9 -0
  41. package/dist/tools/postprocessing.js +405 -0
  42. package/dist/tools/printing.d.ts +9 -0
  43. package/dist/tools/printing.js +338 -0
  44. package/dist/tools/tasks.d.ts +9 -0
  45. package/dist/tools/tasks.js +1074 -0
  46. package/dist/tools/workspace.d.ts +9 -0
  47. package/dist/tools/workspace.js +161 -0
  48. package/dist/types.d.ts +261 -0
  49. package/dist/types.js +4 -0
  50. package/dist/utils/endpoints.d.ts +16 -0
  51. package/dist/utils/endpoints.js +38 -0
  52. package/dist/utils/request-builder.d.ts +15 -0
  53. package/dist/utils/request-builder.js +24 -0
  54. package/dist/utils/response-formatter.d.ts +27 -0
  55. package/dist/utils/response-formatter.js +37 -0
  56. package/dist/utils/slicer-detector.d.ts +29 -0
  57. package/dist/utils/slicer-detector.js +237 -0
  58. package/package.json +64 -0
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Zod schemas for task management tools
3
+ */
4
+ import { z } from "zod";
5
+ import { TaskStatus, TaskPhase, TaskType, POLL_MAX_TIMEOUT } from "../constants.js";
6
+ import { ResponseFormatSchema, PaginationSchema, TaskIdSchema, TaskTypeSchema } from "./common.js";
7
+ /**
8
+ * Get task status input schema (also supports wait mode)
9
+ */
10
+ export const GetTaskStatusInputSchema = z.object({
11
+ task_id: TaskIdSchema,
12
+ task_type: TaskTypeSchema,
13
+ wait: z.boolean()
14
+ .default(true)
15
+ .describe("If true (default), auto-poll until task completes. If false, return current status immediately."),
16
+ timeout_seconds: z.number()
17
+ .int()
18
+ .min(10, "Timeout must be at least 10 seconds")
19
+ .max(POLL_MAX_TIMEOUT / 1000, `Timeout cannot exceed ${POLL_MAX_TIMEOUT / 1000} seconds`)
20
+ .default(300)
21
+ .describe("Maximum wait time in seconds when wait=true (default: 300, max: 300)"),
22
+ response_format: ResponseFormatSchema
23
+ }).strict();
24
+ /**
25
+ * List tasks input schema
26
+ */
27
+ export const ListTasksInputSchema = z.object({
28
+ task_type: z.nativeEnum(TaskType)
29
+ .optional()
30
+ .describe("Filter by task type. If omitted, queries ALL task types (text-to-3d, image-to-3d, multi-image-to-3d, remesh, retexture, text-to-image, image-to-image) and merges results. Note: rigging and animation do not have list endpoints."),
31
+ sort_by: z.enum(["+created_at", "-created_at"])
32
+ .default("-created_at")
33
+ .describe("Sort order by creation time. '-created_at' = newest first (default), '+created_at' = oldest first"),
34
+ status: z.nativeEnum(TaskStatus)
35
+ .optional()
36
+ .describe("Filter by task status"),
37
+ phase: z.nativeEnum(TaskPhase)
38
+ .optional()
39
+ .describe("Filter by task phase"),
40
+ response_format: ResponseFormatSchema
41
+ }).merge(PaginationSchema).strict();
42
+ /**
43
+ * Cancel task input schema
44
+ */
45
+ export const CancelTaskInputSchema = z.object({
46
+ task_id: TaskIdSchema,
47
+ task_type: TaskTypeSchema
48
+ }).strict();
49
+ /**
50
+ * Download model input schema
51
+ */
52
+ export const DownloadModelInputSchema = z.object({
53
+ task_id: TaskIdSchema,
54
+ task_type: TaskTypeSchema,
55
+ format: z.enum(["glb", "fbx", "usdz", "stl", "obj", "3mf"])
56
+ .default("glb")
57
+ .describe("Model format to download. IMPORTANT: Ask the user which format they need before downloading. Recommendations: GLB (general viewing), OBJ (white model printing), 3MF (multicolor printing), FBX (game engines/animation), USDZ (AR/Apple). Do NOT download all formats."),
58
+ include_textures: z.boolean()
59
+ .default(true)
60
+ .describe("Include texture files in response"),
61
+ save_to: z.string()
62
+ .optional()
63
+ .describe("Override auto-save path with a custom ABSOLUTE path. If omitted, auto-saves to meshy_output/{timestamp}_{prompt}_{id}/. Example: /Users/me/models/chair.glb"),
64
+ parent_task_id: z.string()
65
+ .optional()
66
+ .describe("Parent task ID for chaining (e.g., preview_task_id for refine, input_task_id for rig). Places output in the same project folder as the parent."),
67
+ print_ready: z.boolean()
68
+ .optional()
69
+ .describe("If true and format is OBJ, auto-fix coordinates for 3D printing: rotates Y-up to Z-up, scales to target height, centers on XY, aligns bottom to Z=0. Default false."),
70
+ print_height_mm: z.number()
71
+ .optional()
72
+ .describe("Target height in mm when print_ready is true. Default 75. Adjust per user request (e.g. 'print at 15cm' → 150).")
73
+ }).strict();
74
+ /**
75
+ * List models input schema
76
+ */
77
+ export const ListModelsInputSchema = z.object({
78
+ workspace_id: z.string()
79
+ .optional()
80
+ .describe("Workspace ID (uses default if omitted)"),
81
+ filter: z.enum(["all", "published", "private"])
82
+ .default("all")
83
+ .describe("Filter models by visibility"),
84
+ response_format: ResponseFormatSchema
85
+ }).merge(PaginationSchema).strict();
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Centralized error handling for Meshy API
3
+ */
4
+ export interface MeshyError {
5
+ code: string;
6
+ message: string;
7
+ status?: number;
8
+ }
9
+ /**
10
+ * Optional context for richer error messages
11
+ */
12
+ export interface ErrorContext {
13
+ tool?: string;
14
+ taskId?: string;
15
+ }
16
+ /**
17
+ * Handle Meshy API errors and convert to user-friendly messages.
18
+ * When context is provided, appends tool-specific recovery suggestions.
19
+ */
20
+ export declare function handleMeshyError(error: unknown, context?: ErrorContext): string;
21
+ /**
22
+ * Check if error is a rate limit error
23
+ */
24
+ export declare function isRateLimitError(error: unknown): boolean;
25
+ /**
26
+ * Check if error is a network error that should be retried
27
+ */
28
+ export declare function isRetryableError(error: unknown): boolean;
29
+ /**
30
+ * Extract error message from various error formats
31
+ */
32
+ export declare function extractErrorMessage(error: unknown): string;
@@ -0,0 +1,141 @@
1
+ /**
2
+ * Centralized error handling for Meshy API
3
+ */
4
+ import { AxiosError } from "axios";
5
+ import { ErrorCode } from "../constants.js";
6
+ /**
7
+ * Handle Meshy API errors and convert to user-friendly messages.
8
+ * When context is provided, appends tool-specific recovery suggestions.
9
+ */
10
+ export function handleMeshyError(error, context) {
11
+ // Handle Axios errors
12
+ if (error instanceof AxiosError) {
13
+ if (error.response) {
14
+ const status = error.response.status;
15
+ const errorData = error.response.data;
16
+ const errorCode = errorData?.error_code || errorData?.code;
17
+ // Map specific error codes
18
+ switch (errorCode) {
19
+ case ErrorCode.INSUFFICIENT_CREDITS:
20
+ return "Error: Insufficient credits. Use `meshy_check_balance` to check your balance. Upgrade at https://meshy.ai/pricing";
21
+ case ErrorCode.TOO_MANY_PENDING_TASKS:
22
+ return "Error: Too many pending tasks. Please wait for current tasks to complete or cancel some tasks.";
23
+ case ErrorCode.INVALID_MODEL_NOT_SUPPORTED:
24
+ return "Error: Model format not supported. Supported formats: GLB, FBX, USDZ, 3MF";
25
+ case ErrorCode.INVALID_MODEL_INVALID_FORMAT:
26
+ return "Error: Model file is corrupted or invalid. Please try generating again.";
27
+ case ErrorCode.LIMIT_EXCEEDED:
28
+ return "Error: Rate limit exceeded. Please wait a moment before making more requests.";
29
+ case ErrorCode.FORBIDDEN:
30
+ return "Error: Permission denied. Please check your API key has the required permissions.";
31
+ case ErrorCode.NOT_FOUND:
32
+ return "Error: Resource not found. Please verify the task ID is correct.";
33
+ case ErrorCode.INTERNAL_ERROR:
34
+ return "Error: Meshy service error. Please try again later.";
35
+ }
36
+ // Handle HTTP status codes
37
+ switch (status) {
38
+ case 400:
39
+ return `Error: Invalid request. ${errorData?.message || "Please check your parameters."}`;
40
+ case 401:
41
+ return "Error: Authentication failed. Please check your MESHY_API_KEY is valid.";
42
+ case 403:
43
+ return "Error: Permission denied. Your API key may not have access to this resource.";
44
+ case 404:
45
+ return "Error: Resource not found. Please check the ID is correct.";
46
+ case 429:
47
+ return "Error: Rate limit exceeded. Please wait before making more requests.";
48
+ case 500:
49
+ case 502:
50
+ case 503:
51
+ return "Error: Meshy service is temporarily unavailable. Please try again later.";
52
+ default:
53
+ return `Error: API request failed with status ${status}. ${errorData?.message || ""}`;
54
+ }
55
+ }
56
+ // Handle network errors
57
+ if (error.code === "ECONNABORTED") {
58
+ return "Error: Request timed out. Please check your network connection and try again.";
59
+ }
60
+ if (error.code === "ENOTFOUND") {
61
+ return "Error: Cannot reach Meshy API. Please check your internet connection.";
62
+ }
63
+ if (error.code === "ECONNREFUSED") {
64
+ return "Error: Meshy API is unavailable. Please try again later.";
65
+ }
66
+ return `Error: Network error occurred. ${error.message}`;
67
+ }
68
+ // Handle generic errors
69
+ let baseMessage;
70
+ if (error instanceof Error) {
71
+ baseMessage = `Error: ${error.message}`;
72
+ }
73
+ else {
74
+ baseMessage = `Error: An unexpected error occurred. ${String(error)}`;
75
+ }
76
+ // Append context-aware suggestions if context is provided
77
+ return appendContextSuggestions(baseMessage, error, context);
78
+ }
79
+ /**
80
+ * Append context-specific recovery suggestions to error messages
81
+ */
82
+ function appendContextSuggestions(message, error, context) {
83
+ if (!context?.tool)
84
+ return message;
85
+ const errorText = message.toLowerCase();
86
+ const tool = context.tool;
87
+ // Rig + face limit
88
+ if (tool === "meshy_rig" && (errorText.includes("face") || errorText.includes("300,000") || errorText.includes("300000") || errorText.includes("limit"))) {
89
+ return message + `\n\n**Fix**: The model exceeds the 300K face limit for rigging. Call \`meshy_remesh\` with target_polycount 100000 first, then rig the remeshed output.`;
90
+ }
91
+ // Image-to-3D + image errors
92
+ if ((tool === "meshy_image_to_3d" || tool === "meshy_multi_image_to_3d") && (errorText.includes("image") || errorText.includes("url"))) {
93
+ return message + `\n\n**Fix**: For local images, use \`file_path\` parameter (absolute path like "/Users/me/photo.jpg"). The server handles encoding automatically. Do NOT manually base64-encode.`;
94
+ }
95
+ // Multi-color + missing texture
96
+ if (tool === "meshy_process_multicolor" && (errorText.includes("texture") || errorText.includes("input") || errorText.includes("task"))) {
97
+ return message + `\n\n**Fix**: The input model must have textures. Run \`meshy_text_to_3d_refine\` or \`meshy_retexture\` first to add textures, then use the resulting task ID as input_task_id.`;
98
+ }
99
+ // Insufficient credits
100
+ if (errorText.includes("insufficient") || errorText.includes("credit")) {
101
+ return message + `\n\n**Fix**: Check your credit balance at https://meshy.ai/pricing. Current tool: ${tool}.`;
102
+ }
103
+ return message;
104
+ }
105
+ /**
106
+ * Check if error is a rate limit error
107
+ */
108
+ export function isRateLimitError(error) {
109
+ if (error instanceof AxiosError) {
110
+ return error.response?.status === 429;
111
+ }
112
+ return false;
113
+ }
114
+ /**
115
+ * Check if error is a network error that should be retried
116
+ */
117
+ export function isRetryableError(error) {
118
+ if (error instanceof AxiosError) {
119
+ // Retry on network errors
120
+ if (!error.response) {
121
+ return true;
122
+ }
123
+ // Retry on 5xx server errors
124
+ const status = error.response.status;
125
+ return status >= 500 && status < 600;
126
+ }
127
+ return false;
128
+ }
129
+ /**
130
+ * Extract error message from various error formats
131
+ */
132
+ export function extractErrorMessage(error) {
133
+ if (error instanceof AxiosError && error.response?.data) {
134
+ const data = error.response.data;
135
+ return data.message || data.error || data.error_message || "Unknown error";
136
+ }
137
+ if (error instanceof Error) {
138
+ return error.message;
139
+ }
140
+ return String(error);
141
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Utility for reading local image files and converting to base64 data URIs.
3
+ * Used by image-to-3d, multi-image-to-3d, and image-to-image tools
4
+ * so agents don't need to pass huge base64 strings through MCP arguments.
5
+ */
6
+ /**
7
+ * Read a local image file and return a base64 data URI.
8
+ * Throws descriptive errors for missing files, unsupported formats, or oversized files.
9
+ */
10
+ export declare function fileToDataUri(filePath: string): Promise<string>;
11
+ /**
12
+ * Resolve an image source: if file_path is provided, convert to data URI;
13
+ * otherwise return image_url as-is.
14
+ */
15
+ export declare function resolveImageSource(image_url?: string, file_path?: string): Promise<string>;
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Utility for reading local image files and converting to base64 data URIs.
3
+ * Used by image-to-3d, multi-image-to-3d, and image-to-image tools
4
+ * so agents don't need to pass huge base64 strings through MCP arguments.
5
+ */
6
+ import { readFile } from "fs/promises";
7
+ import { extname } from "path";
8
+ const MIME_TYPES = {
9
+ ".jpg": "image/jpeg",
10
+ ".jpeg": "image/jpeg",
11
+ ".png": "image/png",
12
+ ".webp": "image/webp",
13
+ };
14
+ const MAX_FILE_SIZE = 20 * 1024 * 1024; // 20MB
15
+ /**
16
+ * Read a local image file and return a base64 data URI.
17
+ * Throws descriptive errors for missing files, unsupported formats, or oversized files.
18
+ */
19
+ export async function fileToDataUri(filePath) {
20
+ const ext = extname(filePath).toLowerCase();
21
+ const mime = MIME_TYPES[ext];
22
+ if (!mime) {
23
+ throw new Error(`Unsupported image format "${ext}". Supported: ${Object.keys(MIME_TYPES).join(", ")}`);
24
+ }
25
+ let buffer;
26
+ try {
27
+ buffer = await readFile(filePath);
28
+ }
29
+ catch (err) {
30
+ if (err.code === "ENOENT") {
31
+ throw new Error(`File not found: ${filePath}`);
32
+ }
33
+ throw new Error(`Failed to read file ${filePath}: ${err.message}`);
34
+ }
35
+ if (buffer.length > MAX_FILE_SIZE) {
36
+ const sizeMB = (buffer.length / (1024 * 1024)).toFixed(1);
37
+ throw new Error(`File too large (${sizeMB} MB). Maximum is ${MAX_FILE_SIZE / (1024 * 1024)} MB.`);
38
+ }
39
+ const base64 = buffer.toString("base64");
40
+ return `data:${mime};base64,${base64}`;
41
+ }
42
+ /**
43
+ * Resolve an image source: if file_path is provided, convert to data URI;
44
+ * otherwise return image_url as-is.
45
+ */
46
+ export async function resolveImageSource(image_url, file_path) {
47
+ if (file_path) {
48
+ return fileToDataUri(file_path);
49
+ }
50
+ if (image_url) {
51
+ return image_url;
52
+ }
53
+ throw new Error("Either image_url or file_path must be provided. " +
54
+ "Use file_path for local files, image_url for public URLs.");
55
+ }
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Meshy API client with authentication and error handling
3
+ */
4
+ import { GetTaskResponse } from "../types.js";
5
+ export declare class MeshyClient {
6
+ private client;
7
+ private apiKey;
8
+ constructor(apiKey: string);
9
+ /**
10
+ * Make a GET request with retry logic
11
+ */
12
+ get<T>(endpoint: string, params?: Record<string, unknown>): Promise<T>;
13
+ /**
14
+ * Make a POST request with retry logic
15
+ */
16
+ post<T>(endpoint: string, data?: Record<string, unknown>): Promise<T>;
17
+ /**
18
+ * Make a DELETE request with retry logic
19
+ */
20
+ delete<T>(endpoint: string): Promise<T>;
21
+ /**
22
+ * Make a request with exponential backoff retry logic
23
+ */
24
+ private requestWithRetry;
25
+ /**
26
+ * Sleep for specified milliseconds
27
+ */
28
+ private sleep;
29
+ /**
30
+ * Validate API key by making a test request
31
+ */
32
+ validateApiKey(): Promise<boolean>;
33
+ }
34
+ /**
35
+ * Try to fetch a task by ID from all known API endpoints.
36
+ * Tries endpoints in priority order until one succeeds.
37
+ * Useful when the task type is unknown.
38
+ */
39
+ export declare function fetchTaskByIdFromKnownEndpoints(client: MeshyClient, taskId: string): Promise<{
40
+ task: GetTaskResponse;
41
+ endpoint: string;
42
+ } | null>;
43
+ /**
44
+ * Fetch a task, trying the given endpoint first, then falling back to auto-inference.
45
+ * Returns the task data and the resolved endpoint.
46
+ */
47
+ export declare function getTaskWithAutoInference(client: MeshyClient, taskId: string, preferredEndpoint: string): Promise<{
48
+ task: GetTaskResponse;
49
+ endpoint: string;
50
+ }>;
51
+ /**
52
+ * Create and validate Meshy client
53
+ */
54
+ export declare function createMeshyClient(): Promise<MeshyClient>;
@@ -0,0 +1,172 @@
1
+ /**
2
+ * Meshy API client with authentication and error handling
3
+ */
4
+ import axios from "axios";
5
+ import { API_BASE_URL, API_TIMEOUT, RETRY_DELAYS, MAX_RETRIES } from "../constants.js";
6
+ import { isRetryableError, isRateLimitError } from "./error-handler.js";
7
+ export class MeshyClient {
8
+ client;
9
+ apiKey;
10
+ constructor(apiKey) {
11
+ this.apiKey = apiKey;
12
+ this.client = axios.create({
13
+ baseURL: API_BASE_URL,
14
+ timeout: API_TIMEOUT,
15
+ headers: {
16
+ "Content-Type": "application/json",
17
+ "Accept": "application/json"
18
+ }
19
+ });
20
+ // Add request interceptor to inject auth token
21
+ this.client.interceptors.request.use((config) => {
22
+ config.headers.Authorization = `Bearer ${this.apiKey}`;
23
+ return config;
24
+ });
25
+ }
26
+ /**
27
+ * Make a GET request with retry logic
28
+ */
29
+ async get(endpoint, params) {
30
+ return this.requestWithRetry({
31
+ method: "GET",
32
+ url: endpoint,
33
+ params
34
+ });
35
+ }
36
+ /**
37
+ * Make a POST request with retry logic
38
+ */
39
+ async post(endpoint, data) {
40
+ return this.requestWithRetry({
41
+ method: "POST",
42
+ url: endpoint,
43
+ data
44
+ });
45
+ }
46
+ /**
47
+ * Make a DELETE request with retry logic
48
+ */
49
+ async delete(endpoint) {
50
+ return this.requestWithRetry({
51
+ method: "DELETE",
52
+ url: endpoint
53
+ });
54
+ }
55
+ /**
56
+ * Make a request with exponential backoff retry logic
57
+ */
58
+ async requestWithRetry(config, retryCount = 0) {
59
+ try {
60
+ const response = await this.client.request(config);
61
+ return response.data;
62
+ }
63
+ catch (error) {
64
+ // Check if we should retry
65
+ const shouldRetry = retryCount < MAX_RETRIES &&
66
+ (isRetryableError(error) || isRateLimitError(error));
67
+ if (shouldRetry) {
68
+ const delay = RETRY_DELAYS[retryCount] || RETRY_DELAYS[RETRY_DELAYS.length - 1];
69
+ // Log retry attempt to stderr (not stdout for stdio transport)
70
+ console.error(`Request failed, retrying in ${delay}ms (attempt ${retryCount + 1}/${MAX_RETRIES})...`);
71
+ await this.sleep(delay);
72
+ return this.requestWithRetry(config, retryCount + 1);
73
+ }
74
+ // No more retries, throw the error
75
+ throw error;
76
+ }
77
+ }
78
+ /**
79
+ * Sleep for specified milliseconds
80
+ */
81
+ sleep(ms) {
82
+ return new Promise(resolve => setTimeout(resolve, ms));
83
+ }
84
+ /**
85
+ * Validate API key by making a test request
86
+ */
87
+ async validateApiKey() {
88
+ try {
89
+ // Make a simple request to check if API key is valid
90
+ // Use the text-to-3d endpoint which returns a list of tasks
91
+ await this.get("/openapi/v2/text-to-3d");
92
+ return true;
93
+ }
94
+ catch (error) {
95
+ return false;
96
+ }
97
+ }
98
+ }
99
+ /**
100
+ * Try to fetch a task by ID from all known API endpoints.
101
+ * Tries endpoints in priority order until one succeeds.
102
+ * Useful when the task type is unknown.
103
+ */
104
+ export async function fetchTaskByIdFromKnownEndpoints(client, taskId) {
105
+ const endpoints = [
106
+ "/openapi/v2/text-to-3d",
107
+ "/openapi/v1/image-to-3d",
108
+ "/openapi/v1/multi-image-to-3d",
109
+ "/openapi/v1/remesh",
110
+ "/openapi/v1/retexture",
111
+ "/openapi/v1/rigging",
112
+ "/openapi/v1/animations",
113
+ "/openapi/v1/text-to-image",
114
+ "/openapi/v1/image-to-image",
115
+ "/openapi/v1/print/multi-color"
116
+ ];
117
+ for (const endpoint of endpoints) {
118
+ try {
119
+ const task = await client.get(`${endpoint}/${taskId}`);
120
+ if (task && task.id) {
121
+ return { task, endpoint };
122
+ }
123
+ }
124
+ catch {
125
+ // Not found on this endpoint, try next
126
+ continue;
127
+ }
128
+ }
129
+ return null;
130
+ }
131
+ /**
132
+ * Fetch a task, trying the given endpoint first, then falling back to auto-inference.
133
+ * Returns the task data and the resolved endpoint.
134
+ */
135
+ export async function getTaskWithAutoInference(client, taskId, preferredEndpoint) {
136
+ // Try preferred endpoint first
137
+ try {
138
+ const task = await client.get(`${preferredEndpoint}/${taskId}`);
139
+ if (task && task.id) {
140
+ return { task, endpoint: preferredEndpoint };
141
+ }
142
+ }
143
+ catch {
144
+ // Fall through to auto-inference
145
+ }
146
+ // Auto-infer from all endpoints
147
+ const result = await fetchTaskByIdFromKnownEndpoints(client, taskId);
148
+ if (result) {
149
+ return result;
150
+ }
151
+ throw new Error(`Task ${taskId} not found on any endpoint. Verify the task_id is correct.`);
152
+ }
153
+ /**
154
+ * Create and validate Meshy client
155
+ */
156
+ export async function createMeshyClient() {
157
+ const apiKey = process.env.MESHY_API_KEY;
158
+ if (!apiKey) {
159
+ throw new Error("MESHY_API_KEY environment variable is required. " +
160
+ "Get your API key from https://www.meshy.ai/settings/api");
161
+ }
162
+ const client = new MeshyClient(apiKey);
163
+ // Validate API key on startup
164
+ console.error("Validating Meshy API key...");
165
+ const isValid = await client.validateApiKey();
166
+ if (!isValid) {
167
+ throw new Error("Invalid MESHY_API_KEY. Please check your API key is correct. " +
168
+ "Get your API key from https://www.meshy.ai/settings/api");
169
+ }
170
+ console.error("✓ Meshy API key validated successfully");
171
+ return client;
172
+ }
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Output directory manager for Meshy generation tasks.
3
+ *
4
+ * Organizes all downloaded files under {cwd}/meshy_output/ with:
5
+ * - Per-project folders: {YYYYMMDD_HHmmss}_{prompt_slug}_{task_id_prefix}/
6
+ * - Auto-downloaded thumbnails
7
+ * - Per-project metadata.json tracking task chains
8
+ * - Global history.json index
9
+ */
10
+ export interface TaskRecord {
11
+ task_id: string;
12
+ task_type: string;
13
+ stage: string;
14
+ prompt?: string;
15
+ status: string;
16
+ files: string[];
17
+ created_at: string;
18
+ }
19
+ /**
20
+ * Infer a human-readable stage name from task type and API response type field.
21
+ */
22
+ export declare function inferStage(taskType: string, apiType?: string): string;
23
+ /**
24
+ * Resolve (or create) the project directory for a task.
25
+ *
26
+ * For chained tasks (refine → preview, rig → source), pass parentTaskId
27
+ * to place the output in the same project folder as the parent.
28
+ */
29
+ export declare function resolveProjectDir(taskId: string, taskType: string, prompt?: string, parentTaskId?: string, createdAt?: string | number): string;
30
+ /**
31
+ * Generate the file path for a model download within a project directory.
32
+ * Returns: /path/to/project/stage.ext (e.g., preview.glb, refined.glb)
33
+ */
34
+ export declare function getFilePath(projectDir: string, stage: string, format: string): string;
35
+ /**
36
+ * Generate texture file path within a project directory.
37
+ * Returns: /path/to/project/stage_texType.ext (e.g., refined_base_color.png)
38
+ */
39
+ export declare function getTextureFilePath(projectDir: string, stage: string, textureType: string, url: string): string;
40
+ /**
41
+ * Record a completed task into the project's metadata.json.
42
+ */
43
+ export declare function recordTask(projectDir: string, record: TaskRecord): void;
44
+ /**
45
+ * Download and save thumbnail to the project directory.
46
+ * Silently skips on failure.
47
+ */
48
+ export declare function saveThumbnail(projectDir: string, thumbnailUrl: string): Promise<string | null>;
49
+ /**
50
+ * Get the output root path (for display purposes).
51
+ */
52
+ export declare function getOutputRootPath(): string;