Skip to content

Commit b391f1f

Browse files
committed
rearchitect resources
1 parent 33c2104 commit b391f1f

File tree

12 files changed

+1002
-286
lines changed

12 files changed

+1002
-286
lines changed

docs/reference/func_mcp.md

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,22 +13,30 @@ SYNOPSIS
1313

1414
DESCRIPTION
1515
Manages a Model Context Protocol (MCP) server over standard input/output (stdio) transport.
16-
This implementation aims to support tools for deploying and creating serverless functions.
16+
This server enables AI language models to interact with Knative Functions through the
17+
Model Context Protocol.
1718

18-
Note: This command is still under development.
19+
IMPORTANT: This command is designed to be invoked by MCP clients (such as Claude Desktop,
20+
Cursor, VS Code, Windsurf, etc.), not run directly by users. The MCP client automatically
21+
launches and manages the server based on its configuration.
22+
23+
For setup instructions and client configuration examples, see:
24+
https://github.com/knative/func/blob/main/docs/mcp-integration/integration.md
25+
26+
Note: This is an EXPERIMENTAL feature. The FUNC_ENABLE_MCP environment variable must be
27+
set to "true" for the server to start. See documentation for details.
1928

2029
AVAILABLE COMMANDS
2130
start Start the MCP server
2231

2332
EXAMPLES
2433

25-
o Start an MCP server:
26-
func mcp start
27-
28-
o Display this help:
29-
func mcp
34+
o View this help:
3035
func mcp --help
3136

37+
Note: End users should configure their MCP client, not run these commands directly.
38+
See the documentation link above for configuration instructions.
39+
3240

3341
### Options
3442

docs/reference/func_mcp_start.md

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,34 @@ SYNOPSIS
1313

1414
DESCRIPTION
1515
Starts a Model Context Protocol (MCP) server over standard input/output (stdio) transport.
16-
This server provides tools for deploying and creating serverless functions.
16+
This server provides tools for AI language models to deploy and create Knative serverless
17+
functions through the Model Context Protocol.
18+
19+
IMPORTANT: This command is designed to be invoked by MCP clients (such as Claude Desktop,
20+
Cursor, VS Code, Windsurf, etc.), not run directly by users. The MCP client automatically
21+
launches this command based on its configuration and manages the server lifecycle.
22+
23+
PREREQUISITES:
24+
- The FUNC_ENABLE_MCP environment variable must be set to "true"
25+
- The MCP client must be properly configured
26+
27+
For detailed setup instructions and client configuration examples, see:
28+
https://github.com/knative/func/blob/main/docs/mcp-integration/integration.md
1729

1830
EXAMPLES
1931

20-
o Start an MCP server:
21-
func mcp start
32+
This command is typically not run directly. Instead, configure your MCP client with:
33+
34+
{
35+
"mcpServers": {
36+
"func-mcp": {
37+
"command": "func",
38+
"args": ["mcp", "start"]
39+
}
40+
}
41+
}
42+
43+
For complete configuration instructions, see the documentation link above.
2244

2345

2446
```

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ require (
6060
golang.org/x/sync v0.17.0
6161
golang.org/x/sys v0.37.0
6262
golang.org/x/term v0.36.0
63+
golang.org/x/text v0.30.0
6364
gopkg.in/yaml.v2 v2.4.0
6465
gopkg.in/yaml.v3 v3.0.1
6566
gotest.tools/v3 v3.5.2
@@ -286,7 +287,6 @@ require (
286287
go.yaml.in/yaml/v3 v3.0.4 // indirect
287288
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 // indirect
288289
golang.org/x/mod v0.29.0 // indirect
289-
golang.org/x/text v0.30.0 // indirect
290290
golang.org/x/time v0.12.0 // indirect
291291
gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect
292292
google.golang.org/api v0.233.0 // indirect

pkg/mcp/mcp.go

Lines changed: 34 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package mcp
22

33
import (
44
"context"
5+
"os/exec"
56

67
"github.com/modelcontextprotocol/go-sdk/mcp"
78
)
@@ -10,6 +11,22 @@ var templateRepos = []string{
1011
"https://github.com/functions-dev/templates",
1112
}
1213

14+
// Executor abstracts command execution for testability
15+
type Executor interface {
16+
Execute(ctx context.Context, dir string, name string, args ...string) ([]byte, error)
17+
}
18+
19+
// binaryExecutor implements Executor using os/exec
20+
type binaryExecutor struct{}
21+
22+
func (e binaryExecutor) Execute(ctx context.Context, dir string, name string, args ...string) ([]byte, error) {
23+
cmd := exec.CommandContext(ctx, name, args...)
24+
if dir != "" {
25+
cmd.Dir = dir
26+
}
27+
return cmd.CombinedOutput()
28+
}
29+
1330
type Server struct {
1431
impl *mcp.Server
1532
prefix string // Command prefix to use (e.g., "func" or "kn func")
@@ -26,12 +43,12 @@ type tool interface {
2643

2744
type resource interface {
2845
desc() *mcp.Resource
29-
handler(prefix string) mcp.ResourceHandler
46+
handle(context.Context, *mcp.ReadResourceRequest, string, Executor) (*mcp.ReadResourceResult, error)
3047
}
3148

3249
type prompt interface {
3350
desc() *mcp.Prompt
34-
handler(prefix string) mcp.PromptHandler
51+
handle(context.Context, *mcp.GetPromptRequest, string) (*mcp.GetPromptResult, error)
3552
}
3653

3754
type Option func(*Server)
@@ -71,23 +88,22 @@ func New(options ...Option) *Server {
7188
},
7289
resources: []resource{
7390
rootHelpResource{},
74-
cmdHelpResource{[]string{"create"}, "func://create/docs"},
75-
cmdHelpResource{[]string{"build"}, "func://build/docs"},
76-
cmdHelpResource{[]string{"deploy"}, "func://deploy/docs"},
77-
cmdHelpResource{[]string{"list"}, "func://list/docs"},
78-
cmdHelpResource{[]string{"delete"}, "func://delete/docs"},
79-
cmdHelpResource{[]string{"config", "volumes", "add"}, "func://config/volumes/add/docs"},
80-
cmdHelpResource{[]string{"config", "volumes", "remove"}, "func://config/volumes/remove/docs"},
81-
cmdHelpResource{[]string{"config", "labels", "add"}, "func://config/labels/add/docs"},
82-
cmdHelpResource{[]string{"config", "labels", "remove"}, "func://config/labels/remove/docs"},
83-
cmdHelpResource{[]string{"config", "envs", "add"}, "func://config/envs/add/docs"},
84-
cmdHelpResource{[]string{"config", "envs", "remove"}, "func://config/envs/remove/docs"},
91+
cmdHelpResource{[]string{"create"}, "function://help/create"},
92+
cmdHelpResource{[]string{"build"}, "function://help/build"},
93+
cmdHelpResource{[]string{"deploy"}, "function://help/deploy"},
94+
cmdHelpResource{[]string{"list"}, "function://help/list"},
95+
cmdHelpResource{[]string{"delete"}, "function://help/delete"},
96+
cmdHelpResource{[]string{"config", "volumes", "add"}, "function://help/config/volumes/add"},
97+
cmdHelpResource{[]string{"config", "volumes", "remove"}, "function://help/config/volumes/remove"},
98+
cmdHelpResource{[]string{"config", "labels", "add"}, "function://help/config/labels/add"},
99+
cmdHelpResource{[]string{"config", "labels", "remove"}, "function://help/config/labels/remove"},
100+
cmdHelpResource{[]string{"config", "envs", "add"}, "function://help/config/envs/add"},
101+
cmdHelpResource{[]string{"config", "envs", "remove"}, "function://help/config/envs/remove"},
102+
currentFunctionResource{},
85103
templatesResource{},
86104
},
87105
prompts: []prompt{
88-
helpPrompt{},
89-
cmdHelpPrompt{},
90-
listTemplatesPrompt{},
106+
createFunctionPrompt{},
91107
},
92108
}
93109

@@ -100,11 +116,11 @@ func New(options ...Option) *Server {
100116
}
101117

102118
for _, resource := range s.resources {
103-
s.impl.AddResource(resource.desc(), resource.handler(s.prefix))
119+
s.impl.AddResource(resource.desc(), withResourcePrefix(s.prefix, s.executor, resource.handle))
104120
}
105121

106122
for _, prompt := range s.prompts {
107-
s.impl.AddPrompt(prompt.desc(), prompt.handler(s.prefix))
123+
s.impl.AddPrompt(prompt.desc(), withPromptPrefix(s.prefix, prompt.handle))
108124
}
109125

110126
return s

pkg/mcp/mcp_test.go

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -109,21 +109,54 @@ func TestStart(t *testing.T) {
109109
}
110110
}
111111

112-
// List resources - should have all 13 registered resources
112+
// List resources - should have all 14 registered resources
113113
resourcesResult, err := clientSession.ListResources(ctx, &mcp.ListResourcesParams{})
114114
if err != nil {
115115
t.Fatalf("failed to list resources: %v", err)
116116
}
117-
if len(resourcesResult.Resources) != 13 {
118-
t.Errorf("expected 13 resources, got %d", len(resourcesResult.Resources))
117+
if len(resourcesResult.Resources) != 14 {
118+
t.Errorf("expected 14 resources, got %d", len(resourcesResult.Resources))
119+
}
120+
121+
// Verify new resource URIs are present
122+
expectedURIs := map[string]bool{
123+
"function://current": false,
124+
"function://help/root": false,
125+
"function://help/create": false,
126+
"function://help/build": false,
127+
"function://help/deploy": false,
128+
"function://help/list": false,
129+
"function://help/delete": false,
130+
"function://help/config/volumes/add": false,
131+
"function://help/config/volumes/remove": false,
132+
"function://help/config/labels/add": false,
133+
"function://help/config/labels/remove": false,
134+
"function://help/config/envs/add": false,
135+
"function://help/config/envs/remove": false,
136+
"function://templates": false,
137+
}
138+
for _, resource := range resourcesResult.Resources {
139+
if _, ok := expectedURIs[resource.URI]; ok {
140+
expectedURIs[resource.URI] = true
141+
}
142+
}
143+
for uri, found := range expectedURIs {
144+
if !found {
145+
t.Errorf("expected resource URI %q not found in resources list", uri)
146+
}
119147
}
120148

121-
// List prompts - should have all 3 registered prompts
149+
// List prompts - should have the workflow prompt
122150
promptsResult, err := clientSession.ListPrompts(ctx, &mcp.ListPromptsParams{})
123151
if err != nil {
124152
t.Fatalf("failed to list prompts: %v", err)
125153
}
126-
if len(promptsResult.Prompts) != 3 {
127-
t.Errorf("expected 3 prompts, got %d", len(promptsResult.Prompts))
154+
if len(promptsResult.Prompts) != 1 {
155+
t.Errorf("expected 1 prompt, got %d", len(promptsResult.Prompts))
156+
}
157+
158+
// Verify the workflow prompt is registered
159+
if len(promptsResult.Prompts) > 0 && promptsResult.Prompts[0].Name != "create_function" {
160+
t.Errorf("expected prompt name 'create_function', got %q", promptsResult.Prompts[0].Name)
128161
}
129162
}

0 commit comments

Comments
 (0)