UNPKG

@the_cfdude/productboard-mcp

Version:

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

136 lines (135 loc) 4.4 kB
import { EntityFieldMappings } from './search-field-mappings.js'; export class OutputProcessor { entityMappings = EntityFieldMappings; /** * Process search results based on output specification */ processOutput(data, entityType, output) { if (output === 'full') { return data; } if (output === 'ids-only') { return data.map(item => item.id); } if (output === 'summary') { return this.applySummaryOutput(data, entityType); } if (Array.isArray(output)) { return this.applyFieldSelection(data, output); } console.error('[DEBUG OutputProcessor] No matching output mode, returning full data. Output was:', output); return data; } /** * Apply summary output mode */ applySummaryOutput(data, entityType) { const entityConfig = this.entityMappings[entityType]; if (!entityConfig || !entityConfig.summaryFields) { // Fallback to basic fields return data.map(item => ({ id: item.id, name: item.name || item.title, ...(item.status && { status: item.status }), })); } return this.applyFieldSelection(data, entityConfig.summaryFields); } /** * Apply field selection to data */ applyFieldSelection(data, fields) { return data.map(item => this.extractFields(item, fields)); } /** * Extract specific fields from an object, supporting dot notation */ extractFields(obj, fields) { const result = {}; for (const field of fields) { const value = this.getNestedValue(obj, field); this.setNestedValue(result, field, value); } return result; } /** * Get nested value using dot notation (e.g., "owner.email") */ getNestedValue(obj, path) { if (!obj || !path) return undefined; const keys = path.split('.'); let current = obj; for (const key of keys) { if (current === null || current === undefined) { return undefined; } current = current[key]; } return current; } /** * Set nested value using dot notation */ setNestedValue(obj, path, value) { if (!path) return; const keys = path.split('.'); const lastKey = keys.pop(); let current = obj; // Create nested structure for (const key of keys) { if (!(key in current) || typeof current[key] !== 'object') { current[key] = {}; } current = current[key]; } current[lastKey] = value; } /** * Check if field exists in data structure */ isFieldAvailable(obj, field) { return this.getNestedValue(obj, field) !== undefined; } /** * Get all available fields in an object (for debugging/validation) */ getAvailableFields(obj, prefix = '') { const fields = []; for (const key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { const fullPath = prefix ? `${prefix}.${key}` : key; if (obj[key] !== null && typeof obj[key] === 'object' && !Array.isArray(obj[key])) { // Recursively get nested fields fields.push(...this.getAvailableFields(obj[key], fullPath)); } else { fields.push(fullPath); } } } return fields; } /** * Validate that requested fields are available in the data */ validateFieldsAvailability(data, fields) { if (data.length === 0) { return { availableFields: [], missingFields: fields, }; } const sampleItem = data[0]; const availableFields = this.getAvailableFields(sampleItem); const missingFields = fields.filter(field => !availableFields.includes(field) && this.getNestedValue(sampleItem, field) === undefined); return { availableFields: availableFields.filter(field => fields.includes(field)), missingFields, }; } }