Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions docs/server.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion examples/server/auth-middleware/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
)

Expand Down
4 changes: 2 additions & 2 deletions examples/server/auth-middleware/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down
2 changes: 1 addition & 1 deletion examples/server/rate-limiting/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
)

Expand Down
4 changes: 2 additions & 2 deletions examples/server/rate-limiting/go.sum
Original file line number Diff line number Diff line change
@@ -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=
Expand Down
79 changes: 52 additions & 27 deletions examples/server/toolschemas/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't you need to ApplyDefaults also? Not necessarily for this CL.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That wasn't in the original, so I didn't add it.
But yes, that seems right. Will do a followup (possibly tomorrow).

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)

Expand All @@ -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)
}
Expand Down
31 changes: 31 additions & 0 deletions examples/server/toolschemas/main_test.go
Original file line number Diff line number Diff line change
@@ -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 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down
7 changes: 4 additions & 3 deletions mcp/streamable_bench_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"context"
"net/http"
"net/http/httptest"
"reflect"
"testing"

"github.com/google/jsonschema-go/jsonschema"
Expand All @@ -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)
Expand Down
11 changes: 6 additions & 5 deletions mcp/tool_example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"encoding/json"
"fmt"
"log"
"reflect"
"time"

"github.com/google/jsonschema-go/jsonschema"
Expand All @@ -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 {
Expand Down Expand Up @@ -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)
Expand Down
Loading