Skip to main content
Glama
CarmitHaas

Customer Service Data Analyst MCP Server

by CarmitHaas

Customer Service Data Analyst Agent

Python LangGraph FastMCP Nebius License: MIT

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:

CLI demo

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

structured

recommender

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.

architecture

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

agent graph

The editable source for the system diagram is docs/architecture.drawio.

Pieces:

  • Router (src/cs_agent/agent/router.py) — labels a question structured, unstructured, out_of_scope, or recommend, 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 decline node for out-of-scope questions and a graceful fallback after MAX_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

meta-llama/Llama-3.3-70B-Instruct

reliable OpenAI-style function calling and grounded writing

Routing + profile distillation

Qwen/Qwen3-30B-A3B-Instruct-2507

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 carmit

On 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 declined

To 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.py

Chat 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.py

Connect 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.py

Project 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 + screenshots

Tests

uv run pytest

The 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 decline node), 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_suggestion flag makes the suggest → refine → confirm loop deterministic.

License

MIT — see LICENSE.

Available Tools

5 tools
count_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.

ParametersJSON Schema
NameRequiredDescriptionDefault
categoryNo
intentNo
keywordNo

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A4.7/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries the burden. It discloses return behavior (no rows, only count/percentage) and safety (cheap, safe), but lacks details on permissions or side effects. Overall, it provides sufficient transparency for a read-only query tool.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two sentences: the first states purpose, the second provides usage context and safety. No unnecessary words, front-loaded with the core function.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With an output schema (implied), the description does not need to detail return structure. It covers all three optional parameters, gives a real-world chain scenario, and mentions performance characteristics. The sibling tools context is well integrated.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 0%, so the description must compensate. It explains the three parameters (category, intent, keyword) as filters and gives an example with 'intent'. This adds meaning beyond the schema's names, though format constraints are not detailed.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool counts records matching filters (category, intent, keyword), returning a number and percentage. It distinguishes from sibling tools like filter_records (which returns rows) and summarize_category, making the purpose specific and unambiguous.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Provides an explicit example ('how many refund requests?') and explains it is the counting half of a chain, implying when to use it versus filter_records. Also notes it is cheap and safe for large matches, guiding appropriate usage.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
categoryNo
intentNo
keywordNo
limitNo

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A4.4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations provided, so description carries full burden. It discloses that returned data is a small sample, never the full set, and includes total count. Lacks details on authentication or side effects but adequately covers the key behavioral trait.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two concise sentences with no fluff. Key information is front-loaded.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given 4 optional parameters and an output schema, the description covers purpose, usage, and return behavior. It could mention that parameters are optional and combinable, but overall sufficiently complete.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so description must compensate. It names three filter fields (category, intent, keyword) aligning with schema properties, but does not explicitly describe the limit parameter or its default behavior. Provides basic parameter context but misses full synergy.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool finds example records matching criteria (category, intent, keyword). It distinguishes itself from sibling tool count_records by specifying when to use each: 'use this for show me N examples' and 'prefer count_records for counts'.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly provides when to use this tool ('show me N examples...') and when to use an alternative ('prefer count_records'). No ambiguity.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
with_countsNo

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A4.1/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries the full burden. It implies a safe read-only operation but does not explicitly state that it is non-destructive or mention any limitations (e.g., no pagination, no auth requirements). The description is adequate but lacks explicit behavioral guarantees.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is extremely concise: one sentence for purpose and one for usage guidelines. It is front-loaded with the key action, and every word serves a purpose. No unnecessary text.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's simplicity (1 boolean parameter, output schema exists, sibling tools are related), the description covers the essential scenarios. It explains when to use it relative to filtering or counting. However, it could briefly mention the optional count parameter to be fully complete, but overall it is well-contextualized.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters2/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The input schema has one optional boolean parameter 'with_counts' (default false), but the description does not mention it at all. With 0% schema description coverage, the description fails to inform the agent about the ability to include counts. This is a significant omission for a single parameter tool.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool's purpose: 'List the distinct customer-service categories in the dataset' with concrete examples (e.g., refund, shipping). It distinguishes from siblings like list_intents which list different entities.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description explicitly advises when to use this tool: 'Use this first when the user asks what categories exist or when you need to confirm a valid category name before filtering or counting.' This gives clear context and directs the agent to use it as a prerequisite for other operations.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
categoryNo
with_countsNo

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A4.3/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so description carries full burden. It discloses listing distinct intents and optional counts, but omits details like pagination, ordering, or performance. Adequate but not thorough for a list tool.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two sentences only, front-loaded with key action, includes concrete examples. Every word earns its place with no redundancy.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's simplicity (2 optional params) and existence of output schema, the description covers core functionality well. Could mention output schema for completeness, but not essential.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

With 0% schema description coverage, the description compensates fully. It explains 'category' as optional restriction and 'with_counts' for distribution, giving concrete examples (e.g., 'ACCOUNT category', 'get_refund'). Adds meaning beyond schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool 'List the distinct intents' with an optional restriction by category. It distinguishes from siblings by mentioning discovery of valid intent names and distribution queries, fulfilling a specific verb+resource+scope.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Provides explicit use cases: to discover valid intent names and to get distribution with counts. Examples clarify context. Missing explicit 'when not to use' or alternatives, but the sibling list hints at other tools.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
categoryNo
intentNo
sample_sizeNo

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A3.8/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries full burden. It states the tool retrieves a 'representative sample', implying non-exhaustive results, and instructs not to invent details. However, it does not disclose sampling method, determinism, or side effects.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two sentences with no wasted words. First sentence states purpose, second gives usage guidance and constraints. Front-loaded and efficient.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given an output schema exists, description need not detail return values. However, it omits how category and intent combine (AND/OR) and how the sample is selected (random, recent, etc.). Adequate for simple use but could be more complete.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters2/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 0%, so description should compensate. It adds meaning for category and intent (used for filtering) but provides no explanation for sample_size (default 15) beyond the name, leaving its purpose and constraints unclear.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool retrieves a representative sample of customer messages and agent responses for a category and/or intent to allow summarization. It distinguishes from siblings by focusing on sampling for summarization rather than counting, filtering, or listing.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Provides explicit usage examples ('summarize the FEEDBACK category' or 'how do reps respond to cancellations') and instructs to base summary only on returned text, avoiding invention. Lacks explicit when-not-to-use or alternative tools, but context is clear.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 5 tool updatesv0.1.0
    • First observedcount_records
    • First observedfilter_records
    • First observedlist_categories
    • First observedlist_intents
    • First observedsummarize_category

TDQS

A4.3/5.0

Scored across 5 tools

Disambiguation5/5

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.

Naming Consistency5/5

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.

Tool Count5/5

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.

Completeness4/5

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

ActivitySlowing
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • F
    license
    Not graded
    quality
    D
    maintenance
    Enables 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.
    -
  • F
    license
    Not graded
    quality
    D
    maintenance
    Exposes a SQLite database to AI assistants with structured, read-safe access. Includes five tools for schema exploration, querying, and sampling data.
    -
  • A
    license
    A
    quality
    B
    maintenance
    Enables Zendesk support workflows through tools for semantic ticket search, customer context retrieval, solution version assessment, and daily work summaries.
    4
    18 npm
    MIT