UNPKG

touchdesigner-mcp-server

Version:
91 lines (90 loc) 3.98 kB
import { z } from "zod"; import { TOOL_NAMES } from "../../../core/constants.js"; import { handleToolError } from "../../../core/errorHandling.js"; import { buildToolMetadata, } from "../metadata/touchDesignerToolMetadata.js"; import { formatToolMetadata } from "../presenter/index.js"; import { TOOL_DEFINITIONS } from "../toolDefinitions.js"; import { detailOnlyFormattingSchema } from "../types.js"; const describeToolsSchema = detailOnlyFormattingSchema.extend({ filter: z .string() .min(1) .describe("Optional keyword to filter by tool name, module path, or parameter description") .optional(), }); export function registerTdTools(server, logger, tdClient) { // Register every TouchDesigner operation from the single source of truth. for (const definition of TOOL_DEFINITIONS) { server.tool(definition.name, definition.description, definition.schema.strict().shape, async (params = {}) => { try { const output = await definition.run({ logger, params, tdClient }); return createToolResult(tdClient, output); } catch (error) { return handleToolError(error, logger, definition.name, definition.errorComment); } }); } // `describe_td_tools` is the meta tool: it documents the tools above rather // than calling TouchDesigner, so it is registered on its own. const toolMetadataEntries = buildToolMetadata(TOOL_DEFINITIONS); server.tool(TOOL_NAMES.DESCRIBE_TD_TOOLS, "Generate a filesystem-oriented manifest of available TouchDesigner tools", describeToolsSchema.strict().shape, async (params = {}) => { try { const { detailLevel, responseFormat, filter } = params; const normalizedFilter = filter?.trim().toLowerCase(); const filteredEntries = normalizedFilter ? toolMetadataEntries.filter((entry) => matchesMetadataFilter(entry, normalizedFilter)) : toolMetadataEntries; if (filteredEntries.length === 0) { const message = filter ? `No TouchDesigner tools matched filter "${filter}".` : "No TouchDesigner tools are registered."; return { content: [{ text: message, type: "text" }], }; } const formattedText = formatToolMetadata(filteredEntries, { detailLevel: detailLevel ?? (filter ? "summary" : "minimal"), filter: normalizedFilter, responseFormat, }); return { content: [{ text: formattedText, type: "text" }], }; } catch (error) { return handleToolError(error, logger, TOOL_NAMES.DESCRIBE_TD_TOOLS); } }); } const createToolResult = (tdClient, output) => { const content = typeof output === "string" ? [{ text: output, type: "text" }] : output.content.map((block) => block.type === "image" ? { data: block.data, mimeType: block.mimeType, type: "image", } : { text: block.text, type: "text" }); const additionalContents = tdClient.getAdditionalToolResultContents(); if (additionalContents) { content.push(...additionalContents); } return { content }; }; function matchesMetadataFilter(entry, keyword) { const normalizedKeyword = keyword.toLowerCase(); const haystacks = [ entry.functionName, entry.modulePath, entry.description, entry.category, entry.tool, entry.notes ?? "", ]; if (haystacks.some((value) => value.toLowerCase().includes(normalizedKeyword))) { return true; } return entry.parameters.some((param) => [param.name, param.type, param.description ?? ""].some((value) => value.toLowerCase().includes(normalizedKeyword))); }