Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
79 commits
Select commit Hold shift + click to select a range
af82a71
Integrate desktop authoring line into core (squash of feature/authori…
mattcfilbert Jul 9, 2026
96a3b14
Resync feature/authoring → feature/desktop (one-time content-level re…
mattcfilbert Jul 15, 2026
d2ace79
feat(combined): TOOL_PROFILE=combined-lean — full desktop + lazy web …
mattcfilbert Jul 15, 2026
67e88a5
fix(metadata): detail→lod normalization + SQL string-fn knowledge + f…
mattcfilbert Jul 15, 2026
a343ddc
feat(classify): semantic-role-first geo binding + masking hardening (…
mattcfilbert Jul 15, 2026
4ee1239
Port a2td overnight portability+reliability wave (W1/W2/W3/W4/W6/W9) …
mattcfilbert Jul 15, 2026
27ab2c0
W65 red-team fixes: sidecar stamping, geo concept check, readback hon…
mattcfilbert Jul 15, 2026
1a4480d
fix(binder): CLS-003 — temporal completion won't swallow an ask-named…
mattcfilbert Jul 15, 2026
99090d3
Port a2td #209 GUS dogfood wave → feature/desktop (W-23447478/507/473…
mattcfilbert Jul 16, 2026
284d886
feat(interaction): ask-user clarification tool + fail-closed ambiguit…
mattcfilbert Jul 16, 2026
a1658f4
fix(desktop): canonical-name gate — goto-sheet can never focus a stal…
mattcfilbert Jul 16, 2026
5d28364
port(a2td #214): refine sort-insert on unsorted simple bars (#523)
mattcfilbert Jul 16, 2026
ccd809f
port(a2td #215): compose-focus suppression (parallel builds stop stea…
mattcfilbert Jul 16, 2026
a539691
port(a2td #213): resolve-field self-heals stale workbook caches (#526)
mattcfilbert Jul 16, 2026
34f79c0
knowledge: failure-recovery-honesty module (on-demand twin of the a2t…
mattcfilbert Jul 16, 2026
625b699
feat(desktop): route-table gates for authoring skill + plan-before-bu…
tableaukyler Jul 16, 2026
1ed6b77
build(desktop): add watch mode for fast rebuild loop (#536)
lauren-jacksonSFDC Jul 17, 2026
58ff4a3
feat(desktop): propose/classify field identity — a2td W2c lockstep tw…
mattcfilbert Jul 17, 2026
f2c8258
fix(desktop): promised-sort readback loss fails the verification rece…
mattcfilbert Jul 17, 2026
a2a0632
feat(desktop): exact column_ref first-class + shared ref helpers (a2t…
mattcfilbert Jul 17, 2026
f4dfe45
feat(desktop): planner datasource-qualified fields + apply-side datas…
mattcfilbert Jul 17, 2026
d5a3777
feat(desktop): known-command guard on execute-tableau-command + Notio…
mattcfilbert Jul 19, 2026
987e2cd
docs(knowledge): NotionalSpec semantic loop — live-verified authoring…
mattcfilbert Jul 19, 2026
b66941e
feat(desktop): episode-lite telemetry — the eval loop's product-path …
mattcfilbert Jul 19, 2026
af8c694
feat(desktop): TOOL_PROFILE=spec-loop — the 5-tool semantic-first sur…
mattcfilbert Jul 19, 2026
3a80ce9
fix(desktop): carry ancestor xmlns declarations onto extracted sheet/…
mattcfilbert Jul 19, 2026
b8580d7
docs(knowledge): wrong-fork redirects to the semantic calc twin + con…
mattcfilbert Jul 19, 2026
073ee2b
fix(desktop): search-commands no-match recommendation steers semantic…
mattcfilbert Jul 19, 2026
814e0e7
feat(desktop): pre-dispatch guard for generate-viz-from-notional-spec…
mattcfilbert Jul 19, 2026
ba357d7
fix(desktop): refine-worksheet polls its confirmation readback (async…
mattcfilbert Jul 19, 2026
ad185f2
feat(desktop): param-contract guard for every command + live-verified…
mattcfilbert Jul 19, 2026
1f836b6
fix(desktop): verify applied sheet exists before goto-sheet (modal 47…
mattcfilbert Jul 19, 2026
9e64705
desktop: surface the document round-trip (discoverability) + load con…
mattcfilbert Jul 20, 2026
5c5b328
feat(desktop): cut model round-trips — bind-template fast-path on the…
mattcfilbert Jul 20, 2026
41f059e
fix(desktop): pin External Client API schemas to the captured 0.1.0 o…
mattcfilbert Jul 20, 2026
ca66860
desktop: calc-ask routing via author verbs + NotionalSpec corpus purg…
mattcfilbert Jul 21, 2026
4d7afe5
fix(desktop): teach the known-live-500 fallback on tabdoc:sort-nested…
mattcfilbert Jul 21, 2026
0c40c65
feat(desktop): waterfall anchor_category slot + sort proposal wins ov…
mattcfilbert Jul 21, 2026
6961718
feat(desktop): waterfall bind hint names the step-order column (#562)
mattcfilbert Jul 21, 2026
58dc7b2
docs(knowledge): waterfall step-order guidance + profitability margin…
mattcfilbert Jul 21, 2026
1957c6a
feat(desktop): temporal_axis_from_string — bind a string month to a c…
mattcfilbert Jul 21, 2026
620fde2
fix(ci): widen vitest teardown window to stop flaky onTaskUpdate RPC …
mattcfilbert Jul 21, 2026
43e695c
feat(desktop): search-knowledge tool — targeted top-N over list-all (…
mattcfilbert Jul 21, 2026
f3b0b21
fix(desktop): honor session pin when stamping cache sidecars (#568)
tableaukyler Jul 21, 2026
684b314
docs: insights → bind-template mapping (Pulse quick-viz pointer) (#569)
mattcfilbert Jul 21, 2026
3d75d01
Resync main → feature/desktop (brings #502 insight tools + resolve-da…
mattcfilbert Jul 21, 2026
bd9234b
feat(web): slim param on generate-pulse-metric-value-insight-bundle (…
nawartamawi Jul 21, 2026
3a44b6a
feat(fields): add verbosity=slim to list-available-fields (#535)
nawartamawi Jul 21, 2026
1d97073
feat: env-gated External-API command-registry guard (wire names, enum…
mattcfilbert Jul 21, 2026
96aedb9
feat: data-first tools — get-summary-data + list-site-datasources (#573)
mattcfilbert Jul 21, 2026
3e6379b
Resync main → feature/desktop (id_type, MCP-Apps folders, stale-conte…
mattcfilbert Jul 21, 2026
e48d784
feat: surface invokeCommand output — consume the 0.1.1 result envelop…
mattcfilbert Jul 21, 2026
4d461ee
refactor: converge slim vocabulary — verbosity enum on the Pulse bund…
mattcfilbert Jul 21, 2026
0c2d8fe
chore: scrub internal references; retire NotionalSpec readback + inst…
mattcfilbert Jul 21, 2026
5f96962
fix: enum linkage accepts tuple-shaped type_of_param (the shipped reg…
mattcfilbert Jul 21, 2026
8ffbc5b
purge: dead hack-build command dialect — data + knowledge (part A) (#…
mattcfilbert Jul 21, 2026
b798461
refactor: architectural consolidation — guard extraction, read harnes…
mattcfilbert Jul 21, 2026
137dabc
feat: knowledge search length-router — long queries become first-clas…
mattcfilbert Jul 21, 2026
7cdef4a
feat: regrow the singable surface — shelf placement + dashboard compo…
mattcfilbert Jul 21, 2026
4727c3a
feat(binder): three graces + 30-tool singable surface — optional geo …
mattcfilbert Jul 22, 2026
ffa3833
feat(binder): bind-template target_worksheet — one-call render onto t…
mattcfilbert Jul 22, 2026
ff1a1e4
fix(knowledge): pin served docs to the tool registry; purge retired t…
mattcfilbert Jul 22, 2026
9cd8243
test: next-move law — emitted guidance may only name registered tools…
mattcfilbert Jul 22, 2026
50446b9
fix(binder): terminal signal on complete auto-apply bind (Blake spira…
mattcfilbert Jul 22, 2026
3fe9e29
feat(binder): measure-free lat/long symbol map — confident coordinate…
mattcfilbert Jul 22, 2026
993667b
fix(binder): classify honors temporal_from_string — string month bind…
mattcfilbert Jul 22, 2026
9e38d63
fix(binder): two demo-blockers — session:'default' sentinel + build-a…
mattcfilbert Jul 22, 2026
1d5b53d
fix(binder): latlon resolver binds WIDE real map schemas (best detail…
mattcfilbert Jul 22, 2026
95edd00
fix(binder): honest bind-template annotations — not read-only, not id…
mattcfilbert Jul 22, 2026
a2cb446
fix(binder): federated-duplicate tie-break — confident-bind survives …
mattcfilbert Jul 22, 2026
60fbf25
feat(desktop): atomic activate-sheet tool (goto-sheet recovery) — rep…
mattcfilbert Jul 22, 2026
58914c5
fix(binder): live map ask confident-binds — cross-base tie + decline-…
mattcfilbert Jul 22, 2026
b3a80ed
perf(tools): bind-template advertises self-discovery — kill the waste…
mattcfilbert Jul 22, 2026
11bbcc4
fix: read single sheets/dashboards as fragments and apply them withou…
lauren-jacksonSFDC Jul 22, 2026
2e86a2f
feat(binder): bind-recovery state machine — thrash protection for the…
mattcfilbert Jul 22, 2026
30f9edf
fix(apply): a sheet always ships with its window — kill the unreachab…
mattcfilbert Jul 22, 2026
12ae10e
test(binder): pin bind-template's content-block envelope (#605)
mattcfilbert Jul 22, 2026
769bf08
feat(knowledge): reachable, honest, findable — consult route + mustRe…
mattcfilbert Jul 22, 2026
53dc6b5
fix(knowledge): review hardening — route precedence, tokenizer fallba…
mattcfilbert Jul 22, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
4 changes: 4 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ on:
pull_request:
branches:
- main
- feature/desktop

jobs:
build:
Expand Down Expand Up @@ -36,6 +37,9 @@ jobs:
- name: Build
run: npm run build

- name: Build desktop variant
run: npm run build:desktop

- name: Build Docker image
run: npm run build:docker

Expand Down
7 changes: 7 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,13 @@ jobs:
npm install -g npm@latest
npm ci
npm run build
# Build the desktop variant into the same build/ dir. --dirty preserves the
# default build/index.js emitted above (a non-dirty desktop build would wipe it).
npx tsx src/scripts/build.ts --variant desktop --dirty
# Fail the publish if either entry point or the staged desktop data is missing.
test -f build/index.js
test -f build/index.desktop.js
test -d build/desktop/data
npm publish
upload-mcpb:
runs-on: ubuntu-latest
Expand Down
50 changes: 21 additions & 29 deletions .github/workflows/upload-binaries.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,27 +19,20 @@ jobs:
git fetch --tags
git checkout -b sea-linux ${{ github.event.release.tag_name }}
npm ci
npm run build
node --experimental-sea-config sea-config.json
cp $(command -v node) tableau-mcp
npx -y postject tableau-mcp NODE_SEA_BLOB sea-prep.blob --sentinel-fuse NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2
npm run build:sea -- --platform linux-x64
cp build/sea/default/linux-x64/tableau-mcp tableau-mcp
cp build/sea/desktop/linux-x64/tableau-mcp-desktop tableau-mcp-desktop
echo "SERVER=${{ secrets.E2E_TEST_SERVER }}" > .env
echo "AUTH=${{ secrets.E2E_TEST_AUTH }}" >> .env
echo "SITE_NAME=${{ secrets.E2E_TEST_SITE_NAME }}" >> .env
echo "JWT_SUB_CLAIM=${{ secrets.E2E_TEST_JWT_SUB_CLAIM }}" >> .env
echo "CONNECTED_APP_CLIENT_ID=${{ secrets.E2E_TEST_CONNECTED_APP_CLIENT_ID }}" >> .env
echo "CONNECTED_APP_SECRET_ID=${{ secrets.E2E_TEST_CONNECTED_APP_SECRET_ID }}" >> .env
echo "CONNECTED_APP_SECRET_VALUE=${{ secrets.E2E_TEST_CONNECTED_APP_SECRET_VALUE }}" >> .env
./tableau-mcp &
if [ $? -eq 0 ]; then
echo "tableau-mcp started successfully"
rm .env
pkill -f tableau-mcp
else
echo "tableau-mcp failed to start"
exit 1
fi
tar -czf tableau-mcp.tar.gz tableau-mcp
trap 'rm -f .env' EXIT
npx tsx src/scripts/seaSmoke.ts ./tableau-mcp
npx tsx src/scripts/seaSmoke.ts ./tableau-mcp-desktop --require-tool bind-template
tar -czf tableau-mcp.tar.gz tableau-mcp tableau-mcp-desktop
- name: Upload archive to release
run: |
gh release upload ${{github.event.release.tag_name}} tableau-mcp.tar.gz
Expand All @@ -61,11 +54,9 @@ jobs:
git fetch --tags
git checkout -b sea-windows ${{ github.event.release.tag_name }}
npm ci
npm run build

node --experimental-sea-config sea-config.json
node -e "require('fs').copyFileSync(process.execPath, 'tableau-mcp.exe')"
npx -y postject tableau-mcp.exe NODE_SEA_BLOB sea-prep.blob --sentinel-fuse NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2
npm run build:sea -- --platform win-x64
Copy-Item -Path .\build\sea\default\win-x64\tableau-mcp.exe -Destination .\tableau-mcp.exe
Copy-Item -Path .\build\sea\desktop\win-x64\tableau-mcp-desktop.exe -Destination .\tableau-mcp-desktop.exe

"SERVER=${{ secrets.E2E_TEST_SERVER }}" | Out-File -FilePath ".env" -Encoding UTF8
"AUTH=${{ secrets.E2E_TEST_AUTH }}" | Out-File -FilePath ".env" -Append -Encoding UTF8
Expand All @@ -75,18 +66,19 @@ jobs:
"CONNECTED_APP_SECRET_ID=${{ secrets.E2E_TEST_CONNECTED_APP_SECRET_ID }}" | Out-File -FilePath ".env" -Append -Encoding UTF8
"CONNECTED_APP_SECRET_VALUE=${{ secrets.E2E_TEST_CONNECTED_APP_SECRET_VALUE }}" | Out-File -FilePath ".env" -Append -Encoding UTF8

Start-Process -FilePath .\tableau-mcp.exe -NoNewWindow -ErrorAction SilentlyContinue
$processId = Get-Process -Name tableau-mcp -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Id

if ($processId) {
Write-Host "tableau-mcp.exe is running"
try {
npx tsx src/scripts/seaSmoke.ts .\tableau-mcp.exe
if ($LASTEXITCODE -ne 0) {
throw "tableau-mcp.exe smoke failed"
}
npx tsx src/scripts/seaSmoke.ts .\tableau-mcp-desktop.exe --require-tool bind-template
if ($LASTEXITCODE -ne 0) {
throw "tableau-mcp-desktop.exe smoke failed"
}
} finally {
Remove-Item -Path .\.env -Force
Stop-Process -Id $processId -Force
} else {
Write-Host "tableau-mcp.exe failed to start"
exit 1
}
Compress-Archive -Path tableau-mcp.exe -DestinationPath tableau-mcp.zip
Compress-Archive -Path tableau-mcp.exe, tableau-mcp-desktop.exe -DestinationPath tableau-mcp.zip
- name: Upload zip to release
run: |
gh release upload ${{github.event.release.tag_name}} tableau-mcp.zip
Expand Down
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
.env.*
.vscode/
build/
cache/
/cache/
node_modules/
junit/
coverage/
Expand Down
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Repo-level contract for the global agent OS. Defines what "correct" means in `ta

## Project overview

`@tableau/mcp-server` — an MCP server "helping agents see and understand data." Exposes Tableau capabilities to MCP clients over stdio and HTTP. Ships multiple build variants (default web, **desktop**, combined) from one codebase via `src/scripts/build.ts` + esbuild conditional bundling. An active migration on `feature/authoring` ports Tableau Desktop *authoring* tools from the `agent-to-tableau-desktop` repo into `src/tools/desktop/`.
`@tableau/mcp-server` — an MCP server "helping agents see and understand data." Exposes Tableau capabilities to MCP clients over stdio and HTTP. Ships multiple build variants (default web, **desktop**, combined) from one codebase via `src/scripts/build.ts` + esbuild conditional bundling. An active migration on `feature/authoring` ports Tableau Desktop *authoring* tools from the `agent-to-tableau-desktop` repo/source implementation into `src/tools/desktop/`.

## Architecture

Expand Down
87 changes: 87 additions & 0 deletions README.desktop.md
Original file line number Diff line number Diff line change
@@ -1 +1,88 @@
# Tableau Desktop Authoring MCP

The **desktop** build variant of `@tableau/mcp-server`. Where the default variant talks to
Tableau Cloud/Server over REST, this variant exposes a **local authoring** tool surface that
drives a running **Tableau Desktop** instance — inspect a workbook, list/inject chart
templates, and bind fields into worksheets — over MCP (stdio).

This document is a from-source quickstart. The desktop variant is **not** yet built by the
publish pipeline (see [Known gaps](#known-gaps)); build it from a clone.

## The binder tool surface

Alongside the workbook/worksheet/dashboard/field tools, four tools drive the fast-path
chart binder:

- **`list-templates`** — list the bundled chart templates with each one's chart-intent
family, slot contract, and `fast_path_eligible` status. Works **headless** (no Desktop
needed) — it reads the in-package snapshot.
- **`propose-template`** — Call 1: given a natural-language ask and the live workbook,
return candidate templates + a strict `output_schema` for the caller to fill into a
binding proposal (slot_id → field), or a deterministic no-LLM match when one is found.
- **`validate-proposal`** — Call 2 (dry run): run a filled proposal through the binder's
deterministic gate (slot coverage, field/kind/role, derivation legality, confidence
floor) and report valid/invalid **without** creating or applying a worksheet.
- **`bind-template`** — the full two-call flow: validate a filled proposal and, when valid,
return the injector-ready args plus the apply instruction.

Typical flow: `propose-template` → fill the proposal → `validate-proposal` (dry run) →
`bind-template` to get the apply instruction.

## Build & run from source

Requires Node.js `>=22.7.5`.

```bash
npm ci
npm run build:desktop
```

The build emits the desktop entry point at **`build/index.desktop.js`** (the default
variant's `build/index.js` is not produced by this command). It also stages the bundled
authoring data under `build/desktop/data/` — this staging happens **only** for the desktop
and combined variants.

Point an MCP client at the entry over stdio:

```json
{
"mcpServers": {
"tableau-desktop": {
"command": "node",
"args": ["/absolute/path/to/tableau-mcp/build/index.desktop.js"]
}
}
}
```

## Requirements

- **`list-templates`** works headless against the bundled snapshot.
- **`propose-template`**, **`validate-proposal`**, and **`bind-template`** read/drive a
**running Tableau Desktop** instance. Discover the instance with **`list-instances`** and
pass its session id (the Tableau Desktop PID) as the `session` argument to those tools.

## Template content

- Templates ship as a **bundled snapshot** inside the package, hash-verified against a
generated `content-manifest.json` (every resource carries a sha256 + byte count).
- **17** chart templates are bundled today.
- **`fast_path_eligible`** marks a template that is portable across the committed schema
fixture **and** carries a live render-verification stamp — the templates the binder can
one-shot. Ineligible templates report a `fast_path_blockers` entry explaining why (an
explicit blocker code, or a derived note such as "no live render verification stamp").
- **Remote content packs** (fetching a signed, versioned pack instead of the bundled
snapshot) are a **documented milestone-2 skeleton only** — the verification/cache/fallback
contract exists behind the provider seam, but no transport is wired, so the server always
serves the bundled snapshot. Its status honestly reports `satisfies_exec_freshness: false`.

## Known gaps

Stated honestly so nobody is surprised:

- The **search tools** (`search-examples`, `search-commands`, `search-workbook-examples`,
`lookup-workbook-schema`) resolve their data **relative to the current working directory**,
so they are effectively **dev-only** (run from a repo checkout) and are not reachable from
a packaged install.
- The **publish pipeline does not yet build this variant** — the desktop authoring server is
**from-source only** for now.
46 changes: 46 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,52 @@ The quickest way to run Tableau MCP locally. Requires [Node.js](https://nodejs.o

For Docker, building from source, and other self-hosted options, see the [Getting Started guide](https://tableau.github.io/tableau-mcp/docs/getting-started).

### Tableau Desktop Authoring Server (from source)

The **desktop** build variant exposes a local authoring tool surface that drives a running Tableau Desktop instance over MCP stdio. It can inspect workbooks, list/inject chart templates, bind fields into worksheets, and work with dashboards.

Build it from a clone with Node.js 22.7.5 or later:

```bash
npm run build:desktop
```

Point an MCP client at the desktop entry:

```json
{
"mcpServers": {
"tableau-desktop": {
"command": "node",
"args": ["/absolute/path/to/tableau-mcp/build/index.desktop.js"]
}
}
}
```

Headless reference tools such as `list-templates` read the bundled snapshot. Tools that inspect or mutate a workbook require a running Tableau Desktop instance; use `list-instances` and pass the returned `session` id to those calls.

See [`README.desktop.md`](README.desktop.md) for the full desktop authoring quickstart and known gaps.

## Standalone Binaries (SEA)

The server can be packaged as a [Node.js Single Executable Application](https://tableau.github.io/tableau-mcp/docs/extras/node-sea)
so it runs without a Node.js install. Build them locally with:

```bash
npm run build:sea # default (web) + desktop variants, host platform
npm run build:sea:desktop # desktop variant only

# Pick variants/platforms explicitly:
npm run build:sea -- --variant desktop --platform macos-arm64 macos-x64 win-x64
```

Output lands in `build/sea/<variant>/<platform>/`. Platforms: `macos-arm64`, `macos-x64`,
`linux-x64`, `linux-arm64`, `win-x64`. macOS binaries are ad-hoc codesigned when built on a
macOS host. Each binary is fully self-contained: the desktop variant's knowledge, data,
templates, and examples are embedded into the executable as SEA assets, so it can be
distributed and run as a single file with no sibling folders.

## Deploy to Heroku

[![Deploy to Heroku](https://www.herokucdn.com/deploy/button.svg)](https://www.heroku.com/deploy?template=https://github.com/tableau/tableau-mcp)
Expand Down
Loading
Loading