Slice through your data like a hot knife through pizza.
An MCP server that wraps the wise-pizza library, letting LLMs find the most interesting segments in multidimensional data. Load CSVs once, then run multiple analyses — all through the Model Context Protocol.
wise-pizza finds segments in your data that are unusual or that explain changes between time periods. This MCP server exposes four analysis methods:
- explain_levels — Find segments whose average is most different from the global average
- explain_changes_in_totals — Explain what drove changes in totals between two datasets
- explain_changes_in_average — Explain what drove changes in averages between two datasets
- explain_timeseries — Find segments with different temporal patterns in panel data
Plus dataset management tools to load, list, and remove CSV data.
This server uses uv to run. Install it if you don't have it:
curl -LsSf https://astral.sh/uv/install.sh | shAdd to your Claude Desktop config (~/.claude/claude_desktop_config.json):
{
"mcpServers": {
"wise-pizza": {
"command": "uvx",
"args": ["wise-pizza-mcp"]
}
}
}If you're working in a clone of this repo with Claude Code, the server is auto-configured via .mcp.json — no manual setup needed.
Load a CSV file into the in-memory dataset store.
| Parameter | Type | Required | Description |
|---|---|---|---|
file_path |
string | yes | Path to a CSV file on disk |
dataset_name |
string | yes | Unique name to refer to this dataset |
dims |
list[string] | yes | Column names to use as dimensions (categorical columns to segment by) |
total_name |
string | yes | Column name containing the totals (the value to analyze) |
size_name |
string | no | Column name containing segment sizes/weights |
Example:
Load the file /data/sales.csv as "sales_q1" with dimensions ["region", "product", "channel"],
total_name "revenue", and size_name "transactions"
Returns a list of all dataset names currently in the store.
Remove a dataset from the store by name.
Find segments whose average is most different from the global one. Great for answering: "Which segments are unusual in this dataset?"
| Parameter | Type | Default | Description |
|---|---|---|---|
dataset_name |
string | required | Name of a loaded dataset |
max_segments |
int | auto | Maximum segments to find |
min_depth |
int | 1 | Minimum dimensions per segment |
max_depth |
int | 2 | Maximum dimensions per segment |
constrain_signs |
bool | true | Force segment weights to match direction |
cluster_values |
bool | false | Consider clusters of similar dimension values |
image_size |
list[int] | none | [width, height] to generate a PNG chart |
Example:
Run explain_levels on "sales_q1" with max_segments=5 and image_size=[1200, 600]
Find segments that explain differences between totals of two datasets.
| Parameter | Type | Default | Description |
|---|---|---|---|
dataset_name_1 |
string | required | Baseline dataset |
dataset_name_2 |
string | required | Comparison dataset |
max_segments |
int | auto | Maximum segments to find |
min_depth |
int | 1 | Minimum dimensions per segment |
max_depth |
int | 2 | Maximum dimensions per segment |
how |
string | "totals" | "totals", "split_fits", "extra_dim", or "force_dim" |
constrain_signs |
bool | true | Force segment weights to match direction |
cluster_values |
bool | false | Consider clusters of similar dimension values |
image_size |
list[int] | none | [width, height] for PNG chart |
The how parameter controls decomposition:
"totals"— Combined analysis of segment total changes"split_fits"— Separately decompose size changes vs average changes (returnsSplitAnalysisResult)"extra_dim"— Treat size/average contribution as an additional dimension"force_dim"— Like extra_dim, but each segment must specify a Change_from constraint
Same signature as explain_changes_in_totals, but focuses on average value changes rather than total volume changes.
Split time series panel data into segments with different temporal patterns.
| Parameter | Type | Default | Description |
|---|---|---|---|
dataset_name |
string | required | Name of a loaded dataset |
time_name |
string | required | Column containing time values |
num_segments |
int | auto | Number of segments to find |
max_depth |
int | 2 | Maximum dimensions per segment |
fit_sizes |
bool | auto | Also fit segment sizes over time |
num_breaks |
int | 3 | Breakpoints in stylized time series |
n_jobs |
int | 10 | Parallel jobs for tree building |
ignore_averages |
bool | true | Ignore overall level when comparing |
image_size |
list[int] | none | [width, height] for PNG chart |
Most tools return an AnalysisResult JSON:
{
"task": "levels",
"segments": [
{
"segment": {"region": "US", "product": "A"},
"total": 1500.0,
"seg_size": 200.0,
"naive_avg": 7.5,
"impact": 120.0,
"avg_impact": 0.6
}
],
"relevant_clusters": {},
"global_average": 5.2,
"markdown_summary": "| ... |",
"image_path": "/tmp/tmpXXXXXX.png"
}- segment: Dimension-value pairs defining this segment
- total: Total value within the segment
- seg_size: Size/weight of the segment
- naive_avg: Simple average in the segment (total / seg_size)
- impact: Segment's impact on the overall total (lasso solver only)
- avg_impact: Impact per unit weight (lasso solver only)
- relevant_clusters: Cluster definitions when
cluster_values=True - image_path: Path to generated PNG file (when
image_sizeis set)
When using how="split_fits", the response contains separate analyses:
{
"task": "changes (split)",
"size_analysis": { "...AnalysisResult..." },
"average_analysis": { "...AnalysisResult..." },
"image_path": "/tmp/tmpXXXXXX.png"
}1. Load my sales data: load_data("/data/sales_jan.csv", "jan_sales",
dims=["region", "product", "channel"], total_name="revenue", size_name="orders")
2. Find unusual segments: explain_levels("jan_sales", max_segments=5, image_size=[1200, 600])
This identifies segments where the average revenue per order differs most from the global average — e.g., "APAC + Premium products have 3x higher revenue per order than average".
1. Load both periods:
load_data("/data/sales_q1.csv", "q1", dims=["region", "product"], total_name="revenue", size_name="orders")
load_data("/data/sales_q2.csv", "q2", dims=["region", "product"], total_name="revenue", size_name="orders")
2. What drove the total change?
explain_changes_in_totals("q1", "q2", image_size=[1000, 800])
3. What about average changes specifically?
explain_changes_in_average("q1", "q2")
4. Break down size vs average contributions:
explain_changes_in_totals("q1", "q2", how="split_fits")
1. Load panel data:
load_data("/data/monthly_metrics.csv", "monthly",
dims=["region", "product"], total_name="revenue", size_name="customers")
2. Find segments with different trends:
explain_timeseries("monthly", time_name="month", image_size=[1200, 800])
This finds groups like "EU + Product B is declining while US + Product A is growing".
git clone https://github.com/MotleyAI/wise-pizza-mcp.git
cd wise-pizza-mcp
pip install -e ".[dev]"
pytest tests/ -vMIT



