Add multi-org support: list_orgs and switch_org MCP tools#164
Add multi-org support: list_orgs and switch_org MCP tools#164rohitthughtspot wants to merge 1 commit into
Conversation
Add two MCP tools that let a connected user discover the orgs they belong to and switch the active org mid-session, without re-authenticating. - list_orgs: lists the user's orgs (via session/orgs), marking the active one. - switch_org: sets the active org; mints and caches an org-scoped bearer token for it and uses it for that org's calls. Token model: - Global cluster token (props.accessToken) is used to list orgs and to mint per-org tokens. - Per-org tokens are minted on demand via auth/token/fetch with org_identifier and cached in-memory, keyed by org. - An org-scoped token for the session's current org is minted on connect (postInit) so an active org token always exists. - Requests carry the x-thoughtspot-orgs header for the active org. - Login (oauth-callback) fetches the token via gettoken?refresh=true.
There was a problem hiding this comment.
Code Review
This pull request introduces multi-org (tenant) support to the ThoughtSpot MCP server by adding list_orgs and switch_org tools, implementing org-scoping headers, and minting org-scoped bearer tokens. The review feedback suggests adding a safety check in the OAuth callback flow to prevent a potential crash if the token response is null or undefined, and replacing explicit undefined checks with truthiness checks for currentOrgId to avoid converting null or undefined values into the string 'null'.
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 accessToken = (raw && raw.data && raw.data.token) || raw.token; | ||
| const data = { data: { token: accessToken, refreshToken: raw.refreshToken } }; |
There was a problem hiding this comment.
If the response.json() call resolves to null or undefined (for example, due to an empty or unexpected response body), attempting to access raw.token or raw.refreshToken will throw a TypeError and crash the OAuth callback flow in the browser. Adding a safety check to ensure raw is defined before accessing its properties prevents this crash.
| const accessToken = (raw && raw.data && raw.data.token) || raw.token; | |
| const data = { data: { token: accessToken, refreshToken: raw.refreshToken } }; | |
| const accessToken = raw ? ((raw.data && raw.data.token) || raw.token) : undefined; | |
| const data = { data: { token: accessToken, refreshToken: raw ? raw.refreshToken : undefined } }; |
| const currentOrgId = | ||
| this.sessionInfo?.currentOrgId !== undefined | ||
| ? String(this.sessionInfo.currentOrgId) | ||
| : undefined; |
There was a problem hiding this comment.
If this.sessionInfo.currentOrgId is null or undefined, converting it directly might result in an invalid identifier string like "null". Since valid identifiers in this domain are never falsy, a simple truthiness check is sufficient to validate its existence and safely convert it to a string.
| const currentOrgId = | |
| this.sessionInfo?.currentOrgId !== undefined | |
| ? String(this.sessionInfo.currentOrgId) | |
| : undefined; | |
| const currentOrgId = | |
| this.sessionInfo?.currentOrgId | |
| ? String(this.sessionInfo.currentOrgId) | |
| : undefined; |
References
- When validating the existence of identifiers, truthiness checks are sufficient if the domain guarantees that valid identifiers are never falsy (e.g., non-empty strings and non-zero numbers), which simplifies existence checks compared to explicit undefined or null checks.
| if (sessionInfo.currentOrgId !== undefined) { | ||
| activeOrgId = String(sessionInfo.currentOrgId); | ||
| } |
There was a problem hiding this comment.
If sessionInfo.currentOrgId is null or undefined, converting it directly might result in an invalid identifier string like "null". Since valid identifiers in this domain are never falsy, a simple truthiness check is sufficient to validate its existence and safely convert it to a string.
| if (sessionInfo.currentOrgId !== undefined) { | |
| activeOrgId = String(sessionInfo.currentOrgId); | |
| } | |
| if (sessionInfo.currentOrgId) { | |
| activeOrgId = String(sessionInfo.currentOrgId); | |
| } |
References
- When validating the existence of identifiers, truthiness checks are sufficient if the domain guarantees that valid identifiers are never falsy (e.g., non-empty strings and non-zero numbers), which simplifies existence checks compared to explicit undefined or null checks.
Add two MCP tools that let a connected user discover the orgs they belong to and switch the active org mid-session, without re-authenticating.
Token model:
Global cluster token (props.accessToken) is used to list orgs and to mint per-org tokens.
Per-org tokens are minted on demand via auth/token/fetch with org_identifier and cached in-memory, keyed by org.
An org-scoped token for the session's current org is minted on connect (postInit) so an active org token always exists.
Requests carry the x-thoughtspot-orgs header for the active org.
Login (oauth-callback) fetches the token via gettoken?refresh=true.