Skip to content

Commit a376dc5

Browse files
authored
feat(go): Add MCP Client (#2959)
1 parent 6333040 commit a376dc5

File tree

16 files changed

+1962
-4
lines changed

16 files changed

+1962
-4
lines changed

‎go/ai/tools.go‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,10 @@ import (
2222
"fmt"
2323
"maps"
2424

25-
"github.com/invopop/jsonschema"
26-
2725
"github.com/firebase/genkit/go/core"
2826
"github.com/firebase/genkit/go/internal/base"
2927
"github.com/firebase/genkit/go/internal/registry"
28+
"github.com/invopop/jsonschema"
3029
)
3130

3231
var resumedCtxKey = base.NewContextKey[map[string]any]()

‎go/genkit/genkit.go‎

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,13 @@ import (
2525
"os"
2626
"os/signal"
2727
"path/filepath"
28+
"strings"
2829
"syscall"
2930

30-
"github.com/invopop/jsonschema"
31-
3231
"github.com/firebase/genkit/go/ai"
3332
"github.com/firebase/genkit/go/core"
3433
"github.com/firebase/genkit/go/internal/registry"
34+
"github.com/invopop/jsonschema"
3535

3636
sdktrace "go.opentelemetry.io/otel/sdk/trace"
3737
)
@@ -359,6 +359,31 @@ func ListFlows(g *Genkit) []core.Action {
359359
return flows
360360
}
361361

362+
// ListTools returns a slice of all [ai.Tool] instances that are registered
363+
// with the Genkit instance `g`. This is useful for introspection and for
364+
// exposing tools to external systems like MCP servers.
365+
func ListTools(g *Genkit) []ai.Tool {
366+
acts := g.reg.ListActions()
367+
tools := []ai.Tool{}
368+
for _, act := range acts {
369+
action, ok := act.(core.Action)
370+
if !ok {
371+
continue
372+
}
373+
actionDesc := action.Desc()
374+
if strings.HasPrefix(actionDesc.Key, "/"+string(core.ActionTypeTool)+"/") {
375+
// Extract tool name from key
376+
toolName := strings.TrimPrefix(actionDesc.Key, "/"+string(core.ActionTypeTool)+"/")
377+
// Lookup the actual tool
378+
tool := LookupTool(g, toolName)
379+
if tool != nil {
380+
tools = append(tools, tool)
381+
}
382+
}
383+
}
384+
return tools
385+
}
386+
362387
// DefineModel defines a custom model implementation, registers it as a [core.Action]
363388
// of type Model, and returns an [ai.Model] interface.
364389
//

‎go/go.mod‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ require (
2323
github.com/invopop/jsonschema v0.13.0
2424
github.com/jba/slog v0.2.0
2525
github.com/lib/pq v1.10.9
26+
github.com/mark3labs/mcp-go v0.29.0
2627
github.com/pgvector/pgvector-go v0.2.0
2728
github.com/stretchr/testify v1.10.0
2829
github.com/weaviate/weaviate v1.30.0
@@ -39,6 +40,11 @@ require (
3940
google.golang.org/genai v1.8.0
4041
)
4142

43+
require (
44+
github.com/spf13/cast v1.7.1 // indirect
45+
github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
46+
)
47+
4248
require (
4349
cel.dev/expr v0.20.0 // indirect
4450
cloud.google.com/go v0.118.0 // indirect

‎go/go.sum‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,8 @@ github.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfU
7575
github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU=
7676
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
7777
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
78+
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
79+
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
7880
github.com/go-jose/go-jose/v4 v4.1.0 h1:cYSYxd3pw5zd2FSXk2vGdn9igQU2PS8MuxrCOCl0FdY=
7981
github.com/go-jose/go-jose/v4 v4.1.0/go.mod h1:GG/vqmYm3Von2nYiB2vGTXzdoNKE5tix5tuc6iAd+sw=
8082
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
@@ -218,6 +220,8 @@ github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJ
218220
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
219221
github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4=
220222
github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
223+
github.com/mark3labs/mcp-go v0.29.0 h1:sH1NBcumKskhxqYzhXfGc201D7P76TVXiT0fGVhabeI=
224+
github.com/mark3labs/mcp-go v0.29.0/go.mod h1:rXqOudj/djTORU/ThxYx8fqEVj/5pvTuuebQ2RC7uk4=
221225
github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE=
222226
github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0=
223227
github.com/mbleigh/raymond v0.0.0-20250414171441-6b3a58ab9e0a h1:v2cBA3xWKv2cIOVhnzX/gNgkNXqiHfUgJtA3r61Hf7A=
@@ -255,6 +259,8 @@ github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWN
255259
github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
256260
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
257261
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
262+
github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y=
263+
github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
258264
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
259265
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
260266
github.com/spiffe/go-spiffe/v2 v2.5.0 h1:N2I01KCUkv1FAjZXJMwh95KK1ZIQLYbPfhaxw8WS0hE=
@@ -312,6 +318,8 @@ github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHo
312318
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
313319
github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
314320
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
321+
github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4=
322+
github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4=
315323
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
316324
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
317325
github.com/zeebo/errs v1.4.0 h1:XNdoD/RRMKP7HD0UhJnIzUy74ISdGGxURlYG8HSWSfM=

‎go/plugins/mcp/README.md‎

Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
# Genkit MCP Plugin
2+
3+
Model Context Protocol (MCP) integration for Go Genkit - connect to MCP servers and expose Genkit tools as MCP servers.
4+
5+
## GenkitMCPClient - Single Server Connection
6+
7+
Connect to and use tools/prompts from a single MCP server:
8+
9+
```go
10+
package main
11+
12+
import (
13+
"context"
14+
"log"
15+
16+
"github.com/firebase/genkit/go/genkit"
17+
"github.com/firebase/genkit/go/plugins/mcp"
18+
)
19+
20+
func main() {
21+
ctx := context.Background()
22+
g, _ := genkit.Init(ctx)
23+
24+
// Connect to the MCP everything server
25+
client, err := mcp.NewGenkitMCPClient(mcp.MCPClientOptions{
26+
Name: "everything-server",
27+
Stdio: &mcp.StdioConfig{
28+
Command: "npx",
29+
Args: []string{"-y", "@modelcontextprotocol/server-everything"},
30+
},
31+
})
32+
if err != nil {
33+
log.Fatal(err)
34+
}
35+
36+
// Get specific tools from the everything server
37+
echoTool, err := client.GetTool(ctx, g, "echo")
38+
if err != nil {
39+
log.Fatal(err)
40+
}
41+
42+
addTool, err := client.GetTool(ctx, g, "add")
43+
if err != nil {
44+
log.Fatal(err)
45+
}
46+
47+
// Get specific prompts from the everything server
48+
simplePrompt, err := client.GetPrompt(ctx, g, "simple_prompt")
49+
if err != nil {
50+
log.Fatal(err)
51+
}
52+
53+
// Get all available tools
54+
tools, err := client.GetActiveTools(ctx, g)
55+
if err != nil {
56+
log.Fatal(err)
57+
}
58+
59+
}
60+
```
61+
62+
## GenkitMCPManager - Multiple Server Management
63+
64+
Manage connections to multiple MCP servers:
65+
66+
```go
67+
package main
68+
69+
import (
70+
"context"
71+
"log"
72+
73+
"github.com/firebase/genkit/go/genkit"
74+
"github.com/firebase/genkit/go/plugins/mcp"
75+
)
76+
77+
func main() {
78+
ctx := context.Background()
79+
g, _ := genkit.Init(ctx)
80+
81+
// Create manager with multiple servers
82+
manager, err := mcp.NewMCPManager(mcp.MCPManagerOptions{
83+
Name: "my-app",
84+
MCPServers: map[string]mcp.MCPClientOptions{
85+
"everything": {
86+
Name: "everything-server",
87+
Stdio: &mcp.StdioConfig{
88+
Command: "npx",
89+
Args: []string{"-y", "@modelcontextprotocol/server-everything"},
90+
},
91+
},
92+
"filesystem": {
93+
Name: "fs-server",
94+
Stdio: &mcp.StdioConfig{
95+
Command: "npx",
96+
Args: []string{"@modelcontextprotocol/server-filesystem", "/tmp"},
97+
},
98+
},
99+
},
100+
})
101+
if err != nil {
102+
log.Fatal(err)
103+
}
104+
105+
// Connect to new server at runtime
106+
err = manager.ConnectServer(ctx, "weather", mcp.MCPClientOptions{
107+
Name: "weather-server",
108+
Stdio: &mcp.StdioConfig{
109+
Command: "python",
110+
Args: []string{"weather_server.py"},
111+
},
112+
})
113+
if err != nil {
114+
log.Fatal(err)
115+
}
116+
117+
// Temporarily disable/enable servers
118+
manager.DisableServer("filesystem")
119+
manager.EnableServer("filesystem")
120+
121+
// Disconnect server
122+
manager.DisconnectServer("weather")
123+
124+
// Get tools from all active servers
125+
tools, err := manager.GetActiveTools(ctx, g)
126+
if err != nil {
127+
log.Fatal(err)
128+
}
129+
}
130+
```
131+
132+
## GenkitMCPServer - Expose Genkit Tools
133+
134+
Turn your Genkit app into an MCP server:
135+
136+
```go
137+
package main
138+
139+
import (
140+
"context"
141+
"log"
142+
143+
"github.com/firebase/genkit/go/genkit"
144+
"github.com/firebase/genkit/go/plugins/mcp"
145+
"github.com/firebase/genkit/go/ai"
146+
)
147+
148+
func main() {
149+
ctx := context.Background()
150+
g, _ := genkit.Init(ctx)
151+
152+
// Define some tools
153+
addTool := genkit.DefineTool(g, "add", "Add two numbers",
154+
func(ctx *ai.ToolContext, input struct{A, B int}) (int, error) {
155+
return input.A + input.B, nil
156+
})
157+
158+
multiplyTool := genkit.DefineTool(g, "multiply", "Multiply two numbers",
159+
func(ctx *ai.ToolContext, input struct{X, Y int}) (int, error) {
160+
return input.X * input.Y, nil
161+
})
162+
163+
// Option 1: Auto-expose all tools
164+
server := mcp.NewMCPServer(g, mcp.MCPServerOptions{
165+
Name: "genkit-calculator",
166+
Version: "1.0.0",
167+
})
168+
169+
// Option 2: Expose specific tools only
170+
server = mcp.NewMCPServer(g, mcp.MCPServerOptions{
171+
Name: "genkit-calculator",
172+
Version: "1.0.0",
173+
Tools: []ai.Tool{addTool, multiplyTool},
174+
})
175+
176+
// Start MCP server
177+
log.Println("Starting MCP server...")
178+
if err := server.ServeStdio(ctx); err != nil {
179+
log.Fatal(err)
180+
}
181+
}
182+
```
183+
184+
## Testing Your Server
185+
186+
```bash
187+
# Run your server
188+
go run main.go
189+
190+
# Test with MCP Inspector
191+
npx @modelcontextprotocol/inspector go run main.go
192+
```
193+
194+
## Transport Options
195+
196+
### Stdio (Standard)
197+
```go
198+
Stdio: &mcp.StdioConfig{
199+
Command: "uvx",
200+
Args: []string{"mcp-server-time"},
201+
Env: []string{"DEBUG=1"},
202+
}
203+
```
204+
205+
### SSE (Web clients)
206+
```go
207+
SSE: &mcp.SSEConfig{
208+
BaseURL: "http://localhost:3000/sse",
209+
}
210+
```

0 commit comments

Comments
 (0)