@the_cfdude/productboard-mcp
Version:
Model Context Protocol server for Productboard REST API with dynamic tool loading
136 lines (135 loc) • 4.4 kB
JavaScript
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,
};
}
}