Skip to content

@W-22350774: Add Tableau Prep flows tools (list-flows, get-flow)#344

Open
showmethecold wants to merge 1 commit into
tableau:mainfrom
showmethecold:jmao/onboard-prep-flows
Open

@W-22350774: Add Tableau Prep flows tools (list-flows, get-flow)#344
showmethecold wants to merge 1 commit into
tableau:mainfrom
showmethecold:jmao/onboard-prep-flows

Conversation

@showmethecold

@showmethecold showmethecold commented May 11, 2026

Copy link
Copy Markdown

Onboard Tableau Prep flows into the MCP server with two new read-only tools backed by Tableau's public REST API for flows.

New tools

  • list-flows: paginated listing with filter/sort matching Tableau's Flows API (filter fields: name, projectId, projectName, ownerName, createdAt, updatedAt; sort fields: name, createdAt, updatedAt). Empty-result messages include recovery hints when callers pass a value shape Tableau silently rejects (e.g. login/email/UUID for ownerName, or a non-UUID for projectId). Every response carries an mcp.resultInfo completeness signal (see Design note).
  • get-flow: primary flow metadata + output steps, with optional sidecar fetches for connections and recent flow runs. Both sidecars are best-effort; failures degrade to structured warnings under mcp.warnings (SIDECAR_FETCH_FAILED, VERSION_GATE_SKIPPED). The runs sidecar uses a "+1 probe" to detect truncation (Tableau's runs endpoint returns no pagination block); truncation surfaces as a FLOW_RUNS_TRUNCATED warning so callers can distinguish a complete history from a windowed view.

Design note: result completeness & pagination (for maintainers' consideration)

This is the one place list-flows intentionally diverges from the existing list-* tools, so calling it out explicitly.

Bounded results + an explicit completeness signal — no caller-facing cursor in v0.
A site can hold thousands of flows. Rather than surface Tableau's pageNumber/pageSize offset pagination as caller-managed state, list-flows paginates internally and returns a single bounded result:

  • The effective cap is min(caller 'limit', admin MAX_RESULT_LIMIT); when neither is set, the admin cap (if configured) is the backstop.
  • Even an explicit "list all flows" request is capped at MAX_RESULT_LIMIT — the tool never streams an unbounded set. This is deliberate: it keeps a single tool call bounded/predictable and protects the model's context-window/token budget.
  • We chose not to expose a cursor in v0 because (a) it pushes multi-call continuation state onto the LLM for marginal benefit, and (b) callers needing deep/complete enumeration are better served by the Tableau Flows REST API directly (pageNumber pagination or a date-range filter), which the tool description and docs point to. A cursor can be added later without changing the response shape.

Because results are bounded, completeness is made explicit. Every list-flows response carries mcp.resultInfo:

  • returnedCount — flows in this response.
  • truncated + truncationReason (requested-limit | admin-cap) — whether more matched than were returned, and why.
  • totalAvailable — Tableau's server-side total for the query, so the model can honestly report "showing N of M" and decide whether to narrow the filter. Surfaced only when no bounded context (PROJECT_IDS/TAGS) is configured, because that count is taken before the tool's allow-list filtering and would otherwise overstate the accessible total.

For the Tableau MCP team: list-flows is currently the only tool that emits mcp.resultInfo — the other list-*/get-* tools return bare arrays. We're comfortable shipping it scoped to list-flows, but flagging it for a team decision on whether this completeness contract (especially truncated/totalAvailable) should be standardized across the other tools, since silent truncation under an admin cap is a latent issue for any list tool. Happy to generalize it if the team prefers.

Security: bounded-context enforcement

get-flow now enforces the server bounded context. Previously it fetched any flow by ID, ignoring the operator's PROJECT_IDS/TAGS allow-list — a scoping gap get-workbook already closed. Added FlowNotAllowedError (403) and resourceAccessChecker.isFlowAllowed, and gated the fetch behind it (reusing the flow the checker already fetched), with a dedicated flow scope set so the checker doesn't drag in tableau:content:read.

Auth scopes

Added MCP scope tableau:mcp:flow:read and Tableau API scopes tableau:flows:read, tableau:flow_connections:read, tableau:flow_runs:read; wired into toolScopeMap and OAuth defaults. get-flow requests only the scopes its requested sidecars need — connection/run scopes are added only when includeConnections/includeFlowRuns are true — so a metadata-only call succeeds against a connected app that grants only tableau:flows:read.

Shared parser fix

parseAndValidateFilterString previously split filter strings on every comma, shredding multi-element :in: lists like name:in:[A,B] into broken sub-expressions. Live verification on REST 3.30 confirmed every list-* tool that advertises :in: was silently broken on multi-element lists. Replaced the naive split with a bracket-aware top-level split (repo-wide fix).

Descriptions: leaner & version-agnostic

Trimmed the tool descriptions (~−23%) for token budget (some clients cap the whole toolset ~5k tokens) and to follow the repo convention that descriptions stay version-agnostic (runtime gating handles version compatibility): de-duplicated repeated filter contracts, collapsed the run-history section, merged the sizing sections, trimmed examples, and removed >= 3.10 / REST 3.30 references from the descriptions.

Tests & docs

  • Unit tests for both tools and the parser fix, covering filter field/operator validation and rejection, pagination + the mcp.resultInfo truncation signaling, the ownerName/projectId recovery hints, every get-flow sidecar combination, bounded-context allow/deny (isFlowAllowed), the FLOW_RUNS_TRUNCATED "+1 probe" cases, and bracket-aware :in: parsing. Full unit suite green.
  • Description-quality eval (tests/eval/flows.test.ts, site-data-independent) guarding tool selection + argument generation, so the trimmed descriptions still steer the model correctly.
  • Manual end-to-end validation against a live Tableau Cloud site: filtering across each supported field/operator, sort direction, limit/pagination truncation + totalAvailable, get-flow sidecar combinations, live run-history truncation, recovery hints, and error paths (invalid filter, unknown flow id).
  • User docs under docs/docs/tools/flows/ with sizing guidance, filter semantics (case sensitivity, whitespace, ISO 8601 Z requirement), the recovery-hints contract, and a "Required Tableau API scopes" section per tool (flows use the dedicated tableau:flows:read scope).

Type of Change

  • Bug fix
  • New feature
  • Breaking change
  • Documentation update
  • Other (please describe):

Related Issues

Closes #373

Checklist

  • I have updated the version in the package.json file by using npm run version. For example,
    use npm run version:patch for a patch version bump.
  • I have made any necessary changes to the documentation
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes
  • I have documented any breaking changes in the PR description. For example, renaming a config
    environment variable or changing its default value.

Contributor Agreement

By submitting this pull request, I confirm that:

@showmethecold showmethecold force-pushed the jmao/onboard-prep-flows branch 2 times, most recently from 25765ac to 358bed6 Compare May 13, 2026 02:40
@showmethecold showmethecold changed the title Add Tableau Prep flows tools (list-flows, get-flow) Draft: Add Tableau Prep flows tools (list-flows, get-flow) May 13, 2026
@showmethecold showmethecold force-pushed the jmao/onboard-prep-flows branch from ed3f85f to ca1ff10 Compare May 29, 2026 01:05
Add two read-only MCP tools that wrap the public Tableau REST Flows API:

- list-flows: lists Prep flows on a site with field:operator:value
  filtering (createdAt, name, ownerName, projectId, projectName, updatedAt)
  and sorting. Every successful response includes an always-present
  mcp.resultInfo { returnedCount, truncated, truncationReason?,
  totalAvailable? } so a caller can distinguish a complete list from one
  cut short by the caller's limit or the admin MAX_RESULT_LIMIT cap.
  totalAvailable is surfaced only when no server-side bounded context
  (PROJECT_IDS/TAGS) is configured, since that count is taken before the
  tool's allow-list filtering.
- get-flow: retrieves a single flow with optional connections and run
  history. Sidecar OAuth scopes are requested only when the corresponding
  sidecar is included, and access is gated by the bounded-context resource
  checker (isFlowAllowed) so PROJECT_IDS/TAGS restrictions apply. Run
  history is bounded by flowRunLimit and emits a FLOW_RUNS_TRUNCATED
  warning when more runs exist.

Supporting changes:
- Add the flow REST API definitions, Zod types, and methods, plus the
  dedicated tableau:flows:read, tableau:flow_connections:read, and
  tableau:flow_runs:read scopes.
- Harden parseAndValidateFilterString bracket / in: list handling
  (repo-wide fix).
- Make project.description optional (Tableau returns it on embedded
  <project> elements).
- Tools, types, unit tests, a description-quality eval test, and docs for
  both tools.
@showmethecold showmethecold force-pushed the jmao/onboard-prep-flows branch from ca1ff10 to 47b3d95 Compare June 2, 2026 16:23
@showmethecold showmethecold changed the title Draft: Add Tableau Prep flows tools (list-flows, get-flow) @W-22350774: Add Tableau Prep flows tools (list-flows, get-flow) Jun 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support Tableau Prep flows: add list-flows and get-flow tools

1 participant