MCP server should call `pkg/functions` directly, not shell out to the `func` binary
@vishal-iiserb is already working on this.
Since May 17, 2026.
Assessment
This issue has not been assessed yet.
Description
Summary
The MCP server (pkg/mcp) currently exposes Functions to AI agents by shelling out to the func binary as a subprocess. This was an expedient — it let the MCP surface land quickly by piggy-backing on the existing CLI. It should be replaced with direct use of the core library at pkg/functions, in the same way the CLI itself does.
Architectural principle
The real logic of Functions lives in pkg/functions (and friends — pkg/k8s, pkg/docker, pkg/knative, etc.). The cmd/ package wraps that library as a CLI. The pkg/mcp package should wrap the same library as an MCP server.
┌──────────────┐
│ pkg/functions│ ← core: where the logic lives
└──────┬───────┘
│
┌────────────┼────────────┐
▼ ▼ ▼
┌─────┐ ┌─────┐ ┌──────┐
│ cli │ │ mcp │ │ ... │ (future surfaces)
└─────┘ └─────┘ └──────┘
Today's reality is the bottom-left arrow points sideways — pkg/mcp calls into cli, which calls into pkg/functions. That extra hop is a leaky abstraction.
Why this matters now
- Every MCP tool handler runs
exec.Command("func", ...), captures stdout/stderr blobs, and either passes the text to the agent verbatim or tries to parse it. The parsing is brittle (see #3753 for a worked example), the structured-output story is thin, and adding any tool requires routing through anexecutorinterface that has been growing leaky over time (e.g., the recently-proposedExecuteRawin #3752). - Subprocess overhead is real (~100ms+ per MCP call just to fork
func). - Error propagation is constrained to exit codes + text scraping rather than the rich typed errors that already exist in the Go API.
- The in-process API gets exercised consistently by both consumers, surfacing bugs and API gaps faster.
The TODO comment already in the tree at pkg/mcp/mcp.go:154-155 flags exactly this direction.
Scope
- Replace every MCP tool handler in
pkg/mcp/tools_*.gowith direct calls intopkg/functions(andpkg/k8s,pkg/docker, etc. where appropriate). - Drop the
executorinterface and its mocks once all handlers are migrated. - Replace text-blob results with typed values flowing into
CallToolResult.StructuredContent. - Error mapping uses Go's typed errors (
errors.As) rather than substring matching.
Phased migration suggested
- Add an interim adapter that lets shellout-backed and library-backed handlers coexist (so we can land one tool at a time without breaking the others).
- Migrate read-only tools first (
list,describe,healthcheck,check_prerequisitesif that lands first via #3749 / #3752) — lowest risk, easiest to validate. - Migrate mutating tools (
build,deploy,delete,config_*). - Remove the
executorinterface, its mocks, and thefuncbinary as a runtime dependency of the MCP server.
Open design questions
- Long-running operations (
func deploy,func logs): how do we surface progress events to the agent? MCP supports progress notifications via the protocol; the in-process API would need to emit these. - Interactive prompts:
funchas prompt flows; MCP context never has a TTY. Handlers must either resolve prompts via the request payload or fail cleanly. - Testing surface: the current
executormock made it easy to test handlers in isolation. The replacement is to inject the samepkg/functionsclient or its interfaces — most of which already exist as injectable points. - Concurrency: the CLI assumes one operation per process; direct library use means handlers may run concurrently. Review
pkg/functionsfor thread safety where MCP would call into it from multiple goroutines.
Relationship to other work
- #3769 (
--jsonoutput for everyfuncsubcommand) remains valuable — for scripts, CI, third-party tools, and IDEs that drivefuncas a subprocess. It is not blocked by this issue and does not block this issue. - #3770 (MCP consume
func --jsonand forward viaStructuredContent) is superseded by this issue. Ifpkg/mcpcallspkg/functionsdirectly, there is nofunc --jsonsubprocess output to consume. - #3752 (
check_prerequisitestool): a current-day example of the friction this issue resolves — needed to addExecuteRawto the executor to shell out todocker/kubectl. Once the MCP server usespkg/functionsandpkg/k8sdirectly, the same checks become idiomatic Go. - #3750 / #3753 (structured error categories): becomes a non-issue. Typed errors from
pkg/functionsreach MCP handlers directly; categorization is a pure mapping, not a parsing problem.
Non-goals
- Removing the
funcCLI. The CLI is the primary human-facing surface and stays as-is. - Removing the MCP server's behavior. Tools keep the same names, inputs, and observable outputs. This is internals only.
- Dominant language
- Go
- Stars
- 365
- Forks
- 223
- Avg merge
- 2d 3h
- Merged PRs (30d)
- 25
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from knative/func
-
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
-
lifecycle/stale
Difficulty 1/5 Under an hour Newbie friendliness 88/100
-
setServiceOptions: %f format produces invalid autoscaling annotation values with trailing zeros Openlifecycle/stale
Difficulty 1/5 Under an hour Newbie friendliness 90/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
-
kind/bug
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
Similar issues
-
Difficulty 1/5 Under an hour Newbie friendliness 84/100
-
enhancement needs triage
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
-
kind/cleanup
Difficulty 2/5 1-3 hours Newbie friendliness 88/100
kubernetes-sigs/kueue#15947 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
sympozium-ai/sympozium#627 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 86/100