@makingchatbots/genesys-cloud-mcp-server
Version:
A Model Context Protocol (MCP) server exposing Genesys Cloud tools for LLMs, including sentiment analysis, conversation search, topic detection and more.
126 lines (125 loc) • 5.36 kB
JavaScript
import { z } from "zod";
import { createTool } from "../utils/createTool.js";
import { errorResult } from "../utils/errorResult.js";
import { isUnauthorisedError } from "../utils/genesys/isUnauthorisedError.js";
import { waitFor } from "../utils/waitFor.js";
import { isQueueUsedInConvo } from "./isQueueUsedInConvo.js";
const MAX_ATTEMPTS = 10;
const paramsSchema = z.object({
queueIds: z
.array(z
.string()
.uuid()
.describe("A UUID for a queue. (e.g., 00000000-0000-0000-0000-000000000000)"))
.min(1)
.max(300)
.describe("List of up to MAX of 300 queue IDs"),
startDate: z
.string()
.describe("The start date/time in ISO-8601 format (e.g., '2024-01-01T00:00:00Z')"),
endDate: z
.string()
.describe("The end date/time in ISO-8601 format (e.g., '2024-01-07T23:59:59Z')"),
});
export const queryQueueVolumes = ({ analyticsApi }) => createTool({
schema: {
name: "query_queue_volumes",
annotations: { title: "Query Queue Volumes" },
description: "Returns a breakdown of how many conversations occurred in each specified queue between two dates. Useful for comparing workload across queues. MAX 300 queue IDs.",
paramsSchema,
},
call: async ({ queueIds, startDate, endDate }) => {
const from = new Date(startDate);
const to = new Date(endDate);
if (Number.isNaN(from.getTime()))
return errorResult("startDate is not a valid ISO-8601 date");
if (Number.isNaN(to.getTime()))
return errorResult("endDate is not a valid ISO-8601 date");
if (from >= to)
return errorResult("Start date must be before end date");
const now = new Date();
if (to > now) {
to.setTime(now.getTime());
}
try {
const job = await analyticsApi.postAnalyticsConversationsDetailsJobs({
interval: `${from.toISOString()}/${to.toISOString()}`,
order: "asc",
orderBy: "conversationStart",
segmentFilters: [
{
type: "and",
predicates: [
{
dimension: "purpose",
value: "customer",
},
],
},
{
type: "or",
predicates: queueIds.map((id) => ({
dimension: "queueId",
value: id,
})),
},
],
});
const jobId = job.jobId;
if (!jobId)
return errorResult("Job ID not returned from Genesys Cloud.");
let state;
let attempts = 0;
while (attempts < MAX_ATTEMPTS) {
const jobStatus = await analyticsApi.getAnalyticsConversationsDetailsJob(jobId);
state = jobStatus.state ?? "UNKNOWN";
if (state === "FULFILLED")
break;
switch (jobStatus.state) {
case "FAILED":
return errorResult("Analytics job failed.");
case "CANCELLED":
return errorResult("Analytics job was cancelled.");
case "EXPIRED":
return errorResult("Analytics job results have expired.");
case "UNKNOWN":
return errorResult("Analytics job returned an unknown or undefined state.");
}
await waitFor(3000);
attempts++;
}
if (state !== "FULFILLED") {
return errorResult("Timed out waiting for analytics job to complete.");
}
const results = await analyticsApi.getAnalyticsConversationsDetailsJobResults(jobId);
const conversations = results.conversations ?? [];
const queueConversationCount = new Map();
for (const convo of conversations) {
for (const queueId of queueIds) {
if (isQueueUsedInConvo(queueId, convo)) {
const count = queueConversationCount.get(queueId) ?? 0;
queueConversationCount.set(queueId, count + 1);
}
}
}
const queueBreakdown = queueIds.map((id) => {
const totalConversations = queueConversationCount.get(id) ?? 0;
return { queueId: id, totalConversations };
});
return {
content: [
{
type: "text",
text: JSON.stringify({ queues: queueBreakdown }),
},
],
};
}
catch (error) {
const errorMessage = isUnauthorisedError(error)
? "Failed to query conversations: Unauthorised access. Please check API credentials or permissions"
: `Failed to query conversations: ${error instanceof Error ? error.message : JSON.stringify(error)}`;
return errorResult(errorMessage);
}
},
});