Skip to main content
Glama
README.md
# CustSupport

An MCP server for a customer support workflow — **monitoring, reporting,
and resolving** — built as a portfolio project. A local LLM (Ollama)
generates a synthetic ticket dataset, which is validated, stored in
BigQuery, and served to any MCP-compatible client (Claude Desktop,
Claude Code) through a set of read-oriented monitoring/reporting tools
and a resolving tool set that drafts suggestions for human review rather
than acting autonomously.

## Architecture

```
generate_batch.py  →  validator.py  →  load_to_bigquery.py  →  BigQuery
   (Ollama)          (QA / flagging)      (load job)              │
                                                                    ▼
                                                    mcp_server/server.py
                                                    (monitoring / reporting /
                                                     resolving tools)
                                                            │
                                                            ▼
                                            Claude Desktop / Claude Code
```

- **Data**: synthetic support tickets (Zendesk-shaped schema — category,
  persona, priority, channel, subject, body, SLA hours) generated locally
  via Ollama (llama3.2), chosen over a real support-platform API to keep
  cost/friction low while controlling for specific edge cases (SLA
  breaches, ambiguous sentiment) that make the monitoring/reporting logic
  worth testing.
- **Generation**: category/persona/priority/channel/scenario are sampled
  *before* generation (weighted, conditionally correlated — e.g. urgent
  tickets skew frustrated, technical/account issues skew higher priority
  than shipping/feature requests), then a parameterized prompt is built
  per combination. Persona is fully decoupled from category — any persona
  can occur with any category.
- **Validation**: a QA pass built directly from failure patterns observed
  across real generation batches — full-caps "shouting" bodies (~30-40%
  of frustrated-persona tickets, a rate prompt tuning couldn't fully
  eliminate), leftover bracket placeholders, stale absolute dates,
  third-person voice drift, and category/ID mismatches (e.g. a feature
  request referencing an order number). Flagged tickets are separated
  out for review rather than silently dropped or silently kept.
- **Storage**: BigQuery, loaded via a load job (not streaming insert) so
  rows are immediately eligible for the UPDATE used by the resolving
  tool set's status changes.
- **MCP server**: exposes the tool set below over stdio, for use by
  Claude Desktop or Claude Code.

## Tool set

| Category | Tool | Read/Write |
|---|---|---|
| Monitoring | `list_open_tickets`, `get_ticket`, `sla_breaches` | Read |
| Reporting | `category_breakdown`, `sla_risk_summary`, `daily_digest` | Read |
| Resolving | `draft_reply`, `suggest_escalation` | Read (suggestions only) |
| Resolving | `update_ticket_status` | **Write** |

**Human-approval design**: `draft_reply` and `suggest_escalation` never
modify data — they return a suggestion for a human to review.
`update_ticket_status` is the only tool that writes, and is intended to
be called only after that review. This is a convention carried by tool
descriptions and expected usage, not something the server can technically
enforce — the MCP protocol gives a calling agent the tools, not a way to
prove a human looked at the output first.

## Project layout

```
src/custsupport/
├── schema.py                 # SyntheticTicket dataclass, category/persona/priority enums
├── config.py                  # env-based config (Ollama, BigQuery) — loads .env via python-dotenv
├── generator/
│   ├── prompts.py               # build_prompt() — tuned, parameterized, category/persona decoupled
│   ├── sampler.py                # sample_ticket_params() / sample_batch() — weighted, conditional sampling
│   ├── ollama_client.py           # Ollama /api/generate wrapper
│   ├── batch.py                    # wires sampler+prompts+ollama_client into SyntheticTicket objects
│   └── validator.py                 # QA pass — full-caps, placeholders, dates, voice, ID mismatches
├── storage/
│   └── bigquery_client.py    # dataset/table creation, insert/load, monitoring queries, status updates
└── mcp_server/
    ├── draft.py                # draft_reply's Ollama-backed prompt/generation
    └── server.py                 # MCP tool registration (monitoring/reporting/resolving)

scripts/
├── sanity_check_tickets.py   # manual prompt-tuning harness (exploratory, not a test)
├── generate_batch.py           # generate + validate a batch, write clean/flagged JSONL
└── load_to_bigquery.py           # load a generated JSONL batch into BigQuery

tests/
├── test_sampler.py              # sampler distribution checks, no external deps
├── test_build_prompt.py          # prompt cross-pairing checks, needs local Ollama
├── test_batch_generator.py        # parser + pipeline checks, Ollama mocked
├── test_validator.py               # validator checks against real historical failure tickets
└── test_mcp_server.py               # tool registration + pure-logic checks, BigQuery/Ollama mocked
```

## Setup (fresh clone)

This repo excludes both the generated dataset (`data/`) and any
environment-specific credentials — a fresh clone needs its own GCP
project and its own generated tickets.

```bash
# 1. Install dependencies
uv sync
uv pip install -e .

# 2. Configure environment
cp .env.example .env
# edit .env: set BQ_PROJECT_ID to your own GCP project

# 3. GCP auth (local dev)
gcloud auth application-default login
gcloud auth application-default set-quota-project <your-project-id>

# 4. Ollama
ollama pull llama3.2

# 5. Generate and validate a batch of synthetic tickets
uv run python scripts/generate_batch.py --n 50 --seed 1

# 6. Load into BigQuery (creates dataset/table on first run)
uv run python scripts/load_to_bigquery.py --input data/tickets_batch.jsonl

# 7. Run the test suite
uv run python tests/test_sampler.py --n 2000
uv run python tests/test_batch_generator.py
uv run python tests/test_validator.py
uv run python tests/test_mcp_server.py
```

## Registering the MCP server

**Claude Desktop** — add to `claude_desktop_config.json`:
```json
{
  "mcpServers": {
    "custsupport": {
      "command": "uv",
      "args": ["run", "--directory", "<absolute path to this repo>", "python", "-m", "custsupport.mcp_server.server"]
    }
  }
}
```

**Claude Code**:
```bash
claude mcp add custsupport -- uv run --directory "<absolute path to this repo>" python -m custsupport.mcp_server.server
```

Ollama must be running locally for `draft_reply` to work once connected.

## Status

Fully functional end-to-end, verified against live Ollama, a live
BigQuery project, and the real MCP Python SDK (`mcp` 2.x — note the SDK
renamed `FastMCP` to `MCPServer` in 2.0). Not yet verified: the full
round trip through an actual Claude Desktop/Code session (the tool
registration itself was tested via the SDK directly, not via a live
client connection).

## Possible next steps

- `search_similar_resolved_tickets` — RAG over past resolutions. Not
  built yet since the generator has only ever produced `status="open"`
  tickets; would need a small resolved-ticket generation pass first
  (populating `ground_truth_resolution`).
- Auto-regeneration of validator-flagged tickets rather than manual review.

## License

MIT — see [LICENSE](LICENSE).

TDQS

A4.2/5.0

Scored across 9 tools

Disambiguation4/5

Tools are largely distinct, but aggregation tools like daily_digest, category_breakdown, and sla_risk_summary overlap in the data they summarize, which could lead to misselection. Descriptions clarify their differences, but the boundary between daily_digest and category_breakdown is subtle.

Naming Consistency4/5

Names follow snake_case and are generally clear, but mix verb-first (list_open_tickets, get_ticket) with noun-first patterns (sla_breaches, category_breakdown). Despite the mix, the naming is predictable and readable, with no camelCase or chaotic variations.

Tool Count5/5

9 tools is well within the ideal 3-15 range for a focused domain. Each tool covers a distinct part of the support workflow—listing, detail, aggregation, SLA monitoring, draft replies, escalation, and status updates—without redundancy or bloat.

Completeness4/5

The surface covers the main support lifecycle: read (list, get), aggregate (breakdowns, SLA risk), assist (draft reply, escalation), and write (update status). Minor gaps exist, such as no tool to create or assign tickets, but these may be handled externally. The core workflow is well supported with no dead ends.

Maintenance

ActivityMaintained
ResponsivenessNo issues