Customer Service Data Analyst MCP Server
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@Customer Service Data Analyst MCP Server How many refund requests are there?"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Customer Service Data Analyst Agent
A LangGraph ReAct agent that answers questions about the Bitext customer-support dataset (26,872 tagged support messages across 11 categories and 27 intents). It routes each question, calls typed tools over the data, remembers the conversation and a per-user profile across restarts, and exposes its tools over the Model Context Protocol.
It handles three kinds of question:
Type | Example | What happens |
Structured | "How many refund requests are there?" | chains tools → 997 (3.71%) |
Unstructured | "Summarize the FEEDBACK category." | samples rows → a grounded summary |
Out-of-scope | "Who is the president of France?" | politely declined, never answered from general knowledge |
Built by Carmit Shaemesh Haas for Nebius Academy Assignment 3.
Demo
The CLI prints every step of the agent's reasoning. Below, it answers a question and then resolves a follow-up ("what about cancellations?") — noticing a wrong filter and retrying:

The Streamlit UI shows the same reasoning in a chat, with a session switcher and the live user profile in the sidebar.
A structured question | The query recommender | An out-of-scope decline |
|
|
|
Related MCP server: SQL MCP Server
Architecture
The agent is a LangGraph graph. A dedicated router classifies every question before any tool is chosen; out-of-scope questions are refused structurally (they never reach the generation model's general knowledge). In-scope questions enter the ReAct loop, and a profile-update step distills what it learned about the user.

The compiled LangGraph itself (auto-rendered from the code):

The editable source for the system diagram is
docs/architecture.drawio.
Pieces:
Router (
src/cs_agent/agent/router.py) — labels a questionstructured,unstructured,out_of_scope, orrecommend, using the small model with typed structured output (and a plain-text fallback).Tools (
src/cs_agent/tools/) — five Pydantic-typed tools (list_categories,list_intents,filter_records,count_records,summarize_category) implemented as pure functions over a pandas DataFrame. The agent and the MCP server both call these same functions, so they can never drift apart.Memory — two kinds:
Episodic: a LangGraph SqliteSaver checkpoint per
--session, so a conversation resumes after a restart and follow-ups ("what about refunds?") resolve.Semantic: a per-user profile in
profiles/<user>.md(name, interests, preferences), distilled after each answered turn and injected into the prompt.
Guardrails — a
declinenode for out-of-scope questions and a graceful fallback afterMAX_ITERATIONS(12) so the loop never spins forever.MCP — a FastMCP server (
mcp_server/server.py) exposes the same five tools to any MCP client.
Model choice
Both models run on Nebius Token Factory (OpenAI-compatible). The agent uses two, on purpose:
Role | Model | Why |
Generation, tool calling, summaries, recommendations |
| reliable OpenAI-style function calling and grounded writing |
Routing + profile distillation |
| a Mixture-of-Experts model with ~3B active parameters: much cheaper and faster than the 70B, and strong at short classification and merge tasks |
Routing and profile-merging are easy, high-volume jobs, so they go to the small fast model;
the heavier reasoning and writing go to the large one. Both IDs live in
src/cs_agent/config.py and can be overridden via .env.
Quickstart (clone to running in ~5 minutes)
Prerequisites: Python 3.11+, a Nebius Token Factory
API key, and uv (recommended) or pip.
# 1. clone
git clone https://github.com/CarmitHaas/customer-service-agent-carmit-haas.git
cd customer-service-agent-carmit-haas
# 2. install (creates a venv and installs the package + deps)
uv sync
# --- or with pip ---
# python -m venv .venv && source .venv/bin/activate
# pip install -e .
# 3. add your API key
cp .env.example .env
# edit .env and set NEBIUS_API_KEY=...
# 4. run the CLI
uv run python main.py --session demo --user carmitOn first run the dataset (~27k rows) is downloaded from Hugging Face once and cached to
data/bitext.parquet, so later runs start instantly and work offline.
Using the CLI
uv run python main.py --session demo --user carmit--session names the conversation (resume it later with the same value); --user selects
the persistent profile. Every tool call and observation is printed as it happens. Try:
How many refund requests are there?
What categories exist in the dataset?
What is the distribution of intents in the ACCOUNT category?
Summarize the FEEDBACK category.
What should I query next? # the recommender: suggests, you confirm, it runs
What do you remember about me? # answered from your profile
Who is the president of France? # politely declinedTo see memory survive a restart: ask something, exit, relaunch with the same
--session, and ask a follow-up like "what about shipping?".
Using the Streamlit app
uv run streamlit run src/cs_agent/ui/streamlit_app.pyChat in the browser; the reasoning steps appear in a collapsible panel and the sidebar has the session switcher and the live profile.
MCP server
Start the server (stdio transport):
uv run python mcp_server/server.pyConnect a client and call a tool. A runnable example is in
mcp_server/client_demo.py:
import asyncio
from fastmcp import Client
async def main():
async with Client("mcp_server/server.py") as client:
tools = await client.list_tools()
print([t.name for t in tools])
result = await client.call_tool("count_records", {"intent": "get_refund"})
print(result.data) # {'count': 997, 'total': 26872, 'pct': 3.71, ...}
asyncio.run(main())Run it directly:
uv run python mcp_server/client_demo.pyProject layout
customer-service-agent-carmit-haas/
├── main.py # CLI entry point
├── src/cs_agent/
│ ├── config.py # endpoint, model IDs, paths, MAX_ITERATIONS
│ ├── data.py # cached dataset loader
│ ├── tools/
│ │ ├── schemas.py # Pydantic input/return models + tool descriptions
│ │ └── analytics.py # pure analysis functions (single source of truth)
│ ├── agent/
│ │ ├── state.py # graph state
│ │ ├── llm.py # Nebius model factories
│ │ ├── router.py # query router node
│ │ ├── tool_bindings.py # tools as LangChain @tool
│ │ ├── graph.py # the LangGraph wiring
│ │ ├── profile.py # per-user profile
│ │ └── persistence.py # SqliteSaver checkpointer
│ └── ui/streamlit_app.py # Streamlit chat (Bonus A)
├── mcp_server/
│ ├── server.py # FastMCP server (Task 3)
│ └── client_demo.py # minimal MCP client
├── tests/test_analytics.py # tool tests (no API key needed)
└── docs/ # diagrams + screenshotsTests
uv run pytestThe tests cover the pure analysis tools against known dataset facts and need no API key.
Notes
Out-of-scope refusal is enforced structurally (a dedicated
declinenode), not just by a prompt instruction, so the model can't be talked into answering off-topic questions.The recommender proposes with a no-tools model, so it can suggest but never execute; a
pending_suggestionflag makes the suggest → refine → confirm loop deterministic.
License
MIT — see LICENSE.
Available Tools
5 toolscount_recordsA
Count how many records match a category, intent, and/or keyword, as a number and as a percentage of the dataset. This is the counting half of a chain: to answer 'how many refund requests did we get?', pass intent='get_refund'. Returns no rows, so it is cheap and safe for large matches.
Inputs: category, intent, keyword. Returns: object
filter_recordsA
Find example records matching a category, intent, and/or keyword. Returns the total number of matches plus a small sample of example rows (never the full set). Use this for 'show me N examples of ...'. To get only a count, prefer count_records.
Inputs: category, intent, keyword, limit. Returns: object
list_categoriesA
List the distinct customer-service categories in the dataset (e.g. refund, shipping, account). Use this first when the user asks 'what categories exist' or when you need to confirm a valid category name before filtering or counting.
Inputs: with_counts. Returns: object
list_intentsA
List the distinct intents, optionally restricted to one category. Use this to discover valid intent names (e.g. the refund category contains get_refund, track_refund, check_refund_policy) or to answer 'what is the distribution of intents in the ACCOUNT category' by passing with_counts=true.
Inputs: category, with_counts. Returns: object
summarize_categoryA
Retrieve a representative sample of customer messages and agent responses for a category and/or intent, so you can summarize them. Use this for open-ended questions like 'summarize the FEEDBACK category' or 'how do reps respond to cancellations'. Base your summary only on the returned text; do not invent details.
Inputs: category, intent, sample_size. Returns: object
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 5 tools
Each tool has a unique purpose: counting without rows, sampling with total count, listing categories, listing intents (optionally with distribution), and returning representative messages for summarization. No two tools perform the same function.
All tool names follow the consistent verb_noun pattern using snake_case (e.g., count_records, filter_records). There are no deviations or mixed conventions.
Five tools is well-scoped for the server's purpose of analyzing customer service data. Each tool serves a distinct, necessary function without redundancy or bloat.
The set covers discovery (categories, intents), counting, sampling, and summarization. A minor gap is lack of comparative or aggregate statistics across categories, but the core analytical workflow is supported.
Maintenance
Related MCP Connectors
Ask questions about your support tickets, chats, agents and AI answer quality, with cited tickets.
Query and update Teable records, manage tables, and access the Teable API with AI agents.
Manage tickets, run agents and workflows, query tables, and search workspace knowledge.
Create customer-service agents, review conversations and leads, and improve answers.
Related MCP Servers
- FlicenseNot gradedqualityDmaintenanceEnables AI agents to interact with Zendesk ticket data for customer support analysis and insights. It supports searching tickets by tags or keywords, retrieving ticket details, and analyzing agent performance and service trends.-
- FlicenseNot gradedqualityDmaintenanceExposes a SQLite database to AI assistants with structured, read-safe access. Includes five tools for schema exploration, querying, and sampling data.-
- AlicenseAqualityDmaintenanceEnables semantic search and analysis of customer support tickets. Provides tools to search tickets, analyze the dataset, and retrieve individual tickets using natural language.3MIT
- FlicenseNot gradedqualityDmaintenanceEnables natural-language Q&A over customer support ticket data. Provides tools for schema inspection, SQL-based ticket counts and grouping, and full-text search for customer wording without requiring API keys.-


