Skip to content

Commit 96a1d85

Browse files
authored
Merge pull request #16 from nacorid/main
Execute mcp handler before payment settlement
2 parents a200cce + 8b7b638 commit 96a1d85

File tree

1 file changed

+79
-45
lines changed

1 file changed

+79
-45
lines changed

mcp/server/handler.go

Lines changed: 79 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"net/http"
1010

1111
"github.com/mark3labs/x402-go"
12+
"github.com/mark3labs/x402-go/facilitator"
1213
)
1314

1415
// X402Handler wraps an MCP HTTP handler and adds x402 payment verification
@@ -139,45 +140,7 @@ func (h *X402Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
139140
return
140141
}
141142

142-
// Payment verified - settle if not verify-only mode
143-
var settleResp *x402.SettlementResponse
144-
if !h.config.VerifyOnly {
145-
settleCtx, settleCancel := context.WithTimeout(r.Context(), x402.DefaultTimeouts.SettleTimeout)
146-
defer settleCancel()
147-
148-
settleResp, err = h.facilitator.Settle(settleCtx, payment, *requirement)
149-
if err != nil {
150-
if h.config.Verbose {
151-
fmt.Printf("Payment settlement failed: %v\n", err)
152-
}
153-
// Return error with settlement response in error data
154-
errorData := map[string]interface{}{
155-
"x402/payment-response": map[string]interface{}{
156-
"success": false,
157-
"network": payment.Network,
158-
"payer": verifyResp.Payer,
159-
"errorReason": err.Error(),
160-
},
161-
}
162-
h.writeError(w, jsonrpcReq.ID, -32603, fmt.Sprintf("Settlement failed: %v", err), errorData)
163-
return
164-
}
165-
166-
if !settleResp.Success {
167-
if h.config.Verbose {
168-
fmt.Printf("Payment settlement unsuccessful: %s\n", settleResp.ErrorReason)
169-
}
170-
// Return error with settlement response in error data
171-
errorData := map[string]interface{}{
172-
"x402/payment-response": settleResp,
173-
}
174-
h.writeError(w, jsonrpcReq.ID, -32603, fmt.Sprintf("Settlement unsuccessful: %s", settleResp.ErrorReason), errorData)
175-
return
176-
}
177-
}
178-
179-
// Payment successful - forward request and inject settlement response in result
180-
h.forwardWithSettlementResponse(w, r, bodyBytes, jsonrpcReq.ID, settleResp)
143+
h.forwardAndSettle(w, r, bodyBytes, jsonrpcReq.ID, payment, requirement, verifyResp)
181144
}
182145

183146
// checkPaymentRequired checks if a tool requires payment
@@ -244,8 +207,8 @@ func (h *X402Handler) sendPaymentRequiredError(w http.ResponseWriter, id interfa
244207
h.writeError(w, id, 402, "Payment required", errorData)
245208
}
246209

247-
// forwardWithSettlementResponse forwards the request and injects settlement response in result._meta
248-
func (h *X402Handler) forwardWithSettlementResponse(w http.ResponseWriter, r *http.Request, requestBody []byte, requestID interface{}, settleResp *x402.SettlementResponse) {
210+
// forwardAndSettle executes the mcpHandler and on success, settles the payment and injects settlement response in result._meta
211+
func (h *X402Handler) forwardAndSettle(w http.ResponseWriter, r *http.Request, requestBody []byte, requestID interface{}, payment *x402.PaymentPayload, requirement *x402.PaymentRequirement, verifyResp *facilitator.VerifyResponse) {
249212
// Create a response recorder to capture the MCP handler's response
250213
recorder := &responseRecorder{
251214
headerMap: make(http.Header),
@@ -267,24 +230,95 @@ func (h *X402Handler) forwardWithSettlementResponse(w http.ResponseWriter, r *ht
267230
}
268231

269232
if err := json.Unmarshal(recorder.body.Bytes(), &jsonrpcResp); err != nil {
233+
if h.config.Verbose {
234+
fmt.Printf("Failed to parse MCP response, skipping settlement: %v\n", err)
235+
}
270236
// If we can't parse response, just forward it as-is
237+
for k, v := range recorder.headerMap {
238+
w.Header()[k] = v
239+
}
240+
w.WriteHeader(recorder.statusCode)
241+
_, _ = w.Write(recorder.body.Bytes())
242+
return
243+
}
244+
245+
if jsonrpcResp.Error != nil {
246+
if h.config.Verbose {
247+
fmt.Println("Execution failed. Payment will not be settled.")
248+
}
249+
for k, v := range recorder.headerMap {
250+
w.Header()[k] = v
251+
}
271252
w.WriteHeader(recorder.statusCode)
272253
_, _ = w.Write(recorder.body.Bytes())
273254
return
274255
}
275256

276-
// Only inject settlement response if there's a result (not an error)
277-
if jsonrpcResp.Error == nil && jsonrpcResp.Result != nil && settleResp != nil {
257+
var settleResp *x402.SettlementResponse
258+
// Settle if not verify-only mode
259+
if !h.config.VerifyOnly {
260+
if h.config.Verbose {
261+
fmt.Println("Execution successful. Settling payment.")
262+
}
263+
settleCtx, settleCancel := context.WithTimeout(r.Context(), x402.DefaultTimeouts.SettleTimeout)
264+
defer settleCancel()
265+
266+
var err error
267+
settleResp, err = h.facilitator.Settle(settleCtx, payment, *requirement)
268+
if err != nil || settleResp == nil || !settleResp.Success {
269+
reason := "unknown reason"
270+
if err != nil {
271+
reason = err.Error()
272+
} else if settleResp != nil {
273+
reason = settleResp.ErrorReason
274+
}
275+
276+
errorMsg := fmt.Sprintf("Settlement failed: %v", reason)
277+
if h.config.Verbose {
278+
fmt.Println(errorMsg)
279+
}
280+
payer := ""
281+
if verifyResp != nil {
282+
payer = verifyResp.Payer
283+
}
284+
errorData := map[string]interface{}{
285+
"x402/payment-response": x402.SettlementResponse{
286+
Success: false,
287+
Network: payment.Network,
288+
Payer: payer,
289+
ErrorReason: reason,
290+
},
291+
}
292+
h.writeError(w, requestID, -32603, errorMsg, errorData)
293+
return
294+
} else if h.config.Verbose {
295+
fmt.Printf("Payment successful: %s\n", settleResp.Transaction)
296+
}
297+
}
298+
299+
if jsonrpcResp.Result != nil {
278300
var result map[string]interface{}
279301
if err := json.Unmarshal(jsonrpcResp.Result, &result); err == nil {
280-
// Get or create _meta
281302
meta, ok := result["_meta"].(map[string]interface{})
282303
if !ok {
283304
meta = make(map[string]interface{})
284305
}
285306

286307
// Add settlement response
287-
meta["x402/payment-response"] = settleResp
308+
if settleResp != nil {
309+
meta["x402/payment-response"] = settleResp
310+
} else {
311+
payer := ""
312+
if verifyResp != nil {
313+
payer = verifyResp.Payer
314+
}
315+
// In verify-only mode: Success=false indicates settlement was skipped (not attempted), not that it failed.
316+
meta["x402/payment-response"] = x402.SettlementResponse{
317+
Success: false,
318+
Network: payment.Network,
319+
Payer: payer,
320+
}
321+
}
288322
result["_meta"] = meta
289323

290324
// Re-marshal result

0 commit comments

Comments
 (0)