Skip to main content
Glama
Sujan2396

tableau-audit-mcp

by Sujan2396

Tableau Workbook Audit MCP Server

Tests License: MIT Python 3.10+

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

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.

Related MCP server: mcp-shipcheck

Run standalone (for testing)

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

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:

{
  "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.

Contributing

Contributions are welcome — see CONTRIBUTING.md for setup instructions, what kinds of contributions are most valuable, and development guidelines.

License

MIT — free to use, modify, and redistribute.

Related MCP Connectors

Related MCP Servers