Repo: forest6511/secretctl
Version observed: v0.8.8 (built from source, Linux amd64)
MCP SDK: github.com/modelcontextprotocol/go-sdk v1.1.0
Summary
The security_score MCP tool fails schema validation when the vault's security score has no suggestions to return. The suggestions field serializes as JSON null (Go nil slice) instead of an empty [] array, violating the tool's output schema which declares it as array.
Reproduction
# 1. Init vault with strong master password + import healthy .env
secretctl init # strong unique password
secretctl import healthy.env # ~10 unique well-formed secrets
# 2. Configure MCP policy + register secretctl in any MCP client
# Place mcp-policy.yaml at ~/.secretctl/mcp-policy.yaml (regular file, 0600, current user)
# Add to client's mcp config:
# { "secretctl": { "command": "bash", "args": ["wrapper.sh"] } }
# 3. From MCP client, invoke security_score
# Expected: { "overall_score": 90+, "suggestions": [] }
# Actual: MCP error: validating root: validating /properties/suggestions:
# type: <invalid reflect.Value> has type "null", want "array"
Tested under Claude Code v2.x as MCP client. Suspect any MCP client with strict schema validation (which the official Go SDK now enforces) will fail.
Root cause
internal/mcp/tools.go line 1374:
output := SecurityScoreOutput{
...
Suggestions: score.Suggestions, // ← if nil slice, marshals to JSON null
Limited: score.Limited,
}
SecurityScoreOutput.Suggestions is declared as []string (line 165). When the security calculator returns no suggestions (e.g., healthy vault), score.Suggestions is a nil slice. Go encoder marshals nil slice as JSON null, which violates the implied array schema that modelcontextprotocol/go-sdk validates against on tool output.
The other slice field TopIssues []SecurityIssueInfo is OK because the handler explicitly builds it via append(topIssues, info), producing a non-nil empty slice when there are no issues.
Suggested fix
Force empty slice when nil (1-line guard at line 1374):
suggestions := score.Suggestions
if suggestions == nil {
suggestions = []string{}
}
output := SecurityScoreOutput{
...
Suggestions: suggestions,
Limited: score.Limited,
}
Alternative: do the same normalization at score.Suggestions construction site in the security calculator (closer to source of nil). Either works.
Why this matters
security_score becomes unusable from MCP clients with output validation
- Healthy vault is the most common steady state — exactly when no suggestions → bug always fires
- Same pattern likely affects future tools that return slices with all-empty edge cases (
secret_list_fields etc.) — worth a cross-cutting scan
Minimal test case
func TestSecurityScore_EmptySuggestions(t *testing.T) {
// Setup: vault with strong password, 0 issues
...
var out SecurityScoreOutput
_, _, err := s.handleSecurityScore(ctx, req, input)
require.NoError(t, err)
require.NotNil(t, out.Suggestions, "suggestions must be non-nil empty slice, not nil")
require.IsType(t, []string{}, out.Suggestions)
// JSON round-trip
data, _ := json.Marshal(out)
require.Contains(t, string(data), `"suggestions":[]`, "must serialize as empty array")
require.NotContains(t, string(data), `"suggestions":null`)
}
Environment
- secretctl: v0.8.8 (built from source via
make build with GOPROXY=https://goproxy.cn,direct GOFLAGS=-buildvcs=false)
- Go: 1.24.3 (docker
golang:1.24 image)
- OS: Linux x86_64 (Debian 12, kernel 6.12.x)
- MCP client: Claude Code v2.x
Happy to send a PR if the suggested fix direction is OK.
Exact error string verified 2026-05-12 via mcp__secretctl__security_score returning:
MCP error 0: validating tool output: validating root: validating /properties/suggestions: type: <invalid reflect.Value> has type "null", want "array"
Repo: forest6511/secretctl
Version observed: v0.8.8 (built from source, Linux amd64)
MCP SDK: github.com/modelcontextprotocol/go-sdk v1.1.0
Summary
The
security_scoreMCP tool fails schema validation when the vault's security score has no suggestions to return. Thesuggestionsfield serializes as JSONnull(Go nil slice) instead of an empty[]array, violating the tool's output schema which declares it asarray.Reproduction
Tested under Claude Code v2.x as MCP client. Suspect any MCP client with strict schema validation (which the official Go SDK now enforces) will fail.
Root cause
internal/mcp/tools.goline 1374:SecurityScoreOutput.Suggestionsis declared as[]string(line 165). When the security calculator returns no suggestions (e.g., healthy vault),score.Suggestionsis a nil slice. Go encoder marshals nil slice as JSONnull, which violates the implied array schema thatmodelcontextprotocol/go-sdkvalidates against on tool output.The other slice field
TopIssues []SecurityIssueInfois OK because the handler explicitly builds it viaappend(topIssues, info), producing a non-nil empty slice when there are no issues.Suggested fix
Force empty slice when nil (1-line guard at line 1374):
Alternative: do the same normalization at
score.Suggestionsconstruction site in the security calculator (closer to source of nil). Either works.Why this matters
security_scorebecomes unusable from MCP clients with output validationsecret_list_fieldsetc.) — worth a cross-cutting scanMinimal test case
Environment
make buildwithGOPROXY=https://goproxy.cn,directGOFLAGS=-buildvcs=false)golang:1.24image)Happy to send a PR if the suggested fix direction is OK.
Exact error string verified 2026-05-12 via
mcp__secretctl__security_scorereturning:MCP error 0: validating tool output: validating root: validating /properties/suggestions: type: <invalid reflect.Value> has type "null", want "array"