Skip to content

Commit 40b6bd3

Browse files
authored
all: add examples of customizing tool schemas, and clarify documentation (#421)
Add example/server/toolschemas, which demonstrates various ways to customize tool schemas, and illustrates the differences between ToolHandler and ToolHandlerFor. Fixes #385 Fixes #386 For #368
1 parent 5ccd97f commit 40b6bd3

File tree

7 files changed

+309
-100
lines changed

7 files changed

+309
-100
lines changed

README.md

Lines changed: 56 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -19,18 +19,6 @@ software development kit (SDK) for the Model Context Protocol (MCP).
1919
> changes. We aim to tag v1.0.0 in September, 2025. See
2020
> https://github.com/modelcontextprotocol/go-sdk/issues/328 for details.
2121
22-
## Design
23-
24-
The design doc for this SDK is at [design.md](./design/design.md), which was
25-
initially reviewed at
26-
[modelcontextprotocol/discussions/364](https://github.com/orgs/modelcontextprotocol/discussions/364).
27-
28-
Further design discussion should occur in
29-
[issues](https://github.com/modelcontextprotocol/go-sdk/issues) (for concrete
30-
proposals) or
31-
[discussions](https://github.com/modelcontextprotocol/go-sdk/discussions) for
32-
open-ended discussion. See [CONTRIBUTING.md](/CONTRIBUTING.md) for details.
33-
3422
## Package documentation
3523

3624
The SDK consists of two importable packages:
@@ -42,12 +30,53 @@ The SDK consists of two importable packages:
4230
- The
4331
[`github.com/modelcontextprotocol/go-sdk/jsonrpc`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/jsonrpc) package is for users implementing
4432
their own transports.
45-
33+
- The
34+
[`github.com/modelcontextprotocol/go-sdk/auth`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/auth)
35+
package provides some primitives for supporting oauth.
36+
37+
## Getting started
38+
39+
To get started creating an MCP server, create an `mcp.Server` instance, add
40+
features to it, and then run it over an `mcp.Transport`. For example, this
41+
server adds a single simple tool, and then connects it to clients over
42+
stdin/stdout:
43+
44+
```go
45+
package main
46+
47+
import (
48+
"context"
49+
"log"
50+
51+
"github.com/modelcontextprotocol/go-sdk/mcp"
52+
)
4653

47-
## Example
54+
type Input struct {
55+
Name string `json:"name" jsonschema:"the name of the person to greet"`
56+
}
57+
58+
type Output struct {
59+
Greeting string `json:"greeting" jsonschema:"the greeting to tell to the user"`
60+
}
61+
62+
func SayHi(ctx context.Context, req *mcp.CallToolRequest, input Input) (*mcp.CallToolResult, Output, error) {
63+
return nil, Output{Greeting: "Hi " + input.Name}, nil
64+
}
65+
66+
func main() {
67+
// Create a server with a single tool.
68+
server := mcp.NewServer(&mcp.Implementation{Name: "greeter", Version: "v1.0.0"}, nil)
69+
mcp.AddTool(server, &mcp.Tool{Name: "greet", Description: "say hi"}, SayHi)
70+
// Run the server over stdin/stdout, until the client disconnects
71+
if err := server.Run(context.Background(), &mcp.StdioTransport{}); err != nil {
72+
log.Fatal(err)
73+
}
74+
}
75+
```
4876

49-
In this example, an MCP client communicates with an MCP server running in a
50-
sidecar process:
77+
To communicate with that server, we can similarly create an `mcp.Client` and
78+
connect it to the corresponding server, by running the server command and
79+
communicating over its stdin/stdout:
5180

5281
```go
5382
package main
@@ -92,42 +121,20 @@ func main() {
92121
}
93122
```
94123

95-
Here's an example of the corresponding server component, which communicates
96-
with its client over stdin/stdout:
97-
98-
```go
99-
package main
100-
101-
import (
102-
"context"
103-
"log"
104-
105-
"github.com/modelcontextprotocol/go-sdk/mcp"
106-
)
107-
108-
type HiParams struct {
109-
Name string `json:"name" jsonschema:"the name of the person to greet"`
110-
}
111-
112-
func SayHi(ctx context.Context, req *mcp.CallToolRequest, args HiParams) (*mcp.CallToolResult, any, error) {
113-
return &mcp.CallToolResult{
114-
Content: []mcp.Content{&mcp.TextContent{Text: "Hi " + args.Name}},
115-
}, nil, nil
116-
}
124+
The [`examples/`](/examples/) directory contains more example clients and
125+
servers.
117126

118-
func main() {
119-
// Create a server with a single tool.
120-
server := mcp.NewServer(&mcp.Implementation{Name: "greeter", Version: "v1.0.0"}, nil)
127+
## Design
121128

122-
mcp.AddTool(server, &mcp.Tool{Name: "greet", Description: "say hi"}, SayHi)
123-
// Run the server over stdin/stdout, until the client disconnects
124-
if err := server.Run(context.Background(), &mcp.StdioTransport{}); err != nil {
125-
log.Fatal(err)
126-
}
127-
}
128-
```
129+
The design doc for this SDK is at [design.md](./design/design.md), which was
130+
initially reviewed at
131+
[modelcontextprotocol/discussions/364](https://github.com/orgs/modelcontextprotocol/discussions/364).
129132

130-
The [`examples/`](/examples/) directory contains more example clients and servers.
133+
Further design discussion should occur in
134+
[issues](https://github.com/modelcontextprotocol/go-sdk/issues) (for concrete
135+
proposals) or
136+
[discussions](https://github.com/modelcontextprotocol/go-sdk/discussions) for
137+
open-ended discussion. See [CONTRIBUTING.md](/CONTRIBUTING.md) for details.
131138

132139
## Acknowledgements
133140

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
// Copyright 2025 The Go MCP SDK Authors. All rights reserved.
2+
// Use of this source code is governed by an MIT-style
3+
// license that can be found in the LICENSE file.
4+
5+
// The toolschemas example demonstrates how to create tools using both the
6+
// low-level [ToolHandler] and high level [ToolHandlerFor], as well as how to
7+
// customize schemas in both cases.
8+
package main
9+
10+
import (
11+
"context"
12+
"encoding/json"
13+
"fmt"
14+
"log"
15+
16+
"github.com/google/jsonschema-go/jsonschema"
17+
"github.com/modelcontextprotocol/go-sdk/mcp"
18+
)
19+
20+
// Input is the input into all the tools handlers below.
21+
type Input struct {
22+
Name string `json:"name" jsonschema:"the person to greet"`
23+
}
24+
25+
// Output is the structured output of the tool.
26+
//
27+
// Not every tool needs to have structured output.
28+
type Output struct {
29+
Greeting string `json:"greeting" jsonschema:"the greeting to send to the user"`
30+
}
31+
32+
// simpleGreeting is an [mcp.ToolHandlerFor] that only cares about input and output.
33+
func simpleGreeting(_ context.Context, _ *mcp.CallToolRequest, input Input) (*mcp.CallToolResult, Output, error) {
34+
return nil, Output{"Hi " + input.Name}, nil
35+
}
36+
37+
// manualGreeter handles the parsing and validation of input and output manually.
38+
//
39+
// Therfore, it needs to close over its resolved schemas, to use them in
40+
// validation.
41+
type manualGreeter struct {
42+
inputSchema *jsonschema.Resolved
43+
outputSchema *jsonschema.Resolved
44+
}
45+
46+
func (t *manualGreeter) greet(_ context.Context, req *mcp.CallToolRequest) (*mcp.CallToolResult, error) {
47+
// errf produces a 'tool error', embedding the error in a CallToolResult.
48+
errf := func(format string, args ...any) *mcp.CallToolResult {
49+
return &mcp.CallToolResult{
50+
Content: []mcp.Content{&mcp.TextContent{Text: fmt.Sprintf(format, args...)}},
51+
IsError: true,
52+
}
53+
}
54+
// Handle the parsing and validation of input and output.
55+
//
56+
// Note that errors here are treated as tool errors, not protocol errors.
57+
var input Input
58+
if err := json.Unmarshal(req.Params.Arguments, &input); err != nil {
59+
return errf("failed to unmarshal arguments: %v", err), nil
60+
}
61+
if err := t.inputSchema.Validate(input); err != nil {
62+
return errf("invalid input: %v", err), nil
63+
}
64+
output := Output{Greeting: "Hi " + input.Name}
65+
if err := t.outputSchema.Validate(output); err != nil {
66+
return errf("tool produced invalid output: %v", err), nil
67+
}
68+
outputJSON, err := json.Marshal(output)
69+
if err != nil {
70+
return errf("output failed to marshal: %v", err), nil
71+
}
72+
return &mcp.CallToolResult{
73+
Content: []mcp.Content{&mcp.TextContent{Text: string(outputJSON)}},
74+
StructuredContent: output,
75+
}, nil
76+
}
77+
78+
func main() {
79+
server := mcp.NewServer(&mcp.Implementation{Name: "greeter"}, nil)
80+
81+
// Add the 'greeting' tool in a few different ways.
82+
83+
// First, we can just use [mcp.AddTool], and get the out-of-the-box handling
84+
// it provides:
85+
mcp.AddTool(server, &mcp.Tool{Name: "simple greeting"}, simpleGreeting)
86+
87+
// Next, we can create our schemas entirely manually, and add them using
88+
// [mcp.Server.AddTool]. Since we're working manually, we can add some
89+
// constraints on the length of the name.
90+
//
91+
// We don't need to do all this work: below, we use jsonschema.For to start
92+
// from the default schema.
93+
var (
94+
manual manualGreeter
95+
err error
96+
)
97+
inputSchema := &jsonschema.Schema{
98+
Type: "object",
99+
Properties: map[string]*jsonschema.Schema{
100+
"name": {Type: "string", MaxLength: jsonschema.Ptr(10)},
101+
},
102+
}
103+
manual.inputSchema, err = inputSchema.Resolve(nil)
104+
if err != nil {
105+
log.Fatal(err)
106+
}
107+
outputSchema := &jsonschema.Schema{
108+
Type: "object",
109+
Properties: map[string]*jsonschema.Schema{
110+
"greeting": {Type: "string"},
111+
},
112+
}
113+
manual.outputSchema, err = outputSchema.Resolve(nil)
114+
if err != nil {
115+
log.Fatal(err)
116+
}
117+
server.AddTool(&mcp.Tool{
118+
Name: "manual greeting",
119+
InputSchema: inputSchema,
120+
OutputSchema: outputSchema,
121+
}, manual.greet)
122+
123+
// Finally, note that we can also use custom schemas with a ToolHandlerFor.
124+
// We can do this in two ways: by using one of the schema values constructed
125+
// above, or by using jsonschema.For and adjusting the resulting schema.
126+
mcp.AddTool(server, &mcp.Tool{
127+
Name: "customized greeting 1",
128+
InputSchema: inputSchema,
129+
// OutputSchema will still be derived from Output.
130+
}, simpleGreeting)
131+
132+
customSchema, err := jsonschema.For[Input](nil)
133+
if err != nil {
134+
log.Fatal(err)
135+
}
136+
customSchema.Properties["name"].MaxLength = jsonschema.Ptr(10)
137+
mcp.AddTool(server, &mcp.Tool{
138+
Name: "customized greeting 2",
139+
InputSchema: customSchema,
140+
}, simpleGreeting)
141+
142+
// Now run the server.
143+
if err := server.Run(context.Background(), &mcp.StdioTransport{}); err != nil {
144+
log.Printf("Server failed: %v", err)
145+
}
146+
}

internal/readme/README.src.md

Lines changed: 26 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -18,18 +18,6 @@ software development kit (SDK) for the Model Context Protocol (MCP).
1818
> changes. We aim to tag v1.0.0 in September, 2025. See
1919
> https://github.com/modelcontextprotocol/go-sdk/issues/328 for details.
2020
21-
## Design
22-
23-
The design doc for this SDK is at [design.md](./design/design.md), which was
24-
initially reviewed at
25-
[modelcontextprotocol/discussions/364](https://github.com/orgs/modelcontextprotocol/discussions/364).
26-
27-
Further design discussion should occur in
28-
[issues](https://github.com/modelcontextprotocol/go-sdk/issues) (for concrete
29-
proposals) or
30-
[discussions](https://github.com/modelcontextprotocol/go-sdk/discussions) for
31-
open-ended discussion. See [CONTRIBUTING.md](/CONTRIBUTING.md) for details.
32-
3321
## Package documentation
3422

3523
The SDK consists of two importable packages:
@@ -41,21 +29,39 @@ The SDK consists of two importable packages:
4129
- The
4230
[`github.com/modelcontextprotocol/go-sdk/jsonrpc`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/jsonrpc) package is for users implementing
4331
their own transports.
44-
32+
- The
33+
[`github.com/modelcontextprotocol/go-sdk/auth`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/auth)
34+
package provides some primitives for supporting oauth.
4535

46-
## Example
36+
## Getting started
4737

48-
In this example, an MCP client communicates with an MCP server running in a
49-
sidecar process:
38+
To get started creating an MCP server, create an `mcp.Server` instance, add
39+
features to it, and then run it over an `mcp.Transport`. For example, this
40+
server adds a single simple tool, and then connects it to clients over
41+
stdin/stdout:
42+
43+
%include server/server.go -
44+
45+
To communicate with that server, we can similarly create an `mcp.Client` and
46+
connect it to the corresponding server, by running the server command and
47+
communicating over its stdin/stdout:
5048

5149
%include client/client.go -
5250

53-
Here's an example of the corresponding server component, which communicates
54-
with its client over stdin/stdout:
51+
The [`examples/`](/examples/) directory contains more example clients and
52+
servers.
5553

56-
%include server/server.go -
54+
## Design
5755

58-
The [`examples/`](/examples/) directory contains more example clients and servers.
56+
The design doc for this SDK is at [design.md](./design/design.md), which was
57+
initially reviewed at
58+
[modelcontextprotocol/discussions/364](https://github.com/orgs/modelcontextprotocol/discussions/364).
59+
60+
Further design discussion should occur in
61+
[issues](https://github.com/modelcontextprotocol/go-sdk/issues) (for concrete
62+
proposals) or
63+
[discussions](https://github.com/modelcontextprotocol/go-sdk/discussions) for
64+
open-ended discussion. See [CONTRIBUTING.md](/CONTRIBUTING.md) for details.
5965

6066
## Acknowledgements
6167

internal/readme/server/server.go

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,20 +12,21 @@ import (
1212
"github.com/modelcontextprotocol/go-sdk/mcp"
1313
)
1414

15-
type HiParams struct {
15+
type Input struct {
1616
Name string `json:"name" jsonschema:"the name of the person to greet"`
1717
}
1818

19-
func SayHi(ctx context.Context, req *mcp.CallToolRequest, args HiParams) (*mcp.CallToolResult, any, error) {
20-
return &mcp.CallToolResult{
21-
Content: []mcp.Content{&mcp.TextContent{Text: "Hi " + args.Name}},
22-
}, nil, nil
19+
type Output struct {
20+
Greeting string `json:"greeting" jsonschema:"the greeting to tell to the user"`
21+
}
22+
23+
func SayHi(ctx context.Context, req *mcp.CallToolRequest, input Input) (*mcp.CallToolResult, Output, error) {
24+
return nil, Output{Greeting: "Hi " + input.Name}, nil
2325
}
2426

2527
func main() {
2628
// Create a server with a single tool.
2729
server := mcp.NewServer(&mcp.Implementation{Name: "greeter", Version: "v1.0.0"}, nil)
28-
2930
mcp.AddTool(server, &mcp.Tool{Name: "greet", Description: "say hi"}, SayHi)
3031
// Run the server over stdin/stdout, until the client disconnects
3132
if err := server.Run(context.Background(), &mcp.StdioTransport{}); err != nil {

0 commit comments

Comments
 (0)