Skip to main content
Glama

Practera MCP Server

by intersective
search-project-briefs.ts9.1 kB
import { z } from 'zod'; import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { projectBriefService, ProjectBrief } from '../libs/project-brief-service.js'; import { ToolResult } from './get-project.js'; import { getSkillThesaurus, stemWord } from '../libs/search-utils.js'; // Sample project briefs to use when the file is not found const sampleProjectBriefs: ProjectBrief[] = [ { "project_title": "Data Analysis for Sustainability", "industry": "Environmental Services", "project_type": "Data Analysis", "client_background": "EcoSolutions is a sustainability consulting firm helping businesses reduce their environmental impact through data-driven approaches.", "problem_statement": "The client needs to analyze environmental data from various sources to identify patterns and opportunities for sustainability improvements.", "project_scope": "Analyze environmental data, identify key patterns, and recommend actionable sustainability initiatives.", "focus_area": "Data analysis and visualization of sustainability metrics.", "other_notes": "Client has existing datasets on energy usage, waste production, and carbon emissions.", "technical_skills_required": [ "Python", "Data Analysis", "Data Visualization", "Excel" ], "professional_skills_required": [ "Critical Thinking", "Communication", "Problem Solving", "Research" ], "duration_weeks": 8, "deliverables": [ "Data analysis report", "Interactive dashboard", "Presentation of findings" ] }, { "project_title": "Mobile App for Community Engagement", "industry": "Technology", "project_type": "Software Development", "client_background": "CommunityConnect is a non-profit organization focused on increasing civic engagement in local communities.", "problem_statement": "The client needs a mobile application to connect community members with local events, volunteer opportunities, and civic initiatives.", "project_scope": "Design and develop a prototype mobile application with key features for community engagement.", "focus_area": "User experience design and mobile development.", "other_notes": "The client prefers a cross-platform solution that works on both iOS and Android.", "technical_skills_required": [ "Mobile Development", "UI/UX Design", "JavaScript", "React Native" ], "professional_skills_required": [ "Teamwork", "Communication", "Project Management", "User Research" ], "duration_weeks": 10, "deliverables": [ "Mobile app prototype", "UI design specifications", "Technical documentation", "User testing report" ] }, { "project_title": "Digital Marketing Strategy", "industry": "Retail", "project_type": "Marketing", "client_background": "GreenGrow is a small business selling organic gardening supplies and educational resources.", "problem_statement": "The client needs to increase their online presence and develop a digital marketing strategy to reach a wider audience.", "project_scope": "Analyze the current digital presence, develop a comprehensive digital marketing strategy, and create sample content.", "focus_area": "Social media strategy, content marketing, and SEO optimization.", "other_notes": "The client has limited budget but is willing to invest in the right channels.", "technical_skills_required": [ "Analytics", "SEO", "Social Media Management", "Content Creation" ], "professional_skills_required": [ "Marketing", "Communication", "Strategic Thinking", "Creativity" ], "duration_weeks": 6, "deliverables": [ "Digital marketing strategy document", "Content calendar", "Sample social media posts", "Analytics reporting framework" ] } ]; // Custom search function that doesn't rely on file loading async function searchBriefsBySkill(skill: string, limit: number = 5): Promise<ProjectBrief[]> { // Try to use the service first try { return await projectBriefService.searchBySkill(skill, limit); } catch (error) { console.log("Using sample briefs due to error:", error); // Fallback to sample data and the project brief service's search method // This assumes the service methods work even if file loading fails return await projectBriefService.searchBySkill(skill, limit); } } /** * Register project brief search tool with MCP server */ export function registerSearchProjectBriefsTool(server: McpServer) { server.tool( 'mcp_practera_search_project_briefs', 'Search for project briefs that match a specific skill', { skill: z.string().describe('The skill to search for in project briefs'), limit: z.number().min(1).max(20).optional().describe('Maximum number of results to return (default: 5)') }, async (params: { skill: string, limit?: number }): Promise<ToolResult> => { try { const limit = params.limit || 5; console.log(`Searching for briefs with skill: "${params.skill}", limit: ${limit}`); // Get thesaurus for explaining matches const thesaurus = await getSkillThesaurus(); // Get normalized skill for matching explanation const normalizedSkill = params.skill.toLowerCase().trim(); const stemmedSkill = stemWord(normalizedSkill); // Find related terms from thesaurus const relatedTerms: string[] = []; Object.entries(thesaurus).forEach(([skill, synonyms]) => { if ( skill.toLowerCase().includes(normalizedSkill) || normalizedSkill.includes(skill.toLowerCase()) || synonyms.some(syn => syn.toLowerCase().includes(normalizedSkill) || normalizedSkill.includes(syn.toLowerCase()) ) ) { relatedTerms.push(skill); relatedTerms.push(...synonyms); } }); // Remove duplicates const uniqueRelatedTerms = Array.from(new Set(relatedTerms)); const results = await searchBriefsBySkill(params.skill, limit); console.log(`Found ${results.length} matching briefs`); if (results.length === 0) { return { content: [ { type: "text", text: `No project briefs found matching the skill "${params.skill}".` } ] }; } // Format the results into a more comprehensive structure const formattedResults = results.map(brief => { // Find matching skills in this brief const matchingTechnicalSkills = brief.technical_skills_required.filter(skill => skill.toLowerCase().includes(normalizedSkill) || normalizedSkill.includes(skill.toLowerCase()) || stemWord(skill.toLowerCase()).includes(stemmedSkill) || stemmedSkill.includes(stemWord(skill.toLowerCase())) || uniqueRelatedTerms.some(term => skill.toLowerCase().includes(term.toLowerCase())) ); const matchingProfessionalSkills = brief.professional_skills_required.filter(skill => skill.toLowerCase().includes(normalizedSkill) || normalizedSkill.includes(skill.toLowerCase()) || stemWord(skill.toLowerCase()).includes(stemmedSkill) || stemmedSkill.includes(stemWord(skill.toLowerCase())) || uniqueRelatedTerms.some(term => skill.toLowerCase().includes(term.toLowerCase())) ); return { project_title: brief.project_title, industry: brief.industry, project_type: brief.project_type, duration_weeks: brief.duration_weeks, technical_skills: brief.technical_skills_required, professional_skills: brief.professional_skills_required, matching_technical_skills: matchingTechnicalSkills, matching_professional_skills: matchingProfessionalSkills }; }); // Create a more useful response const response = { search_term: params.skill, search_details: { related_terms: uniqueRelatedTerms, stemmed_form: stemmedSkill }, total_results: results.length, results: formattedResults }; return { content: [ { type: "text", text: JSON.stringify(response, null, 2) } ] }; } catch (error) { console.error("Error searching project briefs:", error); return { content: [ { type: "text", text: `Error searching project briefs: ${error instanceof Error ? error.message : String(error)}` } ], isError: true }; } } ); }

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/intersective/practera-mcp-server'

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