Skip to content

Commit 87cb4da

Browse files
authored
Split tools into multiple (#5)
1 parent 97a3b85 commit 87cb4da

File tree

2 files changed

+123
-8
lines changed

2 files changed

+123
-8
lines changed

src/mcp-server.ts

Lines changed: 109 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,24 +5,51 @@ import { zodToJsonSchema } from "zod-to-json-schema";
55
import { Props } from "./utils";
66
import { getRelevantData } from "./thoughtspot/relevant-data";
77
import { getThoughtSpotClient } from "./thoughtspot/thoughtspot-client";
8-
import { DataSource, getDataSources } from "./thoughtspot/thoughtspot-service";
8+
import { DataSource, fetchTMLAndCreateLiveboard, getAnswerForQuestion, getDataSources, getRelevantQuestions } from "./thoughtspot/thoughtspot-service";
99

1010

1111
const ToolInputSchema = ToolSchema.shape.inputSchema;
1212
type ToolInput = z.infer<typeof ToolInputSchema>;
1313

1414
const PingSchema = z.object({});
1515

16+
const GetRelevantQuestionsSchema = z.object({
17+
query: z.string().describe("The query to get relevant data questions for, this could be a high level task or question the user is asking or hoping to get answered. You can pass the complete raw query as the system is smart to make sense of it."),
18+
additionalContext: z.string()
19+
.describe("Additional context to add to the query, this might be older data returned for previous questions or any other relevant context that might help the system generate better questions.")
20+
.optional(),
21+
datasourceId: z.string()
22+
.describe("The datasource to get questions for, this is the id of the datasource to get data from")
23+
.optional()
24+
});
25+
1626
const GetRelevantDataSchema = z.object({
1727
query: z.string().describe("The query to get relevant data for, this could be a high level task or question the user is asking or hoping to get answered. You can pass the complete raw query as the system is smart to make sense of it."),
1828
datasourceId: z.string()
1929
.describe("The datasource to get data from, this is the id of the datasource to get data from")
2030
.optional()
2131
});
2232

33+
const GetAnswerSchema = z.object({
34+
question: z.string().describe("The question to get the answer for, these are generally the questions generated by the getRelevantQuestions tool."),
35+
datasourceId: z.string()
36+
.describe("The datasource to get the answer for, this is the id of the datasource to get data from")
37+
});
38+
39+
const CreateLiveboardSchema = z.object({
40+
name: z.string().describe("The name of the liveboard to create"),
41+
answers: z.array(z.object({
42+
question: z.string(),
43+
session_identifier: z.string(),
44+
generation_number: z.number(),
45+
})).describe("The answers to create the liveboard from, these are the answers generated by the getAnswer tool."),
46+
});
47+
2348
enum ToolName {
2449
Ping = "ping",
25-
GetRelevantData = "getRelevantData",
50+
GetRelevantQuestions = "getRelevantQuestions",
51+
GetAnswer = "getAnswer",
52+
CreateLiveboard = "createLiveboard",
2653
}
2754

2855
interface Context {
@@ -54,9 +81,19 @@ export class MCPServer extends Server {
5481
inputSchema: zodToJsonSchema(PingSchema) as ToolInput,
5582
},
5683
{
57-
name: ToolName.GetRelevantData,
58-
description: "Get relevant data from ThoughtSpot database",
59-
inputSchema: zodToJsonSchema(GetRelevantDataSchema) as ToolInput,
84+
name: ToolName.GetRelevantQuestions,
85+
description: "Get relevant data questions from ThoughtSpot database",
86+
inputSchema: zodToJsonSchema(GetRelevantQuestionsSchema) as ToolInput,
87+
},
88+
{
89+
name: ToolName.GetAnswer,
90+
description: "Get the answer to a question from ThoughtSpot database",
91+
inputSchema: zodToJsonSchema(GetAnswerSchema) as ToolInput,
92+
},
93+
{
94+
name: ToolName.CreateLiveboard,
95+
description: "Create a liveboard from a list of answers",
96+
inputSchema: zodToJsonSchema(CreateLiveboardSchema) as ToolInput,
6097
}
6198
]
6299
};
@@ -95,7 +132,7 @@ export class MCPServer extends Server {
95132
96133
The id of the datasource is ${sourceId}.
97134
98-
Use ThoughtSpot's getRelevantData tool to get data from this datasource for a question.
135+
Use ThoughtSpot's getRelevantQuestions tool to get relevant questions for a query. And then use the getAnswer tool to get the answer for a question.
99136
`,
100137
}],
101138
};
@@ -120,8 +157,16 @@ export class MCPServer extends Server {
120157
};
121158
}
122159

123-
case ToolName.GetRelevantData: {
124-
return this.callGetRelevantData(request);
160+
case ToolName.GetRelevantQuestions: {
161+
return this.callGetRelevantQuestions(request);
162+
}
163+
164+
case ToolName.GetAnswer: {
165+
return this.callGetAnswer(request);
166+
}
167+
168+
case ToolName.CreateLiveboard: {
169+
return this.callCreateLiveboard(request);
125170
}
126171

127172
default:
@@ -130,6 +175,62 @@ export class MCPServer extends Server {
130175
});
131176
}
132177

178+
179+
async callGetRelevantQuestions(request: z.infer<typeof CallToolRequestSchema>) {
180+
const { query, datasourceId: sourceId, additionalContext } = GetRelevantQuestionsSchema.parse(request.params.arguments);
181+
const client = getThoughtSpotClient(this.ctx.props.instanceUrl, this.ctx.props.accessToken);
182+
const progressToken = request.params._meta?.progressToken;
183+
let progress = 0;
184+
console.log("[DEBUG] Getting relevant questions for query: ", query, " and datasource: ", sourceId);
185+
186+
const relevantQuestions = await getRelevantQuestions(
187+
query,
188+
sourceId!,
189+
additionalContext,
190+
client,
191+
);
192+
193+
return {
194+
content: [{
195+
type: "text",
196+
text: relevantQuestions.map((question) => `- ${question}`).join("\n"),
197+
}],
198+
};
199+
}
200+
201+
async callGetAnswer(request: z.infer<typeof CallToolRequestSchema>) {
202+
const { question, datasourceId: sourceId } = GetAnswerSchema.parse(request.params.arguments);
203+
const client = getThoughtSpotClient(this.ctx.props.instanceUrl, this.ctx.props.accessToken);
204+
const progressToken = request.params._meta?.progressToken;
205+
let progress = 0;
206+
console.log("[DEBUG] Getting answer for question: ", question, " and datasource: ", sourceId);
207+
208+
const answer = await getAnswerForQuestion(question, sourceId, false, client);
209+
210+
return {
211+
content: [{
212+
type: "text",
213+
text: answer.data,
214+
}, {
215+
type: "text",
216+
text: `Question: ${question}\nSession Identifier: ${answer.session_identifier}\nGeneration Number: ${answer.generation_number} \n\nUse this information to create a liveboard with the createLiveboard tool, if the user asks.`,
217+
}],
218+
};
219+
}
220+
221+
async callCreateLiveboard(request: z.infer<typeof CallToolRequestSchema>) {
222+
const { name, answers } = CreateLiveboardSchema.parse(request.params.arguments);
223+
const client = getThoughtSpotClient(this.ctx.props.instanceUrl, this.ctx.props.accessToken);
224+
const liveboardUrl = await fetchTMLAndCreateLiveboard(name, answers, client);
225+
return {
226+
content: [{
227+
type: "text",
228+
text: `Liveboard created successfully, you can view it at ${liveboardUrl}`,
229+
}],
230+
};
231+
}
232+
233+
133234
async callGetRelevantData(request: z.infer<typeof CallToolRequestSchema>) {
134235
const { query, datasourceId: sourceId } = GetRelevantDataSchema.parse(request.params.arguments);
135236
const client = getThoughtSpotClient(this.ctx.props.instanceUrl, this.ctx.props.accessToken);

src/thoughtspot/thoughtspot-service.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,20 @@ export async function getAnswerForQuestion(question: string, sourceId: string, s
8181
};
8282
}
8383

84+
export async function fetchTMLAndCreateLiveboard(name: string, answers: any[], client: ThoughtSpotRestApi) {
85+
const tmls = await Promise.all(answers.map((answer) => getAnswerTML({
86+
question: answer.question,
87+
session_identifier: answer.session_identifier,
88+
generation_number: answer.generation_number,
89+
client,
90+
})));
91+
answers.forEach((answer, idx) => {
92+
answer.tml = tmls[idx];
93+
});
94+
95+
return createLiveboard(name, answers, client);
96+
}
97+
8498
export async function createLiveboard(name: string, answers: any[], client: ThoughtSpotRestApi) {
8599
answers = answers.filter((answer) => answer.tml);
86100
const tml = {

0 commit comments

Comments
 (0)