Created
April 6, 2025 17:26
-
-
Save cheeyeo/d3a5a500e2160b4d777f81146648249f to your computer and use it in GitHub Desktop.
Gemini MCP server example
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
// Example of using MCP with Gemini via Function Calls | |
import ( | |
"context" | |
"encoding/json" | |
"fmt" | |
"log" | |
"os" | |
"os/exec" | |
"github.com/google/generative-ai-go/genai" | |
"github.com/joho/godotenv" | |
mcp_golang "github.com/metoro-io/mcp-golang" | |
"github.com/metoro-io/mcp-golang/transport/stdio" | |
"google.golang.org/api/option" | |
) | |
func printResponse(resp *genai.GenerateContentResponse) { | |
for _, cand := range resp.Candidates { | |
if cand.Content != nil { | |
for _, part := range cand.Content.Parts { | |
fmt.Println(part) | |
} | |
} | |
} | |
fmt.Println("---") | |
} | |
type Property struct { | |
Description string `json:"description"` | |
Type string `json:"type"` | |
} | |
type GSchema struct { | |
Schema string `json:"$schema"` | |
Properties map[string]Property `json:"properties"` | |
Required []string `json:"required"` | |
Type string `json:"type"` | |
} | |
func getType(kind string) (genai.Type, error) { | |
var gType genai.Type | |
switch kind { | |
case "object": | |
gType = genai.TypeObject | |
case "array": | |
gType = genai.TypeArray | |
case "string": | |
gType = genai.TypeString | |
case "number": | |
gType = genai.TypeNumber | |
case "integer": | |
gType = genai.TypeInteger | |
case "boolean": | |
gType = genai.TypeBoolean | |
default: | |
return 0, fmt.Errorf("type not found in gemini Type: %s", kind) | |
} | |
return gType, nil | |
} | |
func (g GSchema) Convert() (*genai.Schema, error) { | |
var parseErr error | |
res := &genai.Schema{} | |
gType, parseErr := getType(g.Type) | |
if parseErr != nil { | |
return nil, parseErr | |
} | |
res.Type = gType | |
res.Required = g.Required | |
// Convert properties to map of genai.Schema | |
schemaProperties := map[string]*genai.Schema{} | |
for k, v := range g.Properties { | |
gType, parseErr := getType(v.Type) | |
if parseErr != nil { | |
return nil, parseErr | |
} | |
schemaProperties[k] = &genai.Schema{ | |
Description: v.Description, | |
Type: gType, | |
} | |
} | |
res.Properties = schemaProperties | |
return res, nil | |
} | |
func main() { | |
// Load dotenv file | |
err := godotenv.Load() | |
if err != nil { | |
log.Fatal("error loading dotenv file") | |
} | |
// Start the server process | |
cmd := exec.Command("go", "run", "./server/main.go") | |
stdin, err := cmd.StdinPipe() | |
if err != nil { | |
log.Fatalf("Failed to get stdin pipe: %v", err) | |
} | |
stdout, err := cmd.StdoutPipe() | |
if err != nil { | |
log.Fatalf("Failed to get stdout pipe: %v", err) | |
} | |
if err := cmd.Start(); err != nil { | |
log.Fatalf("Failed to start server: %v", err) | |
} | |
defer cmd.Process.Kill() | |
clientTransport := stdio.NewStdioServerTransportWithIO(stdout, stdin) | |
client := mcp_golang.NewClient(clientTransport) | |
if _, err := client.Initialize(context.Background()); err != nil { | |
log.Fatalf("Failed to initialize client: %v", err) | |
} | |
// List available tools on MCP server | |
tools, err := client.ListTools(context.Background(), nil) | |
if err != nil { | |
log.Fatalf("Failed to list tools: %v\n", err) | |
} | |
log.Println("Available tools:") | |
// Create list of gemini tools | |
geminiTools := []*genai.Tool{} | |
for _, tool := range tools.Tools { | |
desc := "" | |
if tool.Description != nil { | |
desc = *tool.Description | |
} | |
log.Printf("Tool: %s. Description: %s, Schema: %+v", tool.Name, desc, tool.InputSchema) | |
// Cast inputschema from interface to map[string]any | |
inputDict := tool.InputSchema.(map[string]any) | |
jsonbody, err := json.Marshal(inputDict) | |
if err != nil { | |
log.Fatalf("error with converting tool.InputSchema - %s", err) | |
} | |
gschema := GSchema{} | |
err = json.Unmarshal(jsonbody, &gschema) | |
if err != nil { | |
log.Fatalf("error with converting tool.InputSchema - %s", err) | |
} | |
geminiProperties, err := gschema.Convert() | |
geminiTool := &genai.Tool{ | |
FunctionDeclarations: []*genai.FunctionDeclaration{{ | |
Name: tool.Name, | |
Description: *tool.Description, | |
Parameters: geminiProperties, | |
}}, | |
} | |
geminiTools = append(geminiTools, geminiTool) | |
} | |
ctx := context.Background() | |
geminiClient, err := genai.NewClient(ctx, option.WithAPIKey(os.Getenv("API_KEY"))) | |
if err != nil { | |
log.Fatal(err) | |
} | |
defer geminiClient.Close() | |
model := geminiClient.GenerativeModel("gemini-2.5-pro-preview-03-25") | |
model.Tools = geminiTools | |
model.SetTemperature(0.1) | |
session := model.StartChat() | |
// prompt := "Can you say hello to Col444 using my custom tool?" | |
prompt := "What's the current Bitcoin price in GBP?" | |
res, err := session.SendMessage(ctx, genai.Text(prompt)) | |
if err != nil { | |
log.Fatalf("session.SendMessage: %v", err) | |
} | |
part := res.Candidates[0].Content.Parts[0] | |
funcall, ok := part.(genai.FunctionCall) | |
log.Printf("gemini funcall: %+v\n", funcall) | |
if !ok { | |
log.Fatalf("expected functioncall but received error:\n%v", part) | |
} | |
// Make actual call in MCP | |
var geminiFunctionResponse map[string]any | |
helloResp, err := client.CallTool(context.Background(), funcall.Name, funcall.Args) | |
if err != nil { | |
log.Printf("failed to call tool: %v\n", err) | |
geminiFunctionResponse = map[string]any{"error": err} | |
} else { | |
log.Printf("Response: %v\n", helloResp.Content[0].TextContent.Text) | |
geminiFunctionResponse = map[string]any{"response": helloResp.Content[0].TextContent.Text} | |
} | |
// Send resp back to gemini | |
res, err = session.SendMessage(ctx, genai.FunctionResponse{ | |
Name: funcall.Name, | |
Response: geminiFunctionResponse, | |
}) | |
if err != nil { | |
log.Fatal(err) | |
} | |
printResponse(res) | |
// Try testing of calling prompt | |
promptArgs := map[string]interface{}{ | |
"Title": "Hello MCP", | |
} | |
resp, err := client.GetPrompt(ctx, "prompt_test", promptArgs) | |
log.Printf("Prompt resp: %s", resp.Messages[0].Content.TextContent.Text) | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"encoding/json" | |
"fmt" | |
"io" | |
"log" | |
"net/http" | |
"time" | |
mcp_golang "github.com/metoro-io/mcp-golang" | |
"github.com/metoro-io/mcp-golang/transport/stdio" | |
) | |
// HelloArgs represent arguments of hello tool | |
type HelloArgs struct { | |
Name string `json:"name" jsonschema:"required,description=The name to say hello to"` | |
} | |
type BitcoinPriceArguments struct { | |
Currency string `json:"currency" jsonschema:"required,description=The currency to get the Bitcoin price in (USD, EUR, GBP, etc)"` | |
} | |
type CoinGeckoResponse struct { | |
Bitcoin struct { | |
USD float64 `json:"usd"` | |
EUR float64 `json:"eur"` | |
GBP float64 `json:"gbp"` | |
JPY float64 `json:"jpy"` | |
AUD float64 `json:"aud"` | |
CAD float64 `json:"cad"` | |
CHF float64 `json:"chf"` | |
CNY float64 `json:"cny"` | |
KRW float64 `json:"krw"` | |
RUB float64 `json:"rub"` | |
} `json:"bitcoin"` | |
} | |
type Content struct { | |
Title string `json:"title" jsonschema:"required,description=The title to submit"` | |
Description *string `json:"description" jsonschema:"description=The description to submit"` | |
} | |
func getBitcoinPrice(currency string) (float64, error) { | |
log.Printf("INSIDE GET BITCOIN PRICE - CURRENCY - %s", currency) | |
// Create HTTP client with timeout | |
client := &http.Client{ | |
Timeout: 10 * time.Second, | |
} | |
// Make request to CoinGecko API | |
resp, err := client.Get("https://api.coingecko.com/api/v3/simple/price?ids=bitcoin&vs_currencies=usd,eur,gbp,jpy,aud,cad,chf,cny,krw,rub") | |
if err != nil { | |
return 0, fmt.Errorf("error making request to CoinGecko API: %w", err) | |
} | |
defer resp.Body.Close() | |
// Read response body | |
body, err := io.ReadAll(resp.Body) | |
if err != nil { | |
return 0, fmt.Errorf("error reading response body: %w", err) | |
} | |
// Parse JSON response | |
var coinGeckoResp CoinGeckoResponse | |
err = json.Unmarshal(body, &coinGeckoResp) | |
if err != nil { | |
return 0, fmt.Errorf("error parsing JSON response: %w", err) | |
} | |
// Get price for requested currency | |
var price float64 | |
switch currency { | |
case "USD", "usd": | |
price = coinGeckoResp.Bitcoin.USD | |
case "EUR", "eur": | |
price = coinGeckoResp.Bitcoin.EUR | |
case "GBP", "gbp": | |
price = coinGeckoResp.Bitcoin.GBP | |
case "JPY", "jpy": | |
price = coinGeckoResp.Bitcoin.JPY | |
case "AUD", "aud": | |
price = coinGeckoResp.Bitcoin.AUD | |
case "CAD", "cad": | |
price = coinGeckoResp.Bitcoin.CAD | |
case "CHF", "chf": | |
price = coinGeckoResp.Bitcoin.CHF | |
case "CNY", "cny": | |
price = coinGeckoResp.Bitcoin.CNY | |
case "KRW", "krw": | |
price = coinGeckoResp.Bitcoin.KRW | |
case "RUB", "rub": | |
price = coinGeckoResp.Bitcoin.RUB | |
default: | |
return 0, fmt.Errorf("unsupported currency: %s", currency) | |
} | |
return price, nil | |
} | |
func main() { | |
server := mcp_golang.NewServer(stdio.NewStdioServerTransport()) | |
err := server.RegisterTool("hello", "Say hello to a person", func(args HelloArgs) (*mcp_golang.ToolResponse, error) { | |
message := fmt.Sprintf("Hello %s!", args.Name) | |
return mcp_golang.NewToolResponse(mcp_golang.NewTextContent(message)), nil | |
}) | |
if err != nil { | |
panic(err) | |
} | |
// Register the bitcoin_price tool | |
err = server.RegisterTool("bitcoin_price", "Get the latest Bitcoin price in various currencies", func(arguments BitcoinPriceArguments) (*mcp_golang.ToolResponse, error) { | |
log.Printf("received request for bitcoin_price tool with currency: %s", arguments.Currency) | |
currency := arguments.Currency | |
if currency == "" { | |
currency = "USD" | |
} | |
// Call CoinGecko API to get latest Bitcoin price | |
price, err := getBitcoinPrice(currency) | |
if err != nil { | |
return mcp_golang.NewToolResponse(mcp_golang.NewTextContent(fmt.Sprintf("Error fetching Bitcoin price: %v", err))), err | |
} | |
return mcp_golang.NewToolResponse(mcp_golang.NewTextContent(fmt.Sprintf("The current Bitcoin price in %s is %.2f (as of %s)", | |
currency, | |
price, | |
time.Now().Format(time.RFC1123)))), nil | |
}) | |
if err != nil { | |
log.Fatalf("error registering bitcoin_price tool: %v", err) | |
} | |
err = server.RegisterPrompt("prompt_test", "This is a test prompt", func(arguments Content) (*mcp_golang.PromptResponse, error) { | |
log.Println("Received request for prompt_test") | |
return mcp_golang.NewPromptResponse("description", mcp_golang.NewPromptMessage(mcp_golang.NewTextContent(fmt.Sprintf("Hello, %s!", arguments.Title)), mcp_golang.RoleUser)), nil | |
}) | |
if err != nil { | |
log.Fatalf("error registering prompt_test prompt: %v", err) | |
} | |
err = server.Serve() | |
if err != nil { | |
panic(err) | |
} | |
select {} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment