Editorial

Research guide · MCP · Go SDK · middleware · updated 15 September 2026

MCP Go SDK nil pointer on tools/list: fix missing params middleware panic

If a Go MCP server crashes inside receiving middleware on an otherwise ordinary tools/list, prompts/list, resources/list or notifications/initialized message, check how the middleware tests req.GetParams(). A valid request can omit the JSON-RPC params member, while affected released SDK versions can still make GetParams() appear non-nil at the interface boundary and then panic when middleware calls GetMeta().

Published 14 September 2026 · upstream status checked 15 September 2026

tools/list

Common reported trigger

GetParams() != nil

Unsafe presence test in affected releases

typed nil

Go interface boundary causing the trap

PR #1269 merged

HasParams is on main; release inclusion must be checked

Status update

The upstream fix is merged, but main is not the same as a released dependency

MCP Go SDK pull request #1269 merged on 14 September 2026 at 16:45 UTC. It adds mcp.HasParams(req Request) bool, updates the SDK's own per-request metadata path to use the safer check, and documents that a received message need not carry params.

The latest release at the time of that merge was v1.8.0, published earlier on 14 September at 08:05 UTC. Because the release predates the merge, v1.8.0 does not contain PR #1269. Treat HasParams as available on main, not as available in every released build.

The failure boundary

A non-nil interface can still contain a nil params pointer

JSON-RPC allows params to be omitted, and the MCP Go SDK accepts missing params for several methods. The upstream issue opened 13 September 2026 demonstrates a complete tools/list request with no params member.

In the reported path, the SDK leaves the typed params pointer at its zero value. Request.GetParams() boxes that nil pointer into an interface. In Go, an interface that contains a typed nil pointer is itself non-nil, so req.GetParams() != nil reports true. Calling the promoted GetMeta() method then dereferences the nil pointer and can crash the process.

That makes this a middleware/API-shape problem, not evidence that the client sent malformed JSON-RPC. Adding params: may make one reproduction disappear, but forcing every client to do that would paper over the server-side boundary rather than repair it.

Error map

Match the stack before changing protocol behavior

Observed symptomWhat to inspect next
panic: runtime error: invalid memory address or nil pointer dereferenceCheck whether generic receiving middleware dereferences req.GetParams() on a params-optional method.
(*ListToolsParams).GetMetaStrong match for the reported typed-nil path on tools/list.
Panic only when logging, auth or tracing middleware is enabledInspect middleware that reads _meta, protocol version, client information or capabilities before the real handler runs.
Request succeeds after adding params: {}That is a useful reproduction clue, not a protocol requirement; valid callers may still omit params.

Diagnosis

Seven checks before blaming the MCP client

  1. 1. Capture the top useful stack frame. Look for GetMeta on a params type such as *ListToolsParams, not just the generic nil-pointer headline.
  2. 2. Disable only the suspect middleware in a controlled test. If the request succeeds without logging/auth/tracing middleware, inspect that layer before changing tools or transport code.
  3. 3. Record the exact method. The report specifically reproduces params-optional list methods and notifications/initialized. Do not generalize a fix to every request shape without evidence.
  4. 4. Inspect the raw JSON-RPC shape safely. Confirm whether params is actually absent rather than empty. Never log credentials, authorization headers or private tool arguments to prove the point.
  5. 5. Find every generic GetParams() presence check. A check against plain nil is the dangerous pattern described upstream when the interface contains a typed nil pointer.
  6. 6. Check the deployed Go SDK version, not only upstream main. PR #1269 is merged, but v1.8.0 predates that merge. Use mcp.HasParams only when the version you actually build contains it.
  7. 7. Re-test both missing and present params. Your mitigation must preserve requests with legitimate params while allowing params-optional messages to pass without a middleware panic.

Version-scoped mitigation

Use a typed-nil-safe predicate until your released SDK owns the check

If your pinned SDK does not yet expose mcp.HasParams, keep compatibility logic local and easy to delete. The merged upstream implementation uses a typed-nil-safe presence check so middleware does not need to call a method on the potentially nil params value first.

func hasParams(req mcp.Request) bool {

  params := req.GetParams()

  if params == nil { return false }

  v := reflect.ValueOf(params)

  return v.Kind() != reflect.Pointer || !v.IsNil()

}

 

if hasParams(req) {

  meta := req.GetParams().GetMeta()

  _ = meta

}

Pin the dependency, add a regression test with a params-less tools/list, and remove the local helper after you deploy a released SDK version that contains PR #1269 and exposes mcp.HasParams.

What not to do

Do not turn a middleware panic into a protocol incompatibility

Reject every request without params

The reported methods are allowed to omit params. Rejecting them converts a server-side middleware defect into avoidable client incompatibility.

Patch clients to always send params: {}

Useful as a reproduction control, not a durable fix. You do not control every legitimate MCP client that may omit the optional member.

Assume merged means released

PR #1269 is on upstream main, but the latest v1.8.0 release predates the merge. Check the exact dependency version before calling HasParams in production.

Sources

Primary evidence

Continue the implementation path