diff --git a/.env.example b/.env.example index 22ac2b1..b783ff3 100644 --- a/.env.example +++ b/.env.example @@ -14,5 +14,9 @@ PAT_VALUE=replace-me-never-commit-the-real-value # Optional. Tableau REST API version. Defaults to 3.28. # TABLEAU_API_VERSION=3.28 -# Optional. Localhost port for the Python authoring sidecar. Defaults to 8899. +# Optional. Localhost port for the Python authoring sidecar. +# Defaults to an automatically-chosen free port; set only to pin a specific port. # SIDECAR_PORT=8899 + +# Optional. Target project for `npm run demo` (must exist; not the Default project). +# DEMO_PROJECT=Sales diff --git a/.gitignore b/.gitignore index b68201c..6b3b93d 100644 --- a/.gitignore +++ b/.gitignore @@ -31,3 +31,4 @@ tmp/ .DS_Store .idea/ .vscode/ +.cursor/ diff --git a/ACCEPTANCE.md b/ACCEPTANCE.md index 4ce965d..af62a06 100644 --- a/ACCEPTANCE.md +++ b/ACCEPTANCE.md @@ -17,11 +17,11 @@ Acceptance record for `tableau-mcp-publish` v0.1. |---|---|---|---| | 1 | build clean; lint + test pass | ✅ | `tsc` green, eslint 0, vitest 28/28 | | 2 | sidecar ruff + mypy --strict + pytest pass | ✅ | all three clean, pytest 18/18 | -| 3 | single (≤64MB incl. exactly 64MB) vs chunked (>64MB) + mid-stream abort | ✅ | `tests/restClient.test.ts` — strategy boundary, 3-chunk split, "never finalizes" abort test | +| 3 | single (<64MB) vs chunked (≥64MB incl. exactly 64MB, TSC-aligned) + mid-stream abort | ✅ | `tests/restClient.test.ts` — strategy boundary, 3-chunk split, "never finalizes" abort test | | 4 | `.hyper` round-trip; `.tdsx` zip validity; `.twb` structural binding | ✅ | `sidecar/tests/test_hyper_builder.py`, `test_tds_builder.py`, `test_twb_builder.py` | | 5 | 11 tools w/ descriptions+schemas; publish requires explicit non-Default project, overwrite=false; delete needs confirm; perms allowlist + elevated gate; **PAT never logged (asserted)** | ✅ | `tests/tools.test.ts` (incl. Default-delete refusal, elevated-capability gate), `tests/secrets.test.ts` (PAT log-capture), `resolveProjectId` rejects empty + "Default" | | 6 | CI green on the version matrix | ✅ | run 27881730517 — 5/5 jobs success | -| 7 | live publish: datasource opens + workbook renders ≥1 mark (gated demo) | ⏳ pending | requires a Dev-site PAT; run `npm run demo`. Recorded below once executed. | +| 7 | live publish: datasource opens + workbook renders ≥1 mark (gated demo) | ✅ | `npm run demo` succeeded 2026-06-24 — URLs below; manual mark screenshot still optional | ## Security @@ -30,19 +30,57 @@ remediated (error-body redaction, multipart header sanitization, SQL least-privi elevated-capability gate). Quick LOWs fixed (constant-time token compare, psycopg keyword args, log/`.npmignore` hygiene, csv regular-file check). `undici` bumped to 7.28.0. -## Live demo (criterion 7) — gated +## MCP integration test (2026-06-24) -Authorized by the owner; runs against a Tableau Developer Program site with a PAT supplied at run -time (never logged; `.env` gitignored): +Agency smoke-test of the **Cursor-configured** `tableau` and `tableau-publish` MCP servers. + +| Check | Result | Notes | +|---|---|---| +| Cursor MCP servers connected | ❌ | Both `user-tableau` and `user-tableau-publish` report **errored**; neither appears in the agent tool registry this session. | +| `@tableau/mcp-server` package resolves | ✅ | `npx -y @tableau/mcp-server@latest` downloads v2.18.0; fails fast when `SERVER` is unset. | +| `tableau-mcp-publish` on npm | ❌ | **404 — not published yet.** Cursor config `npx -y tableau-mcp-publish@latest` cannot start until npm publish (gated in DEPLOYMENT.md). | +| Local `tableau-mcp-publish` startup | ✅ | `node dist/index.js` signs in, starts the Python sidecar, and serves stdio when credentials are valid; returns **401** with an invalid PAT (expected). | +| Unit + sidecar test suite | ✅ | `make ci` green locally (28 TS + 18 Python tests). | +| Live MCP tool calls | ⏳ blocked | No `.env` in repo; `PAT_VALUE` in `~/.cursor/mcp.json` is the literal placeholder `…`. | + +**Remediation to get both MCP servers green in Cursor:** + +1. **`tableau-publish`** — point at the local build until npm publish: + ```json + "tableau-publish": { + "command": "node", + "args": ["/Users/sebastienhenry/Documents/Projects/Tableau MCP Publish/dist/index.js"], + "env": { "SERVER": "…", "SITE_NAME": "…", "PAT_NAME": "…", "PAT_VALUE": "" } + } + ``` + Run `npm install && npm run build && cd sidecar && uv sync` once so `dist/` and the sidecar venv exist. + +2. **`tableau`** — keep `npx -y @tableau/mcp-server@latest`; replace `PAT_VALUE: "…"` with the real PAT secret. + +3. **Restart** both servers in Cursor Settings → MCP, then run: + ```bash + cp .env.example .env # fill in real values + npm run test:mcp-smoke + ``` + +**Verdict:** local codebase **SOLID**; live MCP integration **FIX** until config + credentials are corrected. + +## Live demo (criterion 7) + +Executed 2026-06-24 against site `sebaustin`, project `agentic-bi-copilot`: ```bash -export SERVER=… SITE_NAME=… PAT_NAME=… PAT_VALUE=… DEMO_PROJECT="" +# .env must include SERVER, SITE_NAME, PAT_NAME, PAT_VALUE, DEMO_PROJECT npm run demo -- examples/top_customers.csv ``` -**Status: deferred by owner.** The owner chose to skip the live run during this build; the demo is -ready to execute against a Dev site at any time. Record the datasource URL, workbook URL, and a -screenshot confirming the revenue-by-region bar mark here once run. +- Datasource: https://10ax.online.tableau.com/#/site/sebaustin/datasources/25884038 +- Workbook: https://10ax.online.tableau.com/#/site/sebaustin/workbooks/2414706 + +Both live in the **agentic-bi-copilot** project (not Default). LUIDs for API/MCP use: +`4b1d6b72-244b-47f2-b43d-e003280d2d9a` (datasource) and `b26bd287-1a1e-4346-a2e7-9fcc86449cfc` (workbook). + +Open the workbook in Cloud to confirm the revenue-by-region bar mark renders. ## Built / deferred / next @@ -50,5 +88,235 @@ screenshot confirming the revenue-by-region bar mark here once run. `.hyper`/`.tdsx`/`.twb` generation; 46 tests; CI matrix; full docs; SECURITY.md; 3 seeded issues. - **Deferred (non-goals for v0.1):** read/query tools (use the official server); live-connection datasources (issue #2); metadata-driven sheet suggestions (issue #3); map marks (experimental). -- **Next:** run the gated live demo; flip the repo public; open the upstream discussion (issue #1); - publish to npm (gated). +- **Next:** flip the repo public; open the upstream discussion (issue #1); publish to npm (gated); restart Cursor MCP servers after `.env` is set. + +--- + +# Acceptance — Prompt-Driven Authoring (feature) + +Acceptance record for the prompt-driven datasource + dashboard authoring feature +(branch `feat/prompt-driven-authoring`). Adds 3 MCP tools (tools 11 → **14**), a deterministic +stateless BI planner (`src/planner/*`), multi-format file ingest, and real dashboard XML. + +## Verification commands (all green locally) + +| Layer | Command | Result | +|---|---|---| +| TypeScript | `npm run build` / `npm run lint` / `npm test` | build clean · lint 0 · **87 tests pass** | +| Python sidecar | `uv run ruff check .` / `mypy --strict .` / `pytest -q` | ruff clean · mypy clean · **114 tests pass** | +| Full gate | `make ci` | **exit 0** — 201 tests total | +| Security | prod `npm audit --omit=dev` | **0 vulnerabilities** | + +The full feature gate was run as the last step of the build loop and again, independently, by the +solution-verifier (verdict **SOLID**, solution-rubric 5.00/5.00). + +## Success criteria (REQUIREMENTS IDs — verified) + +| ID | Criterion | Status | Evidence | +|---|---|---|---| +| PA-1 | File ingest round-trips for csv/json/jsonl/xlsx/parquet | ✅ | `sidecar/tests/test_hyper_builder_formats.py`; `/datasource/from-file` route → valid `.tdsx` per format (`test_server_new_routes.py`) | +| PA-2 | Unsupported extension errors **before** any sidecar call (0 calls) | ✅ | `tests/tools.test.ts` (spy asserts 0 sidecar calls); `test_file_to_dataframe_unsupported_type` | +| PA-3 | Excel sheet selectable by index / name | ✅ | `test_file_to_dataframe_xlsx_sheet_by_index/name` | +| DB-1 | `` with one `` (NO `type` attribute) per sheet, names match | ✅ | `test_twb_dashboard.py`, `test_server_new_routes.py` | +| DB-2 | Zone geometry: distinct offsets, full coverage Σ==100000, zero overlap (n∈[1,8]) | ✅ | `_tile_zones` unit tests (`test_twb_dashboard.py`) | +| DB-3 | `build_from_plan` → sidecar dashboard build + publish flow | ✅ | `tests/tools.test.ts` E2E-1 (call-count asserts) | +| MA-1 | Autonomous exec → ≤3 sheets, marks ⊂ {bar,line,text}, KPI (text) first | ✅ | `tests/planner.test.ts` (reproduced by verifier) | +| MA-2 | Autonomous analyst → ≤8 sheets | ✅ | `tests/planner.test.ts` | +| MA-3 | Autonomous operational → tiled_vertical, ≥1 text mark | ✅ | `tests/planner.test.ts` | +| MB-1 | Interview mode → 3–7 questions, no `sheets` key | ✅ | `tests/planner.test.ts`, `tests/tools.test.ts` | +| MB-2 | `interview_followup` → DashboardPlan with non-empty rationale | ✅ | `tests/planner.test.ts` | +| MC-1 | Directed exact string (audience analyst) → 2 sheets `[text, bar]` | ✅ | `tests/planner.test.ts` | +| MC-2 | Directed **without** `directions` → validation error | ✅ | guard in `src/tools/designDashboard.ts`; rewritten test asserts `/directions/` | +| E2E-1 | `build_from_plan` with `datasourceSpec.filePath` → 1× buildDatasourceFromFile, 1× dashboard build, 1× publishWorkbook, 1× publishDatasource; audience-derived canvas; LUID-only or SQL-only spec → actionable error | ✅ | `tests/tools.test.ts` (mocked sidecar+REST) | +| E2E-2 | `build_from_plan` with `datasourceSpec.filePath` → datasource built first, `datasourceLuid` returned | ✅ | `tests/tools.test.ts` | +| E2E-3 | Live: published workbook has a rendered dashboard tab on Cloud | ✅ **closed** | `npm run demo:dashboard` executed 2026-06-25 — workbook https://10ax.online.tableau.com/#/site/sebaustin/workbooks/2420435 renders on Cloud. See E2E-3 CLOSED section below. | +| CI-1 | New tests pass on Node 22/24/26 + Python 3.12/3.13 | ✅ (configured) | `.github/workflows/ci.yml` matrix; green locally | +| CI-2 | build/lint/ruff/mypy --strict clean after new modules | ✅ | `make ci` exit 0 | + +**18/18 criteria pass.** E2E-3 (live dashboard render) was executed 2026-06-25 and is confirmed. Gate: 87 TS + 115 Python = 202 tests. + +## Security (added surface) + +STRIDE + dependency review appended to `SECURITY.md`: **0 CRITICAL, 0 HIGH.** Two MEDIUM findings +remediated — PA-1 (`file_to_dataframe` now has a byte cap + row clamp, with tests) and PA-3 +(`fastapi`→0.121.0 pulls `starlette==0.49.3`, clearing 8 transitive advisories). `openpyxl` pinned +(`==3.1.5`). The arbitrary-file-read trust boundary is by-design (the agent already holds the PAT's +authority) and matches the existing `csvPath` posture; the path is never logged. + +## Guardrails verified + +14 tools registered (explicit count test); `build_from_plan` rejects empty/`Default` project and +defaults `overwrite=false`; placeholder tokens (``/``) and audience-invariant +violations are rejected **before** any publish; `design_dashboard` is side-effect-free; PAT/sidecar +token never logged (`tests/secrets.test.ts`); the 46-test baseline regression guard stayed green. + +## Gated live demo (E2E-3 — authorized only) + +```bash +# .env: SERVER, SITE_NAME, PAT_NAME, PAT_VALUE, DEMO_PROJECT (non-Default) +npm run demo:dashboard +``` + +Builds a datasource from a bundled file, runs `design_dashboard` (autonomous, analyst audience) for a +sample business question, calls `build_from_plan`, and prints the published datasource + workbook Cloud +URLs. **Not executed by the agency** (outward write to a live site). Note: free port 8899 first if a +stale sidecar is bound — `kill $(lsof -ti :8899)`. + +## Built / deferred / next + +- **Built:** `create_datasource_from_file` (5 formats), `design_dashboard` (4 modes + 4 audiences, + deterministic & stateless), `build_from_plan` (real ``/`` output, per-audience + canvas sizing); 2 sidecar routes; the planner pipeline (field inference → marks → audience clamp → + plan) implementing the normative `BI_DESIGN.md`; 100 new tests (47 TS + 53 Python); full docs + + ADR-0005; DEPLOYMENT.md gated runbook. +- **Deferred (documented gaps with fallbacks, see `BI_DESIGN.md` §2.3/§4.4):** scatter (`Circle`) and + treemap (`Square`) marks, color encoding, reference lines, and top-N filter emission — each falls + back to a supported mark with a `rationale` annotation until the builder gains the mark class. + E2E-3 live render capture (gated). Stale `REQUIREMENTS.md` naming (`datasourceName` → code uses + `name`) — docs follow the code. +- **Next:** run the gated `npm run demo:dashboard` against the Dev site and paste the dashboard URL + + screenshot here to close E2E-3; consider adding `Circle`/`Square`/color-encoding builder support to + graduate the deferred chart types from fallback to native. + +--- + +# Acceptance — Official Ecosystem Review & Adaptation (2026-06-24) + +Reviewed Tableau's official GitHub org (and `tableau/tableau-ui` specifically) and adapted the project +where warranted. Full findings + citations in [`docs/ecosystem-review/REVIEW.md`](docs/ecosystem-review/REVIEW.md); +plan in [`docs/ecosystem-review/ADAPTATION_PLAN.md`](docs/ecosystem-review/ADAPTATION_PLAN.md). +Plan-loop **PASS 94/100**; build & verify **SOLID 1.00/1.00**. + +## Verification (all green locally) + +| Layer | Command | Result | +|---|---|---| +| TypeScript | `npm run build` / `npm run lint` / `npm test` | clean · 0 · **75 tests pass** | +| Python sidecar | `uv run ruff check .` / `mypy --strict .` / `pytest -q` | clean · clean · **83 tests pass** | +| Full gate | `make ci` | **exit 0** — 158 tests total | + +## Adaptations accepted + +| ID | Adaptation | Status | Evidence | +|---|---|---|---| +| A1 | Official TWB XSD wired as a sidecar fidelity gate (`twb_2026.1.0.xsd` from `tableau/tableau-document-schemas`, pinned SHA) | ✅ | `sidecar/tests/test_twb_schema_validation.py` (4 tests, XXE-safe parser, validity-asserting — proven to reject malformed TWB); vendored XSD + W3C `xml.xsd` + `PROVENANCE.md` | +| A2 | Fixed **6** real structural defects so `build_twb_xml()` output is schema-valid | ✅ | dashboards-before-windows; ``; `