📋 Prerequisites
🎯 Affected Service(s)
Multiple services / System-wide issue
🚦 Impact/Severity
Blocker
🐛 Bug Description
When an Agent (kagent.dev/v1alpha2) lists a tool of type: McpServer
referencing a RemoteMCPServer without an explicit toolNames: filter,
the controller materializes the agent's config.json Secret with
http_tools[*].tools = null (and sse_tools[*].tools = null for the SSE
protocol). The Python ADK runtime image
(cr.kagent.dev/kagent-dev/kagent/app:0.9.1) then crashes on startup with a
pydantic ValidationError, leaving the agent pod in CrashLoopBackOff.
Whether toolNames is intended to be required or optional, the current
behavior is a bug because omitting it silently produces a config that the
runtime cannot load, with no controller-side validation or admission error.
The mismatch is between two sides of the same wire format:
The CRD schema for McpServerTool.toolNames does not appear to mark the
field as required, so users (and tooling) have no signal that omitting it
is unsafe.
🔄 Steps To Reproduce
-
Stand up kagent v0.9.1 against any working RemoteMCPServer
(e.g. cr.kagent.dev/kagent-dev/kagent/tools).
-
Apply the following Agent (no toolNames on the tool ref):
apiVersion: kagent.dev/v1alpha2
kind: Agent
metadata:
name: repro
namespace: kagent
spec:
type: Declarative
description: repro
declarative:
modelConfig: <existing>
systemMessage: "test"
tools:
- type: McpServer
mcpServer:
apiGroup: kagent.dev
kind: RemoteMCPServer
name: <existing-rms>
# NOTE: no toolNames
-
Observe the agent pod enter CrashLoopBackOff.
-
kubectl logs shows the pydantic validation error below.
-
Inspect the rendered config:
kubectl get secret repro-config -o jsonpath='{.data.config\.json}' | base64 -d | jq '.http_tools[0].tools'
prints null.
Adding any non-empty toolNames: [<one>] to the tool ref makes the pod
start successfully — that's a viable workaround.
🤔 Expected Behavior
The agent should not CrashLoop on a config the controller itself produced.
Possible resolutions (deferring to maintainers on the intended semantics):
- If
toolNames is meant to be required, validate it in the CRD
schema or in a controller-side admission check so users get a clear
error at apply-time rather than a pydantic crash at pod-start.
- If
toolNames is meant to be optional (with omission meaning "all
discovered tools"), the controller should emit an explicit list (or
the runtime should tolerate None) rather than handing the runtime a
null it can't parse.
📱 Actual Behavior
Agent pod CrashLoops on startup with:
ValidationError: 1 validation error for AgentConfig
http_tools.0.tools
Input should be a valid list [type=list_type, input_value=None,
input_type=NoneType]
The rejected blob is:
{
"http_tools": [
{
"params": {"url": "...", "headers": {}, "terminate_on_close": true},
"tools": null
}
]
}
💻 Environment
- Kubernetes: Talos Linux, k8s 1.34
- kagent: v0.9.1 (
cr.kagent.dev/kagent-dev/kagent/app:0.9.1 runtime,
controller image cr.kagent.dev/kagent-dev/kagent/controller:0.9.1)
- Helm chart:
kagent 0.9.1
- Topology: agents reference a single
RemoteMCPServer named
agentgateway-mcp that fronts several upstream MCP servers via
agentgateway. The RMS reports ~50
discoveredTools and is Healthy.
🔍 Additional Context
- Same field exists on
sse_tools[*].tools and the same nil-slice → JSON
null defect applies there.
- Workaround: pin explicit
toolNames per agent.
- I'll open a PR shortly that addresses both sides defensively (controller
expands discovered tools when toolNames is empty; runtime coerces
None → [] so older controllers don't crash newer runtimes). Happy
to rework toward whichever direction maintainers prefer if the intended
semantics are different.
📋 Logs
$ kubectl logs -n kagent repro-<hash>
Traceback (most recent call last):
...
File ".../kagent/adk/types.py", line ..., in to_agent
config = AgentConfig.model_validate(...)
pydantic_core._pydantic_core.ValidationError: 1 validation error for AgentConfig
http_tools.0.tools
Input should be a valid list [type=list_type, input_value=None, input_type=NoneType]
For further information visit https://errors.pydantic.dev/2.x/v/list_type
🙋 Are you willing to contribute?
📋 Prerequisites
🎯 Affected Service(s)
Multiple services / System-wide issue
🚦 Impact/Severity
Blocker
🐛 Bug Description
When an
Agent(kagent.dev/v1alpha2) lists a tool oftype: McpServerreferencing a
RemoteMCPServerwithout an explicittoolNames:filter,the controller materializes the agent's
config.jsonSecret withhttp_tools[*].tools = null(andsse_tools[*].tools = nullfor the SSEprotocol). The Python ADK runtime image
(
cr.kagent.dev/kagent-dev/kagent/app:0.9.1) then crashes on startup with apydantic
ValidationError, leaving the agent pod inCrashLoopBackOff.Whether
toolNamesis intended to be required or optional, the currentbehavior is a bug because omitting it silently produces a config that the
runtime cannot load, with no controller-side validation or admission error.
The mismatch is between two sides of the same wire format:
HttpMcpServerConfig.Toolsis[]stringwith theJSON tag
json:"tools"(noomitempty). WhenmcpServerTool.ToolNamesis
nilingo/core/internal/controller/translator/agent/adk_api_translator.go,encoding/jsonmarshals the nil slice as JSONnull.HttpMcpServerConfig.toolsis typed
list[str] = Field(default_factory=list), which rejectsNonewith a
list_typevalidator error.The CRD schema for
McpServerTool.toolNamesdoes not appear to mark thefield as required, so users (and tooling) have no signal that omitting it
is unsafe.
🔄 Steps To Reproduce
Stand up kagent v0.9.1 against any working
RemoteMCPServer(e.g.
cr.kagent.dev/kagent-dev/kagent/tools).Apply the following Agent (no
toolNameson the tool ref):Observe the agent pod enter
CrashLoopBackOff.kubectl logsshows the pydantic validation error below.Inspect the rendered config:
kubectl get secret repro-config -o jsonpath='{.data.config\.json}' | base64 -d | jq '.http_tools[0].tools'prints
null.Adding any non-empty
toolNames: [<one>]to the tool ref makes the podstart successfully — that's a viable workaround.
🤔 Expected Behavior
The agent should not CrashLoop on a config the controller itself produced.
Possible resolutions (deferring to maintainers on the intended semantics):
toolNamesis meant to be required, validate it in the CRDschema or in a controller-side admission check so users get a clear
error at apply-time rather than a pydantic crash at pod-start.
toolNamesis meant to be optional (with omission meaning "alldiscovered tools"), the controller should emit an explicit list (or
the runtime should tolerate
None) rather than handing the runtime anullit can't parse.📱 Actual Behavior
Agent pod CrashLoops on startup with:
The rejected blob is:
{ "http_tools": [ { "params": {"url": "...", "headers": {}, "terminate_on_close": true}, "tools": null } ] }💻 Environment
cr.kagent.dev/kagent-dev/kagent/app:0.9.1runtime,controller image
cr.kagent.dev/kagent-dev/kagent/controller:0.9.1)kagent0.9.1RemoteMCPServernamedagentgateway-mcpthat fronts several upstream MCP servers viaagentgateway. The RMS reports ~50
discoveredToolsand isHealthy.🔍 Additional Context
sse_tools[*].toolsand the same nil-slice → JSONnulldefect applies there.toolNamesper agent.expands discovered tools when
toolNamesis empty; runtime coercesNone→[]so older controllers don't crash newer runtimes). Happyto rework toward whichever direction maintainers prefer if the intended
semantics are different.
📋 Logs
🙋 Are you willing to contribute?