tableau-audit-mcp
by Sujan2396
README.md
# Tableau Workbook Audit MCP Server
[](https://github.com/Sujan2396/tableau_aduit_mcp/actions/workflows/tests.yml)
[](LICENSE)
[](pyproject.toml)
An MCP server that audits local Tableau workbooks (`.twb` / `.twbx`) for:
1. **Unused assets** — worksheets never embedded on a dashboard, calculated
fields nothing depends on (directly or transitively), parameters never
referenced, and groups/sets never used.
2. **Duplicate worksheets** — near-identical sheets flagged via a weighted
similarity score (shared fields, mark type, filter overlap), so you know
what to consolidate.
3. **Dashboard weight** — a load-time risk score per dashboard (worksheet
count, filter/action/web-object counts, embedded extract size) with
concrete recommendations.
No Tableau Server/Cloud connection required — it works entirely from the
workbook file on disk.
## Install
```bash
git clone https://github.com/Sujan2396/tableau_aduit_mcp.git
cd tableau_aduit_mcp
pip install -e .
```
**Not yet published to PyPI.** `pip install tableau-audit-mcp` will fail
until/unless this package is actually published there — the command above
(`pip install -e .` from a local clone) is the only working install method
right now. If that changes, this section will be updated accordingly.
## Run standalone (for testing)
```bash
python3 -c "
from tableau_audit import load_workbook, WorkbookGraph, find_unused_assets, analyze_dashboards, find_duplicate_worksheets, render_markdown_report
loaded = load_workbook('/path/to/your/workbook.twbx')
graph = WorkbookGraph(loaded.root)
unused = find_unused_assets(graph)
dups = find_duplicate_worksheets(graph)
dws = analyze_dashboards(graph)
print(render_markdown_report('your_workbook.twbx', unused, dups, dws, loaded.total_extract_bytes / (1024*1024)))
"
```
## Run as an MCP server
```bash
python3 server.py
# or, after `pip install -e .` (registers the console script):
tableau-audit-mcp
```
### Register with Claude Desktop
Add to `claude_desktop_config.json`:
```json
{
"mcpServers": {
"tableau-audit": {
"command": "python3",
"args": ["/absolute/path/to/tableau-audit-mcp/server.py"]
}
}
}
```
## Tools exposed
| Tool | Purpose |
|---|---|
| `load_tableau_workbook(workbook_path)` | Parse + cache a workbook, return a summary |
| `list_unused_assets(workbook_path)` | Unused worksheets/calcs/parameters/groups+sets |
| `find_duplicate_sheets(workbook_path, threshold=0.85)` | Near-duplicate worksheet pairs |
| `analyze_dashboard_performance(workbook_path)` | Per-dashboard weight/risk score + recommendations |
| `get_workbook_health_summary(workbook_path)` | Compact scorecard: counts + structural-complexity % + load-time risk rating |
| `generate_cleanup_report(workbook_path, duplicate_threshold=0.85)` | Full combined Markdown report (recommended default) |
## How the analysis works
- **Field usage**: each `<worksheet>` in the `.twb` XML lists exactly which
columns it uses via `<datasource-dependencies>`. A field/calc/group/set with
zero worksheet references (and, for calcs, no other *used* calc depending on
it) is flagged unused.
- **Parameters**: unlike other fields, parameters aren't referenced via
dependencies — they're used by name inside formulas, filters, actions, and
titles. We scan those specifically for the parameter's name (and exclude the
parameter's own definition, to avoid a trivial self-match).
- **Groups vs. Sets**: both are stored in Tableau's XML as a `<column>` with a
`<groupfilter>` child, so we track them together as `group_or_set`. Reliably
telling groups apart from sets from the XML alone is a known limitation
(see below).
- **Duplicate sheets**: `0.7 × Jaccard(field usage) + 0.15 × (mark type match)
+ 0.15 × Jaccard(filters)`. Tune the threshold per tool call — lower it to
catch looser near-duplicates.
- **Dashboard weight**: a heuristic score from worksheet count, filter/action/
web-object counts on the dashboard, plus overall embedded extract size (for
`.twbx` files). Thresholds are tunable in `tableau_audit/analysis.py`.
## Known limitations / next steps
- **Tableau version drift**: the `.twb` schema has shifted across Desktop
versions (2018.x vs. 2023.x vs. Cloud). The XPath heuristics here cover the
common structure; workbooks with heavy custom XML (Tableau Prep flows,
very old Desktop versions) may need small adjustments in `graph.py`.
- **Group vs. Set disambiguation** is currently a placeholder — both show up
as `group_or_set`. Both static groups/sets (a `<column>` with a
`<groupfilter>` child) and dynamic sets like "Top N by..." (a separate
top-level `<group>` element with a different attribute shape — validated
against a real workbook) are parsed and tracked for usage, including
parameters referenced via `count`/`size-parameter` attributes rather than
formula text. They just aren't labeled apart from each other. If you need
Groups split from Sets specifically, the most reliable path is
cross-referencing against the Tableau Metadata API rather than the raw XML.
- **Dashboard action counts** are attributed per-dashboard via a best-effort
heuristic (matching worksheet names mentioned in each action's XML against
the worksheets on that dashboard) rather than assigning the full
workbook-wide total to every dashboard equally (a real scoring bug fixed
after external code review - see CONTRIBUTING.md history). This can still
under- or over-attribute for exotic action configurations across schema
versions; the raw workbook-wide total is always available alongside the
attributed figure for transparency.
- **Extract size** is only available for `.twbx` (packaged) workbooks, since
`.twb` files reference external `.hyper`/`.tde` files rather than embedding
them. Note this figure comes from the zip header's declared size, which
isn't independently verified (see the security note in `parser.py`).
- The automated suite in `tests/test_audit.py` covers the synthetic sample
workbook, regression cases found while validating against real Tableau
Public workbooks, and a set of crafted fixtures under `tests/fixtures/`
that each reproduce a specific bug found during an external code review
(substring-matching false positives, Story-only worksheets, filterless
duplicate scoring, per-dashboard action attribution, XML/zip hardening,
cache staleness). It's not exhaustive — if you hit a workbook shape that
breaks parsing, please open an issue (see CONTRIBUTING.md) with a minimal
reproducing snippet.
- The two "Workbook Health" headline numbers are intentionally scoped
honestly: **structural complexity reduction %** is a heuristic (documented
in `compute_health_summary`'s docstring) based on unused-asset ratios, not
a calibrated file-size prediction. **Dashboard load-time risk** is a
qualitative Low/Medium/High rating rather than a percentage, because
Tableau publishes no formula for converting static asset counts into a
load-time number — for a measured value, use Tableau's own
[Performance Recorder](https://help.tableau.com/current/pro/desktop/en-us/performance_tips.htm).
## Contributing
Contributions are welcome — see [CONTRIBUTING.md](CONTRIBUTING.md) for setup
instructions, what kinds of contributions are most valuable, and development
guidelines.
## License
[MIT](LICENSE) — free to use, modify, and redistribute.
This server cannot be deployed
Maintenance
ActivitySlowing
ResponsivenessNo issues