touchdesigner-mcp-server
Version:
MCP server for TouchDesigner
99 lines (98 loc) • 3.47 kB
JavaScript
import { TOOL_DEFINITIONS, } from "../toolDefinitions.js";
const MODULE_ROOT = "servers/touchdesigner";
/** snake_case tool name -> camelCase function name (e.g. get_td_info -> getTdInfo). */
function toFunctionName(toolName) {
return toolName.replace(/_(.)/g, (_, char) => char.toUpperCase());
}
/**
* Builds the `describe_td_tools` manifest from the tool table. Parameter
* metadata is introspected from each tool's Zod schema, so it always reflects
* the schema that is actually registered (sourced from the OpenAPI spec).
*/
export function buildToolMetadata(definitions = TOOL_DEFINITIONS) {
return definitions.map((definition) => {
const functionName = toFunctionName(definition.name);
return {
category: definition.category,
description: definition.description,
example: definition.example,
functionName,
modulePath: `${MODULE_ROOT}/${functionName}.ts`,
notes: definition.notes,
parameters: deriveParameters(definition.schema),
returns: definition.returns,
tool: definition.name,
};
});
}
/** Introspects a Zod object schema into flat parameter metadata. */
export function deriveParameters(schema) {
return Object.entries(schema.shape).map(([name, field]) => {
const { base, description, required } = unwrap(field);
return {
description,
name,
required,
type: renderType(base),
};
});
}
/** Peels optional/default/nullable wrappers to reach the base type. */
function unwrap(field) {
let node = field;
let description = node.description;
let required = true;
while (node.def) {
if (description === undefined && node.description) {
description = node.description;
}
const type = node.def.type;
if (type === "optional" ||
type === "default" ||
type === "prefault" ||
type === "nullish") {
required = false;
}
if ((type === "optional" ||
type === "default" ||
type === "prefault" ||
type === "nullable" ||
type === "nullish") &&
node.def.innerType) {
node = node.def.innerType;
continue;
}
break;
}
return { base: node, description: description ?? node.description, required };
}
/** Renders a base Zod type as a TypeScript-flavored type string. */
function renderType(node) {
const def = node.def;
switch (def?.type) {
case "enum":
return Object.values(def.entries ?? {})
.map((value) => `'${value}'`)
.join(" | ");
case "string":
return "string";
case "number":
case "int":
return "number";
case "boolean":
return "boolean";
case "array":
return `Array<${def.element ? renderType(def.element) : "unknown"}>`;
case "record":
return `Record<${def.keyType ? renderType(def.keyType) : "string"}, ${def.valueType ? renderType(def.valueType) : "unknown"}>`;
case "union":
return (def.options ?? []).map(renderType).join(" | ");
case "unknown":
case "any":
return "unknown";
case "object":
return "object";
default:
return def?.type ?? "unknown";
}
}