Skip to content

Conversation

@lkingland
Copy link
Member

@lkingland lkingland commented Oct 29, 2025

Updates the POC Functions MCP server to be a slightly more mature experimental MCP server.

Enable with FUNC_ENABLE_MCP and start (by configuring one's client LLM) via func mcp start.

@knative-prow knative-prow bot requested review from dsimansk and nainaz October 29, 2025 07:29
@knative-prow
Copy link

knative-prow bot commented Oct 29, 2025

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: lkingland

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@knative-prow knative-prow bot added approved Indicates a PR has been approved by an approver from all required OWNERS files. size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files. labels Oct 29, 2025
@lkingland lkingland force-pushed the push-krlrkrmlkzqt branch 4 times, most recently from b75b584 to 59f42c0 Compare October 29, 2025 07:44
@codecov
Copy link

codecov bot commented Oct 29, 2025

Codecov Report

❌ Patch coverage is 89.07692% with 142 lines in your changes missing coverage. Please review.
✅ Project coverage is 65.72%. Comparing base (83ea554) to head (3c9ec8f).
⚠️ Report is 5 commits behind head on main.

Files with missing lines Patch % Lines
pkg/mcp/resources.go 47.42% 89 Missing and 3 partials ⚠️
pkg/mcp/mock/executor.go 0.00% 10 Missing ⚠️
pkg/mcp/tools.go 83.63% 8 Missing and 1 partial ⚠️
pkg/mcp/tools_config.go 95.83% 4 Missing and 4 partials ⚠️
pkg/mcp/mcp.go 91.13% 7 Missing ⚠️
cmd/mcp.go 91.83% 4 Missing ⚠️
pkg/mcp/prompts_deploy.go 98.83% 1 Missing and 1 partial ⚠️
pkg/mcp/tools_build.go 97.50% 1 Missing and 1 partial ⚠️
pkg/mcp/tools_create.go 97.05% 1 Missing and 1 partial ⚠️
pkg/mcp/tools_delete.go 96.36% 1 Missing and 1 partial ⚠️
... and 2 more
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3155      +/-   ##
==========================================
+ Coverage   59.37%   65.72%   +6.34%     
==========================================
  Files         134      145      +11     
  Lines       13500    14104     +604     
==========================================
+ Hits         8016     9270    +1254     
+ Misses       4540     3847     -693     
- Partials      944      987      +43     
Flag Coverage Δ
e2e-tests 40.14% <3.48%> (-0.09%) ⬇️
integration-tests 60.49% <89.07%> (+7.50%) ⬆️
unit-tests 52.87% <89.07%> (+6.15%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@lkingland lkingland force-pushed the push-krlrkrmlkzqt branch 3 times, most recently from 7330b01 to be3efaf Compare October 29, 2025 11:11
/pkg/functions/testdata/default_home/go
/pkg/functions/testdata/default_home/.cache
/pkg/functions/testdata/migrations/*/.gitignore
/pkg/creds/auth.json
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@matzew
Copy link
Member

matzew commented Oct 30, 2025

I'd group prompts and tools into a package. and if needed, even sub-group the tools.

I'd than do something like https://github.com/containers/kubernetes-mcp-server/blob/main/pkg/toolsets/core/toolset.go#L23 for registering those.

Copy link

@twoGiants twoGiants left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @lkingland! Great job on the MCP integration 😸 👍

Most of my comments so far are cleanups, refactorings and a few typos. Some are opinions, so you decide if you want to go that route 😸 . Many can probably be done in a follow up.

I finished reviewing up to tools_build_test.go. Not much left. Will submit another after lunch.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is the actual API (for agents) I would use composition to structure, so that future fine tuning and modifications will be easier and not always to the same string.

No need to do now. Can be done in a follow up. We can create an issue for that and give it more thought. It's a good-first-issue.

Just a draft:

type AgentAPI struct {}

func (AgentAPI) funcDisabledHeader() string {
  return "⚠️ CRITICAL: FUNCTION TOOLS ARE DISABLED\n";
}

func (AgentAPI) funcEnabledHeader() string {
  return "✅ Function tools are enabled!\n";
}

func (a AgentAPI) terminology() string {
  return `TERMINOLOGY: Always capitalize "Function" when referring to a deployable Function (the service).
Use lowercase "function" only for programming concepts (functions in code).`
}

func (a AgentAPI) funcEnabledInstructions() string {
  return fmt.Sprintf(`%s

%s

IMPORTANT INSTRUCTIONS FOR YOU (the AI assistant):
When the user requests to create, deploy, build, or modify Functions, you MUST:
1. STOP immediately - do NOT attempt to use function tools
2. STOP immediately - do NOT offer to run 'func' commands directly
3. STOP immediately - do NOT try to work around this limitation
Instead, you MUST tell the user:
"The Function tools are currently disabled. To enable them:
1. Close/exit this application completely
2. Set FUNC_ENABLE_MCP=true in your MCP client configuration
3. Restart the application
For setup instructions, see:
https://github.com/knative/func/blob/main/docs/mcp-integration/integration.md"
DO NOT attempt any workarounds. The user must restart the client with the environment variable set.`
`, a.funcDisabledHeader(), a.terminology());
}

//...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Totally agree. Not going to do it now just because a giant, annoying string is easy to iterate on. But if this ends up being "correct-ish", then yes, let's make it composable and/or use string templates 👍🏻

// Returns an error with setup instructions if not enabled.
func checkMCPEnabled() error {
if os.Getenv("FUNC_ENABLE_MCP") != "true" {
return fmt.Errorf(`CANNOT EXECUTE: Function tools are disabled.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This error could also go into the AgentAPI stuct proposed above.

return nil, err
}

return &mcp.ReadResourceResult{

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This boilerplate code repeats and could be abstracted into a function.

uri string
}

func (r cmdHelpResource) desc() *mcp.Resource {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Untested => uses title() and description() internally which contains logic.

}
}

func (r templatesResource) handle(ctx context.Context, request *mcp.ReadResourceRequest, _ string, _ Executor) (*mcp.ReadResourceResult, error) {
Copy link

@twoGiants twoGiants Oct 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Untested. See comment about fetchTemplates().

return mcp.NewToolResultError(fmt.Sprintf("func deploy failed: %s", out)), nil
// errorResult creates an error CallToolResult with the given message.
// Used by tools to return errors in MCP protocol format.
func errorResult(msg string) *mcp.CallToolResult {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice! This is what I meant in my messages above too 😸 👍 ...abstractions help to keep the code clean.

DestructiveHint: ptr(false),
IdempotentHint: true, // Building the same source code multiple times produces the same container image.
},
InputSchema: map[string]any{
Copy link

@twoGiants twoGiants Oct 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

InputSchema creation boilerplate is duplicated and could be abstracted into helper functions or a InputSchemaBuilder struct.

Same everywhere else in tools_*.go

@lkingland
Copy link
Member Author

lkingland commented Oct 30, 2025

I'd group prompts and tools into a package. and if needed, even sub-group the tools.

I'd than do something like https://github.com/containers/kubernetes-mcp-server/blob/main/pkg/toolsets/core/toolset.go#L23 for registering those.

The kubernetes-mcp-server toolset registry pattern is good for that use case (dozens of tools across multiple independent domains like Kubernetes, Helm, and Config), but is a bit overly complex here.

  1. No multiple independent tool domains (we have one cohesive domain: Knative Functions)
  2. No need for a plugin system for third-party tools (all our tools are in-tree)
  3. No need for conditional toolset loading (our 8 tools are always loaded)

For our single-domain, static toolset of only 8 tools, explicit registration in New() is simple, without the indirection of init functions, global registries, etc. Lower cognitive load.

I'd say we follow that pattern if our complexity grows 👍

Copy link

@twoGiants twoGiants left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left a few more comments.

Looks all good! I have mostly cleanup comments. Can (not must) all be done in a follow PR and would polish the code a bit. But you make the call 😸

Comment on lines +118 to +131
cmdParts := parseCommand(cmdPrefix)
cmdParts = append(cmdParts, args...)

output, err := executor.Execute(ctx, ".", cmdParts[0], cmdParts[1:]...)
if err != nil {
return errorResult(fmt.Sprintf("func build failed: %s\nOutput: %s", err, string(output))), nil
}

// Build structured output
result := BuildOutput{
Message: string(output),
}

return jsonResult(result), nil

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This part is nearly the same in all handlers, could be a helper parseAndExecute(...).

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar comment applies to this test code: setup and execution code could be in test helpers, so that only what differs between the tests stays in them.

}

// Assert executor invoked
if !executor.ExecuteInvoked {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This couples the test(s) to an implementation detail of the handler. I would not check that the executer was invoked but if the result the handler returns has everything I expect. Wdys?

defer cancel()

executor := mock.NewExecutor()
executor.ExecuteFn = func(ctx context.Context, dir string, name string, args ...string) ([]byte, error) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This mocked function repeats in all the other tests which qualifies it for a test helper 😸.

defer cancel()

executor := mock.NewExecutor()
executor.ExecuteFn = func(ctx context.Context, dir string, name string, args ...string) ([]byte, error) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This mocked function repeats (with small differences) in all the other tests which qualifies it for a test helper 😸.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like all those tool tests are very similar. One could factor out a bunch of test helpers (I left a few comments) and/or one could use table driven tests for the three test types Test*, Test*_Options, Test*_BinaryFailure and remove a lot of duplication.


import "os"

func instructions() string {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would extract the strings into functions so this one is a bit better readable.

func instructions() string {
	if os.Getenv("FUNC_ENABLE_MCP") != "true" {
		return disabledInstructions()
	}
	
	return enabledInstructions()
}

const terminology = `TERMINOLOGY: Always capitalize "Function" when referring to a deployable Function (the service).
Use lowercase "function" only for programming concepts (functions in code).`

func disabledInstruction() string {
  return fmt.Sprintf(`⚠️  CRITICAL: FUNCTION TOOLS ARE DISABLED

%s

IMPORTANT INSTRUCTIONS FOR YOU (the AI assistant):
...`, terminology)
}

func enabledInstructions() string {
  return fmt.Sprintf(`✅ Function tools are enabled!

%s

...`, terminology)
}

lkingland and others added 4 commits October 31, 2025 00:56
Co-authored-by: Stanislav Jakuschevskij <[email protected]>
Co-authored-by: Stanislav Jakuschevskij <[email protected]>
Co-authored-by: Stanislav Jakuschevskij <[email protected]>
Co-authored-by: Stanislav Jakuschevskij <[email protected]>
Copy link
Contributor

@gauron99 gauron99 Oct 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pls remove or include in .gitignore as per #3158
same with absoluteLinkWindows

@lkingland lkingland marked this pull request as draft November 4, 2025 06:21
@knative-prow knative-prow bot added the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Nov 4, 2025
@lkingland
Copy link
Member Author

Updates the POC Functions MCP server to be a slightly more mature experimental MCP server.

Converting back to draft, so we can make it slightly more... slightly more mature.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

approved Indicates a PR has been approved by an approver from all required OWNERS files. do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants