We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
How to add a watched directory
The docs says:

> The serve event is only called when the serve command is used during development. It is passed the Server instance which can be modified before it is activated. For example, additional files or directories could be added to the list of "watched" files for auto-reloading.

How can I add files or directories?

I tried this:

```python
    def on_serve(self, server, config, **kwargs):
        for element in self.config["watch"]:
            server.watch(element)
        return server
```

With this in my `mkdocs.yml`:

```yaml
plugins:
  - search
  - mkdocstrings:
      watch:
        - src/mkdocstrings
```

It detects the changes, but since I gave no function to `server.watch(dir, func)`, the site is not rebuilt.

I checked the source code of `mkdocs`, and I see that you are using a local function that we cannot reuse ourselves without rewriting it:

https://github.com/mkdocs/mkdocs/blob/262c2b70f3b1a450d685530610af3f28e12f9c9f/mkdocs/commands/serve.py#L120-L137

and

https://github.com/mkdocs/mkdocs/blob/262c2b70f3b1a450d685530610af3f28e12f9c9f/mkdocs/commands/serve.py#L69

What would be the best way to add a directory with this same `builder` functionality? Should I simply copy paste it?

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `mkdocs/commands/serve.py`
Content:
```
1 import logging
2 import shutil
3 import tempfile
4 import sys
5 
6 from os.path import isfile, join
7 from mkdocs.commands.build import build
8 from mkdocs.config import load_config
9 
10 log = logging.getLogger(__name__)
11 
12 
13 def _init_asyncio_patch():
14     """
15     Select compatible event loop for Tornado 5+.
16 
17     As of Python 3.8, the default event loop on Windows is `proactor`,
18     however Tornado requires the old default "selector" event loop.
19     As Tornado has decided to leave this to users to set, MkDocs needs
20     to set it. See https://github.com/tornadoweb/tornado/issues/2608.
21     """
22     if sys.platform.startswith("win") and sys.version_info >= (3, 8):
23         import asyncio
24         try:
25             from asyncio import WindowsSelectorEventLoopPolicy
26         except ImportError:
27             pass  # Can't assign a policy which doesn't exist.
28         else:
29             if not isinstance(asyncio.get_event_loop_policy(), WindowsSelectorEventLoopPolicy):
30                 asyncio.set_event_loop_policy(WindowsSelectorEventLoopPolicy())
31 
32 
33 def _get_handler(site_dir, StaticFileHandler):
34 
35     from tornado.template import Loader
36 
37     class WebHandler(StaticFileHandler):
38 
39         def write_error(self, status_code, **kwargs):
40 
41             if status_code in (404, 500):
42                 error_page = '{}.html'.format(status_code)
43                 if isfile(join(site_dir, error_page)):
44                     self.write(Loader(site_dir).load(error_page).generate())
45                 else:
46                     super().write_error(status_code, **kwargs)
47 
48     return WebHandler
49 
50 
51 def _livereload(host, port, config, builder, site_dir):
52 
53     # We are importing here for anyone that has issues with livereload. Even if
54     # this fails, the --no-livereload alternative should still work.
55     _init_asyncio_patch()
56     from livereload import Server
57     import livereload.handlers
58 
59     class LiveReloadServer(Server):
60 
61         def get_web_handlers(self, script):
62             handlers = super().get_web_handlers(script)
63             # replace livereload handler
64             return [(handlers[0][0], _get_handler(site_dir, livereload.handlers.StaticFileHandler), handlers[0][2],)]
65 
66     server = LiveReloadServer()
67 
68     # Watch the documentation files, the config file and the theme files.
69     server.watch(config['docs_dir'], builder)
70     server.watch(config['config_file_path'], builder)
71 
72     for d in config['theme'].dirs:
73         server.watch(d, builder)
74 
75     # Run `serve` plugin events.
76     server = config['plugins'].run_event('serve', server, config=config)
77 
78     server.serve(root=site_dir, host=host, port=port, restart_delay=0)
79 
80 
81 def _static_server(host, port, site_dir):
82 
83     # Importing here to separate the code paths from the --livereload
84     # alternative.
85     _init_asyncio_patch()
86     from tornado import ioloop
87     from tornado import web
88 
89     application = web.Application([
90         (r"/(.*)", _get_handler(site_dir, web.StaticFileHandler), {
91             "path": site_dir,
92             "default_filename": "index.html"
93         }),
94     ])
95     application.listen(port=port, address=host)
96 
97     log.info('Running at: http://%s:%s/', host, port)
98     log.info('Hold ctrl+c to quit.')
99     try:
100         ioloop.IOLoop.instance().start()
101     except KeyboardInterrupt:
102         log.info('Stopping server...')
103 
104 
105 def serve(config_file=None, dev_addr=None, strict=None, theme=None,
106           theme_dir=None, livereload='livereload', **kwargs):
107     """
108     Start the MkDocs development server
109 
110     By default it will serve the documentation on http://localhost:8000/ and
111     it will rebuild the documentation and refresh the page automatically
112     whenever a file is edited.
113     """
114 
115     # Create a temporary build directory, and set some options to serve it
116     # PY2 returns a byte string by default. The Unicode prefix ensures a Unicode
117     # string is returned. And it makes MkDocs temp dirs easier to identify.
118     site_dir = tempfile.mkdtemp(prefix='mkdocs_')
119 
120     def builder():
121         log.info("Building documentation...")
122         config = load_config(
123             config_file=config_file,
124             dev_addr=dev_addr,
125             strict=strict,
126             theme=theme,
127             theme_dir=theme_dir,
128             site_dir=site_dir,
129             **kwargs
130         )
131         # Override a few config settings after validation
132         config['site_url'] = 'http://{}/'.format(config['dev_addr'])
133 
134         live_server = livereload in ['dirty', 'livereload']
135         dirty = livereload == 'dirty'
136         build(config, live_server=live_server, dirty=dirty)
137         return config
138 
139     try:
140         # Perform the initial build
141         config = builder()
142 
143         host, port = config['dev_addr']
144 
145         if livereload in ['livereload', 'dirty']:
146             _livereload(host, port, config, builder, site_dir)
147         else:
148             _static_server(host, port, site_dir)
149     finally:
150         shutil.rmtree(site_dir)
151
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/mkdocs/commands/serve.py b/mkdocs/commands/serve.py
--- a/mkdocs/commands/serve.py
+++ b/mkdocs/commands/serve.py
@@ -73,7 +73,7 @@
         server.watch(d, builder)
 
     # Run `serve` plugin events.
-    server = config['plugins'].run_event('serve', server, config=config)
+    server = config['plugins'].run_event('serve', server, config=config, builder=builder)
 
     server.serve(root=site_dir, host=host, port=port, restart_delay=0)

{"golden_diff": "diff --git a/mkdocs/commands/serve.py b/mkdocs/commands/serve.py\n--- a/mkdocs/commands/serve.py\n+++ b/mkdocs/commands/serve.py\n@@ -73,7 +73,7 @@\n         server.watch(d, builder)\n \n     # Run `serve` plugin events.\n-    server = config['plugins'].run_event('serve', server, config=config)\n+    server = config['plugins'].run_event('serve', server, config=config, builder=builder)\n \n     server.serve(root=site_dir, host=host, port=port, restart_delay=0)\n", "issue": "How to add a watched directory\nThe docs says:\r\n\r\n> The serve event is only called when the serve command is used during development. It is passed the Server instance which can be modified before it is activated. For example, additional files or directories could be added to the list of \"watched\" files for auto-reloading.\r\n\r\nHow can I add files or directories?\r\n\r\nI tried this:\r\n\r\n```python\r\n    def on_serve(self, server, config, **kwargs):\r\n        for element in self.config[\"watch\"]:\r\n            server.watch(element)\r\n        return server\r\n```\r\n\r\nWith this in my `mkdocs.yml`:\r\n\r\n```yaml\r\nplugins:\r\n  - search\r\n  - mkdocstrings:\r\n      watch:\r\n        - src/mkdocstrings\r\n```\r\n\r\nIt detects the changes, but since I gave no function to `server.watch(dir, func)`, the site is not rebuilt.\r\n\r\nI checked the source code of `mkdocs`, and I see that you are using a local function that we cannot reuse ourselves without rewriting it:\r\n\r\nhttps://github.com/mkdocs/mkdocs/blob/262c2b70f3b1a450d685530610af3f28e12f9c9f/mkdocs/commands/serve.py#L120-L137\r\n\r\nand\r\n\r\nhttps://github.com/mkdocs/mkdocs/blob/262c2b70f3b1a450d685530610af3f28e12f9c9f/mkdocs/commands/serve.py#L69\r\n\r\nWhat would be the best way to add a directory with this same `builder` functionality? Should I simply copy paste it?\n", "before_files": [{"content": "import logging\nimport shutil\nimport tempfile\nimport sys\n\nfrom os.path import isfile, join\nfrom mkdocs.commands.build import build\nfrom mkdocs.config import load_config\n\nlog = logging.getLogger(__name__)\n\n\ndef _init_asyncio_patch():\n    \"\"\"\n    Select compatible event loop for Tornado 5+.\n\n    As of Python 3.8, the default event loop on Windows is `proactor`,\n    however Tornado requires the old default \"selector\" event loop.\n    As Tornado has decided to leave this to users to set, MkDocs needs\n    to set it. See https://github.com/tornadoweb/tornado/issues/2608.\n    \"\"\"\n    if sys.platform.startswith(\"win\") and sys.version_info >= (3, 8):\n        import asyncio\n        try:\n            from asyncio import WindowsSelectorEventLoopPolicy\n        except ImportError:\n            pass  # Can't assign a policy which doesn't exist.\n        else:\n            if not isinstance(asyncio.get_event_loop_policy(), WindowsSelectorEventLoopPolicy):\n                asyncio.set_event_loop_policy(WindowsSelectorEventLoopPolicy())\n\n\ndef _get_handler(site_dir, StaticFileHandler):\n\n    from tornado.template import Loader\n\n    class WebHandler(StaticFileHandler):\n\n        def write_error(self, status_code, **kwargs):\n\n            if status_code in (404, 500):\n                error_page = '{}.html'.format(status_code)\n                if isfile(join(site_dir, error_page)):\n                    self.write(Loader(site_dir).load(error_page).generate())\n                else:\n                    super().write_error(status_code, **kwargs)\n\n    return WebHandler\n\n\ndef _livereload(host, port, config, builder, site_dir):\n\n    # We are importing here for anyone that has issues with livereload. Even if\n    # this fails, the --no-livereload alternative should still work.\n    _init_asyncio_patch()\n    from livereload import Server\n    import livereload.handlers\n\n    class LiveReloadServer(Server):\n\n        def get_web_handlers(self, script):\n            handlers = super().get_web_handlers(script)\n            # replace livereload handler\n            return [(handlers[0][0], _get_handler(site_dir, livereload.handlers.StaticFileHandler), handlers[0][2],)]\n\n    server = LiveReloadServer()\n\n    # Watch the documentation files, the config file and the theme files.\n    server.watch(config['docs_dir'], builder)\n    server.watch(config['config_file_path'], builder)\n\n    for d in config['theme'].dirs:\n        server.watch(d, builder)\n\n    # Run `serve` plugin events.\n    server = config['plugins'].run_event('serve', server, config=config)\n\n    server.serve(root=site_dir, host=host, port=port, restart_delay=0)\n\n\ndef _static_server(host, port, site_dir):\n\n    # Importing here to separate the code paths from the --livereload\n    # alternative.\n    _init_asyncio_patch()\n    from tornado import ioloop\n    from tornado import web\n\n    application = web.Application([\n        (r\"/(.*)\", _get_handler(site_dir, web.StaticFileHandler), {\n            \"path\": site_dir,\n            \"default_filename\": \"index.html\"\n        }),\n    ])\n    application.listen(port=port, address=host)\n\n    log.info('Running at: http://%s:%s/', host, port)\n    log.info('Hold ctrl+c to quit.')\n    try:\n        ioloop.IOLoop.instance().start()\n    except KeyboardInterrupt:\n        log.info('Stopping server...')\n\n\ndef serve(config_file=None, dev_addr=None, strict=None, theme=None,\n          theme_dir=None, livereload='livereload', **kwargs):\n    \"\"\"\n    Start the MkDocs development server\n\n    By default it will serve the documentation on http://localhost:8000/ and\n    it will rebuild the documentation and refresh the page automatically\n    whenever a file is edited.\n    \"\"\"\n\n    # Create a temporary build directory, and set some options to serve it\n    # PY2 returns a byte string by default. The Unicode prefix ensures a Unicode\n    # string is returned. And it makes MkDocs temp dirs easier to identify.\n    site_dir = tempfile.mkdtemp(prefix='mkdocs_')\n\n    def builder():\n        log.info(\"Building documentation...\")\n        config = load_config(\n            config_file=config_file,\n            dev_addr=dev_addr,\n            strict=strict,\n            theme=theme,\n            theme_dir=theme_dir,\n            site_dir=site_dir,\n            **kwargs\n        )\n        # Override a few config settings after validation\n        config['site_url'] = 'http://{}/'.format(config['dev_addr'])\n\n        live_server = livereload in ['dirty', 'livereload']\n        dirty = livereload == 'dirty'\n        build(config, live_server=live_server, dirty=dirty)\n        return config\n\n    try:\n        # Perform the initial build\n        config = builder()\n\n        host, port = config['dev_addr']\n\n        if livereload in ['livereload', 'dirty']:\n            _livereload(host, port, config, builder, site_dir)\n        else:\n            _static_server(host, port, site_dir)\n    finally:\n        shutil.rmtree(site_dir)\n", "path": "mkdocs/commands/serve.py"}], "after_files": [{"content": "import logging\nimport shutil\nimport tempfile\nimport sys\n\nfrom os.path import isfile, join\nfrom mkdocs.commands.build import build\nfrom mkdocs.config import load_config\n\nlog = logging.getLogger(__name__)\n\n\ndef _init_asyncio_patch():\n    \"\"\"\n    Select compatible event loop for Tornado 5+.\n\n    As of Python 3.8, the default event loop on Windows is `proactor`,\n    however Tornado requires the old default \"selector\" event loop.\n    As Tornado has decided to leave this to users to set, MkDocs needs\n    to set it. See https://github.com/tornadoweb/tornado/issues/2608.\n    \"\"\"\n    if sys.platform.startswith(\"win\") and sys.version_info >= (3, 8):\n        import asyncio\n        try:\n            from asyncio import WindowsSelectorEventLoopPolicy\n        except ImportError:\n            pass  # Can't assign a policy which doesn't exist.\n        else:\n            if not isinstance(asyncio.get_event_loop_policy(), WindowsSelectorEventLoopPolicy):\n                asyncio.set_event_loop_policy(WindowsSelectorEventLoopPolicy())\n\n\ndef _get_handler(site_dir, StaticFileHandler):\n\n    from tornado.template import Loader\n\n    class WebHandler(StaticFileHandler):\n\n        def write_error(self, status_code, **kwargs):\n\n            if status_code in (404, 500):\n                error_page = '{}.html'.format(status_code)\n                if isfile(join(site_dir, error_page)):\n                    self.write(Loader(site_dir).load(error_page).generate())\n                else:\n                    super().write_error(status_code, **kwargs)\n\n    return WebHandler\n\n\ndef _livereload(host, port, config, builder, site_dir):\n\n    # We are importing here for anyone that has issues with livereload. Even if\n    # this fails, the --no-livereload alternative should still work.\n    _init_asyncio_patch()\n    from livereload import Server\n    import livereload.handlers\n\n    class LiveReloadServer(Server):\n\n        def get_web_handlers(self, script):\n            handlers = super().get_web_handlers(script)\n            # replace livereload handler\n            return [(handlers[0][0], _get_handler(site_dir, livereload.handlers.StaticFileHandler), handlers[0][2],)]\n\n    server = LiveReloadServer()\n\n    # Watch the documentation files, the config file and the theme files.\n    server.watch(config['docs_dir'], builder)\n    server.watch(config['config_file_path'], builder)\n\n    for d in config['theme'].dirs:\n        server.watch(d, builder)\n\n    # Run `serve` plugin events.\n    server = config['plugins'].run_event('serve', server, config=config, builder=builder)\n\n    server.serve(root=site_dir, host=host, port=port, restart_delay=0)\n\n\ndef _static_server(host, port, site_dir):\n\n    # Importing here to separate the code paths from the --livereload\n    # alternative.\n    _init_asyncio_patch()\n    from tornado import ioloop\n    from tornado import web\n\n    application = web.Application([\n        (r\"/(.*)\", _get_handler(site_dir, web.StaticFileHandler), {\n            \"path\": site_dir,\n            \"default_filename\": \"index.html\"\n        }),\n    ])\n    application.listen(port=port, address=host)\n\n    log.info('Running at: http://%s:%s/', host, port)\n    log.info('Hold ctrl+c to quit.')\n    try:\n        ioloop.IOLoop.instance().start()\n    except KeyboardInterrupt:\n        log.info('Stopping server...')\n\n\ndef serve(config_file=None, dev_addr=None, strict=None, theme=None,\n          theme_dir=None, livereload='livereload', **kwargs):\n    \"\"\"\n    Start the MkDocs development server\n\n    By default it will serve the documentation on http://localhost:8000/ and\n    it will rebuild the documentation and refresh the page automatically\n    whenever a file is edited.\n    \"\"\"\n\n    # Create a temporary build directory, and set some options to serve it\n    # PY2 returns a byte string by default. The Unicode prefix ensures a Unicode\n    # string is returned. And it makes MkDocs temp dirs easier to identify.\n    site_dir = tempfile.mkdtemp(prefix='mkdocs_')\n\n    def builder():\n        log.info(\"Building documentation...\")\n        config = load_config(\n            config_file=config_file,\n            dev_addr=dev_addr,\n            strict=strict,\n            theme=theme,\n            theme_dir=theme_dir,\n            site_dir=site_dir,\n            **kwargs\n        )\n        # Override a few config settings after validation\n        config['site_url'] = 'http://{}/'.format(config['dev_addr'])\n\n        live_server = livereload in ['dirty', 'livereload']\n        dirty = livereload == 'dirty'\n        build(config, live_server=live_server, dirty=dirty)\n        return config\n\n    try:\n        # Perform the initial build\n        config = builder()\n\n        host, port = config['dev_addr']\n\n        if livereload in ['livereload', 'dirty']:\n            _livereload(host, port, config, builder, site_dir)\n        else:\n            _static_server(host, port, site_dir)\n    finally:\n        shutil.rmtree(site_dir)\n", "path": "mkdocs/commands/serve.py"}]}