11import { z } from 'zod'
2+ import type { McpToolSchema , McpToolSchemaProperty } from '@/lib/mcp/types'
23import { normalizeInputFormatValue } from '@/lib/workflows/input-format'
4+ import { generateWorkflowInputShape } from '@/lib/workflows/input-schema'
35import { isInputDefinitionTrigger } from '@/lib/workflows/triggers/input-definition-triggers'
46import type { InputFormatField } from '@/lib/workflows/types'
5- import type { McpToolSchema } from './types'
6-
7- /**
8- * Extended property definition for workflow tool schemas.
9- * More specific than the generic McpToolSchema properties.
10- */
11- export interface McpToolProperty {
12- [ key : string ] : unknown
13- type : string
14- description ?: string
15- items ?: McpToolProperty
16- properties ?: Record < string , McpToolProperty >
17- }
18-
19- /**
20- * Extended MCP tool schema with typed properties (for workflow tool generation).
21- * Extends the base McpToolSchema with more specific property types.
22- */
23- export interface McpToolInputSchema extends McpToolSchema {
24- properties : Record < string , McpToolProperty >
25- }
26-
27- export interface McpToolDefinition {
28- name : string
29- description : string
30- inputSchema : McpToolInputSchema
31- }
32-
33- /**
34- * File item Zod schema for MCP file inputs.
35- * This is the single source of truth for file structure.
36- */
37- export const fileItemZodSchema = z . object ( {
38- name : z . string ( ) . describe ( 'File name' ) ,
39- data : z . string ( ) . describe ( 'Base64 encoded file content' ) ,
40- mimeType : z . string ( ) . describe ( 'MIME type of the file' ) ,
41- } )
42-
43- /**
44- * Convert InputFormatField type to Zod schema
45- */
46- function fieldTypeToZod ( fieldType : string | undefined , isRequired : boolean ) : z . ZodTypeAny {
47- let zodType : z . ZodTypeAny
48-
49- switch ( fieldType ) {
50- case 'string' :
51- zodType = z . string ( )
52- break
53- case 'number' :
54- zodType = z . number ( )
55- break
56- case 'boolean' :
57- zodType = z . boolean ( )
58- break
59- case 'object' :
60- zodType = z . record ( z . string ( ) , z . any ( ) )
61- break
62- case 'array' :
63- zodType = z . array ( z . any ( ) )
64- break
65- case 'files' :
66- zodType = z . array ( fileItemZodSchema )
67- break
68- default :
69- zodType = z . string ( )
70- }
71-
72- return isRequired ? zodType : zodType . optional ( )
73- }
74-
75- /**
76- * Generate Zod schema shape from InputFormatField array.
77- * This is used directly by the MCP server for tool registration.
78- */
79- export function generateToolZodSchema ( inputFormat : InputFormatField [ ] ) : z . ZodRawShape | undefined {
80- if ( ! inputFormat || inputFormat . length === 0 ) {
81- return undefined
82- }
83-
84- const shape : Record < string , z . ZodTypeAny > = { }
85-
86- for ( const field of inputFormat ) {
87- if ( ! field . name ) continue
88-
89- const zodType = fieldTypeToZod ( field . type , true )
90- shape [ field . name ] = field . name ? zodType . describe ( field . name ) : zodType
91- }
92-
93- return Object . keys ( shape ) . length > 0 ? shape : undefined
94- }
95-
96- /**
97- * Map InputFormatField type to JSON Schema type (for database storage)
98- */
99- function mapFieldTypeToJsonSchemaType ( fieldType : string | undefined ) : string {
100- switch ( fieldType ) {
101- case 'string' :
102- return 'string'
103- case 'number' :
104- return 'number'
105- case 'boolean' :
106- return 'boolean'
107- case 'object' :
108- return 'object'
109- case 'array' :
110- return 'array'
111- case 'files' :
112- return 'array'
113- default :
114- return 'string'
115- }
116- }
1177
1188/**
1199 * Sanitize a workflow name to be a valid MCP tool name.
@@ -136,54 +26,15 @@ export function sanitizeToolName(name: string): string {
13626 * This converts the workflow's input format definition to JSON Schema format
13727 * that MCP clients can use to understand tool parameters.
13828 */
139- export function generateToolInputSchema ( inputFormat : InputFormatField [ ] ) : McpToolInputSchema {
140- const properties : Record < string , McpToolProperty > = { }
141- const required : string [ ] = [ ]
142-
143- for ( const field of inputFormat ) {
144- if ( ! field . name ) continue
145-
146- const fieldName = field . name
147- const fieldType = mapFieldTypeToJsonSchemaType ( field . type )
148-
149- const property : McpToolProperty = {
150- type : fieldType ,
151- // Use custom description if provided, otherwise use field name
152- description : field . description ?. trim ( ) || fieldName ,
153- }
154-
155- // Handle array types
156- if ( fieldType === 'array' ) {
157- if ( field . type === 'file[]' ) {
158- property . items = {
159- type : 'object' ,
160- properties : {
161- name : { type : 'string' , description : 'File name' } ,
162- url : { type : 'string' , description : 'File URL' } ,
163- type : { type : 'string' , description : 'MIME type' } ,
164- size : { type : 'number' , description : 'File size in bytes' } ,
165- } ,
166- }
167- // Use custom description if provided, otherwise use default
168- if ( ! field . description ?. trim ( ) ) {
169- property . description = 'Array of file objects'
170- }
171- } else {
172- property . items = { type : 'string' }
173- }
174- }
175-
176- properties [ fieldName ] = property
177-
178- // All fields are considered required by default
179- // (in the future, we could add an optional flag to InputFormatField)
180- required . push ( fieldName )
181- }
182-
29+ export function generateToolInputSchema ( inputFormat : InputFormatField [ ] ) : McpToolSchema {
30+ const schema = z . toJSONSchema ( z . object ( generateWorkflowInputShape ( inputFormat ) ) , {
31+ target : 'draft-07' ,
32+ io : 'input' ,
33+ } )
18334 return {
18435 type : 'object' ,
185- properties,
186- required : required . length > 0 ? required : undefined ,
36+ properties : schema . properties as Record < string , McpToolSchemaProperty > ,
37+ ... ( schema . required ? .length ? { required : schema . required } : { } ) ,
18738 }
18839}
18940
@@ -198,10 +49,10 @@ export function applyDescriptionOverrides(
19849 overrides : Record < string , string > | null | undefined
19950) : Record < string , unknown > {
20051 if ( ! overrides || Object . keys ( overrides ) . length === 0 ) return baseSchema
201- const baseProperties = baseSchema . properties as Record < string , McpToolProperty > | undefined
52+ const baseProperties = baseSchema . properties as Record < string , McpToolSchemaProperty > | undefined
20253 if ( ! baseProperties ) return baseSchema
20354
204- const properties : Record < string , McpToolProperty > = { }
55+ const properties : Record < string , McpToolSchemaProperty > = { }
20556 for ( const [ name , property ] of Object . entries ( baseProperties ) ) {
20657 const override = overrides [ name ]
20758 properties [ name ] =
@@ -244,7 +95,7 @@ export function extractDescriptionOverrides(
24495 | Record < string , { description ?: unknown } >
24596 | undefined
24697 if ( ! schemaProperties ) return overrides
247- const baseProperties = ( baseSchema . properties ?? { } ) as Record < string , McpToolProperty >
98+ const baseProperties = ( baseSchema . properties ?? { } ) as Record < string , McpToolSchemaProperty >
24899
249100 for ( const [ name , property ] of Object . entries ( schemaProperties ) ) {
250101 if ( ! ( name in baseProperties ) ) continue
0 commit comments