Skip to content

Commit 2e49dd0

Browse files
authored
mcp: adds remote template support (knative#2951)
Signed-off-by: kapil <[email protected]>
1 parent df09ae9 commit 2e49dd0

File tree

2 files changed

+117
-1
lines changed

2 files changed

+117
-1
lines changed

pkg/mcp/help.go

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,75 @@ package mcp
22

33
import (
44
"context"
5+
"encoding/json"
56
"fmt"
7+
"io"
8+
"net/http"
69
"os/exec"
710
"strings"
811

912
"github.com/mark3labs/mcp-go/mcp"
1013
)
1114

15+
type template struct {
16+
Repository string `json:"repository"`
17+
Language string `json:"language"`
18+
TemplateName string `json:"template"`
19+
}
20+
21+
func fetchTemplates() ([]template, error) {
22+
var out []template
23+
seen := make(map[string]bool)
24+
25+
for _, repoURL := range TEMPLATE_RESOURCE_URIS {
26+
owner, repo := parseGitHubURL(repoURL)
27+
api := fmt.Sprintf("https://api.github.com/repos/%s/%s/git/trees/main?recursive=1", owner, repo)
28+
29+
resp, err := http.Get(api)
30+
if err != nil {
31+
return nil, err
32+
}
33+
defer resp.Body.Close()
34+
35+
body, err := io.ReadAll(resp.Body)
36+
if err != nil {
37+
return nil, err
38+
}
39+
40+
var tree struct {
41+
Tree []struct {
42+
Path string `json:"path"`
43+
} `json:"tree"`
44+
}
45+
if err := json.Unmarshal(body, &tree); err != nil {
46+
return nil, err
47+
}
48+
49+
for _, item := range tree.Tree {
50+
parts := strings.Split(item.Path, "/")
51+
if len(parts) >= 2 && !strings.HasPrefix(parts[0], ".") {
52+
lang, name := parts[0], parts[1]
53+
key := lang + "/" + name
54+
if !seen[key] {
55+
out = append(out, template{
56+
Language: lang,
57+
TemplateName: name,
58+
Repository: repoURL,
59+
})
60+
seen[key] = true
61+
}
62+
}
63+
}
64+
}
65+
return out, nil
66+
}
67+
68+
func parseGitHubURL(url string) (owner, repo string) {
69+
trim := strings.TrimPrefix(url, "https://github.com/")
70+
parts := strings.Split(trim, "/")
71+
return parts[0], parts[1]
72+
}
73+
1274
func handleRootHelpResource(ctx context.Context, request mcp.ReadResourceRequest) ([]mcp.ResourceContents, error) {
1375
content, err := exec.Command("func", "--help").Output()
1476
if err != nil {
@@ -39,6 +101,25 @@ func runHelpCommand(args []string, uri string) ([]mcp.ResourceContents, error) {
39101
}, nil
40102
}
41103

104+
func handleListTemplatesResource(ctx context.Context, request mcp.ReadResourceRequest) ([]mcp.ResourceContents, error) {
105+
templates, err := fetchTemplates()
106+
if err != nil {
107+
return nil, err
108+
}
109+
content, err := json.MarshalIndent(templates, "", " ")
110+
if err != nil {
111+
return nil, err
112+
}
113+
114+
return []mcp.ResourceContents{
115+
mcp.TextResourceContents{
116+
URI: "func://templates",
117+
MIMEType: "text/plain",
118+
Text: string(content),
119+
},
120+
}, nil
121+
}
122+
42123
func handleCmdHelpPrompt(ctx context.Context, request mcp.GetPromptRequest) (*mcp.GetPromptResult, error) {
43124
cmd := request.Params.Arguments["cmd"]
44125
if cmd == "" {
@@ -86,3 +167,22 @@ func handleRootHelpPrompt(ctx context.Context, request mcp.GetPromptRequest) (*m
86167
},
87168
), nil
88169
}
170+
171+
func handleListTemplatesPrompt(ctx context.Context, request mcp.GetPromptRequest) (*mcp.GetPromptResult, error) {
172+
return mcp.NewGetPromptResult(
173+
"List Templates Prompt",
174+
[]mcp.PromptMessage{
175+
mcp.NewPromptMessage(
176+
mcp.RoleUser,
177+
mcp.NewTextContent("List available function templates"),
178+
),
179+
mcp.NewPromptMessage(
180+
mcp.RoleAssistant,
181+
mcp.NewEmbeddedResource(mcp.TextResourceContents{
182+
URI: "func://templates",
183+
MIMEType: "text/plain",
184+
}),
185+
),
186+
},
187+
), nil
188+
}

pkg/mcp/mcp.go

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ import (
77
"github.com/mark3labs/mcp-go/server"
88
)
99

10+
var TEMPLATE_RESOURCE_URIS = []string{
11+
"https://github.com/gauron99/func-templates",
12+
}
13+
1014
type MCPServer struct {
1115
server *server.MCPServer
1216
}
@@ -43,7 +47,7 @@ func NewServer() *MCPServer {
4347

4448
// Optional flags
4549
mcp.WithString("template", mcp.Description("Function template (e.g., http, cloudevents)")),
46-
mcp.WithString("repository", mcp.Description("URI to Git repo containing the template")),
50+
mcp.WithString("repository", mcp.Description("URI to Git repo containing the template. Overrides default template selection when provided.")),
4751
mcp.WithBoolean("confirm", mcp.Description("Prompt to confirm options interactively")),
4852
mcp.WithBoolean("verbose", mcp.Description("Print verbose logs")),
4953
),
@@ -325,6 +329,14 @@ func NewServer() *MCPServer {
325329
return runHelpCommand([]string{"config", "envs", "remove"}, "func://config/envs/remove/docs")
326330
})
327331

332+
// Static resource for listing available templates
333+
mcpServer.AddResource(mcp.NewResource(
334+
"func://templates",
335+
"Available Templates",
336+
mcp.WithResourceDescription("List of available function templates"),
337+
mcp.WithMIMEType("plain/text"),
338+
), handleListTemplatesResource)
339+
328340
mcpServer.AddPrompt(mcp.NewPrompt("help",
329341
mcp.WithPromptDescription("help prompt for the root command"),
330342
), handleRootHelpPrompt)
@@ -337,6 +349,10 @@ func NewServer() *MCPServer {
337349
),
338350
), handleCmdHelpPrompt)
339351

352+
mcpServer.AddPrompt(mcp.NewPrompt("list_templates",
353+
mcp.WithPromptDescription("prompt to list available function templates"),
354+
), handleListTemplatesPrompt)
355+
340356
return &MCPServer{
341357
server: mcpServer,
342358
}

0 commit comments

Comments
 (0)