diff --git a/platypush/backend/http/static/css/source/common/layout.scss b/platypush/backend/http/static/css/source/common/layout.scss
index 2e6cc4ca..beaad3c5 100644
--- a/platypush/backend/http/static/css/source/common/layout.scss
+++ b/platypush/backend/http/static/css/source/common/layout.scss
@@ -26,6 +26,7 @@ $widths: (
         float: left;
         box-sizing: border-box;
         width: ((100%/12)*$i);
+        margin: 0;
     }
 
     @if $i < 12 {
diff --git a/platypush/backend/http/static/css/source/common/notifications.scss b/platypush/backend/http/static/css/source/common/notifications.scss
index e568208c..989a55b3 100644
--- a/platypush/backend/http/static/css/source/common/notifications.scss
+++ b/platypush/backend/http/static/css/source/common/notifications.scss
@@ -3,6 +3,7 @@
     bottom: 0;
     right: 0;
     width: 25em;
+    z-index: 1000;
 
     .notification {
         background: $notification-bg;
diff --git a/platypush/backend/http/static/css/source/common/vars.scss b/platypush/backend/http/static/css/source/common/vars.scss
index 0ec0872a..5db4af81 100644
--- a/platypush/backend/http/static/css/source/common/vars.scss
+++ b/platypush/backend/http/static/css/source/common/vars.scss
@@ -1,7 +1,11 @@
 //// Common defaults
-$default-bg: #f4f5f6 !default;
+$default-bg: white !default;
+$default-bg-2: #f4f5f6 !default;
+$default-bg-3: #f1f3f2 !default;
+$default-bg-4: #edf0ee !default;
 $default-fg: black !default;
 $default-fg-2: #333333 !default;
+$default-font-size: 1.5rem !default;
 
 $default-font-family: "Raleway", "HelveticaNeue", "Helvetica Neue", Helvetica, Arial, sans-serif !default;
 $default-border: 1px solid #e1e4e8 !default;
@@ -9,6 +13,7 @@ $default-border-2: 1px solid #dddddd !default;
 $default-bottom: $default-border !default;
 $default-link-fg: #5f7869 !default;
 
+$font-size: $default-font-size !default;
 $selected-bg: #c8ffd0 !default;
 $hover-bg: #def6ea !default;
 $header-bg: $default_bg !default;
@@ -26,12 +31,12 @@ $fade-in-transition-duration: $fade-transition-duration !default;
 $fade-out-transition-duration: $fade-transition-duration !default;
 
 //// Notifications
-$notification-bg: rgba(185, 255, 193, 0.85) !default;
-$notification-hover-bg: rgba(160,245,178,0.9) !default;
-$notification-warning-bg: rgba(228, 255, 78, 0.85) !default;
-$notification-warning-hover-bg: rgba(218, 245, 68, 0.9) !default;
-$notification-error-bg: rgba(255, 100, 100, 0.85) !default;
-$notification-error-hover-bg: rgba(245, 90, 90, 0.9) !default;
+$notification-bg: rgba(185, 255, 193, 0.9) !default;
+$notification-hover-bg: rgba(160,245,178,0.95) !default;
+$notification-warning-bg: rgba(228, 255, 78, 0.9) !default;
+$notification-warning-hover-bg: rgba(218, 245, 68, 0.95) !default;
+$notification-error-bg: rgba(255, 100, 100, 0.9) !default;
+$notification-error-hover-bg: rgba(245, 90, 90, 0.95) !default;
 $notification-border: 1px solid rgba(109, 205, 134, 0.62) !default;
 $notification-warning-border: 1px solid rgba(205, 205, 109, 0.62) !default;
 $notification-error-border: 1px solid rgba(205, 109, 109, 0.62) !default;
diff --git a/platypush/backend/http/static/css/source/webpanel/header.scss b/platypush/backend/http/static/css/source/webpanel/header.scss
deleted file mode 100644
index 8e0f14d7..00000000
--- a/platypush/backend/http/static/css/source/webpanel/header.scss
+++ /dev/null
@@ -1,34 +0,0 @@
-header {
-    .row {
-        width: 100%;
-        background: $header-bg;
-        padding: 1rem 2.5rem;
-        // margin: 0 1rem 3.5rem -1rem;
-        border-bottom: $header-bottom;
-        display: flex;
-        align-items: center;
-
-        .logo {
-            font-size: 25px;
-
-            .logo-1 {
-                font-weight: bold;
-            }
-        }
-
-        .date-time {
-            text-align: right;
-            padding-right: 3rem;
-
-            .date {
-                color: #666;
-            }
-
-            .time {
-                font-weight: bold;
-                font-size: 25px;
-            }
-        }
-    }
-}
-
diff --git a/platypush/backend/http/static/css/source/webpanel/index.scss b/platypush/backend/http/static/css/source/webpanel/index.scss
index f9d3a09f..5f65e8bf 100644
--- a/platypush/backend/http/static/css/source/webpanel/index.scss
+++ b/platypush/backend/http/static/css/source/webpanel/index.scss
@@ -7,27 +7,34 @@
 @import 'common/modal';
 @import 'common/notifications';
 
-@import 'header';
 @import 'nav';
 
 body {
     width: 100%;
     overflow-x: hidden;
     font-family: $default-font-family;
+    font-size: $default-font-size;
+}
+
+body::-webkit-scrollbar {
+    width: 1em;
+}
+
+body::-webkit-scrollbar-track {
+    -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.3);
+}
+
+body::-webkit-scrollbar-thumb {
+    background-color: darkgrey;
+    outline: 1px solid slategrey;
 }
 
 main {
-    margin: 6rem auto;
+    padding: 4.9rem 0;
+    margin: 0;
 }
 
 a {
     color: $default-link-fg;
 }
 
-.plugin-container {
-    border: $default-border-2;
-    border-radius: 1rem;
-    margin: 1.5rem;
-    box-shadow: 8px 8px 6px -1px rgba(187,187,187,0.75);
-}
-
diff --git a/platypush/backend/http/static/css/source/webpanel/nav.scss b/platypush/backend/http/static/css/source/webpanel/nav.scss
index bb902ac2..f485f1f7 100644
--- a/platypush/backend/http/static/css/source/webpanel/nav.scss
+++ b/platypush/backend/http/static/css/source/webpanel/nav.scss
@@ -4,7 +4,7 @@ nav {
     top: 0;
     width: 100%;
     z-index: 10;
-    opacity: 0.9;
+    opacity: 0.95;
 
     ul {
         position: relative;
@@ -23,9 +23,9 @@ nav {
             margin: 0;
             text-transform: uppercase;
             letter-spacing: .1rem;
+            border-radius: 2rem;
 
             &:hover {
-                border-radius: 2rem;
                 background: $hover-bg;
                 letter-spacing: .4rem;
             }
diff --git a/platypush/backend/http/static/css/source/webpanel/plugins/light.hue/index.scss b/platypush/backend/http/static/css/source/webpanel/plugins/light.hue/index.scss
index da02f79f..4e3aadd5 100644
--- a/platypush/backend/http/static/css/source/webpanel/plugins/light.hue/index.scss
+++ b/platypush/backend/http/static/css/source/webpanel/plugins/light.hue/index.scss
@@ -8,6 +8,8 @@
     font-weight: 400;
     line-height: 3.8rem;
     letter-spacing: .1rem;
+    border-bottom: $default-border-2;
+    border-radius: 0 0 1em 1em;
 
     %panel {
         margin: 1.5rem auto;
@@ -28,7 +30,7 @@
 
         .title {
             padding: .75rem;
-            background: $default-bg;
+            background: $default-bg-2;
             border-bottom: $default-border-2;
 
             &:last-child {
diff --git a/platypush/backend/http/static/css/source/webpanel/plugins/music.mpd/index.scss b/platypush/backend/http/static/css/source/webpanel/plugins/music.mpd/index.scss
new file mode 100644
index 00000000..56184d98
--- /dev/null
+++ b/platypush/backend/http/static/css/source/webpanel/plugins/music.mpd/index.scss
@@ -0,0 +1,162 @@
+@import 'common/vars';
+@import 'common/layout';
+@import 'webpanel/plugins/music.mpd/vars';
+
+// background-image: linear-gradient(to right bottom, rgb(123, 84, 30), rgb(0, 0, 0)), linear-gradient(transparent, rgb(0, 0, 0) 70%);
+
+.music-mpd-container {
+    line-height: 3rem;
+    letter-spacing: .03rem;
+    overflow: hidden;
+
+    * > .item {
+        cursor: pointer;
+        border-radius: 1rem;
+        padding: .5rem;
+
+        &:nth-child(odd) { background: rgba(255, 255, 255, 0.0); }
+        &:nth-child(even) { background: $default-bg-3; }
+
+        .artist {
+            font-size: $artist-font-size;
+        }
+    }
+
+    * > .duration {
+        color: $duration-color;
+        font-size: $duration-font-size;
+    }
+
+    .panels {
+        display: flex;
+
+        .browser, .playlist {
+            height: 100vh - 16rem;
+            overflow: auto;
+        }
+
+        .browser {
+            background: $browser-panel-bg;
+            border-right: $default-border-2;
+            padding: .3rem 1rem 6rem 1rem;
+            font-size: $browser-font-size;
+
+            .item {
+                background: none;
+            }
+
+            .fa {
+                color: #666;
+            }
+        }
+
+        .playlist {
+            padding: .5rem 1rem 6rem 1rem;
+        }
+    }
+
+    .controls {
+        @extend .vertical-center;
+        position: fixed;
+        width: 100%;
+        min-height: 6rem;
+        bottom: 0;
+        border-top: $default-border-2;
+        padding: 1rem;
+        background: $control-panel-bg;
+        box-shadow: $control-panel-shadow;
+        z-index: 2;
+
+        .track-container {
+            @extend .vertical-center;
+            line-height: 2.6rem;
+
+            .track-info {
+                .artist {
+                    font-weight: bold;
+                }
+            }
+        }
+
+        .playback-controls {
+            .row {
+                @extend .vertical-center;
+                justify-content: center;
+            }
+
+        }
+
+        * > button {
+            border: 0;
+            padding: 0 1.5rem;
+
+            &.enabled {
+                color: $button-enabled-color;
+            }
+
+            &:hover {
+                .fa {
+                    color: $button-hover-color;
+                }
+            }
+
+            .fa-play, .fa-pause  {
+                color: $button-hover-color;
+                font-size: $font-size * 2;
+                margin-top: .3rem;
+
+                &:hover {
+                    color: $play-button-hover-color;
+                }
+            }
+        }
+
+        .pull-right {
+            padding-right: 2.5rem;
+            button {
+                &:not(last-child) {
+                    padding: 0 .7rem;
+                }
+
+                &:last-child {
+                    padding: 0;
+                }
+            }
+
+            .volume-container {
+                button {
+                    padding: 0 .3rem 0 0;
+                    background: none;
+                }
+            }
+        }
+
+        * > .seek-slider {
+            width: 75%;
+        }
+
+        * > .volume-slider {
+            width: 75%;
+            margin-right: 1rem;
+        }
+
+        * > .elapsed-time,
+        * > .total-time {
+            font-size: $control-time-font-size;
+            color: $control-time-color;
+        }
+
+        * > .elapsed-time {
+            margin-right: 1.5rem;
+        }
+
+        * > .total-time {
+            margin-left: 1.5rem;
+        }
+    }
+
+    * > .item:hover {
+        background: $hover-bg !important;
+    }
+}
+
diff --git a/platypush/backend/http/static/css/source/webpanel/plugins/music.mpd/vars.scss b/platypush/backend/http/static/css/source/webpanel/plugins/music.mpd/vars.scss
new file mode 100644
index 00000000..b9975a03
--- /dev/null
+++ b/platypush/backend/http/static/css/source/webpanel/plugins/music.mpd/vars.scss
@@ -0,0 +1,17 @@
+$button-enabled-color: #59df3e;
+$button-hover-color: $button-enabled-color;
+$play-button-hover-color: #64ef4a;
+
+$artist-font-size: $font-size * 0.9333;
+
+$duration-color: #666;
+$duration-font-size: $font-size * 0.86666;
+
+$control-panel-bg: rgba(245,245,245,0.95);
+$control-panel-shadow: 0 -2.5px 4px 0 #c0c0c0;
+$control-time-color: #666;
+$control-time-font-size: $font-size * 0.666666;
+
+$browser-panel-bg: rgba(248,250,250,0.95);
+$browser-font-size: $font-size * 0.8666;
+
diff --git a/platypush/backend/http/static/js/plugins/music.mpd/browser.js b/platypush/backend/http/static/js/plugins/music.mpd/browser.js
new file mode 100644
index 00000000..539e60d7
--- /dev/null
+++ b/platypush/backend/http/static/js/plugins/music.mpd/browser.js
@@ -0,0 +1,8 @@
+Vue.component('music-mpd-browser-item', {
+    template: '#tmpl-music-mpd-browser-item',
+    props: ['type','name'],
+
+    methods: {
+    },
+});
+
diff --git a/platypush/backend/http/static/js/plugins/music.mpd/index.js b/platypush/backend/http/static/js/plugins/music.mpd/index.js
new file mode 100644
index 00000000..cb27abc2
--- /dev/null
+++ b/platypush/backend/http/static/js/plugins/music.mpd/index.js
@@ -0,0 +1,331 @@
+Vue.component('music-mpd', {
+    template: '#tmpl-music-mpd',
+    props: ['config'],
+    data: function() {
+        return {
+            track: {},
+            status: {},
+            playlist: [],
+            timer: null,
+            browserPath: [],
+            browserItems: [],
+            syncTime: {
+                timestamp: null,
+                elapsed: null,
+            },
+        };
+    },
+
+    methods: {
+        refresh: async function() {
+            const getStatus = request('music.mpd.status');
+            const getTrack = request('music.mpd.currentsong');
+            const getPlaylist = request('music.mpd.playlistinfo');
+            const getBrowserItems = request('music.mpd.lsinfo');
+
+            let [status, track, playlist, browserItems] = await Promise.all([getStatus, getTrack, getPlaylist, getBrowserItems]);
+
+            this._parseStatus(status);
+            this._parseTrack(track);
+            this._parsePlaylist(playlist);
+            this._parseBrowserItems(browserItems);
+
+            if (this.status.state === 'play') {
+                this.startTimer();
+            }
+        },
+
+        _parseStatus: async function(status) {
+            if (!status || status.length === 0) {
+                status = await request('music.mpd.status');
+            }
+
+            for (const [attr, value] of Object.entries(status)) {
+                if (['consume','random','repeat','single','bitrate'].indexOf(attr) >= 0) {
+                    Vue.set(this.status, attr, !!parseInt(value));
+                } else if (['nextsong','nextsongid','playlist','playlistlength',
+                            'volume','xfade','song','songid'].indexOf(attr) >= 0) {
+                    Vue.set(this.status, attr, parseInt(value));
+                } else if (['elapsed'].indexOf(attr) >= 0) {
+                    Vue.set(this.status, attr, parseFloat(value));
+                } else {
+                    Vue.set(this.status, attr, value);
+                }
+            }
+        },
+
+        _parseTrack: async function(track) {
+            if (!track || track.length === 0) {
+                track = await request('music.mpd.currentsong');
+            }
+
+            for (const [attr, value] of Object.entries(track)) {
+                if (['id','pos','time'].indexOf(attr) >= 0) {
+                    Vue.set(this.track, attr, parseInt(value));
+                } else {
+                    Vue.set(this.track, attr, value);
+                }
+            }
+        },
+
+        _parsePlaylist: function(playlist) {
+            if (!playlist || playlist.length === 0) {
+                return;
+            }
+
+            this.playlist = [];
+
+            for (var track of playlist) {
+                for (const [attr, value] of Object.entries(track)) {
+                    if (['time','pos','id'].indexOf(attr) >= 0) {
+                        track[attr] = parseInt(value);
+                    } else {
+                        track[attr] = value;
+                    }
+                }
+
+                this.playlist.push(track);
+            }
+        },
+
+        _parseBrowserItems: function(browserItems) {
+            if (!browserItems || browserItems.length === 0) {
+                return;
+            }
+
+            this.browserItems = [];
+
+            for (var item of browserItems) {
+                if (item.directory) {
+                    this.browserItems.push({
+                        type: 'directory',
+                        name: item.directory,
+                    });
+                } else if (item.playlist) {
+                    this.browserItems.push({
+                        type: 'playlist',
+                        name: item.playlist,
+                        'last-modified': item['last-modified'],
+                    });
+                }
+            }
+        },
+
+        convertTime: function(time) {
+            time = parseFloat(time);   // Normalize strings
+            var t = {};
+            t.h = '' + parseInt(time/3600);
+            t.m = '' + parseInt(time/60 - t.h*60);
+            t.s = '' + parseInt(time - t.m*60);
+
+            for (var attr of ['m','s']) {
+                if (parseInt(t[attr]) < 10) {
+                    t[attr] = '0' + t[attr];
+                }
+            }
+
+            var ret = [];
+            if (parseInt(t.h)) {
+                ret.push(t.h);
+            }
+
+            ret.push(t.m, t.s);
+            return ret.join(':');
+        },
+
+        previous: async function() {
+            await request('music.mpd.previous');
+            let track = await request('music.mpd.currentsong');
+            this.onNewPlayingTrack({track: track});
+        },
+
+        repeat: async function() {
+            await request('music.mpd.repeat');
+            let status = await request('music.mpd.status');
+            this._parseStatus(status);
+        },
+
+        playPause: async function() {
+            await request('music.mpd.pause');
+            let status = await request('music.mpd.status');
+            const method = status.state === 'play' ? this.onMusicPlay : this.onMusicPause;
+            method({ status: status });
+        },
+
+        stop: async function() {
+            await request('music.mpd.stop');
+            this.onMusicStop({});
+        },
+
+        random: async function() {
+            await request('music.mpd.random');
+            let status = await request('music.mpd.status');
+            this._parseStatus(status);
+        },
+
+        next: async function() {
+            await request('music.mpd.next');
+            let track = await request('music.mpd.currentsong');
+            this.onNewPlayingTrack({track: track});
+        },
+
+        seek: async function(event) {
+            var value;
+
+            if (event.target) {
+                value = parseFloat(event.target.value);
+            } else if (event.value) {
+                value = parseFloat(event.value);
+            } else {
+                value = parseFloat(event);
+            }
+
+            const status = await request('music.mpd.seekcur', {value: value});
+            this.onSeekChange({status: status});
+        },
+
+        volume: async function(event) {
+            var value;
+
+            if (event.target) {
+                value = parseFloat(event.target.value);
+            } else if (event.value) {
+                value = parseFloat(event.value);
+            } else {
+                value = parseFloat(event);
+            }
+
+            const status = await request('music.mpd.setvol', {vol: value});
+            this.onVolumeChange({status: status});
+        },
+
+        onNewPlayingTrack: async function(event) {
+            var previousTrack = {
+                file: this.track.file,
+                artist: this.track.artist,
+                title: this.track.title,
+            };
+
+            this.status.state = 'play';
+            this.status.elapsed = 0;
+            this.track = {};
+
+            let status = await request('music.mpd.status');
+            this._parseStatus(status);
+            this._parseTrack(event.track);
+            this.startTimer();
+
+            if (this.track.file != previousTrack.file
+                    || this.track.artist != previousTrack.artist
+                    || this.track.title != previousTrack.title) {
+                this.showNewTrackNotification();
+            }
+        },
+
+        showNewTrackNotification: function() {
+            createNotification({
+                html: '<b>' + (this.track.artist || '[No Artist]') + '</b><br>' +
+                      (this.track.title || '[No Title]'),
+                image: {
+                    icon: 'play',
+                }
+            });
+        },
+
+        onMusicStop: function(event) {
+            this.status.state = 'stop';
+            this._parseStatus(event.status);
+            this._parseTrack(event.track);
+            this.stopTimer();
+        },
+
+        onMusicPlay: function(event) {
+            this.status.state = 'play';
+            this._parseStatus(event.status);
+            this._parseTrack(event.track);
+            this.startTimer();
+        },
+
+        onMusicPause: function(event) {
+            this.status.state = 'pause';
+            this._parseStatus(event.status);
+            this._parseTrack(event.track);
+
+            this.syncTime.timestamp = new Date();
+            this.syncTime.elapsed = this.status.elapsed;
+        },
+
+        onSeekChange: function(event) {
+            if (event.position != null)
+                this.status.elapsed = parseFloat(event.position);
+            if (event.status)
+                this._parseStatus(event.status);
+            if (event.track)
+                this._parseTrack(event.track);
+
+            this.syncTime.timestamp = new Date();
+            this.syncTime.elapsed = this.status.elapsed;
+        },
+
+        onPlaylistChange: function(event) {
+            console.log(event);
+        },
+
+        onVolumeChange: function(event) {
+            if (event.volume != null)
+                this.status.volume = parseFloat(event.volume);
+            if (event.status)
+                this._parseStatus(event.status);
+            if (event.track)
+                this._parseTrack(event.track);
+        },
+
+        onRepeatChange: function(event) {
+            this.status.repeat = event.state;
+        },
+
+        onRandomChange: function(event) {
+            this.status.random = event.state;
+        },
+
+        startTimer: function() {
+            if (this.timer != null) {
+                this.stopTimer();
+            }
+
+            this.syncTime.timestamp = new Date();
+            this.syncTime.elapsed = this.status.elapsed;
+            this.timer = setInterval(this.timerFunc, 1000);
+        },
+
+        stopTimer: function() {
+            if (this.timer == null) {
+                clearInterval(this.timer);
+                this.timer = null;
+            }
+        },
+
+        timerFunc: function() {
+            if (this.status.state !== 'play' || this.status.elapsed == null) {
+                return;
+            }
+
+            this.status.elapsed = this.syncTime.elapsed +
+                ((new Date()).getTime()/1000) - (this.syncTime.timestamp.getTime()/1000);
+        },
+    },
+
+    created: function() {
+        this.refresh();
+        registerEventHandler(this.onNewPlayingTrack, 'platypush.message.event.music.NewPlayingTrackEvent');
+        registerEventHandler(this.onMusicStop, 'platypush.message.event.music.MusicStopEvent');
+        registerEventHandler(this.onMusicPlay, 'platypush.message.event.music.MusicPlayEvent');
+        registerEventHandler(this.onMusicPause, 'platypush.message.event.music.MusicPauseEvent');
+        registerEventHandler(this.onSeekChange, 'platypush.message.event.music.SeekChangeEvent');
+        registerEventHandler(this.onPlaylistChange, 'platypush.message.event.music.PlaylistChangeEvent');
+        registerEventHandler(this.onVolumeChange, 'platypush.message.event.music.VolumeChangeEvent');
+        registerEventHandler(this.onRepeatChange, 'platypush.message.event.music.PlaybackRepeatModeChangeEvent');
+        registerEventHandler(this.onRandomChange, 'platypush.message.event.music.PlaybackRandomModeChangeEvent');
+    },
+});
+
diff --git a/platypush/backend/http/templates/header.html b/platypush/backend/http/templates/header.html
deleted file mode 100644
index 24e7af4b..00000000
--- a/platypush/backend/http/templates/header.html
+++ /dev/null
@@ -1,18 +0,0 @@
-<script type="text/x-template" id="tmpl-app-header">
-    <header class="s-hidden m-hidden">
-        <div class="row">
-            <div class="logo col-9">
-                <span class="logo-1">Platypush</span>
-                <span class="logo-2">Web Panel</span>
-            </div>
-
-            <div class="date-time col-3">
-                <div class="date" v-text="now.toDateString().substring(0,10)"></div>
-                <div class="time" v-text="now.toTimeString().substring(0,8)"></div>
-            </div>
-        </div>
-    </header>
-</script>
-
-<app-header></app-header>
-
diff --git a/platypush/backend/http/templates/index.html b/platypush/backend/http/templates/index.html
index 0028a780..79dd02a0 100644
--- a/platypush/backend/http/templates/index.html
+++ b/platypush/backend/http/templates/index.html
@@ -40,25 +40,31 @@
     </script>
 
     {% include 'elements.html' %}
+
+    {% for plugin, conf in templates.items() %}
+        {% with configuration=templates[plugin] %}
+            {% include conf['_template_file'] %}
+        {% endwith %}
+    {% endfor %}
+
+    {% for script in scripts.values() %}
+    <script type="text/javascript" src="{{ url_for('static', filename=script['_script_file']) }}"></script>
+    {% endfor %}
 </head>
 
 <body>
     <div id="app">
-        {# include 'header.html' #}
-
         {% with plugins=templates.keys() %}
             {% include 'nav.html' %}
         {% endwith %}
 
         <main>
             <div class="plugins-container">
-                {% for plugin, conf in templates.items() %}
-                    {% with configuration=templates[plugin], utils=utils %}
-                        {% include conf['_template_file'] %}
-                        <plugin tag="{{ utils.plugin_name_to_tag(plugin) }}"
-                                :config="{{ conf }}" :class="{hidden: '{{ plugin }}' != selectedPlugin}"/>
-                    {% endwith %}
-                {% endfor %}
+                <plugin v-for="(conf, plugin) in {{ utils.to_json(templates) }}"
+                        :tag="plugin.replace('.', '-')"
+                        :key="plugin"
+                        :config="conf"
+                        :class="{hidden: plugin != selectedPlugin}"/>
             </div>
         </main>
 
@@ -67,10 +73,6 @@
 
     {% include 'plugins/template.html' %}
 
-    {% for script in scripts.values() %}
-    <script type="text/javascript" src="{{ url_for('static', filename=script['_script_file']) }}"></script>
-    {% endfor %}
-
     <script type="text/javascript" src="{{ url_for('static', filename='js/application.js') }}"></script>
 </body>
 
diff --git a/platypush/backend/http/templates/nav.html b/platypush/backend/http/templates/nav.html
index a701f63f..83968be3 100644
--- a/platypush/backend/http/templates/nav.html
+++ b/platypush/backend/http/templates/nav.html
@@ -6,18 +6,9 @@
                     {{ plugin }}
                 </a>
             </li>
-            <span class="decorator" v-if="'{{ plugin }}' == selectedPlugin"></span>
         {% endfor %}
 
-        <li>
-            <a href="#{{ plugin }}">Test tab 2</a>
-        </li>
-        <li>
-            <a href="#{{ plugin }}">Test tab 3</a>
-        </li>
-
         <div class="date-time pull-right">
-            <!--<div class="date" v-text="now.toDateString().substring(0,10)"></div>-->
             <div class="time" v-text="now.toTimeString().substring(0,8)"></div>
         </div>
     </ul>
diff --git a/platypush/backend/http/templates/plugins/music.mpd/browser.html b/platypush/backend/http/templates/plugins/music.mpd/browser.html
new file mode 100644
index 00000000..f256d465
--- /dev/null
+++ b/platypush/backend/http/templates/plugins/music.mpd/browser.html
@@ -0,0 +1,13 @@
+<script type="application/javascript" src="{{ url_for('static', filename='js/plugins/music.mpd/browser.js') }}"></script>
+
+<script type="text/x-template" id="tmpl-music-mpd-browser-item">
+    <div class="row item">
+        <div class="col-1 icon">
+            <i class="fa fa-folder" v-if="type == 'directory'"></i>
+            <i class="fa fa-list" v-else-if="type == 'playlist'"></i>
+        </div>
+
+        <div class="col-11 name" v-text="name"></div>
+    </div>
+</script>
+
diff --git a/platypush/backend/http/templates/plugins/music.mpd/index.html b/platypush/backend/http/templates/plugins/music.mpd/index.html
new file mode 100644
index 00000000..37d23fc9
--- /dev/null
+++ b/platypush/backend/http/templates/plugins/music.mpd/index.html
@@ -0,0 +1,88 @@
+{% include 'plugins/music.mpd/browser.html' %}
+
+<script type="text/x-template" id="tmpl-music-mpd">
+    <div class="row music-mpd-container">
+        <div class="row panels">
+            <div class="col-no-margin-l-3 s-hidden m-hidden browser">
+                <music-mpd-browser-item
+                        v-for="item in browserItems"
+                        :key="item.type + '-' + item.name"
+                        :type="item.type"
+                        :name="item.name">
+                </music-mpd-browser-item>
+            </div>
+
+            <div class="col-no-margin-s-12 col-no-margin-m-12 col-no-margin-l-9 playlist">
+                <div class="row track item"
+                     v-for="track in playlist">
+                    <div class="col-5 artist" v-text="track.artist"></div>
+                    <div class="col-5 title" v-text="track.title"></div>
+                    <div class="col-2 pull-right duration" v-text="convertTime(track.time)"></div>
+                </div>
+            </div>
+        </div>
+
+        <div class="row controls">
+            <div class="col-3 track-container">
+                <div class="track-info" v-if="status.state == 'play' || status.state == 'pause'">
+                    <div class="row artist" v-text="track.artist"></div>
+                    <div class="row title" v-text="track.title"></div>
+                </div>
+            </div>
+
+            <div class="col-6 playback-controls">
+                <div class="row">
+                    <button @click="previous">
+                        <i class="fa fa-step-backward"></i>
+                    </button>
+                    <button @click="playPause">
+                        <i class="fa fa-pause" v-if="status.state == 'play'"></i>
+                        <i class="fa fa-play" v-else></i>
+                    </button>
+                    <button @click="stop" v-if="status.state != 'stop'">
+                        <i class="fa fa-stop"></i>
+                    </button>
+                    <button @click="next">
+                        <i class="fa fa-step-forward"></i>
+                    </button>
+                </div>
+
+                <div class="row">
+                    <span class="elapsed-time" v-text="status.elapsed && status.state != 'stop' ? convertTime(status.elapsed) : '-:--'"></span>
+                    <input type="range"
+                           class="slider seek-slider"
+                           v-model="status.elapsed"
+                           min="0"
+                           :max="track.time"
+                           :disabled="!track.time || status.state == 'stop'"
+                           @input="seek">
+                    <span class="total-time" v-text="track.time && status.state != 'stop' ? convertTime(track.time) : '-:--'"></span>
+                </div>
+            </div>
+
+            <div class="col-3 pull-right">
+                <div class="row">
+                    <button @click="random" :class="{enabled: status.random}">
+                        <i class="fa fa-random"></i>
+                    </button>
+                    <button @click="repeat" :class="{enabled: status.repeat}">
+                        <i class="fa fa-repeat"></i>
+                    </button>
+                </div>
+
+                <div class="row volume-container">
+                    <button disabled>
+                        <i class="fa fa-volume-up"></i>
+                    </button>
+                    <input type="range"
+                           class="slider volume-slider"
+                           min="0"
+                           max="100"
+                           v-model="status.volume"
+                           @input="volume">
+                </div>
+            </div>
+        </div>
+    </div>
+</script>
+
diff --git a/platypush/backend/music/mopidy.py b/platypush/backend/music/mopidy.py
index 5ac5b68d..2e9d6fce 100644
--- a/platypush/backend/music/mopidy.py
+++ b/platypush/backend/music/mopidy.py
@@ -185,11 +185,11 @@ class MusicMopidyBackend(Backend):
                 new_status = self._get_tracklist_status()
                 if new_status['random'] != self._latest_status.get('random'):
                     self.bus.post(PlaybackRandomModeChangeEvent(state=new_status['random']))
-                if new_status['repeat'] != self._latest_status('repeat'):
+                if new_status['repeat'] != self._latest_status['repeat']:
                     self.bus.post(PlaybackRepeatModeChangeEvent(state=new_status['repeat']))
-                if new_status['single'] != self._latest_status('single'):
+                if new_status['single'] != self._latest_status['single']:
                     self.bus.post(PlaybackSingleModeChangeEvent(state=new_status['single']))
-                if new_status['consume'] != self._latest_status('consume'):
+                if new_status['consume'] != self._latest_status['consume']:
                     self.bus.post(PlaybackConsumeModeChangeEvent(state=new_status['consume']))
 
                 self._latest_status = new_status
diff --git a/platypush/plugins/music/mpd/__init__.py b/platypush/plugins/music/mpd/__init__.py
index de52ec50..3339a9e5 100644
--- a/platypush/plugins/music/mpd/__init__.py
+++ b/platypush/plugins/music/mpd/__init__.py
@@ -37,37 +37,50 @@ class MusicMpdPlugin(MusicPlugin):
         self.port = port
         self.client = None
 
-    def _connect(self):
-        if not self.client:
-            self.client = mpd.MPDClient(use_unicode=True)
-            self.client.connect(self.host, self.port)
-        return self.client
+    def _connect(self, n_tries=2):
+        with self._client_lock:
+            if self.client:
+                return
+
+            error = None
+            while n_tries > 0:
+                try:
+                    n_tries -= 1
+                    self.client = mpd.MPDClient(use_unicode=True)
+                    self.client.connect(self.host, self.port)
+                    return self.client
+                except Exception as e:
+                    error = e
+                    self.logger.warning('Connection exception: {}{}'.
+                                        format(str(e), (': Retrying' if n_tries > 0 else '')))
+                    time.sleep(0.5)
+
+        self.client = None
+        raise error
 
     def _exec(self, method, *args, **kwargs):
-        n_tries = int(kwargs.pop('n_tries')) if 'n_tries' in kwargs else 1
+        error = None
+        n_tries = int(kwargs.pop('n_tries')) if 'n_tries' in kwargs else 2
         return_status = kwargs.pop('return_status') \
             if 'return_status' in kwargs else True
 
-        try:
-            self._connect()
-            response = None
-            with self._client_lock:
-                response = getattr(self.client, method)(*args, **kwargs)
+        while n_tries > 0:
+            try:
+                self._connect()
+                n_tries -= 1
+                with self._client_lock:
+                    response = getattr(self.client, method)(*args, **kwargs)
 
-            if return_status:
-                return self.status().output
-            return response
-        except Exception as e:
-            self.logger.warning('Exception while executing MPD method {}: {}'.
-                                format(method, str(e)))
-            self.client = None
+                if return_status:
+                    return self.status().output
+                return response
+            except Exception as e:
+                error = str(e)
+                self.logger.warning('Exception while executing MPD method {}: {}'.
+                                    format(method, error))
+                self.client = None
 
-            if n_tries > 0:
-                kwargs['return_status'] = return_status
-                kwargs['n_tries'] = n_tries-1
-                return self._exec(method, *args, **kwargs)
-            else:
-                return (None, str(e))
+        return None, error
 
     @action
     def play(self, resource=None):
@@ -346,14 +359,21 @@ class MusicMpdPlugin(MusicPlugin):
             }
         """
 
-        try:
-            self._connect()
-            return self.client.status()
-        except Exception as e:
-            self.logger.warning('Exception while getting MPD status: {}'.
-                                format(str(e)))
-            self.client = None
-            return (None, str(e))
+        n_tries = 2
+        error = None
+
+        while n_tries > 0:
+            try:
+                n_tries -= 1
+                self._connect()
+                return self.client.status()
+            except Exception as e:
+                error = e
+                self.logger.warning('Exception while getting MPD status: {}'.
+                                    format(str(e)))
+                self.client = None
+
+        return None, error
 
     @action
     def currentsong(self):