Skip to main content
Glama
response-formatter.ts6.93 kB
/** * Response Formatter Utility * * Converts tool responses to optimal format (TOON or JSON) based on data structure. * TOON provides 30-60% token savings for uniform tabular data while maintaining * full compatibility with MCP protocol (text content is valid). */ import { encode as encodeTOON } from '@toon-format/toon'; /** * Configuration for response formatting */ export interface FormatOptions { /** * Enable TOON encoding for responses (default: true) * When true, automatically converts uniform arrays to TOON format */ enableTOON?: boolean; /** * Delimiter for TOON arrays (default: ',') * Options: ',' (comma), '\t' (tab), '|' (pipe) * Tab often provides best token efficiency */ delimiter?: ',' | '\t' | '|'; /** * Minimum array length to use TOON (default: 3) * Smaller arrays may not benefit from TOON overhead */ minArrayLength?: number; /** * Enable key folding for nested objects (default: false) * Collapses single-key wrapper chains into dotted paths */ keyFolding?: 'off' | 'safe'; } const DEFAULT_OPTIONS: Required<FormatOptions> = { enableTOON: true, delimiter: '\t', // Tab is most token-efficient minArrayLength: 3, keyFolding: 'off' }; /** * Determines if data structure is suitable for TOON encoding * * TOON excels at: * - Uniform arrays of objects (same fields, primitive values) * - Time-series data * - Tabular data * * JSON is better for: * - Non-uniform data * - Deeply nested structures * - Small arrays (< 3 items) */ function shouldUseTOON(data: any, options: Required<FormatOptions>): boolean { if (!options.enableTOON) return false; // Check if data contains arrays suitable for TOON if (Array.isArray(data)) { return data.length >= options.minArrayLength && isUniformArray(data); } // Check if object contains array properties suitable for TOON if (typeof data === 'object' && data !== null) { return Object.values(data).some(value => Array.isArray(value) && value.length >= options.minArrayLength && isUniformArray(value) ); } return false; } /** * Checks if array contains uniform objects (same keys, primitive values) * * Updated to be more lenient with real-world API data: * - Allows optional fields (not all objects need same keys) * - Requires at least 50% key overlap * - Normalizes data by filling missing fields with null */ function isUniformArray(arr: any[]): boolean { if (arr.length === 0) return false; // Check if all items are objects if (!arr.every(item => typeof item === 'object' && item !== null && !Array.isArray(item))) { return false; } // Collect all unique keys across all objects const allKeys = new Set<string>(); arr.forEach(item => { Object.keys(item).forEach(key => allKeys.add(key)); }); const uniqueKeys = Array.from(allKeys); // Check if objects have reasonable key overlap (at least 50%) const keyOverlapThreshold = 0.5; const hasGoodOverlap = arr.every(item => { const itemKeys = Object.keys(item); const overlap = itemKeys.filter(key => uniqueKeys.includes(key)).length; return overlap / uniqueKeys.length >= keyOverlapThreshold; }); if (!hasGoodOverlap) return false; // Check if all values are primitives or simple objects return arr.every(item => { return Object.values(item).every(value => { const type = typeof value; // Allow primitives, null, and empty objects (like warmup: {}) if (type === 'string' || type === 'number' || type === 'boolean' || value === null) { return true; } // Allow empty objects if (type === 'object' && value !== null && !Array.isArray(value) && Object.keys(value as object).length === 0) { return true; } return false; }); }); } /** * Normalizes array of objects to have consistent keys * Fills missing fields with null for TOON compatibility */ function normalizeArray(arr: any[]): any[] { if (arr.length === 0) return arr; // Collect all unique keys const allKeys = new Set<string>(); arr.forEach(item => { Object.keys(item).forEach(key => allKeys.add(key)); }); const keys = Array.from(allKeys).sort(); // Normalize each object to have all keys return arr.map(item => { const normalized: any = {}; keys.forEach(key => { normalized[key] = item[key] !== undefined ? item[key] : null; }); return normalized; }); } /** * Formats tool response data for optimal token efficiency * * @param data - Response data to format * @param options - Formatting options * @returns Formatted string (TOON or JSON) */ export function formatResponse(data: any, options: Partial<FormatOptions> = {}): string { const opts = { ...DEFAULT_OPTIONS, ...options }; try { // Determine if TOON encoding would be beneficial if (shouldUseTOON(data, opts)) { // Normalize data for TOON encoding let normalizedData = data; // If data is an array, normalize it if (Array.isArray(data)) { normalizedData = normalizeArray(data); } // If data is an object with array properties, normalize those arrays else if (typeof data === 'object' && data !== null) { normalizedData = { ...data }; Object.keys(normalizedData).forEach(key => { if (Array.isArray(normalizedData[key]) && isUniformArray(normalizedData[key])) { normalizedData[key] = normalizeArray(normalizedData[key]); } }); } const toonOutput = encodeTOON(normalizedData, { delimiter: opts.delimiter, keyFolding: opts.keyFolding }); // Check if TOON actually used tabular format (contains "{" for field headers) // If it used YAML-style list format (starts with "- "), it's likely worse than JSON const usedTabularFormat = toonOutput.includes('{') && toonOutput.includes('}:'); if (!usedTabularFormat) { // TOON chose YAML-style format - fall back to JSON for better token efficiency console.error('[Response Formatter] TOON used YAML format instead of tabular, falling back to JSON'); return JSON.stringify(data, null, 2); } // Add format indicator for LLM return `[TOON Format - Tab-delimited]\n${toonOutput}`; } } catch (error) { // Fallback to JSON if TOON encoding fails console.error('[Response Formatter] TOON encoding failed, falling back to JSON:', error); } // Default to pretty-printed JSON return JSON.stringify(data, null, 2); } /** * Creates MCP-compatible content response * * @param data - Response data * @param options - Formatting options * @returns MCP content array */ export function createMCPResponse(data: any, options: Partial<FormatOptions> = {}) { return { content: [ { type: 'text' as const, text: formatResponse(data, options) } ] }; }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/bcharleson/Instantly-MCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server