Problem
The everything server example uses return nil, fmt.Errorf(...) for input validation failures:
// examples/everything/main.go:357-359
message, ok := arguments["message"].(string)
if !ok {
return nil, fmt.Errorf("invalid message argument")
}
In the mcp-go SDK, returning (nil, error) from a tool handler produces -32603 (InternalError) at server.go:1724-1729:
result, err := finalHandler(ctx, request)
if err != nil {
return nil, &requestError{
id: id,
code: mcp.INTERNAL_ERROR,
err: err,
}
}
The correct pattern for input validation is:
message, ok := arguments["message"].(string)
if !ok {
return mcp.NewToolResultError("invalid message argument: expected string"), nil
}
This returns isError: true in the tool result, which agents can act on. -32603 is an unrecoverable protocol error that agents cannot distinguish from a server crash.
Impact
Every developer learning from the everything server example copies this pattern. The echo and add tool handlers both use return nil, fmt.Errorf(...) for type assertion failures. This results in:
echo with a non-string message: -32603 instead of isError: true
add with a non-number argument: -32603 instead of isError: true
echo with null/empty/missing arguments: -32603 instead of isError: true
Tested via mcp-assert fuzz against the everything server: sending wrong types, null, and empty args all produce -32603.
Suggestion
Update the example handlers to use mcp.NewToolResultError() for input validation, reserving return nil, error for actual internal errors (e.g., database connection failed, file not found). This would also serve as documentation for the correct pattern.
The WithInputSchemaValidation() option (v0.50.0) catches type mismatches at the SDK level, but the example code should still demonstrate the correct handler-level pattern for cases where schema validation alone isn't sufficient.
Problem
The everything server example uses
return nil, fmt.Errorf(...)for input validation failures:In the mcp-go SDK, returning
(nil, error)from a tool handler produces-32603(InternalError) atserver.go:1724-1729:The correct pattern for input validation is:
This returns
isError: truein the tool result, which agents can act on.-32603is an unrecoverable protocol error that agents cannot distinguish from a server crash.Impact
Every developer learning from the everything server example copies this pattern. The
echoandaddtool handlers both usereturn nil, fmt.Errorf(...)for type assertion failures. This results in:echowith a non-string message:-32603instead ofisError: trueaddwith a non-number argument:-32603instead ofisError: trueechowith null/empty/missing arguments:-32603instead ofisError: trueTested via
mcp-assert fuzzagainst the everything server: sending wrong types, null, and empty args all produce-32603.Suggestion
Update the example handlers to use
mcp.NewToolResultError()for input validation, reservingreturn nil, errorfor actual internal errors (e.g., database connection failed, file not found). This would also serve as documentation for the correct pattern.The
WithInputSchemaValidation()option (v0.50.0) catches type mismatches at the SDK level, but the example code should still demonstrate the correct handler-level pattern for cases where schema validation alone isn't sufficient.