From 692820fcfe4233bcceb0dcebe70992bfed043c46 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 31 Oct 2025 01:13:24 +0000 Subject: [PATCH 01/24] Initial plan From 1e4bc1212eb7454e83628fd1cd3fb03dbbe0f412 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 31 Oct 2025 01:20:16 +0000 Subject: [PATCH 02/24] Add compliance_level field to MCP server config and API responses Co-authored-by: garland3 <1162675+garland3@users.noreply.github.com> --- backend/domain/rag_mcp_service.py | 6 ++- backend/modules/config/manager.py | 1 + backend/routes/config_routes.py | 9 ++-- config/defaults/mcp-rag.json | 3 +- config/defaults/mcp.json | 82 +++++++++++++++++-------------- 5 files changed, 58 insertions(+), 43 deletions(-) diff --git a/backend/domain/rag_mcp_service.py b/backend/domain/rag_mcp_service.py index ff5c1d5..db44138 100644 --- a/backend/domain/rag_mcp_service.py +++ b/backend/domain/rag_mcp_service.py @@ -179,17 +179,21 @@ async def discover_servers(self, username: str) -> List[Dict[str, Any]]: # New: include per-resource groups when provided "groups": list(r.get("groups", [])) if isinstance(r.get("groups"), list) else None, "selected": bool(r.get("defaultSelected", False)), + # Include compliance_level from resource or inherit from server + "complianceLevel": r.get("complianceLevel") if r.get("complianceLevel") else None, }) - # Optional config-driven icon/name + # Optional config-driven icon/name and compliance level cfg = (self.mcp_manager.available_tools.get(server) or {}).get("config", {}) display_name = cfg.get("displayName") or server icon = (cfg.get("ui") or {}).get("icon") if isinstance(cfg.get("ui"), dict) else None + compliance_level = cfg.get("compliance_level") rag_servers.append({ "server": server, "displayName": display_name, "icon": icon, + "complianceLevel": compliance_level, "sources": ui_sources, }) except Exception as e: diff --git a/backend/modules/config/manager.py b/backend/modules/config/manager.py index 75bc164..ffa05d0 100644 --- a/backend/modules/config/manager.py +++ b/backend/modules/config/manager.py @@ -61,6 +61,7 @@ class MCPServerConfig(BaseModel): url: Optional[str] = None # URL for HTTP servers type: str = "stdio" # Server type: "stdio" or "http" (deprecated, use transport) transport: Optional[str] = None # Explicit transport: "stdio", "http", "sse" - takes priority over auto-detection + compliance_level: Optional[str] = None # Compliance/security level (e.g., "SOC2", "HIPAA", "Public") class MCPConfig(BaseModel): diff --git a/backend/routes/config_routes.py b/backend/routes/config_routes.py index c92d251..d384f67 100644 --- a/backend/routes/config_routes.py +++ b/backend/routes/config_routes.py @@ -102,7 +102,8 @@ async def get_config(current_user: str = Depends(get_current_user)): 'is_exclusive': False, 'author': 'Chat UI Team', 'short_description': 'Visual content display', - 'help_email': 'support@chatui.example.com' + 'help_email': 'support@chatui.example.com', + 'compliance_level': 'Public' }) elif server_name in mcp_manager.available_tools: server_tools = mcp_manager.available_tools[server_name]['tools'] @@ -118,7 +119,8 @@ async def get_config(current_user: str = Depends(get_current_user)): 'is_exclusive': server_config.get('is_exclusive', False), 'author': server_config.get('author', 'Unknown'), 'short_description': server_config.get('short_description', server_config.get('description', f'{server_name} tools')), - 'help_email': server_config.get('help_email', '') + 'help_email': server_config.get('help_email', ''), + 'compliance_level': server_config.get('compliance_level') }) # Collect prompts from this server if available @@ -133,7 +135,8 @@ async def get_config(current_user: str = Depends(get_current_user)): 'description': f'{server_name} custom prompts', 'author': server_config.get('author', 'Unknown'), 'short_description': server_config.get('short_description', f'{server_name} custom prompts'), - 'help_email': server_config.get('help_email', '') + 'help_email': server_config.get('help_email', ''), + 'compliance_level': server_config.get('compliance_level') }) # Read help page configuration (supports new config directory layout + legacy paths) diff --git a/config/defaults/mcp-rag.json b/config/defaults/mcp-rag.json index 7a0d6f5..0823559 100644 --- a/config/defaults/mcp-rag.json +++ b/config/defaults/mcp-rag.json @@ -7,6 +7,7 @@ "description": "Fleet RAG server: discover resources and retrieve locations/meta-data for corporate cars in use by employees.", "author": "Chat UI Team", "short_description": "Corporate cars location & metadata", - "help_email": "support@chatui.example.com" + "help_email": "support@chatui.example.com", + "compliance_level": "SOC2" } } diff --git a/config/defaults/mcp.json b/config/defaults/mcp.json index 0d37c1e..9a7d518 100644 --- a/config/defaults/mcp.json +++ b/config/defaults/mcp.json @@ -1,5 +1,5 @@ -{ - +{ + "calculator": { "command": ["python", "mcp/calculator/main.py"], "cwd": "backend", @@ -8,20 +8,22 @@ "description": "Evaluate mathematical expressions, perform calculations with basic arithmetic, trigonometry, and logarithms", "author": "Chat UI Team", "short_description": "Mathematical calculator", - "help_email": "support@chatui.example.com" - }, - - - "thinking": { - "command": ["python", "mcp/thinking/main.py"], - "cwd": "backend", - "groups": ["users"], - "is_exclusive": false, - "description": "Structured thinking and problem analysis tool", - "author": "Chat UI Team", - "short_description": "Structured problem analysis", - "help_email": "support@chatui.example.com" - }, + "help_email": "support@chatui.example.com", + "compliance_level": "Public" + }, + + + "thinking": { + "command": ["python", "mcp/thinking/main.py"], + "cwd": "backend", + "groups": ["users"], + "is_exclusive": false, + "description": "Structured thinking and problem analysis tool", + "author": "Chat UI Team", + "short_description": "Structured problem analysis", + "help_email": "support@chatui.example.com", + "compliance_level": "Public" + }, "pdfbasic": { "command": ["python", "mcp/pdfbasic/main.py"], "cwd": "backend", @@ -30,18 +32,20 @@ "description": "Extract and analyze text content from PDF documents, search within PDFs, and summarize content", "author": "Chat UI Team", "short_description": "PDF text extraction and analysis", - "help_email": "support@chatui.example.com" + "help_email": "support@chatui.example.com", + "compliance_level": "SOC2" }, - "ui-demo": { - "command": ["python", "mcp/ui-demo/main.py"], - "cwd": "backend", - "groups": ["users"], - "is_exclusive": false, - "description": "Demo server showcasing custom UI modification capabilities", - "author": "Chat UI Team", - "short_description": "UI customization demo", - "help_email": "support@chatui.example.com" - }, + "ui-demo": { + "command": ["python", "mcp/ui-demo/main.py"], + "cwd": "backend", + "groups": ["users"], + "is_exclusive": false, + "description": "Demo server showcasing custom UI modification capabilities", + "author": "Chat UI Team", + "short_description": "UI customization demo", + "help_email": "support@chatui.example.com", + "compliance_level": "Public" + }, "code-executor": { "command": ["python", "mcp/code-executor/main.py"], "cwd": "backend", @@ -50,17 +54,19 @@ "description": "Execute Python, JavaScript, and other code snippets in a secure sandboxed environment with safety validation", "author": "Chat UI Team", "short_description": "Secure code execution sandbox", - "help_email": "support@chatui.example.com" - }, - "prompts": { - "command": ["python", "mcp/prompts/main.py"], - "cwd": "backend", - "groups": ["users"], - "is_exclusive": false, - "description": "Specialized system prompts for AI behavior modification", - "author": "Chat UI Team", - "short_description": "AI behavior prompts", - "help_email": "support@chatui.example.com" + "help_email": "support@chatui.example.com", + "compliance_level": "SOC2" + }, + "prompts": { + "command": ["python", "mcp/prompts/main.py"], + "cwd": "backend", + "groups": ["users"], + "is_exclusive": false, + "description": "Specialized system prompts for AI behavior modification", + "author": "Chat UI Team", + "short_description": "AI behavior prompts", + "help_email": "support@chatui.example.com", + "compliance_level": "Public" } } \ No newline at end of file From c3ddb2781780dcf64484c99ae76666b97b9469e1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 31 Oct 2025 01:24:50 +0000 Subject: [PATCH 03/24] Add frontend compliance level filtering for tools and data sources Co-authored-by: garland3 <1162675+garland3@users.noreply.github.com> --- backend/application/chat/agent/react_loop.py | 2 +- .../application/chat/agent/think_act_loop.py | 1 - backend/application/chat/service.py | 4 -- .../application/chat/utilities/file_utils.py | 2 +- .../application/chat/utilities/tool_utils.py | 2 +- backend/core/utils.py | 2 +- .../transport/websocket_connection_adapter.py | 1 - backend/interfaces/llm.py | 1 - backend/mcp/code-executor/execution_engine.py | 2 +- backend/mcp/code-executor/main.py | 2 +- .../mcp/code-executor/result_processing.py | 3 +- backend/mcp/file_size_test/main.py | 2 +- backend/mcp/pptx_generator/main.py | 1 - backend/mcp/progress_demo/main.py | 1 - backend/mcp/prompts/main.py | 2 +- backend/mcp/ui-demo/main.py | 1 - backend/modules/config/cli.py | 3 +- backend/modules/config/manager.py | 2 +- backend/modules/file_storage/cli.py | 10 ++-- backend/modules/llm/cli.py | 9 +-- backend/modules/llm/litellm_caller.py | 5 +- backend/modules/llm/models.py | 2 +- backend/modules/mcp_tools/client.py | 48 ++++++++-------- backend/modules/rag/client.py | 3 +- backend/routes/admin_routes.py | 2 +- backend/routes/config_routes.py | 4 +- backend/routes/files_routes.py | 2 +- backend/tests/test_config_manager_paths.py | 1 - backend/tests/test_core_auth.py | 1 - backend/tests/test_core_utils.py | 1 - backend/tests/test_file_manager_unit.py | 1 - .../test_mcp_prompt_override_system_prompt.py | 1 - backend/tests/test_prompt_risk_and_acl.py | 1 - backend/tests/test_rag_mcp_service.py | 1 - backend/tests/test_routes_files_health.py | 1 - .../tests/test_security_capability_tokens.py | 1 - .../test_security_headers_and_filename.py | 1 - frontend/src/components/ToolsPanel.jsx | 56 +++++++++++++++++-- frontend/src/contexts/ChatContext.jsx | 2 + frontend/src/contexts/MarketplaceContext.jsx | 21 ++++++- frontend/src/hooks/chat/useSelections.js | 5 +- 41 files changed, 124 insertions(+), 89 deletions(-) diff --git a/backend/application/chat/agent/react_loop.py b/backend/application/chat/agent/react_loop.py index 016ee3f..a69ff96 100644 --- a/backend/application/chat/agent/react_loop.py +++ b/backend/application/chat/agent/react_loop.py @@ -9,7 +9,7 @@ from modules.prompts.prompt_provider import PromptProvider from .protocols import AgentContext, AgentEvent, AgentEventHandler, AgentLoopProtocol, AgentResult -from ..utilities import file_utils, notification_utils, error_utils, tool_utils +from ..utilities import file_utils, error_utils, tool_utils from domain.messages.models import ToolResult diff --git a/backend/application/chat/agent/think_act_loop.py b/backend/application/chat/agent/think_act_loop.py index 3447dc3..7a9b672 100644 --- a/backend/application/chat/agent/think_act_loop.py +++ b/backend/application/chat/agent/think_act_loop.py @@ -1,6 +1,5 @@ from __future__ import annotations -import asyncio from typing import Any, Dict, List, Optional from interfaces.llm import LLMProtocol, LLMResponse diff --git a/backend/application/chat/service.py b/backend/application/chat/service.py index 1cea4bc..053a46b 100644 --- a/backend/application/chat/service.py +++ b/backend/application/chat/service.py @@ -6,13 +6,10 @@ from typing import Any, Dict, List, Optional, Callable, Awaitable from uuid import UUID -from domain.errors import SessionError, ValidationError from domain.messages.models import ( - ConversationHistory, Message, MessageRole, MessageType, - ToolCall, ToolResult ) from domain.sessions.models import Session @@ -26,7 +23,6 @@ from .utilities import tool_utils, file_utils, notification_utils, error_utils from .agent import AgentLoopFactory from .agent.protocols import AgentContext, AgentEvent -from core.prompt_risk import calculate_prompt_injection_risk, log_high_risk_event from core.auth_utils import create_authorization_manager logger = logging.getLogger(__name__) diff --git a/backend/application/chat/utilities/file_utils.py b/backend/application/chat/utilities/file_utils.py index 98cf94e..3c6a889 100644 --- a/backend/application/chat/utilities/file_utils.py +++ b/backend/application/chat/utilities/file_utils.py @@ -319,7 +319,7 @@ async def ingest_v2_artifacts( mime_type = artifact.get("mime") if not name or not b64_content: - logger.warning(f"Skipping artifact with missing name or content") + logger.warning("Skipping artifact with missing name or content") continue files_to_upload.append({ diff --git a/backend/application/chat/utilities/tool_utils.py b/backend/application/chat/utilities/tool_utils.py index 35b3a67..519f0fe 100644 --- a/backend/application/chat/utilities/tool_utils.py +++ b/backend/application/chat/utilities/tool_utils.py @@ -9,7 +9,7 @@ import logging from typing import Any, Dict, List, Optional, Callable, Awaitable -from domain.messages.models import ToolCall, ToolResult, Message, MessageRole +from domain.messages.models import ToolCall, ToolResult from interfaces.llm import LLMResponse from core.capabilities import create_download_url from .notification_utils import _sanitize_filename_value # reuse same filename sanitizer for UI args diff --git a/backend/core/utils.py b/backend/core/utils.py index 653dc05..05755aa 100644 --- a/backend/core/utils.py +++ b/backend/core/utils.py @@ -3,7 +3,7 @@ """ import logging -from fastapi import Depends, Request +from fastapi import Request logger = logging.getLogger(__name__) diff --git a/backend/infrastructure/transport/websocket_connection_adapter.py b/backend/infrastructure/transport/websocket_connection_adapter.py index 4b2ba7a..1f8fef8 100644 --- a/backend/infrastructure/transport/websocket_connection_adapter.py +++ b/backend/infrastructure/transport/websocket_connection_adapter.py @@ -4,7 +4,6 @@ from fastapi import WebSocket -from interfaces.transport import ChatConnectionProtocol class WebSocketConnectionAdapter: diff --git a/backend/interfaces/llm.py b/backend/interfaces/llm.py index 8dab509..f52f1ec 100644 --- a/backend/interfaces/llm.py +++ b/backend/interfaces/llm.py @@ -2,7 +2,6 @@ from typing import Any, Dict, List, Optional, Protocol, runtime_checkable -from domain.messages.models import ToolCall class LLMResponse: diff --git a/backend/mcp/code-executor/execution_engine.py b/backend/mcp/code-executor/execution_engine.py index fb7e5f9..375e146 100644 --- a/backend/mcp/code-executor/execution_engine.py +++ b/backend/mcp/code-executor/execution_engine.py @@ -74,7 +74,7 @@ def execute_code_safely(script_path: Path, timeout: int = 30) -> Dict[str, Any]: "error_type": "SubprocessError" } - except subprocess.TimeoutExpired as e: + except subprocess.TimeoutExpired: error_msg = f"Code execution timed out after {timeout} seconds" logger.error(error_msg) logger.error(f"Traceback: {traceback.format_exc()}") diff --git a/backend/mcp/code-executor/main.py b/backend/mcp/code-executor/main.py index 4847163..34e631e 100644 --- a/backend/mcp/code-executor/main.py +++ b/backend/mcp/code-executor/main.py @@ -10,7 +10,7 @@ import time import traceback from pathlib import Path -from typing import Any, Dict, List, Annotated, Optional +from typing import Any, Dict, Annotated, Optional import requests from fastmcp import FastMCP diff --git a/backend/mcp/code-executor/result_processing.py b/backend/mcp/code-executor/result_processing.py index bbd7ed5..68352a0 100644 --- a/backend/mcp/code-executor/result_processing.py +++ b/backend/mcp/code-executor/result_processing.py @@ -5,11 +5,10 @@ """ import base64 -import json import logging import traceback from pathlib import Path -from typing import Dict, Any, List +from typing import Dict, List logger = logging.getLogger(__name__) diff --git a/backend/mcp/file_size_test/main.py b/backend/mcp/file_size_test/main.py index f23d7d1..81bba62 100644 --- a/backend/mcp/file_size_test/main.py +++ b/backend/mcp/file_size_test/main.py @@ -228,7 +228,7 @@ def get_file_size( logger.debug(f"Successfully downloaded file content, length: {len(file_bytes)} bytes") else: # Assume it's base64-encoded data - logger.debug(f"Treating input as base64 data, attempting to decode") + logger.debug("Treating input as base64 data, attempting to decode") logger.info("Decoding base64 file data") file_bytes = base64.b64decode(filename) logger.debug(f"Successfully decoded base64 data, length: {len(file_bytes)} bytes") diff --git a/backend/mcp/pptx_generator/main.py b/backend/mcp/pptx_generator/main.py index 4e1e742..6ad347b 100644 --- a/backend/mcp/pptx_generator/main.py +++ b/backend/mcp/pptx_generator/main.py @@ -26,7 +26,6 @@ from pptx import Presentation from pptx.util import Inches, Pt from pptx.enum.text import PP_ALIGN -from pptx.dml.color import RGBColor from PIL import Image diff --git a/backend/mcp/progress_demo/main.py b/backend/mcp/progress_demo/main.py index 1b5e33d..61aff88 100644 --- a/backend/mcp/progress_demo/main.py +++ b/backend/mcp/progress_demo/main.py @@ -8,7 +8,6 @@ from __future__ import annotations import asyncio -from typing import Optional from fastmcp import FastMCP, Context diff --git a/backend/mcp/prompts/main.py b/backend/mcp/prompts/main.py index e670d27..df5d324 100644 --- a/backend/mcp/prompts/main.py +++ b/backend/mcp/prompts/main.py @@ -6,7 +6,7 @@ from typing import Dict, Any from fastmcp import FastMCP -from fastmcp.prompts.prompt import Message, PromptMessage, TextContent +from fastmcp.prompts.prompt import PromptMessage, TextContent # Initialize the MCP server mcp = FastMCP("Prompts") diff --git a/backend/mcp/ui-demo/main.py b/backend/mcp/ui-demo/main.py index d2e7713..77fba12 100644 --- a/backend/mcp/ui-demo/main.py +++ b/backend/mcp/ui-demo/main.py @@ -6,7 +6,6 @@ fields to modify the UI. """ -import json import os from typing import Dict, Any from fastmcp import FastMCP diff --git a/backend/modules/config/cli.py b/backend/modules/config/cli.py index df9c2cf..bc3b5de 100644 --- a/backend/modules/config/cli.py +++ b/backend/modules/config/cli.py @@ -11,7 +11,6 @@ import json import logging import sys -from typing import Any, Dict from .manager import ConfigManager @@ -87,7 +86,7 @@ def list_servers(args) -> None: if server.groups: print(f" Groups: {', '.join(server.groups)}") if server.is_exclusive: - print(f" Exclusive: ⚠️ Yes") + print(" Exclusive: ⚠️ Yes") print() diff --git a/backend/modules/config/manager.py b/backend/modules/config/manager.py index ffa05d0..4ac8900 100644 --- a/backend/modules/config/manager.py +++ b/backend/modules/config/manager.py @@ -12,7 +12,7 @@ import logging import os from pathlib import Path -from typing import Any, Dict, List, Optional, Union +from typing import Any, Dict, List, Optional import yaml from pydantic import BaseModel, Field, field_validator, AliasChoices diff --git a/backend/modules/file_storage/cli.py b/backend/modules/file_storage/cli.py index 300b83a..9c603dd 100644 --- a/backend/modules/file_storage/cli.py +++ b/backend/modules/file_storage/cli.py @@ -10,11 +10,9 @@ import argparse import base64 -import json import logging import sys from pathlib import Path -from typing import Optional from .s3_client import S3StorageClient from .manager import FileManager @@ -56,7 +54,7 @@ async def upload_file(args) -> None: source_type=args.source_type ) - print(f"✅ File uploaded successfully!") + print("✅ File uploaded successfully!") print(f" S3 Key: {result['key']}") print(f" Size: {result.get('size', 'unknown')} bytes") print(f" Content Type: {result.get('content_type', 'unknown')}") @@ -157,7 +155,7 @@ async def download_file(args) -> None: with open(output_path, 'wb') as f: f.write(content) - print(f"✅ File downloaded successfully!") + print("✅ File downloaded successfully!") print(f" Saved to: {output_path}") print(f" Size: {len(content)} bytes") @@ -210,14 +208,14 @@ async def get_stats(args) -> None: s3_client = S3StorageClient() stats = await s3_client.get_user_stats(args.user_email) - print(f"\n📈 File Statistics:\n") + print("\n📈 File Statistics:\n") print(f" 📁 Total Files: {stats.get('total_files', 0)}") print(f" 💾 Total Size: {stats.get('total_size_bytes', 0)} bytes") print(f" 📤 User Files: {stats.get('user_files', 0)}") print(f" 🔧 Tool Files: {stats.get('tool_files', 0)}") if 'file_types' in stats: - print(f"\n📊 By File Type:") + print("\n📊 By File Type:") for file_type, count in stats['file_types'].items(): print(f" {file_type}: {count}") diff --git a/backend/modules/llm/cli.py b/backend/modules/llm/cli.py index 4a421c5..4642f8f 100644 --- a/backend/modules/llm/cli.py +++ b/backend/modules/llm/cli.py @@ -8,13 +8,10 @@ """ import argparse -import json import logging import sys -from typing import List, Dict from .litellm_caller import LiteLLMCaller -from .models import LLMResponse # Set up logging for CLI logging.basicConfig(level=logging.INFO, format='%(levelname)s: %(message)s') @@ -231,15 +228,15 @@ def validate_model(args) -> None: import os api_key = os.path.expandvars(model_config.api_key) if api_key and not api_key.startswith("${"): - print(f" ✅ API Key: Configured") + print(" ✅ API Key: Configured") else: - print(f" ❌ API Key: Missing or not resolved") + print(" ❌ API Key: Missing or not resolved") # Check extra headers if model_config.extra_headers: print(f" ✅ Extra Headers: {list(model_config.extra_headers.keys())}") else: - print(f" ℹ️ Extra Headers: None") + print(" ℹ️ Extra Headers: None") print(f"\n🎉 Model '{args.model}' configuration looks valid!") diff --git a/backend/modules/llm/litellm_caller.py b/backend/modules/llm/litellm_caller.py index 52c5ba3..238063f 100644 --- a/backend/modules/llm/litellm_caller.py +++ b/backend/modules/llm/litellm_caller.py @@ -11,15 +11,12 @@ fallbacks, cost tracking, and provider-specific optimizations. """ -import asyncio -import json import logging import os from typing import Any, Dict, List, Optional -from dataclasses import dataclass import litellm -from litellm import completion, acompletion +from litellm import acompletion from .models import LLMResponse logger = logging.getLogger(__name__) diff --git a/backend/modules/llm/models.py b/backend/modules/llm/models.py index 8b11e5d..de961fb 100644 --- a/backend/modules/llm/models.py +++ b/backend/modules/llm/models.py @@ -2,7 +2,7 @@ Data models for LLM responses and related structures. """ -from dataclasses import dataclass, field +from dataclasses import dataclass from typing import Dict, List, Optional diff --git a/backend/modules/mcp_tools/client.py b/backend/modules/mcp_tools/client.py index 979cc95..6726460 100644 --- a/backend/modules/mcp_tools/client.py +++ b/backend/modules/mcp_tools/client.py @@ -178,17 +178,17 @@ async def _initialize_single_client(self, server_name: str, config: Dict[str, An logger.error(f"🔍 DEBUG: Connection failed for HTTP/SSE server '{server_name}'") logger.error(f" → URL: {config.get('url', 'Not specified')}") logger.error(f" → Transport: {transport_type}") - logger.error(f" → Check if server is running and accessible") + logger.error(" → Check if server is running and accessible") else: logger.error(f"🔍 DEBUG: STDIO connection failed for server '{server_name}'") logger.error(f" → Command: {config.get('command', 'Not specified')}") logger.error(f" → CWD: {config.get('cwd', 'Not specified')}") - logger.error(f" → Check if command exists and is executable") + logger.error(" → Check if command exists and is executable") elif "timeout" in str(e).lower(): logger.error(f"🔍 DEBUG: Timeout connecting to server '{server_name}'") - logger.error(f" → Server may be slow to start or overloaded") - logger.error(f" → Consider increasing timeout or checking server health") + logger.error(" → Server may be slow to start or overloaded") + logger.error(" → Consider increasing timeout or checking server health") elif "permission" in str(e).lower() or "access" in str(e).lower(): logger.error(f"🔍 DEBUG: Permission error for server '{server_name}'") @@ -199,13 +199,13 @@ async def _initialize_single_client(self, server_name: str, config: Dict[str, An elif "module" in str(e).lower() or "import" in str(e).lower(): logger.error(f"🔍 DEBUG: Import/module error for server '{server_name}'") - logger.error(f" → Check if required dependencies are installed") - logger.error(f" → Check Python path and virtual environment") + logger.error(" → Check if required dependencies are installed") + logger.error(" → Check Python path and virtual environment") elif "json" in str(e).lower() or "decode" in str(e).lower(): logger.error(f"🔍 DEBUG: JSON/protocol error for server '{server_name}'") - logger.error(f" → Server may not be MCP-compatible") - logger.error(f" → Check server output format") + logger.error(" → Server may not be MCP-compatible") + logger.error(" → Check server output format") else: # Generic debugging info @@ -243,10 +243,10 @@ async def initialize_clients(self): else: logger.warning(f"⚠ Failed to initialize client for {server_name}") - logger.info(f"=== CLIENT INITIALIZATION COMPLETE ===") + logger.info("=== CLIENT INITIALIZATION COMPLETE ===") logger.info(f"Successfully initialized {len(self.clients)} clients: {list(self.clients.keys())}") logger.info(f"Failed to initialize: {set(self.servers_config.keys()) - set(self.clients.keys())}") - logger.info(f"=== END CLIENT INITIALIZATION SUMMARY ===") + logger.info("=== END CLIENT INITIALIZATION SUMMARY ===") async def _discover_tools_for_server(self, server_name: str, client: Client) -> Dict[str, Any]: """Discover tools for a single server. Returns server tools data.""" @@ -282,16 +282,16 @@ async def _discover_tools_for_server(self, server_name: str, client: Client) -> # Targeted debugging for tool discovery errors if "connection" in str(e).lower() or "refused" in str(e).lower(): logger.error(f"🔍 DEBUG: Connection lost during tool discovery for '{server_name}'") - logger.error(f" → Server may have crashed or disconnected") - logger.error(f" → Check server logs for startup errors") + logger.error(" → Server may have crashed or disconnected") + logger.error(" → Check server logs for startup errors") elif "timeout" in str(e).lower(): logger.error(f"🔍 DEBUG: Timeout during tool discovery for '{server_name}'") - logger.error(f" → Server is slow to respond to list_tools() request") - logger.error(f" → Server may be overloaded or hanging") + logger.error(" → Server is slow to respond to list_tools() request") + logger.error(" → Server may be overloaded or hanging") elif "json" in str(e).lower() or "decode" in str(e).lower(): logger.error(f"🔍 DEBUG: Protocol error during tool discovery for '{server_name}'") - logger.error(f" → Server returned invalid MCP response") - logger.error(f" → Check if server implements MCP protocol correctly") + logger.error(" → Server returned invalid MCP response") + logger.error(" → Check if server implements MCP protocol correctly") else: logger.error(f"🔍 DEBUG: Generic tool discovery error for '{server_name}'") logger.error(f" → Client object: {client}") @@ -336,13 +336,13 @@ async def discover_tools(self): else: self.available_tools[server_name] = result - logger.info(f"=== TOOL DISCOVERY COMPLETE ===") - logger.info(f"Final available_tools summary:") + logger.info("=== TOOL DISCOVERY COMPLETE ===") + logger.info("Final available_tools summary:") for server_name, server_data in self.available_tools.items(): tool_count = len(server_data['tools']) tool_names = [tool.name for tool in server_data['tools']] logger.info(f" {server_name}: {tool_count} tools {tool_names}") - logger.info(f"=== END TOOL DISCOVERY SUMMARY ===") + logger.info("=== END TOOL DISCOVERY SUMMARY ===") async def _discover_prompts_for_server(self, server_name: str, client: Client) -> Dict[str, Any]: """Discover prompts for a single server. Returns server prompts data.""" @@ -377,13 +377,13 @@ async def _discover_prompts_for_server(self, server_name: str, client: Client) - # Targeted debugging for prompt discovery errors if "connection" in str(e).lower() or "refused" in str(e).lower(): logger.error(f"🔍 DEBUG: Connection lost during prompt discovery for '{server_name}'") - logger.error(f" → Server may have crashed or disconnected") + logger.error(" → Server may have crashed or disconnected") elif "timeout" in str(e).lower(): logger.error(f"🔍 DEBUG: Timeout during prompt discovery for '{server_name}'") - logger.error(f" → Server is slow to respond to list_prompts() request") + logger.error(" → Server is slow to respond to list_prompts() request") elif "json" in str(e).lower() or "decode" in str(e).lower(): logger.error(f"🔍 DEBUG: Protocol error during prompt discovery for '{server_name}'") - logger.error(f" → Server returned invalid MCP response for prompts") + logger.error(" → Server returned invalid MCP response for prompts") else: logger.error(f"🔍 DEBUG: Generic prompt discovery error for '{server_name}'") @@ -423,14 +423,14 @@ async def discover_prompts(self): else: self.available_prompts[server_name] = result - logger.info(f"=== PROMPT DISCOVERY COMPLETE ===") + logger.info("=== PROMPT DISCOVERY COMPLETE ===") total_prompts = sum(len(server_data['prompts']) for server_data in self.available_prompts.values()) logger.info(f"Total prompts discovered: {total_prompts}") for server_name, server_data in self.available_prompts.items(): prompt_count = len(server_data['prompts']) prompt_names = [prompt.name for prompt in server_data['prompts']] logger.info(f" {server_name}: {prompt_count} prompts {prompt_names}") - logger.info(f"=== END PROMPT DISCOVERY SUMMARY ===") + logger.info("=== END PROMPT DISCOVERY SUMMARY ===") def get_server_groups(self, server_name: str) -> List[str]: """Get required groups for a server.""" diff --git a/backend/modules/rag/client.py b/backend/modules/rag/client.py index 119dfac..360837f 100644 --- a/backend/modules/rag/client.py +++ b/backend/modules/rag/client.py @@ -1,8 +1,7 @@ """RAG Client for integrating with RAG mock endpoint.""" import logging -import os -from typing import Dict, List, Optional, Any +from typing import Dict, List, Optional from fastapi import HTTPException from pydantic import BaseModel diff --git a/backend/routes/admin_routes.py b/backend/routes/admin_routes.py index c77ca12..cd113ab 100644 --- a/backend/routes/admin_routes.py +++ b/backend/routes/admin_routes.py @@ -11,7 +11,7 @@ from typing import Any, Dict, List, Optional import yaml -from fastapi import APIRouter, Depends, HTTPException, Request +from fastapi import APIRouter, Depends, HTTPException from fastapi.responses import FileResponse from pydantic import BaseModel diff --git a/backend/routes/config_routes.py b/backend/routes/config_routes.py index d384f67..06b41eb 100644 --- a/backend/routes/config_routes.py +++ b/backend/routes/config_routes.py @@ -1,7 +1,6 @@ """Configuration API routes.""" import logging -from typing import Optional from fastapi import APIRouter, Depends @@ -193,12 +192,11 @@ async def get_config(current_user: str = Depends(get_current_user)): "tools": tools_info, # Only authorized servers are included "prompts": prompts_info, # Available prompts from authorized servers "data_sources": rag_data_sources, # RAG data sources for the user - "rag_servers": rag_servers, # Optional richer structure for RAG UI + "rag_servers": rag_servers, # Optional richer structure for RAG UI "user": current_user, "is_in_admin_group": is_user_in_group(current_user, app_settings.admin_group), "active_sessions": 0, # TODO: Implement session counting in ChatService "authorized_servers": authorized_servers, # Optional: expose for debugging - "rag_servers": rag_servers, # Optional richer structure for RAG UI "agent_mode_available": app_settings.agent_mode_available, # Whether agent mode UI should be shown "banner_enabled": app_settings.banner_enabled, # Whether banner system is enabled "help_config": help_config, # Help page configuration from help-config.json diff --git a/backend/routes/files_routes.py b/backend/routes/files_routes.py index d89ee39..f30c2f8 100644 --- a/backend/routes/files_routes.py +++ b/backend/routes/files_routes.py @@ -8,7 +8,7 @@ import logging from typing import List, Dict, Any, Optional import re -from fastapi import APIRouter, Depends, HTTPException, Request, Response +from fastapi import APIRouter, Depends, HTTPException, Response from fastapi import Query import base64 from pydantic import BaseModel, Field diff --git a/backend/tests/test_config_manager_paths.py b/backend/tests/test_config_manager_paths.py index eeb1586..9350c30 100644 --- a/backend/tests/test_config_manager_paths.py +++ b/backend/tests/test_config_manager_paths.py @@ -1,4 +1,3 @@ -from pathlib import Path from modules.config.manager import ConfigManager diff --git a/backend/tests/test_core_auth.py b/backend/tests/test_core_auth.py index 44a417a..ac96a82 100644 --- a/backend/tests/test_core_auth.py +++ b/backend/tests/test_core_auth.py @@ -1,4 +1,3 @@ -import pytest from modules.config.manager import config_manager diff --git a/backend/tests/test_core_utils.py b/backend/tests/test_core_utils.py index 6e8bac9..3d68fb5 100644 --- a/backend/tests/test_core_utils.py +++ b/backend/tests/test_core_utils.py @@ -1,4 +1,3 @@ -import asyncio from types import SimpleNamespace import pytest diff --git a/backend/tests/test_file_manager_unit.py b/backend/tests/test_file_manager_unit.py index cd668e8..e6385e5 100644 --- a/backend/tests/test_file_manager_unit.py +++ b/backend/tests/test_file_manager_unit.py @@ -1,4 +1,3 @@ -import pytest from modules.file_storage.manager import FileManager diff --git a/backend/tests/test_mcp_prompt_override_system_prompt.py b/backend/tests/test_mcp_prompt_override_system_prompt.py index 648bbb8..227d1af 100644 --- a/backend/tests/test_mcp_prompt_override_system_prompt.py +++ b/backend/tests/test_mcp_prompt_override_system_prompt.py @@ -1,4 +1,3 @@ -import asyncio import pytest from modules.mcp_tools.client import MCPToolManager diff --git a/backend/tests/test_prompt_risk_and_acl.py b/backend/tests/test_prompt_risk_and_acl.py index e21867e..a359f32 100644 --- a/backend/tests/test_prompt_risk_and_acl.py +++ b/backend/tests/test_prompt_risk_and_acl.py @@ -1,6 +1,5 @@ import os import json -import tempfile import types import pytest diff --git a/backend/tests/test_rag_mcp_service.py b/backend/tests/test_rag_mcp_service.py index c275e02..c4bcc90 100644 --- a/backend/tests/test_rag_mcp_service.py +++ b/backend/tests/test_rag_mcp_service.py @@ -1,4 +1,3 @@ -import asyncio import types from typing import Any, Dict, List diff --git a/backend/tests/test_routes_files_health.py b/backend/tests/test_routes_files_health.py index be641bc..1aa3062 100644 --- a/backend/tests/test_routes_files_health.py +++ b/backend/tests/test_routes_files_health.py @@ -1,4 +1,3 @@ -import pytest from starlette.testclient import TestClient from main import app diff --git a/backend/tests/test_security_capability_tokens.py b/backend/tests/test_security_capability_tokens.py index 4740823..ce8c553 100644 --- a/backend/tests/test_security_capability_tokens.py +++ b/backend/tests/test_security_capability_tokens.py @@ -1,4 +1,3 @@ -import time import base64 from starlette.testclient import TestClient diff --git a/backend/tests/test_security_headers_and_filename.py b/backend/tests/test_security_headers_and_filename.py index 7060985..7795e60 100644 --- a/backend/tests/test_security_headers_and_filename.py +++ b/backend/tests/test_security_headers_and_filename.py @@ -1,4 +1,3 @@ -import pytest from starlette.testclient import TestClient from main import app diff --git a/frontend/src/components/ToolsPanel.jsx b/frontend/src/components/ToolsPanel.jsx index 3c66d1c..b77eb6b 100644 --- a/frontend/src/components/ToolsPanel.jsx +++ b/frontend/src/components/ToolsPanel.jsx @@ -1,4 +1,4 @@ -import { X, Trash2, Search, Plus, Wrench, ChevronDown, ChevronUp } from 'lucide-react' +import { X, Trash2, Search, Plus, Wrench, ChevronDown, ChevronUp, Shield } from 'lucide-react' import { useNavigate } from 'react-router-dom' import { useState, useEffect } from 'react' import { useChat } from '../contexts/ChatContext' @@ -19,13 +19,27 @@ const ToolsPanel = ({ isOpen, onClose }) => { removePrompts, toolChoiceRequired, setToolChoiceRequired, - clearToolsAndPrompts + clearToolsAndPrompts, + complianceLevelFilter, + setComplianceLevelFilter, + tools: allTools, + prompts: allPrompts } = useChat() - const { getFilteredTools, getFilteredPrompts } = useMarketplace() + const { getComplianceFilteredTools, getComplianceFilteredPrompts } = useMarketplace() - // Use filtered tools and prompts instead of all tools - const tools = getFilteredTools() - const prompts = getFilteredPrompts() + // Use compliance-filtered tools and prompts + const tools = getComplianceFilteredTools(complianceLevelFilter) + const prompts = getComplianceFilteredPrompts(complianceLevelFilter) + + // Extract unique compliance levels from all available tools and prompts + const availableComplianceLevels = new Set() + allTools.forEach(tool => { + if (tool.compliance_level) availableComplianceLevels.add(tool.compliance_level) + }) + allPrompts.forEach(prompt => { + if (prompt.compliance_level) availableComplianceLevels.add(prompt.compliance_level) + }) + const complianceLevels = Array.from(availableComplianceLevels).sort() const navigateToMarketplace = () => { clearToolsAndPrompts() @@ -42,6 +56,7 @@ const ToolsPanel = ({ isOpen, onClose }) => { server: toolServer.server, description: toolServer.description, is_exclusive: toolServer.is_exclusive, + compliance_level: toolServer.compliance_level, tools: toolServer.tools || [], tool_count: toolServer.tool_count || 0, prompts: [], @@ -368,6 +383,29 @@ const ToolsPanel = ({ isOpen, onClose }) => { )} + + {/* Compliance Level Filter */} + {complianceLevels.length > 0 && ( +
Filter tools and sources by security level
+{server.description}
diff --git a/frontend/src/contexts/ChatContext.jsx b/frontend/src/contexts/ChatContext.jsx index 6a7ef27..6771c78 100644 --- a/frontend/src/contexts/ChatContext.jsx +++ b/frontend/src/contexts/ChatContext.jsx @@ -223,6 +223,8 @@ export const ChatProvider = ({ children }) => { toolChoiceRequired: selections.toolChoiceRequired, setToolChoiceRequired: selections.setToolChoiceRequired, clearToolsAndPrompts: selections.clearToolsAndPrompts, + complianceLevelFilter: selections.complianceLevelFilter, + setComplianceLevelFilter: selections.setComplianceLevelFilter, agentModeEnabled: agent.agentModeEnabled, setAgentModeEnabled: agent.setAgentModeEnabled, agentMaxSteps: agent.agentMaxSteps, diff --git a/frontend/src/contexts/MarketplaceContext.jsx b/frontend/src/contexts/MarketplaceContext.jsx index d172495..57bd2f1 100644 --- a/frontend/src/contexts/MarketplaceContext.jsx +++ b/frontend/src/contexts/MarketplaceContext.jsx @@ -130,6 +130,23 @@ export const MarketplaceProvider = ({ children }) => { const getFilteredPrompts = () => { return prompts.filter(prompt => selectedServers.has(prompt.server)) } + + const getComplianceFilteredTools = (complianceLevel) => { + if (!complianceLevel) return getFilteredTools() + return getFilteredTools().filter(tool => { + // If no compliance_level specified, include in all filters (backward compatible) + if (!tool.compliance_level) return true + return tool.compliance_level === complianceLevel + }) + } + + const getComplianceFilteredPrompts = (complianceLevel) => { + if (!complianceLevel) return getFilteredPrompts() + return getFilteredPrompts().filter(prompt => { + if (!prompt.compliance_level) return true + return prompt.compliance_level === complianceLevel + }) + } const value = { selectedServers, @@ -138,7 +155,9 @@ export const MarketplaceProvider = ({ children }) => { selectAllServers, deselectAllServers, getFilteredTools, - getFilteredPrompts + getFilteredPrompts, + getComplianceFilteredTools, + getComplianceFilteredPrompts } return ( diff --git a/frontend/src/hooks/chat/useSelections.js b/frontend/src/hooks/chat/useSelections.js index 2d3ba03..cff43f8 100644 --- a/frontend/src/hooks/chat/useSelections.js +++ b/frontend/src/hooks/chat/useSelections.js @@ -10,6 +10,7 @@ export function useSelections() { const [promptsRaw, setPromptsRaw] = usePersistentState('chatui-selected-prompts', []) const [dataSourcesRaw, setDataSourcesRaw] = usePersistentState('chatui-selected-data-sources', []) const [toolChoiceRequired, setToolChoiceRequired] = usePersistentState('chatui-tool-choice-required', false) + const [complianceLevelFilter, setComplianceLevelFilter] = usePersistentState('chatui-compliance-level-filter', null) const selectedTools = useMemo(() => toSet(toolsRaw), [toolsRaw]) const selectedPrompts = useMemo(() => toSet(promptsRaw), [promptsRaw]) @@ -82,6 +83,8 @@ export function useSelections() { removePrompts, toolChoiceRequired, setToolChoiceRequired, - clearToolsAndPrompts + clearToolsAndPrompts, + complianceLevelFilter, + setComplianceLevelFilter } } From b4eec034703187e2c0696c92b2b687be8c9a18ee Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 31 Oct 2025 01:27:53 +0000 Subject: [PATCH 04/24] Add compliance level tests for backend configuration Co-authored-by: garland3 <1162675+garland3@users.noreply.github.com> --- backend/tests/test_compliance_level.py | 56 ++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 backend/tests/test_compliance_level.py diff --git a/backend/tests/test_compliance_level.py b/backend/tests/test_compliance_level.py new file mode 100644 index 0000000..1105582 --- /dev/null +++ b/backend/tests/test_compliance_level.py @@ -0,0 +1,56 @@ +"""Test compliance level functionality for MCP servers and data sources.""" + +import pytest +from modules.config.manager import MCPServerConfig, MCPConfig + + +def test_mcp_server_config_with_compliance_level(): + """Test that MCPServerConfig accepts and stores compliance_level.""" + config = MCPServerConfig( + description="Test server", + compliance_level="SOC2" + ) + assert config.compliance_level == "SOC2" + + +def test_mcp_server_config_without_compliance_level(): + """Test that MCPServerConfig works without compliance_level (backward compatible).""" + config = MCPServerConfig( + description="Test server" + ) + assert config.compliance_level is None + + +def test_mcp_config_from_dict_with_compliance(): + """Test that MCPConfig properly parses servers with compliance levels.""" + data = { + "servers": { + "test_server": { + "description": "Test description", + "compliance_level": "HIPAA" + } + } + } + config = MCPConfig(**data) + assert "test_server" in config.servers + assert config.servers["test_server"].compliance_level == "HIPAA" + + +def test_compliance_level_in_config_response(): + """Test that /api/config returns compliance_level in tools response.""" + # This is an integration test that would require full app setup + # For now, we verify the model supports it + config_dict = { + "description": "PDF processor", + "author": "Test", + "compliance_level": "SOC2", + "groups": ["users"], + "is_exclusive": False, + "enabled": True + } + server_config = MCPServerConfig(**config_dict) + + # Verify it can be serialized to dict (as done in API responses) + as_dict = server_config.model_dump() + assert as_dict["compliance_level"] == "SOC2" + assert as_dict["description"] == "PDF processor" From a48a6b1fc96fce3c2d101015d95061ca35a0ebad Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 31 Oct 2025 01:31:25 +0000 Subject: [PATCH 05/24] Update override config files with compliance levels Co-authored-by: garland3 <1162675+garland3@users.noreply.github.com> --- config/overrides/mcp-rag.json | 3 ++- config/overrides/mcp.json | 12 ++++++++---- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/config/overrides/mcp-rag.json b/config/overrides/mcp-rag.json index 7a0d6f5..0823559 100644 --- a/config/overrides/mcp-rag.json +++ b/config/overrides/mcp-rag.json @@ -7,6 +7,7 @@ "description": "Fleet RAG server: discover resources and retrieve locations/meta-data for corporate cars in use by employees.", "author": "Chat UI Team", "short_description": "Corporate cars location & metadata", - "help_email": "support@chatui.example.com" + "help_email": "support@chatui.example.com", + "compliance_level": "SOC2" } } diff --git a/config/overrides/mcp.json b/config/overrides/mcp.json index 89b5ba6..d7f46b7 100644 --- a/config/overrides/mcp.json +++ b/config/overrides/mcp.json @@ -9,7 +9,8 @@ "description": "Evaluate mathematical expressions, perform calculations with basic arithmetic, trigonometry, and logarithms", "author": "Chat UI Team", "short_description": "Mathematical calculator", - "help_email": "support@chatui.example.com" + "help_email": "support@chatui.example.com", + "compliance_level": "Public" }, "pptx_generator": { @@ -20,7 +21,8 @@ "description": "Create professional PowerPoint presentations from JSON data or Markdown content with automatic slide generation, bullet point formatting, and multimedia support", "author": "Chat UI Team", "short_description": "PowerPoint presentation generator", - "help_email": "support@chatui.example.com" + "help_email": "support@chatui.example.com", + "compliance_level": "SOC2" }, "pdfbasic": { "command": ["python", "mcp/pdfbasic/main.py"], @@ -30,7 +32,8 @@ "description": "Extract and analyze text content from PDF documents, search within PDFs, and summarize content", "author": "Chat UI Team", "short_description": "PDF text extraction and analysis", - "help_email": "support@chatui.example.com" + "help_email": "support@chatui.example.com", + "compliance_level": "SOC2" }, "file_size_test": { "command": ["python", "mcp/file_size_test/main.py"], @@ -40,7 +43,8 @@ "description": "Simple test tool that accepts a file transfer and returns the file size in bytes", "author": "Chat UI Team", "short_description": "File transfer test tool", - "help_email": "support@chatui.example.com" + "help_email": "support@chatui.example.com", + "compliance_level": "Public" } } From 119cda9872abb909e3c41cec6f9a34935d31978b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 31 Oct 2025 01:33:14 +0000 Subject: [PATCH 06/24] Add comprehensive documentation for compliance level feature Co-authored-by: garland3 <1162675+garland3@users.noreply.github.com> --- docs/compliance-level-feature.md | 241 +++++++++++++++++++++++++++++++ 1 file changed, 241 insertions(+) create mode 100644 docs/compliance-level-feature.md diff --git a/docs/compliance-level-feature.md b/docs/compliance-level-feature.md new file mode 100644 index 0000000..aab8129 --- /dev/null +++ b/docs/compliance-level-feature.md @@ -0,0 +1,241 @@ +# Compliance Level Feature + +## Overview +This feature allows MCP servers and RAG data sources to declare a compliance level (e.g., SOC2, HIPAA, Public). Users can then filter their session to only connect to and use sources matching a specific compliance level. This helps minimize the risk of mixing data from secure and insecure environments. + +## Implementation + +### Backend Changes + +#### 1. Configuration Model (`backend/modules/config/manager.py`) +Added `compliance_level` field to `MCPServerConfig`: + +```python +class MCPServerConfig(BaseModel): + # ... existing fields ... + compliance_level: Optional[str] = None # Compliance/security level (e.g., "SOC2", "HIPAA", "Public") +``` + +#### 2. Configuration Files +Updated MCP server configurations to include compliance levels: + +**config/defaults/mcp.json** and **config/overrides/mcp.json**: +```json +{ + "calculator": { + "command": ["python", "mcp/calculator/main.py"], + "cwd": "backend", + "groups": ["users"], + "compliance_level": "Public" + // ... other fields + }, + "pdfbasic": { + "command": ["python", "mcp/pdfbasic/main.py"], + "cwd": "backend", + "groups": ["users"], + "compliance_level": "SOC2" + // ... other fields + } +} +``` + +**config/defaults/mcp-rag.json** and **config/overrides/mcp-rag.json**: +```json +{ + "corporate_cars": { + "command": ["python", "mcp/corporate_cars/main.py"], + "cwd": "backend", + "groups": ["users"], + "compliance_level": "SOC2" + // ... other fields + } +} +``` + +#### 3. API Responses (`backend/routes/config_routes.py`) +The `/api/config` endpoint now includes `compliance_level` in: +- **tools** array (for MCP tool servers) +- **prompts** array (for MCP prompt servers) + +**RAG MCP Service** (`backend/domain/rag_mcp_service.py`): +- Added `complianceLevel` to RAG server discovery responses +- RAG sources can inherit compliance level from their server or specify their own + +Example API response: +```json +{ + "tools": [ + { + "server": "calculator", + "tools": ["calculate"], + "compliance_level": "Public" + }, + { + "server": "pdfbasic", + "tools": ["analyze_pdf"], + "compliance_level": "SOC2" + } + ], + "rag_servers": [ + { + "server": "corporate_cars", + "complianceLevel": "SOC2", + "sources": [ + { + "id": "q3_sales_forecast", + "name": "Q3 Sales Forecast", + "complianceLevel": "SOC2" + } + ] + } + ] +} +``` + +### Frontend Changes + +#### 1. State Management (`frontend/src/hooks/chat/useSelections.js`) +Added compliance level filter to user selection state: +```javascript +const [complianceLevelFilter, setComplianceLevelFilter] = usePersistentState( + 'chatui-compliance-level-filter', + null +) +``` + +#### 2. Context (`frontend/src/contexts/ChatContext.jsx`) +Exposed compliance level filter through ChatContext: +```javascript +{ + complianceLevelFilter, + setComplianceLevelFilter +} +``` + +#### 3. Marketplace Context (`frontend/src/contexts/MarketplaceContext.jsx`) +Added filtering functions for compliance levels: +```javascript +const getComplianceFilteredTools = (complianceLevel) => { + if (!complianceLevel) return getFilteredTools() + return getFilteredTools().filter(tool => { + if (!tool.compliance_level) return true // Backward compatible + return tool.compliance_level === complianceLevel + }) +} +``` + +#### 4. UI Components (`frontend/src/components/ToolsPanel.jsx`) +Added compliance level filter dropdown and badges: +- **Filter dropdown**: Allows users to select a compliance level (All Levels, Public, SOC2, etc.) +- **Server badges**: Display compliance level badge on each server entry +- Uses Shield icon from lucide-react + +UI Example: +``` +┌─ Tools & Integrations ────────────────┐ +│ Compliance Level: [SOC2 ▼] │ +├───────────────────────────────────────┤ +│ 📋 Calculator [🔒 Public] │ +│ 📋 PDF Analyzer [🔒 SOC2] │ +└───────────────────────────────────────┘ +``` + +## Usage + +### For Administrators +1. Edit MCP server configuration files (`config/defaults/mcp.json` or `config/overrides/mcp.json`) +2. Add `compliance_level` field to each server: + ```json + { + "server_name": { + "command": [...], + "compliance_level": "SOC2" + } + } + ``` +3. Supported values are arbitrary strings, but common examples include: + - `"Public"` - Publicly available, no special compliance + - `"SOC2"` - SOC 2 Type II compliant + - `"HIPAA"` - HIPAA compliant + - `"FedRAMP"` - FedRAMP authorized + - Or any custom compliance level + +### For End Users +1. Open the Tools panel in the chat interface +2. Look for the "Compliance Level" dropdown in the controls section +3. Select a compliance level (e.g., "SOC2") +4. Only tools and data sources matching that compliance level will be shown +5. Select "All Levels" to see all available tools regardless of compliance level + +## Benefits + +1. **Data Segregation**: Prevents accidental mixing of data from different security environments +2. **Compliance Enforcement**: Helps ensure users only interact with appropriately certified systems +3. **Transparency**: Users can see the compliance level of each tool/data source +4. **Flexibility**: Supports custom compliance levels beyond standard certifications + +## Backward Compatibility + +- Servers without a `compliance_level` field are shown in all filter modes +- The feature is opt-in; existing configurations continue to work without modification +- Frontend gracefully handles null/missing compliance levels + +## Testing + +### Backend Tests +Located in `backend/tests/test_compliance_level.py`: +- Tests MCPServerConfig with and without compliance_level +- Tests MCPConfig parsing with compliance levels +- Verifies compliance_level is serialized correctly + +Run tests: +```bash +pytest backend/tests/test_compliance_level.py +``` + +### Manual Testing +1. Start the application: `cd backend && python main.py` +2. Open http://localhost:8000 +3. Open the Tools panel +4. Verify compliance level dropdown appears with available levels +5. Select a compliance level and verify tools are filtered +6. Verify badges show on tool servers + +## Example Configuration + +### Complete MCP Server Configuration +```json +{ + "secure_database": { + "command": ["python", "mcp/secure_db/main.py"], + "cwd": "backend", + "groups": ["finance"], + "is_exclusive": false, + "description": "Access to financial database", + "author": "Security Team", + "short_description": "Financial DB access", + "help_email": "security@example.com", + "compliance_level": "SOC2" + }, + "public_api": { + "command": ["python", "mcp/public_api/main.py"], + "cwd": "backend", + "groups": ["users"], + "is_exclusive": false, + "description": "Public API for general data", + "author": "Engineering Team", + "short_description": "Public API", + "help_email": "engineering@example.com", + "compliance_level": "Public" + } +} +``` + +## Future Enhancements + +Potential improvements for future iterations: +1. Hierarchical compliance levels (e.g., HIPAA implies SOC2) +2. Multiple compliance levels per server +3. Visual indicators beyond badges (colors, icons) +4. Compliance level warnings/confirmations +5. Audit logging of compliance-filtered sessions From 84f381d1f0334ac677189bc8e27ab1d97426e3b5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 1 Nov 2025 04:28:57 +0000 Subject: [PATCH 07/24] Add compliance level visibility and auto-cleanup of incompatible selections - Show compliance level indicator in header with quick clear button - Add compliance filter to RAG panel matching tools panel - Auto-clear incompatible tool/prompt selections when compliance level changes - Prevent accidental use of non-compliant tools from previous sessions Co-authored-by: garland3 <1162675+garland3@users.noreply.github.com> --- frontend/src/components/Header.jsx | 19 +++++++++++- frontend/src/components/RagPanel.jsx | 43 +++++++++++++++++++++++++-- frontend/src/contexts/ChatContext.jsx | 37 ++++++++++++++++++++++- 3 files changed, 94 insertions(+), 5 deletions(-) diff --git a/frontend/src/components/Header.jsx b/frontend/src/components/Header.jsx index 0eb25e3..c8f5809 100644 --- a/frontend/src/components/Header.jsx +++ b/frontend/src/components/Header.jsx @@ -19,7 +19,9 @@ const Header = ({ onToggleRag, onToggleTools, onToggleFiles, onToggleCanvas, onC messages, clearChat, features, - isInAdminGroup + isInAdminGroup, + complianceLevelFilter, + setComplianceLevelFilter } = useChat() const { connectionStatus, isConnected } = useWS() const [dropdownOpen, setDropdownOpen] = useState(false) @@ -176,6 +178,21 @@ const Header = ({ onToggleRag, onToggleTools, onToggleFiles, onToggleCanvas, onC )} + {/* Compliance Level Indicator */} + {complianceLevelFilter && ( +