{
  "markdown": "<!-- mcp-name: io.github.tdries/tableau-graphql-mcp -->\n\n<p align=\"center\">\n  <img src=\"docs/images/banner.png\" alt=\"tableau-graphql-mcp\" width=\"880\">\n</p>\n\n<h1 align=\"center\">tableau-graphql-mcp</h1>\n\n<p align=\"center\"><em>Ask any Tableau lineage question, in any MCP client, through the Tableau Metadata API.</em></p>\n\n<p align=\"center\">\n  <a href=\"https://github.com/tdries/tableau-graphQL-mcp/actions/workflows/ci.yml\"><img src=\"https://github.com/tdries/tableau-graphQL-mcp/actions/workflows/ci.yml/badge.svg\" alt=\"CI\"></a>\n  <a href=\"https://github.com/tdries/tableau-graphQL-mcp/actions/workflows/codeql.yml\"><img src=\"https://github.com/tdries/tableau-graphQL-mcp/actions/workflows/codeql.yml/badge.svg\" alt=\"CodeQL\"></a>\n  <a href=\"https://github.com/astral-sh/ruff\"><img src=\"https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json\" alt=\"Ruff\"></a>\n  <a href=\"https://pypi.org/project/tableau-graphql-mcp/\"><img src=\"https://img.shields.io/pypi/v/tableau-graphql-mcp.svg\" alt=\"PyPI\"></a>\n  <img src=\"https://img.shields.io/pypi/pyversions/tableau-graphql-mcp.svg\" alt=\"Python versions\">\n  <img src=\"https://img.shields.io/badge/MCP-compatible-8A2BE2.svg\" alt=\"MCP compatible\">\n  <a href=\"LICENSE\"><img src=\"https://img.shields.io/badge/license-MIT-00696E.svg\" alt=\"MIT License\"></a>\n  <img src=\"https://img.shields.io/badge/Tableau-Server%20%2B%20Cloud-006EFF.svg\" alt=\"Server + Cloud\">\n  <img src=\"https://img.shields.io/badge/deps-just%20mcp-64FA8C.svg\" alt=\"Minimal dependencies\">\n</p>\n\n<p align=\"center\">\n  <a href=\"cursor://anysphere.cursor-deeplink/mcp/install?name=tableau-graphql&config=eyJjb21tYW5kIjogInV2eCIsICJhcmdzIjogWyJ0YWJsZWF1LWdyYXBocWwtbWNwIl19\"><img src=\"https://img.shields.io/badge/Add%20to-Cursor-0098FF.svg\" alt=\"Add to Cursor\"></a>\n</p>\n\n<p align=\"center\">\n  <img src=\"docs/images/demo.gif\" alt=\"Asking lineage questions in Claude\" width=\"760\">\n</p>\n\n---\n\n**tableau-graphql-mcp** turns your Tableau site's [Metadata API](https://help.tableau.com/current/api/metadata_api/en-us/index.html) into a set of MCP tools, so an AI assistant (Claude, Cursor, Cline, and others) can answer lineage questions in plain language:\n\n- _\"If I drop the column `SALES`, which workbooks break?\"_\n- _\"What tables does the **Sales Overview** workbook depend on?\"_\n- _\"Which calculated fields reference `Profit`, and on which dashboards?\"_\n- _\"Who should I notify before changing the `DIM_CUSTOMER` table?\"_\n\nIt ships **seven curated tools**: a universal GraphQL passthrough, live schema introspection, an embedded library of correct query templates, a robust `where_used` resolver, a multi-hop `impact_analysis`, a substring content search, and a connection probe. Together they let the model answer *any* lineage question, not just a fixed menu.\n\n### Why it's different\n- **Any question, done right.** `graphql_query` runs any read-only GraphQL; `introspect_schema` and a built-in cheat-sheet plus 28 worked examples keep the model's queries correct.\n- **True impact analysis (multi-hop).** `impact_analysis` follows the *whole* dependency chain (a calc built on a calc built on a column is included) and returns the full blast radius plus the de-duplicated **owners to notify**, not just direct references.\n- **Works everywhere.** Tableau **Server** *and* **Cloud**. The REST API version and the GraphQL endpoint (`/api/metadata/graphql`, with a `/relationship-service-war/graphql` fallback) are **auto-detected**.\n- **Robust lineage without Catalog.** `where_used` resolves workbooks via core lineage (`referencedByFields -> sheets -> workbook`), so it works even when the Data Management add-on's `downstreamWorkbooks` is empty.\n- **No silent truncation.** `graphql_query` flags `partial_results` when a query hits the node limit, and `search_content` reports `scanned`/`total` coverage, so a truncated answer is never mistaken for a complete one.\n- **Tiny and safe.** Read-only, stdio-only (no inbound port), secrets from env only, and **no dependencies beyond the MCP SDK** (stdlib `urllib` for HTTP).\n\n## Quickstart\n\nYou need [`uv`](https://docs.astral.sh/uv/) (`curl -LsSf https://astral.sh/uv/install.sh | sh`) and a Tableau [Personal Access Token](https://help.tableau.com/current/server/en-us/security_personal_access_tokens.htm).\n\n**Claude Code, one line:**\n\n```bash\nclaude mcp add tableau-graphql \\\n  -e TABLEAU_SERVER=https://10ax.online.tableau.com \\\n  -e TABLEAU_SITE_CONTENT_URL=YourSite \\\n  -e TABLEAU_PAT_NAME=my-token \\\n  -e TABLEAU_PAT_SECRET=the-full-secret \\\n  -- uvx tableau-graphql-mcp\n```\n\nThat's all. `uvx` fetches the package from PyPI and runs it in an isolated environment; nothing to clone or install (and no `git` required). Then ask Claude a lineage question.\n\n## Configuration\n\nAll configuration is via environment variables (set them in your client's `env` block, never on the command line).\n\n| Env var | Required | Default | Description |\n|---|:---:|---|---|\n| `TABLEAU_SERVER` | yes | n/a | `https://tableau.company.com` (Server) or `https://<pod>.online.tableau.com` (Cloud). |\n| `TABLEAU_SITE_CONTENT_URL` | no | `\"\"` | Site slug (the part after `/#/site/`). Empty = Default site (Server only); Cloud always has one. |\n| `TABLEAU_PAT_NAME` | yes¹ | n/a | Personal Access Token **name**. |\n| `TABLEAU_PAT_SECRET` | yes¹ | n/a | PAT **secret**: the whole string, do **not** split on `:`. |\n| `TABLEAU_TIMEOUT` | no | `60` | Per-request timeout (seconds). |\n| `TABLEAU_API_VERSION` | no | auto | REST API version; else read from `/api/serverinfo`. |\n| `TABLEAU_METADATA_PATH` | no | auto | Override the GraphQL path; else auto-detected. |\n| `TABLEAU_AUTH_TOKEN` | no | n/a | Advanced: a pre-obtained `X-Tableau-Auth` token (SSO tenants where PATs are disabled). |\n| `TABLEAU_COOKIE` | no | n/a | Advanced: a browser session cookie (SSO fallback). |\n\n¹ Provide a PAT (`TABLEAU_PAT_NAME` + `TABLEAU_PAT_SECRET`) **or** an advanced `TABLEAU_AUTH_TOKEN` / `TABLEAU_COOKIE`.\n\n<details>\n<summary><b>Claude Desktop</b></summary>\n\nEdit `claude_desktop_config.json` (macOS: `~/Library/Application Support/Claude/`, Windows: `%APPDATA%\\Claude\\`):\n\n```json\n{\n  \"mcpServers\": {\n    \"tableau-graphql\": {\n      \"command\": \"uvx\",\n      \"args\": [\"tableau-graphql-mcp\"],\n      \"env\": {\n        \"TABLEAU_SERVER\": \"https://10ax.online.tableau.com\",\n        \"TABLEAU_SITE_CONTENT_URL\": \"YourSite\",\n        \"TABLEAU_PAT_NAME\": \"my-token\",\n        \"TABLEAU_PAT_SECRET\": \"the-full-secret\"\n      }\n    }\n  }\n}\n```\n\nFully **quit and reopen** Claude Desktop, then check the tools menu.\n</details>\n\n<details>\n<summary><b>Cursor</b> · <b>Cline</b> · <b>Windsurf</b></summary>\n\nEvery client uses the same `mcpServers` schema shown above. Add the same block to:\n- **Cursor**: `~/.cursor/mcp.json` (global) or `.cursor/mcp.json` (project).\n- **Cline**: the MCP Servers panel, then *Configure*, into `cline_mcp_settings.json`.\n- **Windsurf**: `~/.codeium/windsurf/mcp_config.json`.\n\nOn Windows, if `uvx` isn't found by the GUI app, use its absolute path (e.g. `%USERPROFILE%\\.local\\bin\\uvx.exe`).\n</details>\n\n## Tools\n\n| Tool | What it does | Key args |\n|---|---|---|\n| `graphql_query` | Run **any** read-only Metadata API GraphQL query. The general tool for any lineage question. | `query`, `variables` |\n| `introspect_schema` | Live schema introspection: list entry points, or a type's exact fields. | `type_name` |\n| `lineage_examples` | A schema cheat-sheet plus 28 curated question-to-GraphQL templates (8 categories). | `category` |\n| `where_used` | Which workbooks/datasources use given column / field / table names (robust one-hop core-lineage resolution). | `names` |\n| `impact_analysis` | Full transitive **multi-hop** blast radius of a column/field/table: every dependent field, plus affected sheets, dashboards, workbooks, and **owners to notify**. | `name` |\n| `search_content` | Find content whose **name contains** a term (case-insensitive substring), across workbooks, datasources, tables (and optionally fields/columns), with coverage numbers. | `term`, `types` |\n| `server_info` | Connected server, site, versions, endpoint, auth, and whether Catalog lineage is available. | none |\n\nAll tools are **read-only**. The Metadata API has no mutations.\n\n## Example prompts\n\nOnce connected, try:\n\n- _\"Use server_info to confirm what you're connected to.\"_\n- _\"Search for anything with 'revenue' in the name.\"_\n- _\"Which workbooks use the columns SALES, PROFIT and DISCOUNT? Group by owner.\"_\n- _\"Show me the field-to-source-column map for the 'Sales Overview' workbook.\"_\n- _\"List every calculated field in that workbook with its formula.\"_\n- _\"Which published datasources feed workbooks in the Analytics project, and which are uncertified?\"_\n- _\"Run impact_analysis on the 'Profit Ratio' field: every dependent sheet, dashboard, workbook, and owner to notify.\"_\n- _\"What is the blast radius of dropping the DIM_CUSTOMER table: workbooks, sheets, and owners to notify?\"_\n\n## Architecture\n\n<p align=\"center\"><img src=\"docs/images/architecture.svg\" alt=\"Architecture\" width=\"900\"></p>\n\nThe server speaks MCP over **stdio** to the client and HTTPS to Tableau: it signs in with your PAT to get an `X-Tableau-Auth` token (auto-refreshed on expiry), auto-detects the REST API version and the GraphQL endpoint, then forwards queries to the Metadata API. Nothing is stored; every answer is live.\n\n## Security\n\n- **Read-only, enforced.** Only GraphQL queries: no writes, no shell. `graphql_query` rejects `mutation`/`subscription` operations, and the Metadata API is query-only regardless.\n- **Local and stdio-only.** No inbound network port is opened.\n- **Secrets from env only.** Never passed as tool arguments, never logged, never returned in output.\n- **Least privilege.** The PAT inherits your Tableau permissions; the API only returns content you can see.\n- Pin a version in production: `uvx tableau-graphql-mcp==0.1.0`.\n\nSee [SECURITY.md](SECURITY.md).\n\n## Troubleshooting\n\n| Symptom | Fix |\n|---|---|\n| Server doesn't appear | Fully quit and relaunch the client; check the config path and JSON validity. |\n| `spawn uvx ENOENT` | Install `uv`, or use the absolute path to `uvx`. |\n| Sign-in fails (401) | Check the PAT name/secret and `TABLEAU_SITE_CONTENT_URL`. On SSO tenants PATs may be disabled; use `TABLEAU_AUTH_TOKEN`/`TABLEAU_COOKIE`. |\n| \"Could not reach the Metadata API\" | On Tableau **Server**, an admin must enable it: `tsm maintenance metadata-services enable`. On Cloud it is always on. |\n| Empty `downstreamWorkbooks` | Expected without the Data Management add-on; use the `where_used` tool, which resolves via core lineage. |\n\nInspect the server directly with the MCP Inspector:\n\n```bash\nnpx @modelcontextprotocol/inspector uvx tableau-graphql-mcp\n```\n\n## Development\n\n```bash\ngit clone https://github.com/tdries/tableau-graphQL-mcp && cd tableau-graphQL-mcp\nuv sync --all-extras\nuv run tableau-graphql-mcp                     # run from source\nuv run pytest --cov=tableau_graphql_mcp        # tests + coverage (offline; no Tableau needed)\nuv run ruff check .                            # lint\nuv run ruff format --check .                   # format\n```\n\nThe same three gates (lint, format, tests with a 85% coverage floor) run in CI across\nLinux/macOS/Windows and Python 3.10 to 3.13. Coverage is reported to Codecov and the code is\nscanned by CodeQL on every push.\n\nThe same gates run in CI (Linux/macOS/Windows, Python 3.10 to 3.13): `ruff check`,\n`ruff format --check`, `mypy --strict`, and `pytest` with a coverage floor. The package\nships a PEP 561 `py.typed` marker, so importing it gives your type checker full types.\n\nContributions welcome: see [CONTRIBUTING.md](CONTRIBUTING.md) and the [Code of Conduct](CODE_OF_CONDUCT.md).\n\n## Roadmap\n\nShipped: published on [PyPI](https://pypi.org/project/tableau-graphql-mcp/) and listed on the\nofficial [MCP registry](https://registry.modelcontextprotocol.io/v0/servers?search=tableau-graphql).\nNext:\n\n- [ ] Optional Data Management path: richer `downstreamWorkbooks` when Catalog is present\n- [ ] More curated query templates in `lineage_examples`\n- [ ] Optional response caching for repeated introspection within a session\n\nIdeas and votes welcome in [Discussions](https://github.com/tdries/tableau-graphQL-mcp/discussions).\n\n## License\n\n[MIT](LICENSE) © Tim Dries. Built at [Biztory](https://www.biztory.com).\n",
  "bytes": 12361,
  "sha": "3155a41301fa4a98495860f3a6cc64a68a5d9087cbb9b8f84e088d0ad85f03f3",
  "repo_slug": "tdries/tableau-graphql-mcp",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_tdries_tableau_graphql_mcp_489a3917/readme"
}