Skip to content

Commit e0adb40

Browse files
Grivnclaude
andcommitted
docs: add struct-based schema documentation for tools
Add comprehensive documentation for the new struct-based schema features introduced in commit b46b0d7, including: - Input schema definition using Go structs with WithInputSchema - Output schema definition using WithOutputSchema - Structured tool handlers with NewStructuredToolHandler - Array output schemas - Schema tags reference - Complete file operations example with structured I/O 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent b46b0d7 commit e0adb40

File tree

1 file changed

+304
-0
lines changed

1 file changed

+304
-0
lines changed

www/docs/pages/servers/tools.mdx

Lines changed: 304 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

106410
Tool handlers process the actual function calls from LLMs. MCP-Go provides convenient helper methods for safe parameter extraction.

0 commit comments

Comments
 (0)