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
2 changes: 1 addition & 1 deletion .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"name": "genesys-cloud-architect",
"source": "./",
"description": "Create, test, and debug Genesys Cloud Architect flows",
"version": "2.0.0"
"version": "2.0.1"
}
]
}
2 changes: 1 addition & 1 deletion .claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "genesys-cloud-architect",
"description": "Create, test, and debug Genesys Cloud Architect flows",
"version": "2.0.0",
"version": "2.0.1",
"author": {
"name": "Lucas Woodward",
"url": "https://makingchatbots.com/"
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ TODO Add example
* `Language Understanding > NLU Domain Version > View`
* `Textbots > *`
* `Architect > Dependency Tracking > View`
* `Routing > Queue > View`

## Who built this?

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "genesys-cloud-architect",
"version": "2.0.0",
"version": "2.0.1",
"private": true,
"packageManager": "pnpm@11.1.3+sha512.c85357fe17ca12dd23dd7071822666dfd7e3cb76fe214e3370b5ea2fb34f2a231185509b63e717f3cd0acb38dd3f8d82bcd5e8172400ae678b70ea4fbed0896d",
"author": {
Expand Down
18 changes: 9 additions & 9 deletions pnpm-lock.yaml

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

118 changes: 59 additions & 59 deletions servers/genesys-cloud-architect-mcp.js

Large diffs are not rendered by default.

8 changes: 7 additions & 1 deletion skills/interpret-flow-ir/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
name: interpret-flow-ir
description: This skill should be used when interpreting the JSON returned by the flow_ir, flow_action or search_in_flow tools, or when the user asks questions about a deployed Genesys Cloud Architect flow, including one they name only by its flow name (resolve it with find_flow first). Structural questions such as "analyse this flow", "trace the path through the flow", "what happens when the customer says X", "why is this task unreachable", "check the flow for missing error handling", "find dead logic", or "does this flow loop". Semantic questions such as "what does this decision check", "what prompt does it play", "what does this data action send", or "what does pressing 2 do". Search questions such as "find every action that references this queue", "which actions use Flow.DNIS", "where does this flow mention that data action", or "which actions play a prompt containing X". Use it to answer control-flow questions from the IR instead of guessing from the flow's raw configuration JSON, to find the actions worth looking at without fetching them all, and to fetch the per-action settings the IR omits.
description: This skill should be used when interpreting the JSON returned by the flow_ir, flow_action or search_in_flow tools, or when the user asks questions about a deployed Genesys Cloud Architect flow, including one they name only by its flow name (resolve it with find_flow first) or a queue they name only by its queue name (resolve it with find_queue first). Structural questions such as "analyse this flow", "trace the path through the flow", "what happens when the customer says X", "why is this task unreachable", "check the flow for missing error handling", "find dead logic", or "does this flow loop". Semantic questions such as "what does this decision check", "what prompt does it play", "what does this data action send", or "what does pressing 2 do". Search questions such as "find every action that references this queue", "which actions use Flow.DNIS", "where does this flow mention that data action", or "which actions play a prompt containing X". Use it to answer control-flow questions from the IR instead of guessing from the flow's raw configuration JSON, to find the actions worth looking at without fetching them all, and to fetch the per-action settings the IR omits.
---

# Interpreting Flow IRs
Expand All @@ -22,6 +22,12 @@ All three take a flow id, not a flow name. When only the name is known (e.g.
"analyse Book_Payment"), resolve it first with `find_flow`, which searches flow
names and returns each match's id, name, type, and published version.

Likewise, when the user names a *queue* (e.g. "which actions transfer to Sales"),
resolve it first with `find_queue`, which searches queue names and returns each
match's id, exact name, division and in-queue flows. Give `search_in_flow` the
exact name it returns, since a fragment the user typed may not be how the queue is
spelled in the flow.

## Tool output shape

On success the tool returns compact JSON: `{ flowId, ir, warnings }`. Failures
Expand Down
2 changes: 1 addition & 1 deletion skills/write-flow/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ Ask the user:
- **What it should do**: routing, menus, greetings, queue transfers, data lookups, etc.
- **Flow name**: what to name the flow in Architect

Only ask about queue names if the user's description involves queue transfers. Not every flow routes to a queue.
Only ask about queue names if the user's description involves queue transfers. Not every flow routes to a queue. When the user does name a queue, confirm it exists with the `find_queue` MCP tool before writing the flow, and use the exact queue name it returns in the transfer action.

### 2. Read the relevant references

Expand Down
5 changes: 5 additions & 0 deletions src/mcp-server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import platformClient from "purecloud-platform-client-v2";
import { z } from "zod/v3";
import { deployFlow } from "./tools/deploy-flow.ts";
import { findFlow } from "./tools/find-flow.ts";
import { findQueue } from "./tools/find-queue.ts";
import { flowAction } from "./tools/flow-action.ts";
import { flowDependencies } from "./tools/flow-dependencies.ts";
import { flowIr } from "./tools/flow-ir.ts";
Expand Down Expand Up @@ -38,10 +39,14 @@ const server = new McpServer({
});

const architectApi = new platformClient.ArchitectApi();
const routingApi = new platformClient.RoutingApi();

const findFlowTool = findFlow({ architectApi });
server.registerTool("find_flow", findFlowTool.config, findFlowTool.handler);

const findQueueTool = findQueue({ routingApi });
server.registerTool("find_queue", findQueueTool.config, findQueueTool.handler);

const flowDependenciesTool = flowDependencies({ architectApi });
server.registerTool(
"flow_dependencies",
Expand Down
38 changes: 38 additions & 0 deletions src/mcp-server/tools/api-error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
export interface ApiError {
status?: number;
code?: string;
message?: string;
}

export function toApiError(err: unknown): ApiError {
if (err === null || typeof err !== "object") {
return { message: String(err) };
}
const status =
"status" in err && typeof err.status === "number"
? err.status
: undefined;
const code =
"code" in err && typeof err.code === "string" && err.code.length > 0
? err.code
: undefined;
const message =
"message" in err &&
typeof err.message === "string" &&
err.message.length > 0
? err.message
: undefined;
return { status, code, message };
}

export function formatApiError(err: unknown): string {
const { status, code, message } = toApiError(err);
const parts: string[] = [];
if (status !== undefined) {
parts.push(`HTTP ${status}${code ? ` (${code})` : ""}`);
} else if (code) {
parts.push(code);
}
if (message) parts.push(message);
return parts.length > 0 ? parts.join(": ") : "Unknown error";
}
17 changes: 2 additions & 15 deletions src/mcp-server/tools/fetch-flow-configuration.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { ArchitectApi } from "purecloud-platform-client-v2";
import { toApiError } from "./api-error.ts";

export type FetchFlowConfigurationResult =
| { ok: true; configuration: unknown }
Expand All @@ -20,21 +21,7 @@ export async function fetchFlowConfiguration(
}

function describeFailure(flowId: string, err: unknown): string {
const status =
err !== null &&
typeof err === "object" &&
"status" in err &&
typeof err.status === "number"
? err.status
: undefined;
const detail =
err !== null &&
typeof err === "object" &&
"message" in err &&
typeof err.message === "string" &&
err.message.length > 0
? err.message
: undefined;
const { status, message: detail } = toApiError(err);

if (status === 404) {
return `Flow "${flowId}" not found. Check the flow id.`;
Expand Down
30 changes: 3 additions & 27 deletions src/mcp-server/tools/find-flow.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import type platformClient from "purecloud-platform-client-v2";
import type { ArchitectApi } from "purecloud-platform-client-v2";
import { z } from "zod/v3";
import { formatApiError } from "./api-error.ts";
import { isExactNameMatch, moveExactMatchToTop } from "./name-match.ts";
import type { ToolFactory } from "./types.ts";

const MAX_RETURNED_FLOWS = 50;
Expand All @@ -25,32 +27,6 @@ interface FindFlowResult {
notes?: string[];
}

/**
* Returned flows are capped, so without this an exactly-named flow past the
* cap would be silently dropped from a broad query's response. Relies on
* Array.prototype.sort being stable (guaranteed since ES2019) to keep API
* order for the rest.
*/
function moveExactMatchToTop(
flows: platformClient.Models.Flow[],
name: string,
): platformClient.Models.Flow[] {
return flows
.slice()
.sort(
(a, b) =>
Number(isExactNameMatch(b, name)) -
Number(isExactNameMatch(a, name)),
);
}

function isExactNameMatch(
flow: platformClient.Models.Flow,
name: string,
): boolean {
return flow.name.toLowerCase() === name.toLowerCase();
}

function toFlowSummary(flow: platformClient.Models.Flow): FlowSummary {
return {
id: flow.id ?? "",
Expand Down Expand Up @@ -181,7 +157,7 @@ export const findFlow: ToolFactory<ToolConfig, typeof inputSchema> = ({
content: [
{
type: "text",
text: `Failed to search for flows: ${err instanceof Error ? err.message : String(err)}`,
text: `Failed to search for flows: ${formatApiError(err)}`,
},
],
};
Expand Down
Loading
Loading