@@ -101,6 +101,310 @@ mcp.WithNumber("price",
101101)
102102```
103103
104+ ## Struct-Based Schema Definition
105+
106+ MCP-Go supports defining input and output schemas using Go structs with automatic JSON schema generation. This provides a type-safe alternative to manual parameter definition, especially useful for complex tools with structured inputs and outputs.
107+
108+ ### Input Schema with Go Structs
109+
110+ Define your input parameters as a Go struct and use ` WithInputSchema ` :
111+
112+ ``` go
113+ // Define input struct with JSON schema tags
114+ type SearchRequest struct {
115+ Query string ` json:"query" jsonschema_description:"Search query" jsonschema:"required"`
116+ Limit int ` json:"limit,omitempty" jsonschema_description:"Maximum results" jsonschema:"minimum=1,maximum=100,default=10"`
117+ Categories []string ` json:"categories,omitempty" jsonschema_description:"Filter by categories"`
118+ SortBy string ` json:"sortBy,omitempty" jsonschema_description:"Sort field" jsonschema:"enum=relevance,enum=date,enum=popularity"`
119+ }
120+
121+ // Create tool with struct-based input schema
122+ searchTool := mcp.NewTool (" search_products" ,
123+ mcp.WithDescription (" Search product catalog" ),
124+ mcp.WithInputSchema [SearchRequest](),
125+ )
126+ ```
127+
128+ ### Output Schema with Go Structs
129+
130+ Define structured output for predictable tool responses:
131+
132+ ``` go
133+ // Define output struct
134+ type SearchResponse struct {
135+ Query string ` json:"query" jsonschema_description:"Original search query"`
136+ TotalCount int ` json:"totalCount" jsonschema_description:"Total matching products"`
137+ Products []Product ` json:"products" jsonschema_description:"Search results"`
138+ ProcessedAt time.Time ` json:"processedAt" jsonschema_description:"When search was performed"`
139+ }
140+
141+ type Product struct {
142+ ID string ` json:"id" jsonschema_description:"Product ID"`
143+ Name string ` json:"name" jsonschema_description:"Product name"`
144+ Price float64 ` json:"price" jsonschema_description:"Price in USD"`
145+ InStock bool ` json:"inStock" jsonschema_description:"Availability"`
146+ }
147+
148+ // Create tool with both input and output schemas
149+ searchTool := mcp.NewTool (" search_products" ,
150+ mcp.WithDescription (" Search product catalog with structured output" ),
151+ mcp.WithInputSchema [SearchRequest](),
152+ mcp.WithOutputSchema [SearchResponse](),
153+ )
154+ ```
155+
156+ ### Structured Tool Handlers
157+
158+ Use ` NewStructuredToolHandler ` for type-safe handler implementation:
159+
160+ ``` go
161+ func main () {
162+ s := server.NewMCPServer (" Product Search" , " 1.0.0" ,
163+ server.WithToolCapabilities (false ),
164+ )
165+
166+ // Define tool with input and output schemas
167+ searchTool := mcp.NewTool (" search_products" ,
168+ mcp.WithDescription (" Search product catalog" ),
169+ mcp.WithInputSchema [SearchRequest](),
170+ mcp.WithOutputSchema [SearchResponse](),
171+ )
172+
173+ // Add tool with structured handler
174+ s.AddTool (searchTool, mcp.NewStructuredToolHandler (searchProductsHandler))
175+
176+ server.ServeStdio (s)
177+ }
178+
179+ // Handler receives typed input and returns typed output
180+ func searchProductsHandler (ctx context .Context , req mcp .CallToolRequest , args SearchRequest ) (SearchResponse , error ) {
181+ // Input is already validated and bound to SearchRequest struct
182+ limit := args.Limit
183+ if limit <= 0 {
184+ limit = 10
185+ }
186+
187+ // Perform search logic
188+ products := searchDatabase (args.Query , args.Categories , limit)
189+
190+ // Return structured response
191+ return SearchResponse{
192+ Query: args.Query ,
193+ TotalCount: len (products),
194+ Products: products,
195+ ProcessedAt: time.Now (),
196+ }, nil
197+ }
198+ ```
199+
200+ ### Array Output Schema
201+
202+ Tools can return arrays of structured data:
203+
204+ ``` go
205+ // Define asset struct
206+ type Asset struct {
207+ ID string ` json:"id" jsonschema_description:"Asset identifier"`
208+ Name string ` json:"name" jsonschema_description:"Asset name"`
209+ Value float64 ` json:"value" jsonschema_description:"Current value"`
210+ Currency string ` json:"currency" jsonschema_description:"Currency code"`
211+ }
212+
213+ // Tool that returns array of assets
214+ assetsTool := mcp.NewTool (" list_assets" ,
215+ mcp.WithDescription (" List portfolio assets" ),
216+ mcp.WithInputSchema [struct {
217+ Portfolio string ` json:"portfolio" jsonschema_description:"Portfolio ID" jsonschema:"required"`
218+ }](),
219+ mcp.WithOutputSchema [[]Asset ](), // Array output schema
220+ )
221+
222+ func listAssetsHandler (ctx context.Context , req mcp.CallToolRequest , args struct { Portfolio string }) ([]Asset , error ) {
223+ // Return array of assets
224+ return []Asset {
225+ {ID: " btc" , Name: " Bitcoin" , Value: 45000.50 , Currency: " USD" },
226+ {ID: " eth" , Name: " Ethereum" , Value: 3200.75 , Currency: " USD" },
227+ }, nil
228+ }
229+ ```
230+
231+ ### Schema Tags Reference
232+
233+ MCP-Go uses the ` jsonschema ` struct tags for schema generation:
234+
235+ ``` go
236+ type ExampleStruct struct {
237+ // Required field
238+ Name string ` json:"name" jsonschema:"required"`
239+
240+ // Field with description
241+ Age int ` json:"age" jsonschema_description:"User age in years"`
242+
243+ // Field with constraints
244+ Score float64 ` json:"score" jsonschema:"minimum=0,maximum=100"`
245+
246+ // Enum field
247+ Status string ` json:"status" jsonschema:"enum=active,enum=inactive,enum=pending"`
248+
249+ // Optional field with default
250+ PageSize int ` json:"pageSize,omitempty" jsonschema:"default=20"`
251+
252+ // Array with constraints
253+ Tags []string ` json:"tags" jsonschema:"minItems=1,maxItems=10"`
254+ }
255+ ```
256+
257+ ### Manual Structured Results
258+
259+ For more control over the response, use ` NewTypedToolHandler ` with manual result creation:
260+
261+ ``` go
262+ manualTool := mcp.NewTool (" process_data" ,
263+ mcp.WithDescription (" Process data with custom result" ),
264+ mcp.WithInputSchema [ProcessRequest](),
265+ mcp.WithOutputSchema [ProcessResponse](),
266+ )
267+
268+ s.AddTool (manualTool, mcp.NewTypedToolHandler (manualProcessHandler))
269+
270+ func manualProcessHandler (ctx context .Context , req mcp .CallToolRequest , args ProcessRequest ) (*mcp .CallToolResult , error ) {
271+ // Process the data
272+ response := ProcessResponse{
273+ Status: " completed" ,
274+ ProcessedAt: time.Now (),
275+ ItemCount: 42 ,
276+ }
277+
278+ // Create custom fallback text for backward compatibility
279+ fallbackText := fmt.Sprintf (" Processed %d items successfully" , response.ItemCount )
280+
281+ // Return structured result with custom text
282+ return mcp.NewToolResultStructured (response, fallbackText), nil
283+ }
284+ ```
285+
286+ ### Complete Example: File Operations with Structured I/O
287+
288+ Here's a complete example using the file operations pattern from earlier, enhanced with structured schemas:
289+
290+ ``` go
291+ // Define structured input for file operations
292+ type FileOperationRequest struct {
293+ Path string ` json:"path" jsonschema_description:"File path" jsonschema:"required"`
294+ Content string ` json:"content,omitempty" jsonschema_description:"File content (for write operations)"`
295+ Encoding string ` json:"encoding,omitempty" jsonschema_description:"File encoding" jsonschema:"enum=utf-8,enum=ascii,enum=base64,default=utf-8"`
296+ }
297+
298+ // Define structured output
299+ type FileOperationResponse struct {
300+ Success bool ` json:"success" jsonschema_description:"Operation success status"`
301+ Path string ` json:"path" jsonschema_description:"File path"`
302+ Message string ` json:"message" jsonschema_description:"Result message"`
303+ Content string ` json:"content,omitempty" jsonschema_description:"File content (for read operations)"`
304+ Size int64 ` json:"size,omitempty" jsonschema_description:"File size in bytes"`
305+ Modified time.Time ` json:"modified,omitempty" jsonschema_description:"Last modified time"`
306+ }
307+
308+ func main () {
309+ s := server.NewMCPServer (" File Manager" , " 1.0.0" ,
310+ server.WithToolCapabilities (true ),
311+ )
312+
313+ // Create file tool with structured I/O
314+ createFileTool := mcp.NewTool (" create_file" ,
315+ mcp.WithDescription (" Create a new file with content" ),
316+ mcp.WithInputSchema [FileOperationRequest](),
317+ mcp.WithOutputSchema [FileOperationResponse](),
318+ )
319+
320+ // Read file tool
321+ readFileTool := mcp.NewTool (" read_file" ,
322+ mcp.WithDescription (" Read file contents" ),
323+ mcp.WithInputSchema [struct {
324+ Path string ` json:"path" jsonschema_description:"File path to read" jsonschema:"required"`
325+ }](),
326+ mcp.WithOutputSchema [FileOperationResponse](),
327+ )
328+
329+ s.AddTool (createFileTool, mcp.NewStructuredToolHandler (handleCreateFile))
330+ s.AddTool (readFileTool, mcp.NewStructuredToolHandler (handleReadFile))
331+
332+ server.ServeStdio (s)
333+ }
334+
335+ func handleCreateFile (ctx context .Context , req mcp .CallToolRequest , args FileOperationRequest ) (FileOperationResponse , error ) {
336+ // Validate path for security
337+ if strings.Contains (args.Path , " .." ) {
338+ return FileOperationResponse{
339+ Success: false ,
340+ Path: args.Path ,
341+ Message: " Invalid path: directory traversal not allowed" ,
342+ }, nil
343+ }
344+
345+ // Handle different encodings
346+ var data []byte
347+ switch args.Encoding {
348+ case " base64" :
349+ var err error
350+ data, err = base64.StdEncoding .DecodeString (args.Content )
351+ if err != nil {
352+ return FileOperationResponse{
353+ Success: false ,
354+ Path: args.Path ,
355+ Message: fmt.Sprintf (" Invalid base64 content: %v " , err),
356+ }, nil
357+ }
358+ default :
359+ data = []byte (args.Content )
360+ }
361+
362+ // Create file
363+ if err := os.WriteFile (args.Path , data, 0644 ); err != nil {
364+ return FileOperationResponse{
365+ Success: false ,
366+ Path: args.Path ,
367+ Message: fmt.Sprintf (" Failed to create file: %v " , err),
368+ }, nil
369+ }
370+
371+ // Get file info
372+ info , _ := os.Stat (args.Path )
373+
374+ return FileOperationResponse{
375+ Success: true ,
376+ Path: args.Path ,
377+ Message: " File created successfully" ,
378+ Size: info.Size (),
379+ Modified: info.ModTime (),
380+ }, nil
381+ }
382+
383+ func handleReadFile (ctx context.Context , req mcp.CallToolRequest , args struct { Path string }) (FileOperationResponse, error ) {
384+ // Read file
385+ data , err := os.ReadFile (args.Path )
386+ if err != nil {
387+ return FileOperationResponse{
388+ Success: false ,
389+ Path: args.Path ,
390+ Message: fmt.Sprintf (" Failed to read file: %v " , err),
391+ }, nil
392+ }
393+
394+ // Get file info
395+ info , _ := os.Stat (args.Path )
396+
397+ return FileOperationResponse {
398+ Success: true ,
399+ Path: args.Path ,
400+ Message: " File read successfully" ,
401+ Content: string (data),
402+ Size: info.Size (),
403+ Modified: info.ModTime (),
404+ }, nil
405+ }
406+ ```
407+
104408## Tool Handlers
105409
106410Tool handlers process the actual function calls from LLMs. MCP-Go provides convenient helper methods for safe parameter extraction.
0 commit comments