• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

thoughtspot / mcp-server / 17115139974

21 Aug 2025 02:08AM UTC coverage: 90.564% (-0.9%) from 91.432%
17115139974

push

github

web-flow
add open api spec for tools (#57)

* add open api spec for tools

* address comments -  export tool schemas from respective servers

* fix conflicts

* update README with instructions to connect to glean actions

* add annotations for datasource discovery tool

* return tool definition directly for open ai mcp server

* add glean integration info to public docs

* rename doc file from .txt to .md

192 of 225 branches covered (85.33%)

Branch coverage included in aggregate %.

35 of 39 new or added lines in 5 files covered. (89.74%)

1 existing line in 1 file now uncovered.

691 of 750 relevant lines covered (92.13%)

160.79 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

86.11
/src/servers/api-server.ts
1
import { Hono } from 'hono'
2
import { zValidator } from '@hono/zod-validator'
3
import type { Props } from '../utils';
4
import { ThoughtSpotService } from '../thoughtspot/thoughtspot-service';
5
import { getThoughtSpotClient } from '../thoughtspot/thoughtspot-client';
6
import { getActiveSpan, WithSpan } from '../metrics/tracing/tracing-utils';
7
import { CreateLiveboardSchema, GetAnswerSchema, GetRelevantQuestionsSchema } from './mcp-server';
8

9
const apiServer = new Hono<{ Bindings: Env & { props: Props } }>()
33✔
10

11
class ApiHandler {
12

13
    private initSpan(props: Props) {
14
        const span = getActiveSpan();
42✔
15
        span?.setAttributes({
42✔
16
            instance_url: props.instanceUrl,
17
        });
18
    }
19

20
    private getThoughtSpotService(props: Props): ThoughtSpotService {
21
        this.initSpan(props);
42✔
22
        return new ThoughtSpotService(getThoughtSpotClient(props.instanceUrl, props.accessToken));
42✔
23
    }
24

25
    @WithSpan('api-relevant-questions')
26
    async getRelevantQuestions(props: Props, query: string, datasourceIds: string[], additionalContext?: string) {
33✔
27
        const service = this.getThoughtSpotService(props);
14✔
28
        return await service.getRelevantQuestions(query, datasourceIds, additionalContext || '');
14✔
29
    }
30

31
    @WithSpan('api-get-answer')
32
    async getAnswer(props: Props, question: string, datasourceId: string) {
33✔
33
        const service = this.getThoughtSpotService(props);
7✔
34
        return await service.getAnswerForQuestion(question, datasourceId, false);
7✔
35
    }
36

37
    @WithSpan('api-create-liveboard')
38
    async createLiveboard(props: Props, name: string, answers: any[], noteTileParsedHtml: string) {
33✔
39
        const service = this.getThoughtSpotService(props);
7✔
40
        const result = await service.fetchTMLAndCreateLiveboard(name, answers, noteTileParsedHtml);
7✔
41
        return result.url || '';
7!
42
    }
43

44
    @WithSpan('api-get-datasources')
45
    async getDataSources(props: Props) {
33✔
46
        const service = this.getThoughtSpotService(props);
14✔
47
        return await service.getDataSources();
14✔
48
    }
49

50
    @WithSpan('api-proxy-post')
51
    async proxyPost(props: Props, path: string, body: any) {
33✔
52
        const span = getActiveSpan();
14✔
53
        span?.setAttributes({
14✔
54
            instance_url: props.instanceUrl,
55
            path: path,
56
        });
57
        span?.addEvent("proxy-post");
14✔
58
        return fetch(props.instanceUrl + path, {
14✔
59
            method: 'POST',
60
            headers: {
61
                "Authorization": `Bearer ${props.accessToken}`,
62
                "Accept": "application/json",
63
                "Content-Type": "application/json",
64
                "User-Agent": "ThoughtSpot-ts-client",
65
            },
66
            body: JSON.stringify(body),
67
        });
68
    }
69

70
    @WithSpan('api-proxy-get')
71
    async proxyGet(props: Props, path: string) {
33✔
72
        const span = getActiveSpan();
14✔
73
        span?.setAttributes({
14✔
74
            instance_url: props.instanceUrl,
75
            path: path,
76
        });
77
        span?.addEvent("proxy-get");
14✔
78
        return fetch(props.instanceUrl + path, {
14✔
79
            method: 'GET',
80
            headers: {
81
                "Authorization": `Bearer ${props.accessToken}`,
82
                "Accept": "application/json",
83
                "User-Agent": "ThoughtSpot-ts-client",
84
            }
85
        });
86
    }
87
}
88

89
const handler = new ApiHandler();
33✔
90

91
apiServer.post(
33✔
92
    "/api/tools/relevant-questions",
93
    zValidator('json', GetRelevantQuestionsSchema),
94
    async (c) => {
95
        const { props } = c.executionCtx;
14✔
96
        const { query, datasourceIds, additionalContext } = c.req.valid('json');
14✔
97
        const questions = await handler.getRelevantQuestions(props, query, datasourceIds, additionalContext);
14✔
98
        return c.json(questions);
14✔
99
    }
100
);
101

102
apiServer.post(
33✔
103
    "/api/tools/get-answer",
104
    zValidator('json', GetAnswerSchema),
105
    async (c) => {
106
        const { props } = c.executionCtx;
7✔
107
        const { question, datasourceId } = c.req.valid('json');
7✔
108
        const answer = await handler.getAnswer(props, question, datasourceId);
7✔
109
        return c.json(answer);
7✔
110
    }
111
);
112

113
apiServer.post(
33✔
114
    "/api/tools/create-liveboard",
115
    zValidator('json', CreateLiveboardSchema),
116
    async (c) => {
117
        const { props } = c.executionCtx;
7✔
118
        const { name, answers, noteTile } = c.req.valid('json');
7✔
119
        const liveboardUrl = await handler.createLiveboard(props, name, answers, noteTile);
7✔
120
        return c.text(liveboardUrl);
7✔
121
    }
122
);
123

124
apiServer.get("/api/tools/ping", async (c) => {
33✔
UNCOV
125
    const { props } = c.executionCtx;
×
NEW
126
    console.log("Received Ping request");
×
NEW
127
    if (props.accessToken && props.instanceUrl) {
×
NEW
128
        return c.json({
×
129
            content: [{ type: "text", text: "Pong" }],
130
        });
131
    }
NEW
132
    return c.json({
×
133
        isError: true,
134
        content: [{ type: "text", text: "ERROR: Not authenticated" }],
135
    });
136
});
137

138
apiServer.get("/api/resources/datasources", async (c) => {
33✔
139
    const { props } = c.executionCtx;
14✔
140
    const datasources = await handler.getDataSources(props);
14✔
141
    return c.json(datasources);
7✔
142
});
143

144
apiServer.post("/api/rest/2.0/*", async (c) => {
33✔
145
    const { props } = c.executionCtx;
14✔
146
    const path = c.req.path;
14✔
147
    const method = c.req.method;
14✔
148
    const body = await c.req.json();
14✔
149
    return handler.proxyPost(props, path, body);
14✔
150
});
151

152
apiServer.get("/api/rest/2.0/*", async (c) => {
33✔
153
    const { props } = c.executionCtx;
14✔
154
    const path = c.req.path;
14✔
155
    return handler.proxyGet(props, path);
14✔
156
});
157

158
export {
159
    apiServer,
160
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc