Skip to content

MCP server

Expose every meshio++ operation to AI agents over the Model Context Protocol: reading and writing 43 mesh formats, conversion, and the full mesh- and data-operation suite become tools any MCP client (Claude Code, Claude Desktop, the MCP inspector, …) can call.

bash
pip install "meshioplusplus[mcp]"          # the mcp SDK needs Python >= 3.10
claude mcp add meshioplusplus -- meshioplusplus-mcp

Then ask the agent things like "convert bracket.msh to VTU, report its quality, and slice it at z = 0.02" — it drives convert, quality and slice itself.

Every tool is stateless and file-path based: input path(s) in, output path(s) out, a strict-JSON report back. That mirrors the CLI, keeps arbitrarily large meshes out of the protocol, and lets the agent work in its own filesystem workspace. Nothing here is part of the C++ core, which stays dependency-free.

Installation

ExtraBringsFor
meshioplusplus[mcp]the official MCP Python SDK (MIT)the meshioplusplus-mcp server

This extra is not in meshioplusplus[all][all] means "the optional dependencies the formats need", the same reasoning as [interop]/[viewer]. Note the mcp SDK itself requires Python ≥ 3.10 while meshio++ supports 3.8: the package (and the server's pure tool layer) works everywhere, only running the server needs the newer Python.

Without the extra, everything degrades by name rather than by traceback:

$ meshioplusplus-mcp
meshio++: mcp server: mcp is not installed; install it with
`pip install meshioplusplus[mcp]` (requires Python >= 3.10)

meshioplusplus.mcp.has_mcp() answers the same question without raising.

Running the server

The server speaks stdio (the standard local-agent transport):

bash
meshioplusplus-mcp                          # unrestricted paths
meshioplusplus-mcp --root /path/to/work     # sandboxed (recommended)
python -m meshioplusplus.mcp                # equivalent

Claude Desktop (claude_desktop_config.json):

json
{
  "mcpServers": {
    "meshioplusplus": {
      "command": "meshioplusplus-mcp",
      "args": ["--root", "/path/to/workspace"]
    }
  }
}

Interactive browsing: npx @modelcontextprotocol/inspector meshioplusplus-mcp.

Path sandbox

By default paths are unrestricted — the server runs locally under your own account and MCP clients gate filesystem access themselves. Pass --root DIR (or set MESHIOPLUSPLUS_MCP_ROOT) to confine every input and output path: each path is realpath-resolved (symlinks included) and must stay inside the root; relative paths resolve against it. Violations come back as clean {"error": ..., "error_type": "ValueError"} payloads the agent can act on — no tool ever surfaces a raw traceback.

Tools

57 tools; the three marked gated need a further extra and return a named install error without it. Transforming tools take input_path/output_path (+ optional input_format/output_format, otherwise inferred from the extension) and return the written path plus a mesh summary and the operation's report.

Inspection (read-only)

ToolReturns
formatsreadable/writable format lists + extension map (also the meshioplusplus://formats resource)
sniffformat identified from leading bytes + extension
infofast file summary via read_metadata — counts, cell blocks, data names, regions, time steps
statsbbox, centroid, areas/volumes, per-type counts, inverted cells
qualityper-metric summaries + histograms; pass output_path to write the mesh with quality:<metric> cell data
data_infoevery data array's dtype/shape/ranges/NaN counts
data_integratecell-measure-weighted total/mean of one or more cell_data arrays, whole-mesh and per named Cell region
regionsnamed point/cell/side groups with kind/dim/tag and an entries preview
bandwidthnode-numbering bandwidth
data_previewa bounded window (offset/limit) of one data array's values
diffverdict + equal boolean + per-section detail for two files

Conversion

ToolNotes
convertany-to-any format conversion; points_only/arrays/time_step narrow the read; mode: ascii|binary and compression: zlib|lz4|zstd|lzma|gzip|none subsume the CLI's ascii/binary/compress/decompress verbs
pipelinerun a whole settings pipeline (settings_path; read → operation chain → write, PascalCase ops/keys); input_path/output_path override the document's paths, and the sandbox root covers the paths inside the settings file, not just the file itself
sequencerun a multi-file / transient sequence (input_pattern or input_paths, output_path; optional mode/times/time_from). A {step}/{index} token in output_path writes one file per step (fan-out); a plain path writes one multi-step file (fan-in, XDMF only — anything else fails by name rather than keeping step 0). Ordering is natural-numeric, so out_9 precedes out_10. A pattern's directory component is containment-checked against the sandbox root before it is expanded, and every matched file is re-checked individually

Mesh operations

extract_surface, extract_skin, reorder (reports bandwidth before/after), clean, crop (bbox, half-space, or a where_array/where_compare/where_value cell_data predicate), slice, isosurface, compute_sdf (a grid over a surface, filled — structure voxel/octree), transform, convert_cells, subdivide (polyhedral refinement: one polyhedral child per 3D cell face, connected to a new interior point — no per-type template table, unlike refine; see subdivide), agglomerate (polyhedral coarsening: merge groups of cells into single larger polyhedral cells via greedy seed-and-grow over the shared-face dual; see agglomerate), refine (uniform, or a subset via cells/region/where with a conforming closure; record_hierarchy attaches the persistent refine:cell_id/refine:parent_id parent/child hierarchy a multigrid caller resolves across the sequence of meshes it keeps — see refine), undo_green (restores a refine transitional/green cell to its coarse parent, read verbatim from coarse_path — a two-mesh tool like interpolate; reports num_groups_undone/num_cells_removed — see green-element undo), decimate, decimate_volume (the volume-mesh sibling of decimate: quadric-error tet-edge collapse; preserve_boundary defaults False, the opposite of decimate's own default, since boundary vertices participate by real quadric error here — see volume decimation), remesh (replaces a surface's triangulation with a new, well-shaped one at a chosen vertex count by ACVD clustering — the output has NO correspondence to the input, point_data/cell_data/named regions are dropped; see remesh), remesh_volume (the volumetric sibling of remesh: retetrahedralizes a volume mesh or closed surface at a chosen resolution by isosurface stuffing over a BCC lattice — same no-correspondence output contract, plus warp_fraction trading boundary tet quality for a small, measured chance of non-manifold boundary edges reported as num_non_manifold_edges; see remesh_volume), optimize_volume (ODT-remeshes a tetrahedral mesh: raises its worst element quality by relocating vertices AND flipping connectivity (2-3/3-2, predicate-free) — the point set is invariant so point data and Point regions carry, cell data and Cell/Side regions are dropped; tet-only and C++-core-only; see optimize_volume), smooth (method taubin/laplacian/odtodt, optimal-Delaunay-triangulation smoothing, is tet-only and C++-core-only with no pure-Python fallback), merge (N inputs), split (one file per piece, name_template), partition (one file per part), interpolate (source → target field transfer), conservative_interpolate (mass-preserving overlap-measure weighted transfer — unlike interpolate, an unset arrays covers every source point_data AND cell_data array; see conservative interpolation), gradient (the gradient, divergence or curl of a point_data field — see field derivatives; reports num_skipped and num_fallback), hessian (the Hessian, second derivative, of a scalar point_data field — a composition of two gradient calls, gradient's companion one order further; see second derivatives; reports num_skipped and num_fallback), estimate_error (the Zienkiewicz-Zhu recovery-based error indicator of a point_data field, plus optional absolute/fraction/dorfler marking into error:marked for refine's own where selector — see error estimation; reports global_error, num_skipped and num_marked). Parameters mirror the Python API / CLI one-to-one; operations that produce reports (clean, decimate, decimate_volume, remesh, remesh_volume, optimize_volume, smooth, gradient, hessian, estimate_error) include them in the response.

Data operations

ToolNotes
data_managekeep/drop/rename arrays: keep/drop are [location, name] pairs, rename is [location, old, new] triples
data_convertaverage between locations (direction: point_to_cell | cell_to_point)
data_calcexpression evaluator; accepts the CLI's "NAME = EXPR" spelling
data_conditionclamp / normalize / standardize

Dataset manifests

ToolNotes
dataset_addadd a case to a dataset manifest (created if absent): input_pattern or input_paths (the sequence tool's shape, same sandboxed glob handling), optional entry_id/split/tags/group/notes/metadata; the source is validated now and stored relative to the manifest's directory
dataset_lista manifest's entries, filtered by split/tags (must carry all)/group (path or descendant); resolve: true also expands each entry's file/step/time plan — every resolved path is containment-checked, since a hand-edited manifest is client input too
dataset_updatecurate: split on entry_ids/all_entries, assign_splits by fractions (deterministic via seed; by_group keeps groups together), add_tags/remove_tags, or one entry's group/notes/metadata

Gated

ToolExtraNotes
data_export[arrow]data arrays → Parquet table
export_dataset[arrow] ([zarr]/h5py for those layouts)a set of meshes → one mesh_id-keyed dataset (hive Parquet / zarr / hdf5; see ML data handling)
screenshot[viewer]off-screen PNG render, returned as MCP image content

Reports are strict JSON

Every response survives json.dumps(..., allow_nan=False): numpy scalars and arrays are converted, NaN/±Inf become null (with a non_finite_replaced count so the loss is visible), and any array longer than 1000 elements is replaced by a {"truncated": true, "size", "shape", "dtype", "preview"} wrapper — reports stay agent-sized no matter how large the mesh.

Architecture: the pure payload layer

src/python/meshioplusplus/mcp/ is split exactly the way _interop.py is, and for the same reason. Everything a tool does lives in the pure layer _tools.py — imports only meshioplusplus + numpy + stdlib, runs on every supported Python, tested in the default CI matrix with the mcp SDK absent. The FastMCP layer _server.py contributes only typed signatures (the JSON schemas) and docstrings (the tool descriptions), and is the one module importing the SDK.

_tools.TOOL_REGISTRY is the single source of truth: the server registers from it, and the parity guard in tests/python/test_mcp.py asserts every public operation in meshioplusplus.__all__ is claimed by some tool — a new operation fails CI until it gets a tool (or a conscious exemption). That test is what keeps this page's tool table honest as the library grows.

Released under the MIT License.