Skip to content

[SCAL-323294] changes for fetch_data tool - #179

Open
saharsh-ts wants to merge 14 commits into
mainfrom
SCAL-323294_fetch-data
Open

[SCAL-323294] changes for fetch_data tool#179
saharsh-ts wants to merge 14 commits into
mainfrom
SCAL-323294_fetch-data

Conversation

@saharsh-ts

Copy link
Copy Markdown
Collaborator

No description provided.

@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 two new tools, search_objects and fetch_data, to the MCP server for searching and retrieving data from ThoughtSpot objects (Answers and Liveboards). It includes new schemas, gRPC-backed API clients, terminology mapping for synonyms, and comprehensive unit tests. The reviewer identified a critical issue in src/servers/mcp-server.ts where the asynchronous validateConnection method is not awaited in the Ping tool case, causing the check to always evaluate to true.

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 Outdated
case ToolName.Ping: {
if (this.ctx.props.accessToken && this.ctx.props.instanceUrl) {
if (!this.getThoughtSpotService(recorder).validateConnection()) {
if (!this.getThoughtSpotService(recorder).validateConnection()) {

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

The validateConnection method is asynchronous and returns a Promise<boolean>. Since it is not awaited here, the condition !promise will always evaluate to false because any Promise object is truthy. This effectively bypasses the connection validation check. Please add await to correctly evaluate the promise result.

Suggested change
if (!this.getThoughtSpotService(recorder).validateConnection()) {
if (!(await this.getThoughtSpotService(recorder).validateConnection())) {
References
  1. When a method returns a Promise, ensure it is awaited to prevent race conditions and potential data loss, especially when subsequent operations depend on its completion.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

added await

@saharsh-ts
saharsh-ts force-pushed the SCAL-323294_fetch-data branch from 2b7918b to de4930a Compare July 13, 2026 07:47
@mouryabalabhadra

Copy link
Copy Markdown
Collaborator

@copilot review the pr

Copilot AI commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

@copilot review the pr

Reviewed the PR and addressed one issue: fetch_data now rejects empty visualization_ids instead of treating it as “fetch all”. Added validation coverage in tests as well. Fixed in commit 352f265.

@saharsh-ts

Copy link
Copy Markdown
Collaborator Author

@copilot review the pr

@saharsh-ts
saharsh-ts force-pushed the SCAL-323294_fetch-data branch from 6454d11 to ddaf1fa Compare July 20, 2026 09:47
Comment thread src/servers/tool-definitions.ts Outdated
),
columns: z.array(z.string()).describe("The column names, in order."),
data_rows: z
.array(z.array(z.unknown()))

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Is there a way to add a proper type contract

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Tightened to string | number | boolean | null datatypes.


// Round numeric cells to 2 decimals: collapses FP noise and trims payload.
// Non-numbers and non-finite values pass through untouched.
function roundCell(value: unknown): unknown {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This seems arbitrary, what if the customer wants more precision? I feel we should return the full value for now, and not prematurely optimize this.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Removed — cells now returned at full precision.

// treats it as "must hold the whole viz" and 500s when it's too small.
// Start at maxRows; on that error, bump to the required count and refetch,
// then cap rows client-side below. Attempts are bounded (a full Liveboard
// can report a larger viz on each retry) to avoid a runaway loop.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This seems like a hacky design, we should make the API call such that it is successful on the first try. Is there no way to do that using this API?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Reworked to one call — Answers pass record_size = max_rows,
Liveboards request the max 32-bit int.
Verified on local instance.

@saharsh-ts
saharsh-ts force-pushed the SCAL-323294_fetch-data branch from 349b5d8 to cb87e5c Compare July 22, 2026 08:45
@saharsh-ts

Copy link
Copy Markdown
Collaborator Author

@copilot review the PR

Comment thread src/servers/tool-definitions.ts Outdated
description: [
"Fetch the full data (columns and rows) of a saved Answer or Liveboard, identified by its GUID.",
"CALL THIS WHENEVER the user wants to explain, describe, summarize, analyze, interpret, or ask what a specific object contains or shows: any explanation of what an object contains must be grounded in the data this tool returns, so fetch it first with the object's `id`, then answer from the returned data.",
"Returns the object's data exactly as saved — it does not run new queries or change the object's question, filters, or columns. The result is shaped to the object's type: an Answer returns a single tabular result, a Liveboard returns one tabular result per visualization (each with its visualization id and name). To pull a single visualization pinned on a Liveboard, pass the Liveboard GUID as `object_id` and the visualization GUID in `visualization_ids`. Each result includes the column names and data rows. Use the optional `max_rows` to bound the rows returned per visualization (defaults to 25).",

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This description is a bit misleading, since TS will execute the saved query against the latest state of the data, so the tool may return different data compared to when the user first saved the answer. It WILL also run a new database query, so not sure why this says it does not run new queries.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

updated tool definitions.

Comment thread src/servers/tool-definitions.ts Outdated
type: z
.string()
.describe("The resolved object type: either 'ANSWER' or 'LIVEBOARD'."),
description: z.string().describe("The description of the object."),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

There are a lot of fields here duplicated from the search_objects response. Let's make it more concise and only include truly NEW data which the agent doesn't already know.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Trimmed output to just data. Removed id/name/type/description — all already known from search_objects.

Comment thread src/servers/tool-definitions.ts Outdated
.describe(
"The object's data. A single entry for an Answer; one entry per visualization for a Liveboard.",
),
request_id: z

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Let's remove like last time

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Removed request_id. Still sent as x-request-id upstream for tracing.

Comment thread src/servers/tool-definitions.ts Outdated
row_count: z
.number()
.optional()
.describe("Number of rows actually returned in `data_rows`."),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Isn't this redundant? The agent can see how many rows there are

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Removed — agent can count data_rows. Kept total_row_count (upstream total, not derivable when capped).


// Only Answers and Liveboards expose fetchable data.
const ANSWER_TYPE = "ANSWER";
const LIVEBOARD_TYPE = "LIVEBOARD";

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Should these be an enum

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Done — enum ObjectType { Answer, Liveboard }.

const headers = buildHeaders(token, undefined, undefined, { requestId });

// Step 1: resolve the object's type — it decides the data endpoint.
const metaData = await postJson(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Why do we need to do /metadata/search again? I thought we already did this in search_objects tool and returned all the necessary details to directly call the relevant /data API here.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Added optional object_type — pass the type from search_objects and we skip the lookup (one call). Kept optional so a bare GUID still resolves.

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.

4 participants