|
| 1 | +// Copyright 2025 Google LLC |
| 2 | +// |
| 3 | +// Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | +// you may not use this file except in compliance with the License. |
| 5 | +// You may obtain a copy of the License at |
| 6 | +// |
| 7 | +// http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | +// |
| 9 | +// Unless required by applicable law or agreed to in writing, software |
| 10 | +// distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | +// See the License for the specific language governing permissions and |
| 13 | +// limitations under the License. |
| 14 | + |
| 15 | +package main |
| 16 | + |
| 17 | +import ( |
| 18 | + "context" |
| 19 | + "fmt" |
| 20 | + "log" |
| 21 | + "os" |
| 22 | + "os/signal" |
| 23 | + "syscall" |
| 24 | + |
| 25 | + "github.com/firebase/genkit/go/ai" |
| 26 | + "github.com/firebase/genkit/go/core" |
| 27 | + "github.com/firebase/genkit/go/core/logger" |
| 28 | + "github.com/firebase/genkit/go/genkit" |
| 29 | + "github.com/firebase/genkit/go/plugins/googlegenai" |
| 30 | + "github.com/firebase/genkit/go/plugins/mcp" |
| 31 | +) |
| 32 | + |
| 33 | +// MCP self-hosting example: Genkit serves itself through MCP |
| 34 | +// 1. Start a Go MCP server that exposes Genkit resources |
| 35 | +// 2. Connect to that server as an MCP client |
| 36 | +// 3. Use the resources from the server for AI generation |
| 37 | + |
| 38 | +// Create the MCP Server (runs in background) |
| 39 | +func createMCPServer() { |
| 40 | + ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) |
| 41 | + defer cancel() |
| 42 | + |
| 43 | + logger.FromContext(ctx).Info("Starting Genkit MCP Server") |
| 44 | + |
| 45 | + // Initialize Genkit for the server |
| 46 | + g, err := genkit.Init(ctx) |
| 47 | + if err != nil { |
| 48 | + logger.FromContext(ctx).Error("Failed to initialize Genkit server", "error", err) |
| 49 | + os.Exit(1) |
| 50 | + } |
| 51 | + |
| 52 | + // Define a tool that generates creative content (this will be auto-exposed via MCP) |
| 53 | + genkit.DefineTool(g, "genkit-brainstorm", "Generate creative ideas about a topic", |
| 54 | + func(ctx *ai.ToolContext, input struct { |
| 55 | + Topic string `json:"topic" description:"The topic to brainstorm about"` |
| 56 | + }) (map[string]interface{}, error) { |
| 57 | + logger.FromContext(ctx.Context).Debug("Executing genkit-brainstorm tool", "topic", input.Topic) |
| 58 | + |
| 59 | + ideas := fmt.Sprintf(`Creative Ideas for "%s": |
| 60 | +
|
| 61 | +1. Interactive Experience: Create an immersive, hands-on workshop |
| 62 | +2. Digital Innovation: Develop a mobile app or web platform |
| 63 | +3. Community Building: Start a local meetup or online community |
| 64 | +4. Educational Content: Design a course or tutorial series |
| 65 | +5. Collaborative Project: Partner with others for cross-pollination |
| 66 | +6. Storytelling Approach: Create narratives around the topic |
| 67 | +7. Gamification: Turn learning into an engaging game |
| 68 | +8. Real-world Application: Find practical, everyday uses |
| 69 | +9. Creative Challenge: Host competitions or hackathons |
| 70 | +10. Multi-media Approach: Combine video, audio, and interactive elements |
| 71 | +
|
| 72 | +These ideas can be mixed, matched, and customized for "%s".`, input.Topic, input.Topic) |
| 73 | + |
| 74 | + return map[string]interface{}{ |
| 75 | + "topic": input.Topic, |
| 76 | + "ideas": ideas, |
| 77 | + }, nil |
| 78 | + }) |
| 79 | + |
| 80 | + // Define a resource that contains Genkit knowledge (this will be auto-exposed via MCP) |
| 81 | + err = genkit.DefineResource(g, genkit.ResourceOptions{ |
| 82 | + Name: "genkit-knowledge", |
| 83 | + URI: "knowledge://genkit-docs", |
| 84 | + }, func(ctx context.Context, input core.ResourceInput) (genkit.ResourceOutput, error) { |
| 85 | + knowledge := `# Genkit Knowledge Base |
| 86 | +
|
| 87 | +## What is Genkit? |
| 88 | +Genkit is Firebase's open-source framework for building AI-powered applications. |
| 89 | +
|
| 90 | +## Key Features: |
| 91 | +- Multi-modal AI generation (text, images, audio) |
| 92 | +- Tool calling and function execution |
| 93 | +- RAG (Retrieval Augmented Generation) support |
| 94 | +- Evaluation and testing frameworks |
| 95 | +- Multi-language support (TypeScript, Go, Python) |
| 96 | +
|
| 97 | +## Popular Models: |
| 98 | +- Google AI: Gemini 1.5 Flash, Gemini 2.0 Flash |
| 99 | +- Vertex AI: All Gemini models |
| 100 | +- OpenAI Compatible models via plugins |
| 101 | +
|
| 102 | +## Use Cases: |
| 103 | +- Chatbots and conversational AI |
| 104 | +- Content generation and editing |
| 105 | +- Code analysis and generation |
| 106 | +- Document processing and summarization |
| 107 | +- Creative applications (story writing, brainstorming) |
| 108 | +
|
| 109 | +## Architecture: |
| 110 | +Genkit follows a plugin-based architecture where models, retrievers, evaluators, and other components are provided by plugins.` |
| 111 | + |
| 112 | + return genkit.ResourceOutput{ |
| 113 | + Content: []*ai.Part{ai.NewTextPart(knowledge)}, |
| 114 | + }, nil |
| 115 | + }) |
| 116 | + if err != nil { |
| 117 | + logger.FromContext(ctx).Error("Failed to create resource", "error", err) |
| 118 | + os.Exit(1) |
| 119 | + } |
| 120 | + |
| 121 | + // Create MCP server (automatically exposes all defined tools and resources) |
| 122 | + server := mcp.NewMCPServer(g, mcp.MCPServerOptions{ |
| 123 | + Name: "genkit-mcp-server", |
| 124 | + Version: "1.0.0", |
| 125 | + }) |
| 126 | + |
| 127 | + logger.FromContext(ctx).Info("Genkit MCP Server configured successfully") |
| 128 | + logger.FromContext(ctx).Info("Starting MCP server on stdio") |
| 129 | + logger.FromContext(ctx).Info("Registered tools", "count", len(server.ListRegisteredTools())) |
| 130 | + logger.FromContext(ctx).Info("Registered resources", "count", len(server.ListRegisteredResources())) |
| 131 | + |
| 132 | + // Start the server |
| 133 | + if err := server.ServeStdio(); err != nil && err != context.Canceled { |
| 134 | + logger.FromContext(ctx).Error("MCP server failed", "error", err) |
| 135 | + os.Exit(1) |
| 136 | + } |
| 137 | +} |
| 138 | + |
| 139 | +// Create the MCP Client that connects to our server |
| 140 | +func mcpSelfConnection() { |
| 141 | + ctx := context.Background() |
| 142 | + |
| 143 | + logger.FromContext(ctx).Info("MCP self-connection demo") |
| 144 | + logger.FromContext(ctx).Info("Genkit will connect to itself via MCP") |
| 145 | + |
| 146 | + // Initialize Genkit with Google AI for the client |
| 147 | + g, err := genkit.Init(ctx, |
| 148 | + genkit.WithPlugins(&googlegenai.GoogleAI{}), |
| 149 | + genkit.WithDefaultModel("googleai/gemini-2.0-flash"), |
| 150 | + ) |
| 151 | + if err != nil { |
| 152 | + log.Fatalf("Failed to initialize Genkit client: %v", err) |
| 153 | + } |
| 154 | + |
| 155 | + logger.FromContext(ctx).Info("Connecting to our own MCP server") |
| 156 | + logger.FromContext(ctx).Info("Note: Server process will be spawned automatically") |
| 157 | + |
| 158 | + // Create MCP Host that connects to our Genkit server |
| 159 | + host, err := mcp.NewMCPHost(g, mcp.MCPHostOptions{ |
| 160 | + Name: "mcp-ception-host", |
| 161 | + MCPServers: []mcp.MCPServerConfig{ |
| 162 | + { |
| 163 | + Name: "genkit-server", |
| 164 | + Config: mcp.MCPClientOptions{ |
| 165 | + Name: "genkit-mcp-server", |
| 166 | + Version: "1.0.0", |
| 167 | + Stdio: &mcp.StdioConfig{ |
| 168 | + Command: "go", |
| 169 | + Args: []string{"run", "mcp_ception.go", "server"}, |
| 170 | + }, |
| 171 | + }, |
| 172 | + }, |
| 173 | + }, |
| 174 | + }) |
| 175 | + if err != nil { |
| 176 | + logger.FromContext(ctx).Error("Failed to create MCP host", "error", err) |
| 177 | + return |
| 178 | + } |
| 179 | + |
| 180 | + // Get resources from our Genkit server |
| 181 | + logger.FromContext(ctx).Info("Getting resources from Genkit MCP server") |
| 182 | + resources, err := host.GetActiveResources(ctx) |
| 183 | + if err != nil { |
| 184 | + logger.FromContext(ctx).Error("Failed to get resources", "error", err) |
| 185 | + return |
| 186 | + } |
| 187 | + |
| 188 | + logger.FromContext(ctx).Info("Retrieved resources from server", "count", len(resources)) |
| 189 | + |
| 190 | + // Debug: examine retrieved resources |
| 191 | + for i, resource := range resources { |
| 192 | + logger.FromContext(ctx).Info("Resource details", "index", i, "name", resource.Name()) |
| 193 | + // Test if the resource matches our target URI |
| 194 | + matches := resource.Matches("knowledge://genkit-docs") |
| 195 | + logger.FromContext(ctx).Info("Resource URI matching", "matches_target_uri", matches) |
| 196 | + } |
| 197 | + |
| 198 | + // Get tools from our Genkit server |
| 199 | + logger.FromContext(ctx).Info("Getting tools from Genkit MCP server") |
| 200 | + tools, err := host.GetActiveTools(ctx, g) |
| 201 | + if err != nil { |
| 202 | + logger.FromContext(ctx).Error("Failed to get tools", "error", err) |
| 203 | + return |
| 204 | + } |
| 205 | + |
| 206 | + logger.FromContext(ctx).Info("Retrieved tools from server", "count", len(tools)) |
| 207 | + |
| 208 | + // Convert tools to refs |
| 209 | + var toolRefs []ai.ToolRef |
| 210 | + for _, tool := range tools { |
| 211 | + toolRefs = append(toolRefs, tool) |
| 212 | + } |
| 213 | + |
| 214 | + // Use resources and tools from our own server for AI generation |
| 215 | + logger.FromContext(ctx).Info("Asking AI about Genkit using our own MCP resources") |
| 216 | + |
| 217 | + // Use ai.NewResourcePart to explicitly reference the resource |
| 218 | + logger.FromContext(ctx).Info("Starting generation call", "resource_count", len(resources), "tool_count", len(toolRefs)) |
| 219 | + |
| 220 | + response, err := genkit.Generate(ctx, g, |
| 221 | + ai.WithMessages(ai.NewUserMessage( |
| 222 | + ai.NewTextPart("Based on this Genkit knowledge:"), |
| 223 | + ai.NewResourcePart("knowledge://genkit-docs"), // Explicit resource reference |
| 224 | + ai.NewTextPart("What are the key features of Genkit and what models does it support?\n\nAlso, use the brainstorm tool to generate ideas for \"AI-powered cooking assistant\""), |
| 225 | + )), |
| 226 | + ai.WithResources(resources), // Makes resources available for lookup |
| 227 | + ai.WithTools(toolRefs...), // Using tools from our own server! |
| 228 | + ai.WithToolChoice(ai.ToolChoiceAuto), |
| 229 | + ) |
| 230 | + if err != nil { |
| 231 | + logger.FromContext(ctx).Error("AI generation failed", "error", err) |
| 232 | + return |
| 233 | + } |
| 234 | + |
| 235 | + logger.FromContext(ctx).Info("MCP self-connection completed successfully") |
| 236 | + logger.FromContext(ctx).Info("Genkit used itself via MCP to answer questions") |
| 237 | + fmt.Printf("\nAI Response using our own MCP resources:\n%s\n\n", response.Text()) |
| 238 | + |
| 239 | + // Clean disconnect (skip for now to avoid hanging) |
| 240 | + logger.FromContext(ctx).Info("MCP self-connection complete") |
| 241 | +} |
| 242 | + |
| 243 | +func main() { |
| 244 | + if len(os.Args) < 2 { |
| 245 | + fmt.Println("Usage: go run mcp_ception.go [server|demo]") |
| 246 | + fmt.Println(" server - Run as MCP server (exposes Genkit resources)") |
| 247 | + fmt.Println(" demo - Run MCP self-connection demo (connects to server)") |
| 248 | + os.Exit(1) |
| 249 | + } |
| 250 | + |
| 251 | + switch os.Args[1] { |
| 252 | + case "server": |
| 253 | + createMCPServer() |
| 254 | + case "demo": |
| 255 | + mcpSelfConnection() |
| 256 | + default: |
| 257 | + fmt.Printf("Unknown command: %s\n", os.Args[1]) |
| 258 | + fmt.Println("Use 'server' or 'demo'") |
| 259 | + os.Exit(1) |
| 260 | + } |
| 261 | +} |
0 commit comments