jaraco: This idea is a very cool one. And the implementation is so short and concise, signs of an elegant implementation. I have a couple of comments to make about the specific implementation, but I don't see why this change couldn't be rolled into an upcoming release in short order.
diff --git a/CHANGES.txt b/CHANGES.txt
index 20877f63..b06461ad 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -4,6 +4,7 @@
 * Issue #1411: Fix issue where autoreload fails when
   the host interpreter for CherryPy was launched using
   ``python -m``.
+* #1441: Added tool to automatically convert request params.
 
 6.1.0
 -----
diff --git a/cherrypy/_cptools.py b/cherrypy/_cptools.py
index 54c63734..060ca76f 100644
--- a/cherrypy/_cptools.py
+++ b/cherrypy/_cptools.py
@@ -533,5 +533,6 @@ _d.json_in = Tool('before_request_body', jsontools.json_in, priority=30)
 _d.json_out = Tool('before_handler', jsontools.json_out, priority=30)
 _d.auth_basic = Tool('before_handler', auth_basic.basic_auth, priority=1)
 _d.auth_digest = Tool('before_handler', auth_digest.digest_auth, priority=1)
+_d.params = Tool('before_handler', cptools.convert_params)
 
 del _d, cptools, encoding, auth, static
diff --git a/cherrypy/lib/cptools.py b/cherrypy/lib/cptools.py
index fbed0e23..073216e0 100644
--- a/cherrypy/lib/cptools.py
+++ b/cherrypy/lib/cptools.py
@@ -630,3 +630,21 @@ def autovary(ignore=None, debug=False):
         v.sort()
         resp_h['Vary'] = ', '.join(v)
     request.hooks.attach('before_finalize', set_response_header, 95)
+
+
+def convert_params(exception=ValueError, error=400):
+    """Convert request params based on function annotations, with error handling.
+
+    exception
+        Exception class to catch.
+
+    status
+        The HTTP error code to return to the client on failure.
+    """
+    request = cherrypy.serving.request
+    types = request.handler.callable.__annotations__
+    try:
+        for key in set(types).intersection(request.params):
+            request.params[key] = types[key](request.params[key])
+    except exception as exc:
+        raise cherrypy.HTTPError(error, str(exc))

Convert request params based on function annotations.
With the increasing focus on [type hints](https://docs.python.org/3.5/library/typing.html) in Python, having a tool which automatically converted query params (with exception handling) would be a nice feature.

It's common for apps to have boilerplate code for [converting request params](http://stackoverflow.com/questions/32774024/), and returning 400-levels errors on failure.
```
@cherrypy.tools.params(exception=ValueError)
def resource(self, limit: int):
    assert isinstance(limit, int) # a ValueError would have already raised a 400
```

diff --git a/cherrypy/test/test_params.py b/cherrypy/test/test_params.py
new file mode 100644
index 00000000..2d57c279
--- /dev/null
+++ b/cherrypy/test/test_params.py
@@ -0,0 +1,55 @@
+import sys
+import cherrypy
+from cherrypy.test import helper
+
+
+class ParamsTest(helper.CPWebCase):
+    @staticmethod
+    def setup_server():
+        class Root:
+            @cherrypy.expose
+            @cherrypy.tools.params()
+            def resource(self, limit=None, sort=None):
+                return type(limit).__name__
+            # for testing on Py 2
+            resource.__annotations__ = {'limit': int}
+        conf = {'/': {'tools.params.on': True}}
+        cherrypy.tree.mount(Root(), config=conf)
+
+    def test_pass(self):
+        self.getPage('/resource')
+        self.assertStatus(200)
+        self.assertBody('NoneType')
+
+        self.getPage('/resource?limit=0')
+        self.assertStatus(200)
+        self.assertBody('int')
+
+    def test_error(self):
+        self.getPage('/resource?limit=')
+        self.assertStatus(400)
+        self.assertInBody('invalid literal for int')
+
+        cherrypy.config['tools.params.error'] = 422
+        self.getPage('/resource?limit=')
+        self.assertStatus(422)
+        self.assertInBody('invalid literal for int')
+
+        cherrypy.config['tools.params.exception'] = TypeError
+        self.getPage('/resource?limit=')
+        self.assertStatus(500)
+
+    def test_syntax(self):
+        if sys.version_info < (3,):
+            return self.skip("skipped (Python 3 only)")
+        exec("""class Root:
+    @cherrypy.expose
+    @cherrypy.tools.params()
+    def resource(self, limit: int):
+        return type(limit).__name__
+conf = {'/': {'tools.params.on': True}}
+cherrypy.tree.mount(Root(), config=conf)""")
+
+        self.getPage('/resource?limit=0')
+        self.assertStatus(200)
+        self.assertBody('int')

pip install -e .
parse_log_pytest
requirements.txt
nose mock pytest
3.5
requirements/base.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
attrs==22.2.0
certifi==2021.5.30
-e git+https://github.com/cherrypy/cherrypy.git@adb7ff5aa1f48506e2838a22176c43c6f3aa4fb5#egg=CherryPy
importlib-metadata==4.8.3
iniconfig==1.1.1
mock==5.2.0
nose==1.3.7
packaging==21.3
pluggy==1.0.0
py==1.11.0
pyparsing==3.1.4
pytest==7.0.1
six==1.17.0
tomli==1.2.3
typing_extensions==4.1.1
zipp==3.6.0

name: cherrypy
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - certifi=2021.5.30=py36h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.3=he6710b0_2
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=1.1.1w=h7f8727e_0
  - pip=21.2.2=py36h06a4308_0
  - python=3.6.13=h12debd9_1
  - readline=8.2=h5eee18b_0
  - setuptools=58.0.4=py36h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - wheel=0.37.1=pyhd3eb1b0_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - attrs==22.2.0
      - importlib-metadata==4.8.3
      - iniconfig==1.1.1
      - mock==5.2.0
      - nose==1.3.7
      - packaging==21.3
      - pluggy==1.0.0
      - py==1.11.0
      - pyparsing==3.1.4
      - pytest==7.0.1
      - six==1.17.0
      - tomli==1.2.3
      - typing-extensions==4.1.1
      - zipp==3.6.0
prefix: /opt/conda/envs/cherrypy

cherrypy/test/test_params.py::ParamsTest::test_error cherrypy/test/test_params.py::ParamsTest::test_pass cherrypy/test/test_params.py::ParamsTest::test_syntax cherrypy/test/test_params.py::ParamsTest::test_gc
CHANGES.txt cherrypy/lib/cptools.py cherrypy/_cptools.py
CHANGES.txt cherrypy/lib/cptools.py cherrypy/_cptools.py