Skip to content

Commit b96cfa9

Browse files
authored
Merge branch 'main' into smoke-test
2 parents 32c81a7 + d5d455d commit b96cfa9

5 files changed

Lines changed: 331 additions & 0 deletions

File tree

mcp-client-go/.env.example

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
ANTHROPIC_API_KEY=your_api_key_here

mcp-client-go/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# An LLM-Powered Chatbot MCP Client written in Go
2+
3+
See the [Building MCP clients](https://modelcontextprotocol.io/tutorials/building-a-client) tutorial for more information.

mcp-client-go/go.mod

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
module github.com/modelcontextprotocol/quickstart-resources/mcp-client-go
2+
3+
go 1.25.1
4+
5+
require (
6+
github.com/anthropics/anthropic-sdk-go v1.14.0
7+
github.com/joho/godotenv v1.5.1
8+
github.com/modelcontextprotocol/go-sdk v1.0.0
9+
)
10+
11+
require (
12+
github.com/google/jsonschema-go v0.3.0 // indirect
13+
github.com/tidwall/gjson v1.18.0 // indirect
14+
github.com/tidwall/match v1.1.1 // indirect
15+
github.com/tidwall/pretty v1.2.1 // indirect
16+
github.com/tidwall/sjson v1.2.5 // indirect
17+
github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
18+
)

mcp-client-go/go.sum

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
github.com/anthropics/anthropic-sdk-go v1.14.0 h1:EzNQvnZlaDHe2UPkoUySDz3ixRgNbwKdH8KtFpv7pi4=
2+
github.com/anthropics/anthropic-sdk-go v1.14.0/go.mod h1:WTz31rIUHUHqai2UslPpw5CwXrQP3geYBioRV4WOLvE=
3+
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
4+
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
5+
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
6+
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
7+
github.com/google/jsonschema-go v0.3.0 h1:6AH2TxVNtk3IlvkkhjrtbUc4S8AvO0Xii0DxIygDg+Q=
8+
github.com/google/jsonschema-go v0.3.0/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE=
9+
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
10+
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
11+
github.com/modelcontextprotocol/go-sdk v1.0.0 h1:Z4MSjLi38bTgLrd/LjSmofqRqyBiVKRyQSJgw8q8V74=
12+
github.com/modelcontextprotocol/go-sdk v1.0.0/go.mod h1:nYtYQroQ2KQiM0/SbyEPUWQ6xs4B95gJjEalc9AQyOs=
13+
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
14+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
15+
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
16+
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
17+
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
18+
github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
19+
github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
20+
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
21+
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
22+
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
23+
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
24+
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
25+
github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
26+
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
27+
github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4=
28+
github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4=
29+
golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo=
30+
golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg=
31+
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
32+
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

mcp-client-go/main.go

Lines changed: 277 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,277 @@
1+
package main
2+
3+
import (
4+
"bufio"
5+
"context"
6+
"encoding/json"
7+
"fmt"
8+
"log"
9+
"os"
10+
"os/exec"
11+
"strings"
12+
13+
"github.com/anthropics/anthropic-sdk-go"
14+
"github.com/anthropics/anthropic-sdk-go/option"
15+
"github.com/joho/godotenv"
16+
"github.com/modelcontextprotocol/go-sdk/mcp"
17+
)
18+
19+
var model anthropic.Model = anthropic.ModelClaudeSonnet4_5_20250929
20+
21+
type MCPClient struct {
22+
anthropic *anthropic.Client
23+
session *mcp.ClientSession
24+
tools []anthropic.ToolUnionParam
25+
}
26+
27+
func NewMCPClient() (*MCPClient, error) {
28+
// Load .env file
29+
if err := godotenv.Load(); err != nil {
30+
return nil, fmt.Errorf("failed to load .env file: %w", err)
31+
}
32+
33+
apiKey := os.Getenv("ANTHROPIC_API_KEY")
34+
if apiKey == "" {
35+
return nil, fmt.Errorf("ANTHROPIC_API_KEY environment variable not set")
36+
}
37+
38+
client := anthropic.NewClient(option.WithAPIKey(apiKey))
39+
40+
return &MCPClient{
41+
anthropic: &client,
42+
}, nil
43+
}
44+
45+
func (c *MCPClient) ConnectToServer(ctx context.Context, serverArgs []string) error {
46+
if len(serverArgs) == 0 {
47+
return fmt.Errorf("no server command provided")
48+
}
49+
50+
// Create command to spawn server process
51+
cmd := exec.CommandContext(ctx, serverArgs[0], serverArgs[1:]...)
52+
53+
// Create MCP client
54+
client := mcp.NewClient(
55+
&mcp.Implementation{
56+
Name: "mcp-client-go",
57+
Version: "0.1.0",
58+
},
59+
nil,
60+
)
61+
62+
// Connect using CommandTransport
63+
transport := &mcp.CommandTransport{
64+
Command: cmd,
65+
}
66+
67+
session, err := client.Connect(ctx, transport, nil)
68+
if err != nil {
69+
return fmt.Errorf("failed to connect to server: %w", err)
70+
}
71+
72+
c.session = session
73+
74+
// List available tools
75+
toolsResult, err := session.ListTools(ctx, &mcp.ListToolsParams{})
76+
if err != nil {
77+
return fmt.Errorf("failed to list tools: %w", err)
78+
}
79+
80+
var toolNames []string
81+
82+
// Convert MCP tools to Anthropic tool format
83+
for _, tool := range toolsResult.Tools {
84+
toolNames = append(toolNames, tool.Name)
85+
anthropicTool, err := mcpToolToAnthropicTool(tool)
86+
if err != nil {
87+
return fmt.Errorf("failed to convert mcp tool to anthropic tool: %w", err)
88+
}
89+
c.tools = append(c.tools, anthropicTool)
90+
}
91+
92+
fmt.Printf("Connected to server with tools: %v\n", toolNames)
93+
return nil
94+
}
95+
96+
func mcpToolToAnthropicTool(tool *mcp.Tool) (anthropic.ToolUnionParam, error) {
97+
var zeroTool anthropic.ToolUnionParam
98+
schemaJSON, err := json.Marshal(tool.InputSchema)
99+
if err != nil {
100+
return zeroTool, fmt.Errorf("failed to marshal input schema of mcp tool: %w", err)
101+
}
102+
var schema anthropic.ToolInputSchemaParam
103+
err = json.Unmarshal(schemaJSON, &schema)
104+
if err != nil {
105+
return zeroTool, fmt.Errorf("failed to unmarshal to anthropic input schema: %w", err)
106+
}
107+
108+
toolParam := anthropic.ToolParam{
109+
Name: tool.Name,
110+
Description: anthropic.String(tool.Description),
111+
InputSchema: schema,
112+
}
113+
114+
return anthropic.ToolUnionParam{
115+
OfTool: &toolParam,
116+
}, nil
117+
}
118+
119+
func (c *MCPClient) ProcessQuery(ctx context.Context, query string) (string, error) {
120+
if c.session == nil {
121+
return "", fmt.Errorf("client is not connected to any server")
122+
}
123+
124+
messages := []anthropic.MessageParam{
125+
anthropic.NewUserMessage(anthropic.NewTextBlock(query)),
126+
}
127+
128+
// Initial Claude API call with tools
129+
response, err := c.anthropic.Messages.New(ctx, anthropic.MessageNewParams{
130+
Model: model,
131+
MaxTokens: 1024,
132+
Messages: messages,
133+
Tools: c.tools,
134+
})
135+
if err != nil {
136+
return "", fmt.Errorf("anthropic API request failed: %w", err)
137+
}
138+
139+
var toolUseBlocks []anthropic.ToolUseBlock
140+
var finalText []string
141+
for _, block := range response.Content {
142+
switch b := block.AsAny().(type) {
143+
case anthropic.TextBlock:
144+
finalText = append(finalText, b.Text)
145+
case anthropic.ToolUseBlock:
146+
toolUseBlocks = append(toolUseBlocks, b)
147+
}
148+
}
149+
150+
if len(toolUseBlocks) == 0 {
151+
return strings.Join(finalText, "\n"), nil
152+
}
153+
154+
// Append assistant's response to message history
155+
messages = append(messages, response.ToParam())
156+
157+
// Execute each tool call and collect responses
158+
var anthropicToolResults []anthropic.ContentBlockParamUnion
159+
for _, toolUseBlock := range toolUseBlocks {
160+
// Add information about the tool call to final text
161+
finalText = append(finalText, fmt.Sprintf("[Calling tool %s with args %s]", toolUseBlock.Name, string(toolUseBlock.Input)))
162+
163+
// Call the MCP server tool
164+
mcpToolResult, err := c.session.CallTool(ctx, &mcp.CallToolParams{
165+
Name: toolUseBlock.Name,
166+
Arguments: toolUseBlock.Input,
167+
})
168+
if err != nil {
169+
return "", fmt.Errorf("tool call %s failed: %w", toolUseBlock.Name, err)
170+
}
171+
172+
// Serialize tool result
173+
resultJSON, err := json.Marshal(mcpToolResult)
174+
if err != nil {
175+
return "", fmt.Errorf("failed to serialize tool result: %w", err)
176+
}
177+
178+
anthropicToolResults = append(anthropicToolResults, anthropic.NewToolResultBlock(
179+
toolUseBlock.ID,
180+
string(resultJSON),
181+
false,
182+
))
183+
}
184+
185+
// Append tool responses to message history
186+
messages = append(messages, anthropic.NewUserMessage(anthropicToolResults...))
187+
188+
// Make another API call with tool results
189+
response, err = c.anthropic.Messages.New(ctx, anthropic.MessageNewParams{
190+
Model: anthropic.ModelClaude3_7SonnetLatest,
191+
MaxTokens: 1024,
192+
Messages: messages,
193+
})
194+
if err != nil {
195+
return "", fmt.Errorf("anthropic API request with tool results failed: %w", err)
196+
}
197+
198+
// Collect text from final response
199+
for _, block := range response.Content {
200+
switch b := block.AsAny().(type) {
201+
case anthropic.TextBlock:
202+
finalText = append(finalText, b.Text)
203+
}
204+
}
205+
206+
return strings.Join(finalText, "\n"), nil
207+
}
208+
209+
func (c *MCPClient) ChatLoop(ctx context.Context) error {
210+
fmt.Println("\nMCP Client Started!")
211+
fmt.Println("Type your queries or 'quit' to exit.")
212+
213+
scanner := bufio.NewScanner(os.Stdin)
214+
215+
for {
216+
fmt.Print("\nQuery: ")
217+
if !scanner.Scan() {
218+
break // EOF
219+
}
220+
221+
query := strings.TrimSpace(scanner.Text())
222+
if strings.EqualFold(query, "quit") {
223+
break
224+
}
225+
if query == "" {
226+
continue
227+
}
228+
229+
response, err := c.ProcessQuery(ctx, query)
230+
if err != nil {
231+
fmt.Printf("\nError: %v\n", err)
232+
continue
233+
}
234+
235+
fmt.Printf("\n%s\n", response)
236+
}
237+
238+
return scanner.Err()
239+
}
240+
241+
func (c *MCPClient) Cleanup() error {
242+
if c.session != nil {
243+
if err := c.session.Close(); err != nil {
244+
return fmt.Errorf("failed to close session: %w", err)
245+
}
246+
c.session = nil
247+
}
248+
return nil
249+
}
250+
251+
func main() {
252+
if len(os.Args) < 2 {
253+
fmt.Fprintln(os.Stderr, "Usage: go run main.go <server_script_or_binary> [args...]")
254+
os.Exit(1)
255+
}
256+
257+
serverArgs := os.Args[1:]
258+
259+
client, err := NewMCPClient()
260+
if err != nil {
261+
log.Fatalf("Failed to create MCP client: %v", err)
262+
}
263+
264+
ctx := context.Background()
265+
266+
if err := client.ConnectToServer(ctx, serverArgs); err != nil {
267+
log.Fatalf("Failed to connect to MCP server: %v", err)
268+
}
269+
270+
if err := client.ChatLoop(ctx); err != nil {
271+
log.Printf("ChatLoop error: %v", err)
272+
}
273+
274+
if err := client.Cleanup(); err != nil {
275+
log.Printf("Cleanup error: %v", err)
276+
}
277+
}

0 commit comments

Comments
 (0)