Repository navigation
mcp-s3-v1.4.0
Fixes a bug that made failed and empty S3 listings unusable for MCP clients, and brings the dependency set current.
Anyone running v1.3.0 against an endpoint that can refuse a listing should upgrade.
The fix — failed listings no longer reach clients as transport errors
Before this release, a failed s3_list_buckets, s3_list_objects, or s3_list_connections call reached the MCP client as a JSON-RPC error instead of the tool's own error result. The client saw a transport failure and never saw why the call failed — the permission error, the bad bucket, the refused connection were all discarded:
calling "tools/call": validating tool output: validating root:
validating /properties/buckets: type: <invalid reflect.Value> has type "null", want "array"
Who was affected
- Any deployment where a listing can fail — expired credentials, a 403 from a bucket policy, an unreachable endpoint. The more locked-down the deployment, the more often this fired.
- Any deployment running the read-only extension or a custom authorization interceptor.
s3_list_connectionsmakes no S3 call of its own and looks immune, but it broke identically whenever middleware denied a request. - Reported against go-sdk v1.7.0; the same defect is present under v1.6.1, which v1.3.0 shipped with.
Cause
The MCP Go SDK's typed-handler wrapper does not skip output handling for an error result. When a handler returns an error result with no typed output, the SDK substitutes the zero value of the result struct, marshals it, and validates that JSON against the tool's declared OutputSchema before the result leaves the handler. A Go nil slice marshals as null, the schemas declared a bare "type": "array", and null is not an array. The validation failure surfaced as a Go error, which the SDK encoded as a JSON-RPC error — discarding the tool's real error message on the way out.
What changed
Every output-schema property backed by a Go slice or map now admits null alongside its real type — buckets, objects, common_prefixes, connections, and both metadata maps. This fixes every error path at once, including middleware and interceptor denials, rather than patching individual handlers.
Empty listings were already safe (the handlers initialize their slices), and that behavior is now pinned by a test rather than left implicit.
⚠️ Breaking change for one Go consumer shape
tools.DefaultOutputSchema() now returns []string where it returned string for "type" values.
This affects you only if you import github.com/txn2/mcp-s3/pkg/tools and type-assert on a schema property's "type":
// Breaks in v1.4.0
typ := prop["type"].(string)
// Works in both
switch t := prop["type"].(type) {
case string:
// v1.3.0 and earlier
case []string:
// v1.4.0+
}MCP clients consuming the schema over the wire are not affected — JSON Schema has always permitted a type array, and widening a schema is backward compatible for validation. Clients that generate types from the schema will now see array | null, which accurately describes what the server has always been able to send on an error result.
This is a v1.4.0 rather than v2.0.0 because a Go major bump would require a /v2 module path and force every consumer to rewrite their imports — disproportionate to one type assertion on a schema internal. If you maintain a downstream override of these schemas, drop it and use the upstream defaults.
Upgrading
go get github.com/txn2/mcp-s3@v1.4.0No configuration changes. No behavior change for successful calls. If you carried a local workaround that supplied null-tolerant output schemas via WithOutputSchemas, you can remove it.
Dependencies
| Module | v1.3.0 | v1.4.0 |
|---|---|---|
modelcontextprotocol/go-sdk |
1.6.1 | 1.7.0 |
aws-sdk-go-v2 |
1.41.12 | 1.43.2 |
aws-sdk-go-v2/config |
1.32.23 | 1.32.33 |
aws-sdk-go-v2/credentials |
1.19.22 | 1.19.32 |
aws-sdk-go-v2/service/s3 |
1.103.2 | 1.106.2 |
aws-sdk-go-v2/feature/s3/transfermanager |
0.2.8 | 0.3.7 |
Plus the transitive sso, ssooidc, sts, and smithy-go updates.
Security
- Go toolchain 1.26.4 → 1.26.6, clearing five standard-library advisories that reach this code: GO-2026-6218, GO-2026-6090, GO-2026-6088, GO-2026-5972, GO-2026-5026.
govulncheckreports no vulnerabilities against this release. - Base image alpine 3.23 → 3.24.
- All artifacts are Cosign-signed with SBOMs attached.
Engineering
Repository-internal, no runtime effect: a tool-version parity gate that holds local scanners to the CI-pinned versions, a govulncheck allow-list gate so an advisory with no upstream fix cannot silently disable the whole scan, patch-coverage enforcement on changed lines, and tightened complexity limits (cyclomatic 10, cognitive 15). The regression tests added for this fix exercise tools through a real mcp.Server and an in-memory client rather than calling handlers directly — the class of test whose absence let this bug ship, since every handler test passed while three tools were broken for real clients.
Installation
Homebrew (macOS)
brew install txn2/tap/mcp-s3Claude Code CLI
claude mcp add s3 \
-e AWS_REGION=us-east-1 \
-e AWS_ACCESS_KEY_ID=your-key \
-e AWS_SECRET_ACCESS_KEY=your-secret \
-- mcp-s3Docker
docker pull ghcr.io/txn2/mcp-s3:v1.4.0Verification
All release artifacts are signed with Cosign. Verify with:
cosign verify-blob --bundle mcp-s3_1.4.0_linux_amd64.tar.gz.sigstore.json \
mcp-s3_1.4.0_linux_amd64.tar.gzChangelog
Bug Fixes
a99d4cafix(tools): admit null in output schemas so failed listings keep their result (#142) — closes #141
Dependencies
2be37cfdeps: roll up open Dependabot updates (aws-sdk + GitHub Actions) (#130)- deps: bump the aws-sdk group (#111, #114, #122)
- docker: bump alpine 3.23 → 3.24 (#110, #112)
CI
- ci: bump actions/checkout (#113, #131), actions/setup-go (#116), actions/setup-python (#115), docker/login-action (#118), docker/setup-qemu-action (#117), github/codeql-action (#119), golangci/golangci-lint-action (#121)
Docs
Full diff: v1.3.0...v1.4.0