@@ -2,6 +2,7 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
22import type { CallToolResult , TextContent } from '@modelcontextprotocol/sdk/types.js'
33import type { CreateAssemblyParams , LintAssemblyInstructionsResult } from '@transloadit/node'
44import {
5+ extractFieldNamesFromTemplate ,
56 getRobotHelp ,
67 listRobots ,
78 mergeTemplateContent ,
@@ -411,6 +412,21 @@ const mergeImportOverrides = (
411412 return nextOverrides
412413}
413414
415+ const collectFieldNames = ( templateContent : string ) : string [ ] => {
416+ const names = extractFieldNamesFromTemplate ( templateContent ) . map ( ( field ) => field . fieldName )
417+ return Array . from ( new Set ( names ) )
418+ }
419+
420+ const mergeFieldValues = (
421+ templateFields : Record < string , unknown > | undefined ,
422+ argFields : Record < string , unknown > | undefined ,
423+ ) : Record < string , unknown > => {
424+ return {
425+ ...( templateFields ?? { } ) ,
426+ ...( argFields ?? { } ) ,
427+ }
428+ }
429+
414430const getAssemblyIdFromUrl = ( assemblyUrl : string ) : string => {
415431 const match = assemblyUrl . match ( / \/ a s s e m b l i e s \/ ( [ ^ / ? # ] + ) / )
416432 if ( ! match ) {
@@ -637,7 +653,9 @@ export const createTransloaditMcpServer = (
637653 let inputFilesForPrep = fileInputs
638654 let params = parseInstructions ( instructions ) ?? ( { } as CreateAssemblyParams )
639655 let allowStepsOverride = true
656+ let mergedInstructions : CreateAssemblyParams | undefined
640657 let mergedSteps : Record < string , unknown > | undefined
658+ let mergedFields : Record < string , unknown > | undefined
641659
642660 if ( builtin_template ) {
643661 const templateId = buildBuiltinTemplateId ( builtin_template . slug , builtin_template . version )
@@ -651,29 +669,35 @@ export const createTransloaditMcpServer = (
651669 params . template_id = templateId
652670 }
653671
654- if ( hasUrlInputs ) {
655- let analysis = analyzeSteps ( isRecord ( params . steps ) ? params . steps : { } )
656-
657- if ( params . template_id ) {
658- templatePathHint = templatePathHint ?? 'instructions.template_id'
659- const template = await client . getTemplate ( params . template_id )
660- allowStepsOverride = template . content . allow_steps_override !== false
661- try {
662- const merged = mergeTemplateContent ( template . content , params )
663- mergedSteps = isRecord ( merged . steps ) ? ( merged . steps as Record < string , unknown > ) : { }
664- analysis = analyzeSteps ( mergedSteps )
665- } catch ( error ) {
666- if ( error instanceof Error && error . message === 'TEMPLATE_DENIES_STEPS_OVERRIDE' ) {
667- return buildToolError (
668- 'mcp_template_override_denied' ,
669- 'Template forbids step overrides; remove steps overrides or choose a different template.' ,
670- { path : templatePathHint } ,
671- )
672- }
673- throw error
672+ let analysis = analyzeSteps ( isRecord ( params . steps ) ? params . steps : { } )
673+
674+ if ( params . template_id ) {
675+ templatePathHint = templatePathHint ?? 'instructions.template_id'
676+ const template = await client . getTemplate ( params . template_id )
677+ allowStepsOverride = template . content . allow_steps_override !== false
678+ try {
679+ const merged = mergeTemplateContent ( template . content , params )
680+ mergedInstructions = merged as CreateAssemblyParams
681+ mergedSteps = isRecord ( merged . steps ) ? ( merged . steps as Record < string , unknown > ) : { }
682+ mergedFields = isRecord ( merged . fields ) ? ( merged . fields as Record < string , unknown > ) : { }
683+ analysis = analyzeSteps ( mergedSteps )
684+ } catch ( error ) {
685+ if ( error instanceof Error && error . message === 'TEMPLATE_DENIES_STEPS_OVERRIDE' ) {
686+ return buildToolError (
687+ 'mcp_template_override_denied' ,
688+ 'Template forbids step overrides; remove steps overrides or choose a different template.' ,
689+ { path : templatePathHint } ,
690+ )
674691 }
692+ throw error
675693 }
694+ } else {
695+ mergedInstructions = params
696+ mergedSteps = isRecord ( params . steps ) ? ( params . steps as Record < string , unknown > ) : { }
697+ mergedFields = isRecord ( params . fields ) ? ( params . fields as Record < string , unknown > ) : { }
698+ }
676699
700+ if ( hasUrlInputs ) {
677701 if ( ! analysis . hasHttpImport && ! analysis . requiresUpload ) {
678702 inputFilesForPrep = fileInputs . filter ( ( file ) => file . kind !== 'url' )
679703 warnings . push ( {
@@ -706,6 +730,31 @@ export const createTransloaditMcpServer = (
706730 }
707731 }
708732 }
733+
734+ if ( mergedInstructions ) {
735+ const fieldTemplateContent = JSON . stringify ( mergedInstructions )
736+ const requiredFields = collectFieldNames ( fieldTemplateContent )
737+ const providedFields = mergeFieldValues (
738+ mergedFields ,
739+ isRecord ( fields ) ? ( fields as Record < string , unknown > ) : undefined ,
740+ )
741+ const missingFields = requiredFields . filter ( ( fieldName ) => ! ( fieldName in providedFields ) )
742+ const effectiveMissing =
743+ hasUrlInputs && analysis . hasHttpImport
744+ ? missingFields . filter ( ( fieldName ) => fieldName !== 'input' )
745+ : missingFields
746+
747+ if ( effectiveMissing . length > 0 ) {
748+ return buildToolError (
749+ 'mcp_missing_fields' ,
750+ `Missing required fields: ${ effectiveMissing . join ( ', ' ) } ` ,
751+ {
752+ path : 'fields' ,
753+ hint : 'Provide these field names under the fields argument.' ,
754+ } ,
755+ )
756+ }
757+ }
709758 const prep = await prepareInputFiles ( {
710759 inputFiles : inputFilesForPrep ,
711760 params,
0 commit comments