Mmcp.market

3dCMS MCP server

by 3dcms.info·info.3dcms/mcp·v1.1.0

Build a real 3D website by chatting: pages, models and text in space, published to a live URL.

C57/100grade C
What users say
No reviews yet
Be the first
Safety scan
C57/100

full report

Adoption
New

Little public usage data yet

Reviews

Write one

Nobody has reviewed 3dCMS yet.

If you have run it, two minutes of your experience saves the next person an afternoon.

3dCMS tools (8, 2 write)

write = sends, deletes, buys or posts
  • apply_opsFree

    Edit a scene by applying a batch of operations ATOMICALLY (all-or-nothing; one undo step in the editor). Call this for any add / move / edit / delete / duplicate / group / reparent / re-material. Reference existing nodes ONLY by the id shown in get_scene — never guess ids. To create a node and then modify or parent it in the same batch, put a `tempId` on the add op and use that same string as the id/parentId of later ops. Coordinates: Y up, units ≈ meters, ground at y≈0 — place objects so they rest sensibly. Geometry size lives in `data` (box {width,height,depth}; sphere/cylinder/torus {radius}; torus {tube}); the default box is 1.5×1.5×1.5. MATERIALS: to color an object pass `color` (#rrggbb) on add/assignMaterial; to apply a GENERATED material pass `materialMaps` (the map urls from get_generation) on add/assignMaterial; for a real surface with no image at all pass `materialPreset` (brushed-metal, frosted-ice, rough-stone, ... see the field) — all three mint-or-reuse a material and assign it, no pre-existing library entry needed. Prefer a preset or a set from list_assets over a flat color for walls, floors, trim and metal, so the first pass reads as a place, not a grey box. IMPORTED MODELS: a glb from list_assets (kind "glb") or get_generation goes on a geometry node as data: {primitive:"imported", assetUrl:"<url>"}, NOT as the top-level `primitive` (that enum is the five built-ins). Once placed, get_scene reports the model's bounds and size. The built-in low-poly picture frames face +X: to hang one on a wall that faces +Z rotate it [0, 1.5708, 0]. INSTANCING (971): identical PRIMITIVES are drawn as one batch too, keyed on the primitive, its data dimensions and segments, its material (color, materialMaps or materialPreset) and its room: 281 boxes that share one size and one material cost one draw call, and the weight meter credits it. Vary placement with position, rotation and the node SCALE (not data.width/height/depth) to stay in the batch; a different data size or material starts another. An animated, tapped or surface-emitter host node draws on its own. WEB BLOCKS (responsive site building-blocks): add webText (data.role h1|h2|h3|body|caption + data.text + data.fontWeight 400|500|600|700 for a real weighted face (a role already picks one); glyph outline via data.outline true|false + data.outlineColor + data.outlineWidth), webImage (data.src + data.alt), webLink (data.label + data.href or data.linkedPageId), webVideo (data.src + data.poster); any web block also accepts data.anchor = world|billboard|hud. PAGES & NEIGHBORHOODS: add a `webpage` node per page (set data.pageTitle, data.slug); place pages tens of meters apart and parent each area's content under its page to build a multi-area site. Link areas with a webLink (data.linkedPageId = target page, or data.linkedCameraId = a saved viewpoint); a tempId minted in the SAME batch works as linkedPageId, so a "sign in the park linking to the mall" can be built in one call. Set data.arrivalCameraId on a page (a camera node id, or a tempId from the same batch) to choose the viewpoint visitors ARRIVE at for it — otherwise the runtime auto-frames the page from a generic distance. It belongs on the PAGE, not on the signs: a sign that jumps to a camera skips the page change, so the URL and title would still name the previous room. MULTI-LEG ARRIVALS (969): a flight is a straight line between two poses, which cuts through a wall to reach a room. Set data.viaCameraIds on a CAMERA node (an ordered array of camera node ids, or same-batch tempIds, up to 8) and every flight to that camera (a page arrival through arrivalCameraId, a sign or tap with linkedCameraId, a #view deep link) passes through those viewpoints first on one smooth curve: place a via camera in the doorway, aimed into the room, and the visitor is threaded through the door. The flight takes longer in proportion to the path, the mid-flight bow is off on a legged flight, a via camera's own legs are not followed, and a via id that names no camera is skipped (get_scene warns). Wire a doorway in one batch: add the door camera with tempId "door", then the arrival camera with data.viaCameraIds ["door"]. PACING (1028): data.travelScale 0.25-4 on a camera node multiplies the viewport.cameraTravelScale of the scene for every flight that ENDS at that viewpoint (a page arrival, a sign, a #view link): 2 is a slow reveal into a hero room, 0.5 a quick hop. TAP ACTIONS: object nodes (geometry, text3d, group, cable) take data.tap = {"action":"inspect"} (fullscreen orbit lightbox on tap — products/exhibits/hero models), {"action":"moveTo"} (camera flies to frame it — tap-to-travel on landmarks), or {"action":"link", href|linkedPageId|linkedCameraId} (navigate like a webLink). inspect/moveTo take an optional "distance" (meters) overriding the auto camera distance. Runs for visitors in the published scene. ANIMATION: any node (geometry, text3d, group, cable, the web blocks and lights, and a camera, which makes it a RIDE, below) takes data.animation to move on a loop in the published scene: {"duration":4,"loop":"loop","easing":"easeInOut","tracks":{"position":[{"t":0,"v":[0,0,0]},{"t":0.5,"v":[0,0.6,0]},{"t":1,"v":[0,0,0]}]}} bobs the node half a meter. t is 0-1 across duration seconds; values are OFFSETS from the authored transform (position meters, rotation euler radians, scale a multiplier, 1 = authored size). tracks = position|rotation|scale; loop = loop|pingpong|once; optional delay seconds; max 16 keys per track. Animated nodes cost scene weight and cannot be GPU-instanced: animate a few focal objects, not everything. MOTION PATH (973): data.animation.path puts the node ON a curve instead of (or as well as) the tracks: {"duration":12,"loop":"loop","path":{"curveId":"<a nurbs node id, or its same-batch tempId>","facing":"tangent","forward":"z","up":[0,1,0]}}. facing = tangent (nose along the curve) | target (toward a world point, pass "target":[x,y,z]) | fixed (keep the authored rotation); forward = z|-z|x|-x is which local axis of the model leads (a glb whose nose points -Z wants "-z"); up is the world up the roll is kept against; offset 0..1 is where along the curve the ride starts (space several riders around one loop with different offsets); reverse:true rides it backwards; facingEase (seconds, default 1, 0 snaps) is how long the rider takes to TURN from the way it was placed into the facing of the path when the ride begins (and again if the heading jumps, such as the loop seam of an open curve), eased in and out. `easing` shapes the ride itself: linear is the default on a loop (constant speed, no braking at the seam) and easeInOut on once/pingpong (pull away, brake at the end). While bound, the authored position and rotation are NOT where the node is (it sits on the curve; scale still applies, tracks stack on top as offsets); a curveId that names no curve node leaves it at its authored place. The curve is a nurbs node: {"op":"add","nodeType":"nurbs","tempId":"track","data":{"controlPoints":[[0,0,0],[8,0,-4],[16,0,0],[8,0,4]],"closed":true}} then {"op":"add","nodeType":"geometry","primitive":"box","data":{"animation":{"duration":10,"path":{"curveId":"track"}}}}. get_scene reads a rider back as motionPath:<curveId>. A curve something rides is a HELPER on the published site (1032): registered for the ride, not drawn, so a swing arc does not show as a green spline across the door; set data.renderCurve:true on the curve to draw it anyway. The editor always shows it. CAMERA RIDE (1026): a CAMERA node with data.animation.path is a ride: every flight that lands on it (a page arrival through arrivalCameraId, a sign or tap with linkedCameraId, a #view link) ends at the curve's start pose and the visitor's camera then follows the curve, looking along it (facing tangent), at a world point (facing target + target) or the way it arrived (facing fixed); animation.duration, loop (once = ride to the end and hand control back there; loop and pingpong ride until the visitor takes over; on an OPEN curve loop cuts back to the start each lap, so use once or pingpong there or set closed:true on the curve), easing, path.offset and path.reverse mean what they mean for any rider (a pingpong ride looks where it is going on the way back too). data.rideBreak is the author's choice of what ends the ride early: "any" (default: any pointer down, key, wheel or touch), "move" (only a drag, a wheel or a movement key, so a tap on a sign still navigates and a stray click does not stop the ride) or "none" (nothing; a loop rides until the visitor navigates elsewhere). A new flight always ends a ride; a reduced-motion visitor and a screenshot get the start pose and no ride; the curve is a helper (not drawn) like any ridden curve. {"op":"add","nodeType":"nurbs","tempId":"rail","data":{"controlPoints":[[0,1.6,8],[0,1.6,2],[3,1.6,-2]]}},{"op":"add","nodeType":"camera","tempId":"arrive","data":{"animation":{"duration":8,"loop":"once","path":{"curveId":"rail"}},"rideBreak":"move"}} then arrivalCameraId "arrive" on the page. FAST PATH — make_page: for a page that is a heading + paragraph + some links, ONE `make_page` op builds the whole thing: pass name, data.pageTitle+data.slug, hero (h1 text), body (paragraph), and links [{label, to: a page id/tempId | href}], plus a tempId (which becomes the PAGE id so other signs can target it). Create link-target pages earlier in the batch so their tempIds resolve. It replaces ~6–12 primitive ops. SET-DRESSING: use the `scatter` op to make `count` jittered copies of a node across an {area:{x,z}} (random rotation + slight scale + optional per-copy `tint` for imported models) — efficient for many props, debris, foliage, a trashy parking lot. The result names what it CREATED (`created`: id, type, name) so you can edit those nodes in your next call without re-reading the scene. LARGE SCENES: build INCREMENTALLY across multiple apply_ops calls (~40–50 objects per batch) instead of one huge batch — each call is its own atomic Undo and stays well within limits; call get_scene between batches to stay aligned. Do not try to emit hundreds of operations in a single call. The result may carry `warnings`: advisory scene lint (floating content, orphan web blocks, unreadable text, crowded pages, a lamp whose range reaches the ceiling above it, a plane facing a shadow lamp with content behind it, ambient IBL in a sun-less studio scene, a page card inside a room, a camera ride looping an open curve, scene WEIGHT over the perf red line). A per-node warning you have read and disagree with goes quiet with data.lintMute:true on that node (every rule about one node honours it; scene-wide ones do not). When more than six fire, the last entry counts the rest. The ops APPLIED; act on warnings in your next batch unless they contradict what the user asked for. A weight warning names the heaviest categories: trim them (lower-poly models, fewer shadow lights/particles) or publish will warn and far heavier scenes are blocked.

  • create_scenewrite actionFree

    Create a new, empty scene in your 3dCMS account and return its id. Build it up with apply_ops. It appears in your account and opens in the 3dCMS editor.

  • get_sceneFree

    Read a compact, high-signal summary of one scene: its nodes (id, type, name, parent, position, rotation [euler radians], scale, primitive, real size, assigned material; an imported model also carries its bounds {min,max}, size [w,h,d] and assetFile, the tail of its list_assets url, so a frame can be sized to a picture without a screenshot) plus the material library — including each solid material's color/roughness/metalness and `usedBy` (how many nodes wear it, the first three ids) or `unused:true`, so you can reason about how the scene LOOKS, not just its structure, and `settings`: the scene-level look the `settings` op writes (lighting: mode, sun, timeOfDay, env IBL intensity; viewport: post effects, tone mapping, LUT, draw distance, camera flight; environment: fog; audio), every field that exists, plus `settings.sky`, one sentence on what actually lights the scene. Read it FIRST when a surface looks lit for no reason: env.intensity is ambient light with no source and no falloff, and a scene with every lamp deleted stays lit by it. This is the view to reason over before editing. Sizes are in scene units (≈ meters), BEFORE each node's scale; transforms that are zero/default are omitted. ALWAYS returns `counts` (nodes by type) and `matched`, so on a big scene you orient first and then ask for the part you need: narrow with `type` / `name` / `nameContains` / `parentId`, page with `limit` + `offset` (the reply carries `nextOffset` and a `truncated` note when there is more). A scene of a few hundred nodes WILL be cut off if you ask for all of it at once.

  • get_scene_screenshotFree

    Render the scene server-side (real WebGL, headless) and return screenshot image(s) — your EYES on the scene. Use it after apply_ops batches: judge the image (floating objects, unreadable/overlapping text, empty horizon, bad spacing, wrong colors) and fix with apply_ops. Discipline: at most 2 screenshot→fix rounds per user request, then report what you built. views (max 4, default = the visitor's entry view): pass "page:<pageIdOrSlug>" to frame one page/neighborhood, or {position:[x,y,z], target:[x,y,z], fov?} for an exact camera. Rendering is real but slow — ~10s for simple scenes, and the server gives up after 45 s per view by default; a 400-node scene with many textures needs more, so pass timeoutSeconds (up to 180) and a smaller maxDim (a 512-wide shot is a fine check of placement and readability and renders faster). A timeout comes back as an error naming it, never as an empty image; repeat shots of an unchanged scene at the same size are cached and instant. Rate-limited: prefer ONE well-chosen view; only multi-view when areas are far apart (four views at 180 s can hold the renderer for twelve minutes). If it returns a busy/unavailable error, keep building and retry once later — do not loop on it.

  • list_assetsFree

    Search the images and 3D models already in the account. This is how you TEXTURE something with what the user already owns: search for the look you want, take the `url` of the result, and bind it on an apply_ops add or assignMaterial as `materialMaps: { baseColor: "<url>" }` — plus `normal` / `roughness` / `metalness` when the set has those maps. PBR sets are stored as SEPARATE images that share a setId; the base color member is often unsuffixed (mapRole null) while its siblings end in the map name ("... Normal", "... Ormh"). Search the set name, then pick one url per slot by mapRole; a base colour alone already reads as a texture. ALWAYS search rather than listing: an account can hold thousands of images. `q` is a set of terms that must ALL appear in the label or keywords, so "weathered wood" narrows instead of returning everything wooden. Returns id, kind, label, url, sizeBytes, keywords, mapRole and setId. mapRole names the slot an image is (baseColor | normal | roughness | metalness | height | emissive | ao | orm | ormh, or null when the label does not say); setId is shared by every image of one PBR set, so bind a whole set by matching setId and placing each url by its mapRole. orm/ormh are channel-packed files (R=AO, G=roughness, B=metalness, A=height): bind one on materialMaps.ormh (or .orm), never as roughness, and the channels are split out for you, so a library set is baseColor + normal + ormh. An `emissive` member goes on materialMaps.emissive. `kind` is "image" for textures and "glb" for models (a glb url goes on a geometry node as data: {primitive:"imported", assetUrl}, never as the top-level primitive; the low-poly picture frames face +X, rotate [0, 1.5708, 0] to hang one on a wall facing +Z).

  • list_scenesFree

    List the 3D scenes saved in your 3dCMS account (editable "sources"). Returns each scene's id, name, page count and last-updated time. Use a scene id with get_scene and apply_ops.

  • probe_lightingFree

    For one world point, the contribution of EVERY light in the scene, computed from the saved scene (no render, instant): each light node in reach with its value (three's falloff and spot cone, from the node's WORLD position and aim), the studio sun or the live sky, and `ambient` = lighting.env.intensity NAMED as a term, sorted by contribution, plus a `why` on each light that does not reach (out of range, outside the cone, behind the panel). Pass `nodeId` for the surface's albedo: a pale albedo shows the ambient term 2-3x as strongly as a dark one. Use it BEFORE deleting lights to find a phantom glow: a surface no lamp reaches that still reads lit is lit by env.intensity (or the sky in time mode), and the answer is the settings op, not another lamp. Occlusion is not computed (a wall between does not reduce the number).

  • publish_scenewrite actionFree

    Publish a saved scene to a PUBLIC, shareable URL so anyone can view it in 3D in a browser — no login required. PUBLISHING IS PARTLY IRREVERSIBLE: the URL is submitted to search engines (IndexNow) the moment it goes live, so it may appear in results even after a takedown. ALWAYS confirm with the user first, then call with confirm:true — without it this tool publishes nothing and returns what WOULD happen. Returns the public site URL plus a per-page URL list. Re-publishing the SAME scene updates its existing public site (the URL stays stable).

Public scan report

scanner v0.1.9 · 2026-09-20 · same rubric, same numbers if you re-run it

1 high2 low
  • Code scanremote-only server, no package to scann/a
  • Live reliabilityremote reachable in 861ms20/20
  • Tool poisoning8 tool descriptions checked13/15
  • Auth qualityopen endpoint exposes 2 write-action tools with no auth3/15
  • Maintenanceno repository listed3/15
  • Maintainer identityverified namespace with website, no repo4/10

Findings (3)

  • highWrite-action tools reachable without authenticationauth.open-write
  • lowUnusually long tool description (over 2,000 characters)poison.long-description
    tool apply_ops: …Edit a scene by applying a batch of operations ATOMICALLY (all-or-nothing; one undo step in the editor). Call this for any add / move / edit / delete / duplicate / group / reparent / re-material. Reference existing nodes ONLY by the id shown in get_scene — never guess ids. To create a node and then modify or parent it in the same batch, put a `tempId` on the add op and use that same string as the id/parentId of later ops. Coordinates: Y up, units ≈ meters, ground at y≈0 — place objects so they rest sensibly. Geometry size lives in `data` (box {width,height,depth}; sphere/cylinder/torus {radius}; torus {tube}); the default box is 1.5×1.5×1.5. MATERIALS: to color an object pass `color` (#rrggbb) on add/assignMaterial; to apply a GENERATED material pass `materialMaps` (the map urls from get_generation) on add/assignMaterial; for a real surface with no image at all pass `materialPreset` (brushed-metal, frosted-ice, rough-stone, ... see the field) — all three mint-or-reuse a material and assign it, no pre-existing library entry needed. Prefer a preset or a set from list_assets over a flat color for walls, floors, trim and metal, so the first pass reads as a place, not a grey box. IMPORTED MODELS: a glb from list_assets (kind "glb") or get_generation goes on a geometry node as data: {primitive:"imported", assetUrl:"<url>"}, NOT as the top-level `primitive` (that enum is the five built-ins). Once placed, get_scene reports the model's bounds and size. The built-in low-poly picture frames face +X: to hang one on a wall that faces +Z rotate it [0, 1.5708, 0]. INSTANCING (971): identical PRIMITIVES are drawn as one batch too, keyed on the primitive, its data dimensions and segments, its material (color, materialMaps or materialPreset) and its room: 281 boxes that share one size and one material cost one draw call, and the weight meter credits it. Vary placement with position, rotation and the node SCALE (not data.width/height/depth) to stay in the batch; a different data size or material starts another. An animated, tapped or surface-emitter host node draws on its own. WEB BLOCKS (responsive site building-blocks): add webText (data.role h1|h2|h3|body|caption + data.text + data.fontWeight 400|500|600|700 for a real weighted face (a role already picks one); glyph outline via data.outline true|false + data.outlineColor + data.outlineWidth), webImage (data.src + data.alt), webLink (data.label + data.href or data.linkedPageId), webVideo (data.src + data.poster); any web block also accepts data.anchor = world|billboard|hud. PAGES & NEIGHBORHOODS: add a `webpage` node per page (set data.pageTitle, data.slug); place pages tens of meters apart and parent each area's content under its page to build a multi-area site. Link areas with a webLink (data.linkedPageId = target page, or data.linkedCameraId = a saved viewpoint); a tempId minted in the SAME batch works as linkedPageId, so a "sign in the park linking to the mall" can be built in one call. Set data.arrivalCameraId on a page (a camera node id, or a tempId from the same batch) to choose the viewpoint visitors ARRIVE at for it — otherwise the runtime auto-frames the page from a generic distance. It belongs on the PAGE, not on the signs: a sign that jumps to a camera skips the page change, so the URL and title would still name the previous room. MULTI-LEG ARRIVALS (969): a flight is a straight line between two poses, which cuts through a wall to reach a room. Set data.viaCameraIds on a CAMERA node (an ordered array of camera node ids, or same-batch tempIds, up to 8) and every flight to that camera (a page arrival through arrivalCameraId, a sign or tap with linkedCameraId, a #view deep link) passes through those viewpoints first on one smooth curve: place a via camera in the doorway, aimed into the room, and the visitor is threaded through the door. The flight takes longer in proportion to the path, the mid-flight bow is off on a legged flight, a via camera's own legs are not followed, and a via id that names no camera is skipped (get_scene warns). Wire a doorway in one batch: add the door camera with tempId "door", then the arrival camera with data.viaCameraIds ["door"]. PACING (1028): data.travelScale 0.25-4 on a camera node multiplies the viewport.cameraTravelScale of the scene for every flight that ENDS at that viewpoint (a page arrival, a sign, a #view link): 2 is a slow reveal into a hero room, 0.5 a quick hop. TAP ACTIONS: object nodes (geometry, text3d, group, cable) take data.tap = {"action":"inspect"} (fullscreen orbit lightbox on tap — products/exhibits/hero models), {"action":"moveTo"} (camera flies to frame it — tap-to-travel on landmarks), or {"action":"link", href|linkedPageId|linkedCameraId} (navigate like a webLink). inspect/moveTo take an optional "distance" (meters) overriding the auto camera distance. Runs for visitors in the published scene. ANIMATION: any node (geometry, text3d, group, cable, the web blocks and lights, and a camera, which makes it a RIDE, below) takes data.animation to move on a loop in the published scene: {"duration":4,"loop":"loop","easing":"easeInOut","tracks":{"position":[{"t":0,"v":[0,0,0]},{"t":0.5,"v":[0,0.6,0]},{"t":1,"v":[0,0,0]}]}} bobs the node half a meter. t is 0-1 across duration seconds; values are OFFSETS from the authored transform (position meters, rotation euler radians, scale a multiplier, 1 = authored size). tracks = position|rotation|scale; loop = loop|pingpong|once; optional delay seconds; max 16 keys per track. Animated nodes cost scene weight and cannot be GPU-instanced: animate a few focal objects, not everything. MOTION PATH (973): data.animation.path puts the node ON a curve instead of (or as well as) the tracks: {"duration":12,"loop":"loop","path":{"curveId":"<a nurbs node id, or its same-batch tempId>","facing":"tangent","forward":"z","up":[0,1,0]}}. facing = tangent (nose along the curve) | target (toward a world point, pass "target":[x,y,z]) | fixed (keep the authored rotation); forward = z|-z|x|-x is which local axis of the model leads (a glb whose nose points -Z wants "-z"); up is the world up the roll is kept against; offset 0..1 is where along the curve the ride starts (space several riders around one loop with different offsets); reverse:true rides it backwards; facingEase (seconds, default 1, 0 snaps) is how long the rider takes to TURN from the way it was placed into the facing of the path when the ride begins (and again if the heading jumps, such as the loop seam of an open curve), eased in and out. `easing` shapes the ride itself: linear is the default on a loop (constant speed, no braking at the seam) and easeInOut on once/pingpong (pull away, brake at the end). While bound, the authored position and rotation are NOT where the node is (it sits on the curve; scale still applies, tracks stack on top as offsets); a curveId that names no curve node leaves it at its authored place. The curve is a nurbs node: {"op":"add","nodeType":"nurbs","tempId":"track","data":{"controlPoints":[[0,0,0],[8,0,-4],[16,0,0],[8,0,4]],"closed":true}} then {"op":"add","nodeType":"geometry","primitive":"box","data":{"animation":{"duration":10,"path":{"curveId":"track"}}}}. get_scene reads a rider back as motionPath:<curveId>. A curve something rides is a HELPER on the published site (1032): registered for the ride, not drawn, so a swing arc does not show as a green spline across the door; set data.renderCurve:true on the curve to draw it anyway. The editor always shows it. CAMERA RIDE (1026): a CAMERA node with data.animation.path is a ride: every flight that lands on it (a page arrival through arrivalCameraId, a sign or tap with linkedCameraId, a #view link) ends at the curve's start pose and the visitor's camera then follows the curve, looking along it (facing tangent), at a world point (facing target + target) or the way it arrived (facing fixed); animation.duration, loop (once = ride to the end and hand control back there; loop and pingpong ride until the visitor takes over; on an OPEN curve loop cuts back to the start each lap, so use once or pingpong there or set closed:true on the curve), easing, path.offset and path.reverse mean what they mean for any rider (a pingpong ride looks where it is going on the way back too). data.rideBreak is the author's choice of what ends the ride early: "any" (default: any pointer down, key, wheel or touch), "move" (only a drag, a wheel or a movement key, so a tap on a sign still navigates and a stray click does not stop the ride) or "none" (nothing; a loop rides until the visitor navigates elsewhere). A new flight always ends a ride; a reduced-motion visitor and a screenshot get the start pose and no ride; the curve is a helper (not drawn) like any ridden curve. {"op":"add","nodeType":"nurbs","tempId":"rail","data":{"controlPoints":[[0,1.6,8],[0,1.6,2],[3,1.6,-2]]}},{"op":"add","nodeType":"camera","tempId":"arrive","data":{"animation":{"duration":8,"loop":"once","path":{"curveId":"rail"}},"rideBreak":"move"}} then arrivalCameraId "arrive" on the page. FAST PATH — make_page: for a page that is a heading + paragraph + some links, ONE `make_page` op builds the whole thing: pass name, data.pageTitle+data.slug, hero (h1 text), body (paragraph), and links [{label, to: a page id/tempId | href}], plus a tempId (which becomes the PAGE id so other signs can target it). Create link-target pages earlier in the batch so their tempIds resolve. It replaces ~6–12 primitive ops. SET-DRESSING: use the `scatter` op to make `count` jittered copies of a node across an {area:{x,z}} (random rotation + slight scale + optional per-copy `tint` for imported models) — efficient for many props, debris, foliage, a trashy parking lot. The result names what it CREATED (`created`: id, type, name) so you can edit those nodes in your next call without re-reading the scene. LARGE SCENES: build INCREMENTALLY across multiple apply_ops calls (~40–50 objects per batch) instead of one huge batch — each call is its own atomic Undo and stays well within limits; call get_scene between batches to stay aligned. Do not try to emit hundreds of operations in a single call. The result may carry `warnings`: advisory scene lint (floating content, orphan web blocks, unreadable text, crowded pages, a lamp whose range reaches the ceiling above it, a plane facing a shadow lamp with content behind it, ambient IBL in a sun-less studio scene, a page card inside a room, a camera ride looping an open curve, scene WEIGHT over the perf red line). A per-node warning you have read and disagree with goes quiet with data.lintMute:true on that node (every rule about one node honours it; scene-wide ones do not). When more than six fire, the last entry counts the rest. The ops APPLIED; act on warnings in your next batch unless they contradict what the user asked for. A weight warning names the heaviest categories: trim them (lower-poly models, fewer shadow lights/particles) or publish will warn and far heavier scenes are blocked.…
  • lowNo source repository listedmaint.no-repo
Overall 57/100. Components that don't apply are left out of the denominator. Any critical finding is an F.RubricAppeal a findingJSON

Install directly

claude mcp add --transport http mcp https://3dcms.info/mcp
Add to Cursor

3dCMS: common questions

Is 3dCMS MCP server safe?
With care: it is graded C, so read the findings first (57/100). Read the 3dCMS safety report
How do I install 3dCMS?
It runs remotely at 3dcms.info. Add it to Claude Code, Claude Desktop or Cursor with the snippets above, or call it through the mcp.market gateway without installing anything.
Does 3dCMS need an API key?
Not as far as the registry entry and our scan can tell: no credentials are declared or required.
Is 3dCMS maintained?
The latest release is v1.1.0.
Is 3dCMS up?
100% of our last 5 checks got an answer. We check remote servers about four times a day.

More from 3dcms.info