diff --git a/mcp_server/server.py b/mcp_server/server.py index 9c2e327..bf8fc6b 100644 --- a/mcp_server/server.py +++ b/mcp_server/server.py @@ -14,6 +14,12 @@ sys.path.insert(0, str(Path(__file__).parent.parent)) from fastapi import FastAPI, Request, UploadFile, File, HTTPException, Depends, BackgroundTasks from fastapi.middleware.cors import CORSMiddleware +from mcp_server.auth import ( + authenticate_user, + create_access_token, + is_auth_enabled, + get_current_user +) from fastapi.responses import JSONResponse from pydantic import BaseModel import uvicorn @@ -564,14 +570,14 @@ class SearchAllRequest(BaseModel): # REST API Endpoints for UI @app.get("/api/kb") -async def api_list_knowledge_bases(): +async def api_list_knowledge_bases(current_user: dict = Depends(get_current_user)): """REST API: List all knowledge bases.""" kbs = await list_knowledge_bases() return {"knowledge_bases": kbs} @app.post("/api/kb") -async def api_create_knowledge_base(request: CreateKBRequest): +async def api_create_knowledge_base(request: CreateKBRequest, current_user: dict = Depends(get_current_user)): """REST API: Create a new knowledge base.""" result = await create_knowledge_base( name=request.name, @@ -585,7 +591,7 @@ async def api_create_knowledge_base(request: CreateKBRequest): @app.post("/api/kb/search-all") @limiter.limit("10/minute") -async def api_search_all(request: Request, body: SearchAllRequest): +async def api_search_all(request: Request, body: SearchAllRequest, current_user: dict = Depends(get_current_user)): """REST API: Search across multiple knowledge bases.""" try: from llama_index.embeddings.ollama import OllamaEmbedding @@ -634,7 +640,7 @@ async def api_search_all(request: Request, body: SearchAllRequest): @app.delete("/api/kb/{kb_name}") -async def api_delete_knowledge_base(kb_name: str): +async def api_delete_knowledge_base(kb_name: str, current_user: dict = Depends(get_current_user)): """REST API: Delete a knowledge base.""" try: db_manager = get_sqlite_manager() @@ -654,7 +660,7 @@ async def api_delete_knowledge_base(kb_name: str): @app.get("/api/kb/{kb_name}/stats") -async def api_get_kb_stats(kb_name: str): +async def api_get_kb_stats(kb_name: str, current_user: dict = Depends(get_current_user)): """REST API: Get knowledge base statistics.""" stats = await get_kb_stats(kb_name) if "error" in stats: @@ -663,7 +669,7 @@ async def api_get_kb_stats(kb_name: str): @app.get("/api/kb/{kb_name}/documents") -async def api_list_documents(kb_name: str): +async def api_list_documents(kb_name: str, current_user: dict = Depends(get_current_user)): """REST API: List documents in a knowledge base.""" docs = await list_documents(kb_name) return {"documents": docs} @@ -671,7 +677,7 @@ async def api_list_documents(kb_name: str): @app.post("/api/kb/{kb_name}/chat") @limiter.limit("20/minute") # Rate limit: 20 queries per minute per IP -async def api_chat(request: Request, kb_name: str, chat_request: ChatRequest): +async def api_chat(request: Request, kb_name: str, chat_request: ChatRequest, current_user: dict = Depends(get_current_user)): """REST API: Chat with a knowledge base.""" result = await search_knowledge_base( kb_name=kb_name, @@ -686,7 +692,7 @@ async def api_chat(request: Request, kb_name: str, chat_request: ChatRequest): @app.post("/api/kb/{kb_name}/upload") -async def api_upload_document(kb_name: str, file: UploadFile = File(...)): +async def api_upload_document(kb_name: str, file: UploadFile = File(...), current_user: dict = Depends(get_current_user)): """REST API: Upload a single document to a knowledge base.""" import tempfile import os @@ -733,7 +739,7 @@ class ContentUploadRequest(BaseModel): @app.post("/api/kb/{kb_name}/documents/content") -async def api_upload_content(kb_name: str, request: ContentUploadRequest): +async def api_upload_content(kb_name: str, request: ContentUploadRequest, current_user: dict = Depends(get_current_user)): """REST API: Upload raw text content directly to a knowledge base (no file needed).""" import tempfile import os as _os @@ -777,7 +783,7 @@ async def api_upload_content(kb_name: str, request: ContentUploadRequest): @app.post("/api/kb/{kb_name}/import") -async def api_import_directory(kb_name: str, directory_path: str): +async def api_import_directory(kb_name: str, directory_path: str, current_user: dict = Depends(get_current_user)): """REST API: Import all files from a directory into a knowledge base.""" from app.core.ingestion import ingest_directory @@ -810,7 +816,7 @@ async def api_import_directory(kb_name: str, directory_path: str): @app.delete("/api/kb/{kb_name}/documents/{filename}") -async def api_delete_document(kb_name: str, filename: str): +async def api_delete_document(kb_name: str, filename: str, current_user: dict = Depends(get_current_user)): """REST API: Delete a document from a knowledge base by filename.""" db_manager = get_sqlite_manager() @@ -886,7 +892,7 @@ class SemanticIndexRequest(BaseModel): @app.post("/api/kb/{kb_name}/index-structural") -async def api_index_structural(kb_name: str, request: StructuralIndexRequest): +async def api_index_structural(kb_name: str, request: StructuralIndexRequest, current_user: dict = Depends(get_current_user)): """ REST API: Fast structural indexing using tree-sitter. Phase 1 of hybrid indexing - completes in ~30 seconds for 10k files. @@ -1145,7 +1151,7 @@ def _run_semantic_indexing_sync(kb_name: str, project_path: str, selective: bool @app.post("/api/kb/{kb_name}/index-semantic") -async def api_index_semantic(kb_name: str, request: SemanticIndexRequest, background_tasks: BackgroundTasks): +async def api_index_semantic(kb_name: str, request: SemanticIndexRequest, background_tasks: BackgroundTasks, current_user: dict = Depends(get_current_user)): """ REST API: Semantic indexing with selective embedding. Phase 2 of hybrid indexing. @@ -1224,7 +1230,7 @@ async def api_index_semantic(kb_name: str, request: SemanticIndexRequest, backgr @app.get("/api/jobs/{job_id}") -async def api_get_job_status(job_id: str): +async def api_get_job_status(job_id: str, current_user: dict = Depends(get_current_user)): """Get the status of a background indexing job.""" with INDEXING_JOBS_LOCK: if job_id not in INDEXING_JOBS: @@ -1233,7 +1239,7 @@ async def api_get_job_status(job_id: str): @app.get("/api/jobs") -async def api_list_jobs(): +async def api_list_jobs(current_user: dict = Depends(get_current_user)): """List all indexing jobs (recent first).""" with INDEXING_JOBS_LOCK: jobs = list(INDEXING_JOBS.values()) @@ -1248,7 +1254,7 @@ async def api_get_repo_map( token_budget: int = 1024, project_path: Optional[str] = None, personalization: Optional[str] = None # JSON string -): +, current_user: dict = Depends(get_current_user)): """ REST API: Get compact repo map for Claude's prompt context. Returns the most important symbols fitting within token budget. @@ -1361,7 +1367,7 @@ class ProjectDocumentIngestRequest(BaseModel): @app.get("/api/projects") -async def api_list_projects(): +async def api_list_projects(current_user: dict = Depends(get_current_user)): """REST API: List all projects.""" try: db_manager = get_sqlite_manager() @@ -1373,7 +1379,7 @@ async def api_list_projects(): @app.post("/api/projects") -async def api_create_project(request: ProjectRequest): +async def api_create_project(request: ProjectRequest, current_user: dict = Depends(get_current_user)): """REST API: Create a new project with 4 required MCPs.""" try: db_manager = get_sqlite_manager() @@ -1429,7 +1435,7 @@ async def api_create_project(request: ProjectRequest): @app.get("/api/projects/{project_id}") -async def api_get_project(project_id: int): +async def api_get_project(project_id: int, current_user: dict = Depends(get_current_user)): """REST API: Get project details with MCP assignments.""" try: db_manager = get_sqlite_manager() @@ -1456,7 +1462,7 @@ async def api_get_project(project_id: int): @app.get("/api/projects/{project_id}/mcps") -async def api_get_project_mcps(project_id: int): +async def api_get_project_mcps(project_id: int, current_user: dict = Depends(get_current_user)): """REST API: Get detailed MCP info for a project (KB names and IDs).""" try: db_manager = get_sqlite_manager() @@ -1495,7 +1501,7 @@ async def api_get_project_mcps(project_id: int): @app.post("/api/projects/{project_id}/folders") -async def api_set_kb_folder(project_id: int, request: ProjectKBFolderRequest): +async def api_set_kb_folder(project_id: int, request: ProjectKBFolderRequest, current_user: dict = Depends(get_current_user)): """REST API: Set the folder path for a project's MCP KB.""" try: db_manager = get_sqlite_manager() @@ -1580,7 +1586,7 @@ async def api_set_kb_folder(project_id: int, request: ProjectKBFolderRequest): @app.get("/api/projects/{project_id}/folders") -async def api_get_kb_folders(project_id: int): +async def api_get_kb_folders(project_id: int, current_user: dict = Depends(get_current_user)): """REST API: Get folder configurations for a project's MCPs.""" try: db_manager = get_sqlite_manager() @@ -1606,7 +1612,7 @@ async def api_get_kb_folders(project_id: int): @app.post("/api/projects/{project_id}/ingest-document") -async def api_ingest_document(project_id: int, request: ProjectDocumentIngestRequest): +async def api_ingest_document(project_id: int, request: ProjectDocumentIngestRequest, current_user: dict = Depends(get_current_user)): """REST API: Ingest a document directly into a project's MCP KB.""" try: db_manager = get_sqlite_manager() @@ -1669,7 +1675,7 @@ async def api_ingest_document(project_id: int, request: ProjectDocumentIngestReq @app.delete("/api/projects/{project_id}") -async def api_delete_project(project_id: int): +async def api_delete_project(project_id: int, current_user: dict = Depends(get_current_user)): """REST API: Delete a project and its associated KBs.""" try: db_manager = get_sqlite_manager() @@ -1734,7 +1740,7 @@ class SyncRequest(BaseModel): @app.post("/api/projects/{project_id}/hooks/{mcp_type}/enable") -async def api_enable_hook(project_id: int, mcp_type: str, request: HookRequest): +async def api_enable_hook(project_id: int, mcp_type: str, request: HookRequest, current_user: dict = Depends(get_current_user)): """Enable automatic KB synchronization for a folder.""" try: hook = get_project_hook(project_id) @@ -1759,7 +1765,7 @@ async def api_enable_hook(project_id: int, mcp_type: str, request: HookRequest): @app.post("/api/projects/{project_id}/hooks/{mcp_type}/disable") -async def api_disable_hook(project_id: int, mcp_type: str): +async def api_disable_hook(project_id: int, mcp_type: str, current_user: dict = Depends(get_current_user)): """Disable automatic KB synchronization.""" try: hook = get_project_hook(project_id) @@ -1777,7 +1783,7 @@ async def api_disable_hook(project_id: int, mcp_type: str): @app.post("/api/projects/{project_id}/hooks/sync") -async def api_sync_hooks(project_id: int, request: Optional[SyncRequest] = None): +async def api_sync_hooks(project_id: int, request: Optional[SyncRequest] = None, current_user: dict = Depends(get_current_user)): """Manually sync KB folders for a project.""" try: hook = get_project_hook(project_id) @@ -1803,7 +1809,7 @@ async def api_sync_hooks(project_id: int, request: Optional[SyncRequest] = None) @app.get("/api/projects/{project_id}/hooks") -async def api_get_hooks_status(project_id: int): +async def api_get_hooks_status(project_id: int, current_user: dict = Depends(get_current_user)): """Get hook status for a project.""" try: hook = get_project_hook(project_id) @@ -1824,7 +1830,7 @@ async def api_get_hooks_status(project_id: int): # ============================================================================ @app.post("/api/watcher/start/{project_id}") -async def api_start_watcher(project_id: int): +async def api_start_watcher(project_id: int, current_user: dict = Depends(get_current_user)): """Start file watcher for a project.""" try: watcher = get_global_watcher() @@ -1842,7 +1848,7 @@ async def api_start_watcher(project_id: int): @app.post("/api/watcher/stop/{project_id}") -async def api_stop_watcher(project_id: int): +async def api_stop_watcher(project_id: int, current_user: dict = Depends(get_current_user)): """Stop file watcher for a project.""" try: watcher = get_global_watcher() @@ -1860,7 +1866,7 @@ async def api_stop_watcher(project_id: int): @app.post("/api/watcher/restart/{project_id}") -async def api_restart_watcher(project_id: int): +async def api_restart_watcher(project_id: int, current_user: dict = Depends(get_current_user)): """Restart file watcher for a project.""" try: watcher = get_global_watcher() @@ -1878,7 +1884,7 @@ async def api_restart_watcher(project_id: int): @app.get("/api/watcher/status") -async def api_watcher_status(): +async def api_watcher_status(current_user: dict = Depends(get_current_user)): """Get file watcher status.""" try: watcher = get_global_watcher() @@ -1893,7 +1899,7 @@ async def api_watcher_status(): # ============================================================================ @app.post("/api/spec-watcher/start/{project_id}") -async def api_start_spec_watcher(project_id: int): +async def api_start_spec_watcher(project_id: int, current_user: dict = Depends(get_current_user)): """Start spec watcher for a project.""" try: from app.core.spec_watcher import get_global_spec_watcher @@ -1918,7 +1924,7 @@ async def api_start_spec_watcher(project_id: int): @app.post("/api/spec-watcher/stop/{project_id}") -async def api_stop_spec_watcher(project_id: int): +async def api_stop_spec_watcher(project_id: int, current_user: dict = Depends(get_current_user)): """Stop spec watcher for a project.""" try: from app.core.spec_watcher import get_global_spec_watcher @@ -1937,7 +1943,7 @@ async def api_stop_spec_watcher(project_id: int): @app.post("/api/spec-watcher/start-all") -async def api_start_all_spec_watchers(): +async def api_start_all_spec_watchers(current_user: dict = Depends(get_current_user)): """Start spec watchers for all projects.""" try: from app.core.spec_watcher import get_global_spec_watcher @@ -1955,7 +1961,7 @@ async def api_start_all_spec_watchers(): @app.get("/api/spec-watcher/status") -async def api_spec_watcher_status(): +async def api_spec_watcher_status(current_user: dict = Depends(get_current_user)): """Get spec watcher status.""" try: from app.core.spec_watcher import get_global_spec_watcher @@ -1972,7 +1978,7 @@ async def api_spec_watcher_status(): # ============================================================================ @app.get("/api/projects/{project_id}/specs") -async def api_get_project_specs(project_id: int): +async def api_get_project_specs(project_id: int, current_user: dict = Depends(get_current_user)): """Get all specs for a project.""" try: manager = SpecManager() @@ -1984,7 +1990,7 @@ async def api_get_project_specs(project_id: int): @app.get("/api/specs/{spec_id}/tasks") -async def api_get_spec_tasks(spec_id: int): +async def api_get_spec_tasks(spec_id: int, current_user: dict = Depends(get_current_user)): """Get all tasks for a spec.""" try: manager = SpecManager() @@ -2001,7 +2007,7 @@ class TaskStatusUpdate(BaseModel): @app.patch("/api/tasks/{task_id}/status") -async def api_update_task_status(task_id: int, update: TaskStatusUpdate): +async def api_update_task_status(task_id: int, update: TaskStatusUpdate, current_user: dict = Depends(get_current_user)): """Update a task's status.""" try: manager = SpecManager() @@ -2023,7 +2029,7 @@ async def api_update_task_status(task_id: int, update: TaskStatusUpdate): @app.post("/api/projects/{project_id}/specs/sync") -async def api_sync_project_specs(project_id: int): +async def api_sync_project_specs(project_id: int, current_user: dict = Depends(get_current_user)): """Sync specs from project's agent-os folder.""" try: # Get project path @@ -2049,7 +2055,7 @@ async def api_sync_project_specs(project_id: int): @app.get("/api/projects/{project_id}/kanban") -async def api_get_kanban_view(project_id: int, include_archived: bool = False): +async def api_get_kanban_view(project_id: int, include_archived: bool = False, current_user: dict = Depends(get_current_user)): """Get Kanban board view for a project.""" try: manager = SpecManager() @@ -2100,7 +2106,7 @@ async def api_get_kanban_view(project_id: int, include_archived: bool = False): @app.post("/api/specs/{spec_id}/archive") -async def api_archive_spec(spec_id: int): +async def api_archive_spec(spec_id: int, current_user: dict = Depends(get_current_user)): """Archive a spec.""" try: manager = SpecManager() @@ -2112,7 +2118,7 @@ async def api_archive_spec(spec_id: int): @app.post("/api/specs/{spec_id}/unarchive") -async def api_unarchive_spec(spec_id: int): +async def api_unarchive_spec(spec_id: int, current_user: dict = Depends(get_current_user)): """Unarchive a spec.""" try: manager = SpecManager() @@ -2147,7 +2153,7 @@ class SessionExtractRequest(BaseModel): @app.get("/api/sessions/list") -async def api_list_sessions(project_path: str, limit: int = 50): +async def api_list_sessions(project_path: str, limit: int = 50, current_user: dict = Depends(get_current_user)): """ REST API: List Claude Code session files for a project. @@ -2177,7 +2183,7 @@ async def api_list_sessions(project_path: str, limit: int = 50): @app.post("/api/sessions/extract") -async def api_extract_session_insights(request: SessionExtractRequest): +async def api_extract_session_insights(request: SessionExtractRequest, current_user: dict = Depends(get_current_user)): """ REST API: Extract insights from a Claude Code session file. @@ -2325,7 +2331,7 @@ class SkillInstallRequest(BaseModel): @app.get("/api/skills") -async def api_list_skills(project_path: Optional[str] = None, include_content: bool = False): +async def api_list_skills(project_path: Optional[str] = None, include_content: bool = False, current_user: dict = Depends(get_current_user)): """ REST API: List all skills (global and project). @@ -2352,7 +2358,7 @@ async def api_list_skills(project_path: Optional[str] = None, include_content: b @app.get("/api/skills/templates") -async def api_list_skill_templates(category: Optional[str] = None): +async def api_list_skill_templates(category: Optional[str] = None, current_user: dict = Depends(get_current_user)): """ REST API: List available skill templates. @@ -2378,7 +2384,7 @@ async def api_list_skill_templates(category: Optional[str] = None): @app.get("/api/skills/{scope}/{name}") -async def api_get_skill(scope: str, name: str, project_path: Optional[str] = None): +async def api_get_skill(scope: str, name: str, project_path: Optional[str] = None, current_user: dict = Depends(get_current_user)): """ REST API: Get skill details including content. @@ -2412,7 +2418,7 @@ async def api_get_skill(scope: str, name: str, project_path: Optional[str] = Non @app.post("/api/skills/install") -async def api_install_skill_template(request: SkillInstallRequest, project_path: str): +async def api_install_skill_template(request: SkillInstallRequest, project_path: str, current_user: dict = Depends(get_current_user)): """ REST API: Install a skill template to a project. @@ -2446,7 +2452,7 @@ async def api_install_skill_template(request: SkillInstallRequest, project_path: @app.post("/api/skills") -async def api_create_skill(request: SkillCreateRequest, project_path: str): +async def api_create_skill(request: SkillCreateRequest, project_path: str, current_user: dict = Depends(get_current_user)): """ REST API: Create a custom skill for a project. @@ -2483,7 +2489,7 @@ async def api_create_skill(request: SkillCreateRequest, project_path: str): @app.put("/api/skills/{name}") -async def api_update_skill(name: str, request: SkillUpdateRequest, project_path: str): +async def api_update_skill(name: str, request: SkillUpdateRequest, project_path: str, current_user: dict = Depends(get_current_user)): """ REST API: Update an existing project skill. @@ -2520,7 +2526,7 @@ async def api_update_skill(name: str, request: SkillUpdateRequest, project_path: @app.delete("/api/skills/{name}") -async def api_delete_skill(name: str, project_path: str): +async def api_delete_skill(name: str, project_path: str, current_user: dict = Depends(get_current_user)): """ REST API: Delete a project skill. @@ -2562,7 +2568,7 @@ class CommunitySkillInstallRequest(BaseModel): @app.get("/api/skills/community/sources") -async def api_list_community_sources(): +async def api_list_community_sources(current_user: dict = Depends(get_current_user)): """ REST API: List available community skill sources. @@ -2582,7 +2588,7 @@ async def api_list_community_sources(): @app.get("/api/skills/community") -async def api_list_community_skills(source: Optional[str] = None): +async def api_list_community_skills(source: Optional[str] = None, current_user: dict = Depends(get_current_user)): """ REST API: List skills from community repositories. @@ -2620,7 +2626,7 @@ async def api_list_community_skills(source: Optional[str] = None): async def api_install_community_skill( request: CommunitySkillInstallRequest, project_path: str -): +, current_user: dict = Depends(get_current_user)): """ REST API: Install a community skill to a project. @@ -2665,7 +2671,7 @@ async def api_install_community_skill( # ============================================================================ @app.get("/api/ollama/models") -async def api_list_ollama_models(): +async def api_list_ollama_models(current_user: dict = Depends(get_current_user)): """REST API: List available Ollama models.""" try: response = requests.get(f"{Config.OLLAMA_HOST}/api/tags", timeout=2) @@ -2767,7 +2773,7 @@ def check_port_listening(port: int) -> bool: @app.get("/api/services/status") -async def api_get_services_status(): +async def api_get_services_status(current_user: dict = Depends(get_current_user)): """ Get status of all Claude OS services. @@ -2905,7 +2911,7 @@ async def api_get_services_status(): @app.post("/api/services/{service_type}/control") -async def api_control_service(service_type: str, request: ServiceControlRequest): +async def api_control_service(service_type: str, request: ServiceControlRequest, current_user: dict = Depends(get_current_user)): """ Control a service (start/stop/restart). @@ -2966,7 +2972,7 @@ async def api_control_service(service_type: str, request: ServiceControlRequest) @app.get("/api/browse-directory") -async def api_browse_directory(path: str = None): +async def api_browse_directory(path: str = None, current_user: dict = Depends(get_current_user)): """REST API: Browse directories and return subdirectories.""" try: from pathlib import Path @@ -3052,19 +3058,19 @@ async def api_browse_directory(path: str = None): # MCP Protocol Endpoints @app.post("/") -async def mcp_root(request: Request): +async def mcp_root(request: Request, current_user: dict = Depends(get_current_user)): """Main MCP endpoint - handles JSON-RPC requests.""" return await handle_mcp_request(request) @app.post("/mcp") -async def mcp_endpoint(request: Request): +async def mcp_endpoint(request: Request, current_user: dict = Depends(get_current_user)): """Global MCP endpoint - exposes all KBs.""" return await handle_mcp_request(request, kb_filter=None) @app.post("/mcp/kb/{kb_slug}") -async def mcp_kb_endpoint(kb_slug: str, request: Request): +async def mcp_kb_endpoint(kb_slug: str, request: Request, current_user: dict = Depends(get_current_user)): """KB-specific MCP endpoint - only exposes tools for this KB.""" # Get KB name from slug db_manager = get_sqlite_manager() @@ -3263,12 +3269,6 @@ async def handle_mcp_request(request: Request, kb_filter: Optional[str] = None) # Authentication Endpoints # ============================================================================ -from mcp_server.auth import ( - authenticate_user, - create_access_token, - is_auth_enabled, - get_current_user -) from datetime import timedelta @@ -3489,7 +3489,7 @@ def _run_consolidation_background(job_id: str, kb_name: str, doc_ids: list, new_ @app.post("/api/kb/{kb_name}/lifecycle/dedup-scan") -async def api_lifecycle_dedup_scan(kb_name: str, request: DedupScanRequest, background_tasks: BackgroundTasks): +async def api_lifecycle_dedup_scan(kb_name: str, request: DedupScanRequest, background_tasks: BackgroundTasks, current_user: dict = Depends(get_current_user)): """Scan KB for duplicate/near-duplicate documents.""" import uuid @@ -3520,7 +3520,7 @@ async def api_lifecycle_dedup_scan(kb_name: str, request: DedupScanRequest, back @app.post("/api/kb/{kb_name}/lifecycle/dedup-merge") -async def api_lifecycle_dedup_merge(kb_name: str, request: DedupMergeRequest): +async def api_lifecycle_dedup_merge(kb_name: str, request: DedupMergeRequest, current_user: dict = Depends(get_current_user)): """Merge duplicate documents by keeping one and removing the rest.""" db_manager = get_sqlite_manager() if not db_manager.collection_exists(kb_name): @@ -3532,7 +3532,7 @@ async def api_lifecycle_dedup_merge(kb_name: str, request: DedupMergeRequest): @app.post("/api/kb/{kb_name}/lifecycle/consolidate") -async def api_lifecycle_consolidate(kb_name: str, request: ConsolidateRequest, background_tasks: BackgroundTasks): +async def api_lifecycle_consolidate(kb_name: str, request: ConsolidateRequest, background_tasks: BackgroundTasks, current_user: dict = Depends(get_current_user)): """Consolidate related documents into a single merged document using LLM.""" import uuid @@ -3559,7 +3559,7 @@ async def api_lifecycle_consolidate(kb_name: str, request: ConsolidateRequest, b @app.get("/api/kb/{kb_name}/lifecycle/health") -async def api_lifecycle_health(kb_name: str): +async def api_lifecycle_health(kb_name: str, current_user: dict = Depends(get_current_user)): """Get comprehensive health report for a knowledge base.""" db_manager = get_sqlite_manager() if not db_manager.collection_exists(kb_name): @@ -3570,7 +3570,7 @@ async def api_lifecycle_health(kb_name: str): @app.get("/api/kb/{kb_name}/lifecycle/growth") -async def api_lifecycle_growth(kb_name: str, granularity: str = "month"): +async def api_lifecycle_growth(kb_name: str, granularity: str = "month", current_user: dict = Depends(get_current_user)): """Get document growth timeline for a knowledge base.""" db_manager = get_sqlite_manager() if not db_manager.collection_exists(kb_name): @@ -3581,7 +3581,7 @@ async def api_lifecycle_growth(kb_name: str, granularity: str = "month"): @app.post("/api/kb/{kb_name}/lifecycle/archive") -async def api_lifecycle_archive(kb_name: str, request: ArchiveRequest): +async def api_lifecycle_archive(kb_name: str, request: ArchiveRequest, current_user: dict = Depends(get_current_user)): """Archive documents in a knowledge base.""" db_manager = get_sqlite_manager() if not db_manager.collection_exists(kb_name): @@ -3592,7 +3592,7 @@ async def api_lifecycle_archive(kb_name: str, request: ArchiveRequest): @app.post("/api/kb/{kb_name}/lifecycle/restore") -async def api_lifecycle_restore(kb_name: str, request: RestoreRequest): +async def api_lifecycle_restore(kb_name: str, request: RestoreRequest, current_user: dict = Depends(get_current_user)): """Restore archived documents in a knowledge base.""" db_manager = get_sqlite_manager() if not db_manager.collection_exists(kb_name): @@ -3603,7 +3603,7 @@ async def api_lifecycle_restore(kb_name: str, request: RestoreRequest): @app.get("/api/kb/{kb_name}/lifecycle/archived") -async def api_lifecycle_archived(kb_name: str): +async def api_lifecycle_archived(kb_name: str, current_user: dict = Depends(get_current_user)): """List archived documents in a knowledge base.""" db_manager = get_sqlite_manager() if not db_manager.collection_exists(kb_name): @@ -3614,7 +3614,7 @@ async def api_lifecycle_archived(kb_name: str): @app.get("/api/kb/{kb_name}/lifecycle/stale") -async def api_lifecycle_stale(kb_name: str, stale_days: int = 90): +async def api_lifecycle_stale(kb_name: str, stale_days: int = 90, current_user: dict = Depends(get_current_user)): """Find stale documents older than the specified threshold.""" db_manager = get_sqlite_manager() if not db_manager.collection_exists(kb_name): @@ -3625,7 +3625,7 @@ async def api_lifecycle_stale(kb_name: str, stale_days: int = 90): @app.get("/api/kb/{kb_name}/lifecycle/logs") -async def api_lifecycle_logs(kb_name: str, operation_type: Optional[str] = None, limit: int = 50): +async def api_lifecycle_logs(kb_name: str, operation_type: Optional[str] = None, limit: int = 50, current_user: dict = Depends(get_current_user)): """Get lifecycle operation logs for a knowledge base.""" db_manager = get_sqlite_manager() if not db_manager.collection_exists(kb_name):