UNPKG

@prsna_ai/mcp-server

Version:

Model Context Protocol server for PRSNA personality profiles and communication insights

278 lines 11.3 kB
import { z } from 'zod'; import { logger } from '../utils/logger.js'; import { PRSNAApiClient } from '../api/client.js'; import { safeJSONStringify, cleanObject } from '../utils/json.js'; // Input schema const MentionPersonSchema = z.object({ name: z.string().min(1, 'Name is required') }); // Helper to get API client function getApiClient() { const token = process.env.PRSNA_API_TOKEN; if (!token) { throw new Error('PRSNA_API_TOKEN environment variable is required. Please authenticate first.'); } return new PRSNAApiClient(token); } export async function mentionPerson(args) { try { const { name } = MentionPersonSchema.parse(args); logger.info('Looking up person for mention', { name }); const client = getApiClient(); const profile = await client.findProfileByName(name); client.destroy(); if (!profile) { return { content: [{ type: "text", text: safeJSONStringify({ success: false, message: `No profile found for "${name}". Try searching with a partial name or check spelling.`, suggestions: [ 'Try searching with just the first name', 'Check for alternative spellings', 'Use the search_personality_profiles tool for broader matching' ] }, 2) }] }; } // Return condensed profile info optimized for mention context const mentionContext = { success: true, profile: { id: profile._id, name: profile.name, jobTitle: profile.jobTitle, company: profile.company, // Quick personality snapshot personalitySnapshot: { summary: profile.personalitySummary || 'No summary available', keyTraits: profile.keyTraits?.slice(0, 3) || [], dominantDimensions: extractDominantDimensions(profile.dimensions) }, // Essential communication context communicationEssentials: { style: extractCommunicationStyle(profile), quickTips: generateQuickTips(profile), preferredApproach: extractPreferredApproach(profile) }, // Context for immediate use contextForMention: { roleContext: `${profile.jobTitle} at ${profile.company}`, whenToMention: generateWhenToMention(profile), howToEngage: generateHowToEngage(profile) }, // Metadata lastUpdated: profile.updatedAt, profileCompleteness: calculateProfileCompleteness(profile) } }; logger.info('Person found for mention', { name, foundProfile: profile.name, profileId: profile._id }); return { content: [{ type: "text", text: safeJSONStringify(cleanObject(mentionContext), 2) }] }; } catch (error) { const errorMessage = error.message; logger.error('Failed to lookup person for mention', { name: args, error: errorMessage }); return { content: [{ type: "text", text: safeJSONStringify({ success: false, message: 'Failed to lookup person', error: errorMessage }, 2) }] }; } } // Helper functions for generating mention-optimized context function extractDominantDimensions(dimensions) { return dimensions .filter(dim => dim.confidence_percentage >= 70) // Only high-confidence dimensions .sort((a, b) => b.confidence_percentage - a.confidence_percentage) .slice(0, 2) // Top 2 most confident dimensions .map(dim => ({ dimension: dim.dimension, pole: dim.dominant_pole, strength: dim.confidence_percentage >= 85 ? 'strong' : 'moderate' })); } function extractCommunicationStyle(profile) { const dimensions = profile.dimensions || []; // Find key communication-related dimensions const expression = dimensions.find((d) => d.dimension === 'expression'); const reasoning = dimensions.find((d) => d.dimension === 'reasoning'); const tempo = dimensions.find((d) => d.dimension === 'tempo'); const styles = []; if (expression) { styles.push(expression.dominant_pole === 'Internal' ? 'Reflective' : 'Interactive'); } if (reasoning) { styles.push(reasoning.dominant_pole === 'Logic' ? 'Analytical' : 'People-focused'); } if (tempo) { styles.push(tempo.dominant_pole === 'Fast-Paced' ? 'Quick-paced' : 'Thoughtful'); } return styles.length > 0 ? styles.join(', ') : 'Professional'; } function generateQuickTips(profile) { const tips = []; const dimensions = profile.dimensions || []; dimensions.forEach((dim) => { if (dim.confidence_percentage >= 75) { // Only include high-confidence tips switch (dim.dimension) { case 'expression': if (dim.dominant_pole === 'Internal') { tips.push('💭 Give processing time'); } else { tips.push('🗣️ Engage in discussion'); } break; case 'reasoning': if (dim.dominant_pole === 'Logic') { tips.push('📊 Present data/facts'); } else { tips.push('❤️ Consider people impact'); } break; case 'flexibility': if (dim.dominant_pole === 'Structured') { tips.push('📋 Provide clear agendas'); } else { tips.push('🔄 Stay flexible'); } break; case 'work_style': if (dim.dominant_pole === 'Process-Driven') { tips.push('⚙️ Follow procedures'); } else { tips.push('🎯 Focus on outcomes'); } break; case 'tempo': if (dim.dominant_pole === 'Fast-Paced') { tips.push('⚡ Keep it concise'); } else { tips.push('🕐 Allow time to decide'); } break; } } }); return tips.slice(0, 3); // Maximum 3 quick tips for mention context } function extractPreferredApproach(profile) { const dimensions = profile.dimensions || []; // Create a preference-based approach description const approaches = []; const internal = dimensions.some((d) => d.dimension === 'expression' && d.dominant_pole === 'Internal'); const structured = dimensions.some((d) => d.dimension === 'flexibility' && d.dominant_pole === 'Structured'); const logic = dimensions.some((d) => d.dimension === 'reasoning' && d.dominant_pole === 'Logic'); if (internal) { approaches.push('Send information in advance'); } if (structured) { approaches.push('Use structured meetings'); } if (logic) { approaches.push('Lead with facts and data'); } return approaches.length > 0 ? approaches.join(', ') : 'Standard professional communication'; } function generateWhenToMention(profile) { const keyTraits = profile.keyTraits || []; const jobTitle = profile.jobTitle || ''; // Generate context-appropriate mention suggestions const contexts = []; if (jobTitle.toLowerCase().includes('manager') || jobTitle.toLowerCase().includes('director')) { contexts.push('Strategic decisions and planning'); } if (jobTitle.toLowerCase().includes('analyst') || keyTraits.some((t) => t.toLowerCase().includes('analytical'))) { contexts.push('Data analysis and insights'); } if (keyTraits.some((t) => t.toLowerCase().includes('collaborative') || t.toLowerCase().includes('team'))) { contexts.push('Cross-functional collaboration'); } return contexts.length > 0 ? contexts.join(', ') : 'General project discussions and professional matters'; } function generateHowToEngage(profile) { const dimensions = profile.dimensions || []; // Generate engagement approach based on personality const approaches = []; const fastPaced = dimensions.some((d) => d.dimension === 'tempo' && d.dominant_pole === 'Fast-Paced'); const resultsOriented = dimensions.some((d) => d.dimension === 'work_style' && d.dominant_pole === 'Results-Driven'); const external = dimensions.some((d) => d.dimension === 'expression' && d.dominant_pole === 'External'); if (fastPaced || resultsOriented) { approaches.push('Be direct and concise'); } if (external) { approaches.push('Invite discussion and input'); } else { approaches.push('Allow time for consideration'); } return approaches.length > 0 ? approaches.join(', ') : 'Professional and respectful communication'; } function calculateProfileCompleteness(profile) { const requiredFields = ['personalitySummary', 'dimensions', 'keyTraits', 'communicationPreferences']; const optionalFields = ['professionalContext', 'communicationRecommendations', 'operatingStyle']; let score = 0; const missing = []; // Check required fields (70% of score) requiredFields.forEach(field => { if (profile[field] && (Array.isArray(profile[field]) ? profile[field].length > 0 : true)) { score += 17.5; // 70% / 4 fields } else { missing.push(field); } }); // Check optional fields (30% of score) optionalFields.forEach(field => { if (profile[field] && (Array.isArray(profile[field]) ? profile[field].length > 0 : true)) { score += 10; // 30% / 3 fields } }); return { score: Math.round(score), missing: missing.map(field => field.replace(/([A-Z])/g, ' $1').toLowerCase().trim()) }; } // Tool definition for MCP export const mentionTool = { name: "mention_person", description: "Quick lookup for @mention functionality - find person by name and get essential context", inputSchema: { type: "object", properties: { name: { type: "string", description: "Person's name to mention", minLength: 1 } }, required: ["name"] } }; //# sourceMappingURL=mention.js.map