diff --git a/contrib/middleware/replacer.go b/contrib/middleware/replacer.go
new file mode 100644
index 0000000000000000000000000000000000000000..e77065baacea5784be5214c236074b44e413f905
--- /dev/null
+++ b/contrib/middleware/replacer.go
@@ -0,0 +1,19 @@
+package middleware
+
+import (
+	"strings"
+
+	"gfx.cafe/open/jrpc/pkg/codec"
+)
+
+var LegacyUnderscoreReplacer = MethodReplacer(strings.NewReplacer("_", "/"))
+
+// MethodReplacer will use the replacer on every method before handling
+func MethodReplacer(replacer *strings.Replacer) codec.Middleware {
+	return func(next codec.Handler) codec.Handler {
+		return codec.HandlerFunc(func(w codec.ResponseWriter, r *codec.Request) {
+			r.Method = replacer.Replace(r.Method)
+			next.ServeRPC(w, r)
+		})
+	}
+}