atlas-mcp-server
Version:
ATLAS (Adaptive Task & Logic Automation System): An MCP server enabling LLM agents to manage projects, tasks, and knowledge via a Neo4j-backed, three-tier architecture. Facilitates complex workflow automation and project management through LLM Agents.
370 lines (369 loc) • 16 kB
JavaScript
import { ProjectService } from "../../../services/neo4j/projectService.js";
import { TaskService } from "../../../services/neo4j/taskService.js";
import { BaseErrorCode, McpError, ProjectErrorCode, TaskErrorCode, } from "../../../types/errors.js";
import { PriorityLevel, TaskStatus } from "../../../types/mcp.js";
import { logger, requestContextService } from "../../../utils/index.js"; // Import requestContextService
import { ResourceTemplates, ResourceURIs, toTaskResource } from "../types.js";
/**
* Register Task Resources
*
* This function registers resource endpoints for the Tasks entity
* - GET atlas://tasks - List all tasks
* - GET atlas://tasks/{taskId} - Get specific task by ID
* - GET atlas://projects/{projectId}/tasks - List tasks for a specific project
*
* @param server The MCP server instance
*/
export function registerTaskResources(server) {
// List all tasks
server.resource("tasks-list", ResourceURIs.TASKS, {
name: "All Tasks",
description: "List of all tasks in the Atlas platform with pagination and filtering support",
mimeType: "application/json",
}, async (uri) => {
const reqContext = requestContextService.createRequestContext({
operation: "listAllTasks",
resourceUri: uri.href,
});
try {
logger.info("Listing all tasks", { ...reqContext, uri: uri.href });
// Parse query parameters
const queryParams = new URLSearchParams(uri.search);
// Default project ID required by task service
const projectId = queryParams.get("projectId") || "*";
const filters = {
projectId,
};
// Parse status parameter using TaskStatus enum
const status = queryParams.get("status");
if (status) {
switch (status) {
case TaskStatus.BACKLOG:
filters.status = "backlog";
break;
case TaskStatus.TODO:
filters.status = "todo";
break;
case TaskStatus.IN_PROGRESS:
filters.status = "in-progress";
break;
case TaskStatus.COMPLETED:
filters.status = "completed";
break;
default:
logger.warning(`Invalid status value: ${status}, ignoring filter`, { ...reqContext, invalidStatus: status });
}
}
// Parse priority parameter using PriorityLevel enum
const priority = queryParams.get("priority");
if (priority) {
switch (priority) {
case PriorityLevel.LOW:
filters.priority = "low";
break;
case PriorityLevel.MEDIUM:
filters.priority = "medium";
break;
case PriorityLevel.HIGH:
filters.priority = "high";
break;
case PriorityLevel.CRITICAL:
filters.priority = "critical";
break;
default:
logger.warning(`Invalid priority value: ${priority}, ignoring filter`, { ...reqContext, invalidPriority: priority });
}
}
// Parse assignedTo parameter
const assignedTo = queryParams.get("assignedTo");
if (assignedTo) {
filters.assignedTo = String(assignedTo);
}
// Parse taskType parameter
const taskType = queryParams.get("taskType");
if (taskType) {
filters.taskType = String(taskType);
}
// Parse tags parameter
const tags = queryParams.get("tags");
if (tags) {
// Split comma-separated tags
filters.tags = String(tags)
.split(",")
.map((tag) => tag.trim());
}
// Parse sort parameters
const sortBy = queryParams.get("sortBy");
if (sortBy) {
// Validate sortBy value
const validSortByValues = ["priority", "createdAt", "status"];
if (validSortByValues.includes(sortBy)) {
filters.sortBy = sortBy;
}
else {
logger.warning(`Invalid sortBy value: ${sortBy}, using default sorting`, { ...reqContext, invalidSortBy: sortBy });
}
}
const sortDirection = queryParams.get("sortDirection");
if (sortDirection) {
// Validate sortDirection value
const validDirections = ["asc", "desc"];
if (validDirections.includes(sortDirection)) {
filters.sortDirection = sortDirection;
}
else {
logger.warning(`Invalid sortDirection value: ${sortDirection}, using default direction`, { ...reqContext, invalidSortDirection: sortDirection });
}
}
// Parse pagination parameters
const page = queryParams.has("page")
? parseInt(queryParams.get("page") || "1", 10)
: 1;
const limit = queryParams.has("limit")
? parseInt(queryParams.get("limit") || "20", 10)
: 20;
// Add pagination to filters
filters.page = page;
filters.limit = limit;
// Query the database
const result = await TaskService.getTasks(filters);
// Map Neo4j tasks to resource objects
const taskResources = result.data.map((task) => toTaskResource(task));
return {
contents: [
{
uri: uri.href,
mimeType: "application/json",
text: JSON.stringify({
tasks: taskResources,
pagination: {
total: result.total,
page: result.page,
limit: result.limit,
totalPages: result.totalPages,
},
}, null, 2),
},
],
};
}
catch (error) {
logger.error("Error listing tasks", error, {
...reqContext,
// error is now part of the Error object passed to logger
uri: uri.href,
});
throw new McpError(BaseErrorCode.INTERNAL_ERROR, `Failed to list tasks: ${error instanceof Error ? error.message : String(error)}`);
}
});
// Get task by ID
server.resource("task-by-id", ResourceTemplates.TASK, {
name: "Task by ID",
description: "Retrieves a single task by its unique identifier",
mimeType: "application/json",
}, async (uri, params) => {
const reqContext = requestContextService.createRequestContext({
operation: "getTaskById",
resourceUri: uri.href,
taskIdParam: params.taskId,
});
try {
const taskId = params.taskId;
logger.info("Fetching task by ID", {
...reqContext,
taskId, // Already in reqContext
uri: uri.href, // Already in reqContext
});
if (!taskId) {
throw new McpError(BaseErrorCode.VALIDATION_ERROR, "Task ID is required");
}
// Query the database
const task = await TaskService.getTaskById(taskId);
if (!task) {
throw new McpError(TaskErrorCode.TASK_NOT_FOUND, `Task with ID ${taskId} not found`, { taskId });
}
// Convert to resource object
const taskResource = toTaskResource(task);
return {
contents: [
{
uri: uri.href,
mimeType: "application/json",
text: JSON.stringify(taskResource, null, 2),
},
],
};
}
catch (error) {
// Handle specific error cases
if (error instanceof McpError) {
throw error;
}
logger.error("Error fetching task by ID", error, {
...reqContext,
// error is now part of the Error object passed to logger
parameters: params,
});
throw new McpError(BaseErrorCode.INTERNAL_ERROR, `Failed to fetch task: ${error instanceof Error ? error.message : String(error)}`);
}
});
// List tasks by project
server.resource("tasks-by-project", ResourceTemplates.TASKS_BY_PROJECT, {
name: "Tasks by Project",
description: "Retrieves all tasks belonging to a specific project",
mimeType: "application/json",
}, async (uri, params) => {
const reqContext = requestContextService.createRequestContext({
operation: "listTasksByProject",
resourceUri: uri.href,
projectIdParam: params.projectId,
});
try {
const projectId = params.projectId;
logger.info("Listing tasks for project", {
...reqContext,
projectId, // Already in reqContext
uri: uri.href, // Already in reqContext
});
if (!projectId) {
throw new McpError(BaseErrorCode.VALIDATION_ERROR, "Project ID is required");
}
// Verify the project exists
const project = await ProjectService.getProjectById(projectId);
if (!project) {
throw new McpError(ProjectErrorCode.PROJECT_NOT_FOUND, `Project with ID ${projectId} not found`, { projectId });
}
// Parse query parameters
const queryParams = new URLSearchParams(uri.search);
const filters = {
projectId,
};
// Parse status parameter using TaskStatus enum
const status = queryParams.get("status");
if (status) {
switch (status) {
case TaskStatus.BACKLOG:
filters.status = "backlog";
break;
case TaskStatus.TODO:
filters.status = "todo";
break;
case TaskStatus.IN_PROGRESS:
filters.status = "in-progress";
break;
case TaskStatus.COMPLETED:
filters.status = "completed";
break;
default:
logger.warning(`Invalid status value: ${status}, ignoring filter`, { ...reqContext, invalidStatus: status });
}
}
// Parse priority parameter using PriorityLevel enum
const priority = queryParams.get("priority");
if (priority) {
switch (priority) {
case PriorityLevel.LOW:
filters.priority = "low";
break;
case PriorityLevel.MEDIUM:
filters.priority = "medium";
break;
case PriorityLevel.HIGH:
filters.priority = "high";
break;
case PriorityLevel.CRITICAL:
filters.priority = "critical";
break;
default:
logger.warning(`Invalid priority value: ${priority}, ignoring filter`, { ...reqContext, invalidPriority: priority });
}
}
// Parse assignedTo parameter
const assignedTo = queryParams.get("assignedTo");
if (assignedTo) {
filters.assignedTo = String(assignedTo);
}
// Parse taskType parameter
const taskType = queryParams.get("taskType");
if (taskType) {
filters.taskType = String(taskType);
}
// Parse tags parameter
const tags = queryParams.get("tags");
if (tags) {
// Split comma-separated tags
filters.tags = String(tags)
.split(",")
.map((tag) => tag.trim());
}
// Parse sort parameters
const sortBy = queryParams.get("sortBy");
if (sortBy) {
// Validate sortBy value
const validSortByValues = ["priority", "createdAt", "status"];
if (validSortByValues.includes(sortBy)) {
filters.sortBy = sortBy;
}
else {
logger.warning(`Invalid sortBy value: ${sortBy}, using default sorting`, { ...reqContext, invalidSortBy: sortBy });
}
}
const sortDirection = queryParams.get("sortDirection");
if (sortDirection) {
// Validate sortDirection value
const validDirections = ["asc", "desc"];
if (validDirections.includes(sortDirection)) {
filters.sortDirection = sortDirection;
}
else {
logger.warning(`Invalid sortDirection value: ${sortDirection}, using default direction`, { ...reqContext, invalidSortDirection: sortDirection });
}
}
// Parse pagination parameters
const page = queryParams.has("page")
? parseInt(queryParams.get("page") || "1", 10)
: 1;
const limit = queryParams.has("limit")
? parseInt(queryParams.get("limit") || "20", 10)
: 20;
// Add pagination to filters
filters.page = page;
filters.limit = limit;
// Query the database
const result = await TaskService.getTasks(filters);
// Map Neo4j tasks to resource objects
const taskResources = result.data.map((task) => toTaskResource(task));
return {
contents: [
{
uri: uri.href,
mimeType: "application/json",
text: JSON.stringify({
projectId,
projectName: project.name,
tasks: taskResources,
pagination: {
total: result.total,
page: result.page,
limit: result.limit,
totalPages: result.totalPages,
},
}, null, 2),
},
],
};
}
catch (error) {
// Handle specific error cases
if (error instanceof McpError) {
throw error;
}
logger.error("Error listing tasks for project", error, {
...reqContext,
// error is now part of the Error object passed to logger
parameters: params,
});
throw new McpError(BaseErrorCode.INTERNAL_ERROR, `Failed to list tasks for project: ${error instanceof Error ? error.message : String(error)}`);
}
});
}