Skip to content

Commit af0763b

Browse files
authored
Refactor tools to return JSON (#26)
This change updates all the tools so they return a JSON response. This makes it easier for LLMs to leverage code execution to manipulate the data.
1 parent 527c5ce commit af0763b

25 files changed

+929
-998
lines changed

docs/tools.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ Retrieves voice call quality metrics for one or more conversations by ID. This t
9595

9696
Read more [about MOS scores and how they're determined](https://developer.genesys.cloud/analyticsdatamanagement/analytics/detail/call-quality).
9797

98-
[Source file](/src/tools/voiceCallQuality.ts).
98+
[Source file](/src/tools/voiceCallQuality/voiceCallQuality.ts).
9999

100100
### Inputs
101101

package-lock.json

Lines changed: 496 additions & 561 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@makingchatbots/genesys-cloud-mcp-server",
3-
"version": "0.0.13",
3+
"version": "0.0.14",
44
"description": "A Model Context Protocol (MCP) server exposing Genesys Cloud tools for LLMs, including sentiment analysis, conversation search, topic detection and more.",
55
"bin": "./dist/cli.js",
66
"type": "module",
@@ -36,28 +36,27 @@
3636
"test:pack": "npm run build && npm pack --pack-destination ./dist"
3737
},
3838
"dependencies": {
39-
"@modelcontextprotocol/sdk": "^1.13.0",
39+
"@modelcontextprotocol/sdk": "^1.16.0",
4040
"date-fns": "^4.1.0",
41-
"purecloud-platform-client-v2": "^224.0.0",
42-
"table": "^6.9.0",
43-
"zod": "^3.25.67"
41+
"purecloud-platform-client-v2": "^227.0.0",
42+
"zod": "^3.23.8"
4443
},
4544
"devDependencies": {
4645
"@eslint/eslintrc": "^3.3.1",
47-
"@eslint/js": "^9.28.0",
46+
"@eslint/js": "^9.31.0",
4847
"@types/eslint-config-prettier": "^6.11.3",
49-
"@types/node": "^22.15.29",
48+
"@types/node": "^22.16.4",
5049
"@typescript-eslint/eslint-plugin": "^8.33.1",
51-
"@typescript-eslint/parser": "^8.33.1",
52-
"eslint": "^9.28.0",
53-
"eslint-config-prettier": "^10.1.5",
54-
"eslint-import-resolver-typescript": "^4.4.2",
55-
"lint-staged": "^16.1.0",
56-
"prettier": "^3.5.3",
57-
"tsx": "^4.19.4",
50+
"@typescript-eslint/parser": "^8.37.0",
51+
"eslint": "^9.31.0",
52+
"eslint-config-prettier": "^10.1.8",
53+
"eslint-import-resolver-typescript": "^4.4.4",
54+
"lint-staged": "^16.1.2",
55+
"prettier": "^3.6.2",
56+
"tsx": "^4.20.3",
5857
"typescript": "^5.8.3",
59-
"typescript-eslint": "^8.33.1",
60-
"vitest": "^3.2.0"
58+
"typescript-eslint": "^8.37.0",
59+
"vitest": "^3.2.4"
6160
},
6261
"lint-staged": {
6362
"src/*.ts": [

src/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { createConfigRetriever } from "./createConfigRetriever.js";
55
import { searchQueues } from "./tools/searchQueues.js";
66
import { sampleConversationsByQueue } from "./tools/sampleConversationsByQueue/sampleConversationsByQueue.js";
77
import { queryQueueVolumes } from "./tools/queryQueueVolumes/queryQueueVolumes.js";
8-
import { voiceCallQuality } from "./tools/voiceCallQuality.js";
8+
import { voiceCallQuality } from "./tools/voiceCallQuality/voiceCallQuality.js";
99
import { conversationSentiment } from "./tools/conversationSentiment/conversationSentiment.js";
1010
import { conversationTopics } from "./tools/conversationTopics/conversationTopics.js";
1111
import { searchVoiceConversations } from "./tools/searchVoiceConversations.js";
@@ -19,7 +19,7 @@ const withAuth = OAuthClientCredentialsWrapper(
1919

2020
const server: McpServer = new McpServer({
2121
name: "Genesys Cloud",
22-
version: "0.0.13", // Same version as version in package.json
22+
version: "0.0.14", // Same version as version in package.json
2323
});
2424

2525
const routingApi = new platformClient.RoutingApi();

src/tools/conversationSentiment/conversationSentiment.test.ts

Lines changed: 33 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,10 @@ describe("Conversation Sentiment Tool", () => {
124124
content: [
125125
{
126126
type: "text",
127-
text: "Failed to retrieve sentiment analysis: Unauthorised access. Please check API credentials or permissions.",
127+
text: JSON.stringify({
128+
errorMessage:
129+
"Failed to retrieve sentiment analysis: Unauthorised access. Please check API credentials or permissions",
130+
}),
128131
},
129132
],
130133
});
@@ -152,12 +155,10 @@ describe("Conversation Sentiment Tool", () => {
152155
content: [
153156
{
154157
type: "text",
155-
text: `
156-
Sentiment results for 1 conversation(s):
157-
158-
• Conversation ID: ${conversationId}
159-
• Error: Conversation not found
160-
`.trim(),
158+
text: JSON.stringify({
159+
conversationsWithSentiment: [],
160+
conversationsWithoutSentiment: [conversationId],
161+
}),
161162
},
162163
],
163164
});
@@ -183,12 +184,16 @@ Sentiment results for 1 conversation(s):
183184
content: [
184185
{
185186
type: "text",
186-
text: `
187-
Sentiment results for 1 conversation(s):
188-
189-
• Conversation ID: ${conversationId}
190-
• Sentiment Score: 40 (Slightly Positive)
191-
`.trim(),
187+
text: JSON.stringify({
188+
conversationsWithSentiment: [
189+
{
190+
conversationId: conversationId,
191+
sentimentScore: 40,
192+
sentimentDescription: "Slightly Positive",
193+
},
194+
],
195+
conversationsWithoutSentiment: [],
196+
}),
192197
},
193198
],
194199
});
@@ -231,15 +236,21 @@ Sentiment results for 1 conversation(s):
231236
content: [
232237
{
233238
type: "text",
234-
text: `
235-
Sentiment results for 2 conversation(s):
236-
237-
• Conversation ID: ${conversationOneId}
238-
• Sentiment Score: 10 (Neutral)
239-
240-
• Conversation ID: ${conversationTwoId}
241-
• Sentiment Score: 20 (Slightly Positive)
242-
`.trim(),
239+
text: JSON.stringify({
240+
conversationsWithSentiment: [
241+
{
242+
conversationId: conversationOneId,
243+
sentimentScore: 10,
244+
sentimentDescription: "Neutral",
245+
},
246+
{
247+
conversationId: conversationTwoId,
248+
sentimentScore: 20,
249+
sentimentDescription: "Slightly Positive",
250+
},
251+
],
252+
conversationsWithoutSentiment: [],
253+
}),
243254
},
244255
],
245256
});

src/tools/conversationSentiment/conversationSentiment.ts

Lines changed: 32 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,15 @@ export const conversationSentiment: ToolFactory<
5555
)),
5656
);
5757

58-
const output: string[] = [];
58+
const output: (
59+
| {
60+
found: true;
61+
conversationId: string;
62+
sentimentScore: number;
63+
sentimentDescription: string;
64+
}
65+
| { found: false; conversationId: string }
66+
)[] = [];
5967

6068
for (const convo of conversations) {
6169
if (convo.status === "fulfilled") {
@@ -65,18 +73,22 @@ export const conversationSentiment: ToolFactory<
6573
if (id === undefined || score === undefined) continue;
6674
const scaledScore = Math.round(score * 100);
6775

68-
output.push(
69-
`• Conversation ID: ${id}\n • Sentiment Score: ${String(scaledScore)} (${interpretSentiment(scaledScore)})`,
70-
);
76+
output.push({
77+
found: true,
78+
conversationId: id,
79+
sentimentScore: scaledScore,
80+
sentimentDescription: interpretSentiment(scaledScore),
81+
});
7182
} else {
7283
const result = isConversationNotFoundError(convo.reason);
7384
if (result.isResourceNotFoundError && result.conversationId) {
74-
output.push(
75-
`• Conversation ID: ${result.conversationId}\n • Error: Conversation not found`,
76-
);
85+
output.push({
86+
conversationId: result.conversationId,
87+
found: false,
88+
});
7789
} else if (isUnauthorisedError(convo.reason)) {
7890
return errorResult(
79-
"Failed to retrieve sentiment analysis: Unauthorised access. Please check API credentials or permissions.",
91+
"Failed to retrieve sentiment analysis: Unauthorised access. Please check API credentials or permissions",
8092
);
8193
} else {
8294
// Ignore conversation
@@ -88,13 +100,18 @@ export const conversationSentiment: ToolFactory<
88100
content: [
89101
{
90102
type: "text",
91-
text:
92-
output.length > 0
93-
? [
94-
`Sentiment results for ${String(output.length)} conversation(s):`,
95-
...output,
96-
].join("\n\n")
97-
: "No sentiment data found for the given conversation IDs.",
103+
text: JSON.stringify({
104+
conversationsWithSentiment: output
105+
.filter((o) => o.found)
106+
.map((o) => ({
107+
conversationId: o.conversationId,
108+
sentimentScore: o.sentimentScore,
109+
sentimentDescription: o.sentimentDescription,
110+
})),
111+
conversationsWithoutSentiment: output
112+
.filter((o) => !o.found)
113+
.map((o) => o.conversationId),
114+
}),
98115
},
99116
],
100117
};

src/tools/conversationTopics/conversationTopics.test.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -129,12 +129,13 @@ describe("Conversation Topics Tool", () => {
129129
content: [
130130
{
131131
type: "text",
132-
text: `
133-
Conversation ID: ${conversationId}
134-
Detected Topics:
135-
• Test Topic 1: Test Topic 1 Desc
136-
• Test Topic 2: Test Topic 2 Desc
137-
`.trim(),
132+
text: JSON.stringify({
133+
conversationId: conversationId,
134+
detectedTopics: [
135+
{ name: "Test Topic 1", description: "Test Topic 1 Desc" },
136+
{ name: "Test Topic 2", description: "Test Topic 2 Desc" },
137+
],
138+
}),
138139
},
139140
],
140141
});

src/tools/conversationTopics/conversationTopics.ts

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,11 @@ export const conversationTopics: ToolFactory<
5151
conversationDetails =
5252
await analyticsApi.getAnalyticsConversationDetails(conversationId);
5353
} catch (error: unknown) {
54-
const message = isUnauthorisedError(error)
55-
? "Failed to retrieve conversation topics: Unauthorised access. Please check API credentials or permissions."
54+
const errorMessage = isUnauthorisedError(error)
55+
? "Failed to retrieve conversation topics: Unauthorised access. Please check API credentials or permissions"
5656
: `Failed to retrieve conversation topics: ${error instanceof Error ? error.message : JSON.stringify(error)}`;
5757

58-
return errorResult(message);
58+
return errorResult(errorMessage);
5959
}
6060

6161
if (
@@ -98,11 +98,11 @@ export const conversationTopics: ToolFactory<
9898
},
9999
);
100100
} catch (error: unknown) {
101-
const message = isUnauthorisedError(error)
102-
? "Failed to retrieve conversation topics: Unauthorised access. Please check API credentials or permissions."
101+
const errorMessage = isUnauthorisedError(error)
102+
? "Failed to retrieve conversation topics: Unauthorised access. Please check API credentials or permissions"
103103
: `Failed to retrieve conversation topics: ${error instanceof Error ? error.message : JSON.stringify(error)}`;
104104

105-
return errorResult(message);
105+
return errorResult(errorMessage);
106106
}
107107

108108
const topicIds = new Set<string>();
@@ -140,11 +140,11 @@ export const conversationTopics: ToolFactory<
140140
topics.push(...(topicsListings.entities ?? []));
141141
}
142142
} catch (error: unknown) {
143-
const message = isUnauthorisedError(error)
144-
? "Failed to retrieve conversation topics: Unauthorised access. Please check API credentials or permissions."
143+
const errorMessage = isUnauthorisedError(error)
144+
? "Failed to retrieve conversation topics: Unauthorised access. Please check API credentials or permissions"
145145
: `Failed to retrieve conversation topics: ${error instanceof Error ? error.message : JSON.stringify(error)}`;
146146

147-
return errorResult(message);
147+
return errorResult(errorMessage);
148148
}
149149

150150
const topicNames = topics
@@ -158,13 +158,10 @@ export const conversationTopics: ToolFactory<
158158
content: [
159159
{
160160
type: "text",
161-
text: [
162-
`Conversation ID: ${conversationId}`,
163-
"Detected Topics:",
164-
...topicNames.map(
165-
({ name, description }) => ` • ${name}: ${description}`,
166-
),
167-
].join("\n"),
161+
text: JSON.stringify({
162+
conversationId: conversationId,
163+
detectedTopics: topicNames,
164+
}),
168165
},
169166
],
170167
};

src/tools/conversationTranscription/conversationTranscription.test.ts

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,9 @@ describe("Conversation Transcription Tool", () => {
9191
content: [
9292
{
9393
type: "text",
94-
text: "Failed to retrieve transcript: Test Error Message",
94+
text: JSON.stringify({
95+
errorMessage: "Failed to retrieve transcript: Test Error Message",
96+
}),
9597
},
9698
],
9799
});
@@ -120,7 +122,9 @@ describe("Conversation Transcription Tool", () => {
120122
content: [
121123
{
122124
type: "text",
123-
text: "Failed to retrieve transcript: Test Error Message",
125+
text: JSON.stringify({
126+
errorMessage: "Failed to retrieve transcript: Test Error Message",
127+
}),
124128
},
125129
],
126130
});
@@ -205,11 +209,20 @@ describe("Conversation Transcription Tool", () => {
205209
expect(result).toStrictEqual({
206210
content: [
207211
{
208-
text: `
209-
Time Who Sentiment Utterance
210-
00:00 IVR I'm an IVR
211-
00:05 customer Positive I'm a customer
212-
`.trim(),
212+
text: JSON.stringify([
213+
{
214+
time: "00:00",
215+
who: "IVR",
216+
sentiment: "",
217+
utterance: "I'm an IVR",
218+
},
219+
{
220+
time: "00:05",
221+
who: "customer",
222+
sentiment: "Positive",
223+
utterance: "I'm a customer",
224+
},
225+
]),
213226
type: "text",
214227
},
215228
],

0 commit comments

Comments
 (0)