UNPKG

@mseep/atlas-mcp-server

Version:

A Model Context Protocol (MCP) server for ATLAS, a Neo4j-powered task management system for LLM Agents - implementing a three-tier architecture (Projects, Tasks, Knowledge) to manage complex workflows.

213 lines (189 loc) 7.1 kB
import { KnowledgeService, ProjectService, TaskService } from '../../../services/neo4j/index.js'; import { BaseErrorCode, McpError } from '../../../types/errors.js'; import { logger } from '../../../utils/logger.js'; import { Project, ProjectListRequest, ProjectListResponse, Knowledge, Task } from './types.js'; // Import Knowledge and Task /** * Retrieve and filter project entities based on specified criteria * Provides two query modes: detailed entity retrieval or paginated collection listing * * @param request The project query parameters including filters and pagination controls * @returns Promise resolving to structured project entities with optional related resources */ export async function listProjects(request: ProjectListRequest): Promise<ProjectListResponse> { try { const { mode = 'all', id, page = 1, limit = 20, includeKnowledge = false, includeTasks = false, taskType, status } = request; // Parameter validation if (mode === 'details' && !id) { throw new McpError( BaseErrorCode.VALIDATION_ERROR, 'Project identifier is required when using mode="details"' ); } // Sanitize pagination parameters const validatedPage = Math.max(1, page); const validatedLimit = Math.min(Math.max(1, limit), 100); let projects: Project[] = []; let total = 0; let totalPages = 0; if (mode === 'details') { // Retrieve specific project entity by identifier const projectResult = await ProjectService.getProjectById(id!); if (!projectResult) { throw new McpError( BaseErrorCode.NOT_FOUND, `Project with identifier ${id} not found` ); } // Cast to the tool's Project type projects = [projectResult as Project]; total = 1; totalPages = 1; } else { // Get paginated list of projects with filters const projectsResult = await ProjectService.getProjects({ status, taskType, page: validatedPage, limit: validatedLimit }); // Cast each project to the tool's Project type projects = projectsResult.data.map(p => p as Project); total = projectsResult.total; totalPages = projectsResult.totalPages; } // Process knowledge resource associations if requested if (includeKnowledge && projects.length > 0) { for (const project of projects) { if (mode === 'details') { // For detailed view, retrieve comprehensive knowledge resources const knowledgeResult = await KnowledgeService.getKnowledge({ projectId: project.id, // Access directly page: 1, limit: 100 // Reasonable threshold for associated resources }); // Add debug logging logger.info('Knowledge items retrieved', { projectId: project.id, // Access directly count: knowledgeResult.data.length, firstItem: knowledgeResult.data[0] ? JSON.stringify(knowledgeResult.data[0]) : 'none' }); // Map directly, assuming KnowledgeService returns Neo4jKnowledge objects project.knowledge = knowledgeResult.data.map(item => { // More explicit mapping with debug info logger.debug('Processing knowledge item', { id: item.id, domain: item.domain, textLength: item.text ? item.text.length : 0 }); // Cast to the tool's Knowledge type return item as Knowledge; }); } else { // For list mode, get abbreviated knowledge items const knowledgeResult = await KnowledgeService.getKnowledge({ projectId: project.id, // Access directly page: 1, limit: 5 // Just a few for summary view }); // Map directly, assuming KnowledgeService returns Neo4jKnowledge objects project.knowledge = knowledgeResult.data.map(item => { // Cast to the tool's Knowledge type, potentially truncating text const knowledgeItem = item as Knowledge; return { ...knowledgeItem, // Show a preview of the text - increased to 200 characters text: item.text && item.text.length > 200 ? item.text.substring(0, 200) + '... (truncated)' : item.text, }; }); } } } // Process task entity associations if requested if (includeTasks && projects.length > 0) { for (const project of projects) { if (mode === 'details') { // For detailed view, retrieve prioritized task entities const tasksResult = await TaskService.getTasks({ projectId: project.id, // Access directly page: 1, limit: 100, // Reasonable threshold for associated entities sortBy: 'priority', sortDirection: 'desc' }); // Add debug logging logger.info('Tasks retrieved for project', { projectId: project.id, // Access directly count: tasksResult.data.length, firstItem: tasksResult.data[0] ? JSON.stringify(tasksResult.data[0]) : 'none' }); // Map directly, assuming TaskService returns Neo4jTask objects project.tasks = tasksResult.data.map(item => { // Debug info logger.debug('Processing task item', { id: item.id, title: item.title, status: item.status, priority: item.priority }); // Cast to the tool's Task type return item as Task; }); } else { // For list mode, get abbreviated task items const tasksResult = await TaskService.getTasks({ projectId: project.id, // Access directly page: 1, limit: 5, // Just a few for summary view sortBy: 'priority', sortDirection: 'desc' }); // Map directly, assuming TaskService returns Neo4jTask objects project.tasks = tasksResult.data.map(item => { // Cast to the tool's Task type return item as Task; }); } } } // Construct the response const response: ProjectListResponse = { projects, total, page: validatedPage, limit: validatedLimit, totalPages }; logger.info('Project query executed successfully', { mode, count: projects.length, total, includeKnowledge, includeTasks }); return response; } catch (error) { logger.error('Project query execution failed', { error }); if (error instanceof McpError) { throw error; } throw new McpError( BaseErrorCode.INTERNAL_ERROR, `Failed to retrieve project entities: ${error instanceof Error ? error.message : String(error)}` ); } }