Interesting question! I can see a couple of ways of doing this. The one would be to write a custom plugin that wraps the JSONPlugin:
from bottle import route, run, install, JSONPlugin
from bson import json_util
class JSONDefaultPlugin(JSONPlugin):
    def __init__(self):
        super(JSONDefaultPlugin, self).__init__()
        self.plain_dump = self.json_dumps
        self.json_dumps = lambda body: self.plain_dump(body, default=json_util.default)
Which can then be used like this:
@route('/hello')
def index(name):
    return {'test': datetime.datetime(2014, 2, 1, 0, 0)}
install(JSONDefaultPlugin())
run(host='localhost', port=8080)
And will give output like this:
{"test": {"$date": 1391212800000}}
Another, shorter, way is to simply specify the json_loads parameter when instantiating the JSONPlugin class:
import json
from bson import json_util
install(JSONPlugin(json_dumps=lambda body: json.dumps(body, default=json_util.default)))
This produces the same result.
Background
This all makes a little more sense when you look at the source code for bottle (some parts removed below for brevity):
class JSONPlugin(object):
    name = 'json'
    api  = 2
    def __init__(self, json_dumps=json_dumps):
        self.json_dumps = json_dumps
    def apply(self, callback, route):
        dumps = self.json_dumps
        if not dumps: return callback
        def wrapper(*a, **ka):
            ... 
            if isinstance(rv, dict):
                ...
            elif isinstance(rv, HTTPResponse) and isinstance(rv.body, dict):
                rv.body = dumps(rv.body)
                rv.content_type = 'application/json'
            return rv
        return wrapper
All we need to do is make sure the call to dumps there receives the default keyword argument you wish to provide.