diff --git a/klippy/chelper/__init__.py b/klippy/chelper/__init__.py
index d373d72e0..851fda708 100644
--- a/klippy/chelper/__init__.py
+++ b/klippy/chelper/__init__.py
@@ -1,6 +1,6 @@
 # Wrapper around C helper code
 #
-# Copyright (C) 2016-2020  Kevin O'Connor <kevin@koconnor.net>
+# Copyright (C) 2016-2021  Kevin O'Connor <kevin@koconnor.net>
 #
 # This file may be distributed under the terms of the GNU GPLv3 license.
 import os, logging
@@ -31,8 +31,8 @@ OTHER_FILES = [
 defs_stepcompress = """
     struct stepcompress *stepcompress_alloc(uint32_t oid);
     void stepcompress_fill(struct stepcompress *sc, uint32_t max_error
-        , uint32_t invert_sdir, uint32_t queue_step_msgid
-        , uint32_t set_next_step_dir_msgid);
+        , uint32_t invert_sdir, int32_t queue_step_msgtag
+        , int32_t set_next_step_dir_msgtag);
     void stepcompress_free(struct stepcompress *sc);
     int stepcompress_reset(struct stepcompress *sc, uint64_t last_step_clock);
     int stepcompress_queue_msg(struct stepcompress *sc
diff --git a/klippy/chelper/stepcompress.c b/klippy/chelper/stepcompress.c
index 812a3b50e..cd803ae74 100644
--- a/klippy/chelper/stepcompress.c
+++ b/klippy/chelper/stepcompress.c
@@ -1,6 +1,6 @@
 // Stepper pulse schedule compression
 //
-// Copyright (C) 2016-2020  Kevin O'Connor <kevin@koconnor.net>
+// Copyright (C) 2016-2021  Kevin O'Connor <kevin@koconnor.net>
 //
 // This file may be distributed under the terms of the GNU GPLv3 license.
 
@@ -36,7 +36,8 @@ struct stepcompress {
     // Message generation
     uint64_t last_step_clock;
     struct list_head msg_queue;
-    uint32_t queue_step_msgid, set_next_step_dir_msgid, oid;
+    uint32_t oid;
+    int32_t queue_step_msgtag, set_next_step_dir_msgtag;
     int sdir, invert_sdir;
     // Step+dir+step filter
     uint64_t next_step_clock;
@@ -244,13 +245,13 @@ stepcompress_alloc(uint32_t oid)
 // Fill message id information
 void __visible
 stepcompress_fill(struct stepcompress *sc, uint32_t max_error
-                  , uint32_t invert_sdir, uint32_t queue_step_msgid
-                  , uint32_t set_next_step_dir_msgid)
+                  , uint32_t invert_sdir, int32_t queue_step_msgtag
+                  , int32_t set_next_step_dir_msgtag)
 {
     sc->max_error = max_error;
     sc->invert_sdir = !!invert_sdir;
-    sc->queue_step_msgid = queue_step_msgid;
-    sc->set_next_step_dir_msgid = set_next_step_dir_msgid;
+    sc->queue_step_msgtag = queue_step_msgtag;
+    sc->set_next_step_dir_msgtag = set_next_step_dir_msgtag;
 }
 
 // Free memory associated with a 'stepcompress' object
@@ -307,7 +308,7 @@ queue_flush(struct stepcompress *sc, uint64_t move_clock)
             return ret;
 
         uint32_t msg[5] = {
-            sc->queue_step_msgid, sc->oid, move.interval, move.count, move.add
+            sc->queue_step_msgtag, sc->oid, move.interval, move.count, move.add
         };
         struct queue_message *qm = message_alloc_and_encode(msg, 5);
         qm->min_clock = qm->req_clock = sc->last_step_clock;
@@ -331,7 +332,7 @@ static int
 stepcompress_flush_far(struct stepcompress *sc, uint64_t abs_step_clock)
 {
     uint32_t msg[5] = {
-        sc->queue_step_msgid, sc->oid, abs_step_clock - sc->last_step_clock,
+        sc->queue_step_msgtag, sc->oid, abs_step_clock - sc->last_step_clock,
         1, 0
     };
     struct queue_message *qm = message_alloc_and_encode(msg, 5);
@@ -353,7 +354,7 @@ set_next_step_dir(struct stepcompress *sc, int sdir)
     if (ret)
         return ret;
     uint32_t msg[3] = {
-        sc->set_next_step_dir_msgid, sc->oid, sdir ^ sc->invert_sdir
+        sc->set_next_step_dir_msgtag, sc->oid, sdir ^ sc->invert_sdir
     };
     struct queue_message *qm = message_alloc_and_encode(msg, 3);
     qm->req_clock = sc->last_step_clock;
diff --git a/klippy/chelper/stepcompress.h b/klippy/chelper/stepcompress.h
index 3462b3f86..2d1534c17 100644
--- a/klippy/chelper/stepcompress.h
+++ b/klippy/chelper/stepcompress.h
@@ -7,8 +7,8 @@
 
 struct stepcompress *stepcompress_alloc(uint32_t oid);
 void stepcompress_fill(struct stepcompress *sc, uint32_t max_error
-                       , uint32_t invert_sdir, uint32_t queue_step_msgid
-                       , uint32_t set_next_step_dir_msgid);
+                       , uint32_t invert_sdir, int32_t queue_step_msgtag
+                       , int32_t set_next_step_dir_msgtag);
 void stepcompress_free(struct stepcompress *sc);
 uint32_t stepcompress_get_oid(struct stepcompress *sc);
 int stepcompress_get_step_dir(struct stepcompress *sc);
diff --git a/klippy/console.py b/klippy/console.py
index 69095345d..b787111fd 100755
--- a/klippy/console.py
+++ b/klippy/console.py
@@ -138,7 +138,7 @@ class KeyboardReader:
     def command_LIST(self, parts):
         self.update_evals(self.reactor.monotonic())
         mp = self.ser.get_msgparser()
-        cmds = [msgformat for msgid, msgtype, msgformat in mp.get_messages()
+        cmds = [msgformat for msgtag, msgtype, msgformat in mp.get_messages()
                 if msgtype == 'command']
         out = "Available mcu commands:"
         out += "\n  ".join([""] + sorted(cmds))
diff --git a/klippy/mcu.py b/klippy/mcu.py
index 6f6c80024..5e31602c5 100644
--- a/klippy/mcu.py
+++ b/klippy/mcu.py
@@ -694,9 +694,9 @@ class MCU:
             return self.lookup_command(msgformat)
         except self._serial.get_msgparser().error as e:
             return None
-    def lookup_command_id(self, msgformat):
+    def lookup_command_tag(self, msgformat):
         all_msgs = self._serial.get_msgparser().get_messages()
-        return {msgfmt: msgid for msgid, msgtype, msgfmt in all_msgs}[msgformat]
+        return {fmt: msgtag for msgtag, msgtype, fmt in all_msgs}[msgformat]
     def get_enumerations(self):
         return self._serial.get_msgparser().get_enumerations()
     def get_constants(self):
diff --git a/klippy/msgproto.py b/klippy/msgproto.py
index d1f461b87..c3c0cda94 100644
--- a/klippy/msgproto.py
+++ b/klippy/msgproto.py
@@ -357,14 +357,17 @@ class MessageParser:
                 start_value, count = value
                 for i in range(count):
                     enums[enum_root + str(start_enum + i)] = start_value + i
-    def _init_messages(self, messages, command_ids=[], output_ids=[]):
-        for msgformat, msgid in messages.items():
+    def _init_messages(self, messages, command_tags=[], output_tags=[]):
+        for msgformat, msgtag in messages.items():
             msgtype = 'response'
-            if msgid in command_ids:
+            if msgtag in command_tags:
                 msgtype = 'command'
-            elif msgid in output_ids:
+            elif msgtag in output_tags:
                 msgtype = 'output'
-            self.messages.append((msgid, msgtype, msgformat))
+            self.messages.append((msgtag, msgtype, msgformat))
+            if msgtag < -32 or msgtag > 95:
+                raise error("Multi-byte msgtag not supported")
+            msgid = msgtag & 0x7f
             if msgtype == 'output':
                 self.messages_by_id[msgid] = OutputFormat(msgid, msgformat)
             else:
diff --git a/klippy/stepper.py b/klippy/stepper.py
index 1b1ad7ad0..8ba0b5f17 100644
--- a/klippy/stepper.py
+++ b/klippy/stepper.py
@@ -1,6 +1,6 @@
 # Printer stepper support
 #
-# Copyright (C) 2016-2019  Kevin O'Connor <kevin@koconnor.net>
+# Copyright (C) 2016-2021  Kevin O'Connor <kevin@koconnor.net>
 #
 # This file may be distributed under the terms of the GNU GPLv3 license.
 import math, logging, collections
@@ -33,7 +33,7 @@ class MCU_stepper:
         self._invert_dir = dir_pin_params['invert']
         self._mcu_position_offset = self._tag_position = 0.
         self._min_stop_interval = 0.
-        self._reset_cmd_id = self._get_position_cmd = None
+        self._reset_cmd_tag = self._get_position_cmd = None
         self._active_callbacks = []
         ffi_main, self._ffi_lib = chelper.get_ffi()
         self._stepqueue = ffi_main.gc(self._ffi_lib.stepcompress_alloc(oid),
@@ -78,18 +78,18 @@ class MCU_stepper:
                 self._invert_step))
         self._mcu.add_config_cmd("reset_step_clock oid=%d clock=0"
                                  % (self._oid,), on_restart=True)
-        step_cmd_id = self._mcu.lookup_command_id(
+        step_cmd_tag = self._mcu.lookup_command_tag(
             "queue_step oid=%c interval=%u count=%hu add=%hi")
-        dir_cmd_id = self._mcu.lookup_command_id(
+        dir_cmd_tag = self._mcu.lookup_command_tag(
             "set_next_step_dir oid=%c dir=%c")
-        self._reset_cmd_id = self._mcu.lookup_command_id(
+        self._reset_cmd_tag = self._mcu.lookup_command_tag(
             "reset_step_clock oid=%c clock=%u")
         self._get_position_cmd = self._mcu.lookup_query_command(
             "stepper_get_position oid=%c",
             "stepper_position oid=%c pos=%i", oid=self._oid)
         self._ffi_lib.stepcompress_fill(
             self._stepqueue, self._mcu.seconds_to_clock(max_error),
-            self._invert_dir, step_cmd_id, dir_cmd_id)
+            self._invert_dir, step_cmd_tag, dir_cmd_tag)
     def get_oid(self):
         return self._oid
     def get_step_dist(self):
@@ -132,9 +132,9 @@ class MCU_stepper:
         ret = self._ffi_lib.stepcompress_reset(self._stepqueue, 0)
         if ret:
             raise error("Internal error in stepcompress")
-        data = (self._reset_cmd_id, self._oid, 0)
-        ret = self._ffi_lib.stepcompress_queue_msg(
-            self._stepqueue, data, len(data))
+        data = (self._reset_cmd_tag, self._oid, 0)
+        ret = self._ffi_lib.stepcompress_queue_msg(self._stepqueue, data,
+                                                   len(data))
         if ret:
             raise error("Internal error in stepcompress")
         if not did_trigger or self._mcu.is_fileoutput():
diff --git a/scripts/buildcommands.py b/scripts/buildcommands.py
index 9e811485b..d41cab0fa 100644
--- a/scripts/buildcommands.py
+++ b/scripts/buildcommands.py
@@ -286,22 +286,25 @@ class HandleCommandGeneration:
             if msg not in self.msg_to_id:
                 msgid += 1
                 self.msg_to_id[msg] = msgid
-        if msgid >= 96:
+        if msgid >= 128:
             # The mcu currently assumes all message ids encode to one byte
             error("Too many message ids")
     def update_data_dictionary(self, data):
-        command_ids = [self.msg_to_id[msg]
-                       for msgname, msg in self.messages_by_name.items()
-                       if msgname in self.commands]
-        response_ids = [self.msg_to_id[msg]
+        # Handle message ids over 96 (they are decoded as negative numbers)
+        msg_to_tag = {msg: msgid if msgid < 96 else msgid - 128
+                      for msg, msgid in self.msg_to_id.items()}
+        command_tags = [msg_to_tag[msg]
                         for msgname, msg in self.messages_by_name.items()
-                        if msgname not in self.commands]
-        data['commands'] = { msg: msgid for msg, msgid in self.msg_to_id.items()
-                             if msgid in command_ids }
-        data['responses'] = {msg: msgid for msg, msgid in self.msg_to_id.items()
-                             if msgid in response_ids }
-        output = { msg: msgid for msg, msgid in self.msg_to_id.items()
-                   if msgid not in command_ids and msgid not in response_ids }
+                        if msgname in self.commands]
+        response_tags = [msg_to_tag[msg]
+                         for msgname, msg in self.messages_by_name.items()
+                         if msgname not in self.commands]
+        data['commands'] = { msg: msgtag for msg, msgtag in msg_to_tag.items()
+                             if msgtag in command_tags }
+        data['responses'] = { msg: msgtag for msg, msgtag in msg_to_tag.items()
+                              if msgtag in response_tags }
+        output = {msg: msgtag for msg, msgtag in msg_to_tag.items()
+                  if msgtag not in command_tags and msgtag not in response_tags}
         if output:
             data['output'] = output
     def build_parser(self, msgid, msgformat, msgtype):