Skip to content

Add multi-org support: list_orgs and switch_org MCP tools#164

Open
rohitthughtspot wants to merge 1 commit into
thoughtspot:mainfrom
rohitthughtspot:multi-org-support-list-switch
Open

Add multi-org support: list_orgs and switch_org MCP tools#164
rohitthughtspot wants to merge 1 commit into
thoughtspot:mainfrom
rohitthughtspot:multi-org-support-list-switch

Conversation

@rohitthughtspot

Copy link
Copy Markdown

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.

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.

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

Comment thread static/oauth-callback.js
Comment on lines +137 to +138
const accessToken = (raw && raw.data && raw.data.token) || raw.token;
const data = { data: { token: accessToken, refreshToken: raw.refreshToken } };

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.

high

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.

Suggested change
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 } };

Comment thread src/servers/mcp-server.ts
Comment on lines +107 to +110
const currentOrgId =
this.sessionInfo?.currentOrgId !== undefined
? String(this.sessionInfo.currentOrgId)
: undefined;

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

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.

Suggested change
const currentOrgId =
this.sessionInfo?.currentOrgId !== undefined
? String(this.sessionInfo.currentOrgId)
: undefined;
const currentOrgId =
this.sessionInfo?.currentOrgId
? String(this.sessionInfo.currentOrgId)
: undefined;
References
  1. 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.

Comment thread src/servers/mcp-server.ts
Comment on lines +706 to +708
if (sessionInfo.currentOrgId !== undefined) {
activeOrgId = String(sessionInfo.currentOrgId);
}

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

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.

Suggested change
if (sessionInfo.currentOrgId !== undefined) {
activeOrgId = String(sessionInfo.currentOrgId);
}
if (sessionInfo.currentOrgId) {
activeOrgId = String(sessionInfo.currentOrgId);
}
References
  1. 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.

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