Skip to content

SCAL-319970 Add OAuth-gated list_orgs tool#166

Open
rohitthughtspot wants to merge 2 commits into
thoughtspot:mainfrom
rohitthughtspot:SCAL-319970
Open

SCAL-319970 Add OAuth-gated list_orgs tool#166
rohitthughtspot wants to merge 2 commits into
thoughtspot:mainfrom
rohitthughtspot:SCAL-319970

Conversation

@rohitthughtspot

Copy link
Copy Markdown

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. The response includes the current_org_id read from session info.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread src/servers/mcp-server.ts
Comment thread src/thoughtspot/thoughtspot-service.ts Outdated
Comment on lines +737 to +741
const results: Org[] = orgs
.filter(
(org): org is typeof org & { id: number } =>
typeof org.id === "number" && org.status === "ACTIVE",
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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 ?? []).

Suggested change
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",
)

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>
…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>
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.

1 participant