/**
* LiteFarm MCP Server - Tool Utilities
* Shared functions for formatting and processing tool responses
*/
import { CHARACTER_LIMIT } from "./constants.js";
import { ResponseFormat, type PaginatedResponse, type ToolResponse } from "./types.js";
/**
* Format a paginated response with metadata
*/
export function formatPaginatedResponse<T>(
items: T[],
total: number,
offset: number,
limit: number
): PaginatedResponse<T> {
return {
total,
count: items.length,
offset,
items,
has_more: total > offset + items.length,
...(total > offset + items.length ? { next_offset: offset + items.length } : {})
};
}
/**
* Truncate text content if it exceeds CHARACTER_LIMIT
*/
export function truncateIfNeeded(text: string): string {
if (text.length <= CHARACTER_LIMIT) {
return text;
}
const truncated = text.substring(0, CHARACTER_LIMIT);
const additionalChars = text.length - CHARACTER_LIMIT;
return `${truncated}\n\n[Content truncated. ${additionalChars} characters omitted. Use pagination or filtering to reduce response size.]`;
}
/**
* Create a tool response with optional structured content
*/
export function createToolResponse(
text: string,
structuredContent?: { [x: string]: unknown },
isError = false
): ToolResponse {
return {
content: [{
type: "text",
text: truncateIfNeeded(text)
}],
...(structuredContent ? { structuredContent } : {}),
...(isError ? { isError: true } : {})
};
}
/**
* Format date to human-readable string
*/
export function formatDate(dateString: string | undefined): string {
if (!dateString) return "N/A";
const date = new Date(dateString);
return date.toLocaleDateString("en-US", {
year: "numeric",
month: "long",
day: "numeric"
});
}
/**
* Format date with time
*/
export function formatDateTime(dateString: string | undefined): string {
if (!dateString) return "N/A";
const date = new Date(dateString);
return date.toLocaleString("en-US", {
year: "numeric",
month: "long",
day: "numeric",
hour: "2-digit",
minute: "2-digit"
});
}
/**
* Apply pagination parameters with defaults and validation
*/
export function applyPagination<T>(
items: T[],
limit: number,
offset: number
): { paginatedItems: T[]; total: number } {
const total = items.length;
const start = Math.min(offset, total);
const end = Math.min(offset + limit, total);
const paginatedItems = items.slice(start, end);
return { paginatedItems, total };
}
/**
* Convert object to markdown table
*/
export function objectToMarkdownTable(obj: Record<string, unknown>): string {
const rows = Object.entries(obj).map(([key, value]) => {
const formattedKey = key.replace(/_/g, " ").replace(/\b\w/g, l => l.toUpperCase());
const formattedValue = value === null || value === undefined ? "N/A" : String(value);
return `| ${formattedKey} | ${formattedValue} |`;
});
return `| Property | Value |\n|----------|-------|\n${rows.join("\n")}`;
}
/**
* Create markdown list from array of items
*/
export function createMarkdownList(items: string[]): string {
return items.map(item => `- ${item}`).join("\n");
}
/**
* Format a list response based on format preference
*/
export function formatListResponse<T>(
items: T[],
formatter: (item: T) => string,
format: ResponseFormat = ResponseFormat.MARKDOWN,
paginationInfo?: PaginatedResponse<T>
): { text: string; structuredContent?: { [x: string]: unknown } } {
if (format === ResponseFormat.JSON) {
const output = paginationInfo || {
total: items.length,
count: items.length,
offset: 0,
items,
has_more: false
};
return {
text: JSON.stringify(output, null, 2),
structuredContent: output
};
}
// Markdown format
const markdownItems = items.map(formatter).join("\n\n---\n\n");
let paginationText = "";
if (paginationInfo) {
paginationText = `\n\n**Total:** ${paginationInfo.total} | **Showing:** ${paginationInfo.count} | **Offset:** ${paginationInfo.offset}`;
if (paginationInfo.has_more) {
paginationText += ` | **More available** (use offset=${paginationInfo.next_offset})`;
}
}
return {
text: markdownItems + paginationText,
structuredContent: paginationInfo
};
}
/**
* Create error response
*/
export function createErrorResponse(error: unknown): ToolResponse {
const errorMessage = error instanceof Error ? error.message : String(error);
return createToolResponse(`Error: ${errorMessage}`, undefined, true);
}
/**
* Validate required fields in an object
*/
export function validateRequiredFields<T extends Record<string, unknown>>(
obj: T,
requiredFields: (keyof T)[]
): { valid: boolean; missing: string[] } {
const missing: string[] = [];
for (const field of requiredFields) {
if (obj[field] === undefined || obj[field] === null || obj[field] === "") {
missing.push(String(field));
}
}
return {
valid: missing.length === 0,
missing
};
}
/**
* Safe JSON stringify with fallback
*/
export function safeJsonStringify(obj: unknown): string {
try {
return JSON.stringify(obj, null, 2);
} catch (error) {
return String(obj);
}
}