UNPKG

@prsna_ai/mcp-server

Version:

Model Context Protocol server for PRSNA personality profiles and communication insights

382 lines 14.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 schemas const ListProfilesSchema = z.object({ limit: z.number().min(1).max(100).optional().default(20), offset: z.number().min(0).optional().default(0) }); const SearchProfilesSchema = z.object({ query: z.string().min(1, 'Search query is required'), limit: z.number().min(1).max(50).optional().default(10) }); const GetProfileContextSchema = z.object({ profileId: z.string().optional(), name: z.string().optional() }).refine(data => data.profileId || data.name, { message: "Either profileId or name must be provided" }); // Helper to get API client with token from environment 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 listPersonalityProfiles(args) { try { const { limit, offset } = ListProfilesSchema.parse(args); logger.info('Listing personality profiles', { limit, offset }); const client = getApiClient(); const response = await client.getProfiles(limit, offset); client.destroy(); const result = { success: true, profiles: response.profiles.map(profile => ({ id: profile._id, name: profile.name, jobTitle: profile.jobTitle, company: profile.company, personalitySummary: profile.personalitySummary, dimensions: profile.dimensions, lastUpdated: profile.updatedAt, keyTraits: profile.keyTraits, communicationPreferences: profile.communicationPreferences })), pagination: { total: response.total, limit: response.limit, offset: response.offset, hasMore: response.offset + response.limit < response.total } }; logger.info('Profiles listed successfully', { returned: response.profiles.length, total: response.total }); return { content: [{ type: "text", text: safeJSONStringify(cleanObject(result), 2) }] }; } catch (error) { const errorMessage = error.message; logger.error('Failed to list profiles', { error: errorMessage }); return { content: [{ type: "text", text: safeJSONStringify({ success: false, message: 'Failed to list personality profiles', error: errorMessage }, 2) }] }; } } export async function searchPersonalityProfiles(args) { try { const { query, limit } = SearchProfilesSchema.parse(args); logger.info('Searching personality profiles', { query, limit }); const client = getApiClient(); const response = await client.searchProfiles(query, limit); client.destroy(); const result = { success: true, query, profiles: response.profiles.map(profile => ({ id: profile._id, name: profile.name, jobTitle: profile.jobTitle, company: profile.company, personalitySummary: profile.personalitySummary, dimensions: profile.dimensions, relevance: calculateRelevance(profile, query), keyTraits: profile.keyTraits, industry: profile.industry })), total: response.total }; logger.info('Profile search completed', { query, found: response.total }); return { content: [{ type: "text", text: safeJSONStringify(cleanObject(result), 2) }] }; } catch (error) { const errorMessage = error.message; logger.error('Profile search failed', { error: errorMessage }); return { content: [{ type: "text", text: safeJSONStringify({ success: false, message: 'Failed to search personality profiles', error: errorMessage }, 2) }] }; } } export async function getPersonalityContext(args) { try { const { profileId, name } = GetProfileContextSchema.parse(args); logger.info('Getting personality context', { profileId, name }); const client = getApiClient(); let profile; if (profileId) { profile = await client.getProfile(profileId); } else if (name) { profile = await client.findProfileByName(name); if (!profile) { client.destroy(); return { content: [{ type: "text", text: safeJSONStringify({ success: false, message: `No profile found for name: ${name}` }, 2) }] }; } } client.destroy(); if (!profile) { throw new Error('Profile not found'); } const result = { success: true, profile: { // Basic Information id: profile._id, name: profile.name, jobTitle: profile.jobTitle, company: profile.company, industry: profile.industry, location: profile.location, // Personality Analysis personalitySummary: profile.personalitySummary, professionalContext: profile.professionalContext, keyTraits: profile.keyTraits, operatingStyle: profile.operatingStyle, dominantTraitOverview: profile.dominantTraitOverview, // Personality Dimensions dimensions: profile.dimensions.map(dim => ({ dimension: dim.dimension, dominantPole: dim.dominant_pole, confidence: dim.confidence_percentage, description: getDimensionDescription(dim.dimension, dim.dominant_pole) })), // Communication Guidance communicationPreferences: profile.communicationPreferences, communicationRecommendations: profile.communicationRecommendations, // Additional Context notes: profile.notes, context: profile.context, llmOptimizedProfile: profile.llmOptimizedProfile, // Metadata lastUpdated: profile.updatedAt, linkedInUrl: profile.linkedInUrl } }; logger.info('Personality context retrieved', { profileId: profile._id, name: profile.name }); return { content: [{ type: "text", text: safeJSONStringify(cleanObject(result), 2) }] }; } catch (error) { const errorMessage = error.message; logger.error('Failed to get personality context', { error: errorMessage }); return { content: [{ type: "text", text: safeJSONStringify({ success: false, message: 'Failed to get personality context', error: errorMessage }, 2) }] }; } } export async function myPersonalityProfile(args) { try { logger.info('Getting user\'s own personality profile'); const client = getApiClient(); const userProfile = await client.authenticate(); client.destroy(); const result = { success: true, profile: { // Basic Information id: userProfile._id, name: userProfile.name, email: userProfile.email, // Subscription Information subscriptionStatus: userProfile.subscriptionStatus, subscriptionPlan: userProfile.subscriptionPlan, // Personality Assessment Results dimensions: userProfile.results?.map(dim => ({ dimensionId: dim.dimensionId, dominantPole: dim.dominantPole, score: dim.score, description: getDimensionDescription(dim.dimensionId, dim.dominantPole) })) || [], // Metadata createdAt: userProfile.createdAt, updatedAt: userProfile.updatedAt } }; logger.info('User personality profile retrieved', { userId: userProfile._id, name: userProfile.name }); return { content: [{ type: "text", text: safeJSONStringify(cleanObject(result), 2) }] }; } catch (error) { const errorMessage = error.message; logger.error('Failed to get user personality profile', { error: errorMessage }); return { content: [{ type: "text", text: safeJSONStringify({ success: false, message: 'Failed to get your personality profile', error: errorMessage }, 2) }] }; } } // Helper functions function calculateRelevance(profile, query) { const searchableText = [ profile.name, profile.jobTitle, profile.company, profile.industry || '' ].join(' ').toLowerCase(); const queryTerms = query.toLowerCase().split(' '); const matches = queryTerms.filter(term => searchableText.includes(term)); return Math.round((matches.length / queryTerms.length) * 100); } function getDimensionDescription(dimension, pole) { const descriptions = { expression: { 'Internal': 'Prefers to process thoughts internally before sharing, values reflection and careful consideration', 'External': 'Thinks out loud, processes ideas through discussion, energized by external interaction' }, reasoning: { 'Logic': 'Makes decisions based on objective analysis, facts, and logical reasoning', 'Feeling': 'Considers people and values when making decisions, focuses on impact and harmony' }, flexibility: { 'Structured': 'Prefers clear plans, deadlines, and organized approaches to work', 'Adaptive': 'Comfortable with ambiguity, prefers flexibility and spontaneous approaches' }, work_style: { 'Process-Driven': 'Values following established procedures, systematic approaches, and thoroughness', 'Results-Driven': 'Focuses on outcomes, efficiency, and getting things done quickly' }, tempo: { 'Fast-Paced': 'Prefers quick decisions, rapid execution, and high-energy environments', 'Deliberate-Paced': 'Values careful consideration, thorough analysis, and steady progress' } }; return descriptions[dimension]?.[pole] || `${pole} preference in ${dimension}`; } // Tool definitions for MCP export const profileTools = [ { name: "list_personality_profiles", description: "List all saved personality profiles for the authenticated user", inputSchema: { type: "object", properties: { limit: { type: "number", description: "Maximum number of profiles to return (1-100)", minimum: 1, maximum: 100, default: 20 }, offset: { type: "number", description: "Number of profiles to skip for pagination", minimum: 0, default: 0 } } } }, { name: "search_personality_profiles", description: "Search personality profiles by name, company, or job title", inputSchema: { type: "object", properties: { query: { type: "string", description: "Search query (name, company, or job title)", minLength: 1 }, limit: { type: "number", description: "Maximum number of results to return (1-50)", minimum: 1, maximum: 50, default: 10 } }, required: ["query"] } }, { name: "get_personality_context", description: "Get comprehensive personality context for a specific person", inputSchema: { type: "object", properties: { profileId: { type: "string", description: "Profile ID (optional if name is provided)" }, name: { type: "string", description: "Person's name (optional if profileId is provided)" } } } }, { name: "my_personality_profile", description: "Get your own personality profile and assessment results", inputSchema: { type: "object", properties: {} } } ]; //# sourceMappingURL=profiles.js.map