@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,405 @@
1
+ /**
2
+ * Post-processing tools (remesh, retexture, rig, animate)
3
+ */
4
+ import { handleMeshyError } from "../services/error-handler.js";
5
+ import { RemeshInputSchema, RetextureInputSchema, RigInputSchema, AnimateInputSchema } from "../schemas/postprocessing.js";
6
+ import { TaskCreatedOutputSchema } from "../schemas/output.js";
7
+ import { RIGGING_MAX_FACES } from "../constants.js";
8
+ import { formatTaskCreatedResponse } from "../utils/response-formatter.js";
9
+ import { fetchTaskByIdFromKnownEndpoints } from "../services/meshy-client.js";
10
+ /**
11
+ * Register post-processing tools with the MCP server
12
+ */
13
+ export function registerPostProcessingTools(server, client) {
14
+ // Remesh tool
15
+ server.registerTool("meshy_remesh", {
16
+ title: "Remesh or Convert 3D Model",
17
+ description: `Remesh an existing 3D model or convert it to different formats using Meshy AI.
18
+
19
+ Use this to optimize polygon count, change topology, convert formats, or reposition model origin.
20
+
21
+ Args:
22
+ - input_task_id (string, optional): Task ID of an existing completed task to remesh
23
+ - model_url (string, optional): Direct URL to a model file to remesh
24
+ (Provide either input_task_id or model_url)
25
+ - target_formats (array, optional): Output formats to generate (default: ["glb"]). Options: "glb", "fbx", "obj", "usdz", "blend", "stl"
26
+ - topology (enum, optional): Mesh topology - "quad" or "triangle"
27
+ - target_polycount (number, optional): Target polygon count (100–300,000)
28
+ - resize_height (number, optional): Resize model height in meters (0 = no resize, default: 0)
29
+ - origin_at (enum, optional): Model origin placement - "bottom" or "center"
30
+ - convert_format_only (boolean, optional): Only convert format without remeshing (default: false)
31
+ - response_format (enum): Output format - "markdown" or "json" (default: "markdown")
32
+
33
+ Returns:
34
+ {
35
+ "task_id": "abc-123-def",
36
+ "status": "PENDING",
37
+ "message": "Remesh task created...",
38
+ "estimated_time": "1-2 minutes"
39
+ }
40
+
41
+ Next Steps:
42
+ Use meshy_get_task_status with task_id and task_type="remesh" to check progress.
43
+
44
+ Examples:
45
+ - Remesh by task: { input_task_id: "abc-123", target_polycount: 10000 }
46
+ - Convert format: { model_url: "https://...", target_formats: ["fbx", "obj"], convert_format_only: true }
47
+
48
+ Error Handling:
49
+ - Returns "NotFound" if input_task_id doesn't exist
50
+ - Returns "InvalidModel" if model_url is not accessible`,
51
+ inputSchema: RemeshInputSchema,
52
+ outputSchema: TaskCreatedOutputSchema,
53
+ annotations: {
54
+ readOnlyHint: false,
55
+ destructiveHint: false,
56
+ idempotentHint: false,
57
+ openWorldHint: true
58
+ }
59
+ }, async (params) => {
60
+ try {
61
+ if (!params.input_task_id && !params.model_url) {
62
+ return {
63
+ isError: true,
64
+ content: [{
65
+ type: "text",
66
+ text: "Error: Either input_task_id or model_url must be provided."
67
+ }]
68
+ };
69
+ }
70
+ const request = {
71
+ target_formats: params.target_formats,
72
+ resize_height: params.resize_height,
73
+ convert_format_only: params.convert_format_only
74
+ };
75
+ if (params.input_task_id)
76
+ request.input_task_id = params.input_task_id;
77
+ if (params.model_url)
78
+ request.model_url = params.model_url;
79
+ if (params.topology)
80
+ request.topology = params.topology;
81
+ if (params.target_polycount)
82
+ request.target_polycount = params.target_polycount;
83
+ if (params.auto_size !== undefined)
84
+ request.auto_size = params.auto_size;
85
+ if (params.origin_at)
86
+ request.origin_at = params.origin_at;
87
+ const response = await client.post("/openapi/v1/remesh", request);
88
+ const taskId = response.result;
89
+ const output = {
90
+ task_id: taskId,
91
+ status: "PENDING",
92
+ message: `Remesh task created successfully. Task ID: ${taskId}`,
93
+ estimated_time: "1-2 minutes"
94
+ };
95
+ return formatTaskCreatedResponse(output, params.response_format, "Remesh Task Created", "Remeshing the model with the specified parameters.", "remesh");
96
+ }
97
+ catch (error) {
98
+ return {
99
+ isError: true,
100
+ content: [{
101
+ type: "text",
102
+ text: handleMeshyError(error)
103
+ }]
104
+ };
105
+ }
106
+ });
107
+ // Retexture tool
108
+ server.registerTool("meshy_retexture", {
109
+ title: "Retexture 3D Model",
110
+ description: `Apply new AI-generated textures to an existing 3D model using Meshy AI.
111
+
112
+ IMPORTANT: Before calling this tool, ask the user to provide EITHER:
113
+ - text_style_prompt: A text description of the desired texture style (e.g. "rusty metal", "cartoon style")
114
+ - image_style_url: A reference image URL for the texture style
115
+ One of these is REQUIRED — the tool will fail without it.
116
+ If both are provided, image_style_url takes precedence.
117
+
118
+ Args:
119
+ - input_task_id (string, optional): Task ID of an existing completed task to retexture
120
+ - model_url (string, optional): Direct URL to a model file to retexture
121
+ (Provide either input_task_id or model_url)
122
+ - text_style_prompt (string): Text prompt describing the desired texture style. Max 600 characters. REQUIRED if image_style_url not provided.
123
+ - image_style_url (string): URL of an image to use as texture style reference. REQUIRED if text_style_prompt not provided. Takes precedence if both given.
124
+ - ai_model (enum): AI model - "meshy-5", "meshy-6", or "latest" (default). Ask user which model before proceeding
125
+ - enable_original_uv (boolean): Preserve original UV mapping (default: true)
126
+ - enable_pbr (boolean): Enable PBR textures (default: false)
127
+ - remove_lighting (boolean, optional): Remove highlights/shadows from base color texture. Default true. Only meshy-6/latest
128
+ - target_formats (string[], optional): Output formats. 3MF must be explicitly included if needed.
129
+ - response_format (enum): Output format - "markdown" or "json" (default: "markdown")
130
+
131
+ Returns:
132
+ {
133
+ "task_id": "abc-123-def",
134
+ "status": "PENDING",
135
+ "message": "Retexture task created...",
136
+ "estimated_time": "2-3 minutes"
137
+ }
138
+
139
+ Next Steps:
140
+ Use meshy_get_task_status with task_id and task_type="retexture" to check progress.
141
+
142
+ Examples:
143
+ - Text style: { input_task_id: "abc-123", text_style_prompt: "rusty metal" }
144
+ - Image style: { model_url: "https://...", image_style_url: "https://style-ref.jpg" }
145
+
146
+ Error Handling:
147
+ - Returns "NotFound" if input_task_id doesn't exist
148
+ - Returns error if neither text_style_prompt nor image_style_url is provided`,
149
+ inputSchema: RetextureInputSchema,
150
+ outputSchema: TaskCreatedOutputSchema,
151
+ annotations: {
152
+ readOnlyHint: false,
153
+ destructiveHint: false,
154
+ idempotentHint: false,
155
+ openWorldHint: true
156
+ }
157
+ }, async (params) => {
158
+ try {
159
+ if (!params.input_task_id && !params.model_url) {
160
+ return {
161
+ isError: true,
162
+ content: [{
163
+ type: "text",
164
+ text: "Error: Either input_task_id or model_url must be provided."
165
+ }]
166
+ };
167
+ }
168
+ if (!params.text_style_prompt && !params.image_style_url) {
169
+ return {
170
+ isError: true,
171
+ content: [{
172
+ type: "text",
173
+ text: "Error: Either text_style_prompt or image_style_url must be provided."
174
+ }]
175
+ };
176
+ }
177
+ const request = {
178
+ enable_original_uv: params.enable_original_uv,
179
+ enable_pbr: params.enable_pbr
180
+ };
181
+ if (params.input_task_id)
182
+ request.input_task_id = params.input_task_id;
183
+ if (params.model_url)
184
+ request.model_url = params.model_url;
185
+ if (params.text_style_prompt)
186
+ request.text_style_prompt = params.text_style_prompt;
187
+ if (params.image_style_url)
188
+ request.image_style_url = params.image_style_url;
189
+ if (params.ai_model)
190
+ request.ai_model = params.ai_model;
191
+ if (params.remove_lighting !== undefined)
192
+ request.remove_lighting = params.remove_lighting;
193
+ if (params.target_formats)
194
+ request.target_formats = params.target_formats;
195
+ const response = await client.post("/openapi/v1/retexture", request);
196
+ const taskId = response.result;
197
+ const output = {
198
+ task_id: taskId,
199
+ status: "PENDING",
200
+ message: `Retexture task created successfully. Task ID: ${taskId}`,
201
+ estimated_time: "2-3 minutes"
202
+ };
203
+ return formatTaskCreatedResponse(output, params.response_format, "Retexture Task Created", "Applying new textures to the model.", "retexture");
204
+ }
205
+ catch (error) {
206
+ return {
207
+ isError: true,
208
+ content: [{
209
+ type: "text",
210
+ text: handleMeshyError(error)
211
+ }]
212
+ };
213
+ }
214
+ });
215
+ // Rig tool
216
+ server.registerTool("meshy_rig", {
217
+ title: "Auto-Rig 3D Character",
218
+ description: `Automatically rig a 3D character model for animation using Meshy AI.
219
+
220
+ Creates a skeletal rig for character models, enabling them to be animated. Rigging INCLUDES free walking + running animations.
221
+
222
+ IMPORTANT: The source model should be generated with pose_mode="t-pose" for best rigging results. When the user asks to rig or animate, always ensure the generation step used t-pose. If the model was not generated with t-pose, recommend regenerating with pose_mode="t-pose" first.
223
+
224
+ CONSTRAINT: Model must have ≤300,000 faces. This tool auto-checks and suggests remeshing if exceeded.
225
+
226
+ Args:
227
+ - input_task_id (string, optional): Task ID of an existing completed task to rig
228
+ - model_url (string, optional): Direct URL to a model file to rig
229
+ (Provide either input_task_id or model_url)
230
+ - height_meters (number, optional): Height of the character in meters (default: 1.7)
231
+ - texture_image_url (string, optional): URL of a texture image to apply to the model
232
+ - response_format (enum): Output format - "markdown" or "json" (default: "markdown")
233
+
234
+ Returns:
235
+ {
236
+ "task_id": "abc-123-def",
237
+ "status": "PENDING",
238
+ "message": "Rigging task created...",
239
+ "estimated_time": "2-3 minutes"
240
+ }
241
+
242
+ Included with rigging (FREE):
243
+ - Walking animation (GLB + FBX)
244
+ - Running animation (GLB + FBX)
245
+ Only call meshy_animate (3 credits) for CUSTOM animations beyond walking/running.
246
+
247
+ Examples:
248
+ - Basic rig: { input_task_id: "abc-123" }
249
+ - With height: { model_url: "https://...", height_meters: 1.8 }
250
+
251
+ Error Handling:
252
+ - Returns "NotFound" if input_task_id doesn't exist
253
+ - Returns error if model exceeds 300K faces (suggests remeshing)
254
+ - Returns error if model is not suitable for rigging`,
255
+ inputSchema: RigInputSchema,
256
+ outputSchema: TaskCreatedOutputSchema,
257
+ annotations: {
258
+ readOnlyHint: false,
259
+ destructiveHint: false,
260
+ idempotentHint: false,
261
+ openWorldHint: true
262
+ }
263
+ }, async (params) => {
264
+ try {
265
+ if (!params.input_task_id && !params.model_url) {
266
+ return {
267
+ isError: true,
268
+ content: [{
269
+ type: "text",
270
+ text: "Error: Either input_task_id or model_url must be provided."
271
+ }]
272
+ };
273
+ }
274
+ // Pre-validate face count for rigging constraint
275
+ if (params.input_task_id) {
276
+ try {
277
+ const result = await fetchTaskByIdFromKnownEndpoints(client, params.input_task_id);
278
+ const sourceTask = result?.task;
279
+ if (sourceTask?.face_count && sourceTask.face_count > RIGGING_MAX_FACES) {
280
+ const faceCount = sourceTask.face_count.toLocaleString();
281
+ const maxFaces = RIGGING_MAX_FACES.toLocaleString();
282
+ return {
283
+ content: [{
284
+ type: "text",
285
+ text: `# Rigging Blocked: Face Count Too High
286
+
287
+ **Model faces**: ${faceCount}
288
+ **Maximum allowed**: ${maxFaces}
289
+
290
+ The model exceeds the ${maxFaces}-face limit for rigging.
291
+
292
+ **How to fix**:
293
+ 1. Call \`meshy_remesh\` with input_task_id "${params.input_task_id}" and target_polycount 100000
294
+ 2. Wait for remesh to complete (use \`meshy_get_task_status\` with task_type "remesh")
295
+ 3. Then call \`meshy_rig\` with the remeshed task's ID`
296
+ }]
297
+ };
298
+ }
299
+ }
300
+ catch {
301
+ // If we can't fetch the source task, proceed anyway — the API will catch it
302
+ }
303
+ }
304
+ const request = {
305
+ height_meters: params.height_meters
306
+ };
307
+ if (params.input_task_id)
308
+ request.input_task_id = params.input_task_id;
309
+ if (params.model_url)
310
+ request.model_url = params.model_url;
311
+ if (params.texture_image_url)
312
+ request.texture_image_url = params.texture_image_url;
313
+ const response = await client.post("/openapi/v1/rigging", request);
314
+ const taskId = response.result;
315
+ const output = {
316
+ task_id: taskId,
317
+ status: "PENDING",
318
+ message: `Rigging task created successfully. Task ID: ${taskId}`,
319
+ estimated_time: "2-3 minutes"
320
+ };
321
+ return formatTaskCreatedResponse(output, params.response_format, "Rigging Task Created", `Rigging INCLUDES walking + running animations for free. Download with \`meshy_download_model\` (task_type "rigging").\nOnly call \`meshy_animate\` (3 credits) for CUSTOM animations beyond walking/running.`, "rigging");
322
+ }
323
+ catch (error) {
324
+ return {
325
+ isError: true,
326
+ content: [{
327
+ type: "text",
328
+ text: handleMeshyError(error, { tool: "meshy_rig", taskId: params.input_task_id })
329
+ }]
330
+ };
331
+ }
332
+ });
333
+ // Animate tool
334
+ server.registerTool("meshy_animate", {
335
+ title: "Animate Rigged 3D Character",
336
+ description: `Apply a CUSTOM animation to a rigged 3D character using Meshy AI. Cost: 3 credits.
337
+
338
+ NOTE: Walking and running animations are already FREE with meshy_rig. Only use this tool for CUSTOM animations (dancing, jumping, fighting, etc.) that are NOT included in rigging.
339
+
340
+ Applies a predefined animation action to a character that has been rigged with meshy_rig.
341
+
342
+ Args:
343
+ - rig_task_id (string): Task ID of the completed rigging task (required)
344
+ - action_id (number): Integer ID of the animation action to apply (required)
345
+ - post_process (object, optional): Optional post-processing to apply after animation:
346
+ - operation_type (enum): "change_fps", "fbx2usdz", or "extract_armature"
347
+ - fps (number, optional): Target FPS for change_fps operation (24, 25, 30, or 60)
348
+ - response_format (enum): Output format - "markdown" or "json" (default: "markdown")
349
+
350
+ Returns:
351
+ {
352
+ "task_id": "abc-123-def",
353
+ "status": "PENDING",
354
+ "message": "Animation task created...",
355
+ "estimated_time": "1-2 minutes"
356
+ }
357
+
358
+ Next Steps:
359
+ Use meshy_get_task_status with task_id and task_type="animation" to check progress.
360
+
361
+ Examples:
362
+ - Basic animate: { rig_task_id: "abc-123", action_id: 1 }
363
+ - With FPS change: { rig_task_id: "abc-123", action_id: 1, post_process: { operation_type: "change_fps", fps: 30 } }
364
+
365
+ Error Handling:
366
+ - Returns "NotFound" if rig_task_id doesn't exist
367
+ - Returns error if rig task is not yet completed`,
368
+ inputSchema: AnimateInputSchema,
369
+ outputSchema: TaskCreatedOutputSchema,
370
+ annotations: {
371
+ readOnlyHint: false,
372
+ destructiveHint: false,
373
+ idempotentHint: false,
374
+ openWorldHint: true
375
+ }
376
+ }, async (params) => {
377
+ try {
378
+ const request = {
379
+ rig_task_id: params.rig_task_id,
380
+ action_id: params.action_id
381
+ };
382
+ if (params.post_process) {
383
+ request.post_process = params.post_process;
384
+ }
385
+ const response = await client.post("/openapi/v1/animations", request);
386
+ const taskId = response.result;
387
+ const output = {
388
+ task_id: taskId,
389
+ status: "PENDING",
390
+ message: `Animation task created successfully. Task ID: ${taskId}`,
391
+ estimated_time: "1-2 minutes"
392
+ };
393
+ return formatTaskCreatedResponse(output, params.response_format, "Animation Task Created", "Applying custom animation to the rigged character.", "animation");
394
+ }
395
+ catch (error) {
396
+ return {
397
+ isError: true,
398
+ content: [{
399
+ type: "text",
400
+ text: handleMeshyError(error)
401
+ }]
402
+ };
403
+ }
404
+ });
405
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * 3D printing tools (send to slicer, analyze printability, process multicolor)
3
+ */
4
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
5
+ import { MeshyClient } from "../services/meshy-client.js";
6
+ /**
7
+ * Register 3D printing tools with the MCP server
8
+ */
9
+ export declare function registerPrintingTools(server: McpServer, client: MeshyClient): void;