Skip to main content
Glama

Spline MCP Server

by aydinfer
multi-scene-tools.js9.43 kB
/** * Multi-Scene Tools for Spline.design * * Tools for managing multiple scenes and their relationships. */ import { z } from "zod"; import { fetchFromSplineApi } from "../../utils/api-client.js"; /** * Register multi-scene tools * @param {McpServer} server - The MCP server instance */ export function registerMultiSceneTools(server) { server.tool( "createScene", { projectId: z.string().min(1).describe("Project ID"), name: z.string().min(1).describe("Scene name"), description: z.string().optional().describe("Scene description"), template: z.enum(["empty", "product", "presentation", "interactive", "portfolio"]).optional() .describe("Scene template"), sceneType: z.enum(["3d", "ui", "hana"]).default("3d").describe("Scene type"), }, async ({ projectId, name, description, template, sceneType }) => { try { const result = await fetchFromSplineApi(`/projects/${projectId}/scenes`, { method: "POST", body: JSON.stringify({ name, description, template, sceneType, }), }); return { content: [{ type: "text", text: `Created new ${sceneType} scene "${name}" (ID: ${result.sceneId})` }] }; } catch (error) { return { content: [{ type: "text", text: `Error creating scene: ${error.message}` }], isError: true }; } } ); server.tool( "listScenes", { projectId: z.string().min(1).describe("Project ID"), filter: z.enum(["all", "active", "archived"]).default("all").describe("Filter scenes by status"), page: z.number().int().min(1).default(1).describe("Page number"), pageSize: z.number().int().min(1).max(100).default(20).describe("Scenes per page"), }, async ({ projectId, filter, page, pageSize }) => { try { const result = await fetchFromSplineApi(`/projects/${projectId}/scenes`, { method: "GET", query: { filter, page, pageSize, }, }); return { content: [{ type: "text", text: `Found ${result.total} scenes in project ${projectId}`, }, { type: "json", json: result.items, }] }; } catch (error) { return { content: [{ type: "text", text: `Error listing scenes: ${error.message}` }], isError: true }; } } ); server.tool( "linkScenes", { projectId: z.string().min(1).describe("Project ID"), sourceSceneId: z.string().min(1).describe("Source scene ID"), targetSceneId: z.string().min(1).describe("Target scene ID"), linkType: z.enum(["reference", "embed", "portal"]).describe("Type of scene link"), sourceObjectId: z.string().optional().describe("Source object ID (for embedding or portal)"), linkName: z.string().optional().describe("Link name"), position: z.object({ x: z.number().default(0), y: z.number().default(0), z: z.number().default(0), }).optional().describe("Position for embedded scene"), scale: z.number().positive().default(1).optional().describe("Scale for embedded scene"), }, async ({ projectId, sourceSceneId, targetSceneId, linkType, sourceObjectId, linkName, position, scale }) => { try { const result = await fetchFromSplineApi(`/projects/${projectId}/scenes/links`, { method: "POST", body: JSON.stringify({ sourceSceneId, targetSceneId, linkType, sourceObjectId, linkName, position, scale, }), }); return { content: [{ type: "text", text: `Created ${linkType} link between scenes (Link ID: ${result.linkId})` }] }; } catch (error) { return { content: [{ type: "text", text: `Error linking scenes: ${error.message}` }], isError: true }; } } ); server.tool( "copyBetweenScenes", { projectId: z.string().min(1).describe("Project ID"), sourceSceneId: z.string().min(1).describe("Source scene ID"), targetSceneId: z.string().min(1).describe("Target scene ID"), objectIds: z.array(z.string().min(1)).optional().describe("Object IDs to copy"), stateIds: z.array(z.string().min(1)).optional().describe("State IDs to copy"), eventIds: z.array(z.string().min(1)).optional().describe("Event IDs to copy"), materialIds: z.array(z.string().min(1)).optional().describe("Material IDs to copy"), componentIds: z.array(z.string().min(1)).optional().describe("Component IDs to copy"), copyDependencies: z.boolean().default(true).describe("Copy dependent objects automatically"), position: z.object({ x: z.number().default(0), y: z.number().default(0), z: z.number().default(0), }).optional().describe("Target position for copied objects"), }, async ({ projectId, sourceSceneId, targetSceneId, objectIds, stateIds, eventIds, materialIds, componentIds, copyDependencies, position }) => { try { const result = await fetchFromSplineApi(`/projects/${projectId}/scenes/copy`, { method: "POST", body: JSON.stringify({ sourceSceneId, targetSceneId, objectIds, stateIds, eventIds, materialIds, componentIds, copyDependencies, position, }), }); const itemCounts = []; if (result.copiedObjects?.length) itemCounts.push(`${result.copiedObjects.length} objects`); if (result.copiedStates?.length) itemCounts.push(`${result.copiedStates.length} states`); if (result.copiedEvents?.length) itemCounts.push(`${result.copiedEvents.length} events`); if (result.copiedMaterials?.length) itemCounts.push(`${result.copiedMaterials.length} materials`); if (result.copiedComponents?.length) itemCounts.push(`${result.copiedComponents.length} components`); return { content: [{ type: "text", text: `Copied ${itemCounts.join(', ')} between scenes` }] }; } catch (error) { return { content: [{ type: "text", text: `Error copying between scenes: ${error.message}` }], isError: true }; } } ); server.tool( "navigateBetweenScenes", { projectId: z.string().min(1).describe("Project ID"), currentSceneId: z.string().min(1).describe("Current scene ID"), targetSceneId: z.string().min(1).describe("Target scene ID"), transitionType: z.enum(["none", "fade", "slide", "zoom", "custom"]).default("fade") .describe("Transition animation type"), transitionDuration: z.number().positive().default(0.5).describe("Transition duration in seconds"), preserveCamera: z.boolean().default(false).describe("Preserve camera position/rotation in target scene"), stateToTrigger: z.string().optional().describe("State ID to trigger in target scene on load"), }, async ({ projectId, currentSceneId, targetSceneId, transitionType, transitionDuration, preserveCamera, stateToTrigger }) => { try { await fetchFromSplineApi(`/projects/${projectId}/scenes/${currentSceneId}/navigate`, { method: "POST", body: JSON.stringify({ targetSceneId, transitionType, transitionDuration, preserveCamera, stateToTrigger, }), }); return { content: [{ type: "text", text: `Navigated from scene ${currentSceneId} to scene ${targetSceneId} with ${transitionType} transition` }] }; } catch (error) { return { content: [{ type: "text", text: `Error navigating between scenes: ${error.message}` }], isError: true }; } } ); server.tool( "createSceneGroup", { projectId: z.string().min(1).describe("Project ID"), name: z.string().min(1).describe("Scene group name"), sceneIds: z.array(z.string().min(1)).min(1).describe("Scene IDs to include in group"), description: z.string().optional().describe("Group description"), }, async ({ projectId, name, sceneIds, description }) => { try { const result = await fetchFromSplineApi(`/projects/${projectId}/scene-groups`, { method: "POST", body: JSON.stringify({ name, sceneIds, description, }), }); return { content: [{ type: "text", text: `Created scene group "${name}" with ${sceneIds.length} scenes (Group ID: ${result.groupId})` }] }; } catch (error) { return { content: [{ type: "text", text: `Error creating scene group: ${error.message}` }], isError: true }; } } ); }

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/aydinfer/spline-mcp-server'

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