From 051993507a941a10719e227f45c4ece17b82f0b1 Mon Sep 17 00:00:00 2001 From: Ashish Shubham Date: Wed, 21 May 2025 22:54:00 -0700 Subject: [PATCH 1/2] Use Mixpanel instead of Cloudflare anaylitics --- package-lock.json | 109 ++++++++++++++++++++++--- package.json | 2 + src/handlers.ts | 36 ++++---- src/index.ts | 8 +- src/metrics/index.ts | 17 ++++ src/metrics/mixpanel.ts | 28 +++++++ src/servers/mcp-server.ts | 27 ++++-- src/thoughtspot/thoughtspot-client.ts | 52 ++++++------ src/thoughtspot/thoughtspot-service.ts | 31 +++++++ src/utils.ts | 1 + 10 files changed, 243 insertions(+), 68 deletions(-) create mode 100644 src/metrics/index.ts create mode 100644 src/metrics/mixpanel.ts diff --git a/package-lock.json b/package-lock.json index 59a6c3c..dc171bc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,30 +1,32 @@ { - "name": "mcp-server", - "version": "1.0.0", + "name": "@thoughtspot/mcp-server", + "version": "0.4.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "mcp-server", - "version": "1.0.0", - "license": "ISC", + "name": "@thoughtspot/mcp-server", + "version": "0.4.0", + "license": "ThoughtSpot End user license agreement", "dependencies": { "@cloudflare/workers-oauth-provider": "^0.0.5", "@modelcontextprotocol/sdk": "^1.10.2", "@thoughtspot/rest-api-sdk": "^2.13.1", "agents": "^0.0.75", "hono": "^4.7.8", + "mixpanel-browser": "^2.65.0", "yaml": "^2.7.1", "zod": "^3.24.3", "zod-to-json-schema": "^3.24.5" }, "bin": { - "mcp-server": "node --loader tsx ./src/stdio.ts" + "mcp-server": "node --import tsx ./src/stdio.ts" }, "devDependencies": { "@biomejs/biome": "^1.9.4", "@cloudflare/vitest-pool-workers": "^0.8.24", "@cloudflare/workers-types": "^4.20250430.0", + "@types/mixpanel-browser": "^2.60.0", "@types/node": "^22.15.3", "@types/node-fetch": "^2.6.0", "@vitest/coverage-istanbul": "^3.1.2", @@ -1999,6 +2001,18 @@ "win32" ] }, + "node_modules/@rrweb/types": { + "version": "2.0.0-alpha.18", + "resolved": "https://registry.npmjs.org/@rrweb/types/-/types-2.0.0-alpha.18.tgz", + "integrity": "sha512-iMH3amHthJZ9x3gGmBPmdfim7wLGygC2GciIkw2A6SO8giSn8PHYtRT8OKNH4V+k3SZ6RSnYHcTQxBA7pSWZ3Q==", + "license": "MIT" + }, + "node_modules/@rrweb/utils": { + "version": "2.0.0-alpha.18", + "resolved": "https://registry.npmjs.org/@rrweb/utils/-/utils-2.0.0-alpha.18.tgz", + "integrity": "sha512-qV8azQYo9RuwW4NGRtOiQfTBdHNL1B0Q//uRLMbCSjbaKqJYd88Js17Bdskj65a0Vgp2dwTLPIZ0gK47dfjfaA==", + "license": "MIT" + }, "node_modules/@silvia-odwyer/photon-node": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/@silvia-odwyer/photon-node/-/photon-node-0.3.3.tgz", @@ -2015,6 +2029,12 @@ "whatwg-fetch": "^3.0.0" } }, + "node_modules/@types/css-font-loading-module": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/@types/css-font-loading-module/-/css-font-loading-module-0.0.7.tgz", + "integrity": "sha512-nl09VhutdjINdWyXxHWN/w9zlNCfr60JUqJbd24YXUuCwgeL0TpFSdElCwb6cxfB6ybE19Gjj4g0jsgkXxKv1Q==", + "license": "MIT" + }, "node_modules/@types/diff-match-patch": { "version": "1.0.36", "resolved": "https://registry.npmjs.org/@types/diff-match-patch/-/diff-match-patch-1.0.36.tgz", @@ -2049,6 +2069,13 @@ "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==", "dev": true }, + "node_modules/@types/mixpanel-browser": { + "version": "2.60.0", + "resolved": "https://registry.npmjs.org/@types/mixpanel-browser/-/mixpanel-browser-2.60.0.tgz", + "integrity": "sha512-70oe8T3KdxHwsSo5aZphALdoqcsIorQBrlisnouIn9Do4dmC2C6/D56978CmSE/BO2QHgb85ojPGa4R8OFvVHA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/node": { "version": "22.15.3", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.3.tgz", @@ -2240,6 +2267,12 @@ "url": "https://opencollective.com/vitest" } }, + "node_modules/@xstate/fsm": { + "version": "1.6.5", + "resolved": "https://registry.npmjs.org/@xstate/fsm/-/fsm-1.6.5.tgz", + "integrity": "sha512-b5o1I6aLNeYlU/3CPlj/Z91ybk1gUsKT+5NAJI+2W4UjvS5KLG28K9v5UvNoFVjHV8PajVZ00RH3vnjyQO7ZAw==", + "license": "MIT" + }, "node_modules/accepts": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", @@ -2389,6 +2422,15 @@ "dev": true, "license": "MIT" }, + "node_modules/base64-arraybuffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", + "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, "node_modules/birpc": { "version": "0.2.14", "resolved": "https://registry.npmjs.org/birpc/-/birpc-0.2.14.tgz", @@ -4163,6 +4205,21 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", + "license": "MIT" + }, + "node_modules/mixpanel-browser": { + "version": "2.65.0", + "resolved": "https://registry.npmjs.org/mixpanel-browser/-/mixpanel-browser-2.65.0.tgz", + "integrity": "sha512-BtrVYqilloAqx3TIhoIpNikHznTocEy/z3QIf6WEiz4PFxrgI6LgSMFIVKqLqGZJ8svrPlHbpp/CJp5wQYUZWw==", + "license": "Apache-2.0", + "dependencies": { + "rrweb": "2.0.0-alpha.18" + } + }, "node_modules/mkdirp": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", @@ -4437,8 +4494,7 @@ "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" }, "node_modules/picomatch": { "version": "2.3.1", @@ -4464,7 +4520,6 @@ "version": "8.5.3", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", - "dev": true, "funding": [ { "type": "opencollective", @@ -4493,7 +4548,6 @@ "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "dev": true, "funding": [ { "type": "github", @@ -4693,6 +4747,40 @@ "node": ">= 18" } }, + "node_modules/rrdom": { + "version": "2.0.0-alpha.18", + "resolved": "https://registry.npmjs.org/rrdom/-/rrdom-2.0.0-alpha.18.tgz", + "integrity": "sha512-fSFzFFxbqAViITyYVA4Z0o5G6p1nEqEr/N8vdgSKie9Rn0FJxDSNJgjV0yiCIzcDs0QR+hpvgFhpbdZ6JIr5Nw==", + "license": "MIT", + "dependencies": { + "rrweb-snapshot": "^2.0.0-alpha.18" + } + }, + "node_modules/rrweb": { + "version": "2.0.0-alpha.18", + "resolved": "https://registry.npmjs.org/rrweb/-/rrweb-2.0.0-alpha.18.tgz", + "integrity": "sha512-1mjZcB+LVoGSx1+i9E2ZdAP90fS3MghYVix2wvGlZvrgRuLCbTCCOZMztFCkKpgp7/EeCdYM4nIHJkKX5J1Nmg==", + "license": "MIT", + "dependencies": { + "@rrweb/types": "^2.0.0-alpha.18", + "@rrweb/utils": "^2.0.0-alpha.18", + "@types/css-font-loading-module": "0.0.7", + "@xstate/fsm": "^1.4.0", + "base64-arraybuffer": "^1.0.1", + "mitt": "^3.0.0", + "rrdom": "^2.0.0-alpha.18", + "rrweb-snapshot": "^2.0.0-alpha.18" + } + }, + "node_modules/rrweb-snapshot": { + "version": "2.0.0-alpha.18", + "resolved": "https://registry.npmjs.org/rrweb-snapshot/-/rrweb-snapshot-2.0.0-alpha.18.tgz", + "integrity": "sha512-hBHZL/NfgQX6wO1D9mpwqFu1NJPpim+moIcKhFEjVTZVRUfCln+LOugRc4teVTCISYHN8Cw5e2iNTWCSm+SkoA==", + "license": "MIT", + "dependencies": { + "postcss": "^8.4.38" + } + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -4973,7 +5061,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" diff --git a/package.json b/package.json index a1700de..6443619 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "@biomejs/biome": "^1.9.4", "@cloudflare/vitest-pool-workers": "^0.8.24", "@cloudflare/workers-types": "^4.20250430.0", + "@types/mixpanel-browser": "^2.60.0", "@types/node": "^22.15.3", "@types/node-fetch": "^2.6.0", "@vitest/coverage-istanbul": "^3.1.2", @@ -46,6 +47,7 @@ "@thoughtspot/rest-api-sdk": "^2.13.1", "agents": "^0.0.75", "hono": "^4.7.8", + "mixpanel-browser": "^2.65.0", "yaml": "^2.7.1", "zod": "^3.24.3", "zod-to-json-schema": "^3.24.5" diff --git a/src/handlers.ts b/src/handlers.ts index 32589c2..bfaf506 100644 --- a/src/handlers.ts +++ b/src/handlers.ts @@ -18,26 +18,26 @@ app.get("/", async (c) => { app.get("/authorize", async (c) => { const oauthReqInfo = await c.env.OAUTH_PROVIDER.parseAuthRequest(c.req.raw); const { clientId } = oauthReqInfo - if (!clientId) { - return c.text('Invalid request', 400) - } + if (!clientId) { + return c.text('Invalid request', 400) + } return renderApprovalDialog(c.req.raw, { - client: await c.env.OAUTH_PROVIDER.lookupClient(clientId), - server: { - name: "ThoughtSpot MCP Server", - logo: "https://avatars.githubusercontent.com/u/8906680?s=200&v=4", - description: 'MCP Server for ThoughtSpot Agent', // optional - }, - state: { oauthReqInfo }, // arbitrary data that flows through the form submission below - }) + client: await c.env.OAUTH_PROVIDER.lookupClient(clientId), + server: { + name: "ThoughtSpot MCP Server", + logo: "https://avatars.githubusercontent.com/u/8906680?s=200&v=4", + description: 'MCP Server for ThoughtSpot Agent', // optional + }, + state: { oauthReqInfo }, // arbitrary data that flows through the form submission below + }) }) app.post("/authorize", async (c) => { // Validates form submission and extracts state - const { state, instanceUrl } = await parseRedirectApproval(c.req.raw) - if (!state.oauthReqInfo) { - return c.text('Invalid request', 400) - } + const { state, instanceUrl } = await parseRedirectApproval(c.req.raw) + if (!state.oauthReqInfo) { + return c.text('Invalid request', 400) + } if (!instanceUrl) { return new Response('Missing instance URL', { status: 400 }); @@ -45,7 +45,7 @@ app.post("/authorize", async (c) => { // Construct the redirect URL to v1/saml const redirectUrl = new URL('callosum/v1/saml/login', instanceUrl); - + // TODO(shikhar.bhargava): remove this once we have a proper callback URL // the proper callback URL is the one /callosum/v1/v2/auth/token/authroize endpoint @@ -110,6 +110,9 @@ app.post("/store-token", async (c) => { return c.text('Missing token or OAuth request info or instanceUrl', 400); } + const { clientId } = oauthReqInfo; + const clientName = await c.env.OAUTH_PROVIDER.lookupClient(clientId); + // Complete the authorization with the provided information const { redirectTo } = await c.env.OAUTH_PROVIDER.completeAuthorization({ request: oauthReqInfo, @@ -121,6 +124,7 @@ app.post("/store-token", async (c) => { props: { accessToken: token.data.token, instanceUrl: instanceUrl, + clientName: clientName, } as Props, }); diff --git a/src/index.ts b/src/index.ts index a604031..e33da29 100644 --- a/src/index.ts +++ b/src/index.ts @@ -9,13 +9,7 @@ export class ThoughtSpotMCP extends McpAgent { server = new MCPServer(this); async init() { - await this.server.init((eventName, ...args) => { - this.env.ANALYTICS.writeDataPoint({ - blobs: [eventName, this.props.instanceUrl, ...args], - doubles: [1], - indexes: [crypto.randomUUID()], - }); - }); + await this.server.init(); } } diff --git a/src/metrics/index.ts b/src/metrics/index.ts new file mode 100644 index 0000000..cfffae4 --- /dev/null +++ b/src/metrics/index.ts @@ -0,0 +1,17 @@ +export enum TrackEvent { + CallTool = "mcp-call-tool", + Init = "mcp-init", +} + + +export interface Tracker { + track(eventName: string, props: { [key: string]: any }): void; +} + +export class Trackers extends Set { + track(eventName: TrackEvent, props: { [key: string]: any } = {}) { + for (const tracker of this) { + tracker.track(eventName, props); + } + } +} \ No newline at end of file diff --git a/src/metrics/mixpanel.ts b/src/metrics/mixpanel.ts new file mode 100644 index 0000000..9efb9b0 --- /dev/null +++ b/src/metrics/mixpanel.ts @@ -0,0 +1,28 @@ +import { ThoughtSpotRestApi } from "@thoughtspot/rest-api-sdk"; +import mixpanel, { type Mixpanel } from "mixpanel-browser"; +import type { SessionInfo } from "../thoughtspot/thoughtspot-service"; +import type { Tracker } from "./index"; + + +export class MixpanelTracker implements Tracker { + private mixpanel: Mixpanel; + + constructor(sessionInfo: SessionInfo, clientName: string) { + this.mixpanel = mixpanel.init(sessionInfo.mixpanelToken, { + disable_cookie: true, + disable_persistence: true, + autocapture: false, + }, 'mcpServer'); + this.mixpanel.identify(sessionInfo.userGUID); + this.mixpanel.register_once({ + clusterId: sessionInfo.clusterId, + clusterName: sessionInfo.clusterName, + releaseVersion: sessionInfo.releaseVersion, + clientName: clientName, + }); + } + + track(eventName: string, props: { [key: string]: any }) { + this.mixpanel.track(eventName, props); + } +} diff --git a/src/servers/mcp-server.ts b/src/servers/mcp-server.ts index 9f1cd11..da509f1 100644 --- a/src/servers/mcp-server.ts +++ b/src/servers/mcp-server.ts @@ -15,9 +15,11 @@ import { fetchTMLAndCreateLiveboard, getAnswerForQuestion, getDataSources, - getRelevantQuestions + getRelevantQuestions, + getSessionInfo } from "../thoughtspot/thoughtspot-service"; - +import { MixpanelTracker } from "../metrics/mixpanel"; +import { Trackers, type Tracker, TrackEvent } from "../metrics"; const ToolInputSchema = ToolSchema.shape.inputSchema; type ToolInput = z.infer; @@ -60,6 +62,7 @@ interface Context { } export class MCPServer extends Server { + private trackers: Trackers = new Trackers(); constructor(private ctx: Context) { super({ name: "ThoughtSpot", @@ -74,7 +77,16 @@ export class MCPServer extends Server { }); } - async init(sendAnalytics: (eventName: string, ...args: any[]) => void = () => { }) { + async init() { + const client = getThoughtSpotClient(this.ctx.props.instanceUrl, this.ctx.props.accessToken); + const sessionInfo = await getSessionInfo(client); + const mixpanel = new MixpanelTracker( + sessionInfo, + this.ctx.props.clientName + ); + this.addTracker(mixpanel); + this.trackers.track(TrackEvent.Init); + this.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ @@ -146,7 +158,8 @@ export class MCPServer extends Server { this.setRequestHandler(CallToolRequestSchema, async (request: z.infer) => { const { name } = request.params; - sendAnalytics("call_tool", name); + + this.trackers.track(TrackEvent.CallTool, { toolName: name }); switch (name) { case ToolName.Ping: @@ -188,7 +201,7 @@ export class MCPServer extends Server { const relevantQuestions = await getRelevantQuestions( query, sourceIds!, - additionalContext, + additionalContext ?? "", client, ); @@ -276,4 +289,8 @@ export class MCPServer extends Server { } return this._sources; } + + async addTracker(tracker: Tracker) { + this.trackers.add(tracker); + } } diff --git a/src/thoughtspot/thoughtspot-client.ts b/src/thoughtspot/thoughtspot-client.ts index 66308cb..8c5908a 100644 --- a/src/thoughtspot/thoughtspot-client.ts +++ b/src/thoughtspot/thoughtspot-client.ts @@ -1,17 +1,14 @@ import { createBearerAuthenticationConfig, ThoughtSpotRestApi } from "@thoughtspot/rest-api-sdk" import YAML from "yaml"; -let token: string; - export const getThoughtSpotClient = (instanceUrl: string, bearerToken: string) => { const client = new ThoughtSpotRestApi(createBearerAuthenticationConfig( instanceUrl, () => Promise.resolve(bearerToken), )); (client as any).instanceUrl = instanceUrl; - token = bearerToken; - addExportUnsavedAnswerTML(client, instanceUrl); - addExportAnswerDataProxied(client, instanceUrl); + addExportUnsavedAnswerTML(client, instanceUrl, bearerToken); + addGetSessionInfo(client, instanceUrl, bearerToken); return client; } @@ -37,32 +34,9 @@ mutation GetUnsavedAnswerTML($session: BachSessionIdInput!, $exportDependencies: const PROXY_URL = "https://plugin-party-vercel.vercel.app/api/proxy"; -function addExportAnswerDataProxied(client: any, instanceUrl: string) { - (client as any).exportAnswerReportProxied = async ({ session_identifier, generation_number, file_format }: { session_identifier: string, generation_number: number, file_format: string }) => { - const endpoint = "/api/rest/2.0/report/answer"; - const response = await fetch(PROXY_URL, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - token, - clusterUrl: instanceUrl, - endpoint, - payload: { - session_identifier, - generation_number, - file_format, - } - }) - }); - return response; - } -} - // This is a workaround until we get the public API for this -function addExportUnsavedAnswerTML(client: any, instanceUrl: string) { +function addExportUnsavedAnswerTML(client: any, instanceUrl: string, token: string) { (client as any).exportUnsavedAnswerTML = async ({ session_identifier, generation_number }) => { const endpoint = "/prism/?op=GetUnsavedAnswerTML"; // make a graphql request to `ThoughtspotHost/prism endpoint. @@ -93,4 +67,24 @@ function addExportUnsavedAnswerTML(client: any, instanceUrl: string) { const edoc = data.data.UnsavedAnswer_getTML.object[0].edoc; return YAML.parse(edoc); } +} + +async function addGetSessionInfo(client: any, instanceUrl: string, token: string) { + (client as any).getSessionInfo = async (): Promise => { + const endpoint = "/prism/preauth/info"; + // make a graphql request to `ThoughtspotHost/prism endpoint. + const response = await fetch(`${instanceUrl}${endpoint}`, { + method: "GET", + headers: { + "Content-Type": "application/json", + "Accept": "application/json", + "user-agent": "ThoughtSpot-ts-client", + "Authorization": `Bearer ${token}`, + } + }); + + const data: any = await response.json(); + const info = data.info; + return info; + }; } \ No newline at end of file diff --git a/src/thoughtspot/thoughtspot-service.ts b/src/thoughtspot/thoughtspot-service.ts index 9debd6a..4882e8f 100644 --- a/src/thoughtspot/thoughtspot-service.ts +++ b/src/thoughtspot/thoughtspot-service.ts @@ -191,3 +191,34 @@ export async function getDataSources(client: ThoughtSpotRestApi): Promise { + const info = await (client as any).getSessionInfo(); + const devMixpanelToken = info.configInfo.mixpanelConfig.devSdkKey; + const prodMixpanelToken = info.configInfo.mixpanelConfig.prodSdkKey; + const mixpanelToken = info.configInfo.mixpanelConfig.production + ? prodMixpanelToken + : devMixpanelToken; + return { + mixpanelToken, + userGUID: info.userGUID, + userName: info.userName, + clusterName: info.configInfo.selfClusterName, + clusterId: info.configInfo.selfClusterId, + releaseVersion: info.releaseVersion, + currentOrgId: info.currentOrgId, + privileges: info.privileges, + } +} \ No newline at end of file diff --git a/src/utils.ts b/src/utils.ts index 39b332f..8a56d1a 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,4 +1,5 @@ export type Props = { accessToken: string; instanceUrl: string; + clientName: string; }; \ No newline at end of file From 6f6c8bf280c2eae02765b9b538eaef49644bc816 Mon Sep 17 00:00:00 2001 From: Ashish Shubham Date: Wed, 21 May 2025 23:23:49 -0700 Subject: [PATCH 2/2] fix tests --- test/mcp-server.spec.ts | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/test/mcp-server.spec.ts b/test/mcp-server.spec.ts index ead3c37..52cfb08 100644 --- a/test/mcp-server.spec.ts +++ b/test/mcp-server.spec.ts @@ -1,13 +1,23 @@ -import { describe, it, expect } from "vitest"; +import { describe, it, expect, vi } from "vitest"; import { connect, close } from "mcp-testing-kit"; import { MCPServer } from "../src/servers/mcp-server"; +import * as thoughtspotService from "../src/thoughtspot/thoughtspot-service"; describe("MCP Server", () => { it("should be able to send a message to the server", async () => { + // Mock getSessionInfo to return empty object + vi.spyOn(thoughtspotService, "getSessionInfo").mockResolvedValue({ + clusterId: "123", + clusterName: "test", + releaseVersion: "1.0.0", + userGUID: "123", + mixpanelToken: "123", + } as any); + const server = new MCPServer({ props: {} as any, }); - server.init(); + await server.init(); const { callTool } = connect(server);