nothingworry commited on
Commit
e4abb85
·
1 Parent(s): eb29e58

feat: add admin rules ingestion UI and persistent backend storage

Browse files
LICENSE ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ MIT License
2
+
3
+ Copyright (c) 2024 IntegraChat
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
README.md CHANGED
@@ -1,683 +1,134 @@
1
- <div align="center">
2
-
3
- # 🚀 IntegraChat
4
-
5
- ### Multi-Tenant Autonomous MCP Platform
6
-
7
- **Enterprise-grade AI with autonomous agents, secure multi-tenant RAG, real-time web search, red-flag governance, and analytics.**
8
-
9
- [![Python](https://img.shields.io/badge/Python-3.10+-blue.svg)](https://www.python.org/)
10
- [![React](https://img.shields.io/badge/React-18-blue.svg)](https://reactjs.org/)
11
- [![FastAPI](https://img.shields.io/badge/FastAPI-Latest-green.svg)](https://fastapi.tiangolo.com/)
12
- [![License](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
13
- [![MCP](https://img.shields.io/badge/MCP-In%20Action-purple.svg)](https://modelcontextprotocol.io/)
14
-
15
- </div>
16
-
17
- ---
18
-
19
- ## 📑 Table of Contents
20
-
21
- - [Overview](#-overview)
22
- - [Purpose](#-purpose)
23
- - [Key Features](#-key-features)
24
- - [Technology Stack](#-technology-stack)
25
- - [System Architecture](#-system-architecture)
26
- - [Project Structure](#-project-structure)
27
- - [Getting Started](#-getting-started)
28
- - [Why IntegraChat Stands Out](#-why-integrachat-stands-out)
29
- - [Submission Metadata](#-submission-metadata)
30
- - [License](#-license)
31
 
32
- ---
33
-
34
- ## 📌 Overview
35
-
36
- **IntegraChat** is an enterprise-ready, multi-tenant AI platform built to demonstrate the full capabilities of the **Model Context Protocol (MCP)** in a real production-style environment.
37
-
38
- It combines **autonomous tool-using agents**, **RAG retrieval**, **live web search**, and **admin governance** under strict **tenant isolation**, powered by **Ollama** (local) or **Groq** (cloud) LLM inference.
39
-
40
- IntegraChat is a complete **"MCP in Action"** ecosystem — ideal for enterprise demos, research, production scaffolds, and governance-focused AI deployments.
41
 
42
  ---
43
 
44
- ## 🎯 Purpose
45
 
46
- IntegraChat showcases how MCP can power **intelligent, governed, multi-tenant AI systems** with real-world requirements:
47
 
48
- - 🔒 **Isolation & Access Control** - Strict tenant separation
49
- - 🛡️ **Compliance & Red-Flag Detection** - Automated safety monitoring
50
- - 🤖 **Tool-Aware Autonomous Reasoning** - Dynamic tool selection
51
- - 🔍 **RAG + Web Search Hybrid AI** - Best of both worlds
52
- - 📊 **Analytics & Observability** - Full system insights
53
- - ⚙️ **Admin Governance Workflows** - Enterprise-ready controls
54
 
55
  ---
56
 
57
- ## 🧩 Key Features
58
-
59
- ### 1. 🤖 Autonomous MCP Agents
60
-
61
- Agents can intelligently:
62
-
63
- - ✅ Analyze user intent and context
64
- - ✅ Detect sensitive or unsafe content
65
- - ✅ **Dynamically select multiple tools in sequence** (RAG + Web + LLM, Web + LLM, RAG + LLM, etc.)
66
- - ✅ **Multi-step tool execution** - Execute tools sequentially and synthesize results
67
- - ✅ Retrieve tenant-private knowledge
68
- - ✅ Pull real-time data from the internet
69
- - ✅ Trigger admin alerts when needed
70
- - ✅ Respond using **Ollama** (local) or **Groq** (cloud) LLM
71
-
72
- ### 2. 📚 Enterprise RAG System
73
-
74
- - **MiniLM embeddings** (384-dim) for semantic search
75
- - **Supabase + pgvector** for vector storage
76
- - **Automatic database schema initialization** on server startup
77
- - **Strict multi-tenant isolation** with tenant_id filtering
78
- - **Intelligent text chunking** (~300 words per chunk)
79
- - **Vector similarity search** using cosine distance
80
- - **Multi-format document ingestion**:
81
- - **PDF files** - Server-side parsing with PyPDF2
82
- - **DOCX files** - Server-side parsing with python-docx
83
- - **TXT/Markdown files** - Direct text ingestion
84
- - **URLs** - Automatic content fetching and extraction
85
- - **Raw text** - Direct paste and ingest
86
- - **File upload endpoint** (`/rag/ingest-file`) for binary file processing
87
- - **Enhanced ingestion API** (`/rag/ingest-document`) with metadata support
88
- - **Document listing** (`/rag/list`) with pagination and filtering
89
- - **Knowledge base management UI**:
90
- - Search interface with semantic search
91
- - File upload with drag-and-drop support
92
- - Source type selection (PDF, DOCX, TXT, URL, raw text)
93
- - Document library page showing all ingested content
94
- - Filter by document type (PDF, FAQ, Link, Text)
95
- - **Async document ingestion** via Celery workers (optional)
96
-
97
- ### 3. 🌐 Live Web Search Tool
98
-
99
- - Real-time news and information
100
- - General web search capabilities
101
- - **English language results** - Forces English content via region parameters
102
- - Fact-checking & fresh data retrieval
103
- - Multiple provider support (DuckDuckGo, SerpAPI, Bing)
104
-
105
- ### 4. 🔄 Multi-Tool Selection & Execution
106
-
107
- The intelligent tool selector can:
108
-
109
- - **Pattern-based detection** - Recognizes fact queries, freshness keywords, internal docs
110
- - **LLM-enhanced planning** - Uses LLM to determine optimal tool combinations
111
- - **Sequential execution** - Executes tools in order (RAG → Web → LLM)
112
- - **Result synthesis** - Combines all tool outputs into comprehensive responses
113
-
114
- **Supported combinations:**
115
- - `RAG + LLM` - Internal knowledge questions
116
- - `Web + LLM` - Public fact questions
117
- - `RAG + Web + LLM` - Comprehensive queries needing both sources
118
- - `LLM only` - Simple conversational queries
119
-
120
- ### 5. 🚨 Red-Flag Governance Engine
121
-
122
- Admins configure rules to:
123
-
124
- - ⛔ Block unsafe queries automatically
125
- - 📝 Log violations for audit trails
126
- - 🔔 Trigger admin alerts in real-time
127
-
128
- **Example rules**: `salary`, `delete all data`, `confidential client info`
129
-
130
- ### 6. 📊 Analytics Dashboard
131
-
132
- Comprehensive insights for:
133
-
134
- - 📈 Query volume and trends
135
- - 🔧 Tool usage statistics
136
- - 🎯 RAG performance metrics
137
- - 🚨 Red-flag violation tracking
138
- - 🧠 Agent reasoning traces
139
- - 👥 Tenant activity monitoring
140
- - **Real-time analytics panel** in the frontend UI
141
- - **Async analytics processing** via Celery workers
142
-
143
- ### 7. 📄 Document Ingestion System
144
-
145
- Complete document management workflow:
146
-
147
- - **Multiple ingestion methods**:
148
- - File upload (PDF, DOCX, TXT, MD)
149
- - URL fetching with HTML extraction
150
- - Raw text pasting
151
- - Programmatic API ingestion
152
- - **Automatic type detection** from filename or content
153
- - **Metadata support** (filename, URL, doc_id, custom fields)
154
- - **Server-side parsing** for binary files (PDF/DOCX)
155
- - **Text normalization** and sanitization
156
- - **Knowledge base library** page with:
157
- - Document grid view
158
- - Type-based filtering (PDF, FAQ, Link, Text)
159
- - Search functionality
160
- - Document metadata display
161
- - Creation date tracking
162
-
163
- ### 8. 🏢 Multi-Tenant Isolation
164
-
165
- Each tenant gets:
166
-
167
- - 🔐 Private agents
168
- - 📦 Private knowledge base
169
- - ⚙️ Private admin rules
170
- - 📊 Private analytics
171
-
172
- Isolation is guaranteed via **Supabase Row-Level Security (RLS)**.
173
 
174
- ---
175
-
176
- ## 🛠 Technology Stack
177
-
178
- ### Backend
179
-
180
- | Technology | Purpose |
181
- |------------|---------|
182
- | **FastAPI** | High-performance API framework |
183
- | **MCP Client + Servers** | Model Context Protocol implementation |
184
- | **Supabase** | Auth + Storage + pgvector database |
185
- | **PostgreSQL + pgvector** | Vector database for embeddings |
186
- | **MiniLM Embeddings** | Semantic search embeddings (384-dim) |
187
- | **Ollama / Groq** | LLM inference engine (configurable) |
188
- | **DuckDuckGo Search** | Web search provider |
189
- | **Slack / Email** | Alerting system |
190
- | **Celery** | Async task queue for document ingestion and analytics |
191
- | **Redis / RabbitMQ** | Message broker for Celery workers |
192
- | **PyPDF2** | PDF text extraction |
193
- | **python-docx** | DOCX text extraction |
194
- | **python-multipart** | File upload handling |
195
-
196
- ### Frontend
197
-
198
- | Technology | Purpose |
199
- |------------|---------|
200
- | **Next.js 16** | React framework with App Router |
201
- | **React 19** | Modern UI framework |
202
- | **TailwindCSS** | Utility-first styling |
203
- | **TypeScript** | Type-safe development |
204
- | **WebSocket** | Real-time streaming |
205
- | **Admin Dashboard** | Analytics & governance UI |
206
- | **Knowledge Base UI** | Document management interface |
207
 
208
  ---
209
 
210
- ## 🧱 System Architecture
211
-
212
- ```
213
- ┌─────────────────────────────────────────────────────────────┐
214
- │ Frontend (React) │
215
- │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
216
- │ │ Chat UI │ │ Admin │ │Analytics │ │WebSocket │ │
217
- │ │ │ │ Panel │ │ Dashboard│ │Streaming │ │
218
- │ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
219
- └─────────────────────────────────────────────────────────────┘
220
- ↕ HTTP/WebSocket
221
- ┌─────────────────────────────────────────────────────────────┐
222
- �� MCP Client (FastAPI) │
223
- │ • Intent handling │
224
- │ • Red-flag scanning │
225
- │ • Multi-tool selection logic │
226
- │ • Sequential tool execution │
227
- │ • Groq Llama-3.1 integration │
228
- │ • Event logging │
229
- └─────────────────────────────────────────────────────────────┘
230
- ↕ MCP Protocol
231
- ┌─────────────────────────────────────────────────────────────┐
232
- │ MCP Servers │
233
- │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
234
- │ │ RAG Server │ │ Web Search │ │ Admin Server │ │
235
- │ │ (Knowledge) │ │ (Live/EN) │ │ (Governance) │ │
236
- │ └──────────────┘ └──────────────┘ └──────────────┘ │
237
- └─────────────────────────────────────────────────────────────┘
238
-
239
- ┌─────────────────────────────────────────────────────────────┐
240
- │ Supabase Database │
241
- │ • Authentication │
242
- │ • pgvector (embeddings) │
243
- │ • Row-Level Security (RLS) │
244
- │ • Storage │
245
- └─────────────────────────────────────────────────────────────┘
246
- ```
247
-
248
- ---
249
-
250
- ## 📁 Project Structure
251
-
252
- ```
253
- IntegraChat/
254
- ├── backend/
255
- │ ├── api/
256
- │ │ ├── main.py
257
- │ │ ├── routes/
258
- │ │ │ ├── agent.py
259
- │ │ │ ├── rag.py
260
- │ │ │ ├── web.py
261
- │ │ │ ├── admin.py
262
- │ │ │ └── analytics.py
263
- │ │ ├── services/
264
- │ │ │ ├── agent_orchestrator.py
265
- │ │ │ ├── intent_classifier.py
266
- │ │ │ ├── redflag_detector.py
267
- │ │ │ ├── tool_selector.py
268
- │ │ │ ├── tool_scoring.py
269
- │ │ │ ├── semantic_encoder.py
270
- │ │ │ ├── prompt_builder.py
271
- │ │ │ ├── llm_client.py
272
- │ │ │ └── document_ingestion.py
273
- │ │ ├── mcp_clients/
274
- │ │ │ ├── rag_client.py
275
- │ │ │ ├── web_client.py
276
- │ │ │ └── admin_client.py
277
- │ │ ├── models/
278
- │ │ │ ├── agent.py
279
- │ │ │ └── redflag.py
280
- │ │ ├── utils/
281
- │ │ │ └── text_extractor.py
282
- │ │ └── config.py
283
- │ │
284
- │ ├── mcp_servers/
285
- │ │ ├── main.py # RAG MCP Server (FastAPI)
286
- │ │ ├── rag_server.py # Alternative RAG server implementation
287
- │ │ ├── web_server.py # Web search MCP server
288
- │ │ ├── admin_server.py # Admin governance MCP server
289
- │ │ ├── database.py # Supabase/PostgreSQL connection + pgvector
290
- │ │ ├── embeddings.py # Sentence transformers embeddings
291
- │ │ └── models/
292
- │ │ ├── rag.py
293
- │ │ ├── web.py
294
- │ │ └── admin.py
295
- │ │
296
- │ ├── workers/
297
- │ │ ├── ingestion_worker.py # Celery tasks for document ingestion
298
- │ │ ├── analytics_worker.py # Celery tasks for analytics processing
299
- │ │ ├── scheduler.py # Scheduled task definitions
300
- │ │ └── celeryconfig.py # Celery app configuration
301
- │ │
302
- │ ├── tests/
303
- │ │ ├── test_agent.py
304
- │ │ ├── test_rag.py
305
- │ │ ├── test_admin.py
306
- │ │ ├── test_web.py
307
- │ │ └── test_end_to_end.py
308
- │ │
309
- │ └── Dockerfile
310
-
311
- ├── frontend/
312
- │ ├── app/
313
- │ │ ├── page.tsx # Main landing page
314
- │ │ ├── layout.tsx # Root layout
315
- │ │ ├── globals.css # Global styles
316
- │ │ └── knowledge-base/
317
- │ │ └── page.tsx # Knowledge base library page
318
- │ ├── components/
319
- │ │ ├── chat-panel.tsx # Chat interface component
320
- │ │ ├── analytics-panel.tsx # Analytics dashboard component
321
- │ │ ├── knowledge-base-panel.tsx # Knowledge base search/ingest UI
322
- │ │ ├── hero.tsx # Hero section
323
- │ │ ├── feature-grid.tsx # Feature showcase grid
324
- │ │ └── footer.tsx # Footer component
325
- │ ├── public/
326
- │ └── package.json
327
-
328
- ├── supabase/
329
- │ ├── migrations/
330
- │ │ ├── 001_init.sql
331
- │ │ ├── 002_embeddings.sql
332
- │ │ ├── 003_redflag_rules.sql
333
- │ │ └── 004_analytics_tables.sql
334
- │ ├── seed/
335
- │ │ ├── default_redflags.sql
336
- │ │ └── demo_tenants.sql
337
- │ ├── policies/
338
- │ │ ├── rls_tenant_documents.sql
339
- │ │ ├── rls_tenant_embeddings.sql
340
- │ │ └── rls_admin_rules.sql
341
- │ ├── functions/
342
- │ │ └── vector_search.sql
343
- │ └── README.md
344
-
345
- ├── docs/
346
- │ ├── architecture-diagram.png
347
- │ ├── sequence-mcp-agent.png
348
- │ ├── api-spec.md
349
- │ ├── mcp-server-protocol.md
350
- │ └── rag-pipeline.md
351
-
352
- ├── scripts/
353
- │ ├── run_dev.sh
354
- │ ├── build_all.sh
355
- │ ├── reset_db.sh
356
- │ └── run_workers.sh
357
-
358
- ├── docker-compose.yml
359
- ├── README.md
360
- └── LICENSE
361
- ```
362
-
363
- ---
364
-
365
- ## 🚀 Getting Started
366
 
367
  ### Prerequisites
368
 
369
- Before you begin, ensure you have the following installed:
370
-
371
- - **Python 3.10+**
372
- - ✅ **Node.js 20+ (64-bit)** (for frontend - required for Next.js)
373
- - ✅ **Supabase project** (with pgvector extension enabled)
374
- - ✅ **PostgreSQL connection string** (from Supabase)
375
- - ✅ **Ollama** (for local LLM) - [Installation Guide](#llm-setup)
376
- - ✅ **DuckDuckGo Search** (built-in, no key required)
377
- - ✅ **Slack/Email webhook** for alerts (optional)
378
- - ✅ **Redis/RabbitMQ** (for Celery workers, optional)
379
 
380
- ### Backend Setup
381
 
382
- 1. **Navigate to backend directory**
383
- ```bash
384
- cd backend
385
- ```
386
 
387
- 2. **Install dependencies**
388
  ```bash
389
  pip install -r requirements.txt
390
  ```
391
 
392
- 3. **Configure environment variables**
393
-
394
- Create a `.env` file in the project root with the following:
395
- ```env
396
- # Database Configurationa
397
- POSTGRESQL_URL=postgresql://user:password@host:port/database
398
- SUPABASE_URL=https://your-project.supabase.co
399
- SUPABASE_SERVICE_KEY=your_service_role_key
400
-
401
- # MCP Server URLs
402
- RAG_MCP_URL=http://localhost:8001
403
- WEB_MCP_URL=http://localhost:8002
404
- ADMIN_MCP_URL=http://localhost:8003
405
-
406
- # LLM Configuration
407
- OLLAMA_URL=http://localhost:11434
408
- OLLAMA_MODEL=llama3.1:latest
409
- LLM_BACKEND=ollama
410
- # Note: Install Ollama from https://ollama.ai and run: ollama pull llama3.1:latest
411
-
412
- # Celery Configuration (for async workers)
413
- CELERY_BROKER_URL=redis://localhost:6379/0
414
- CELERY_RESULT_BACKEND=redis://localhost:6379/0
415
- ```
416
-
417
- 4. **Start the MCP Servers** (in separate terminals or use start.bat)
418
-
419
- **RAG MCP Server:**
420
- ```bash
421
- cd backend/mcp_servers
422
- python main.py
423
- # Or: uvicorn main:app --reload --port 8001
424
- ```
425
- - Server runs on `http://localhost:8001`
426
- - Automatically initializes database schema on startup
427
- - API docs: `http://localhost:8001/docs`
428
-
429
- **Web MCP Server:**
430
- ```bash
431
- cd backend/mcp_servers
432
- uvicorn web_server:web_app --reload --port 8002
433
- ```
434
-
435
- **Admin MCP Server:**
436
- ```bash
437
- cd backend/mcp_servers
438
- uvicorn admin_server:admin_app --reload --port 8003
439
- ```
440
-
441
- **Or use the start script:**
442
- ```bash
443
- ./start.bat # Windows
444
- # or
445
- ./start.sh # Linux/Mac
446
- ```
447
-
448
- 5. **Start Celery workers** (for async document ingestion and analytics)
449
- ```bash
450
- # Start Celery worker
451
- celery -A backend.workers.celeryconfig worker --loglevel=info
452
-
453
- # In another terminal, start Celery beat (for scheduled tasks)
454
- celery -A backend.workers.celeryconfig beat --loglevel=info
455
- ```
456
-
457
- 6. **Run the main API server**
458
  ```bash
459
- cd backend
460
- uvicorn backend.api.main:app --reload --port 8000
461
  ```
462
- - Server runs on `http://localhost:8000`
463
- - API docs: `http://localhost:8000/docs`
464
-
465
- ### RAG API Endpoints
466
-
467
- The RAG system provides multiple endpoints:
468
-
469
- **1. Enhanced Document Ingestion (with metadata):**
470
- ```bash
471
- curl -X POST http://localhost:8000/rag/ingest-document \
472
- -H "Content-Type: application/json" \
473
- -H "x-tenant-id: tenant123" \
474
- -d '{
475
- "action": "ingest_document",
476
- "source_type": "raw_text",
477
- "content": "Your document text here...",
478
- "metadata": {
479
- "filename": "policy.txt",
480
- "doc_id": "policy-001"
481
- }
482
- }'
483
- ```
484
-
485
- **2. File Upload (PDF, DOCX, TXT, MD):**
486
- ```bash
487
- curl -X POST http://localhost:8000/rag/ingest-file \
488
- -H "x-tenant-id: tenant123" \
489
- -F "file=@document.pdf"
490
- ```
491
-
492
- **3. Semantic Search:**
493
- ```bash
494
- curl -X POST http://localhost:8000/rag/search \
495
- -H "Content-Type: application/json" \
496
- -H "x-tenant-id: tenant123" \
497
- -d '{
498
- "query": "What are the HR policies?"
499
- }'
500
- ```
501
-
502
- **4. List All Documents:**
503
- ```bash
504
- curl -X GET "http://localhost:8000/rag/list?limit=100&offset=0" \
505
- -H "x-tenant-id: tenant123"
506
- ```
507
 
508
- **5. Legacy Simple Ingestion:**
509
- ```bash
510
- curl -X POST http://localhost:8000/rag/ingest \
511
- -H "Content-Type: application/json" \
512
- -H "x-tenant-id: tenant123" \
513
- -d '{
514
- "content": "Your document text here..."
515
- }'
516
- ```
517
-
518
- ### Document Ingestion System
519
-
520
- Documents can be ingested through multiple methods:
521
-
522
- **Supported formats:**
523
- - **PDF files** - Server-side parsing with PyPDF2
524
- - **DOCX files** - Server-side parsing with python-docx
525
- - **TXT/Markdown files** - Direct text ingestion
526
- - **URLs** - Automatic content fetching and HTML extraction
527
- - **Raw text** - Direct paste and ingest
528
-
529
- **Ingestion methods:**
530
-
531
- 1. **File Upload** (recommended for PDF/DOCX):
532
- - Use the frontend UI or `/rag/ingest-file` endpoint
533
- - Files are parsed server-side automatically
534
 
535
- 2. **Enhanced API** (with metadata):
536
- - Use `/rag/ingest-document` for structured ingestion
537
- - Supports filename, URL, doc_id, and custom metadata
538
 
539
- 3. **Simple API** (legacy):
540
- - Use `/rag/ingest` for quick text ingestion
 
 
541
 
542
- **The ingestion process automatically:**
543
- - Detects document type from filename or content
544
- - Extracts text (PDF/DOCX parsed server-side)
545
- - Normalizes and sanitizes text
546
- - Chunks text with configurable overlap (~300 words)
547
- - Generates embeddings using Sentence-Transformers (MiniLM)
548
- - Stores chunks and embeddings in pgvector
549
- - Preserves metadata (filename, URL, doc_id)
550
-
551
- **Optional: Async Processing via Celery**
552
- - For large-scale ingestion, use Celery workers
553
- - Configure `CELERY_BROKER_URL` in `.env`
554
- - Workers process ingestion tasks asynchronously
555
-
556
- ### Frontend Setup
557
-
558
- 1. **Navigate to frontend directory**
559
- ```bash
560
- cd frontend
561
- ```
562
-
563
- 2. **Install dependencies**
564
- ```bash
565
- npm install
566
- ```
567
-
568
- 3. **Configure environment variables** (optional)
569
-
570
- Create a `.env.local` file in the frontend directory:
571
- ```env
572
- NEXT_PUBLIC_API_URL=http://localhost:8000
573
- ```
574
-
575
- 4. **Start the development server**
576
- ```bash
577
- npm run dev
578
- ```
579
-
580
- The app will be available at `http://localhost:3000` with:
581
- - **Main landing page** (`/`) with:
582
- - Hero section and feature overview
583
- - **Knowledge Base Panel** - Search and ingest documents
584
- - **Chat Panel** - Interact with the AI agent
585
- - **Analytics Panel** - View metrics and tool usage
586
- - **Knowledge Base Library** (`/knowledge-base`) - Browse all ingested documents with filtering
587
 
588
- ### LLM Setup
589
 
590
- **Ollama (Recommended for Local Development):**
591
 
592
- 1. **Install Ollama:**
593
- - Download from https://ollama.ai
594
- - Install and start the service
595
 
596
- 2. **Pull a model:**
597
- ```bash
598
- ollama pull llama3.1:latest
599
- ```
600
 
601
- 3. **Verify it's running:**
602
- ```bash
603
- curl http://localhost:11434/api/tags
604
- ```
 
 
605
 
606
- 4. **Configure in `.env`:**
607
- ```env
608
- OLLAMA_URL=http://localhost:11434
609
- OLLAMA_MODEL=llama3.1:latest
610
- LLM_BACKEND=ollama
611
- ```
612
 
613
- **Note:** If Ollama is not running, the system will show helpful error messages with setup instructions.
614
 
615
- ### Quick Start with Docker
616
 
617
- ```bash
618
- docker-compose up -d
619
- ```
620
 
621
- ---
622
 
623
- ## Why IntegraChat Stands Out
624
-
625
- | Feature | Description |
626
- |---------|-------------|
627
- | 🤖 **True MCP-Native** | Autonomous agents (not static prompts) |
628
- | 🛡️ **Enterprise Governance** | Regex-based red-flag rules system |
629
- | 🔍 **Hybrid Intelligence** | Multi-tool selection (RAG + Web + LLM combinations) |
630
- | 🔄 **Sequential Execution** | Execute multiple tools in sequence and synthesize results |
631
- | 🌐 **English Web Search** | Forces English language results for better accuracy |
632
- | 🏢 **Production-Grade** | Multi-tenant design with strict Supabase RLS |
633
- | 📊 **Full Observability** | Logs, analytics, tool events, violations |
634
- | 📚 **Knowledge Base UI** | Complete document management with search, ingestion, and library view |
635
- | 📄 **Multi-Format Ingestion** | PDF, DOCX, TXT, URL, and raw text support with server-side parsing |
636
- | 🔍 **Document Library** | Browse, filter, and search all ingested documents |
637
- | ⚡ **Async Processing** | Celery workers for scalable document ingestion and analytics |
638
- | 🎯 **Demo-Ready** | Perfect for enterprise presentations |
639
 
640
  ---
641
 
642
- ## 🏷 Submission Metadata
643
 
644
- | Field | Value |
645
- |-------|-------|
646
- | **Track** | MCP in Action |
647
- | **Category** | Enterprise |
648
- | **Tag** | `mcp-in-action-track-enterprise` |
649
- | **Project Name** | **IntegraChat** |
650
-
651
- ### Short Summary
652
 
653
- > IntegraChat is a multi-tenant AI platform where autonomous MCP-powered agents intelligently select and execute multiple tools in sequence (RAG + Web + LLM combinations), retrieve private knowledge using RAG, access live web information in English, and enforce admin-defined safety rules via a red-flag compliance system. It includes an analytics dashboard, advanced multi-tool selection engine, and strict tenant isolation.
654
 
655
  ---
656
 
657
- ## 📄 License
658
 
659
- This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
660
 
661
  ---
662
 
663
- ## 📬 Contributing
664
 
665
- Contributions are welcome! Please feel free to submit a Pull Request.
666
 
667
  ---
668
 
669
- ## 🙏 Acknowledgments
670
 
671
  - Built with [Model Context Protocol (MCP)](https://modelcontextprotocol.io/)
672
- - Powered by [Ollama](https://ollama.ai/) or [Groq](https://groq.com/) for LLM inference
673
- - Database powered by [Supabase](https://supabase.com/)
674
 
675
  ---
676
 
677
  <div align="center">
678
 
679
- **Made with ❤️ for the MCP community**
680
 
681
- [⬆ Back to Top](#-integrachat)
682
 
683
  </div>
 
1
+ # IntegraChat — MCP Autonomous Agent
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
 
3
+ **Track:** MCP in Action
4
+ **Category:** Enterprise
5
+ **Tag:** `mcp-in-action-track-enterprise`
 
 
 
 
 
 
6
 
7
  ---
8
 
9
+ ## Overview
10
 
11
+ IntegraChat is an enterprise-ready, multi-tenant AI platform that demonstrates the full capabilities of the **Model Context Protocol (MCP)** in a production-style environment. It combines autonomous tool-using agents, RAG retrieval, live web search, and admin governance under strict tenant isolation.
12
 
13
+ This Hugging Face Space provides a Gradio interface to interact with the IntegraChat MCP backend, showcasing how MCP can power intelligent, governed, multi-tenant AI systems.
 
 
 
 
 
14
 
15
  ---
16
 
17
+ ## Features
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
 
19
+ - 🤖 **Autonomous MCP Agents** - Intelligent tool selection and execution
20
+ - 📚 **Enterprise RAG System** - Multi-tenant knowledge base with semantic search
21
+ - 🌐 **Live Web Search** - Real-time information retrieval
22
+ - 🛡️ **Red-Flag Governance** - Automated safety monitoring and compliance
23
+ - 🏢 **Multi-Tenant Isolation** - Strict tenant separation with secure access control
24
+ - 📊 **Analytics Dashboard** - Comprehensive system insights and observability
25
+ - 🔄 **Multi-Tool Selection** - Dynamic tool combinations (RAG + Web + LLM)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
 
27
  ---
28
 
29
+ ## How to Run the Space
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
 
31
  ### Prerequisites
32
 
33
+ 1. **Backend Server Running**: The IntegraChat FastAPI backend must be running at `http://localhost:8000`
34
+ - See the [backend README](backend/README.md) for setup instructions
35
+ - The backend provides the MCP agent endpoint at `/agent/chat`
 
 
 
 
 
 
 
36
 
37
+ 2. **Python 3.10+** installed
38
 
39
+ ### Installation
 
 
 
40
 
41
+ 1. **Install dependencies**:
42
  ```bash
43
  pip install -r requirements.txt
44
  ```
45
 
46
+ 2. **Start the Gradio app**:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
47
  ```bash
48
+ python app.py
 
49
  ```
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
50
 
51
+ 3. **Access the interface**:
52
+ - Local: `http://localhost:7860`
53
+ - The app will automatically connect to the backend at `http://localhost:8000`
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
54
 
55
+ ### Usage
 
 
56
 
57
+ 1. Enter your **Tenant ID** in the textbox (e.g., `tenant123`)
58
+ 2. Type your message in the chat interface
59
+ 3. Click **Send** or press Enter
60
+ 4. The agent will process your message using MCP tools and respond
61
 
62
+ ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
63
 
64
+ ## API Endpoint
65
 
66
+ The Gradio interface communicates with the backend MCP agent via:
67
 
68
+ **Endpoint:** `POST http://localhost:8000/agent/chat`
 
 
69
 
70
+ **Headers:**
71
+ - `Content-Type: application/json`
72
+ - `x-tenant-id: <your-tenant-id>`
 
73
 
74
+ **Request Body:**
75
+ ```json
76
+ {
77
+ "message": "Your message here"
78
+ }
79
+ ```
80
 
81
+ **Response:**
82
+ ```json
83
+ {
84
+ "response": "Agent's response text"
85
+ }
86
+ ```
87
 
88
+ **Note:** The backend MCP system must be running separately. This Space provides only the frontend interface. See the [backend documentation](backend/README.md) for full setup instructions.
89
 
90
+ ---
91
 
92
+ ## Demo Video
 
 
93
 
94
+ 🎥 **[Demo Video Placeholder]** - Coming soon!
95
 
96
+ Watch how IntegraChat uses MCP to power autonomous agents with multi-tool selection, RAG retrieval, and governance.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
97
 
98
  ---
99
 
100
+ ## Social Media
101
 
102
+ 📱 **[Social Media Post Placeholder]** - Coming soon!
 
 
 
 
 
 
 
103
 
104
+ Follow us for updates and demos of IntegraChat in action!
105
 
106
  ---
107
 
108
+ ## Team Member(s)
109
 
110
+ - **Your Name Here** - Developer & MCP Enthusiast
111
 
112
  ---
113
 
114
+ ## License
115
 
116
+ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
117
 
118
  ---
119
 
120
+ ## Acknowledgments
121
 
122
  - Built with [Model Context Protocol (MCP)](https://modelcontextprotocol.io/)
123
+ - Powered by [Gradio](https://gradio.app/) for the interface
124
+ - Backend built with [FastAPI](https://fastapi.tiangolo.com/)
125
 
126
  ---
127
 
128
  <div align="center">
129
 
130
+ **Made with ❤️ for the MCP Hackathon**
131
 
132
+ [⬆ Back to Top](#integrachat--mcp-autonomous-agent)
133
 
134
  </div>
app.py ADDED
@@ -0,0 +1,627 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import requests
3
+ import json
4
+ import os
5
+ from pathlib import Path
6
+
7
+ BACKEND_BASE_URL = os.getenv("BACKEND_BASE_URL", "http://localhost:8000")
8
+
9
+
10
+ def chat_with_agent(message, tenant_id, history):
11
+ """
12
+ Send a message to the backend MCP agent and return the response.
13
+
14
+ Args:
15
+ message: User's message text
16
+ tenant_id: Tenant ID for multi-tenant isolation
17
+ history: Chat history (Gradio messages format)
18
+
19
+ Returns:
20
+ Updated chat history with agent response
21
+ """
22
+ if not message or not message.strip():
23
+ return history
24
+
25
+ if not tenant_id or not tenant_id.strip():
26
+ error_msg = "Please enter a Tenant ID before sending a message."
27
+ history.append({"role": "user", "content": message})
28
+ history.append({"role": "assistant", "content": error_msg})
29
+ return history
30
+
31
+ # Backend API endpoint
32
+ backend_url = f"{BACKEND_BASE_URL}/agent/message"
33
+
34
+ # Prepare request payload (matching backend API format)
35
+ payload = {
36
+ "tenant_id": tenant_id.strip(),
37
+ "message": message,
38
+ "user_id": None,
39
+ "conversation_history": [],
40
+ "temperature": 0.0
41
+ }
42
+
43
+ # Prepare headers
44
+ headers = {
45
+ "Content-Type": "application/json"
46
+ }
47
+
48
+ try:
49
+ # Send POST request to backend
50
+ # Increased timeout to 120 seconds for complex agent operations
51
+ # (RAG search, web search, LLM calls can take time)
52
+ response = requests.post(
53
+ backend_url,
54
+ json=payload,
55
+ headers=headers,
56
+ timeout=120
57
+ )
58
+
59
+ # Check if request was successful
60
+ if response.status_code == 200:
61
+ response_data = response.json()
62
+ # Backend returns response in "text" field
63
+ agent_response = response_data.get("text", "No response received from agent.")
64
+ history.append({"role": "user", "content": message})
65
+ history.append({"role": "assistant", "content": agent_response})
66
+ else:
67
+ error_msg = f"Error {response.status_code}: {response.text}"
68
+ history.append({"role": "user", "content": message})
69
+ history.append({"role": "assistant", "content": error_msg})
70
+
71
+ except requests.exceptions.ConnectionError:
72
+ error_msg = "❌ Connection Error: Could not connect to backend. Please ensure the FastAPI server is running at http://localhost:8000"
73
+ history.append({"role": "user", "content": message})
74
+ history.append({"role": "assistant", "content": error_msg})
75
+
76
+ except requests.exceptions.Timeout:
77
+ error_msg = "⏱️ Request Timeout: The backend took longer than 2 minutes to respond. This may happen if:\n- The LLM is processing a complex query\n- Multiple tools (RAG, Web Search) are being used\n- The backend is under heavy load\n\nPlease try again with a simpler query, or check if the backend services (Ollama, MCP servers) are running properly."
78
+ history.append({"role": "user", "content": message})
79
+ history.append({"role": "assistant", "content": error_msg})
80
+
81
+ except requests.exceptions.RequestException as e:
82
+ error_msg = f"❌ Request Error: {str(e)}"
83
+ history.append({"role": "user", "content": message})
84
+ history.append({"role": "assistant", "content": error_msg})
85
+
86
+ except Exception as e:
87
+ error_msg = f"❌ Unexpected Error: {str(e)}"
88
+ history.append({"role": "user", "content": message})
89
+ history.append({"role": "assistant", "content": error_msg})
90
+
91
+ return history
92
+
93
+
94
+ def ingest_document(
95
+ tenant_id: str,
96
+ source_type: str,
97
+ content: str,
98
+ document_url: str,
99
+ filename: str,
100
+ doc_id: str,
101
+ metadata_json: str
102
+ ):
103
+ if not tenant_id or not tenant_id.strip():
104
+ return "❗ Tenant ID is required to ingest documents."
105
+
106
+ tenant_id = tenant_id.strip()
107
+
108
+ payload_content = content or ""
109
+ if source_type == "url" and document_url:
110
+ payload_content = document_url.strip()
111
+
112
+ metadata = {}
113
+ if filename:
114
+ metadata["filename"] = filename.strip()
115
+ if document_url:
116
+ metadata["url"] = document_url.strip()
117
+ if doc_id:
118
+ metadata["doc_id"] = doc_id.strip()
119
+
120
+ if metadata_json:
121
+ try:
122
+ extra_metadata = json.loads(metadata_json)
123
+ if isinstance(extra_metadata, dict):
124
+ metadata.update(extra_metadata)
125
+ else:
126
+ return "❗ Metadata JSON must represent an object (key/value pairs)."
127
+ except json.JSONDecodeError as exc:
128
+ return f"❗ Invalid metadata JSON: {exc}"
129
+
130
+ payload = {
131
+ "action": "ingest_document",
132
+ "tenant_id": tenant_id,
133
+ "source_type": source_type,
134
+ "content": payload_content,
135
+ "metadata": metadata
136
+ }
137
+
138
+ try:
139
+ response = requests.post(
140
+ f"{BACKEND_BASE_URL}/rag/ingest-document",
141
+ json=payload,
142
+ headers={"Content-Type": "application/json"},
143
+ timeout=60
144
+ )
145
+ if response.status_code == 200:
146
+ data = response.json()
147
+ return f"✅ Document ingested successfully.\n\n{data.get('message', '')}"
148
+ return f"❌ Ingestion failed ({response.status_code}): {response.text}"
149
+ except requests.exceptions.ConnectionError:
150
+ return "❌ Could not reach the backend. Make sure the FastAPI server is running."
151
+ except requests.exceptions.Timeout:
152
+ return "⏱️ The ingestion request timed out. Please try again."
153
+ except Exception as exc:
154
+ return f"❌ Unexpected error during ingestion: {exc}"
155
+
156
+
157
+ def ingest_file(tenant_id: str, file_obj):
158
+ if not tenant_id or not tenant_id.strip():
159
+ return "❗ Tenant ID is required to ingest files."
160
+ if file_obj is None:
161
+ return "❗ Please select a file to upload."
162
+
163
+ tenant_id = tenant_id.strip()
164
+
165
+ try:
166
+ file_path = Path(file_obj.name)
167
+ with open(file_path, "rb") as f:
168
+ file_bytes = f.read()
169
+
170
+ files = {
171
+ "file": (file_path.name, file_bytes, "application/octet-stream")
172
+ }
173
+ response = requests.post(
174
+ f"{BACKEND_BASE_URL}/rag/ingest-file",
175
+ files=files,
176
+ headers={"x-tenant-id": tenant_id},
177
+ timeout=120
178
+ )
179
+ if response.status_code == 200:
180
+ data = response.json()
181
+ return f"✅ File ingested successfully.\n\n{data.get('message', '')}"
182
+ return f"❌ File ingestion failed ({response.status_code}): {response.text}"
183
+ except FileNotFoundError:
184
+ return "❌ Could not read the uploaded file."
185
+ except requests.exceptions.ConnectionError:
186
+ return "❌ Could not reach the backend. Make sure the FastAPI server is running."
187
+ except requests.exceptions.Timeout:
188
+ return "⏱️ File ingestion timed out. Please try again."
189
+ except Exception as exc:
190
+ return f"❌ Unexpected error during file ingestion: {exc}"
191
+
192
+
193
+ def _format_rules_table(rules: list[str]) -> list[list]:
194
+ return [[idx + 1, rule] for idx, rule in enumerate(rules)]
195
+
196
+
197
+ def fetch_admin_rules(tenant_id: str) -> tuple[str, list[list]]:
198
+ if not tenant_id or not tenant_id.strip():
199
+ return "❗ Tenant ID is required.", []
200
+
201
+ tenant_id = tenant_id.strip()
202
+ try:
203
+ response = requests.get(
204
+ f"{BACKEND_BASE_URL}/admin/rules",
205
+ headers={"x-tenant-id": tenant_id},
206
+ timeout=30
207
+ )
208
+ if response.status_code == 200:
209
+ rules = response.json().get("rules", [])
210
+ if not rules:
211
+ return "✅ No admin rules have been configured yet.", []
212
+ summary = f"### Current Rules ({len(rules)})"
213
+ return summary, _format_rules_table(rules)
214
+ return f"❌ Error {response.status_code}: {response.text}", []
215
+ except requests.exceptions.ConnectionError:
216
+ return "❌ Could not reach backend. Ensure the FastAPI server is running.", []
217
+ except requests.exceptions.Timeout:
218
+ return "⏱️ Request timed out. Please try again.", []
219
+ except Exception as exc:
220
+ return f"❌ Unexpected error: {exc}", []
221
+
222
+
223
+ def add_admin_rules(tenant_id: str, rules_text: str) -> str:
224
+ if not tenant_id or not tenant_id.strip():
225
+ return "❗ Tenant ID is required."
226
+ if not rules_text or not rules_text.strip():
227
+ return "❗ Provide at least one rule to upload."
228
+
229
+ tenant_id = tenant_id.strip()
230
+ rules = [rule.strip() for rule in rules_text.splitlines() if rule.strip()]
231
+ if not rules:
232
+ return "❗ No valid rules detected."
233
+
234
+ added = []
235
+ errors = []
236
+ for rule in rules:
237
+ try:
238
+ resp = requests.post(
239
+ f"{BACKEND_BASE_URL}/admin/rules",
240
+ params={"rule": rule},
241
+ headers={"x-tenant-id": tenant_id},
242
+ timeout=15
243
+ )
244
+ if resp.status_code == 200:
245
+ added.append(rule)
246
+ else:
247
+ errors.append(f"{rule} -> {resp.status_code}: {resp.text}")
248
+ except Exception as exc:
249
+ errors.append(f"{rule} -> {exc}")
250
+
251
+ summary = []
252
+ if added:
253
+ summary.append(f"✅ Added {len(added)} rule(s):\n" + "\n".join([f"- {r}" for r in added]))
254
+ if errors:
255
+ summary.append("⚠️ Errors:\n" + "\n".join(errors))
256
+
257
+ return "\n\n".join(summary) if summary else "No rules were added."
258
+
259
+
260
+ def delete_admin_rule(tenant_id: str, rule: str) -> str:
261
+ if not tenant_id or not tenant_id.strip():
262
+ return "❗ Tenant ID is required."
263
+ if not rule or not rule.strip():
264
+ return "❗ Provide the exact rule text to delete."
265
+
266
+ tenant_id = tenant_id.strip()
267
+ rule = rule.strip()
268
+
269
+ try:
270
+ resp = requests.delete(
271
+ f"{BACKEND_BASE_URL}/admin/rules/{rule}",
272
+ headers={"x-tenant-id": tenant_id},
273
+ timeout=15
274
+ )
275
+ if resp.status_code == 200:
276
+ return f"🗑️ Deleted rule: {rule}"
277
+ return f"❌ Error {resp.status_code}: {resp.text}"
278
+ except requests.exceptions.ConnectionError:
279
+ return "❌ Could not reach backend. Ensure the FastAPI server is running."
280
+ except requests.exceptions.Timeout:
281
+ return "⏱️ Delete request timed out. Please try again."
282
+ except Exception as exc:
283
+ return f"❌ Unexpected error: {exc}"
284
+
285
+
286
+ def add_rules_and_refresh(tenant_id: str, rules_text: str):
287
+ status = add_admin_rules(tenant_id, rules_text)
288
+ summary, rows = fetch_admin_rules(tenant_id)
289
+ return status, summary, rows
290
+
291
+
292
+ def delete_rule_and_refresh(tenant_id: str, rule: str):
293
+ status = delete_admin_rule(tenant_id, rule)
294
+ summary, rows = fetch_admin_rules(tenant_id)
295
+ return status, summary, rows
296
+
297
+
298
+ def fetch_admin_analytics(tenant_id: str) -> str:
299
+ if not tenant_id or not tenant_id.strip():
300
+ return "❗ Tenant ID is required to view analytics."
301
+
302
+ tenant_id = tenant_id.strip()
303
+ headers = {"x-tenant-id": tenant_id}
304
+ sections = []
305
+
306
+ endpoints = [
307
+ ("Overview", "/analytics/overview"),
308
+ ("Tool Usage", "/analytics/tool-usage"),
309
+ ("Red Flags", "/analytics/redflags"),
310
+ ("Activity", "/analytics/activity"),
311
+ ]
312
+
313
+ for label, path in endpoints:
314
+ try:
315
+ resp = requests.get(
316
+ f"{BACKEND_BASE_URL}{path}",
317
+ headers=headers,
318
+ timeout=30
319
+ )
320
+ if resp.status_code == 200:
321
+ data = resp.json()
322
+ pretty = json.dumps(data, indent=2)
323
+ sections.append(f"### {label}\n```json\n{pretty}\n```")
324
+ else:
325
+ sections.append(f"### {label}\n❌ Error {resp.status_code}: {resp.text}")
326
+ except requests.exceptions.ConnectionError:
327
+ sections.append(f"### {label}\n❌ Could not reach backend. Is the FastAPI server running?")
328
+ except requests.exceptions.Timeout:
329
+ sections.append(f"### {label}\n⏱️ Request timed out. Please try again.")
330
+ except Exception as exc:
331
+ sections.append(f"### {label}\n❌ Unexpected error: {exc}")
332
+
333
+ return "\n\n".join(sections) if sections else "No analytics available."
334
+
335
+
336
+ # Create Gradio interface
337
+ with gr.Blocks(title="IntegraChat — MCP Autonomous Agent", theme=gr.themes.Soft()) as demo:
338
+ gr.Markdown(
339
+ """
340
+ # 🤖 IntegraChat — MCP Autonomous Agent
341
+
342
+ **Enterprise-grade AI with autonomous agents, secure multi-tenant RAG, real-time web search, and governance.**
343
+
344
+ Enter your Tenant ID to chat with the MCP-powered agent or ingest documents into the enterprise knowledge base.
345
+ """
346
+ )
347
+
348
+ tenant_id_input = gr.Textbox(
349
+ label="Tenant ID",
350
+ placeholder="Enter your tenant ID (e.g., tenant123)",
351
+ value="",
352
+ interactive=True
353
+ )
354
+
355
+ with gr.Tabs():
356
+ with gr.Tab("Chat"):
357
+ with gr.Row():
358
+ with gr.Column(scale=2):
359
+ chatbot = gr.Chatbot(
360
+ label="Chat with Agent",
361
+ height=500,
362
+ show_label=True,
363
+ container=True,
364
+ type="messages"
365
+ )
366
+
367
+ with gr.Row():
368
+ message_input = gr.Textbox(
369
+ label="Message",
370
+ placeholder="Type your message here...",
371
+ scale=4,
372
+ show_label=False,
373
+ container=False
374
+ )
375
+ send_button = gr.Button("Send", variant="primary", scale=1)
376
+
377
+ with gr.Column(scale=1):
378
+ gr.Markdown(
379
+ """
380
+ ### 📝 Chat Instructions
381
+ 1. Enter your **Tenant ID** above
382
+ 2. Ask a question or give a task to the agent
383
+ 3. The MCP agent will automatically select tools (RAG, Web, etc.)
384
+
385
+ ### ⚙️ Backend Configuration
386
+ The agent connects to the FastAPI backend at `http://localhost:8000/agent/message`
387
+ """
388
+ )
389
+
390
+ # Event handlers for chat tab
391
+ def send_message(message, tenant_id, history):
392
+ updated_history = chat_with_agent(message, tenant_id, history)
393
+ return updated_history, "" # Clear message input after sending
394
+
395
+ send_button.click(
396
+ fn=send_message,
397
+ inputs=[message_input, tenant_id_input, chatbot],
398
+ outputs=[chatbot, message_input]
399
+ )
400
+
401
+ message_input.submit(
402
+ fn=send_message,
403
+ inputs=[message_input, tenant_id_input, chatbot],
404
+ outputs=[chatbot, message_input]
405
+ )
406
+
407
+ with gr.Tab("Document Ingestion"):
408
+ gr.Markdown(
409
+ """
410
+ ### 📚 Knowledge Base Ingestion
411
+ Ingest documents so the MCP agent can reference tenant-private knowledge.
412
+
413
+ - **Raw text / URLs:** Use the fields below.
414
+ - **Files (PDF, DOCX, TXT, MD):** Use the file upload section.
415
+ """
416
+ )
417
+
418
+ ingestion_mode = gr.Radio(
419
+ ["Raw Text", "URL", "File Upload"],
420
+ value="Raw Text",
421
+ label="Select Ingestion Mode"
422
+ )
423
+
424
+ with gr.Row():
425
+ doc_filename = gr.Textbox(label="Filename (optional)")
426
+ doc_id = gr.Textbox(label="Document ID (optional)")
427
+
428
+ document_url = gr.Textbox(
429
+ label="Document URL (for URL ingestion)",
430
+ placeholder="https://example.com/policy",
431
+ visible=False
432
+ )
433
+
434
+ doc_content = gr.Textbox(
435
+ label="Content / Notes",
436
+ placeholder="Paste the document text here...",
437
+ lines=8,
438
+ visible=True
439
+ )
440
+
441
+ metadata_json = gr.Textbox(
442
+ label="Additional Metadata (JSON)",
443
+ placeholder='{"department": "HR", "tags": ["policy", "benefits"]}'
444
+ )
445
+
446
+ ingest_doc_button = gr.Button("Ingest Text / URL Document", variant="primary")
447
+
448
+ document_status = gr.Markdown("")
449
+
450
+ def handle_ingest_document(
451
+ tenant_id,
452
+ mode,
453
+ content,
454
+ doc_url,
455
+ filename,
456
+ doc_id_value,
457
+ metadata
458
+ ):
459
+ source_type = "raw_text" if mode == "Raw Text" else "url"
460
+ return ingest_document(
461
+ tenant_id=tenant_id,
462
+ source_type=source_type,
463
+ content=content,
464
+ document_url=doc_url,
465
+ filename=filename,
466
+ doc_id=doc_id_value,
467
+ metadata_json=metadata
468
+ )
469
+
470
+ ingest_doc_button.click(
471
+ fn=handle_ingest_document,
472
+ inputs=[
473
+ tenant_id_input,
474
+ ingestion_mode,
475
+ doc_content,
476
+ document_url,
477
+ doc_filename,
478
+ doc_id,
479
+ metadata_json
480
+ ],
481
+ outputs=document_status
482
+ )
483
+
484
+ file_section = gr.Markdown("#### 📁 File Upload (PDF, DOCX, TXT, Markdown)", visible=False)
485
+ file_upload = gr.File(
486
+ label="Upload File",
487
+ file_types=[".pdf", ".docx", ".txt", ".md", ".markdown"],
488
+ visible=False
489
+ )
490
+ ingest_file_button = gr.Button("Upload & Ingest File", visible=False)
491
+
492
+ def handle_file_ingestion(tenant_id, file_obj):
493
+ return ingest_file(tenant_id, file_obj)
494
+
495
+ ingest_file_button.click(
496
+ fn=handle_file_ingestion,
497
+ inputs=[tenant_id_input, file_upload],
498
+ outputs=document_status
499
+ )
500
+
501
+ def toggle_source_fields(mode):
502
+ show_text = mode == "Raw Text"
503
+ show_url = mode == "URL"
504
+ show_file = mode == "File Upload"
505
+ return (
506
+ gr.update(visible=show_text),
507
+ gr.update(visible=show_url),
508
+ gr.update(visible=not show_file),
509
+ gr.update(visible=not show_file),
510
+ gr.update(visible=not show_file),
511
+ gr.update(visible=show_file),
512
+ gr.update(visible=show_file),
513
+ gr.update(visible=show_file),
514
+ )
515
+
516
+ ingestion_mode.change(
517
+ fn=toggle_source_fields,
518
+ inputs=[ingestion_mode],
519
+ outputs=[
520
+ doc_content,
521
+ document_url,
522
+ doc_filename,
523
+ doc_id,
524
+ ingest_doc_button,
525
+ file_section,
526
+ file_upload,
527
+ ingest_file_button,
528
+ ]
529
+ )
530
+
531
+ with gr.Tab("Admin Analytics"):
532
+ gr.Markdown(
533
+ """
534
+ ### 📊 Admin Analytics
535
+ Review tenant-level analytics generated by the IntegraChat backend.
536
+
537
+ - **Overview:** Total queries, active users, red-flag count.
538
+ - **Tool Usage:** How often RAG, Web, and Admin tools are invoked.
539
+ - **Red Flags:** Recent governance events for this tenant.
540
+ - **Activity:** Summary of tenant activity metrics.
541
+ """
542
+ )
543
+
544
+ analytics_refresh = gr.Button("Fetch Analytics Snapshot", variant="primary")
545
+ analytics_output = gr.Markdown("👉 Click the button to load analytics for the current tenant.")
546
+
547
+ analytics_refresh.click(
548
+ fn=fetch_admin_analytics,
549
+ inputs=[tenant_id_input],
550
+ outputs=analytics_output
551
+ )
552
+
553
+ with gr.Tab("Admin Rules & Compliance"):
554
+ gr.Markdown(
555
+ """
556
+ ### 🛡️ Admin Rules & Regulations
557
+ Upload or manage tenant-specific governance rules (red-flag patterns, compliance policies, etc.).
558
+
559
+ - Enter one rule per line to upload multiple at once.
560
+ - Use the delete box to remove an exact rule.
561
+ - Refresh anytime to view the latest rule set.
562
+ """
563
+ )
564
+
565
+ rules_summary = gr.Markdown("👉 Click **Refresh Rules** to see existing entries.")
566
+ rules_table = gr.Dataframe(
567
+ headers=["#", "Rule"],
568
+ datatype=["number", "str"],
569
+ interactive=False,
570
+ value=[]
571
+ )
572
+ rules_status = gr.Markdown("")
573
+
574
+ with gr.Row():
575
+ refresh_rules_button = gr.Button("Refresh Rules", variant="secondary")
576
+ gr.Markdown("")
577
+
578
+ rules_input = gr.Textbox(
579
+ label="Rules / Regulations",
580
+ placeholder="Enter one rule per line...",
581
+ lines=6
582
+ )
583
+ upload_rules_button = gr.Button("Upload / Append Rules", variant="primary")
584
+
585
+ delete_rule_input = gr.Textbox(
586
+ label="Delete Rule",
587
+ placeholder="Enter the exact rule text to remove..."
588
+ )
589
+ delete_rule_button = gr.Button("Delete Rule", variant="stop")
590
+
591
+ refresh_rules_button.click(
592
+ fn=fetch_admin_rules,
593
+ inputs=[tenant_id_input],
594
+ outputs=[rules_summary, rules_table]
595
+ )
596
+
597
+ upload_rules_button.click(
598
+ fn=add_rules_and_refresh,
599
+ inputs=[tenant_id_input, rules_input],
600
+ outputs=[rules_status, rules_summary, rules_table]
601
+ )
602
+
603
+ delete_rule_button.click(
604
+ fn=delete_rule_and_refresh,
605
+ inputs=[tenant_id_input, delete_rule_input],
606
+ outputs=[rules_status, rules_summary, rules_table]
607
+ )
608
+
609
+ gr.Markdown(
610
+ """
611
+ ---
612
+ **Built with [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) for the MCP Hackathon**
613
+ """
614
+ )
615
+
616
+ if __name__ == "__main__":
617
+ import os
618
+ # For Hugging Face Spaces, bind to 0.0.0.0; for local dev, use 127.0.0.1
619
+ # HF Spaces sets SPACE_ID environment variable
620
+ server_name = "0.0.0.0" if os.getenv("SPACE_ID") else "127.0.0.1"
621
+
622
+ demo.launch(
623
+ server_name=server_name,
624
+ server_port=7860,
625
+ share=False
626
+ )
627
+
assets/banner.png ADDED
backend/README.md ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Backend Documentation
2
+
3
+ This directory contains the IntegraChat backend implementation.
4
+
5
+ ## Structure
6
+
7
+ - `api/` - FastAPI application with routes and services
8
+ - `mcp_servers/` - MCP server implementations (RAG, Web Search, Admin)
9
+ - `workers/` - Celery workers for async task processing
10
+
11
+ ## Setup
12
+
13
+ For full backend setup instructions, refer to the main project README.md in the root directory.
14
+
15
+ ## API Endpoints
16
+
17
+ The main API endpoint used by the Gradio interface:
18
+
19
+ - `POST /agent/chat` - Chat with the MCP agent
20
+ - Headers: `x-tenant-id: <tenant-id>`
21
+ - Body: `{ "message": "<text>" }`
22
+ - Response: `{ "response": "<agent-response>" }`
23
+
24
+ ## Note
25
+
26
+ This Hugging Face Space submission includes only placeholder files for the backend structure.
27
+ The full backend codebase exists in the main IntegraChat repository.
28
+
backend/api/placeholder.txt ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ This directory contains the FastAPI backend API code.
2
+ For the Hugging Face Space submission, only placeholder files are included.
3
+ The full backend implementation exists separately.
4
+
backend/api/routes/admin.py CHANGED
@@ -1,17 +1,27 @@
1
  from fastapi import APIRouter, Header, HTTPException
2
- from typing import List
 
 
 
3
 
4
  router = APIRouter()
5
 
6
- # Temporary in-memory store (replace with Supabase later)
7
- TENANT_RULES = {}
 
 
 
 
 
 
 
8
 
9
 
10
  def get_rules_for_tenant(tenant_id: str) -> List[str]:
11
- return TENANT_RULES.get(tenant_id, [])
12
 
13
 
14
- @router.get("/admin/rules")
15
  async def get_redflag_rules(
16
  x_tenant_id: str = Header(None)
17
  ):
@@ -28,33 +38,64 @@ async def get_redflag_rules(
28
  }
29
 
30
 
31
- @router.post("/admin/rules")
32
  async def add_redflag_rule(
33
- rule: str,
 
34
  x_tenant_id: str = Header(None)
35
  ):
36
  """
37
  Adds a new red-flag rule to this tenant.
 
38
  """
39
 
40
  if not x_tenant_id:
41
  raise HTTPException(status_code=400, detail="Missing tenant ID")
42
 
 
 
 
 
 
 
 
 
 
43
  rules = get_rules_for_tenant(x_tenant_id)
44
 
45
- if rule not in rules:
46
- rules.append(rule)
 
 
 
47
 
48
- TENANT_RULES[x_tenant_id] = rules
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
49
 
50
  return {
51
  "tenant_id": x_tenant_id,
52
- "added_rule": rule,
53
  "rules": rules
54
  }
55
 
56
 
57
- @router.delete("/admin/rules/{rule}")
58
  async def delete_redflag_rule(
59
  rule: str,
60
  x_tenant_id: str = Header(None)
@@ -66,13 +107,11 @@ async def delete_redflag_rule(
66
  if not x_tenant_id:
67
  raise HTTPException(status_code=400, detail="Missing tenant ID")
68
 
69
- rules = get_rules_for_tenant(x_tenant_id)
70
-
71
- if rule not in rules:
72
  raise HTTPException(status_code=404, detail="Rule not found")
73
 
74
- rules.remove(rule)
75
- TENANT_RULES[x_tenant_id] = rules
76
 
77
  return {
78
  "tenant_id": x_tenant_id,
 
1
  from fastapi import APIRouter, Header, HTTPException
2
+ from pydantic import BaseModel
3
+ from typing import List, Optional
4
+
5
+ from backend.api.storage.rules_store import RulesStore
6
 
7
  router = APIRouter()
8
 
9
+ rules_store = RulesStore()
10
+
11
+
12
+ class RulePayload(BaseModel):
13
+ rule: str
14
+
15
+
16
+ class BulkRulePayload(BaseModel):
17
+ rules: List[str]
18
 
19
 
20
  def get_rules_for_tenant(tenant_id: str) -> List[str]:
21
+ return rules_store.get_rules(tenant_id)
22
 
23
 
24
+ @router.get("/rules")
25
  async def get_redflag_rules(
26
  x_tenant_id: str = Header(None)
27
  ):
 
38
  }
39
 
40
 
41
+ @router.post("/rules")
42
  async def add_redflag_rule(
43
+ payload: Optional[RulePayload] = None,
44
+ rule: Optional[str] = None,
45
  x_tenant_id: str = Header(None)
46
  ):
47
  """
48
  Adds a new red-flag rule to this tenant.
49
+ Accepts either JSON body {"rule": "..."} or query parameter ?rule=...
50
  """
51
 
52
  if not x_tenant_id:
53
  raise HTTPException(status_code=400, detail="Missing tenant ID")
54
 
55
+ rule_value = payload.rule if payload else rule
56
+ if not rule_value:
57
+ raise HTTPException(status_code=400, detail="Missing rule text")
58
+
59
+ rule_value = rule_value.strip()
60
+ if not rule_value:
61
+ raise HTTPException(status_code=400, detail="Rule cannot be empty")
62
+
63
+ rules_store.add_rule(x_tenant_id, rule_value)
64
  rules = get_rules_for_tenant(x_tenant_id)
65
 
66
+ return {
67
+ "tenant_id": x_tenant_id,
68
+ "added_rule": rule_value,
69
+ "rules": rules
70
+ }
71
 
72
+
73
+ @router.post("/rules/bulk")
74
+ async def add_redflag_rules_bulk(
75
+ payload: BulkRulePayload,
76
+ x_tenant_id: str = Header(None)
77
+ ):
78
+ """
79
+ Adds multiple rules in one call.
80
+ """
81
+ if not x_tenant_id:
82
+ raise HTTPException(status_code=400, detail="Missing tenant ID")
83
+
84
+ if not payload.rules:
85
+ raise HTTPException(status_code=400, detail="No rules provided")
86
+
87
+ cleaned = [rule.strip() for rule in payload.rules if rule.strip()]
88
+ added = rules_store.add_rules_bulk(x_tenant_id, cleaned)
89
+ rules = get_rules_for_tenant(x_tenant_id)
90
 
91
  return {
92
  "tenant_id": x_tenant_id,
93
+ "added_rules": added,
94
  "rules": rules
95
  }
96
 
97
 
98
+ @router.delete("/rules/{rule}")
99
  async def delete_redflag_rule(
100
  rule: str,
101
  x_tenant_id: str = Header(None)
 
107
  if not x_tenant_id:
108
  raise HTTPException(status_code=400, detail="Missing tenant ID")
109
 
110
+ deleted = rules_store.delete_rule(x_tenant_id, rule)
111
+ if not deleted:
 
112
  raise HTTPException(status_code=404, detail="Rule not found")
113
 
114
+ rules = get_rules_for_tenant(x_tenant_id)
 
115
 
116
  return {
117
  "tenant_id": x_tenant_id,
backend/api/services/prompt_builder.py DELETED
@@ -1,66 +0,0 @@
1
- class PromptBuilder:
2
- """
3
- Builds the final prompt sent to the LLM.
4
- """
5
-
6
- def __init__(self):
7
- pass
8
-
9
- def build(
10
- self,
11
- user_message: str,
12
- tool: str,
13
- rag_results: list[str] = None,
14
- web_results: list[dict] = None,
15
- redflag_info: dict = None,
16
- tenant_id: str = None
17
- ) -> str:
18
-
19
- # 1. Base system prompt
20
- system_prompt = (
21
- "You are VariaAI, an enterprise-grade MCP agent. "
22
- "Always follow tenant isolation: only use data belonging to the tenant. "
23
- "Be concise, correct, and safe.\n\n"
24
- )
25
-
26
- # 2. Insert admin red-flag context (if triggered)
27
- if tool == "admin":
28
- system_prompt += (
29
- "⚠️ SECURITY NOTICE:\n"
30
- "The user request appears sensitive or unsafe.\n"
31
- "Provide a safe response, avoid executing harmful instructions.\n\n"
32
- )
33
-
34
- # 3. Insert RAG context
35
- if tool == "rag" and rag_results:
36
- system_prompt += "📄 Relevant Knowledge Base Documents:\n"
37
- for i, chunk in enumerate(rag_results, start=1):
38
- system_prompt += f"({i}) {chunk}\n"
39
- system_prompt += "\n"
40
-
41
- # 4. Insert Web Search results
42
- if tool == "web" and web_results:
43
- system_prompt += "🌐 Web Search Results:\n"
44
- for i, item in enumerate(web_results, start=1):
45
- title = item.get("title")
46
- snippet = item.get("snippet")
47
- system_prompt += f"({i}) {title}\n {snippet}\n"
48
- system_prompt += "\n"
49
-
50
- # 5. Add tenant metadata (optional)
51
- if tenant_id:
52
- system_prompt += f"Tenant ID: {tenant_id}\n\n"
53
-
54
- # 6. Insert user query
55
- system_prompt += "🗣️ User Message:\n"
56
- system_prompt += user_message + "\n\n"
57
-
58
- # 7. Instructions for the model
59
- system_prompt += (
60
- "🎯 Your Task:\n"
61
- "- Answer using the context above.\n"
62
- "- If context is missing, say so.\n"
63
- "- Do not hallucinate facts.\n"
64
- )
65
-
66
- return system_prompt
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backend/api/storage/rules_store.py ADDED
@@ -0,0 +1,76 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import sqlite3
2
+ from pathlib import Path
3
+ from typing import List
4
+
5
+
6
+ class RulesStore:
7
+ """
8
+ Lightweight SQLite-backed store for admin rules.
9
+ Ensures data persists across restarts without requiring external DB setup.
10
+ """
11
+
12
+ def __init__(self):
13
+ root_dir = Path(__file__).resolve().parents[3] # points to project root
14
+ data_dir = root_dir / "data"
15
+ data_dir.mkdir(parents=True, exist_ok=True)
16
+ self.db_path = data_dir / "admin_rules.db"
17
+ self._init_db()
18
+
19
+ def _init_db(self):
20
+ with sqlite3.connect(self.db_path) as conn:
21
+ conn.execute(
22
+ """
23
+ CREATE TABLE IF NOT EXISTS admin_rules (
24
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
25
+ tenant_id TEXT NOT NULL,
26
+ rule TEXT NOT NULL,
27
+ UNIQUE(tenant_id, rule)
28
+ )
29
+ """
30
+ )
31
+ conn.commit()
32
+
33
+ def get_rules(self, tenant_id: str) -> List[str]:
34
+ with sqlite3.connect(self.db_path) as conn:
35
+ cursor = conn.execute(
36
+ "SELECT rule FROM admin_rules WHERE tenant_id = ? ORDER BY id ASC",
37
+ (tenant_id,),
38
+ )
39
+ return [row[0] for row in cursor.fetchall()]
40
+
41
+ def add_rule(self, tenant_id: str, rule: str) -> bool:
42
+ try:
43
+ with sqlite3.connect(self.db_path) as conn:
44
+ conn.execute(
45
+ "INSERT OR IGNORE INTO admin_rules (tenant_id, rule) VALUES (?, ?)",
46
+ (tenant_id, rule),
47
+ )
48
+ conn.commit()
49
+ return True
50
+ except sqlite3.Error:
51
+ return False
52
+
53
+ def add_rules_bulk(self, tenant_id: str, rules: List[str]) -> List[str]:
54
+ added = []
55
+ with sqlite3.connect(self.db_path) as conn:
56
+ for rule in rules:
57
+ try:
58
+ conn.execute(
59
+ "INSERT OR IGNORE INTO admin_rules (tenant_id, rule) VALUES (?, ?)",
60
+ (tenant_id, rule),
61
+ )
62
+ added.append(rule)
63
+ except sqlite3.Error:
64
+ continue
65
+ conn.commit()
66
+ return added
67
+
68
+ def delete_rule(self, tenant_id: str, rule: str) -> bool:
69
+ with sqlite3.connect(self.db_path) as conn:
70
+ cursor = conn.execute(
71
+ "DELETE FROM admin_rules WHERE tenant_id = ? AND rule = ?",
72
+ (tenant_id, rule),
73
+ )
74
+ conn.commit()
75
+ return cursor.rowcount > 0
76
+
backend/mcp_servers/placeholder.txt ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ This directory contains the MCP server implementations.
2
+ For the Hugging Face Space submission, only placeholder files are included.
3
+ The full MCP server code exists separately.
4
+
backend/workers/placeholder.txt ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ This directory contains Celery worker tasks for async processing.
2
+ For the Hugging Face Space submission, only placeholder files are included.
3
+ The full worker implementation exists separately.
4
+
data/admin_rules.db ADDED
Binary file (16.4 kB). View file
 
requirements.txt CHANGED
@@ -11,4 +11,6 @@ pytest-asyncio
11
  duckduckgo-search
12
  PyPDF2
13
  python-docx
14
- python-multipart
 
 
 
11
  duckduckgo-search
12
  PyPDF2
13
  python-docx
14
+ python-multipart
15
+ gradio>=4.0.0
16
+ requests>=2.31.0