Skip to main content
Glama

πŸ€– ARIA β€” Internal Knowledge & Ticketing Assistant

MCP-connected chatbot for IT operations teams β€” RAG answers from Confluence, writes new articles when nothing exists, and turns chat into clean Jira tickets, streamed live

ARIA sits in front of six internal teams β€” Service Desk, Command Center, Network, Linux, Database, and Windows Engineering β€” as one chat interface. A LangChain probing layer reads each question first: if it's too vague to search on, ARIA asks one clarifying question instead of guessing. Once the issue is clear, a ChromaDB-backed retrieval loop finds the best matching Confluence article β€” rewording and re-searching if the first pass isn't confident β€” and streams a grounded answer token-by-token. If nothing matches, ARIA drafts and publishes a new article, so the knowledge base grows itself. A "Log as Ticket" action turns the raw back-and-forth of the chat into a well-formed Jira Service Desk ticket, not a dump of the conversation.

Python FastAPI LangChain ChromaDB MCP License


✨ Features

Feature

Description

πŸ—‚οΈ Six team spaces

Service Desk Β· Command Center Β· Network Β· Linux Β· Database Β· Windows Engineering, each with its own Confluence space + Jira project

🧭 LangChain probing layer

Reads the question before anything else runs; a genuinely vague report ("my thing is broken") gets one clarifying question instead of a guess

πŸ”Ž RAG retrieval (ChromaDB)

Articles are chunked and embedded, not dumped whole into the prompt; retrieval is confidence-scored and re-tried with a reworded query when the first pass is weak

✍️ Self-healing KB

No confident match β†’ ARIA drafts an article, publishes it, and indexes it immediately β€” the next person with the same question finds it already there

🌊 Real-time streaming

Answers stream token-by-token over SSE with a "Musing…" / "Reading…" / "Drafting…" status and a blinking cursor, like a live typed reply

🌑️ temperature=0.2 everywhere

Every completion call β€” decision, synthesis, drafting, ticket writing β€” is grounded and low-temperature on purpose; prompts explicitly forbid inventing steps not in the source

πŸ›‘οΈ Guardrails

Hard-blocks high-sensitivity PHI/PII (SSN, card numbers, MRNs), sexual/nudity content, and security-risk requests (credential theft, bypassing auth) before anything reaches an LLM, Confluence, or Jira

πŸ”“ Soft PII redaction

Low-sensitivity contact info (email, phone, IP) is masked but still passed through β€” a ticket needs a reporter's email

🎫 Log as Ticket

Converts the chat's raw comments into a clean summary + description + priority and files it in Jira Service Desk

πŸ”Œ MCP server

Every capability is also exposed as an MCP tool for Claude Desktop / Claude Code

πŸ§ͺ Offline-first

Local JSON knowledge base + ticket store, fully demo-able with no Jira/Confluence/Okta tenant; flip one flag to go live


πŸ—οΈ Project Structure

aria-service-desk-assistant/
β”œβ”€β”€ main.py                 ← FastAPI app: /api/chat, /api/chat/stream (SSE), /api/ticket, /api/teams
β”œβ”€β”€ mcp_server.py            ← Same capabilities exposed over MCP (stdio)
β”œβ”€β”€ config.py                ← Settings: mock mode, temperature, Jira/Confluence/Okta creds
β”œβ”€β”€ models.py                 ← Team enum + Pydantic request/response models
β”œβ”€β”€ prompts.py                ← Prompt templates (decision, synthesis, drafting, ticket writer)
β”œβ”€β”€ probe_agent.py             ← LangChain layer: clarity check + search-intent reword loop
β”œβ”€β”€ vector_store.py              ← ChromaDB chunking/embedding/retrieval
β”œβ”€β”€ knowledge_agent.py            ← Orchestrates probe β†’ retrieve β†’ synthesize/draft, streamed
β”œβ”€β”€ ticket_writer.py                ← Chat transcript β†’ clean ticket summary/description
β”œβ”€β”€ guardrails.py                    ← Hard blocks: PHI/PII, sexual content, security risk
β”œβ”€β”€ phi_filter.py                     ← Soft redaction: email/phone/IP, masked not blocked
β”œβ”€β”€ confluence_client.py               ← Search + publish articles (mock JSON ↔ real Confluence REST)
β”œβ”€β”€ jira_client.py                      ← Create tickets (mock JSON ↔ real Jira REST)
β”œβ”€β”€ okta_client.py                       ← Requester lookup, name/department only (mock ↔ real Okta REST)
β”œβ”€β”€ kb_store.py                           ← Local JSON-backed knowledge base (the mock/offline Confluence)
β”œβ”€β”€ data/
β”‚   β”œβ”€β”€ mock_kb.json                       ← Seed articles per team (Troubleshooting/How-To/Runbook)
β”‚   β”œβ”€β”€ chroma/ (runtime)                   ← Local vector index
β”‚   └── mock_tickets.json (runtime)          ← Filed tickets in mock mode
└── static/
    └── index.html                           ← Chat UI: musing status, streaming text, blinking cursor

πŸ”„ Chat Flow

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ USER picks a team, asks a question                                        β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                  β–Ό
                POST /api/chat/stream  β†’  guardrails.screen()  β†’ blocked? refuse & stop
                                  β”‚
                                  β–Ό
               probe_agent.assess()  (LangChain: ChatOpenAI + prompt template)
                                  β”‚
                     clear enough to search?
                β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
               NO                                   YES
                β”‚                                     β”‚
   stream back ONE clarifying              vector_store: chunk + embed (ChromaDB)
   question, wait for the reply                        β”‚
                                            best_articles_context(team, intent)
                                                         β”‚
                                          LLM: found? confidence 0.0–1.0
                                        low confidence β†’ probe_agent.reword()
                                          β†’ re-embed search β†’ retry (max 2 rounds)
                                                β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”
                                            confident match     still nothing
                                                β”‚                     β”‚
                                  stream synthesis from        stream a drafted
                                  the one matched article       article, publish +
                                  (source: knowledge_base)       index it immediately
                                                β”‚                (source: drafted_article)
                                                β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                                            β–Ό
                                              ChatResponse events β†’ SSE β†’
                                              UI renders with blinking cursor

🎫 Log as Ticket Flow

USER clicks "Log as Ticket" on the current conversation
                 β”‚
                 β–Ό
   POST /api/ticket  β†’  guardrails.screen(transcript)  β†’ blocked? 400, no ticket filed
                 β”‚
                 β–Ό
   phi_filter.redact_history()  (soft-mask email/phone/IP)
                 β”‚
                 β–Ό
   ticket_writer.build_ticket(team, history)
     LLM (temperature=0.2) turns raw comments into:
       { summary, description, priority }
                 β”‚
                 β–Ό
   okta_client.lookup_user(email)  β†’ requester name/department only
                 β”‚
                 β–Ό
   jira_client.create_ticket(...)  β†’ files in the team's Jira project
                 β”‚
                 β–Ό
   TicketResponse: { ticket_key, ticket_url, summary, description }

πŸš€ Quick Start

git clone https://github.com/emran-Automation-Techlead/aria-service-desk-assistant.git
cd aria-service-desk-assistant
pip install -r requirements.txt
cp .env.example .env      # keep ARIA_MOCK_MODE=true to run fully offline
python main.py

Open http://localhost:7862

Going live against real Jira / Confluence / Okta

Set ARIA_MOCK_MODE=false in .env and fill in:

JIRA_BASE_URL=https://yourcompany.atlassian.net
JIRA_EMAIL=aria-bot@yourcompany.com
JIRA_API_TOKEN=...

CONFLUENCE_BASE_URL=https://yourcompany.atlassian.net
CONFLUENCE_EMAIL=aria-bot@yourcompany.com
CONFLUENCE_API_TOKEN=...

OKTA_DOMAIN=yourcompany.okta.com
OKTA_API_TOKEN=...

No code changes required β€” confluence_client.py, jira_client.py, and okta_client.py switch from the local JSON stores to live REST calls automatically. The vector index (vector_store.py) indexes whatever confluence_client.search() returns either way, so RAG retrieval works identically against mock or live content.

Running the MCP server

python mcp_server.py

Point any MCP host (Claude Desktop, Claude Code) at this command to give it search_knowledge_base, create_confluence_article, create_jira_ticket, and lookup_okta_user as tools β€” each screened by the same guardrails as the chat UI.


πŸ›‘οΈ Guardrails vs. soft redaction

Two distinct layers, on purpose:

  • guardrails.py β€” hard refusal. Runs before anything reaches an LLM, Confluence, or Jira. Blocks outright: high-sensitivity PHI/PII (SSNs, card numbers, medical record numbers, via regex), sexual/nudity content (OpenAI Moderation API, scored against thresholds tuned tighter than the API's own flagged default β€” validated so ordinary IT phrasing like "kill the process" scores near zero), and security-risk requests (credential theft, bypassing MFA/auth, malware, exfiltration β€” a curated keyword heuristic plus the moderation model's illicit-content score, since phrasing like this isn't reliably caught by moderation alone).

  • phi_filter.py β€” soft redaction. Low-sensitivity, operationally-necessary contact details (email, phone, IP) are masked with [REDACTED-<TYPE>] but the message still goes through β€” a ticket needs a reporter's email, a network issue needs an IP. API responses carry a redactions audit list (type + count).


πŸ› οΈ Tech Stack

Layer

Technology

API

FastAPI + Uvicorn, SSE streaming

AI

OpenAI (gpt-4o-mini by default, temperature=0.2 everywhere)

Orchestration

LangChain (langchain-openai + langchain-core) for the intake-probing/reword loop

RAG

ChromaDB β€” chunked, embedded (text-embedding-3-small), confidence-scored retrieval

Safety

OpenAI Moderation API + custom PHI/PII and security-risk pattern checks

Agent protocol

MCP (mcp Python SDK, stdio transport)

Integrations

Jira Service Desk REST, Confluence Cloud REST, Okta REST β€” each with an offline JSON-backed mock mode

Config

Pydantic Settings v2 Β· python-dotenv

UI

Single-page vanilla HTML/JS chat widget β€” SSE consumer, musing status, blinking cursor


πŸ“„ License

MIT

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/emran-Automation-Techlead/aria-service-desk-assistant'

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