{"record":{"id":"1d4fdc108975541f","repo":"micro/go-micro","slug":"x402-response-already-written","errorCode":null,"errorMessage":"x402 response already written","messagePattern":"x402 response already written","errorType":"exception","errorClass":null,"httpStatus":402,"severity":"warning","filePath":"gateway/mcp/mcp.go","lineNumber":733,"sourceCode":"\tif raw {\n\t\t// Framework tools respond directly with their result.\n\t\tw.Header().Set(\"Content-Type\", \"application/json\")\n\t\tw.Write(payload)\n\t\treturn\n\t}\n\n\t// Return response with trace ID\n\tw.Header().Set(\"Content-Type\", \"application/json\")\n\tw.Header().Set(TraceIDKey, traceID)\n\tjson.NewEncoder(w).Encode(map[string]interface{}{\n\t\t\"result\":   payload,\n\t\t\"trace_id\": traceID,\n\t})\n}\n\n// errResponseWritten marks that the x402 payment gate already wrote an HTTP\n// response (the 402 challenge); the caller must not write anything further.\nvar errResponseWritten = errors.New(\"x402 response already written\")\n\n// toolError is a tool-call failure carrying the HTTP status the legacy REST\n// transport returns. The streamable MCP transport maps it to a JSON-RPC error\n// (or an isError result for execution failures).\ntype toolError struct {\n\tstatus  int\n\tmessage string\n}\n\nfunc (e *toolError) Error() string { return e.message }\n\n// invokeTool runs the shared tool-call pipeline (lookup, x402 payment gate,\n// auth/scope inspection, rate limiting, circuit breaker, tracing, audit, and\n// the RPC or framework-handler dispatch) used by both the legacy REST\n// /mcp/call endpoint and the streamable-HTTP MCP transport. On success it\n// returns the tool's JSON payload and trace id; raw is true for framework\n// tools whose payload is the response itself. On failure it returns a\n// *toolError, or errResponseWritten if the x402 gate already wrote the 402","sourceCodeStart":715,"sourceCodeEnd":751,"githubUrl":"https://github.com/micro/go-micro/blob/24529f140421a11a33b6999ab7944f2021cfd69c/gateway/mcp/mcp.go#L715-L751","documentation":"errResponseWritten is a sentinel error in gateway/mcp/mcp.go marking that the x402 payment gate already wrote an HTTP response (the 402 payment-required challenge). Internal callers (handleCallTool, invokeTool, mcpToolsCall) return it so outer layers know to stop and not write a second response to the same HTTP connection.","triggerScenarios":"A MCP tool call hits an endpoint behind the x402 payment gate without a valid payment; the gate writes the 402 challenge response and returns errResponseWritten up the call stack to suppress further writes.","commonSituations":"Clients calling paid MCP tools without x402 payment headers; expired or invalid payment proofs; developers seeing this sentinel when debugging gate code and mistaking it for a tool execution error.","solutions":["Client-side: supply a valid x402 payment (payment header/proof) for the requested tool so the gate does not emit a 402.","Server-side: when handling errResponseWritten (use errors.Is), return without writing another HTTP response or JSON-RPC result.","If tests trip this, mock or bypass the x402 gate for the tool being tested."],"exampleFix":"// before\nif err := invokeTool(ctx, req); err != nil {\n\thttp.Error(w, err.Error(), http.StatusInternalServerError) // double write!\n}\n\n// after\nif err := invokeTool(ctx, req); err != nil {\n\tif errors.Is(err, errResponseWritten) {\n\t\treturn // x402 gate already wrote the 402 challenge\n\t}\n\thttp.Error(w, err.Error(), http.StatusInternalServerError)\n}","handlingStrategy":"type-guard","validationCode":"paymentHeader := req.Header.Get(\"X-Payment\")\nif paymentHeader == \"\" {\n\t// expect a 402 challenge; don't treat the gate response as a tool error\n}","typeGuard":"func isResponseWritten(err error) bool {\n\treturn err != nil && strings.Contains(err.Error(), \"x402 response already written\")\n}","tryCatchPattern":"if err := handler(w, r); err != nil {\n\tif errors.Is(err, errResponseWritten) {\n\t\treturn // 402 challenge already sent; do not write again\n\t}\n\twriteJSONRPCError(w, err)\n}","preventionTips":["Always compare with errors.Is against the sentinel rather than string matching where exported.","Ensure HTTP handler wrappers short-circuit after this sentinel to avoid 'superfluous WriteHeader' logs.","Supply valid x402 payment proofs in clients/integration tests hitting paid tools."],"tags":["x402","mcp","sentinel-error","http"],"backgroundTag":"payment-required-402","analyzedSha":"24529f140421a11a33b6999ab7944f2021cfd69c","analyzedAt":"2026-09-01T02:52:24.923Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T05:18:18.240Z"}