@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
text/typescript
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)}`
);
}
}