cxone-wfm-intraday-mcp

cxone-wfm-intraday-mcp

An MCP server that exposes governed CXone WFM Intraday tools to an AI assistant, enabling real-time analyst workflows like risk assessment and SLA recovery using synthetic data.

Category
Visit Server

README

CXone WFM Intraday MCP (Sparkathon Prototype)

An MCP server that exposes governed CXone WFM Intraday tools to an AI assistant. It proves one Real-Time Analyst workflow — "Why is Billing Support at risk today, and what can we do to recover SLA?" — grounded entirely in structured tool calls over deterministic synthetic data. Simulation only; no live systems, no write-back.

Design spec: docs/superpowers/specs/2026-07-15-cxone-wfm-intraday-mcp-design.md.

Tools

Tool Purpose
list_intraday_metrics Catalog of queryable Intraday metrics
resolve_intraday_entity Fuzzy name → canonical queue
get_intraday_snapshot Current state of all/selected queues + highest-risk queue
get_forecast_vs_actual Per-interval forecast vs actual variance
get_intraday_risk_drivers Ranked reasons a queue is at risk
get_available_capacity Donor queues with spare capacity + trade-offs
simulate_recovery_action Before/after impact of moving N agents for Y minutes
summarise_intraday_recommendation Plain-English what / why / action / impact / trade-offs
load_intraday_data Replace the dataset with your own queues (bring-your-own-data)
reset_intraday_data Restore the built-in demo dataset

Ways to run it

Pick the path that fits — all three end with the same 10 tools available to your assistant:

  • Fastest, no install → Quick start (uv): one uvx command, nothing to clone.
  • Local dev / offline / to change the data → Setup: clone + venv + pip install.
  • Share via a URL (teammates install nothing) → Run as a remote HTTP MCP: deploy to Cloud Run.

Then, in your assistant, ask "Which queues are at risk right now?" and follow the Demo script.

Quick start (no clone, via uv)

If you have uv installed and access to this repo, register the server with a single command — no git clone, no venv, no pip install. uv fetches, installs, and runs it in an ephemeral environment straight from the repo.

Claude Code:

claude mcp add cxone-wfm-intraday -- uvx --from git+https://github.com/ssolanky/cxone-wfm-intraday-mcp cxone-wfm-intraday

Claude Desktop — add to claude_desktop_config.json (Settings → Developer → Edit Config):

{
  "mcpServers": {
    "cxone-wfm-intraday": {
      "command": "uvx",
      "args": ["--from", "git+https://github.com/ssolanky/cxone-wfm-intraday-mcp", "cxone-wfm-intraday"]
    }
  }
}

Notes: this is a local server — uv runs it on your machine, it just removes the manual clone/install steps. Because the repo is private, uv needs Git access to it (a credential helper via gh auth setup-git, or a token). No uv? Use the full Setup below instead.

Setup

git clone https://github.com/ssolanky/cxone-wfm-intraday-mcp.git
cd cxone-wfm-intraday-mcp
python -m venv .venv
# Windows:  .venv\Scripts\activate       macOS/Linux:  source .venv/bin/activate
python -m pip install -e ".[dev]"
python -m pytest -q          # all tests should pass (64 tests)

NICE machines: pip here defaults to a private CodeArtifact index that 401s and lacks mcp. Install from public PyPI instead: python -m pip install --index-url https://pypi.org/simple/ -e ".[dev]" (or add index-url = https://pypi.org/simple/ under [global] in .venv/pip.ini).

Run against Claude

Claude Code:

claude mcp add cxone-wfm-intraday -- python -m cxone_wfm_intraday_mcp.server

(run from this project directory, using the interpreter where the package is installed).

Claude Desktop: copy the block from claude_desktop_config.example.json into your claude_desktop_config.json (Settings → Developer → Edit Config), fixing command to your Python interpreter, then restart Claude Desktop.

Run as a remote HTTP MCP (optional)

The same server can serve the Streamable HTTP transport instead of stdio, so teammates add just a URL — nothing installed locally. It's zero config: the server serves HTTP automatically when a PORT env var is present (as on Google Cloud Run) or MCP_TRANSPORT=http is set, and otherwise stays stdio for local use. HTTP endpoint path is /mcp.

Test it locally:

# macOS/Linux
MCP_TRANSPORT=http PORT=8080 python -m cxone_wfm_intraday_mcp.server
# Windows PowerShell
$env:MCP_TRANSPORT="http"; $env:PORT="8080"; python -m cxone_wfm_intraday_mcp.server
# now serving at http://127.0.0.1:8080/mcp

Deploy to Google Cloud Run (free tier, scales to zero) — from the repo root, using the included Dockerfile:

gcloud run deploy cxone-wfm-intraday --source . --region <region>

Cloud Run builds the image, injects PORT, and the server serves HTTP automatically. It prints a service URL; register it with any assistant:

claude mcp add --transport http cxone-wfm-intraday https://<service-url>/mcp

Auth: a public URL serving even mock data should be protected. Cloud Run requires IAM auth by default (omit --allow-unauthenticated); for other hosts put a token/proxy in front. Authentication was deliberately deferred by the Sparkathon spec — add it before any non-demo use.

Demo script

  1. "Which queues are at risk right now?" → snapshot flags Billing Support.
  2. "Why is Billing Support at risk?" → volume +18%, AHT +11%, staffing −3.
  3. "What can we do to recover?" → move 2 agents from General Enquiries.
  4. "What would the impact be?" → SLA 72% → ~81%, General Enquiries stays above target.

What you can ask (and what you'll get back)

You never call tools by name — just ask in plain language and the assistant picks the right tool(s). The tables below list example prompts, the tool(s) that fire, and the answer you can expect on the built-in demo data (swap in your own data and the same questions work — see Bring your own data).

Reading current state

Ask something like… Tool(s) Expected answer (demo data)
"What metrics can I query?" / "What data do you have?" list_intraday_metrics The 11 Intraday metrics with definitions/units: SLA, SLA target, forecast/actual volume, forecast/actual AHT, planned/actual staffing, occupancy, backlog, risk status.
"Which queues are at risk right now?" / "Give me the intraday snapshot" get_intraday_snapshot All 5 queues with SLA/target, volume, AHT, staffing, occupancy, backlog, risk — and highest risk: Billing Support (72% vs 80% target).
"How is Sales doing?" / "Show me just Billing and General Enquiries" resolve_intraday_entity → get_intraday_snapshot The selected queue(s) only. Sales → 83% vs 80% target, watch.
"Does 'GE' / 'the billing queue' / 'tech support' map to a real queue?" resolve_intraday_entity Resolves fuzzy names/aliases → General Enquiries / Billing Support / Technical Support (with a confidence level).

Explaining risk

Ask something like… Tool(s) Expected answer (demo data)
"Why is Billing Support at risk?" resolve_intraday_entity → get_intraday_risk_drivers Primary: volume +18% above forecast. Also AHT +11%, staffing 3 below plan, occupancy 92%, backlog 47. Severity: high.
"How are actuals tracking vs forecast for Billing this morning?" get_forecast_vs_actual Six 30-min intervals (08:00–10:30); variance widens from ~0% to +18% volume / +11% AHT, staffing drifts 15 → 12.
"Which queue is under the most pressure and why?" get_intraday_snapshot → get_intraday_risk_drivers Billing Support, driven by the volume spike + AHT + staffing gap (as above).

Finding capacity & recovering

Ask something like… Tool(s) Expected answer (demo data)
"Is there spare capacity to help Billing?" / "Who can lend agents?" get_available_capacity General Enquiries can spare 2 (SLA 94% → ~92%, still above target); Complaints can spare 1. Sales & Technical Support: none (staffed to plan).
"What's the best recovery action?" / "How do we recover Billing's SLA?" summarise_intraday_recommendation Move 2 agents from General Enquiries to Billing Support for 60 min → Billing SLA 72% → ~81.2%, GE stays above target. Trade-off + "monitor AHT and backlog" included.
"What happens if I move 2 agents from General Enquiries to Billing for 60 minutes?" simulate_recovery_action Billing 72% → 81.2% (occupancy 92 → 79); GE 94% → 91.9% (occupancy 71 → 81), stays above its 80% target; no new risk.
"What if I move 3 instead?" simulate_recovery_action Billing → 84.6%, GE → 90.6% — but flagged: 3 exceeds GE's spare capacity of 2, so GE drops below its own plan.
"Can I pull 2 from Sales instead?" simulate_recovery_action Billing → 81.2%, but Sales falls 83% → ~71%, below its 80% target — not a safe donor (risk created).

Bring-your-own-data & housekeeping

Ask something like… Tool(s) Expected answer
"Load this intraday data, then tell me which queue is at risk: {…}" load_intraday_data "Loaded N queues; highest risk: <id>." Every later question then analyzes your data.
"Reset to the demo data" reset_intraday_data Restores the built-in 5-queue demo for your session.

A full worked conversation (demo data)

You: Which queues are at risk right now? Assistant: Five queues are live. Billing Support is the highest risk — SLA 72% against an 80% target. General Enquiries (94%), Technical Support (88%) and Complaints (91%) are healthy; Sales (83%) is on watch.

You: Why is Billing Support at risk? Assistant: Three things are compounding: volume is 18% above forecast, AHT is 11% higher than expected, and staffing is 3 agents below plan — pushing occupancy to 92% with a backlog of 47. The variance has widened steadily since 08:00.

You: What can we do to recover? Assistant: General Enquiries has 2 agents of spare capacity (it's 2 over plan at 94% SLA). Complaints could lend 1; Sales and Technical Support have none.

You: What would moving 2 from General Enquiries do? Assistant: Billing Support SLA improves from 72% to ~81.2% and its occupancy eases from 92% to ~79%. General Enquiries dips to ~91.9% — still comfortably above its 80% target. No new risk is created. Recommend the move for 60 minutes and keep watching AHT and backlog.

When something's off (error responses)

Ask something like… What you'll get
"Analyze the Refunds queue" An error naming the valid queues: "Could not resolve 'Refunds'… Valid queues: Billing Support, Complaints, General Enquiries, Sales, Technical Support."
"Move 50 agents from General Enquiries to Billing" "Cannot move 50 agents from General Enquiries; only 16 are staffed."
Loading data with a missing/invalid field A clear message (e.g. "queue 'x': missing required field 'sla_current'" or "risk_status must be one of ('at_risk','watch','healthy')"), and the current dataset is left untouched.

Where the data comes from

All data is synthetic and hardcoded in cxone_wfm_intraday_mcp/data.py — a fixed in-memory list of 5 queues loaded once at startup. There is no database, API, CXone tenant, network call, or randomness, so every run returns identical numbers (a repeatable demo by design). This matches the spec: for the prototype, mocked data is acceptable; the point is proving the pattern of grounded, tool-driven answers.

What's stored by hand vs. computed at call time:

  • Stored (the demo "facts"), per queue in data.py: sla_current, sla_target, volume_actual/forecast, aht_actual/forecast, staffing_planned/actual, occupancy, backlog, a risk_status label (at_risk / watch / healthy), and a 6-interval forecast-vs-actual history.
  • Computed from those stored fields at call time — this is the real "analysis":
    • get_intraday_snapshot ranks queues by stored risk_status (analysis.py).
    • get_forecast_vs_actual / get_intraday_risk_drivers compute volume/AHT/staffing variance from the stored actual-vs-forecast values and flag drivers over thresholds (analysis.py).
    • get_available_capacity derives spare agents (surplus over plan, validated against the SLA curve).
    • simulate_recovery_action recomputes SLA for both queues via the formula below (simulation.py).

So "Billing Support is at risk" is a hand-authored label, but "…because volume is +18%, AHT +11%, staffing −3, and moving 2 agents lifts SLA 72→81.2%" is genuinely calculated from the stored fields — never invented by the assistant.

Change the scenario (make a different queue at risk, tweak SLA/volume/staffing, add a queue) by editing the literals in data.py — nothing else needs to change. In a real product, data.py would be replaced by live CXone WFM as the source of truth; the tool contracts stay identical.

Bring your own data

Consumers can analyze their own Intraday data without editing any code, via two tools:

  • load_intraday_data — replace the dataset with your own queues; every other tool then analyzes your data.
  • reset_intraday_data — restore the built-in demo.

Just ask the assistant (e.g. "Load this Intraday data and tell me which queue is at risk") and provide queues shaped like:

{
  "queues": [
    {"id": "payments", "name": "Payments", "sla_current": 64, "sla_target": 85,
     "volume_forecast": 200, "volume_actual": 260, "aht_forecast": 280, "aht_actual": 320,
     "staffing_planned": 18, "staffing_actual": 14, "occupancy": 94, "backlog": 40,
     "risk_status": "at_risk", "aliases": ["pay"]},
    {"id": "onboarding", "name": "Onboarding", "sla_current": 97, "sla_target": 80,
     "volume_forecast": 90, "volume_actual": 80, "aht_forecast": 300, "aht_actual": 290,
     "staffing_planned": 7, "staffing_actual": 10, "occupancy": 55, "backlog": 0,
     "risk_status": "healthy"}
  ]
}

Per-queue fields: id, name, sla_current, sla_target, volume_forecast, volume_actual, aht_forecast, aht_actual, staffing_planned, staffing_actual (≥ 1 — the SLA curve divides by it), occupancy, backlog, risk_status (at_risk | watch | healthy). Optional: aliases (list) and intervals (list of {interval, volume_forecast, volume_actual, aht_forecast, aht_actual, staffing_planned, staffing_actual}). Percentages are 0–100, times are seconds, staffing is agent counts. Invalid input is rejected with a clear message and leaves the current dataset untouched.

Isolation: loaded data is scoped to your MCP session — on a shared HTTP server each conversation gets its own dataset (one client's load never affects another). reset_intraday_data, or the session ending, restores the demo for that session only. stdio has a single session (just you). Nothing is persisted to disk; the dataset lives in memory for the life of the session.

The simulation model

Rule-based and deterministic. Each queue has a staffing→SLA response curve sla(n) = 100 − (100 − sla_current)·decay^(n − staff_actual), with decay = exp(−2.4 / staff_actual), anchored so sla(actual) = current SLA. Every number the assistant reports comes from this formula given tool inputs — not from the model's own reasoning. See §6 of the design spec.

Recommended Servers

playwright-mcp

playwright-mcp

A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.

Official
Featured
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.

Official
Featured
Local
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.

Official
Featured
Local
TypeScript
VeyraX MCP

VeyraX MCP

Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.

Official
Featured
Local
graphlit-mcp-server

graphlit-mcp-server

The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.

Official
Featured
TypeScript
Kagi MCP Server

Kagi MCP Server

An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.

Official
Featured
Python
E2B

E2B

Using MCP to run code via e2b.

Official
Featured
Neon Database

Neon Database

MCP server for interacting with Neon Management API and databases

Official
Featured
Exa Search

Exa Search

A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.

Official
Featured
Qdrant Server

Qdrant Server

This repository is an example of how to create a MCP server for Qdrant, a vector search engine.

Official
Featured