Skip to main content
Glama
userProjectsTools.ts6.38 kB
/** * MCP tools for Projects management * Provides project listing, searching, and task retrieval functionality */ import { z } from 'zod'; import { zodToJsonSchema } from 'zod-to-json-schema'; import { MocoApiService } from '../services/mocoApi.js'; import { createValidationErrorMessage, createEmptyResultMessage } from '../utils/errorHandler.js'; import type { Project, Task } from '../types/mocoTypes.js'; // Schema for get_projects tool with optional search query const GetProjectsSchema = z.object({ query: z.string().optional().describe('Optional search query to find projects by name or description (case-insensitive)') }); // Schema for get_project_tasks tool const GetProjectTasksSchema = z.object({ projectId: z.number().positive().describe('Project ID to retrieve tasks for') }); /** * Tool: get_user_projects * Retrieves all assigned projects or searches for assigned projects by name/description */ export const getUserProjectsTool = { name: 'get_user_projects', description: 'Get all projects assigned to the current user or search within assigned projects by name/description. If no query is provided, returns all assigned projects.', inputSchema: zodToJsonSchema(GetProjectsSchema), handler: async (params: z.infer<typeof GetProjectsSchema>): Promise<string> => { const { query } = params; try { const apiService = new MocoApiService(); // If query is provided and not empty, search; otherwise list all if (query && query.trim()) { const projects = await apiService.searchProjects(query.trim()); if (projects.length === 0) { return createEmptyResultMessage({ type: 'projects', query: query.trim() }); } return formatProjectsSearchResults(projects, query.trim()); } else { const projects = await apiService.getProjects(); if (projects.length === 0) { return createEmptyResultMessage({ type: 'projects' }); } return formatProjectsList(projects); } } catch (error) { return `Error retrieving projects: ${error instanceof Error ? error.message : 'Unknown error'}`; } } }; /** * Tool: get_user_project_tasks * Retrieves all tasks for a specific assigned project */ export const getUserProjectTasksTool = { name: 'get_user_project_tasks', description: 'Get all tasks for a specific assigned project by project ID. Only works for projects assigned to the current user.', inputSchema: zodToJsonSchema(GetProjectTasksSchema), handler: async (params: z.infer<typeof GetProjectTasksSchema>): Promise<string> => { const { projectId } = params; if (!Number.isInteger(projectId) || projectId <= 0) { return createValidationErrorMessage({ field: 'projectId', value: projectId, reason: 'invalid_project_id' }); } try { const apiService = new MocoApiService(); const tasks = await apiService.getProjectTasks(projectId); if (tasks.length === 0) { return createEmptyResultMessage({ type: 'tasks', projectId }); } return formatProjectTasks(tasks, projectId); } catch (error) { return `Error retrieving tasks for project ${projectId}: ${error instanceof Error ? error.message : 'Unknown error'}`; } } }; /** * Formats a list of projects into a readable string */ function formatProjectsList(projects: Project[]): string { const lines: string[] = []; lines.push(`Assigned projects (${projects.length}):\n`); projects.forEach(project => { lines.push(`ID: ${project.id}`); lines.push(`Name: ${project.name}`); if (project.description) { lines.push(`Description: ${project.description}`); } lines.push(`Status: ${project.active ? 'Active' : 'Inactive'}`); if (project.customer) { lines.push(`Customer: ${project.customer.name}`); } if (project.leader) { lines.push(`Leader: ${project.leader.firstname} ${project.leader.lastname}`); } if (project.budget) { lines.push(`Budget: ${project.budget} ${project.currency}`); } lines.push(''); // Empty line between projects }); return lines.join('\\n'); } /** * Formats search results with highlighting of the search term */ function formatProjectsSearchResults(projects: Project[], query: string): string { const lines: string[] = []; lines.push(`Search results for "${query}" (${projects.length} found):\n`); projects.forEach(project => { lines.push(`ID: ${project.id}`); lines.push(`Name: ${highlightSearchTerm(project.name, query)}`); if (project.description) { lines.push(`Description: ${highlightSearchTerm(project.description, query)}`); } lines.push(`Status: ${project.active ? 'Active' : 'Inactive'}`); if (project.customer) { lines.push(`Customer: ${project.customer.name}`); } lines.push(''); // Empty line between projects }); return lines.join('\\n'); } /** * Formats project tasks into a readable string */ function formatProjectTasks(tasks: Task[], projectId: number): string { const lines: string[] = []; lines.push(`Tasks for project ${projectId} (${tasks.length} found):\n`); tasks.forEach(task => { lines.push(`ID: ${task.id}`); lines.push(`Name: ${task.name}`); lines.push(`Status: ${task.active ? 'Active' : 'Inactive'}`); lines.push(`Billable: ${task.billable ? 'Yes' : 'No'}`); lines.push(''); // Empty line between tasks }); return lines.join('\\n'); } /** * Highlights search terms in text (simple text-based highlighting) * @param text - Text to highlight in * @param searchTerm - Term to highlight * @returns Text with highlighted search terms */ function highlightSearchTerm(text: string, searchTerm: string): string { if (!searchTerm.trim()) { return text; } // Case-insensitive replacement with markers const regex = new RegExp(`(${escapeRegExp(searchTerm)})`, 'gi'); return text.replace(regex, '**$1**'); // Using markdown-style bold for highlighting } /** * Escapes special regex characters in a string * @param string - String to escape * @returns Escaped string safe for regex */ function escapeRegExp(string: string): string { return string.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&'); }

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/niondigital/moco-mcp'

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