mcp: update to jsonschema that does not validate structs (#511)
Update to the latest jsonschema commit, which removes the ability to validate structs and changes the type of ForOptions.TypeSchemas.
This commit is contained in:
parent
ef2fc60b5c
commit
33ff851ef3
11 changed files with 107 additions and 45 deletions
|
|
@ -364,9 +364,9 @@ the `Tool.InputSchema` explicitly:
|
|||
|
||||
```go
|
||||
// Distinguished Go types allow custom schemas to be reused during inference.
|
||||
customSchemas := map[any]*jsonschema.Schema{
|
||||
Probability(0): {Type: "number", Minimum: jsonschema.Ptr(0.0), Maximum: jsonschema.Ptr(1.0)},
|
||||
WeatherType(""): {Type: "string", Enum: []any{Sunny, PartlyCloudy, Cloudy, Rainy, Snowy}},
|
||||
customSchemas := map[reflect.Type]*jsonschema.Schema{
|
||||
reflect.TypeFor[Probability](): {Type: "number", Minimum: jsonschema.Ptr(0.0), Maximum: jsonschema.Ptr(1.0)},
|
||||
reflect.TypeFor[WeatherType](): {Type: "string", Enum: []any{Sunny, PartlyCloudy, Cloudy, Rainy, Snowy}},
|
||||
}
|
||||
opts := &jsonschema.ForOptions{TypeSchemas: customSchemas}
|
||||
in, err := jsonschema.For[WeatherInput](opts)
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ require (
|
|||
)
|
||||
|
||||
require (
|
||||
github.com/google/jsonschema-go v0.2.3 // indirect
|
||||
github.com/google/jsonschema-go v0.3.0 // indirect
|
||||
github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@ github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeD
|
|||
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/jsonschema-go v0.2.3 h1:dkP3B96OtZKKFvdrUSaDkL+YDx8Uw9uC4Y+eukpCnmM=
|
||||
github.com/google/jsonschema-go v0.2.3/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE=
|
||||
github.com/google/jsonschema-go v0.3.0 h1:6AH2TxVNtk3IlvkkhjrtbUc4S8AvO0Xii0DxIygDg+Q=
|
||||
github.com/google/jsonschema-go v0.3.0/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE=
|
||||
github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4=
|
||||
github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4=
|
||||
golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo=
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ require (
|
|||
)
|
||||
|
||||
require (
|
||||
github.com/google/jsonschema-go v0.2.3 // indirect
|
||||
github.com/google/jsonschema-go v0.3.0 // indirect
|
||||
github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/jsonschema-go v0.2.3 h1:dkP3B96OtZKKFvdrUSaDkL+YDx8Uw9uC4Y+eukpCnmM=
|
||||
github.com/google/jsonschema-go v0.2.3/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE=
|
||||
github.com/google/jsonschema-go v0.3.0 h1:6AH2TxVNtk3IlvkkhjrtbUc4S8AvO0Xii0DxIygDg+Q=
|
||||
github.com/google/jsonschema-go v0.3.0/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE=
|
||||
github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4=
|
||||
github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4=
|
||||
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
|
||||
|
|
|
|||
|
|
@ -54,27 +54,72 @@ func (t *manualGreeter) greet(_ context.Context, req *mcp.CallToolRequest) (*mcp
|
|||
// Handle the parsing and validation of input and output.
|
||||
//
|
||||
// Note that errors here are treated as tool errors, not protocol errors.
|
||||
|
||||
// First, unmarshal to a map[string]any and validate.
|
||||
if err := unmarshalAndValidate(req.Params.Arguments, t.inputSchema); err != nil {
|
||||
return errf("invalid input: %v", err), nil
|
||||
}
|
||||
|
||||
// Now unmarshal again to input.
|
||||
var input Input
|
||||
if err := json.Unmarshal(req.Params.Arguments, &input); err != nil {
|
||||
return errf("failed to unmarshal arguments: %v", err), nil
|
||||
}
|
||||
if err := t.inputSchema.Validate(input); err != nil {
|
||||
return errf("invalid input: %v", err), nil
|
||||
}
|
||||
output := Output{Greeting: "Hi " + input.Name}
|
||||
if err := t.outputSchema.Validate(output); err != nil {
|
||||
return errf("tool produced invalid output: %v", err), nil
|
||||
}
|
||||
outputJSON, err := json.Marshal(output)
|
||||
if err != nil {
|
||||
return errf("output failed to marshal: %v", err), nil
|
||||
}
|
||||
//
|
||||
if err := unmarshalAndValidate(outputJSON, t.outputSchema); err != nil {
|
||||
return errf("invalid output: %v", err), nil
|
||||
}
|
||||
|
||||
return &mcp.CallToolResult{
|
||||
Content: []mcp.Content{&mcp.TextContent{Text: string(outputJSON)}},
|
||||
StructuredContent: output,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// unmarshalAndValidate unmarshals data to a map[string]any, then validates that against res.
|
||||
func unmarshalAndValidate(data []byte, res *jsonschema.Resolved) error {
|
||||
var m map[string]any
|
||||
if err := json.Unmarshal(data, &m); err != nil {
|
||||
return err
|
||||
}
|
||||
return res.Validate(m)
|
||||
}
|
||||
|
||||
var (
|
||||
inputSchema = &jsonschema.Schema{
|
||||
Type: "object",
|
||||
Properties: map[string]*jsonschema.Schema{
|
||||
"name": {Type: "string", MaxLength: jsonschema.Ptr(10)},
|
||||
},
|
||||
}
|
||||
outputSchema = &jsonschema.Schema{
|
||||
Type: "object",
|
||||
Properties: map[string]*jsonschema.Schema{
|
||||
"greeting": {Type: "string"},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
func newManualGreeter() (*manualGreeter, error) {
|
||||
resIn, err := inputSchema.Resolve(nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resOut, err := outputSchema.Resolve(nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &manualGreeter{
|
||||
inputSchema: resIn,
|
||||
outputSchema: resOut,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
server := mcp.NewServer(&mcp.Implementation{Name: "greeter"}, nil)
|
||||
|
||||
|
|
@ -90,27 +135,7 @@ func main() {
|
|||
//
|
||||
// We don't need to do all this work: below, we use jsonschema.For to start
|
||||
// from the default schema.
|
||||
var (
|
||||
manual manualGreeter
|
||||
err error
|
||||
)
|
||||
inputSchema := &jsonschema.Schema{
|
||||
Type: "object",
|
||||
Properties: map[string]*jsonschema.Schema{
|
||||
"name": {Type: "string", MaxLength: jsonschema.Ptr(10)},
|
||||
},
|
||||
}
|
||||
manual.inputSchema, err = inputSchema.Resolve(nil)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
outputSchema := &jsonschema.Schema{
|
||||
Type: "object",
|
||||
Properties: map[string]*jsonschema.Schema{
|
||||
"greeting": {Type: "string"},
|
||||
},
|
||||
}
|
||||
manual.outputSchema, err = outputSchema.Resolve(nil)
|
||||
manual, err := newManualGreeter()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
|
|
|||
31
examples/server/toolschemas/main_test.go
Normal file
31
examples/server/toolschemas/main_test.go
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
// Copyright 2025 The Go MCP SDK Authors. All rights reserved.
|
||||
// Use of this source code is governed by an MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/modelcontextprotocol/go-sdk/mcp"
|
||||
)
|
||||
|
||||
func TestGreet(t *testing.T) {
|
||||
manual, err := newManualGreeter()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
res, err := manual.greet(context.Background(), &mcp.CallToolRequest{
|
||||
Params: &mcp.CallToolParamsRaw{
|
||||
Arguments: json.RawMessage(`{"name": "Bob"}`),
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if res.IsError {
|
||||
t.Fatalf("tool error: %q", res.Content[0].(*mcp.TextContent).Text)
|
||||
}
|
||||
}
|
||||
2
go.mod
2
go.mod
|
|
@ -5,7 +5,7 @@ go 1.23.0
|
|||
require (
|
||||
github.com/golang-jwt/jwt/v5 v5.2.2
|
||||
github.com/google/go-cmp v0.7.0
|
||||
github.com/google/jsonschema-go v0.2.3
|
||||
github.com/google/jsonschema-go v0.3.0
|
||||
github.com/yosida95/uritemplate/v3 v3.0.2
|
||||
golang.org/x/tools v0.34.0
|
||||
)
|
||||
|
|
|
|||
4
go.sum
4
go.sum
|
|
@ -4,6 +4,10 @@ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
|||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/jsonschema-go v0.2.3 h1:dkP3B96OtZKKFvdrUSaDkL+YDx8Uw9uC4Y+eukpCnmM=
|
||||
github.com/google/jsonschema-go v0.2.3/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE=
|
||||
github.com/google/jsonschema-go v0.2.4-0.20250922144851-e08864c65371 h1:e1VCqWtKpTYBOBhPcgGV5whTlMFpTbH5Ghm56wpxBsk=
|
||||
github.com/google/jsonschema-go v0.2.4-0.20250922144851-e08864c65371/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE=
|
||||
github.com/google/jsonschema-go v0.3.0 h1:6AH2TxVNtk3IlvkkhjrtbUc4S8AvO0Xii0DxIygDg+Q=
|
||||
github.com/google/jsonschema-go v0.3.0/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE=
|
||||
github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4=
|
||||
github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4=
|
||||
golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo=
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import (
|
|||
"context"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/google/jsonschema-go/jsonschema"
|
||||
|
|
@ -17,9 +18,9 @@ import (
|
|||
func BenchmarkStreamableServing(b *testing.B) {
|
||||
// This benchmark measures how fast we can handle a single tool on a
|
||||
// streamable server, including tool validation and stream management.
|
||||
customSchemas := map[any]*jsonschema.Schema{
|
||||
Probability(0): {Type: "number", Minimum: jsonschema.Ptr(0.0), Maximum: jsonschema.Ptr(1.0)},
|
||||
WeatherType(""): {Type: "string", Enum: []any{Sunny, PartlyCloudy, Cloudy, Rainy, Snowy}},
|
||||
customSchemas := map[reflect.Type]*jsonschema.Schema{
|
||||
reflect.TypeFor[Probability](): {Type: "number", Minimum: jsonschema.Ptr(0.0), Maximum: jsonschema.Ptr(1.0)},
|
||||
reflect.TypeFor[WeatherType](): {Type: "string", Enum: []any{Sunny, PartlyCloudy, Cloudy, Rainy, Snowy}},
|
||||
}
|
||||
opts := &jsonschema.ForOptions{TypeSchemas: customSchemas}
|
||||
in, err := jsonschema.For[WeatherInput](opts)
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import (
|
|||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
"github.com/google/jsonschema-go/jsonschema"
|
||||
|
|
@ -34,8 +35,8 @@ func ExampleAddTool_customMarshalling() {
|
|||
// In this case, you can use jsonschema.For along with jsonschema.ForOptions
|
||||
// to customize the schema inference for your custom type.
|
||||
inputSchema, err := jsonschema.For[Input](&jsonschema.ForOptions{
|
||||
TypeSchemas: map[any]*jsonschema.Schema{
|
||||
MyDate{}: {Type: "string"},
|
||||
TypeSchemas: map[reflect.Type]*jsonschema.Schema{
|
||||
reflect.TypeFor[MyDate](): {Type: "string"},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
|
|
@ -152,9 +153,9 @@ func ExampleAddTool_complexSchema() {
|
|||
// !+customschemas
|
||||
|
||||
// Distinguished Go types allow custom schemas to be reused during inference.
|
||||
customSchemas := map[any]*jsonschema.Schema{
|
||||
Probability(0): {Type: "number", Minimum: jsonschema.Ptr(0.0), Maximum: jsonschema.Ptr(1.0)},
|
||||
WeatherType(""): {Type: "string", Enum: []any{Sunny, PartlyCloudy, Cloudy, Rainy, Snowy}},
|
||||
customSchemas := map[reflect.Type]*jsonschema.Schema{
|
||||
reflect.TypeFor[Probability](): {Type: "number", Minimum: jsonschema.Ptr(0.0), Maximum: jsonschema.Ptr(1.0)},
|
||||
reflect.TypeFor[WeatherType](): {Type: "string", Enum: []any{Sunny, PartlyCloudy, Cloudy, Rainy, Snowy}},
|
||||
}
|
||||
opts := &jsonschema.ForOptions{TypeSchemas: customSchemas}
|
||||
in, err := jsonschema.For[WeatherInput](opts)
|
||||
|
|
|
|||
Loading…
Reference in a new issue