Skip to main content
Glama
bhargavlukka

TicketDesk MCP

by bhargavlukka
README.md
# TicketDesk MCP — Production-Style MCP Server & Client

A Model Context Protocol (MCP) server exposing an internal support-ticket queue, paired
with a client that discovers and invokes its tools, resources, and prompts. Built for the
"Build a Production MCP Server & Client" lab (Module 4, Model Context Protocol).

## Contents

- [Architecture](#architecture)
- [Setup & running the demo](#setup--running-the-demo)
- [`docs/tools-resources-prompts.md`](docs/tools-resources-prompts.md) — what the server exposes
- [`docs/architecture.md`](docs/architecture.md) — transport choice & justification
- [`docs/security-summary.md`](docs/security-summary.md) — authentication, least privilege, error handling
- [`demo/session_log.txt`](demo/session_log.txt) — a real captured discovery-and-invocation run

## Architecture

```mermaid
flowchart TD
    HOST["Host application\n(e.g. an AI assistant / chatbot)"] --> CLIENT

    subgraph Client["client.py — fastmcp.Client"]
        CLIENT["Discovery: list_tools / list_resources /\nlist_resource_templates / list_prompts"]
        ELICIT["elicitation_handler\n(client-side safety gate for\nhigh-impact actions)"]
        CLIENT <--> ELICIT
    end

    CLIENT <-->|"Streamable HTTP\nAuthorization: Bearer <token>"| SERVER

    subgraph Server["server.py — FastMCP('TicketDesk')"]
        AUTH["StaticTokenVerifier\n(read:tickets / write:tickets scopes)"]
        TOOLS["Tools: search_tickets, get_ticket,\nadd_ticket_note, close_ticket"]
        RES["Resources: ticket://{ticket_id},\npolicy://sla"]
        PROMPT["Prompt: triage_ticket"]
        AUTH --> TOOLS
        AUTH --> RES
        AUTH --> PROMPT
    end

    SERVER --> DATA[("data/tickets.json,\ndata/sla_policy.md")]
```

**Transport: Streamable HTTP** — justified in [`docs/architecture.md`](docs/architecture.md)
(short version: this is meant to be a shared internal capability multiple teams' AI
applications connect to, not a single local process pair, which is what stdio is suited
for).

## What the server exposes

- **4 tools**: `search_tickets`, `get_ticket`, `add_ticket_note`, `close_ticket` (the last
  is a high-impact action gated by client-side elicitation)
- **2 resources**: `ticket://{ticket_id}` (templated) and `policy://sla` (static)
- **1 prompt**: `triage_ticket`

Full documentation of each: [`docs/tools-resources-prompts.md`](docs/tools-resources-prompts.md).

## Setup & running the demo

```bash
pip install -r requirements.txt

# Terminal 1
python server.py

# Terminal 2
python client.py                # interactive: real confirmation prompts via input()
python client.py --auto-confirm # non-interactive demo mode (used to produce demo/session_log.txt)
```

`client.py` connects with `dev-agent-token` (read + write scopes), runs discovery, invokes
one of each tool/resource/prompt, demonstrates both error-handling failure modes
(Requirement 8), performs the high-impact `close_ticket` action through client-side
elicitation (Requirement 6), and finishes with a least-privilege demo showing
`dev-readonly-token` correctly rejected from a write-scoped tool.

## Security

Full write-up: [`docs/security-summary.md`](docs/security-summary.md). Short version:
bearer-token auth via `StaticTokenVerifier`, explicit per-tool scope checks (a real gap —
connection-level scope requirements alone do **not** restrict individual tool calls — was
found and fixed during development; see the security doc for the honest account), and
`ToolError` + `mask_error_details=True` to distinguish user-actionable errors from
internal failures that must never reach the client verbatim.

## Requirements mapping

| Requirement | Where it's satisfied |
|---|---|
| ≥3 tools | `search_tickets`, `get_ticket`, `add_ticket_note`, `close_ticket` (`server.py`) |
| ≥2 resources, each a URI | `ticket://{ticket_id}`, `policy://sla` (`server.py`) |
| ≥1 reusable prompt | `triage_ticket` (`server.py`) |
| Client discovers + invokes each | `client.py` `run_discovery_and_demo()` |
| Transport choice justified | `docs/architecture.md` |
| Client-side safety feature | Elicitation gating `close_ticket` (`client.py` `elicitation_handler`) |
| Security design summary | `docs/security-summary.md` |
| Error handling for a realistic failure mode | Invalid ticket ID (`ToolError`) + simulated backing-store outage (`mask_error_details`) — both in `server.py`, demonstrated in `client.py` |
| Demonstration logs | `demo/session_log.txt` (real captured run) |