SCAL-319970 Add OAuth-gated list_orgs tool#166
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces the list_orgs tool, which is restricted to OAuth-authenticated connections. It adds the necessary schemas, types, and service methods to search and list active Orgs from the ThoughtSpot instance. Feedback on these changes suggests fetching session info dynamically to ensure current_org_id is reliably populated, and adding a defensive nullish coalescing guard when filtering the upstream Orgs response to prevent potential runtime errors.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| const results: Org[] = orgs | ||
| .filter( | ||
| (org): org is typeof org & { id: number } => | ||
| typeof org.id === "number" && org.status === "ACTIVE", | ||
| ) |
There was a problem hiding this comment.
The orgs returned from this.client.searchOrgs could potentially be null or undefined if the upstream API returns an empty or unexpected response. To prevent a runtime TypeError when calling .filter() on a nullish value, we should add a defensive nullish coalescing guard (orgs ?? []).
| const results: Org[] = orgs | |
| .filter( | |
| (org): org is typeof org & { id: number } => | |
| typeof org.id === "number" && org.status === "ACTIVE", | |
| ) | |
| const results: Org[] = (orgs ?? []) | |
| .filter( | |
| (org): org is typeof org & { id: number } => | |
| typeof org.id === "number" && org.status === "ACTIVE", | |
| ) |
1cd84a5 to
644f5d2
Compare
Add a new `list_orgs` MCP tool that lists the ACTIVE Orgs configured on the ThoughtSpot instance, available only to connections authenticated via OAuth. Auth method is per client connection (decided by the endpoint the client connects to), not per user or per deployment. To gate on it at runtime, this introduces an explicit `authMode` field on the connection `Props`: - OAuth routes (/mcp, /sse) set authMode = "oauth" - Static-token routes (/bearer/*, /token/*) set authMode = "bearer" | "token" The tool is gated with defense in depth: - Filtered out of listTools() for non-OAuth connections - Rejected in callTool() if invoked directly by a non-OAuth connection Data comes from the public POST /api/rest/2.0/orgs/search endpoint (client.searchOrgs), filtered to ACTIVE orgs. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
644f5d2 to
d0fff63
Compare
…rgs-enabled Build out multi-org support on top of the OAuth-gated list_orgs tool. Tools - Add switch_org: mints an org-scoped bearer token for the requested org and makes subsequent tool calls operate against it. No pre-validation against list_orgs — a 401 from minting is surfaced as "org not accessible". - Gate both list_orgs and switch_org on OAuth AND cluster Orgs-enabled (configInfo.orgsConfiguration.enabled from session info), failing closed. Hidden from listTools and rejected in callTool when unavailable. Token flow - OAuth callback fetches the global token via session/v2/gettoken?refresh=true and stores token + refreshToken + tokenCreatedTime + tokenExpiryDuration. - getRefreshedToken sends the refresh token via the X-Refresh-Token header (verified this is what mints a fresh access token). - Org-scoped tokens are minted on demand from the global token and cached in-memory per instance. Durable active org (multi-session safe) - The active org is stored in a per-user Durable Object instance keyed by hash(refreshToken ?? accessToken) via the existing conversation-storage DO namespace. The refresh-token hash is stable across the access token's 24h rotation and shared across the multiple MCP sessions/DOs a single client opens, so a switch in one session is visible to the others. Conversation storage is re-keyed onto the same hash so it survives token refresh too. - The active org is NOT stored in props (props are rebuilt from the OAuth grant on every request and would clobber it). Tests - Org tool gate (OAuth + orgs-enabled, fail-closed), getStorageKeyHash (refresh-token preference + access-token fallback), isOrgsEnabled, searchOrgs ACTIVE filtering, fetchOrgBearerToken/getRefreshedToken delegation, switch_org persistence + 401 path, and shared active-org store across instances. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a new
list_orgsMCP tool that lists the ACTIVE Orgs configured on the ThoughtSpot instance, available only to connections authenticated via OAuth.Auth method is per client connection (decided by the endpoint the client connects to), not per user or per deployment. To gate on it at runtime, this introduces an explicit
authModefield on the connectionProps:The tool is gated with defense in depth:
Data comes from the public POST /api/rest/2.0/orgs/search endpoint (client.searchOrgs), filtered to ACTIVE orgs. The response includes the current_org_id read from session info.