Skip to main content
Glama
by cristip73
tool-handler.ts21.5 kB
import { Tool, CallToolRequest, CallToolResult } from "@modelcontextprotocol/sdk/types.js"; import { AsanaClientWrapper } from './asana-client-wrapper.js'; import { normalizeArrayParameters } from './utils/array-utils.js'; import { formatErrorResponse } from './utils/error-utils.js'; import { validateParameters } from './utils/validation.js'; import { listWorkspacesTool } from './tools/workspace-tools.js'; import { searchProjectsTool, getProjectTool, getProjectTaskCountsTool, getProjectSectionsTool, createSectionForProjectTool, createProjectForWorkspaceTool, updateProjectTool, addMembersForProjectTool, addFollowersForProjectTool, reorderSectionsTool } from './tools/project-tools.js'; import { getProjectStatusTool, getProjectStatusesForProjectTool, createProjectStatusTool, deleteProjectStatusTool } from './tools/project-status-tools.js'; import { searchTasksTool, getTaskTool, createTaskTool, updateTaskTool, createSubtaskTool, getMultipleTasksByGidTool, addTaskToSectionTool, getTasksForSectionTool, getProjectHierarchyTool, getSubtasksForTaskTool, getTasksForProjectTool } from './tools/task-tools.js'; import { getTasksForTagTool, getTagsForWorkspaceTool, addTagsToTaskTool } from './tools/tag-tools.js'; import { addTaskDependenciesTool, addTaskDependentsTool, setParentForTaskTool, addFollowersToTaskTool } from './tools/task-relationship-tools.js'; import { getStoriesForTaskTool, createTaskStoryTool } from './tools/story-tools.js'; import { getTeamsForUserTool, getTeamsForWorkspaceTool, getUsersForWorkspaceTool } from './tools/user-tools.js'; import { getAttachmentsForObjectTool, uploadAttachmentForObjectTool, downloadAttachmentTool } from './tools/attachment-tools.js'; export const tools: Tool[] = [ listWorkspacesTool, searchProjectsTool, getProjectTool, getProjectTaskCountsTool, getProjectSectionsTool, createSectionForProjectTool, createProjectForWorkspaceTool, updateProjectTool, reorderSectionsTool, getProjectStatusTool, getProjectStatusesForProjectTool, createProjectStatusTool, deleteProjectStatusTool, searchTasksTool, getTaskTool, createTaskTool, updateTaskTool, createSubtaskTool, getMultipleTasksByGidTool, addTaskToSectionTool, getTasksForSectionTool, getProjectHierarchyTool, getSubtasksForTaskTool, getTasksForProjectTool, getTasksForTagTool, getTagsForWorkspaceTool, addTagsToTaskTool, addTaskDependenciesTool, addTaskDependentsTool, setParentForTaskTool, addFollowersToTaskTool, getStoriesForTaskTool, createTaskStoryTool, getTeamsForUserTool, getTeamsForWorkspaceTool, addMembersForProjectTool, addFollowersForProjectTool, getUsersForWorkspaceTool, getAttachmentsForObjectTool, uploadAttachmentForObjectTool, downloadAttachmentTool ]; // Exportăm și ca list_of_tools pentru compatibilitate cu index.ts export const list_of_tools = tools; export function tool_handler(asanaClient: AsanaClientWrapper): (request: CallToolRequest) => Promise<CallToolResult> { return async (request: CallToolRequest) => { console.error("Received CallToolRequest:", request); try { if (!request.params.arguments) { throw new Error("No arguments provided"); } // Normalizăm parametrii array înainte de procesare const rawArgs = request.params.arguments as any; const args = normalizeArrayParameters(rawArgs); // Validăm parametrii în funcție de tipul operațiunii const toolName = request.params.name; const validationResult = validateParameters(toolName, args); if (!validationResult.valid) { throw new Error(`Parameter validation failed: ${validationResult.errors.join(', ')}`); } // Folosim console.error în loc de console.log pentru debugging // și doar dacă este necesar pentru debugging // if (JSON.stringify(rawArgs) !== JSON.stringify(args)) { // console.error("Normalized array parameters:", { // before: rawArgs, // after: args // }); // } switch (request.params.name) { case "asana_list_workspaces": { const response = await asanaClient.listWorkspaces(args); return { content: [{ type: "text", text: JSON.stringify(response) }], }; } case "asana_search_projects": { const { workspace, name_pattern, archived = false, ...opts } = args; const response = await asanaClient.searchProjects( workspace || undefined, name_pattern, archived, opts ); return { content: [{ type: "text", text: JSON.stringify(response) }], }; } case "asana_search_tasks": { const { workspace, ...searchOpts } = args; const response = await asanaClient.searchTasks(workspace || undefined, searchOpts); return { content: [{ type: "text", text: JSON.stringify(response) }], }; } case "asana_get_task": { const { task_id, ...opts } = args; const response = await asanaClient.getTask(task_id, opts); return { content: [{ type: "text", text: JSON.stringify(response) }], }; } case "asana_create_task": { const { project_id, ...taskData } = args; const response = await asanaClient.createTask(project_id, taskData); return { content: [{ type: "text", text: JSON.stringify(response) }], }; } case "asana_get_task_stories": { const { task_id, ...opts } = args; const response = await asanaClient.getStoriesForTask(task_id, opts); return { content: [{ type: "text", text: JSON.stringify(response) }], }; } case "asana_update_task": { const { task_id, ...taskData } = args; const response = await asanaClient.updateTask(task_id, taskData); return { content: [{ type: "text", text: JSON.stringify(response) }], }; } case "asana_get_project": { const { project_id, ...opts } = args; const response = await asanaClient.getProject(project_id, opts); return { content: [{ type: "text", text: JSON.stringify(response) }], }; } case "asana_get_project_task_counts": { const { project_id, ...opts } = args; const response = await asanaClient.getProjectTaskCounts(project_id, opts); return { content: [{ type: "text", text: JSON.stringify(response) }], }; } case "asana_get_project_status": { const { project_status_gid, ...opts } = args; const response = await asanaClient.getProjectStatus(project_status_gid, opts); return { content: [{ type: "text", text: JSON.stringify(response) }], }; } case "asana_get_project_statuses": { const { project_gid, ...opts } = args; const response = await asanaClient.getProjectStatusesForProject(project_gid, opts); return { content: [{ type: "text", text: JSON.stringify(response) }], }; } case "asana_create_project_status": { const { project_gid, ...statusData } = args; const response = await asanaClient.createProjectStatus(project_gid, statusData); return { content: [{ type: "text", text: JSON.stringify(response) }], }; } case "asana_delete_project_status": { const { project_status_gid } = args; const response = await asanaClient.deleteProjectStatus(project_status_gid); return { content: [{ type: "text", text: JSON.stringify(response) }], }; } case "asana_get_project_sections": { const { project_id, ...opts } = args; const response = await asanaClient.getProjectSections(project_id, opts); return { content: [{ type: "text", text: JSON.stringify(response) }], }; } case "asana_create_task_story": { const { task_id, text, ...opts } = args; const response = await asanaClient.createTaskStory(task_id, text, opts); return { content: [{ type: "text", text: JSON.stringify(response) }], }; } case "asana_add_task_dependencies": { const { task_id, dependencies } = args; const response = await asanaClient.addTaskDependencies(task_id, dependencies); return { content: [{ type: "text", text: JSON.stringify(response) }], }; } case "asana_add_task_dependents": { const { task_id, dependents } = args; const response = await asanaClient.addTaskDependents(task_id, dependents); return { content: [{ type: "text", text: JSON.stringify(response) }], }; } case "asana_create_subtask": { const { parent_task_id, opt_fields, ...taskData } = args; const response = await asanaClient.createSubtask(parent_task_id, taskData, { opt_fields }); return { content: [{ type: "text", text: JSON.stringify(response) }], }; } case "asana_get_multiple_tasks_by_gid": { const { task_ids, ...opts } = args; // Handle both array and string input const taskIdList = Array.isArray(task_ids) ? task_ids : task_ids.split(',').map((id: string) => id.trim()).filter((id: string) => id.length > 0); const response = await asanaClient.getMultipleTasksByGid(taskIdList, opts); return { content: [{ type: "text", text: JSON.stringify(response) }], }; } case "asana_set_parent_for_task": { let { data, task_id, opts } = args; if ( typeof data == "string" ) { data = JSON.parse( data ); } if ( typeof opts == "string" ) { opts = JSON.parse( opts ); } const response = await asanaClient.setParentForTask(data, task_id, opts); return { content: [{ type: "text", text: JSON.stringify(response) }], }; } case "asana_get_tasks_for_tag": { const { tag_gid, ...opts } = args; const response = await asanaClient.getTasksForTag(tag_gid, opts); return { content: [{ type: "text", text: JSON.stringify(response) }], }; } case "asana_get_tags_for_workspace": { const { workspace_gid, ...opts } = args; const response = await asanaClient.getTagsForWorkspace(workspace_gid || undefined, opts); return { content: [{ type: "text", text: JSON.stringify(response) }], }; } case "asana_create_section_for_project": { const { project_id, name, ...opts } = args; const response = await asanaClient.createSectionForProject(project_id, name, opts); return { content: [{ type: "text", text: JSON.stringify(response) }], }; } case "asana_add_task_to_section": { const { section_id, task_id, ...opts } = args; const response = await asanaClient.addTaskToSection(section_id, task_id, opts); return { content: [{ type: "text", text: JSON.stringify(response) }], }; } case "asana_get_tasks_for_section": { const { section_id, ...opts } = args; const response = await asanaClient.getTasksForSection(section_id, opts); return { content: [{ type: "text", text: JSON.stringify(response) }], }; } case "asana_get_project_hierarchy": { const { project_id, ...opts } = args; const response = await asanaClient.getProjectHierarchy(project_id, opts); return { content: [{ type: "text", text: JSON.stringify(response) }], }; } case "asana_get_subtasks_for_task": { const { task_id, ...opts } = args; const response = await asanaClient.getSubtasksForTask(task_id, opts); return { content: [{ type: "text", text: JSON.stringify(response) }], }; } case "asana_create_project": { const { workspace_id, ...projectData } = args; // Extragem opt_fields pentru opțiuni const { opt_fields, ...restData } = projectData; // Pregătim datele pentru proiect const data = { ...restData }; // Verificăm dacă avem team_id și îl păstrăm, în asana-client-wrapper // va fi redenumit automat în team // Conversia array-urilor în formatul așteptat de API if (data.members && Array.isArray(data.members)) { data.members = data.members.map((id: string) => ({ gid: id })); } if (data.followers && Array.isArray(data.followers)) { data.followers = data.followers.map((id: string) => ({ gid: id })); } const response = await asanaClient.createProjectForWorkspace(workspace_id || undefined, data, { opt_fields }); return { content: [{ type: "text", text: JSON.stringify(response) }], }; } case "asana_update_project": { const { project_id, ...projectData } = args; // Extragem opt_fields pentru opțiuni const { opt_fields, ...restData } = projectData; // Câmpuri problematice care necesită API-uri separate const problematicFields = ['members', 'followers', 'public', 'html_notes', 'start_on']; let hasProblematicFields = false; // Verificăm dacă există câmpuri problematice în datele de actualizare for (const field of problematicFields) { if (field in restData) { // Înlăturăm câmpul problematic din datele trimise către API delete restData[field]; hasProblematicFields = true; } } // Pregătim datele pentru actualizare const data = { ...restData }; const response = await asanaClient.updateProject(project_id, data, { opt_fields }); // Avertizare pentru utilizator dacă au fost înlăturate câmpuri problematice if (hasProblematicFields) { return { content: [ { type: "text", text: "Unele câmpuri nu pot fi actualizate direct prin updateProject și necesită API-uri separate:\n" + "- Pentru a actualiza membrii, folosește asana_add_members_for_project\n" + "- Pentru a actualiza followeri, folosește asana_add_followers_for_project\n" + "- Câmpurile public, html_notes și start_on au de asemenea limitări\n\n" + "Proiectul a fost actualizat cu succes pentru celelalte câmpuri. Iată răspunsul:\n" + JSON.stringify(response) } ], }; } return { content: [{ type: "text", text: JSON.stringify(response) }], }; } case "asana_get_teams_for_user": { const { user_gid, ...opts } = args; const response = await asanaClient.getTeamsForUser(user_gid, opts); return { content: [{ type: "text", text: JSON.stringify(response) }], }; } case "asana_get_teams_for_workspace": { const { workspace_gid, ...opts } = args; const response = await asanaClient.getTeamsForWorkspace(workspace_gid || undefined, opts); return { content: [{ type: "text", text: JSON.stringify(response) }], }; } case "asana_add_followers_to_task": { const { task_id, followers } = args; const response = await asanaClient.addFollowersToTask(task_id, followers); return { content: [{ type: "text", text: JSON.stringify(response) }], }; } case "asana_add_members_for_project": { const { project_id, members, ...opts } = args; const response = await asanaClient.addMembersForProject(project_id, members); return { content: [{ type: "text", text: JSON.stringify(response) }], }; } case "asana_add_followers_for_project": { const { project_id, followers, ...opts } = args; const response = await asanaClient.addFollowersForProject(project_id, followers); return { content: [{ type: "text", text: JSON.stringify(response) }], }; } case "asana_get_tasks_for_project": { const { project_id, ...opts } = args; const response = await asanaClient.getTasksForProject(project_id, opts); return { content: [{ type: "text", text: JSON.stringify(response) }], }; } case "asana_list_workspace_users": { const { workspace_id, ...opts } = args; const response = await asanaClient.getUsersForWorkspace(workspace_id || undefined, opts); return { content: [{ type: "text", text: JSON.stringify(response) }], }; } case "asana_add_tags_to_task": { const { task_id, tag_ids } = args; const response = await asanaClient.addTagsToTask(task_id, tag_ids); return { content: [{ type: "text", text: JSON.stringify(response) }], }; } case "asana_reorder_sections": { const { project_id, section_id, before_section_id, after_section_id } = args; const response = await asanaClient.reorderSections(project_id, section_id, before_section_id, after_section_id); return { content: [{ type: "text", text: JSON.stringify(response) }], }; } case "asana_get_users_for_workspace": { const { workspace_id, ...opts } = args; const response = await asanaClient.getUsersForWorkspace(workspace_id || undefined, opts); return { content: [{ type: "text", text: JSON.stringify(response) }], }; } case "asana_get_attachments_for_object": { const { object_gid, ...opts } = args; const response = await asanaClient.getAttachmentsForObject(object_gid, opts); return { content: [{ type: "text", text: JSON.stringify(response) }], }; } case "asana_upload_attachment_for_object": { const { object_gid, file_path, file_name, file_type } = args; const response = await asanaClient.uploadAttachmentForObject(object_gid, file_path, file_name, file_type); return { content: [{ type: "text", text: JSON.stringify(response) }], }; } case "asana_download_attachment": { const { attachment_gid, output_dir } = args; const response = await asanaClient.downloadAttachment(attachment_gid, output_dir); return { content: [{ type: "text", text: JSON.stringify(response) }], }; } default: throw new Error(`Unknown tool: ${request.params.name}`); } } catch (error: any) { console.error("Error executing tool:", error); // Log detailed error information for debugging if (error.response && error.response.body) { console.error("Response error details:", error.response.body); } // Add more context for specific tools if (request.params.name === "asana_get_task" && error.message === "Not Found" && request.params.arguments) { error = new Error(`Task with ID '${request.params.arguments.task_id}' not found or inaccessible`); } return { content: [ { type: "text", text: formatErrorResponse(error), }, ], }; } }; }

Implementation Reference

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/cristip73/mcp-server-asana'

If you have feedback or need assistance with the MCP directory API, please join our Discord server