@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.
171 lines (170 loc) • 7.99 kB
JavaScript
import { KnowledgeService, ProjectService, TaskService } from '../../../services/neo4j/index.js';
import { BaseErrorCode, McpError } from '../../../types/errors.js';
import { logger } from '../../../utils/logger.js';
/**
* 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) {
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 = [];
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];
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);
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;
});
}
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;
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;
});
}
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;
});
}
}
}
// Construct the response
const response = {
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)}`);
}
}