Skip to content

[POC] MCP Apps#163

Draft
Rifdhan wants to merge 1 commit into
mainfrom
poc-mcp-apps
Draft

[POC] MCP Apps#163
Rifdhan wants to merge 1 commit into
mainfrom
poc-mcp-apps

Conversation

@Rifdhan

@Rifdhan Rifdhan commented Jun 12, 2026

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 updates the @modelcontextprotocol/sdk dependency and introduces a new GetInteractiveAnswer tool and resource to render an interactive ThoughtSpot visualization inline in chat. Key feedback highlights critical security and architectural concerns, including a hardcoded trusted authentication token, a wildcard * target origin in postMessage communication, and hardcoded host and visualization IDs in the HTML template. The reviewer recommends retrieving credentials securely, restricting the postMessage target origin, and refactoring the tool and resource reading logic to dynamically parse and template the host, liveboard, and visualization parameters.

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
init({
thoughtSpotHost,
authType: AuthType.TrustedAuthToken,
getAuthToken: () => Promise.resolve('cmlmZGhhbi5uYXplZXJAdGhvdWdodHNwb3QuY29tOk9EbGlOR05oTkRNdFltWmpNQzAwTmpWakxXRXpaakl0TkRFNU5UazJNMlptT1dOa09qRTNOems1TWpZNU9ERTRNemM2SkhOb2FYSnZNU1JUU0VFdE1qVTJKRFV3TURBd01DUmtVREZzVDFsRU9HcGpaMlZuYTNsc1lrczNSSGhCUFQwa1JXNUhielpoUzFjMWFuQXJiRGhHZFVoaVdXUnFNVVpUYzJGcWIydFVVV3BOV1RGalMzUjFWMHhUVFQw'),

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.

security-critical critical

The trusted authentication token is hardcoded in the source code. This token contains credentials associated with rifdhan.nazeer@thoughtspot.com and poses a severe security risk if committed to a public or shared repository.

Please remove this hardcoded credential. Instead, retrieve the token dynamically (e.g., by calling a token generation endpoint on your ThoughtSpot instance using the server's credentials) or pass it securely via configuration/environment variables (e.g., this.ctx.props.trustedAuthToken).

Comment thread src/servers/mcp-server.ts
Comment on lines +75 to +90
const thoughtSpotHost = 'champagne-master-aws.thoughtspotstaging.cloud';

init({
thoughtSpotHost,
authType: AuthType.TrustedAuthToken,
getAuthToken: () => Promise.resolve('cmlmZGhhbi5uYXplZXJAdGhvdWdodHNwb3QuY29tOk9EbGlOR05oTkRNdFltWmpNQzAwTmpWakxXRXpaakl0TkRFNU5UazJNMlptT1dOa09qRTNOems1TWpZNU9ERTRNemM2SkhOb2FYSnZNU1JUU0VFdE1qVTJKRFV3TURBd01DUmtVREZzVDFsRU9HcGpaMlZuYTNsc1lrczNSSGhCUFQwa1JXNUhielpoUzFjMWFuQXJiRGhHZFVoaVdXUnFNVVpUYzJGcWIydFVVV3BOV1RGalMzUjFWMHhUVFQw'),
});

const embed = new LiveboardEmbed(document.getElementById('ts-embed'), {
frameParams: {
width: '100%',
height: '100%',
},
liveboardId: '31cbf421-b859-44ff-ab86-839197bb2bf2',
vizId: 'a9ff85e8-c28f-4189-a59a-c9dd2635ab0c',
});

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 ThoughtSpot host, liveboard ID, and visualization ID are currently hardcoded in the HTML template. This limits the interactive answer tool to a single static visualization on a specific staging environment, and will fail or violate CSP if the MCP server is connected to a different ThoughtSpot instance.

We can make this dynamic by using placeholders (e.g., __THOUGHTSPOT_HOST__, __LIVEBOARD_ID__, __VIZ_ID__) and replacing them dynamically when reading the resource.

Suggested change
const thoughtSpotHost = 'champagne-master-aws.thoughtspotstaging.cloud';
init({
thoughtSpotHost,
authType: AuthType.TrustedAuthToken,
getAuthToken: () => Promise.resolve('cmlmZGhhbi5uYXplZXJAdGhvdWdodHNwb3QuY29tOk9EbGlOR05oTkRNdFltWmpNQzAwTmpWakxXRXpaakl0TkRFNU5UazJNMlptT1dOa09qRTNOems1TWpZNU9ERTRNemM2SkhOb2FYSnZNU1JUU0VFdE1qVTJKRFV3TURBd01DUmtVREZzVDFsRU9HcGpaMlZuYTNsc1lrczNSSGhCUFQwa1JXNUhielpoUzFjMWFuQXJiRGhHZFVoaVdXUnFNVVpUYzJGcWIydFVVV3BOV1RGalMzUjFWMHhUVFQw'),
});
const embed = new LiveboardEmbed(document.getElementById('ts-embed'), {
frameParams: {
width: '100%',
height: '100%',
},
liveboardId: '31cbf421-b859-44ff-ab86-839197bb2bf2',
vizId: 'a9ff85e8-c28f-4189-a59a-c9dd2635ab0c',
});
const thoughtSpotHost = '__THOUGHTSPOT_HOST__';
init({
thoughtSpotHost,
authType: AuthType.TrustedAuthToken,
getAuthToken: () => Promise.resolve('cmlmZGhhbi5uYXplZXJAdGhvdWdodHNwb3QuY29tOk9EbGlOR05oTkRNdFltWmpNQzAwTmpWakxXRXpaakl0TkRFNU5UazJNMlptT1dOa09qRTNOems1TWpZNU9ERTRNemM2SkhOb2FYSnZNU1JUU0VFdE1qVTJKRFV3TURBd01DUmtVREZzVDFsRU9HcGpaMlZuYTNsc1lrczNSSGhCUFQwa1JXNUhielpoUzFjMWFuQXJiRGhHZFVoaVdXUnFNVVpUYzJGcWIydFVVV3BOV1RGalMzUjFWMHhUVFQw'),
});
const embed = new LiveboardEmbed(document.getElementById('ts-embed'), {
frameParams: {
width: '100%',
height: '100%',
},
liveboardId: '__LIVEBOARD_ID__',
vizId: '__VIZ_ID__',
});

Comment thread src/servers/mcp-server.ts
Comment on lines +283 to +310
if (uri === INTERACTIVE_ANSWER_RESOURCE_URI) {
const cspMeta = {
ui: {
csp: {
// Allow the SDK's API calls to the ThoughtSpot instance
connectDomains: [this.ctx.props.instanceUrl, "api-js.mixpanel.com"],
// Allow loading the SDK script from the CDN, and allow the
// ThoughtSpot SDK to embed the ThoughtSpot host in an iframe (frame-src)
resourceDomains: [
"https://cdn.jsdelivr.net",
this.ctx.props.instanceUrl,
],
// This is not being used but we need it...
frameDomains: [this.ctx.props.instanceUrl],
},
},
};
return {
contents: [
{
uri,
mimeType: "text/html;profile=mcp-app",
text: INTERACTIVE_ANSWER_HTML,
_meta: cspMeta,
},
],
};
}

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

Update readResource to support dynamic parameter parsing from the resource URI. This allows the client to request any liveboard or visualization dynamically by passing query parameters (e.g., ?liveboardId=...&vizId=...), and automatically templates the host domain using this.ctx.props.instanceUrl.

		if (uri.startsWith(INTERACTIVE_ANSWER_RESOURCE_URI)) {
			const url = new URL(uri);
			const liveboardId = url.searchParams.get("liveboardId") || "31cbf421-b859-44ff-ab86-839197bb2bf2";
			const vizId = url.searchParams.get("vizId") || "a9ff85e8-c28f-4189-a59a-c9dd2635ab0c";
			const host = new URL(this.ctx.props.instanceUrl).host;

			const html = INTERACTIVE_ANSWER_HTML
				.replace("__THOUGHTSPOT_HOST__", host)
				.replace("__LIVEBOARD_ID__", liveboardId)
				.replace("__VIZ_ID__", vizId);

			const cspMeta = {
				ui: {
					csp: {
						// Allow the SDK's API calls to the ThoughtSpot instance
						connectDomains: [this.ctx.props.instanceUrl, "api-js.mixpanel.com"],
						// Allow loading the SDK script from the CDN, and allow the
						// ThoughtSpot SDK to embed the ThoughtSpot host in an iframe (frame-src)
						resourceDomains: [
							"https://cdn.jsdelivr.net",
							this.ctx.props.instanceUrl,
						],
						// This is not being used but we need it...
						frameDomains: [this.ctx.props.instanceUrl],
					},
				},
			};
			return {
				contents: [
					{
						uri,
						mimeType: "text/html;profile=mcp-app",
						text: html,
						_meta: cspMeta,
					},
				],
			};
		}

Comment thread src/servers/mcp-server.ts
Comment on lines +399 to +401
case ToolName.GetInteractiveAnswer: {
return this.callGetInteractiveAnswer();
}

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

Pass the tool request arguments to callGetInteractiveAnswer so that the LLM can dynamically specify which liveboard or visualization to display.

Suggested change
case ToolName.GetInteractiveAnswer: {
return this.callGetInteractiveAnswer();
}
case ToolName.GetInteractiveAnswer: {
return this.callGetInteractiveAnswer(request);
}

Comment thread src/servers/mcp-server.ts
Comment on lines +716 to +725
callGetInteractiveAnswer() {
return {
content: [
{
type: "text" as const,
text: "Interactive answer rendered. The UI is displayed inline in the chat.",
},
],
};
}

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

Update callGetInteractiveAnswer to accept the tool request, parse the liveboardId and vizId arguments, and return a dynamic resource URI with query parameters in _meta.ui.resourceUri.

	callGetInteractiveAnswer(request: z.infer<typeof CallToolRequestSchema>) {
		const { liveboardId, vizId } = request.params.arguments as { liveboardId?: string; vizId?: string } || {};
		const queryParams = new URLSearchParams();
		if (liveboardId) queryParams.set("liveboardId", liveboardId);
		if (vizId) queryParams.set("vizId", vizId);
		const queryString = queryParams.toString();
		const resourceUri = queryString ? `${INTERACTIVE_ANSWER_RESOURCE_URI}?${queryString}` : INTERACTIVE_ANSWER_RESOURCE_URI;

		return {
			content: [
				{
					type: "text" as const,
					text: "Interactive answer rendered. The UI is displayed inline in the chat.",
				},
			],
			_meta: {
				ui: {
					resourceUri,
				},
			},
		};
	}

Comment on lines +405 to +421
{
name: ToolName.GetInteractiveAnswer,
description:
"Returns an interactive HTML application for exploring and visualising answers. Use this when the user asks for an interactive view, a visual answer, or an embedded app experience.",
inputSchema: z.toJSONSchema(z.object({})),
annotations: {
title: "Get Interactive Answer",
readOnlyHint: true,
destructiveHint: false,
openWorldHint: false,
},
_meta: {
ui: {
resourceUri: INTERACTIVE_ANSWER_RESOURCE_URI,
},
},
},

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

Update the tool definition for GetInteractiveAnswer to accept optional liveboardId and vizId parameters. This allows the LLM to dynamically request specific visualizations or liveboards (such as those it just created).

	{
		name: ToolName.GetInteractiveAnswer,
		description:
			"Returns an interactive HTML application for exploring and visualising answers. Use this when the user asks for an interactive view, a visual answer, or an embedded app experience.",
		inputSchema: z.toJSONSchema(
			z.object({
				liveboardId: z.string().optional().describe("The ID of the liveboard to display"),
				vizId: z.string().optional().describe("The ID of the specific visualization to display"),
			})
		),
		annotations: {
			title: "Get Interactive Answer",
			readOnlyHint: true,
			destructiveHint: false,
			openWorldHint: false,
		},
		_meta: {
			ui: {
				resourceUri: INTERACTIVE_ANSWER_RESOURCE_URI,
			},
		},
	},

Comment thread src/servers/mcp-server.ts
Comment on lines +117 to +122
window.parent.postMessage({ jsonrpc: '2.0', id: id, method: method, params: params }, '*');
});
}

function sendNotification(method, params) {
window.parent.postMessage({ jsonrpc: '2.0', method: method, params: params || {} }, '*');

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.

security-medium medium

Using * as the target origin in postMessage allows any origin to intercept the messages sent by the iframe. This can lead to sensitive data leakage if the iframe is embedded in an untrusted parent window.

Consider restricting the target origin to the expected host client origin. If the parent origin is dynamic, you can pass it as a query parameter to the iframe (e.g., ?parentOrigin=...) and use that value instead of *.

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