UNPKG

@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
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); } }, });