Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 12 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,18 @@ A Model Context Protocol (MCP) server for Genesys Cloud's Platform API.

## Usage with Claude Desktop

### MCP Bundle

This MCP Server provides an [MCP Bundle](https://github.com/anthropics/mcpb) (.mcpb file) along with each [release](https://github.com/MakingChatbots/genesys-cloud-mcp-server/releases),
which is a single-click installable package for Claude Desktop. To use it:

1. Download the `.mcpb` file from the [latest release](https://github.com/MakingChatbots/genesys-cloud-mcp-server/releases)
2. In Claude Desktop navigate to Settings > Extensions.
3. Open the .mcpb file with Claude
4. Configure the Region and OAuth Client for the extension

The extension will now be available in your conversations.

### NPX

Add this to your `claude_desktop_config.json`:
Expand All @@ -43,19 +55,6 @@ Add this to your `claude_desktop_config.json`:
}
```

### MCP Bundle

This MCP Server provides an [MCP Bundle](https://github.com/anthropics/mcpb) (.mcpb file) along with each [release](https://github.com/MakingChatbots/genesys-cloud-mcp-server/releases),
which is a single-click installable package for Claude Desktop. To use it:

1. Download the `.mcpb` file from the [latest release](https://github.com/MakingChatbots/genesys-cloud-mcp-server/releases)
2. In Claude Desktop navigate to Settings > Extensions.
3. Browse to, or drag in the .mcpb file downloaded
4. Click "Install"
5. Configure the Region and OAuth Client for the extension

The extension will now be available in your conversations.

## Usage with Gemini CLI

Add below to your `.gemini/settings.json` file. You can read more about the [setup from the official guide](https://github.com/google-gemini/gemini-cli/blob/main/docs/cli/tutorials.md#configure-the-mcp-server-in-settingsjson).
Expand Down
10 changes: 9 additions & 1 deletion manifest.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"manifest_version": "0.2",
"name": "Genesys Cloud MCP Server",
"version": "0.0.16",
"version": "1.0.2",
"description": "Interact with Genesys Cloud's Platform API",
"long_description": "This extension allows Claude to connect to Genesys Cloud's Platform API via a local MCP server. It provides tools for querying queue volumes, retrieving conversation samples, analyzing sentiment and voice quality, accessing transcripts, and more.\n\nThis project is not affiliated with Genesys.",
"author": {
Expand Down Expand Up @@ -55,6 +55,14 @@
{
"name": "conversation_transcript",
"description": "Retrieves a structured transcript of the conversation, including speaker labels, utterance timestamps, and sentiment annotations where available. The transcript is formatted as a time-aligned list of utterances attributed to each participant (e.g., customer or agent)."
},
{
"name": "oauth_clients",
"description": "Retrieves a list of all OAuth clients, including their associated roles and divisions. This tool is useful for auditing and managing OAuth clients in the Genesys Cloud organization."
},
{
"name": "oauth_client_usage",
"description": "Retrieves the usage of an OAuth Client for a given period. It returns the total number of requests and a breakdown of requests per organization."
}
],
"user_config": {
Expand Down
14 changes: 12 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 5 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@makingchatbots/genesys-cloud-mcp-server",
"version": "1.0.1",
"version": "1.0.2",
"description": "A Model Context Protocol (MCP) server exposing Genesys Cloud tools for LLMs, including sentiment analysis, conversation search, topic detection and more.",
"bin": "./dist/cli.js",
"type": "module",
Expand Down Expand Up @@ -39,19 +39,20 @@
"dependencies": {
"@modelcontextprotocol/sdk": "^1.18.2",
"date-fns": "^4.1.0",
"lru-cache": "^11.2.4",
"purecloud-platform-client-v2": "^239.0.0",
"zod": "^3.23.8"
},
"devDependencies": {
"dotenv": "^17.2.2",
"@anthropic-ai/mcpb": "^2.0.1",
"@biomejs/biome": "2.2.4",
"@google/genai": "^1.12.0",
"@types/node": "^18.19.130",
"dotenv": "^17.2.2",
"lint-staged": "^16.2.7",
"tsx": "^4.20.6",
"typescript": "^5.8.3",
"vitest": "^3.2.4",
"@biomejs/biome": "2.2.4"
"vitest": "^3.2.4"
},
"lint-staged": {
"src/*.ts": [
Expand Down
19 changes: 17 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { LRUCache } from "lru-cache";
import platformClient from "purecloud-platform-client-v2";
import { OAuthClientCredentialsWrapper } from "./auth/OAuthClientCredentialsWrapper.js";
import { createConfigRetriever } from "./createConfigRetriever.js";
import { conversationSentiment } from "./tools/conversationSentiment/conversationSentiment.js";
import { conversationTopics } from "./tools/conversationTopics/conversationTopics.js";
import { conversationTranscription } from "./tools/conversationTranscription/conversationTranscription.js";
import { oauthClients } from "./tools/oauthClients/oauthClients.js";
import { oauthClientUsage } from "./tools/oauthClientUsage/oauthClientUsage.js";
import {
type OAuthClientUsageResponse,
oauthClientUsage,
} from "./tools/oauthClientUsage/oauthClientUsage.js";
import { queryQueueVolumes } from "./tools/queryQueueVolumes/queryQueueVolumes.js";
import { sampleConversationsByQueue } from "./tools/sampleConversationsByQueue/sampleConversationsByQueue.js";
import { searchQueues } from "./tools/searchQueues.js";
Expand All @@ -21,7 +25,17 @@ const withAuth = OAuthClientCredentialsWrapper(

const server: McpServer = new McpServer({
name: "Genesys Cloud",
version: "1.0.1", // Same version as version in package.json
version: "1.0.2", // Same version as version in package.json
});

const cache = new LRUCache<string, OAuthClientUsageResponse>({
max: 500,
ttl: 1000 * 60 * 5, // 5 minutes

allowStale: false,

updateAgeOnGet: false,
updateAgeOnHas: false,
});

const routingApi = new platformClient.RoutingApi();
Expand Down Expand Up @@ -147,6 +161,7 @@ server.registerTool(
);

const oauthClientUsageTool = oauthClientUsage({
cache,
oauthApi,
});
server.registerTool(
Expand Down
65 changes: 49 additions & 16 deletions src/tools/oauthClientUsage/oauthClientUsage.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { LRUCache } from "lru-cache";
import type { Models, OAuthApi } from "purecloud-platform-client-v2";
import { z } from "zod";
import { createTool, type ToolFactory } from "../utils/createTool.js";
Expand All @@ -6,12 +7,24 @@ import { isUnauthorisedError } from "../utils/genesys/isUnauthorisedError.js";
import { waitFor } from "../utils/waitFor.js";

const MAX_ATTEMPTS = 10;
const TOOL_CACHE_KEY = "oauthClientUsage";

export interface OAuthClientUsageResponse {
startDate: string;
endDate: string;
totalRequest: number;
requestsPerOrganisation: {
organisationId?: string;
requests?: number;
}[];
}

export interface ToolDependencies {
readonly oauthApi: Pick<
OAuthApi,
"postOauthClientUsageQuery" | "getOauthClientUsageQueryResult"
>;
readonly cache?: LRUCache<string, OAuthClientUsageResponse>;
}

const paramsSchema = z.object({
Expand All @@ -36,7 +49,7 @@ const paramsSchema = z.object({
export const oauthClientUsage: ToolFactory<
ToolDependencies,
typeof paramsSchema
> = ({ oauthApi }) =>
> = ({ oauthApi, cache }) =>
createTool({
schema: {
name: "oauth_client_usage",
Expand All @@ -59,14 +72,27 @@ export const oauthClientUsage: ToolFactory<
to.setTime(now.getTime());
}

const cacheName = `${TOOL_CACHE_KEY}.${oauthClientId}-${from.getTime()}-${to.getTime()}`;

const cachedResult = cache?.get(cacheName);
if (cachedResult) {
return {
content: [
{
type: "text",
text: JSON.stringify(cachedResult),
},
],
};
}

let result: Models.UsageExecutionResult;
try {
result = await oauthApi.postOauthClientUsageQuery(oauthClientId, {
interval: `${from.toISOString()}/${to.toISOString()}`,
metrics: ["Requests"],
groupBy: ["OrganizationId"],
});
console.log(result);
} catch (error: unknown) {
const errorMessage = isUnauthorisedError(error)
? "Failed to retrieve usage of OAuth client: Unauthorised access. Please check API credentials or permissions"
Expand Down Expand Up @@ -118,24 +144,31 @@ export const oauthClientUsage: ToolFactory<
);
}

const toolResult: OAuthClientUsageResponse = {
startDate,
endDate,
totalRequest: (apiUsageQueryResult?.results ?? []).reduce(
(acc, curr) => acc + (curr.requests ?? 0),
0,
),
requestsPerOrganisation: (apiUsageQueryResult?.results ?? []).map(
(result) => ({
organisationId: result.organizationId,
requests: result.requests,
}),
),
};

cache?.set(
`${TOOL_CACHE_KEY}.${oauthClientId}-${from.getTime()}-${to.getTime()}`,
toolResult,
);

return {
content: [
{
type: "text",
text: JSON.stringify({
startDate,
endDate,
totalRequest: (apiUsageQueryResult?.results ?? []).reduce(
(acc, curr) => acc + (curr.requests ?? 0),
0,
),
requestsPerOrganisation: (apiUsageQueryResult?.results ?? []).map(
(result) => ({
organisationId: result.organizationId,
requests: result.requests,
}),
),
}),
text: JSON.stringify(toolResult),
},
],
};
Expand Down
2 changes: 1 addition & 1 deletion tests/integration/serverRuns.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ describe("Server Runs", () => {

client = new Client({
name: "test-client",
version: "1.0.1",
version: "1.0.2",
});

await client.connect(transport);
Expand Down