@W-21254846: Implement Pulse registration conditions and unmet-condition instructions - #881
Conversation
Fills in the three pieces left open by the conditional-registration
mechanism: the capability checks, the tool declarations, and the
client-facing explanation.
Capability checks (src/tools/web/registrationConditions.ts):
- RequiresPulse has no dedicated availability endpoint, so checkPulseEnabled
probes the cheapest Pulse read (a one-item page of metric definitions) and
classifies the outcome. PulseDisabledError (site setting off) and
PulseNotAvailableError (Tableau Server) are definitive answers and are not
retried; anything indeterminate is retried and then fails closed. A site
with no definitions still counts as enabled.
- RequiresPulsePremium reads GET /api/-/pulse/entitlements and requires
ENTITLEMENT_TYPE_PULSE_PREMIUM_INSIGHTS to be enabled -- the entitlement
whose absence produces PulseInsightsDisabledError ("requires Tableau+").
It short-circuits on the memoized base Pulse probe first, so a tool
declaring both conditions costs one probe per capability.
The entitlements endpoint is new to the SDK: added the Zodios endpoint,
the response schema (entitlement_type kept as a bare string so a new
service-side enum member cannot fail validation and hide entitled tools),
the getPulseEntitlements method, and the tableau:entitlements:read scope.
Tool declarations: all eight Pulse-backed tools declare RequiresPulse,
including generate-insight-cards, which routes through the Pulse insight
bundle. The three AI-insight tools additionally declare
RequiresPulsePremium.
Initialization message (src/server.web.ts): unmet conditions now append a
per-condition explanation to the initialize instructions, so a Pulse-less
site is told Pulse is off and how to enable it instead of silently
receiving a shorter tool list. Typed as a total Record so adding a
condition without user-facing copy fails to compile.
Note for operators: the premium probe requests tableau:entitlements:read,
and a Connected App rejects a JWT mint asking for an un-granted scope, so
a deployment turning on enforce-registration-conditions must grant that
scope or the premium tools fail closed.
Stephen's notes on tableau#881: - RequiresPulse: don't assume site-admin-disabled; Pulse can also be unavailable for user-level preferences or Tableau Server. Soften to "not available" and keep the Cloud setup link as optional guidance. - MissingConditionCheck: reconnect cannot help (missing code path). Drop the disconnect/reconnect advice and say the check is not implemented.
Live verificationExercised the registration path against a built server with
Notes:
|
Live verification (premium split)Follow-up to the earlier Pulse-unavailable runs. Same setup: built server with
Together with the earlier Server / Pulse-disabled Cloud runs, that covers the four registration outcomes we care about for this change. |
Live verification summaryEnd-to-end checks of the registration-condition path against a built Method
Tool buckets used when scoring results
Results
What this covers
Also confirms the initialize-instruction copy paths for Not claimed here
|
5f5e496
into
tableau:add-conditional-registration
* add requireAdmin to tools * Fetch userRole during tool reg * change requireAdmin to requiredRoles * Add version bump * Add min role hierarchy * role value changes * add support user and remove cache * add role requirements feature flag and bump version * make SupportUser Admin * add retry and tool omission message * add retries * add registration context and lazy fetching * add conditional registration * update fetch user role logic * chore: bump version to 4.8.2 * chore: bump version to 4.8.3 * @W-21254846: Implement Pulse registration conditions and unmet-condition instructions (#881) * implement Pulse registration conditions and unmet-condition instructions Fills in the three pieces left open by the conditional-registration mechanism: the capability checks, the tool declarations, and the client-facing explanation. Capability checks (src/tools/web/registrationConditions.ts): - RequiresPulse has no dedicated availability endpoint, so checkPulseEnabled probes the cheapest Pulse read (a one-item page of metric definitions) and classifies the outcome. PulseDisabledError (site setting off) and PulseNotAvailableError (Tableau Server) are definitive answers and are not retried; anything indeterminate is retried and then fails closed. A site with no definitions still counts as enabled. - RequiresPulsePremium reads GET /api/-/pulse/entitlements and requires ENTITLEMENT_TYPE_PULSE_PREMIUM_INSIGHTS to be enabled -- the entitlement whose absence produces PulseInsightsDisabledError ("requires Tableau+"). It short-circuits on the memoized base Pulse probe first, so a tool declaring both conditions costs one probe per capability. The entitlements endpoint is new to the SDK: added the Zodios endpoint, the response schema (entitlement_type kept as a bare string so a new service-side enum member cannot fail validation and hide entitled tools), the getPulseEntitlements method, and the tableau:entitlements:read scope. Tool declarations: all eight Pulse-backed tools declare RequiresPulse, including generate-insight-cards, which routes through the Pulse insight bundle. The three AI-insight tools additionally declare RequiresPulsePremium. Initialization message (src/server.web.ts): unmet conditions now append a per-condition explanation to the initialize instructions, so a Pulse-less site is told Pulse is off and how to enable it instead of silently receiving a shorter tool list. Typed as a total Record so adding a condition without user-facing copy fails to compile. Note for operators: the premium probe requests tableau:entitlements:read, and a Connected App rejects a JWT mint asking for an un-granted scope, so a deployment turning on enforce-registration-conditions must grant that scope or the premium tools fail closed. * address review: genericize Pulse omit copy Stephen's notes on #881: - RequiresPulse: don't assume site-admin-disabled; Pulse can also be unavailable for user-level preferences or Tableau Server. Soften to "not available" and keep the Cloud setup link as optional guidance. - MissingConditionCheck: reconnect cannot help (missing code path). Drop the disconnect/reconnect advice and say the check is not implemented. * chore: bump version to 4.8.4 * Update comments and remove unneccesary scope * chore: bump version to 4.8.5 * Remove lazy fetching * chore: bump version to 4.8.6 * update comment * chore: bump version to 4.10.0 * update comments * Fix lint * update comments --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: dplatt84 <dplatt@salesforce.com>
Description
Builds on #871, which added the
registrationConditionstool property and the omission logic forRequiresPulse/RequiresPulsePremium. That PR left three things open: how to actually check theconditions, declaring them on the Pulse tools, and an initialization message explaining why Pulse
was unavailable. This adds all three, plus tests for the Pulse enablement scenarios.
1. Capability checks (
src/tools/web/registrationConditions.ts)RequiresPulse— there is no endpoint that reports Pulse availability directly, socheckPulseEnabledprobes the cheapest Pulse read available (a one-item page of metricdefinitions) and classifies the outcome:
OkPulseDisabledErrorPulseNotAvailableErrorThe two Pulse-specific errors are definitive answers rather than failures, so retrying them would
just delay registration for every caller on a Pulse-less site. The retry predicate mirrors
getCurrentUserSiteRolebut also understandsMcpToolError, which is what the Pulse methodsreturn instead of a raw
AxiosError.RequiresPulsePremium— readsGET /api/-/pulse/entitlementsand requiresENTITLEMENT_TYPE_PULSE_PREMIUM_INSIGHTSto be enabled. That is the entitlement whose absenceproduces
PulseInsightsDisabledError("requires Tableau+"). It short-circuits on the memoizedbase-Pulse probe first, since premium is a superset of base Pulse — so a tool declaring both
conditions costs one probe per capability, not two.
The entitlements endpoint was not in the SDK yet, so this also adds the Zodios endpoint, the
response schema,
getPulseEntitlements, and thetableau:entitlements:readscope.2. Tool declarations
All eight Pulse-backed tools declare
RequiresPulse. That includesgenerate-insight-cards, whichisn't named like a Pulse tool but routes through
generatePulseMetricValueInsightBundle. The threeAI-insight tools (
generate-pulse-insight-brief,generate-pulse-metric-value-insight-bundle,generate-insight-cards) additionally declareRequiresPulsePremium.3. Initialization message (
src/server.web.ts)Replaces the
TODOwith one explanation per distinct unmet condition, appended to the initializeinstructions. Registration runs before the transport connects, so the instructions are the only
channel that reaches the user — without this a Pulse-less site just gets a shorter tool list and no
reason for it. The copy is a total
Record<RegistrationCondition, string>, so adding a conditionwithout user-facing copy is a compile error rather than a silent omission.
Motivation and Context
Continues the goal from #871: register only the tools that are actually usable for a given site, and
tell the user when something was withheld and why.
Notes for reviewers
Two deliberate decisions worth a look:
entitlement_typeis a bare string, not a Zod enum. The service owns that enum, and avalidation error here happens at registration time — it would fail closed and hide tools from
entitled customers. Forward compatibility is worth more than strictness in this specific spot.
tableau:entitlements:read. Per the existing notes inscopes.ts,a Connected App rejects a JWT mint that asks for an un-granted scope, so any deployment turning
on
enforce-registration-conditionsmust grant that scope or the premium tools will fail closed.Everything stays behind that default-off flag, so there is no change in behavior until it is
enabled — but it should be called out in the rollout notes.
One open question:
ENTITLEMENT_TYPE_PULSE_PREMIUM_GAIis a sibling entitlement that gatesgenerative-AI features specifically. Mapping the single coarse
RequiresPulsePremiumcondition toPULSE_PREMIUM_INSIGHTScovers the insight-bundle tools cleanly, but the conversational insightbrief is arguably GAI. Splitting the condition is a reasonable follow-up if that distinction matters
in practice.
Type of Change
How Has This Been Tested?
Unit tests.
registrationConditions.test.tscovers the enablement scenarios end to end: Pulseenabled with and without definitions, disabled by site setting, unavailable on Tableau Server,
retry-then-succeed, exhausted retries, 4xx not retried, 5xx retried, memoization across tools, and
for premium — entitlement enabled, disabled, absent from the response, a different premium
entitlement not being mistaken for it, and the short-circuit when base Pulse is off.
server.web.test.tscovers registration and the initialize instructions in both directions.Full unit suite: 2895 passing.
tsc --noEmitandeslintclean over the changed files.Related Issues
Stacked on #871. Work item: @W-21254846
Checklist
npm run version. Not bumpeddeliberately — the base branch is still at 4.8.0, and bumping in a stacked PR would just
conflict. Worth a single bump when the stack lands.