Skip to main content
Glama

hidim-mcp

A local MCP server ("connector") that lets Claude work with high-dimensional data without the data ever entering the conversation.

The model cannot read a 10,000 x 512 matrix; it reads text and images. So the data stays inside this server process and Claude gets back only what fits through the straw:

Claude asks for

hidim returns

"what is this dataset?"

schema, per-feature stats, missingness, identifier / label-like columns, duplicated features, effective rank, PCA variance curve on both raw and standardized scales, the number of factors above the Marchenko-Pastur noise edge, intrinsic dimension (TwoNN + MLE), distance-concentration and hubness diagnostics, and one consistent hint

"clean it up first"

transform (log1p, z-score with clipping, winsorize, returns, deseasonalize, residualize, row normalization, ...) and tensor_transform (anomalies / climatology, area weights, differences) as new datasets, nothing in place

"show me the structure"

an embedding (PCA / t-SNE / UMAP / Isomap / LLE / MDS / spectral / kernel PCA / random projection) stored server-side, with trustworthiness and explained variance, then a PNG scatter it can actually look at

"which groups are there?"

k-means / GMM / DBSCAN / HDBSCAN / agglomerative labels, silhouette, centroid signatures

"what is near this point?"

k nearest neighbours with distances and context

"turn this long table into a cube"

a labelled N-d tensor (e.g. date x strike x maturity), readable through budgeted slices and reductions, plottable as heatmap / 3-D surface / lines, saved and reloaded as .npz with dims and coordinates intact

Every text result is capped (24k chars by default; tables 200 rows x 60 cols x 4000 cells) so a careless call cannot flood the context.

Scaling: the one default that matters

Most tools take standardize, and since 0.2.0 it defaults to "auto":

  • data whose columns share one unit (pixel intensities, read counts, [0, 1] images, spectra) is analysed on its raw scale. Z-scoring such data turns near-constant columns (border pixels, rare genes) into dominant features, inflates the condition number and roughly doubles the PCA rank (MNIST: 183 components for 90% standardized vs 86 raw);

  • heterogeneous columns (prices next to volumes next to ratios) are standardized;

  • every result says which scaling it used, describe prints the PCA counts on both scales, and clip_z=10 clips z-scores so that sparse or near-constant features cannot hijack k-means or kNN (True / False force either choice).

describe also reports how many correlation eigenvalues sit above the Marchenko-Pastur noise edge. When that number is small and the "components for 90% variance" count is large, the extra rank is noise (a return panel has 3-4 real factors and 30 noise eigenvalues), and the hint says so instead of recommending t-SNE on noise.

Related MCP server: XLMCP

Install

Python 3.10+. Required: mcp, numpy, pandas, scipy, scikit-learn, matplotlib. Optional: umap-learn (UMAP), pyarrow (parquet/feather), openpyxl (xlsx), h5py (HDF5), xarray + netCDF4 (NetCDF/Zarr).

cd hidim-mcp
python3 -m venv .venv && source .venv/bin/activate        # or: uv venv && source .venv/bin/activate
pip install -e ".[all]"                                     # or: pip install -e .   (core only)
python -m hidim_mcp.server --help 2>/dev/null; python tests/stdio_smoke.py   # end-to-end check (~20 s)

Works with both mcp 1.x (FastMCP) and 2.x (MCPServer).

Connect to Claude Desktop

The short way (merges the entry into your existing config, backs the file up first, refuses to touch invalid JSON):

.venv/bin/python register.py                       # add --roots ~/data:~/research --out ~/hidim_out to change defaults
.venv/bin/python register.py --dry-run             # preview; --remove takes it out again

The manual way:

  1. Claude menu (menu bar) -> Settings... -> Developer -> Edit Config. On macOS this reveals ~/Library/Application Support/Claude/claude_desktop_config.json in Finder (Windows: %APPDATA%\Claude\claude_desktop_config.json); open it in a plain-text editor (straight quotes only; TextEdit's smart quotes break JSON).

  2. Merge the "hidim" entry into the existing mcpServers object (do not paste a second mcpServers key). Use absolute paths; the interpreter must be the one where you installed the package.

{
  "mcpServers": {
    "hidim": {
      "command": "/Users/you/hidim-mcp/.venv/bin/python",
      "args": ["-m", "hidim_mcp.server"],
      "env": {
        "HIDIM_ROOTS": "/Users/you/data:/Users/you/research",
        "HIDIM_OUT": "/Users/you/hidim_out"
      }
    }
  }
}
  1. Quit Claude Desktop completely (Cmd+Q) and reopen it. The tools appear under the "+" menu -> Connectors -> hidim.

  2. If it does not connect: tail -f ~/Library/Logs/Claude/mcp-server-hidim.log shows the server's stderr, and running the command + args by hand in a terminal shows import errors. The config file must be valid JSON.

Connect to Claude Code

claude mcp add --transport stdio --scope user hidim \
  -e HIDIM_ROOTS=/Users/you/data -e HIDIM_OUT=/Users/you/hidim_out \
  -- /Users/you/hidim-mcp/.venv/bin/python -m hidim_mcp.server

Everything after -- is the server command. Claude Code warns above 10k tokens of tool output and truncates at 25k by default; hidim's own caps keep well under that.

Configuration (environment variables)

Variable

Default

Meaning

HIDIM_ROOTS

home directory

os.pathsep-separated directories the server may read from / write to

HIDIM_OUT

~/hidim_out

where PNGs and exports go (always allowed)

HIDIM_MAX_CHARS

24000

cap on any text result

HIDIM_MAX_ROWS / HIDIM_MAX_COLS / HIDIM_MAX_CELLS

200 / 60 / 4000

table rendering caps

HIDIM_MAX_TENSOR_BYTES

2e9

refuse to build larger tensors

HIDIM_PLOT_MAX_POINTS

20000

scatter/pairs sample size

HIDIM_MAX_CLASSES

20

legend entries before rarer classes fold into 'Other'

HIDIM_MAX_ANNOTATIONS

300

points labelled by plot(annotate=...)

HIDIM_MAX_PNG_BYTES

700000

re-render at lower dpi above this

HIDIM_ALLOW_PICKLE

unset

set to 1 to allow .pkl / pickled .npy (can execute code: only for your own files)

Tools

Tool

What it does

list_datasets

what is in memory (name, kind, shape, memory, parent)

load_dataset(path, name, format, options)

csv tsv txt parquet feather json jsonl xlsx npy npz mat h5 nc zarr -> table or tensor; returns only a schema

save_dataset(name, path, format)

tables: csv parquet feather json npy xlsx mat; tensors: npz (round-trips dims+coords), npy, nc, mat

drop_dataset(name)

free memory

make_synthetic(kind, n, d, k, noise, seed)

blobs, swiss_roll, s_curve, gaussian, lowrank, options_surface

describe(name, standardize="auto")

the diagnostic profile described above (tables and tensors)

set_labels(name, columns)

declare integer class codes / years / hours / lat-lon / ids as labels so they leave the feature matrix

transform(name, op, columns, params)

log1p, log, sqrt, zscore (clip), minmax, clip, winsorize, rank, demean, detrend, diff, pct_change, log_return, cumsum, row_normalize, center_rows, deseasonalize, residualize, dropna -> new table

subset(name, where, columns, top_variance, sample, new_name)

pandas-query filter, column selection, sampling; the only way to read raw rows

`neighbors(name, index

point, k, metric)`

project(name, method, n_components, standardize, clip_z, ...)

pca svd ica random kpca tsne umap isomap lle mds spectral -> stored embedding + metrics

loadings(name, component, top, all_features)

full loading vector of a PCA component (level / slope / curvature shapes)

cluster(name, method, k, standardize, clip_z, k_max)

kmeans gmm dbscan hdbscan agglomerative -> label column + metrics; auto-k shows the whole silhouette-by-k table

tensorize(name, axes, values, agg, order, sort)

long table -> labelled N-d tensor; natural sort of text axes, explicit order per axis

tensor_from_wide(name, sep, dims, sort, order)

AAPL_close, MSFT_close, ... columns -> index x series x feature tensor, column order kept

tensor_transform(name, op, dim, params)

log1p, demean, zscore, diff, pct_change, log_return, cumsum, normalize, anomaly (climatology), area_weight, fillna -> new tensor

tensor_to_table(name, row_dim)

flatten time x lat x lon into time rows x gridpoint columns for PCA / EOF analysis

stack_datasets(names, axis_name)

same-shaped tables/tensors -> one tensor with a new leading axis

slice_tensor(name, select, reduce, new_name)

read cells: exact / nearest / range / isin / positions / stride selection, NaN-aware reductions

plot(name, kind, ..., annotate, order, log_axes)

PNG returned inline: scatter (2-D/3-D, legends up to 20 classes, colour bars for numeric and datetime columns, point labels), pairs, parallel, corr (labelled, clustered order), heatmap, scree, hist, andrews; tensor, surface (true axis values), lines

A typical session

make_synthetic(kind="blobs", n=5000, d=128, k=6)        -> 'synthetic_blobs'
describe("synthetic_blobs")                              -> rank 5 explains 90%, intrinsic dim ~40, hubness high ...
project("synthetic_blobs", method="tsne")                -> 'synthetic_blobs__tsne2', trustworthiness 0.96
cluster("synthetic_blobs__tsne2", method="hdbscan")      -> 6 clusters, silhouette 0.81, labels written back to parent
plot("synthetic_blobs__tsne2", kind="scatter", color_by="cluster_hdbscan")   -> picture
neighbors("synthetic_blobs", index=17, k=5)              -> who sits next to row 17 in 128-D

load_dataset("~/data/option_quotes.parquet", name="q")
tensorize("q", axes=["date","strike","expiry"], values=["iv","volume"])      -> 'q__tensor' date x strike x expiry x variable
slice_tensor("q__tensor", select={"variable":"iv","date":"2024-03-15"})      -> the surface for one day as a table
plot("q__tensor", kind="surface", select={"variable":"iv","date":"2024-03-15"}, log_axes=["expiry"])
plot("q__tensor", kind="lines", select={"variable":"iv","strike":{"nearest":100}}, series_dim="expiry")
tensor_transform("q__tensor", op="diff", dim="date", new_name="dq")          -> daily surface changes
tensor_to_table("dq", row_dim="date", select={"variable":"iv"}, new_name="dq_rows")
project("dq_rows", method="pca", n_components=3, standardize=False)          -> the level / term / skew modes of the surface
save_dataset("q__tensor", "~/data/iv_cube.npz")          -> reloadable with load_dataset

load_dataset("~/data/sst.mnmean.nc", name="sst", options={"key": "sst"})     -> tensor time x lat x lon
tensor_transform("sst", op="anomaly", dim="time", params={"period": "month"}) -> seasonal cycle removed
tensor_transform("sst__anomaly", op="area_weight", dim="lat")                 -> cos(lat)^0.5 weights
tensor_to_table("sst__anomaly__area_weight", row_dim="time", select={"lat": {"range": [-30, 30]}, "lon": {"range": [120, 290]}})
project(<that table>, method="pca", n_components=3, standardize=False)        -> EOF1 is ENSO

What it deliberately does not do

  • It never returns a whole dataset. subset previews are capped; slice_tensor thins anything over the cell budget and says so.

  • Memory lives in the server process. Restarting Claude Desktop restarts the server and empties it: save_dataset what you want to keep.

  • t-SNE, Isomap, LLE, spectral and MDS run on random samples by default (they are O(n^2)); PCA, SVD, UMAP and random projection scale to the full table.

  • It is a local tool for your own data. subset(where=...) is evaluated by pandas' query engine, which is an expression language, not a security sandbox; the model is the caller, so do not expose this server over HTTP to clients you do not trust, and keep HIDIM_ROOTS to directories you are happy to have read and written.

Provenance

Idea and direction: Daniel Margolis. Design and implementation: written with Claude (Anthropic) in a single working session, November 2026, then run end to end over stdio against Claude Desktop. Version 0.2.0 followed a test battery of eleven real datasets with published answers (Treasury yield curve, SPX/SPY option surfaces, Fama-French 49 industries, sklearn digits and MNIST, UCI HAR, PBMC 3k single-cell RNA-seq, GloVe word vectors, Golub leukemia and Olivetti faces, Indian Pines and Pavia University hyperspectral cubes, NOAA ERSST sea-surface temperature, NYC taxi trips); the numbers matched independent scikit-learn runs everywhere, and every defect that battery exposed in the defaults, hints and plots is fixed here (see CHANGELOG.md). The intrinsic-dimension estimators follow Facco et al. (2017, TwoNN) and Levina & Bickel (2004, MLE); the concentration diagnostic follows Beyer et al. (1999); hubness follows Radovanovic et al. (2010); trustworthiness is scikit-learn's implementation of Venna & Kaski (2001).

Development

python -m pytest tests -q        # unit tests of the engine (scaling, Marchenko-Pastur, hints, transforms, ordering, plots)
python tests/stdio_smoke.py     # speaks JSON-RPC over stdio to a real server process and exercises every tool

The engine (hidim_mcp/engine.py) has no MCP dependency and can be driven directly from Python or a notebook.

Available Tools

20 tools
clusterA

Cluster a table (or an embedding) and add a label column (cluster_) usable as color_by in plot. If run on an embedding, labels are also written back to the parent table. methods: kmeans (k omitted -> silhouette over 2..k_max, with the whole silhouette-by-k table, the Calinski-Harabasz choice and the finest k within 10% of the best, because silhouette favours coarse splits), gmm (k by BIC), dbscan (eps from 5-NN distances), hdbscan, agglomerative. standardize 'auto'/True/False and clip_z as in project. Returns sizes (with a warning when clusters hold <0.5% of rows: outliers, not groups), silhouette, Davies-Bouldin and each cluster's centroid signature (features most displaced from the global mean, in SD units).

Inputs: name (required), k, seed, k_max, +6 more. Returns: result

describeC

Budgeted profile of a dataset. For tables: schema, label columns, identifier-like and label-like integer columns, per-feature stats, missingness, constant and duplicated features, plus high-dimensional geometry on a sample: strongest correlations, effective rank, condition number, PCA components needed for 50/80/90/95/99% variance (on the chosen scaling AND on the other one), the number of correlation eigenvalues above the Marchenko-Pastur noise edge (how many factors carry more than noise), intrinsic dimension (TwoNN and MLE), distance concentration and hubness. standardize: 'auto' (default) keeps raw scales for data in one shared unit such as pixels / counts / [0,1] intensities and z-scores heterogeneous columns; True/False force it. For tensors: dims, coordinate ranges, fill rate per axis, value distribution. Ends with one consistent hint on what to do next.

Inputs: name (required), seed, sample, standardize, +1 more. Returns: result

drop_datasetC

Remove a dataset from memory.

Inputs: name (required). Returns: result

list_datasetsA

List datasets currently held in memory: name, kind (table|tensor), shape, memory, parent, how they were made.

Inputs: no named parameters. Returns: result

load_datasetA

Load a file into memory as a table (rows x columns) or tensor (N-d labelled array). Returns a schema summary, never the data.

Formats by extension (or format): csv tsv txt parquet feather json jsonl xlsx npy npz mat h5/hdf5 nc zarr. options: pandas/reader keyword arguments, e.g. {"sep": ";", "index_col": 0, "parse_dates": ["date"]}, {"sheet": "Sheet2"}, {"key": "array_name"} for npz/mat/h5/nc with several arrays. Relative paths resolve against the first HIDIM_ROOTS entry (home by default). npz files written by save_dataset come back as tensors with their dims and coordinates intact.

Inputs: path (required), name, format, options. Returns: result

loadingsA

Loading vector of one principal component of a PCA embedding made by project(method='pca'): the strongest positive and negative features, or the whole vector in feature order (all_features=True) to read shapes such as level/slope/curvature.

Inputs: name (required), top, component, all_features. Returns: result

make_syntheticB

Create a synthetic dataset for demos and sanity checks.

kinds: blobs (k Gaussian clusters in d dims, label column), swiss_roll / s_curve (2-D manifold embedded in d dims, colour column t), gaussian (iid noise: the null model for the diagnostics), lowrank (rank-k matrix plus noise), options_surface (long table date x strike x maturity_days -> iv, spot, volume; for tensorize demos).

Inputs: d, k, n, kind, +3 more. Returns: result

neighborsA

k nearest neighbours of one row (index = row label or integer position) or of an explicit point (vector in feature order), in the original high-dimensional space. Returns distances, positions and a few columns of each neighbour, plus how those distances compare with the distance to a random row (relative contrast). metric: any scikit-learn metric (euclidean, cosine, manhattan, ...); cosine is usually the right choice for embeddings and other hub-prone spaces. standardize: False (default), True or 'auto'.

Inputs: name (required), k, index, point, +4 more. Returns: result

plotA

Render a PNG picture (returned as an image you can look at; also saved under HIDIM_OUT) with a one-line caption.

Tables: scatter (x,y[,z]; defaults to the first components of an embedding; color_by a label/cluster/numeric/datetime column, parent columns of an embedding work too: categories get a legend of up to 20 classes, numeric and datetime columns a colour bar; annotate= writes that column's value next to each point, up to 300 points), pairs (scatter matrix of <=6 features), parallel (parallel coordinates, min-max scaled), corr (correlation heatmap with feature labels, reordered by hierarchical clustering so correlated blocks are visible; order='given' keeps table order), heatmap (raw value matrix rows x columns), scree (PCA variance curve, auto scaling), hist (small-multiple histograms), andrews (Andrews curves by class). Tensors: tensor (heatmap of a 2-D slice), surface (3-D surface of a 2-D slice; numeric and datetime axes are drawn at their true values, log_axes=['maturity_days'] draws a dim on a log10 scale), lines (1-D slices over one dim, up to 12 series from series_dim; use select={'dim': {'isin': [...]}} to pick). Use select/reduce exactly as in slice_tensor to get down to 2 dims (or 1-2 for lines). Large tables are randomly sampled to HIDIM_PLOT_MAX_POINTS (20000) points; categorical colours beyond HIDIM_MAX_CLASSES (20) fold into 'Other'.

Inputs: name (required), x, y, z, +15 more.

projectA

Reduce a table's numeric features to n_components and store the embedding as a new table (name__method) with label, datetime and cluster columns carried over for colouring. Returns quality metrics, not coordinates.

methods: pca (explained variance, top loadings; full vectors via loadings()), svd, ica, random (Gaussian random projection), kpca, tsne, umap (needs umap-learn), isomap, lle, mds, spectral. Non-linear methods run on a random sample (default caps: tsne 5000, umap 50000, isomap/lle/spectral 5000, mds 2000; override with sample) and get an automatic PCA to 50 dims first when there are more than 50 features (pre_pca). params passes method-specific keywords (perplexity, n_neighbors, min_dist, kernel, metric, ...). standardize: 'auto' (default; raw scales for pixel/count/[0,1] data, z-scores otherwise), True or False. clip_z=10 clips z-scores so near-constant or sparse features cannot dominate. Every method reports trustworthiness (1.0 = neighbourhoods perfectly preserved). ascii=True adds a coarse text density map.

Inputs: name (required), seed, ascii, clip_z, +8 more. Returns: result

save_datasetB

Write a dataset to disk under HIDIM_ROOTS. Tables: csv parquet feather json npy xlsx mat. Tensors: npz (keeps dims+coords, reloadable), npy, nc (needs xarray), mat.

Inputs: name (required), path (required), format. Returns: result

set_labelsA

Declare columns of a table as labels / coordinates (integer class codes, year, hour, lat, lon, ids): they are excluded from the feature matrix used by describe/project/cluster/neighbors and stay available for color_by and annotate. unset=True reverses it.

Inputs: name (required), columns (required), unset. Returns: result

slice_tensorA

Read part of a tensor. select maps dim -> scalar (exact, or nearest for numeric/datetime axes), list (several values), {"range": [lo, hi]}, {"isin": [...]}, {"nearest": v}, {"iloc": [start, stop]}, {"positions": [...]}, {"every": n}. reduce maps dim -> mean|std|var|sum|min|max|median|count|first|last (NaN-aware). Scalar selections drop the dim. When 1 or 2 dims remain the values are returned as a table (evenly thinned if over max_cells, default 4000); with more dims you get summary stats and should select/reduce further. new_name stores the result (2-D or less becomes a table that describe/project/plot accept; otherwise a tensor).

Inputs: name (required), reduce, select, new_name, +1 more. Returns: result

stack_datasetsA

Stack several same-shaped tables (their numeric blocks) or tensors along a new leading dimension, e.g. one CSV per ticker -> ticker x row x column. Shapes and column sets must match exactly (use subset/tensorize to align first).

Inputs: names (required), labels, new_name, axis_name. Returns: result

subsetA

Filter / select / sample rows and columns of a table; preview a few rows; optionally store the result as new_name.

where: pandas query string, e.g. "iv > 0.3 and maturity_days <= 30" (backtick-quote odd column names). columns: list of names, "re:", or "top_variance:". top_variance keeps the k highest-variance numeric features. This is the only way to read raw rows; preview is capped (200 rows x 60 cols).

Inputs: name (required), seed, where, sample, +6 more. Returns: result

tensor_from_wideB

Turn a wide table whose column names encode several keys (AAPL_close, AAPL_volume, MSFT_close, ...) into a tensor index x dims..., splitting column names on sep. dims default to ['series', 'feature']. The wide table's column order is kept for the name-derived dims (so a tenor axis 1m,3m,6m,1y,2y stays in that order); sort=True sorts naturally instead, order={dim: [...]} pins an explicit order.

Inputs: name (required), sep, dims, sort, +3 more. Returns: result

tensorizeA

Condense a long/tidy table into a labelled N-d tensor: each column in axes becomes a dimension and each value column fills the cells; several value columns add a trailing 'variable' dimension. Coordinates: numeric/datetime axes sort numerically, text axes sort naturally (b2 before b10); sort=False keeps first-appearance order; order={'tenor': ['1m','3m','1y']} pins an explicit order per axis. Duplicate axis combinations are aggregated with agg (mean|sum|first|last|max|min|median|count); missing combinations become NaN. Note that a later reduce 'mean' over a dim averages cells, not underlying rows: to get a row-weighted mean, tensorize agg='sum' and agg='count' and divide. Example: tensorize('opts', axes=['date','strike','maturity_days'], values=['iv']) -> a date x strike x maturity volatility cube.

Inputs: axes (required), name (required), agg, sort, +3 more. Returns: result

tensor_to_tableB

Flatten a tensor into a table: one row per coordinate of row_dim, one feature column per combination of the other dims (time x lat x lon -> time rows x gridpoint columns). All-NaN columns are dropped; the row coordinate becomes a label column. select/reduce (as in slice_tensor) are applied first. Then describe/project/cluster it like any table.

Inputs: name (required), row_dim (required), reduce, select, +1 more. Returns: result

tensor_transformA

Preprocess a tensor into a new tensor. Elementwise: log1p, log (params offset), sqrt, abs, fillna (params value, or dim for a mean fill). Along dim: demean, zscore, diff / pct_change / log_return (params periods; the axis shrinks), cumsum, normalize (params target). anomaly (dim = a datetime axis, params period=month|dayofyear|dayofweek|hour|quarter, baseline=[start,end]) subtracts the per-period climatology -> anomalies. area_weight (dim = latitude axis in degrees, params power: 0.5 for PCA/EOF, 1 for means) multiplies by cos(lat)^power. Typical EOF chain: tensor_transform(op='anomaly', dim='time') -> tensor_transform(op='area_weight', dim='lat') -> tensor_to_table(row_dim='time') -> project(pca, standardize=False).

Inputs: op (required), name (required), dim, params, +1 more. Returns: result

transformB

Preprocess the numeric features of a table into a new table (labels, ids and datetimes carried over unchanged).

ops: log1p, log (params offset), sqrt, abs, clip (lo/hi or z), winsorize (q), rank, zscore (params clip), minmax, demean, detrend (params by), diff / pct_change / log_return / cumsum along rows (params periods, by=, group_by, dropna), row_normalize (params target; library-size normalization for counts), center_rows (subtract each row's mean: removes the equal-weight market / common mode of a panel), deseasonalize (params by=, period=month|dayofweek|hour|dayofyear|quarter: subtracts per-period means -> anomalies), residualize (params on=[cols]: regress every feature on those columns and keep residuals, e.g. remove a market factor), dropna. Typical chains: counts -> row_normalize -> log1p -> project(clip_z=10); prices -> log_return -> describe; monthly series -> deseasonalize -> project(pca, standardize=False).

Inputs: op (required), name (required), params, columns, +1 more. Returns: result

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

    TDQS

    A3.5/5.0

    Scored across 20 tools

    Disambiguation4/5

    Most tools have clearly distinct roles, and the table-versus-tensor split is consistently reflected in names and descriptions. The few overlaps are paired conversions (tensorize vs tensor_from_wide, tensor_to_table vs slice_tensor) where the descriptions help but an agent may still pause to choose.

    Naming Consistency4/5

    Names are consistently snake_case and generally action-oriented, with predictable prefixes for tensor operations. There are minor deviations: some tools are bare verbs (plot, describe, transform, subset, cluster) while others are noun-like (loadings, neighbors), but the set remains highly readable.

    Tool Count4/5

    Twenty tools is on the heavy side for a single server, but the scope is broad: loading/saving, profiling, plotting, preprocessing, dimensionality reduction, clustering, and tensor operations. Each tool earns its place, though the surface could be slightly tightened.

    Completeness4/5

    The lifecycle is well covered for datasets: create/load, inspect, transform, subset, save, and drop. Notable gaps remain for common table workflows such as joins/merges and explicit group-by aggregation, though some workarounds exist through tensorize and subset.

    Maintenance

    ActivityMaintained
    ResponsivenessNo recent community issues

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      Not graded
      quality
      D
      maintenance
      Enables creating interactive data visualizations from natural language queries using DuckDB for local databases or Databricks for enterprise data warehouses. Supports multiple chart types, CSV imports, SQL queries, and automatic statistical analysis through Claude Desktop.
      19
      MIT
    • A
      license
      B
      quality
      D
      maintenance
      Enables Claude Code to interact with Jupyter notebooks, perform semantic search over knowledge files, and manage research projects.
      33
      4
      MIT
    • F
      license
      Not graded
      quality
      C
      maintenance
      Enables LLM clients to load, clean, visualize, and analyze tabular datasets, and to generate multi-format analytic reports through natural language instructions.
      -
    • A
      license
      A
      quality
      C
      maintenance
      Enables read-only analytical access to local CSV, Parquet, JSON files or a DuckDB database through MCP, with table discovery, column profiling, guarded SQL querying, and explain plans for safe use with Claude Desktop and Claude Code.
      8
      1
      MIT