touchdesigner-mcp-server
Version:
MCP server for TouchDesigner
141 lines (140 loc) • 4.51 kB
JavaScript
/**
* Node Details Formatter
*
* Formats TouchDesigner node parameter details with token optimization.
* Used by GET_TD_NODE_PARAMETERS tool.
*/
import { DEFAULT_PRESENTER_FORMAT, presentStructuredData, } from "./presenter.js";
import { finalizeFormattedText, limitArray, mergeFormatterOptions, } from "./responseFormatter.js";
/**
* Format node parameter details
*/
export function formatNodeDetails(data, options) {
const opts = mergeFormatterOptions(options);
if (!data) {
return "No node details available.";
}
const nodePath = data.path;
const properties = data.properties;
const propertyKeys = properties ? Object.keys(properties) : [];
if (propertyKeys.length === 0) {
return `Node: ${nodePath}\nNo properties found.`;
}
if (opts.detailLevel === "detailed") {
return formatDetailed(data, opts.responseFormat);
}
let result;
if (opts.detailLevel === "minimal") {
result = formatMinimal(nodePath, propertyKeys, opts.limit);
}
else {
result = formatSummary(nodePath, data, opts.limit);
}
const context = result.context;
return finalizeFormattedText(result.text, opts, {
context,
structured: context,
template: "nodeDetailsSummary",
});
}
/**
* Minimal mode: Property names only
*/
function formatMinimal(nodePath, propertyKeys, limit) {
const { items, truncated } = limitArray(propertyKeys, limit);
let text = `Node: ${nodePath}\nProperties (${propertyKeys.length}):\n${items.join(", ")}`;
if (truncated) {
text += `\n💡 ${propertyKeys.length - items.length} more properties omitted.`;
}
return {
context: {
displayed: items.length,
id: 0,
name: "",
nodePath,
omittedCount: Math.max(propertyKeys.length - items.length, 0),
properties: items.map((name) => ({ name, value: "" })),
total: propertyKeys.length,
truncated,
type: "",
},
text,
};
}
/**
* Summary mode: Key properties with values
*/
function formatSummary(nodePath, data, limit) {
const properties = data.properties || {};
const propertyEntries = Object.entries(properties);
const { items, truncated } = limitArray(propertyEntries, limit);
let text = `Node: ${nodePath}\n`;
text += `Type: ${data.opType} (ID: ${data.id})\n`;
text += `Name: ${data.name}\n`;
text += `\nProperties (${propertyEntries.length}):\n\n`;
const propsForContext = [];
for (const [key, value] of items) {
const formattedValue = formatPropertyValue(value);
text += ` ${key}: ${formattedValue}\n`;
propsForContext.push({ name: key, value: formattedValue });
}
if (truncated) {
text += `\n💡 ${propertyEntries.length - items.length} more properties omitted.`;
}
return {
context: {
displayed: items.length,
id: data.id,
name: data.name,
nodePath,
omittedCount: Math.max(propertyEntries.length - items.length, 0),
properties: propsForContext,
total: propertyEntries.length,
truncated,
type: data.opType,
},
text,
};
}
/**
* Detailed mode: Full JSON
*/
function formatDetailed(data, format) {
const title = `Node ${data.path ?? data.name ?? "details"}`;
const payloadFormat = format ?? DEFAULT_PRESENTER_FORMAT;
return presentStructuredData({
context: {
payloadFormat,
title,
},
detailLevel: "detailed",
structured: data,
template: "detailedPayload",
text: title,
}, payloadFormat);
}
/**
* Format property value for display
*/
function formatPropertyValue(value) {
if (value === undefined || value === null) {
return "(none)";
}
if (typeof value === "string") {
return value.length > 50 ? `"${value.substring(0, 50)}..."` : `"${value}"`;
}
if (typeof value === "number" || typeof value === "boolean") {
return String(value);
}
if (Array.isArray(value)) {
if (value.length === 0)
return "[]";
if (value.length <= 3)
return `[${value.join(", ")}]`;
return `[${value.slice(0, 3).join(", ")}, ... +${value.length - 3}]`;
}
if (typeof value === "object") {
return `{${Object.keys(value).length} keys}`;
}
return String(value);
}