@W-22350774: Add Tableau Prep flows tools (list-flows, get-flow)#344
@W-22350774: Add Tableau Prep flows tools (list-flows, get-flow)#344showmethecold wants to merge 1 commit into
Conversation
25765ac to
358bed6
Compare
ca1ff10 to
47b3d95
Compare
|
@showmethecold — 🤖 MattGPT (Matt Filbert's agent) Onboards Tableau Prep flows with two read-only tools ( Highest-signal points for reviewers:
One open decision: |
12cc2ce to
8a8975c
Compare
8a8975c to
d407919
Compare
ccrick-tableau
left a comment
There was a problem hiding this comment.
Thank you! These work great in my local testing. Appreciate adding these.
d407919 to
cfd1565
Compare
| // includeConnections:false / includeFlowRuns:false. Compute the | ||
| // smallest viable scope set per call so metadata-only deployments | ||
| // succeed. | ||
| const tableauApiScopes: TableauApiScope[] = [ |
There was a problem hiding this comment.
ideally we should utilize the defined scopes from scopes.ts, instead of defining them. maybe we could have the getflow scopes var as the minimum instead of maximum scopes, and then add these other scopes as variables also from scopes.ts? i dont like the magic scope values floating around here
There was a problem hiding this comment.
Pulled all four scope values into named constants in scopes.ts:
GET_FLOW_BASE_API_SCOPES (the always-required minimum: flows:read + mcp_site_settings:read)
GET_FLOW_CONNECTIONS_API_SCOPE and GET_FLOW_RUNS_API_SCOPE for the optional sidecars
getFlow.ts now starts from the base (minimum) set and conditionally appends the connections/runs scopes only when the caller opts into those sidecars — all imported from scopes.ts. And toolScopeMap['get-flow'] (the max set for the OAuth gate) is composed from those same constants, so it's a single source of truth for both the per-call minimum and the maximum surface.
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:
- Gate all flow tools behind FLOW_TOOLS_ENABLED, disabled by default; set
it to "true" to register list-flows and get-flow (mirrors
ADMIN_TOOLS_ENABLED). Individual tools can still be excluded via
EXCLUDE_TOOLS.
- 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.
cfd1565 to
a31f97d
Compare
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 forownerName, or a non-UUID forprojectId). Every response carries anmcp.resultInfocompleteness 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 undermcp.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 aFLOW_RUNS_TRUNCATEDwarning 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-flowsintentionally diverges from the existinglist-*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/pageSizeoffset pagination as caller-managed state,list-flowspaginates internally and returns a single bounded result:min(caller 'limit', admin MAX_RESULT_LIMIT); when neither is set, the admin cap (if configured) is the backstop.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.pageNumberpagination or a date-rangefilter), 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-flowsresponse carriesmcp.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-flowsis currently the only tool that emitsmcp.resultInfo— the otherlist-*/get-*tools return bare arrays. We're comfortable shipping it scoped tolist-flows, but flagging it for a team decision on whether this completeness contract (especiallytruncated/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-flownow enforces the server bounded context. Previously it fetched any flow by ID, ignoring the operator'sPROJECT_IDS/TAGSallow-list — a scoping gapget-workbookalready closed. AddedFlowNotAllowedError(403) andresourceAccessChecker.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 intableau:content:read.Auth scopes
Added MCP scope
tableau:mcp:flow:readand Tableau API scopestableau:flows:read,tableau:flow_connections:read,tableau:flow_runs:read; wired intotoolScopeMapand OAuth defaults.get-flowrequests only the scopes its requested sidecars need — connection/run scopes are added only whenincludeConnections/includeFlowRunsare true — so a metadata-only call succeeds against a connected app that grants onlytableau:flows:read.Operational kill switch:
FLOW_TOOLS_ENABLEDAdded a single environment-variable switch so operators can turn all flow tools off at once if something goes wrong, without editing per-tool exclusions.
FLOW_TOOLS_ENABLEDis explicitly set tofalse(any other value — or unset — leaves them on). This mirrors the existingADMIN_TOOLS_ENABLEDgating pattern, with the default inverted since these tools ship on.config.flowToolsEnabledand each tool'sdisabledprovider (disabled: !config.flowToolsEnabled), soserver.web.tsfilters bothlist-flowsandget-flowout of the manifest at registration time. Any future flow tool gated the same way is covered automatically.EXCLUDE_TOOLS(e.g.EXCLUDE_TOOLS=flowdrops the whole flow group);FLOW_TOOLS_ENABLED=falseis the one-switch shortcut.env-vars.mdand registered in the Claude MCP bundle manifest.Shared parser fix
parseAndValidateFilterStringpreviously split filter strings on every comma, shredding multi-element:in:lists likename:in:[A,B]into broken sub-expressions. Live verification on REST 3.30 confirmed everylist-*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
mcp.resultInfotruncation signaling, theownerName/projectIdrecovery hints, everyget-flowsidecar combination, bounded-context allow/deny (isFlowAllowed), theFLOW_RUNS_TRUNCATED"+1 probe" cases, and bracket-aware:in:parsing. Full unit suite green.FLOW_TOOLS_ENABLED: config parsing (default-on,falsedisables, any non-falsevalue stays on), per-tooldisabledgating on bothlist-flowsandget-flow, and end-to-end server-registration tests driving the realregisterTools()pipeline — both flow tools drop from the manifest whenFLOW_TOOLS_ENABLED=falsewhile unrelated tools (e.g.list-datasources) remain registered.tests/eval/flows.test.ts, site-data-independent) guarding tool selection + argument generation, so the trimmed descriptions still steer the model correctly.totalAvailable,get-flowsidecar combinations, live run-history truncation, recovery hints, and error paths (invalid filter, unknown flow id).docs/docs/tools/flows/with sizing guidance, filter semantics (case sensitivity, whitespace, ISO 8601Zrequirement), the recovery-hints contract, and a "Required Tableau API scopes" section per tool (flows use the dedicatedtableau:flows:readscope). DocumentedFLOW_TOOLS_ENABLEDinenv-vars.md(default,false-to-disable semantics, and theEXCLUDE_TOOLS=flowalternative).Type of Change
Related Issues
Closes #373
Checklist
npm run version. For example,use
npm run version:patchfor a patch version bump.environment variable or changing its default value.
Contributor Agreement
By submitting this pull request, I confirm that:
its Contribution Checklist.