diff --git a/check_source/check_mypy.py b/check_source/check_mypy.py
new file mode 100755
index 0000000000000000000000000000000000000000..92a6bea0da1894b9ad6ea14973e4dbea0258b07d
--- /dev/null
+++ b/check_source/check_mypy.py
@@ -0,0 +1,112 @@
+#!/usr/bin/env python3
+
+import os
+from os.path import join, splitext
+
+from check_mypy_config import PATHS, BLACKLIST
+
+from typing import (
+    Any,
+    Callable,
+    Generator,
+    Optional,
+    Tuple,
+    Dict,
+)
+
+FileAndArgs = Tuple[str, Tuple[Any, ...], Dict[str, str]]
+
+# print(PATHS)
+SOURCE_EXT = (
+    # Python
+    ".py",
+)
+
+
+def is_source(filename: str) -> bool:
+    return filename.endswith(SOURCE_EXT)
+
+
+def path_iter(
+        path: str,
+        filename_check: Optional[Callable[[str], bool]] = None,
+) -> Generator[str, None, None]:
+    for dirpath, dirnames, filenames in os.walk(path):
+        # skip ".git"
+        dirnames[:] = [d for d in dirnames if not d.startswith(".")]
+
+        for filename in filenames:
+            if filename.startswith("."):
+                continue
+            filepath = join(dirpath, filename)
+            if filename_check is None or filename_check(filepath):
+                yield filepath
+
+
+def path_expand_with_args(
+        paths_and_args: Tuple[FileAndArgs, ...],
+        filename_check: Optional[Callable[[str], bool]] = None,
+) -> Generator[FileAndArgs, None, None]:
+    for f_and_args in paths_and_args:
+        f, f_args = f_and_args[0], f_and_args[1:]
+        if not os.path.exists(f):
+            print("Missing:", f)
+        elif os.path.isdir(f):
+            for f_iter in path_iter(f, filename_check):
+                yield (f_iter, *f_args)
+        else:
+            yield (f, *f_args)
+
+
+def main() -> None:
+    import sys
+    import subprocess
+    import shlex
+
+    # Fixed location, so change the current working directory doesn't create cache everywhere.
+    cache_dir = os.path.join(os.getcwd(), ".mypy_cache")
+
+    if os.path.samefile(sys.argv[-1], __file__):
+        paths = path_expand_with_args(PATHS, is_source)
+    else:
+        paths = path_expand_with_args(
+            tuple((p, (), {}) for p in sys.argv[1:]),
+            is_source,
+        )
+
+    for f, extra_args, extra_env in paths:
+        if f in BLACKLIST:
+            continue
+
+        if not extra_args:
+            extra_args = ()
+        if not extra_env:
+            extra_env = {}
+
+        # print(f)
+        print(repr(f[len('/src/blender/'):]) + ',')
+        cmd = (
+            "mypy",
+            "--strict",
+            "--cache-dir=" + cache_dir,
+            "--color-output",
+            f,
+            *extra_args,
+        )
+        # p = subprocess.Popen(cmd, env=extra_env, stdout=sys.stdout, stderr=sys.stderr)
+
+        if extra_env:
+            for k, v in extra_env.items():
+                os.environ[k] = v
+
+        os.chdir(os.path.dirname(f))
+
+        os.system(" ".join([shlex.quote(arg) for arg in cmd]))
+
+        if extra_env:
+            for k in extra_env.keys():
+                del os.environ[k]
+
+
+if __name__ == "__main__":
+    main()
diff --git a/check_source/check_mypy_config.py b/check_source/check_mypy_config.py
new file mode 100644
index 0000000000000000000000000000000000000000..85a6063a8ba65dda6c3a6e5c31f80d91797bac7f
--- /dev/null
+++ b/check_source/check_mypy_config.py
@@ -0,0 +1,45 @@
+
+import os
+from typing import (
+    Any,
+    Tuple,
+    Dict,
+)
+
+PATHS: Tuple[Tuple[str, Tuple[Any, ...], Dict[str, str]], ...] = (
+    ("build_files/cmake/", (), {'MYPYPATH': "modules"}),
+    ("doc/manpage/blender.1.py", (), {}),
+    ("source/tools/check_source/", (), {'MYPYPATH': "modules"}),
+    ("source/tools/utils_maintenance/", (), {'MYPYPATH': "modules"}),
+)
+
+SOURCE_DIR = os.path.normpath(os.path.abspath(os.path.normpath(
+    os.path.join(os.path.dirname(__file__), "..", "..", ".."))))
+
+BLACKLIST = set(
+    os.path.join(SOURCE_DIR, p.replace("/", os.sep))
+    for p in
+    (
+        "build_files/cmake/clang_array_check.py",
+        "build_files/cmake/cmake_netbeans_project.py",
+        "build_files/cmake/cmake_print_build_options.py",
+        "build_files/cmake/cmake_qtcreator_project.py",
+        "build_files/cmake/cmake_static_check_smatch.py",
+        "build_files/cmake/cmake_static_check_sparse.py",
+        "build_files/cmake/cmake_static_check_splint.py",
+        "source/tools/check_source/check_descriptions.py",
+        "source/tools/check_source/check_header_duplicate.py",
+        "source/tools/check_source/check_spelling.py",
+        "source/tools/check_source/check_unused_defines.py",
+        "source/tools/utils_maintenance/blender_menu_search_coverage.py",
+        "source/tools/utils_maintenance/blender_update_themes.py",
+        "source/tools/utils_maintenance/clang_format_paths.py",
+        "source/tools/utils_maintenance/trailing_space_clean.py",
+        "source/tools/utils_maintenance/trailing_space_clean_config.py",
+    )
+)
+
+PATHS = tuple(
+    (os.path.join(SOURCE_DIR, p_items[0].replace("/", os.sep)), *p_items[1:])
+    for p_items in PATHS
+)