Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
a0032cf
feat(go): Add support for genkit resources
huangjeff5 Jul 29, 2025
5f10992
add copyright
huangjeff5 Jul 29, 2025
53faf28
Add action type resource
huangjeff5 Jul 29, 2025
9c4b6cd
move helpers to core
huangjeff5 Jul 29, 2025
0ebba79
remove unused code
huangjeff5 Jul 29, 2025
fa392d0
Update generate.go
huangjeff5 Jul 30, 2025
58c93cf
Update generate.go
huangjeff5 Jul 30, 2025
b08729d
Update resource.go
huangjeff5 Jul 30, 2025
0f23cab
dynamic handlers don't need cleanup
huangjeff5 Jul 30, 2025
388b7ef
fix cleanup
huangjeff5 Jul 30, 2025
b761633
Update generate.go
huangjeff5 Jul 30, 2025
623be6c
Update generate.go
huangjeff5 Jul 30, 2025
5f78159
Update generate.go
huangjeff5 Jul 30, 2025
7694bf4
Update generate.go
huangjeff5 Jul 30, 2025
d9193ce
Change AttachToRegistry to Register
huangjeff5 Jul 30, 2025
99a55ba
Merge branch 'jh-go-resources' of https://github.com/firebase/genkit …
huangjeff5 Jul 30, 2025
831234b
feat(go): Add MCP support for genkit resources
huangjeff5 Jul 31, 2025
eb518de
Add resources mcp sample
huangjeff5 Jul 31, 2025
73fc76b
Update host.go
huangjeff5 Jul 31, 2025
3c7a273
Add ListResource
huangjeff5 Jul 31, 2025
3cc9804
Merge branch 'jh-mcp-resources' of https://github.com/firebase/genkit…
huangjeff5 Jul 31, 2025
a0f5de7
Update host.go
huangjeff5 Jul 31, 2025
65fba4d
Update host.go
huangjeff5 Jul 31, 2025
cea4136
revert ai.NewUserMessageWithResource usage
huangjeff5 Jul 31, 2025
0543760
Merge branch 'jh-mcp-resources' of https://github.com/firebase/genkit…
huangjeff5 Jul 31, 2025
ab5d2dc
add mcp ception sample
huangjeff5 Jul 31, 2025
95e766d
better logging
huangjeff5 Jul 31, 2025
1c7ab6d
fix server
huangjeff5 Aug 1, 2025
e83ef87
remove duplicate file
huangjeff5 Aug 1, 2025
693c880
Add edge case testing for mcp resources
huangjeff5 Aug 4, 2025
010a8f0
add copyright headers
huangjeff5 Aug 4, 2025
46e4fc7
add integration tests
huangjeff5 Aug 4, 2025
5a8c30a
Merge branch 'main' into jh-mcp-resources
huangjeff5 Aug 21, 2025
7fa7a43
Fix tests
huangjeff5 Aug 22, 2025
ea5f03f
format
huangjeff5 Aug 22, 2025
0272867
fix
huangjeff5 Aug 22, 2025
0bba994
refactor
huangjeff5 Aug 22, 2025
b9513d3
Merge branch 'main' into jh-mcp-resources
huangjeff5 Aug 22, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 61 additions & 8 deletions go/ai/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,69 @@ import (
"context"
"fmt"
"maps"
"net/url"
"strings"

"github.com/firebase/genkit/go/core"
coreresource "github.com/firebase/genkit/go/core/resource"
"github.com/firebase/genkit/go/internal/registry"
"github.com/yosida95/uritemplate/v3"
)

// normalizeURI normalizes a URI for template matching by removing query parameters,
// fragments, and trailing slashes from the path.
func normalizeURI(rawURI string) string {
// Parse the URI
u, err := url.Parse(rawURI)
if err != nil {
// If parsing fails, return the original URI
return rawURI
}

// Remove query parameters and fragment
u.RawQuery = ""
u.Fragment = ""

// Remove trailing slash from path (but not from the root path)
if len(u.Path) > 1 && strings.HasSuffix(u.Path, "/") {
u.Path = strings.TrimSuffix(u.Path, "/")
}

return u.String()
}

// matches checks if a URI matches the given URI template.
func matches(templateStr, uri string) (bool, error) {
template, err := uritemplate.New(templateStr)
if err != nil {
return false, fmt.Errorf("invalid URI template %q: %w", templateStr, err)
}

normalizedURI := normalizeURI(uri)
values := template.Match(normalizedURI)
return len(values) > 0, nil
}

// extractVariables extracts variables from a URI using the given URI template.
func extractVariables(templateStr, uri string) (map[string]string, error) {
template, err := uritemplate.New(templateStr)
if err != nil {
return nil, fmt.Errorf("invalid URI template %q: %w", templateStr, err)
}

normalizedURI := normalizeURI(uri)
values := template.Match(normalizedURI)
if len(values) == 0 {
return nil, fmt.Errorf("URI %q does not match template", uri)
}

// Convert uritemplate.Values to string map
result := make(map[string]string)
for name, value := range values {
result[name] = value.String()
}
return result, nil
}

// ResourceInput represents the input to a resource function.
type ResourceInput struct {
URI string `json:"uri"` // The resource URI
Expand Down Expand Up @@ -127,11 +184,11 @@ func (r *resource) Matches(uri string) bool {

// Check template
if template, ok := resourceMeta["template"].(string); ok && template != "" {
matcher, err := coreresource.NewTemplateMatcher(template)
matches, err := matches(template, uri)
if err != nil {
return false
}
return matcher.Matches(uri)
return matches
}

return false
Expand All @@ -154,11 +211,7 @@ func (r *resource) ExtractVariables(uri string) (map[string]string, error) {

// Extract from template
if template, ok := resourceMeta["template"].(string); ok && template != "" {
matcher, err := coreresource.NewTemplateMatcher(template)
if err != nil {
return nil, fmt.Errorf("invalid template %q: %w", template, err)
}
return matcher.ExtractVariables(uri)
return extractVariables(template, uri)
}

return nil, fmt.Errorf("no URI or template found in resource metadata")
Expand Down
84 changes: 0 additions & 84 deletions go/core/resource/matcher.go

This file was deleted.

18 changes: 7 additions & 11 deletions go/genkit/genkit.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ import (
"os"
"os/signal"
"path/filepath"
"strings"
"syscall"

"github.com/firebase/genkit/go/ai"
Expand Down Expand Up @@ -385,11 +384,9 @@ func ListTools(g *Genkit) []ai.Tool {
continue
}
actionDesc := action.Desc()
if strings.HasPrefix(actionDesc.Key, "/"+string(core.ActionTypeTool)+"/") {
// Extract tool name from key
toolName := strings.TrimPrefix(actionDesc.Key, "/"+string(core.ActionTypeTool)+"/")
// Lookup the actual tool
tool := LookupTool(g, toolName)
if actionDesc.Type == core.ActionTypeTool {
// Lookup the actual tool using the action name
tool := LookupTool(g, actionDesc.Name)
if tool != nil {
tools = append(tools, tool)
}
Expand Down Expand Up @@ -1038,11 +1035,10 @@ func ListResources(g *Genkit) []ai.Resource {
}
actionDesc := action.Desc()
if actionDesc.Type == core.ActionTypeResource {
// Look up the resource wrapper
if resourceValue := g.reg.LookupValue(fmt.Sprintf("resource/%s", actionDesc.Name)); resourceValue != nil {
if resource, ok := resourceValue.(ai.Resource); ok {
resources = append(resources, resource)
}
// Lookup the actual resource using the action name
resource := ai.LookupResource(g.reg, actionDesc.Name)
if resource != nil {
resources = append(resources, resource)
}
}
}
Expand Down
17 changes: 3 additions & 14 deletions go/plugins/mcp/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ package mcp

import (
"fmt"
"strings"

"github.com/mark3labs/mcp-go/mcp"
)
Expand All @@ -32,19 +31,9 @@ func (c *GenkitMCPClient) GetToolNameWithNamespace(toolName string) string {
return fmt.Sprintf("%s_%s", c.options.Name, toolName)
}

// ContentToText extracts text content from MCP Content
func ContentToText(contentList []mcp.Content) string {
var textParts []string
for _, contentItem := range contentList {
if textContent, ok := contentItem.(mcp.TextContent); ok && textContent.Type == "text" {
textParts = append(textParts, textContent.Text)
} else if erContent, ok := contentItem.(mcp.EmbeddedResource); ok {
if trc, ok := erContent.Resource.(mcp.TextResourceContents); ok {
textParts = append(textParts, trc.Text)
}
}
}
return strings.Join(textParts, "")
// GetResourceNameWithNamespace returns a resource name prefixed with the client's namespace
func (c *GenkitMCPClient) GetResourceNameWithNamespace(resourceName string) string {
return fmt.Sprintf("%s_%s", c.options.Name, resourceName)
}

// ExtractTextFromContent extracts text content from MCP Content
Expand Down
37 changes: 37 additions & 0 deletions go/plugins/mcp/fixtures/basic_server/basic_server.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
"context"

"github.com/firebase/genkit/go/ai"
"github.com/firebase/genkit/go/genkit"
"github.com/firebase/genkit/go/plugins/mcp"
)

func main() {
g := genkit.Init(context.Background())
genkit.DefineResource(g, "test-docs", &ai.ResourceOptions{
Template: "file://test/{filename}",
}, func(ctx context.Context, input *ai.ResourceInput) (*ai.ResourceOutput, error) {
return &ai.ResourceOutput{
Content: []*ai.Part{ai.NewTextPart("test content")},
}, nil
})

server := mcp.NewMCPServer(g, mcp.MCPServerOptions{Name: "test"})
server.ServeStdio()
}
42 changes: 42 additions & 0 deletions go/plugins/mcp/fixtures/content_server/content_server.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
"context"
"fmt"

"github.com/firebase/genkit/go/ai"
"github.com/firebase/genkit/go/genkit"
"github.com/firebase/genkit/go/plugins/mcp"
)

func main() {
g := genkit.Init(context.Background())

// Resource that provides different content based on filename
genkit.DefineResource(g, "content-provider", &ai.ResourceOptions{
Template: "file://data/{filename}",
}, func(ctx context.Context, input *ai.ResourceInput) (*ai.ResourceOutput, error) {
filename := input.Variables["filename"]
content := fmt.Sprintf("CONTENT_FROM_SERVER: This is %s with important data.", filename)
return &ai.ResourceOutput{
Content: []*ai.Part{ai.NewTextPart(content)},
}, nil
})

server := mcp.NewMCPServer(g, mcp.MCPServerOptions{Name: "content-server"})
server.ServeStdio()
}
36 changes: 36 additions & 0 deletions go/plugins/mcp/fixtures/policy_server/policy_server.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
"context"

"github.com/firebase/genkit/go/ai"
"github.com/firebase/genkit/go/genkit"
"github.com/firebase/genkit/go/plugins/mcp"
)

func main() {
g := genkit.Init(context.Background())
genkit.DefineResource(g, "company-policy", &ai.ResourceOptions{
Template: "docs://policy/{section}",
}, func(ctx context.Context, input *ai.ResourceInput) (*ai.ResourceOutput, error) {
return &ai.ResourceOutput{
Content: []*ai.Part{ai.NewTextPart("VACATION_POLICY: Employees get 20 days vacation per year.")},
}, nil
})
server := mcp.NewMCPServer(g, mcp.MCPServerOptions{Name: "test"})
server.ServeStdio()
}
Loading
Loading