Skip to main content
Glama
ssolanky

cxone-wfm-intraday-mcp

by ssolanky

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

Related MCP server: Sablier MCP Server

Ways to run it

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

  • Fastest, no installQuick start (uv): one uvx command, nothing to clone.

  • Local dev / offline / to change the dataSetup: 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_entityget_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_entityget_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_snapshotget_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.

Install Server
F
license - not found
A
quality
B
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/ssolanky/cxone-wfm-intraday-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server