diff --git a/monitor.c b/monitor.c
index e81f9e6..5c5ae97 100644
--- a/monitor.c
+++ b/monitor.c
@@ -96,6 +96,7 @@ struct mon_fd_t {
 typedef struct MonitorControl {
     uint8_t buf[1024];
     int size;
+    QObject *id;
 } MonitorControl;
 
 struct Monitor {
@@ -296,6 +297,11 @@ static void monitor_protocol_emitter(Monitor *mon, QObject *data)
         mon->error = NULL;
     }
 
+    if (mon->mc->id) {
+        qdict_put_obj(qmp, "id", mon->mc->id);
+        mon->mc->id = NULL;
+    }
+
     monitor_json_emitter(mon, QOBJECT(qmp));
     QDECREF(qmp);
 }
@@ -374,16 +380,27 @@ static void do_info(Monitor *mon, const QDict *qdict, QObject **ret_data)
     const mon_cmd_t *cmd;
     const char *item = qdict_get_try_str(qdict, "item");
 
-    if (!item)
+    if (!item) {
+        if (monitor_ctrl_mode(mon)) {
+            /* 'item' is mandatory in ctrl mode */
+            qemu_error_new(QERR_COMMAND_NOT_ISSUED);
+            return;
+        }
         goto help;
+    }
 
     for (cmd = info_cmds; cmd->name != NULL; cmd++) {
         if (compare_cmd(item, cmd->name))
             break;
     }
 
-    if (cmd->name == NULL)
+    if (cmd->name == NULL) {
+        if (monitor_ctrl_mode(mon)) {
+            qemu_error_new(QERR_COMMAND_NOT_FOUND, item);
+            return;
+        }
         goto help;
+    }
 
     if (monitor_handler_ported(cmd)) {
         cmd->mhandler.info_new(mon, ret_data);
@@ -397,7 +414,12 @@ static void do_info(Monitor *mon, const QDict *qdict, QObject **ret_data)
                 cmd->user_print(mon, *ret_data);
         }
     } else {
-        cmd->mhandler.info(mon);
+        if (monitor_ctrl_mode(mon)) {
+            /* handler not converted yet */
+            qemu_error_new(QERR_COMMAND_NOT_FOUND, cmd->name);
+        } else {
+            cmd->mhandler.info(mon);
+        }
     }
 
     return;
@@ -3355,10 +3377,15 @@ static void monitor_handle_command(Monitor *mon, const char *cmdline)
     if (monitor_handler_ported(cmd)) {
         monitor_call_handler(mon, cmd, qdict);
     } else {
-        cmd->mhandler.cmd(mon, qdict);
+        if (monitor_ctrl_mode(mon)) {
+            /* handler not converted yet */
+            qemu_error_new(QERR_COMMAND_NOT_FOUND, cmd->name);
+        } else {
+            cmd->mhandler.cmd(mon, qdict);
 
-        if (monitor_has_error(mon)) {
-            monitor_print_error(mon);
+            if (monitor_has_error(mon)) {
+                monitor_print_error(mon);
+            }
         }
     }
 
@@ -3590,6 +3617,216 @@ static int monitor_can_read(void *opaque)
     return (mon->suspend_cnt == 0) ? 128 : 0;
 }
 
+typedef struct CmdArgs {
+    QString *name;
+    int type;
+    int flag;
+    int optional;
+} CmdArgs;
+
+static int check_opt(const CmdArgs *cmd_args, const char *name)
+{
+    if (!cmd_args->optional) {
+        qemu_error_new(QERR_INVALID_PARAMETER, name, "parameter required");
+        return -1;
+    }
+
+    return 0;
+}
+
+static int check_arg(const CmdArgs *cmd_args, const QDict *args)
+{
+    QObject *value;
+    const char *name;
+
+    name = qstring_get_str(cmd_args->name);
+
+    if (!args) {
+        return check_opt(cmd_args, name);
+    }
+
+    value = qdict_get(args, name);
+    if (!value) {
+        return check_opt(cmd_args, name);
+    }
+
+    switch (cmd_args->type) {
+        case 'F':
+        case 'B':
+        case 's':
+            if (qobject_type(value) != QTYPE_QSTRING) {
+                qemu_error_new(QERR_INVALID_PARAMETER, name,"must be a string");
+                return -1;
+            }
+            break;
+        case '/': {
+            int i;
+            const char *keys[] = { "count", "format", "size", NULL };
+
+            for (i = 0; keys[i]; i++) {
+                QObject *obj = qdict_get(args, keys[i]);
+                if (!obj) {
+                    qemu_error_new(QERR_INVALID_PARAMETER, name,
+                                   "missing 'count', 'format' or 'size'");
+                    return -1;
+                }
+                if (qobject_type(obj) != QTYPE_QINT) {
+                    qemu_error_new(QERR_INVALID_PARAMETER, name,
+                                   "must be integer");
+                    return -1;
+                }
+            }
+            break;
+        }
+        case 'i':
+        case 'l':
+        case '-':
+            if (qobject_type(value) != QTYPE_QINT) {
+                qemu_error_new(QERR_INVALID_PARAMETER, name,
+                               "must be an integer");
+                return -1;
+            }
+            break;
+        default:
+            /* impossible */
+            abort();
+    }
+
+    return 0;
+}
+
+static void cmd_args_init(CmdArgs *cmd_args)
+{
+    cmd_args->name = qstring_new();
+    cmd_args->type = cmd_args->flag = cmd_args->optional = 0;
+}
+
+/*
+ * This is not trivial, we have to parse Monitor command's argument
+ * type syntax to be able to check the arguments provided by clients.
+ *
+ * In the near future we will use an array for that and will drop all
+ * this parsing...
+ */
+static int monitor_check_qmp_args(const mon_cmd_t *cmd, const QDict *args)
+{
+    int err;
+    const char *p;
+    CmdArgs cmd_args;
+
+    if (cmd->args_type == '\0') {
+        return (qdict_size(args) == 0 ? 0 : -1);
+    }
+
+    err = 0;
+    cmd_args_init(&cmd_args);
+
+    for (p = cmd->args_type;; p++) {
+        if (*p == ':') {
+            cmd_args.type = *++p;
+            p++;
+            if (cmd_args.type == '-') {
+                cmd_args.flag = *p++;
+            } else if (*p == '?') {
+                cmd_args.optional = 1;
+                p++;
+            }
+
+            assert(*p == ',' || *p == '\0');
+            err = check_arg(&cmd_args, args);
+
+            QDECREF(cmd_args.name);
+            cmd_args_init(&cmd_args);
+
+            if (err < 0) {
+                break;
+            }
+        } else {
+            qstring_append_chr(cmd_args.name, *p);
+        }
+
+        if (*p == '\0') {
+            break;
+        }
+    }
+
+    QDECREF(cmd_args.name);
+    return err;
+}
+
+static void monitor_handle_qmp_command(Monitor *mon)
+{
+    int err;
+    QObject *obj;
+    QDict *input, *args;
+    const char *cmd_name;
+    const mon_cmd_t *cmd;
+
+    qemu_errors_to_mon(mon);
+
+    obj = qobject_from_json((char *) mon->mc->buf);
+    if (!obj) {
+        // FIXME: should be triggered in qobject_from_json()
+        qemu_error_new(QERR_BAD_SYNTAX, "parsing error");
+        goto error;
+    } else if (qobject_type(obj) != QTYPE_QDICT) {
+        qemu_error_new(QERR_BAD_SYNTAX, "should be a dictionary");
+        qobject_decref(obj);
+        goto error;
+    }
+
+    input = qobject_to_qdict(obj);
+
+    mon->mc->id = qdict_get(input, "id");
+    qobject_incref(mon->mc->id);
+
+    if (!qdict_haskey(input, "execute")) {
+        qemu_error_new(QERR_BAD_SYNTAX, "missing 'execute' keyword");
+        QDECREF(input);
+        goto error;
+    }
+
+    cmd_name = qdict_get_str(input, "execute");
+    for (cmd = mon_cmds; cmd->name != NULL; cmd++) {
+        if (compare_cmd(cmd_name, cmd->name)) {
+            break;
+        }
+    }
+
+    if (cmd->name == NULL) {
+        qemu_error_new(QERR_COMMAND_NOT_FOUND, cmd_name);
+        QDECREF(input);
+        goto error;
+    }
+
+    obj = qdict_get(input, "arguments");
+    if (!obj) {
+        QDECREF(input);
+        args = qdict_new();
+    } else {
+        args = qobject_to_qdict(obj);
+        QINCREF(args);
+        QDECREF(input);
+    }
+
+    err = monitor_check_qmp_args(cmd, args);
+    if (err < 0) {
+        QDECREF(args);
+        goto error;
+    }
+
+    monitor_call_handler(mon, cmd, args);
+
+    QDECREF(args);
+
+    qemu_errors_to_previous();
+    return;
+
+error:
+    monitor_protocol_emitter(mon, NULL);
+    qemu_errors_to_previous();
+}
+
 /**
  * monitor_control_read(): Read and handle QMP input
  */
@@ -3605,7 +3842,7 @@ static void monitor_control_read(void *opaque, const uint8_t *buf, int size)
             cur_mon->mc->buf[cur_mon->mc->size] = '\0';
             cur_mon->mc->size = 0;
 
-            /* TODO: parse QMP input */
+            monitor_handle_qmp_command(cur_mon);
             break;
         } else {
             cur_mon->mc->buf[cur_mon->mc->size++] = buf[i];
