Skip to content

Instantly share code, notes, and snippets.

@cheeyeo
Created April 6, 2025 17:26
Show Gist options
  • Save cheeyeo/d3a5a500e2160b4d777f81146648249f to your computer and use it in GitHub Desktop.
Save cheeyeo/d3a5a500e2160b4d777f81146648249f to your computer and use it in GitHub Desktop.
Gemini MCP server example
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)
}
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