This document provides instructions and context for AI coding agents working in the kagent-dev/tools repository.
KAgent Tools is a Go-based MCP (Model Context Protocol) server that provides Kubernetes and cloud-native management tools. It wraps CLI tools (kubectl, helm, istioctl, cilium, etc.) behind a standardized MCP interface for use by AI agents.
Architecture:
ββββββββββββββββββ
β MCP Client β (AI agent or kagent runtime)
βββββββββ¬βββββββββ
β MCP Protocol (SSE / Streamable HTTP)
βΌ
ββββββββββββββββββ
β MCP Server β cmd/main.go
β (mcp-go) β
βββββββββ¬βββββββββ
β Registers tool providers
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββ
β Tool Providers (pkg/) β
β k8s β helm β istio β argo β cilium β ... β
βββββββββββββββββββββββββββββββββββββββββββββββββ
β CommandBuilder (internal/commands/)
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββ
β CLI Binaries β
β kubectl β helm β istioctl β cilium β argoctl β
βββββββββββββββββββββββββββββββββββββββββββββββββ
tools/
βββ cmd/
β βββ main.go # MCP server entry point, tool registration
βββ pkg/ # Public tool provider packages
β βββ argo/ # Argo Rollouts tools
β βββ cilium/ # Cilium CNI networking tools
β βββ helm/ # Helm package management tools
β βββ istio/ # Istio service mesh tools
β βββ k8s/ # Kubernetes management tools
β β βββ resources/ # K8s resource sub-packages
β βββ kubescape/ # Security scanning tools
β βββ prometheus/ # Prometheus query tools
β βββ utils/ # DateTime and general utilities
βββ internal/ # Internal packages
β βββ cache/ # Thread-safe TTL cache with metrics
β βββ cmd/ # Shell command execution (with mock support)
β βββ commands/ # CommandBuilder fluent CLI interface
β βββ errors/ # Structured ToolError with context
β βββ logger/ # Structured logging
β βββ metrics/ # Prometheus metrics definitions
β βββ security/ # Input validation (K8s names, URLs, PromQL)
β βββ telemetry/ # OpenTelemetry tracing and HTTP middleware
β βββ version/ # Version info
βββ test/
β βββ e2e/ # End-to-end tests (Kind cluster)
βββ plugin/ # kagent Agent Plugin sources (plugin.json, mcp.json, kubeconfig, skills/)
βββ scripts/ # Build and setup scripts
β βββ kind/ # Kind cluster configuration
βββ helm/ # Helm chart for deployment
βββ docs/ # Documentation
βββ .github/workflows/ # CI/CD pipelines
β βββ ci.yaml # Build, test, e2e, Helm tests
β βββ tag.yaml # Release tagging
βββ Makefile # Build orchestration
βββ Dockerfile # Multi-stage build (multi-arch)
βββ go.mod # Go 1.27.3
βββ DEVELOPMENT.md # Development setup and standards
βββ CONTRIBUTION.md # Contribution process
Each provider lives in pkg/ and registers MCP tools via a RegisterTools(server, readOnly) function:
| Provider | Package | Purpose |
|---|---|---|
| Kubernetes | pkg/k8s/ |
kubectl get, describe, logs, exec, scale, patch, rollouts |
| Helm | pkg/helm/ |
List, install, upgrade, uninstall releases; repo management |
| Istio | pkg/istio/ |
VirtualService, Gateway, DestinationRule; proxy diagnostics |
| Argo Rollouts | pkg/argo/ |
Rollout promotion, pause/resume, canary/blue-green |
| Cilium | pkg/cilium/ |
BGP routing, cluster connectivity checks |
| Kubescape | pkg/kubescape/ |
Image scanning, configuration compliance |
| Prometheus | pkg/prometheus/ |
PromQL instant/range queries, scrape status |
| Utils | pkg/utils/ |
DateTime formatting/parsing |
| Task | Command |
|---|---|
| Build all platforms | make build |
| Format code | make fmt |
| Lint | make lint |
| Lint with auto-fix | make lint-fix |
| Run tests with coverage | make test |
| Run tests only (no build/lint) | make test-only |
| Run E2E tests | make e2e |
| Build Docker image | make docker-build |
| Build multi-arch Docker | make docker-build-all |
| Helm chart tests | make helm-test |
| Install locally | make tools-install |
| Run MCP server locally | make run |
| Check tool version updates | make check-releases |
| Run Jaeger tracing | make otel-local |
| Show all targets | make help |
Before submitting changes, run make fmt && make lint && make test.
go run ./cmd # defaults to stdio
./bin/kagent-tools --stdio # stdio transport
./bin/kagent-tools --http --port 8084 # HTTP transportUseful flags: --tools k8s,helm (limit providers), --kubeconfig <path>,
--read-only (do not register write tools), --metrics-port.
make run builds the image and runs the server in Docker on
http://localhost:8084/mcp.
Each provider implements a RegisterTools function that adds MCP tool handlers to the server. Registration goes through the wrapper in internal/mcp, which records the tool's provider for metrics and relaxes the inferred input schema so optional fields stay optional:
func RegisterTools(s *mcp.Server, readOnly bool) {
mcp.AddTool(s, "k8s", &mcp.Tool{
Name: "k8s_get_resources",
Description: "Get Kubernetes resources",
}, handleGetResources)
if !readOnly {
mcp.AddTool(s, "k8s", &mcp.Tool{
Name: "k8s_delete_resource",
Description: "Delete a Kubernetes resource",
}, handleDeleteResource)
}
}Handlers are registered with a typed input and a typed output: func handleX(ctx context.Context, req *mcp.CallToolRequest, in xInput) (*mcp.CallToolResult, xOutput, error). Handler functions are prefixed with handle (e.g., handleKubectlGetEnhanced, handleHelmList).
All MCP tool inputs and outputs must be strongly typed. The Go MCP SDK derives an input and output JSON schema from the handler's In and Out type parameters, populates CallToolResult.StructuredContent from the typed Out value, and validates that value against the inferred output schema on every call β so an untyped or wrongly-shaped Out is not merely untidy, it breaks the tool.
- Define a concrete input struct for every tool with
jsonandjsonschematags. - Define a concrete output DTO for every structured response.
- Never register handlers with
Out=any; typed outputs enable output schema inference and validation. - Do not use
any,interface{},map[string]any,map[string]interface{},[]any, or[]interface{}for handler inputs, handler outputs, public response DTOs, or tests. - Handler signature:
func handleX(ctx, req, in xInput) (*mcp.CallToolResult, xOutput, error).
Raw CLI text. Most providers wrap CLI output in text. Use the shared mcp.TextOutput wrapper ({"output": "..."}) instead of inventing a per-tool shape, and return it through the helpers so the zero value on an error path still validates:
// success β text is preserved in Content and mirrored in StructuredContent
return mcp.TextResult(output)
// tool-level failure β IsError=true, and the empty TextOutput keeps the
// inferred output schema satisfied
return mcp.TextError("resource_name is required")When a helper builds the *mcp.CallToolResult itself (e.g. runKubectlCommand returning (*mcp.CallToolResult, error)), convert it with mcp.TextOf(res) and return res, mcp.TextOf(res), err so the typed value matches the returned result.
Output-schema pitfalls. These are enforced by the SDK at call time and are easy to trip:
- Zero values are validated on every path, including errors. A field whose zero value marshals to
nullbut whose schema type is non-nullable (notablymap[K]V) makes all error returns fail withvalidating tool output. Give such fieldsomitempty. Slices and pointers infer as nullable (["null", ...]) and are safe. json.RawMessagedoes not mean "arbitrary JSON". The schema inference treats it as a byte slice and validation then rejects real objects and arrays. For genuinely dynamic JSON, return the raw text throughmcp.TextOutputrather than ajson.RawMessagefield, or re-indent it in place withjson.Indentwithout decoding intointerface{}(seeprettyJSONBodyinpkg/prometheus).- Not every type can be an
Out. Third-party structs with custom JSON marshallers can fail schema inference, which makesmcp.AddToolpanic at registration (the server will not start).v1beta1.WorkloadConfigurationScanis one such type; such handlers returnmcp.TextOutput.cmd/tools_output_schema_test.goregisters every provider and fails if anyOuttype cannot produce a valid schema. - A third-party type may be used as an
Outfield where inference succeeds ([]v1beta1.Match,v1beta1.ExecCalls,metav1.LabelSelectorall work today); prefer a local summary DTO where it does not.
Use the fluent CommandBuilder interface for executing CLI commands:
result, err := commands.NewCommandBuilder("helm").
WithArgs("list").
WithNamespace("default").
Execute(ctx)Available builders: KubectlBuilder(), HelmBuilder(), IstioCtlBuilder(), CiliumBuilder(), ArgoRolloutsBuilder().
MCP handlers return (*mcp.CallToolResult, error). Always return a nil Go error and use the structured ToolError type to format errors as MCP results:
toolErr := errors.NewToolError("get_pods", "kubernetes", errors.ErrValidation).
WithSuggestion("Check that the namespace exists").
WithContext("namespace", namespace)
return toolErr.ToMCPResult(), nilNever panic in tool handlers β always return a *mcp.CallToolResult.
Always validate user inputs using the internal/security package before passing them to CLI commands:
security.ValidateK8sResourceName()β Kubernetes resource namessecurity.ValidateNamespace()β Kubernetes namespacessecurity.ValidateURL()β HTTP URLssecurity.ValidatePromQLQuery()β PromQL syntaxsecurity.ValidateCommandInput()β General input sanitization
The internal/cache package provides a thread-safe generic Cache[T] with TTL:
- Cache is automatically invalidated on write operations (apply, delete, patch, scale)
- Metrics tracked: hits, misses, evictions, size
- Do not bypass caching for read-heavy operations
- Use descriptive variable and function names throughout. Names should clearly convey purpose and intent.
- Avoid abbreviations and single-letter names (except loop counters). Use
resourceNamenotrn,kubeClientnotkc,toolResultnottr. - Function names should describe what they do:
handleKubectlGetEnhancednotdoGet,validatePromQLQuerynotcheckQ. - Handler functions: prefix with
handle. - Builder methods: prefix with
With. - Validation functions: prefix with
Validate.
- Before writing new code, search for existing utilities in
internal/that already solve the problem. - Do not duplicate logic across tool providers. Shared functionality belongs in
internal/packages:- Command execution β
internal/commands/ - Error formatting β
internal/errors/ - Input validation β
internal/security/ - Caching β
internal/cache/ - Logging β
internal/logger/
- Command execution β
- If you find duplicated code while working on a change, consolidate it as part of your PR when the scope is reasonable.
- Ginkgo v2 + Gomega for behavioral tests
- testify for assertions and mocking
- Table-driven tests for comprehensive coverage
- Minimum 80% test coverage is the repository standard. CI runs
go test -v -coverand reports coverage but has no threshold gate, so the standard is on you to check (go test -cover ./pkg/...). Everypkg/package currently exceeds it;internal/commandsandinternal/cmdare below it and predate the standard.
Use the mock shell executor for unit tests instead of calling real CLI tools:
mockExecutor := cmd.NewMockShellExecutor()
mockExecutor.AddResponse("kubectl get pods -n default", "NAME READY STATUS\npod1 1/1 Running", nil)
ctx := cmd.WithShellExecutor(context.Background(), mockExecutor)- Unit tests: co-located
*_test.gofiles in each package - E2E tests:
test/e2e/(requires Kind cluster) - All public functions require unit tests
- Decode structured tool results into the same output DTOs used by production code. Avoid
map[string]interface{}/[]interface{}assertions in tests.
The main workflow (.github/workflows/ci.yaml) runs on pushes/PRs to main:
- Multi-arch Docker build β amd64 + arm64
- Go unit tests β
go test -v -cover - E2E tests β Kind cluster-based integration tests
- Helm chart tests β Chart unit tests
Additional workflow: tag.yaml for release tagging.
The server includes built-in observability:
- OpenTelemetry tracing β gRPC, HTTP exporters, stdout
- Prometheus metrics:
kagent_tools_mcp_invocations_totalβ invocation count by tool/providerkagent_tools_mcp_invocations_failure_totalβ failure countkagent_tools_mcp_registered_toolsβ tool inventorykagent_tools_mcp_server_infoβ server metadata
- Structured logging with context via
internal/logger/
Use Conventional Commits:
<type>: <description>
[optional body]
Signed-off-by: Name <email>
Types: feat, fix, docs, refactor, test, chore, perf, ci
- Do not call CLI tools directly β use the
CommandBuilderfrominternal/commands/. - Do not skip input validation β always use
internal/security/validators. - Do not return Go errors from MCP handlers β use
ToolError.ToMCPResult()instead. - Do not duplicate logic across providers β extract to
internal/packages. - Do not bypass the cache for read operations.
- Do not use untyped maps or
anyfor MCP tool input/output schemas or public response bodies. - Do not register a handler with
Out=anyβ the SDK cannot infer or validate an output schema, and the typed-output contract is what keeps the tool callable. - Do not add a map-typed field to an output DTO without
omitempty, and do not usejson.RawMessageas a dynamic-JSON output field β both make the SDK reject valid results at call time (see the output-schema pitfalls above). - Do not add new tool providers without a corresponding
RegisterToolsfunction. - Do not commit without running
make fmt && make lint && make test.
- Create a new package under
pkg/<provider>/. - Implement tool handlers (prefix with
handle). - Implement
RegisterTools(server, readOnly)β respect thereadOnlyflag for write operations. - Register the provider in
cmd/main.goinsideregisterMCP(). - Add input validation using
internal/security/. - Use
CommandBuilderfor CLI execution. - Define concrete typed input and output DTOs; avoid
any,interface{}, and untyped maps. Returnmcp.TextResult(...)/mcp.TextError(...)for raw CLI text, and a concrete DTO for a structured response. Watch the output-schema pitfalls above (omitemptyon map fields, nojson.RawMessagefields, noOuttypes that fail schema inference). - Return errors via
ToolError.ToMCPResult(); remember theOutvalue must still validate on the error path, so return the zero value of the DTO (ormcp.TextOutput{}). - Write unit tests with mock shell executor (80% coverage minimum).
- Add E2E tests if the tool interacts with a cluster.
- Run
make fmt && make lint && make testbefore submitting.
The image doubles as a kagent Agent Plugin (see docs/kagent-skills.md).
New tool providers should add or extend a skill in plugin/skills/<name>/SKILL.md and keep
cmd/plugin_bundle_test.go passing; it checks manifests, mcp.json, skill frontmatter, that
every tool a skill names is registered, and that every `argument: value` on a skill line is an
input of a tool named on that line. cmd/plugin_docs_test.go checks the AgentTemplate examples
in docs/kagent-skills.md against kagent's rules (modelConfig, at most 50 bound tools, and a
tools: list that covers the selected skills). Skills must only name tools that work with the
chart defaults (no tool that needs an unconfigured LLM key). Run make plugin-verify after
changing the Dockerfile or plugin/.
- DEVELOPMENT.md β Development setup and code standards
- CONTRIBUTION.md β Contribution process and PR guidelines
- docs/quickstart.md β Quick start guide