touchdesigner-mcp-server
Version:
MCP server for TouchDesigner
188 lines (187 loc) • 5.8 kB
JavaScript
/**
* Script Result Formatter
*
* Formats Python script execution results with token optimization.
* Used by EXECUTE_PYTHON_SCRIPT tool.
*/
import { DEFAULT_PRESENTER_FORMAT, presentStructuredData, } from "./presenter.js";
import { finalizeFormattedText, mergeFormatterOptions, } from "./responseFormatter.js";
/**
* Format script execution result
*/
export function formatScriptResult(data, scriptSnippet, options) {
const opts = mergeFormatterOptions(options);
if (!data) {
return "No result returned.";
}
const success = data.success ?? true;
const result = data.data?.result;
const stdout = data.data?.stdout;
const stderr = data.data?.stderr;
const error = data.data?.error;
// Error case - always show full details
if (!success || error) {
return formatError(error, scriptSnippet);
}
if (opts.detailLevel === "detailed") {
return formatDetailed(data, opts.responseFormat);
}
let formattedText = "";
let context;
switch (opts.detailLevel) {
case "minimal":
formattedText = formatMinimal(result);
context = buildScriptContext(scriptSnippet, result, stdout, stderr);
break;
case "summary": {
const summary = formatSummary(result, stdout, stderr, scriptSnippet);
formattedText = summary.text;
context = summary.context;
break;
}
}
const ctx = context;
return finalizeFormattedText(formattedText, opts, {
context: ctx,
structured: ctx,
template: "scriptSummary",
});
}
/**
* Format error result
*/
function formatError(error, scriptSnippet) {
const errorMsg = typeof error === "string" ? error : JSON.stringify(error);
const snippet = scriptSnippet
? `\nScript: ${truncateScript(scriptSnippet)}`
: "";
return `❌ Script execution failed:${snippet}\n\nError: ${errorMsg}`;
}
/**
* Minimal mode: Just the result value
*/
function formatMinimal(result) {
if (result === undefined || result === null) {
return "✓ Script executed successfully (no return value)";
}
if (typeof result === "string" ||
typeof result === "number" ||
typeof result === "boolean") {
return `✓ Result: ${result}`;
}
if (Array.isArray(result)) {
return `✓ Result: Array[${result.length}]`;
}
if (typeof result === "object") {
const keys = Object.keys(result);
return `✓ Result: Object{${keys.length} keys}`;
}
return `✓ Result: ${String(result)}`;
}
/**
* Summary mode: Result with context
*/
function formatSummary(result, stdout, stderr, scriptSnippet) {
let formatted = "✓ Script executed successfully\n\n";
const snippet = scriptSnippet ? truncateScript(scriptSnippet) : "";
if (snippet) {
formatted += `Script: ${snippet}\n\n`;
}
let resultPreview = "(none)";
if (result !== undefined && result !== null) {
resultPreview = formatResultValue(result, 500);
formatted += `Result: ${resultPreview}\n`;
}
const outputPreview = truncateOutput(stdout);
if (outputPreview) {
formatted += `\nOutput:\n${outputPreview}`;
}
const stderrPreview = truncateOutput(stderr);
if (stderrPreview) {
formatted += `\nStderr:\n${stderrPreview}`;
}
return {
context: {
hasOutput: Boolean(outputPreview),
hasStderr: Boolean(stderrPreview),
outputPreview,
outputType: getValueType(stdout),
resultPreview,
resultType: getValueType(result),
snippet,
stderrPreview,
},
text: formatted,
};
}
function buildScriptContext(scriptSnippet, result, stdout, stderr) {
return {
hasOutput: Boolean(stdout?.trim()),
hasStderr: Boolean(stderr?.trim()),
outputPreview: stdout,
outputType: getValueType(stdout),
resultPreview: formatResultValue(result ?? "", 200),
resultType: getValueType(result),
snippet: scriptSnippet ? truncateScript(scriptSnippet) : "",
stderrPreview: stderr,
};
}
/**
* Truncate captured stdout/stderr for display
*/
function truncateOutput(output) {
if (!output?.trim()) {
return undefined;
}
return output.length > 200 ? `${output.substring(0, 200)}...` : output;
}
/**
* Detailed mode: Full JSON
*/
function formatDetailed(data, format) {
const title = "Script Result";
const payloadFormat = format ?? DEFAULT_PRESENTER_FORMAT;
return presentStructuredData({
context: {
payloadFormat,
title,
},
detailLevel: "detailed",
structured: data,
template: "detailedPayload",
text: title,
}, payloadFormat);
}
function getValueType(value) {
if (value === undefined || value === null) {
return "none";
}
if (Array.isArray(value)) {
return `array(${value.length})`;
}
return typeof value;
}
/**
* Format result value with size limit
*/
function formatResultValue(value, maxChars) {
const str = typeof value === "object" ? JSON.stringify(value, null, 2) : String(value);
if (str.length <= maxChars) {
return str;
}
return `${str.substring(0, maxChars)}...\n💡 Result truncated. Use detailLevel='detailed' for full output.`;
}
/**
* Truncate script for display
*/
function truncateScript(script, maxLength = 100) {
const trimmed = script.trim();
if (trimmed.length <= maxLength) {
return trimmed;
}
const firstLine = trimmed.split("\n")[0];
if (firstLine.length <= maxLength) {
return `${firstLine}...`;
}
return `${trimmed.substring(0, maxLength)}...`;
}