UNPKG

@the_cfdude/productboard-mcp

Version:

Model Context Protocol server for Productboard REST API with dynamic tool loading

555 lines (554 loc) 21.7 kB
/** * Context-aware tools with simplified approach * Provides intelligent response adaptation and user context management */ import { contextAwareAdapter, } from '../utils/context-aware.js'; import { ValidationError } from '../errors/index.js'; /** * Set user context for intelligent response adaptation */ export async function setUserContext(args) { const { sessionId, userPreferences, workspaceContext, instanceContext } = args; // Validate session ID if (!sessionId || typeof sessionId !== 'string') { throw new ValidationError('sessionId is required and must be a string', 'sessionId'); } try { const contextData = { sessionId, ...(userPreferences && { userPreferences }), ...(workspaceContext && { workspaceContext }), ...(instanceContext && { instanceContext }), }; contextAwareAdapter.setContext(sessionId, contextData); return { success: true, sessionId, message: 'User context set successfully', context: { dataFormat: userPreferences?.dataFormat || 'standard', workspace: workspaceContext?.name || 'default', permissions: workspaceContext?.permissions?.length || 0, features: instanceContext?.features?.length || 0, }, }; } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; throw new Error(`Failed to set user context: ${errorMessage}`); } } /** * Get current user context */ export async function getUserContext(args) { const { sessionId } = args; // Validate session ID if (!sessionId || typeof sessionId !== 'string') { throw new ValidationError('sessionId is required and must be a string', 'sessionId'); } try { const context = contextAwareAdapter.getContext(sessionId); return { sessionId, context: { userPreferences: context.userPreferences || null, workspaceContext: context.workspaceContext || null, instanceContext: context.instanceContext || null, recentQueries: context.recentQueries || [], hasContext: Object.keys(context).length > 0, }, stats: contextAwareAdapter.getContextStats(), }; } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; throw new Error(`Failed to get user context: ${errorMessage}`); } } /** * Adapt response based on user context */ export async function adaptResponse(args) { const { sessionId, query, originalResponse, includeGuidance = true, includeSuggestions = true, } = args; // Validate inputs if (!sessionId || typeof sessionId !== 'string') { throw new ValidationError('sessionId is required and must be a string', 'sessionId'); } if (!query || typeof query !== 'string') { throw new ValidationError('query is required and must be a string', 'query'); } try { const adaptedResponse = contextAwareAdapter.adaptResponse(sessionId, query, originalResponse); // Optionally filter response content const response = { adapted: true, sessionId, query, data: adaptedResponse.data, }; if (adaptedResponse.metadata) { response.metadata = adaptedResponse.metadata; } if (includeGuidance && adaptedResponse.userGuidance) { response.userGuidance = adaptedResponse.userGuidance; } if (includeSuggestions && adaptedResponse.metadata?.suggestions) { response.suggestions = adaptedResponse.metadata.suggestions; } return response; } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; throw new Error(`Failed to adapt response: ${errorMessage}`); } } /** * Add custom adaptation rule */ export async function addAdaptationRule(args) { const { sessionId, name, description, priority, condition, adaptation } = args; // Validate inputs if (!name || typeof name !== 'string') { throw new ValidationError('name is required and must be a string', 'name'); } if (!description || typeof description !== 'string') { throw new ValidationError('description is required and must be a string', 'description'); } if (typeof priority !== 'number' || priority < 1 || priority > 10) { throw new ValidationError('priority must be a number between 1 and 10', 'priority'); } try { // Create adaptation rule based on simplified configuration const rule = { condition: createConditionFunction(condition), adaptation: createAdaptationFunction(adaptation), priority, description: `${name}: ${description}`, }; contextAwareAdapter.addAdaptationRule(rule); return { success: true, rule: { name, description, priority, condition: condition.type, adaptation: adaptation.type, }, message: 'Adaptation rule added successfully', ...(sessionId && { sessionId }), }; } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; throw new Error(`Failed to add adaptation rule: ${errorMessage}`); } } /** * Clear user context */ export async function clearUserContext(args) { const { sessionId } = args; // Validate session ID if (!sessionId || typeof sessionId !== 'string') { throw new ValidationError('sessionId is required and must be a string', 'sessionId'); } try { contextAwareAdapter.clearContext(sessionId); return { success: true, sessionId, message: 'User context cleared successfully', }; } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; throw new Error(`Failed to clear user context: ${errorMessage}`); } } /** * Get context-aware system statistics */ export async function getContextStats(_args = {}) { try { const stats = contextAwareAdapter.getContextStats(); // Clean up old contexts as part of stats gathering const cleanedCount = contextAwareAdapter.cleanOldContexts(); return { system: 'context-aware', stats: { totalSessions: stats.totalSessions, activeRules: stats.activeRules, averageAdaptations: stats.averageAdaptations, cleanedSessions: cleanedCount, }, status: 'operational', timestamp: new Date().toISOString(), }; } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; throw new Error(`Failed to get context stats: ${errorMessage}`); } } /** * Create condition function from simplified configuration */ function createConditionFunction(condition) { switch (condition.type) { case 'query_contains': return (_context, query, _response) => { return condition.value ? query.toLowerCase().includes(condition.value.toLowerCase()) : false; }; case 'response_size': return (_context, _query, response) => { const threshold = condition.threshold || 1000; const responseSize = JSON.stringify(response).length; return responseSize > threshold; }; case 'context_field': return (context, _query, _response) => { if (!condition.field) return false; const fieldPath = condition.field.split('.'); let current = context; for (const field of fieldPath) { if (typeof current === 'object' && current !== null) { current = current[field]; } else { return false; } } return condition.value ? current === condition.value : current !== undefined; }; case 'custom': default: return (_context, _query, _response) => false; // Default to false for custom conditions } } /** * Create adaptation function from simplified configuration */ function createAdaptationFunction(adaptation) { switch (adaptation.type) { case 'summarize': return (_context, _query, response) => ({ data: Array.isArray(response) ? response.slice(0, 10) : response, metadata: { responseFormat: 'summarized', adaptations: ['Response summarized due to custom rule'], }, }); case 'simplify': return (_context, _query, response) => ({ data: typeof response === 'object' && response !== null ? { id: response.id, name: response.name } : response, metadata: { responseFormat: 'simplified', adaptations: ['Response simplified due to custom rule'], }, }); case 'enhance': return (_context, _query, response) => ({ data: response, metadata: { responseFormat: 'enhanced', adaptations: ['Response enhanced due to custom rule'], }, userGuidance: { tips: ['This response was enhanced with additional context'], }, }); case 'filter': return (_context, _query, response) => ({ data: response, metadata: { responseFormat: 'filtered', adaptations: ['Response filtered due to custom rule'], }, }); case 'custom': default: return (_context, _query, response) => ({ data: response, metadata: { responseFormat: 'custom', adaptations: ['Custom adaptation applied'], }, }); } } /** * Tool handler function */ export async function handleContextAwareTool(operation, args) { switch (operation) { case 'set_user_context': return setUserContext(args); case 'get_user_context': return getUserContext(args); case 'adapt_response': return adaptResponse(args); case 'add_adaptation_rule': return addAdaptationRule(args); case 'clear_user_context': return clearUserContext(args); case 'get_context_stats': return getContextStats(args); default: throw new ValidationError(`Unknown context-aware operation: ${operation}`, 'operation'); } } /** * Setup context-aware tools definitions */ export function setupContextAwareTools() { return [ { name: 'set_user_context', description: 'Set user context for intelligent response adaptation and personalization.', inputSchema: { type: 'object', properties: { sessionId: { type: 'string', description: 'Unique session identifier for context tracking', }, userPreferences: { type: 'object', properties: { dataFormat: { type: 'string', enum: ['detailed', 'standard', 'basic'], description: 'Preferred response detail level', default: 'standard', }, includeMetadata: { type: 'boolean', description: 'Include metadata in responses', default: true, }, maxResults: { type: 'number', description: 'Maximum number of results to return', minimum: 1, maximum: 1000, default: 50, }, timezone: { type: 'string', description: 'User timezone (e.g., "America/New_York")', }, language: { type: 'string', description: 'Preferred language code (e.g., "en", "es")', }, }, description: 'User preferences for response formatting', }, workspaceContext: { type: 'object', properties: { id: { type: 'string', description: 'Workspace identifier', }, name: { type: 'string', description: 'Workspace name', }, permissions: { type: 'array', items: { type: 'string', }, description: 'List of user permissions in workspace', }, }, description: 'Current workspace context', }, instanceContext: { type: 'object', properties: { name: { type: 'string', description: 'ProductBoard instance name', }, features: { type: 'array', items: { type: 'string', }, description: 'Available instance features', }, limits: { type: 'object', description: 'Instance-specific limits', additionalProperties: { type: 'number', }, }, }, description: 'ProductBoard instance context', }, }, required: ['sessionId'], }, }, { name: 'get_user_context', description: 'Retrieve current user context and preferences.', inputSchema: { type: 'object', properties: { sessionId: { type: 'string', description: 'Session identifier to get context for', }, }, required: ['sessionId'], }, }, { name: 'adapt_response', description: 'Adapt a response based on user context and preferences. Provides intelligent formatting and guidance.', inputSchema: { type: 'object', properties: { sessionId: { type: 'string', description: 'Session identifier for context lookup', }, query: { type: 'string', description: 'Original query that generated the response', }, originalResponse: { description: 'Original response data to adapt', additionalProperties: true, }, includeGuidance: { type: 'boolean', description: 'Include user guidance in adapted response', default: true, }, includeSuggestions: { type: 'boolean', description: 'Include suggestions in adapted response', default: true, }, }, required: ['sessionId', 'query', 'originalResponse'], }, }, { name: 'add_adaptation_rule', description: 'Add custom adaptation rule for response processing. Allows personalized response handling.', inputSchema: { type: 'object', properties: { sessionId: { type: 'string', description: 'Optional session identifier for rule context', }, name: { type: 'string', description: 'Rule name for identification', }, description: { type: 'string', description: 'Rule description explaining its purpose', }, priority: { type: 'number', description: 'Rule priority (1-10, higher numbers = higher priority)', minimum: 1, maximum: 10, }, condition: { type: 'object', properties: { type: { type: 'string', enum: [ 'query_contains', 'response_size', 'context_field', 'custom', ], description: 'Type of condition to check', }, value: { type: 'string', description: 'Value to match (for query_contains, context_field)', }, threshold: { type: 'number', description: 'Threshold value (for response_size)', }, field: { type: 'string', description: 'Context field path (for context_field)', }, }, required: ['type'], description: 'Condition that triggers this rule', }, adaptation: { type: 'object', properties: { type: { type: 'string', enum: ['summarize', 'simplify', 'enhance', 'filter', 'custom'], description: 'Type of adaptation to apply', }, parameters: { type: 'object', description: 'Additional parameters for adaptation', additionalProperties: true, }, }, required: ['type'], description: 'Adaptation to apply when condition matches', }, }, required: [ 'name', 'description', 'priority', 'condition', 'adaptation', ], }, }, { name: 'clear_user_context', description: 'Clear all context data for a session. Useful for privacy or starting fresh.', inputSchema: { type: 'object', properties: { sessionId: { type: 'string', description: 'Session identifier to clear context for', }, }, required: ['sessionId'], }, }, { name: 'get_context_stats', description: 'Get system statistics about context-aware features usage and performance.', inputSchema: { type: 'object', properties: {}, }, }, ]; }