Skip to content

Conversation

@shikharbsar
Copy link
Collaborator

No description provided.

@cloudflare-workers-and-pages
Copy link

cloudflare-workers-and-pages bot commented Jul 29, 2025

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
❌ Deployment failed
View logs
png-image 0dc21cc Aug 11 2025, 09:32 PM

@mnk-blr
Copy link

mnk-blr commented Jul 29, 2025

🎉 Snyk checks have passed. No issues have been found so far.

security/snyk check is complete. No issues have been found. (View Details)

license/snyk check is complete. No issues have been found. (View Details)

Copy link
Contributor

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

Choose a reason for hiding this comment

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

Summary of Changes

Hello @shikharbsar, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces the capability to generate and serve PNG images of data visualizations directly from ThoughtSpot. It establishes a new API endpoint for image retrieval, backed by a temporary token system utilizing Cloudflare KV for secure access to session details. The changes also enhance the server's context to facilitate environment access and update various configurations and type definitions to support the new functionality.

Highlights

  • Image Retrieval Endpoint: Introduced a new GET endpoint /data/img in src/handlers.ts to serve PNG images of ThoughtSpot visualizations. This endpoint uses a temporary token to retrieve necessary session and access details from a KV store.
  • Temporary Token Management: Implemented a system in src/servers/mcp-server.ts and src/servers/openai-mcp-server.ts to generate unique tokens, store ThoughtSpot session ID, generation number, instance URL, and access token in Cloudflare KV, and provide a URL for image retrieval in the API response.
  • Server Context Enhancement: Modified the Context interface in src/servers/mcp-server-base.ts and refactored instrumentedMCPServer in src/utils.ts to allow Durable Objects to access the Cloudflare Workers environment (env), enabling KV interactions.
  • ThoughtSpot Integration for Images: Added a new getAnswerImagePNG method to src/thoughtspot/thoughtspot-service.ts to interact with the ThoughtSpot API and export answer reports as PNG images.
  • Configuration and Type Updates: Updated environment variable access in src/index.ts, added HOST_NAME to wrangler.jsonc and worker-configuration.d.ts, and performed various type definition cleanups in worker-configuration.d.ts.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point in your pull request via creating an issue comment (i.e. comment on the pull request page) using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in issue comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments or fill out our survey to provide feedback.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

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

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 functionality to generate and serve PNG images of ThoughtSpot answers. It adds a new endpoint /data/img which uses a token-based mechanism with KV store to retrieve image data. The changes touch multiple files to support this flow, including modifications to the MCP servers to generate image URLs, and updates to the ThoughtSpot service to fetch the image data. The PR also includes some refactoring to pass environment variables to server instances and updates to dependency configurations.

My review focuses on ensuring the new endpoint is robust, identifying potential runtime errors, and suggesting improvements for code clarity, consistency, and maintainability. I've pointed out a critical issue with a method call that would lead to a runtime error, and a high-severity issue regarding a potentially accidental removal of a configuration block. Other comments focus on removing debug logs, improving type safety, and standardizing naming conventions.

accessToken: this.ctx.props.accessToken
};
await putInKV(token, tokenData, this.ctx.env);
tokenUrl = `${this.ctx.env?.HOST_NAME}/data/img?token=${token}`;
Copy link
Member

Choose a reason for hiding this comment

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

Maybe we call token something like uniqueId or tempId to not make the agent believe its a token protected resource and should not be opened

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Done

Comment on lines 148 to 156
const token = crypto.randomUUID();
const tokenData = {
sessionId: answer.session_identifier,
generationNo: answer.generation_number,
instanceURL: this.ctx.props.instanceUrl,
accessToken: this.ctx.props.accessToken
};
await putInKV(token, tokenData, this.ctx.env);
tokenUrl = `${this.ctx.env?.HOST_NAME}/data/img?token=${token}`;
Copy link
Member

Choose a reason for hiding this comment

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

this seems to be duplicate code

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Done

tokenUrl = `${this.ctx.env?.HOST_NAME}/data/img?token=${token}`;
}
let content = answer.data;
if (tokenUrl) {
Copy link
Member

Choose a reason for hiding this comment

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

will tokenUrl be not available in any case ? If not we can remove this conditionality

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

done

Comment on lines 162 to 166
**CHART VISUALIZATION AVAILABLE:**
There is a data visualization chart for this query available at: "${tokenUrl}".
This URL contains a PNG image showing the visual representation of the above data for the question: "${question}". Please fetch and display this image to provide a complete analysis with both the data and its visual representation. The image must be displayed in the response of this question.
Use just this image in the response, do not use any other images or visualizations or charts. It may take time to get the image, so please wait for the image to be fetched and displayed in the response.`;
Copy link
Member

Choose a reason for hiding this comment

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

Can we make this consistent across different mcp servers ? Maybe move to the common service layer.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

done

wrangler.jsonc Outdated
"HONEYCOMB_DATASET": "",
"HONEYCOMB_API_KEY": ""
"HONEYCOMB_API_KEY": "",
"HOST_NAME": "https://agent.thoughtspot.app"
Copy link
Member

Choose a reason for hiding this comment

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

We have created aliases for this, and might also change this in the future. Lets not hardcode this. This should be available in the request payload headers.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yeah, added it as part of props, let me know if I need to change it.

src/handlers.ts Outdated
async getAnswerImage(request: Request, env: Env) {
const span = getActiveSpan();
const url = new URL(request.url);
const token = url.searchParams.get('token') || url.searchParams.get('uniqueId') || '';
Copy link
Member

Choose a reason for hiding this comment

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

remove token

Copy link
Member

Choose a reason for hiding this comment

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

Rename the variable as well

src/handlers.ts Outdated
const tokenData = await getFromKV(token, env);
if (!tokenData) {
span?.setStatus({ code: SpanStatusCode.ERROR, message: "Token not found" });
return new Response("Token not found", { status: 404 });
Copy link
Member

Choose a reason for hiding this comment

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

Can we create an error response utility for the handlers as well.

src/handlers.ts Outdated
Comment on lines 204 to 207
const sessionId = (tokenData as any).sessionId;
const generationNo = (tokenData as any).GenNo || (tokenData as any).generationNo; // Handle both field names
const instanceURL = (tokenData as any).instanceURL;
const accessToken = (tokenData as any).accessToken;
Copy link
Member

Choose a reason for hiding this comment

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

const { sessionId, generationNo, instanceURL, accessToken } = tokenData as any;

Comment on lines 50 to 54
@WithSpan('api-get-answer-image')
async getAnswerImage(props: Props, sessionId: string, generationNo: number) {
const service = this.getThoughtSpotService(props);
return await service.getAnswerImage(sessionId, generationNo);
}
Copy link
Member

Choose a reason for hiding this comment

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

this might not be needed.

}

protected async getAnswerContent(answer: any, question: string) {
const tokenUrl = await this.getTokenUrl(answer.session_identifier, answer.generation_number);
Copy link
Member

Choose a reason for hiding this comment

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

createImageUrl

instanceURL: this.ctx.props.instanceUrl,
accessToken: this.ctx.props.accessToken
};
await putInKV(token, tokenData, this.ctx.env);
Copy link
Member

Choose a reason for hiding this comment

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

should not put in a get function. Lets rename this createImageUrl

protected async getTokenUrl(sessionId: string, generationNo: number) {
let tokenUrl = "";
// Generate token and store in KV store
if (this.ctx.env?.OAUTH_KV) {
Copy link
Member

Choose a reason for hiding this comment

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

Lets put this in a try ... catch. The putInKV method should throw if OAUTH_KV is not available. And we should handle that condition in the catch

};
await putInKV(token, tokenData, this.ctx.env);
const hostName = this.ctx.props.hostName;
if (hostName && (hostName.startsWith("http") || hostName.startsWith("https"))) {
Copy link
Member

Choose a reason for hiding this comment

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

handle this while setting the prop

}

@WithSpan('get-answer-image')
async getAnswerImagePNG(sessionId: string, GenNo: number): Promise<HttpFile> {
Copy link
Member

Choose a reason for hiding this comment

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

genNo

src/utils.ts Outdated
Comment on lines 96 to 127
private _server: T | undefined;
private _env: Env;

// Lazy getter for server that creates it when first accessed
//
// WHY THIS APPROACH:
// Originally we passed 'this' directly as Context: `server = new MCPServer(this)`
// This worked when Context was just { props: Props }, but broke when we added env.
//
// PROBLEMS WITH ORIGINAL APPROACH:
// 1. McpAgent's 'env' property is protected, but Context expects public
// 2. TypeScript error: "Property 'env' is protected but public in Context"
//
// WHY NOT CONSTRUCTOR CREATION:
// We tried creating server in constructor: `new MCPServer({ props: this.props, env })`
// But this.props is undefined during constructor - it gets set later by McpAgent._init()
// Runtime error: "Cannot read properties of undefined (reading 'instanceUrl')"
//
// SOLUTION - LAZY INITIALIZATION:
// - Store env from constructor (available immediately)
// - Create server only when first accessed (after props are set by McpAgent lifecycle)
// - Combine both props and env into proper Context object
get server(): T {
if (!this._server) {
const context: Context = {
props: this.props, // Available after McpAgent._init() sets it
env: this._env // Stored from constructor
};
this._server = new MCPServer(context);
}
return this._server;
}
Copy link
Member

Choose a reason for hiding this comment

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

declare public env: Env;

And then move creation of the server inside the init method.

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