diff --git a/pyproject.toml b/pyproject.toml
index 0a7432c5918c73dbed9a05e15a36e6cc30e18b4c..4e6738fe186ffd6239cd21d5ecd0baeaf998eb41 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -43,3 +43,7 @@ extend-exclude = '''
 [tool.isort]
 profile = "black"
 skip = ["src/_zkapauthorizer/_version.py"]
+
+[tool.mypy]
+namespace_packages = true
+plugins = "mypy_zope:plugin"
diff --git a/shell.nix b/shell.nix
index c7ed13786f4d7d394237bcf99009423c2b22c639..e80e642b0570057293c5a295c8834aef9baf0b37 100644
--- a/shell.nix
+++ b/shell.nix
@@ -17,6 +17,8 @@ let
     ];
     requirements =
       ''
+      mypy
+      mypy-zope
       ${builtins.readFile ./requirements/test.in}
       ${zkapauthorizer.requirements}
       '';
diff --git a/src/_zkapauthorizer/config.py b/src/_zkapauthorizer/config.py
index a3ab5882e7aef071d8117c28a4c609a068cf924c..17e9c878680c8acb122b7c50b613cc05d18c2ed1 100644
--- a/src/_zkapauthorizer/config.py
+++ b/src/_zkapauthorizer/config.py
@@ -17,7 +17,7 @@ Helpers for reading values from the Tahoe-LAFS configuration.
 """
 
 from datetime import timedelta
-from typing import Optional
+from typing import Any, Optional
 
 from allmydata.node import _Config
 
@@ -113,7 +113,7 @@ def get_configured_lease_duration(node_config):
     return int(upper_bound - min_time_remaining)
 
 
-def _read_duration(cfg, option, default):
+def _read_duration(cfg: _Config, option: str, default: Any) -> Optional[timedelta]:
     """
     Read an integer number of seconds from the ZKAPAuthorizer section of a
     Tahoe-LAFS config.
@@ -124,7 +124,6 @@ def _read_duration(cfg, option, default):
     :return: ``None`` if the option is missing, otherwise the parsed duration
         as a ``timedelta``.
     """
-    # type: (_Config, str) -> Optional[timedelta]
     section_name = "storageclient.plugins." + NAME
     value_str = cfg.get_config(
         section=section_name,
diff --git a/src/_zkapauthorizer/lease_maintenance.py b/src/_zkapauthorizer/lease_maintenance.py
index 10846fe542f1736d9144f79f8a9799024d60049c..a7cc77c8313519a98cf83f85dcd5ab47df30be41 100644
--- a/src/_zkapauthorizer/lease_maintenance.py
+++ b/src/_zkapauthorizer/lease_maintenance.py
@@ -20,7 +20,7 @@ refresh leases on all shares reachable from a root.
 from datetime import datetime, timedelta
 from errno import ENOENT
 from functools import partial
-from typing import Any, Dict, Iterable
+from typing import Any, Callable, Dict, Iterable
 
 import attr
 from allmydata.interfaces import IDirectoryNode, IFilesystemNode
@@ -321,7 +321,7 @@ class _FuzzyTimerService(Service):
     operation = attr.ib()
     initial_interval = attr.ib()
     sample_interval_distribution = attr.ib()
-    get_config = attr.ib()  # type: () -> Any
+    get_config: Callable[[], Any] = attr.ib()
     reactor = attr.ib()
 
     def startService(self):
diff --git a/src/_zkapauthorizer/tests/test_plugin.py b/src/_zkapauthorizer/tests/test_plugin.py
index 1c50a993b3d5d9a485268ba8395dcb055c10c888..98147f84ec1cb51204395c70ab7743369ae01ad5 100644
--- a/src/_zkapauthorizer/tests/test_plugin.py
+++ b/src/_zkapauthorizer/tests/test_plugin.py
@@ -745,24 +745,22 @@ class LeaseMaintenanceServiceTests(TestCase):
         )
 
 
-def has_lease_maintenance_service():
+def has_lease_maintenance_service() -> Matcher:
     """
     Return a matcher for a Tahoe-LAFS client object that has a lease
     maintenance service.
     """
-    # type: () -> Matcher
     return AfterPreprocessing(
         lambda client: client.getServiceNamed(SERVICE_NAME),
         Always(),
     )
 
 
-def has_lease_maintenance_configuration(lease_maint_config):
+def has_lease_maintenance_configuration(lease_maint_config: LeaseMaintenanceConfig) -> Matcher:
     """
     Return a matcher for a Tahoe-LAFS client object that has a lease
     maintenance service with the given configuration.
     """
-    # type: (LeaseMaintenanceConfig) -> Matcher
     def get_lease_maintenance_config(lease_maint_service):
         return lease_maint_service.get_config()