BumpMesh MCP
# BumpMesh MCP
A local MCP server that applies real image-driven displacement to STL/OBJ meshes using [CNC Kitchen's BumpMesh/stlTexturizer](https://github.com/CNCKitchen/stlTexturizer). No browser, WebGL, LLM calls, or runtime network downloads are involved.
The TypeScript adapter runs the upstream JavaScript computational core directly. Python or Rust would require a JavaScript subprocess or an algorithm port without a demonstrated benefit for this workload. Node keeps a single runtime and the original mesh algorithms. A worker thread keeps mesh processing off the MCP event loop; this is isolation and cancellation support, not a claim of faster geometry processing.
## Install and run
Requires Node.js 22+ and npm. Development was tested on macOS Apple Silicon. `sharp` uses a native image decoder with platform-specific prebuilt packages; a supported Node/platform combination is required.
```sh
git clone https://github.com/kolengri/bumpmesh-mcp.git
cd bumpmesh-mcp
npm ci
npm run build
mkdir -p files
node dist/index.js --root "$PWD/files"
```
The default transport is stdio. Waiting silently for MCP messages is expected. Diagnostics go to stderr; stdout is reserved for MCP.
```sh
npm test
npm run verify:vendor
npm run demo
```
The demo creates a synthetic cube, a grayscale texture, textured STL and 3MF, and a JSON report under a new `demo-output/<timestamp>` directory. It exercises the real stdio MCP protocol. No personal files are read or uploaded.
## Tools
| Tool | Purpose |
| --- | --- |
| `list_supported_formats` | Formats, limits, units, upstream version, and configured file root |
| `inspect_model` | Triangle count, bounds, degenerate triangles, open/non-manifold edge counts |
| `apply_texture` | Load, subdivide, displace, optionally decimate/repair, export, and return diagnostics |
Inputs: ASCII/binary STL and OBJ geometry. OBJ meshes are merged; materials, UVs, lines, and points are ignored. Textures: single-frame PNG, JPEG, WebP. Exports: binary STL and single-mesh 3MF in millimeters. STEP and 3MF import are deliberately outside this initial adapter, even though the browser application supports them.
Example `apply_texture` arguments (paths are relative to the configured root):
```json
{
"model_path": "housing.stl",
"texture_path": "height-map.png",
"output_path": "housing-textured.stl",
"projection": "triplanar",
"amplitude_mm": 0.3,
"tile_size_mm": 5,
"refine_length_mm": 0.4,
"max_triangles": 100000,
"exclude_bottom_deg": 5,
"exclude_top_deg": 5
}
```
### Parameter semantics
- Coordinates are interpreted as millimeters, with Z up. No scaling, recentering, orientation conversion, or automatic repair of the input is performed.
- `projection`: `planar_xy`, `planar_xz`, `planar_yz`, `cylindrical` (Z axis), `spherical`, `triplanar` (default), or `cubic`.
- `amplitude_mm`: signed displacement at white, default 0.3. Black is zero. Negative values engrave. `symmetric: true` instead maps brightness to [-0.5, +0.5] times amplitude. `invert: true` reverses brightness first.
- RGB textures are converted to 8-bit grayscale using weighted encoded sRGB channels (0.2126 R + 0.7152 G + 0.0722 B); alpha is ignored. Upstream samples the resulting red channel with tiled bilinear interpolation. This preserves upstream behavior for grayscale maps; color handling is explicitly defined by this adapter.
- `tile_size_mm`: nominal square repeat size, default 5. The core preserves image aspect ratio, so a non-square image produces a proportionally shorter tile on its short axis. `offset_u`/`offset_v` are texture cycles; `rotation_deg` rotates projection coordinates.
- `refine_length_mm`: target maximum edge length before displacement, default 0.5. Smaller values increase memory and work. `max_triangles` is a **soft output target after subdivision**, not a memory cap; masks and repair can exceed it.
- `exclude_bottom_deg` and `exclude_top_deg`: exclude faces whose normals are within the given angle of -Z/+Z. Zero disables each mask. Shared mask-boundary vertices are pinned by the core. `boundary_falloff_mm` tapers displacement near masked boundaries. This does not protect arbitrary holes, threads, or vertical mating surfaces.
- Regularization and flat-face harvesting are disabled in this minimal adapter. Decimation and upstream T-junction repair run only when the output exceeds the triangle target.
The schema returned by `tools/list` is authoritative for defaults and ranges. Results include the normalized parameters, before/after measurements, output path, byte count, SHA-256, and warnings. Files with degenerate triangles can be inspected but must be repaired before texturing.
## Codex connection
Build first. Replace both paths below with absolute paths on the machine where Codex runs. Use `command -v node` to locate Node if a desktop launcher has a different PATH.
```sh
codex mcp add bumpmesh -- /absolute/path/to/node /absolute/path/to/bumpmesh-mcp/dist/index.js --root /absolute/path/to/3d-files
codex mcp list
```
Equivalent scoped entry in `~/.codex/config.toml` (preserve all existing entries):
```toml
[mcp_servers.bumpmesh]
command = "/absolute/path/to/node"
args = ["/absolute/path/to/bumpmesh-mcp/dist/index.js", "--root", "/absolute/path/to/3d-files"]
startup_timeout_sec = 20
tool_timeout_sec = 150
```
The CLI command saves the server but does not prove the active task loaded it. Reload MCP connections or start a new task as required by your client. For a connection-only check, discover the three tools and call `list_supported_formats`; no model mutation is needed. The project tests separately prove successful mesh processing on disposable fixtures.
Official reference: [Codex MCP configuration](https://learn.chatgpt.com/docs/extend/mcp?surface=cli).
## ChatGPT / Streamable HTTP
This server also implements stateless Streamable HTTP:
```sh
BUMPMESH_ROOT=/absolute/path/to/3d-files node dist/index.js --http --port 3000
```
Endpoint: `http://127.0.0.1:3000/mcp`. It binds only to loopback, rejects browser Origin headers and unexpected Host headers, and supports optional `BUMPMESH_HTTP_TOKEN` bearer authentication. It is not an OAuth authorization server.
ChatGPT's web developer mode connects to **remote** SSE/Streamable HTTP endpoints, not this machine's stdio or localhost. For a real ChatGPT connection:
1. Run the HTTP server on the host holding the intended files, with a dedicated root folder.
2. Put it behind an HTTPS endpoint with an MCP-compatible OAuth gateway. The gateway must validate users, forward requests to loopback, rewrite Host to `127.0.0.1:3000`, and avoid forwarding browser Origin. It can inject `BUMPMESH_HTTP_TOKEN` as an upstream bearer token. Allow requests lasting at least 150 seconds. OAuth/gateway deployment is not included here.
3. Enable ChatGPT Developer mode in Settings, create an app for the public HTTPS `/mcp` endpoint, and configure OAuth for that gateway. The UI and account eligibility are described in the official link below. ChatGPT does not document a generic static-bearer connector mode; do not assume the server's token environment variable alone supplies ChatGPT authentication.
4. Select the app, discover tools, and call `list_supported_formats` to verify the root and formats. Files must already exist on the server host; ChatGPT attachments are not automatically synchronized. The current API returns paths and metadata, not downloadable attachments.
For a short disposable test, ChatGPT also supports No Authentication, but this would expose the selected folder's MCP operations to anyone who reaches the endpoint. Use only generated fixtures in an isolated environment, then stop the endpoint. No tunnel or public service is started by this project.
Local HTTP protocol behavior is covered by integration tests. A live ChatGPT connection, gateway authentication, and remote file delivery have not been tested or deployed.
Official reference: [ChatGPT Developer mode](https://developers.openai.com/api/docs/guides/developer-mode) (remote transports and OAuth/No Authentication).
## Agent skill
The instruction-only skill is [skills/bumpmesh-texturing/SKILL.md](skills/bumpmesh-texturing/SKILL.md). It covers projection/height semantics, fit boundaries, inspection, error recovery, and evidence limits. It needs this MCP server, not another runtime or paid API.
To install for Codex, copy its folder to your skill directory (do not overwrite an existing customized copy):
```sh
mkdir -p "${CODEX_HOME:-$HOME/.codex}/skills"
cp -R skills/bumpmesh-texturing "${CODEX_HOME:-$HOME/.codex}/skills/"
```
Reload skills/start a new task if required. Invoke `$bumpmesh-texturing` with the local model and height-map paths. Other MCP clients can use the same workflow as instructions; installing a local Codex skill does not automatically install it in ChatGPT web.
## Limits and failure behavior
- Required explicit file root; inputs and outputs must remain inside it, including resolved symlinks. Parent output folders must already exist. Existing files, including dangling symlinks, are never replaced.
- One mesh job at a time; concurrent jobs receive `BUSY`. Jobs run in a worker with a 120-second timeout and a 768 MiB V8 old-generation limit. Native allocations and typed-array buffers are not covered by that heap limit; this is not an OS sandbox or a total RAM guarantee.
- 32 MiB model files, 16 MiB texture files, 4096² decoded pixels, 100,000 input triangles. Subdivision progress is checked against 500,000 intermediate triangles; a subdivision iteration can allocate beyond the threshold before the callback aborts. Very large or adversarial inputs should use separate process/container memory limits.
- Output publication uses a same-directory temporary file and an exclusive hard link. Cancellation before publication removes the temporary file; cancellation after the atomic publication point cannot undo the completed output. Abrupt process termination can leave a `.bumpmesh-*.tmp` file. Local filesystems supporting hard links are required.
- `TIMEOUT`/`MESH_BUDGET_EXCEEDED`: increase `refine_length_mm`; reducing the final decimation target does not reduce subdivision work. `OUTPUT_EXISTS`: choose a new filename. `DEGENERATE_GEOMETRY`: repair the input.
- Edge diagnostics weld at 0.0001 mm and do not test self-intersection, orientation consistency, slicer toolpaths, printer behavior, or physical fit. Inspect output in a slicer before printing. Input OBJ scene details and source 3MF printer settings are not preserved.
## Source, license, and maintenance
Pinned upstream: [`a6ac179149b8a17c71a9469dd4cb6f866c0c01d1`](https://github.com/CNCKitchen/stlTexturizer/commit/a6ac179149b8a17c71a9469dd4cb6f866c0c01d1), inspected 2026-09-23 (upstream commit dated 2026-07-23, v1.2.0). [UPSTREAM.md](UPSTREAM.md) records the reused modules and exact adaptation.
This combined work is **AGPL-3.0-only**, retaining CNC Kitchen / Stefan Hermann and contributors' notices; see [LICENSE](LICENSE). The vendored source and adapter source are included. If distributing binaries or offering a modified network service, provide the corresponding source under the applicable AGPL terms, including its network-interaction source requirement. A private development repository is not itself a source offer to external service users.
Three.js, fflate, Zod, and the MCP SDK use MIT licenses. Sharp uses Apache-2.0 and bundles native components with their own notices (including libvips); retain dependency license notices when redistributing installed binaries.
`src/engine.ts` contains reusable domain logic, `src/server.ts` tools, `src/index.ts` transports, and `src/runner.ts` worker lifecycle. To update upstream, compare the selected modules, keep the pristine sources and license notices, regenerate `nodeExporter.js`, update the commit/hash manifest deliberately, and rerun geometry and protocol tests. Do not replace the core with an approximate reimplementation.
TDQS
Scored across 3 tools
Each tool has a clearly distinct role: listing supported formats/configuration, inspecting a model, and applying a texture operation. There is no overlap or ambiguity in their purposes.
All tool names follow a consistent snake_case verb-first pattern: list_supported_formats, inspect_model, apply_texture. The naming style is uniform and predictable.
Three tools is a well-scoped set for a focused single-purpose server. Each tool occupies a necessary step in the local mesh texturing workflow with no redundant additions.
The tool surface covers the full intended pipeline: discover supported formats, inspect the input model, and apply a heightmap texture with export options. There are no obvious dead ends or missing operations for the stated domain.