diff --git a/xtrn/mrc/mrc-client.js b/xtrn/mrc/mrc-client.js
index 8a3bc1e91f31620877b998f47b2ff552be033853..d38a91c9c8a5fed0aaa448171074082b7ccb395b 100644
--- a/xtrn/mrc/mrc-client.js
+++ b/xtrn/mrc/mrc-client.js
@@ -48,6 +48,7 @@ js.on_exit("js.counter = 0");
 js.time_limit=0;
 
 var input_state = 'chat';
+var paused_msg_buffer = []; 
 var show_nicks = false;
 var stat_mode = 0; // 0 = Local Session stats (Chatters, Latency, & Mentions); 1 = Global MRC Stats
 
@@ -68,6 +69,7 @@ if (!f.open('r')) {
 	alert("Error " + f.error + " (" + strerror(f.error) + ") opening " + f.name);
 	exit(1);
 }
+
 const settings = {
     root: f.iniGetObject(),
     startup: f.iniGetObject('startup'),
@@ -140,7 +142,7 @@ function init_display(msg_color) {
     f.nicklist = new Frame(w - 2, 2, 2, h - 3, BG_BLACK|LIGHTGRAY, f.top);
     f.nicks = new Frame(w - 1, 2, 1, h - 3, BG_BLACK|LIGHTGRAY, f.nicklist);
     f.input_color = new Frame(1, h, 1, 1, BG_BLACK|LIGHTGRAY, f.top);
-    f.input = new Frame(2, h, w-2, 1, BG_BLACK|WHITE, f.top); // TODO: Test
+    f.input = new Frame(2, h, w-2, 1, BG_BLACK|WHITE, f.top);
     f.output_scroll = new ScrollBar(f.output, { autohide: true });
     f.nick_scroll = new ScrollBar(f.nicks, { autohide: true });
     f.output.word_wrap = true;
@@ -180,7 +182,15 @@ function refresh_stats(frames, session) {
     }
 }
 
-function append_message(frames, msg, mention) {
+function append_message(frames, msg, mention, when) {
+    
+    if (input_state !== "chat") {                     // pause incoming messages while scrolling.
+        paused_msg_buffer.push({"msg": msg,           // we'll capture any incoming messages in the meantime
+                                "mention": mention,   // and display them when done scrolling.
+                                "when": new Date()});
+        return;                                       
+    }
+    
     const top = frames.output.offset.y;
     if (frames.output.data_height > frames.output.height) {
         while (frames.output.down()) {
@@ -196,7 +206,7 @@ function append_message(frames, msg, mention) {
     }
 
     frames.output.putmsg(
-        (mention ? "\x01k\x017" : "\x01k\x01h") + getShortTime(new Date()) + // timestamp formatting
+        (mention ? "\x01k\x017" : "\x01k\x01h") + getShortTime(when || new Date()) + // timestamp formatting
         (mention ? ( "\x01n\x01r\x01h\x01i" + MENTION_MARKER ) : " ") +      // mention formatting
         "\x01n" + msg + '\r\n'                                               // message itself
     );
@@ -428,7 +438,7 @@ function main() {
                     console.beep();
                     mention = true;
                     session.mention_count = session.mention_count + 1;
-                    refresh_stats (frames, session);
+                    refresh_stats(frames, session);
                 }
                 display_message(frames, msg, mention);
             } /*else {
@@ -456,6 +466,9 @@ function main() {
     session.on('latency', function () {
         refresh_stats(frames, session);
     });
+    session.on('ctcp-msg', function (msg) {
+        display_server_message(frames, pipeToCtrlA( msg ) );
+    });
 
     if (settings.startup.splash) display_external_text(frames, "splash");
     if (settings.startup.motd) session.motd();
@@ -478,14 +491,14 @@ function main() {
     var cmd, line, user_input;
     var lastnodechk = time();
     while (!js.terminated && !break_loop) {
-	if ((time() - lastnodechk) >= 10) {
-		// Check the node "interrupt flag" once every 10 seconds
-		if (system.node_list[bbs.node_num - 1].misc & NODE_INTR) {
-			bbs.nodesync(); // this will display a message to to the user and disconnect
-			break;
-		}
-		lastnodechk = time();
-	}
+        if ((time() - lastnodechk) >= 10) {
+            // Check the node "interrupt flag" once every 10 seconds
+            if (system.node_list[bbs.node_num - 1].misc & NODE_INTR) {
+                bbs.nodesync(); // this will display a message to to the user and disconnect
+                break;
+            }
+            lastnodechk = time();
+        }
         session.cycle();
         if (input_state == 'chat') {
             frames.divider.gotoxy(frames.divider.width - 16, 1);
@@ -611,6 +624,9 @@ function main() {
                                 manage_twits( cmd[1].toLowerCase(), cmd.slice(2).join(' ').toLowerCase(), session.twit_list, frames );
                             }
                             break;
+                        case "?": // shortcut for "help", because why not?
+                            session.send_command("help");
+                            break;
                         default:
                             if (typeof session[cmd[0]] == 'function') {
                                 session[cmd[0]](cmd.slice(1).join(' '));
@@ -673,6 +689,12 @@ function main() {
                     input_state = 'chat';
                     session.mention_count = 0;
                     refresh_stats(frames, session);
+                    if (paused_msg_buffer.length > 0) {
+                        for (var pmb in paused_msg_buffer) {
+                            append_message(frames, paused_msg_buffer[pmb].msg, paused_msg_buffer[pmb].mention, paused_msg_buffer[pmb].when);
+                        }
+                        paused_msg_buffer = [];
+                    }
                 }
             } else if (input_state == 'scroll' || input_state == 'scroll_nicks') {
                 var sframe = input_state == 'scroll' ? frames.output : frames.nicks;
@@ -697,6 +719,12 @@ function main() {
                     frames.output_scroll.cycle();
                     input_state = 'chat';
                     refresh_stats(frames, session);
+                    if (paused_msg_buffer.length > 0) {
+                        for (var pmb in paused_msg_buffer) {
+                            append_message(frames, paused_msg_buffer[pmb].msg, paused_msg_buffer[pmb].mention, paused_msg_buffer[pmb].when);
+                        }
+                        paused_msg_buffer = [];
+                    }
                 }
             }
         }
diff --git a/xtrn/mrc/mrc-connector.js b/xtrn/mrc/mrc-connector.js
index 380c1cbf54735b39dc1095ed18d330413a23dcec..8eafd7c6e81d861b1a4d084910baeab651641c99 100644
--- a/xtrn/mrc/mrc-connector.js
+++ b/xtrn/mrc/mrc-connector.js
@@ -28,7 +28,7 @@ f = undefined;
 if (!settings.ssl)
 	settings.ssl=false;
 
-const PROTOCOL_VERSION = '1.3.2';
+const PROTOCOL_VERSION = '1.3.3';
 const MAX_LINE = 512;
 const FROM_SITE = system.name.replace(/ /g, "_");
 const SYSTEM_NAME = system_info.system_name || system.name;
diff --git a/xtrn/mrc/mrc-help-ctcp.msg b/xtrn/mrc/mrc-help-ctcp.msg
new file mode 100644
index 0000000000000000000000000000000000000000..9e8a2285fea229711dcbdbc292d966b9004722a4
--- /dev/null
+++ b/xtrn/mrc/mrc-help-ctcp.msg
@@ -0,0 +1,14 @@
+NBH__[ HWHow to use CTCP commandsN: BH]_______________________________
+
+N CTCP (Client-to-Client Protocol) commands are special messages
+N that can be sent to a channel or other clients.
+
+N Usage:
+   WH/Cctcp NHtarget HBcommand K
+
+N A Htarget Nis a user or room name. HB* Ntargets all.
+
+N Supported commands: 
+HB   VERSION  TIME  PING  CLIENTINFO
+
+N Type WH/Ctoggle_ctcp Nto hide/show incoming requests.
\ No newline at end of file
diff --git a/xtrn/mrc/mrc-help-main.msg b/xtrn/mrc/mrc-help-main.msg
index 2b82190c7bb9f130bca968c50d0d3d41a6fc1faa..a263fdbedeb87bcec7780a6b589df19f147d14aa 100644
--- a/xtrn/mrc/mrc-help-main.msg
+++ b/xtrn/mrc/mrc-help-main.msg
@@ -1,21 +1,21 @@
-NB__[ HWList of available commandsN: B]_____________________________
+NBH__[ HWList of available commandsN: BH]_____________________________
 WH/CinfoN�HK:N View information about a BBSHK:N H/CinfoN H#
-WH/Ct Kor W/Cmsg�K:N Send a direct messageHK:N�H/CtN HnickN HBmessage
+WH/Cmsg Kor W/Ct�K:N Send a direct messageHK:N�H/CtN HnickN HBmessage
 WH/CrN�HK:N Reply to last direct messageHK:N H/CrB message
-WH/CjoinN�HK:N Move to a new roomHK:W�/CjoinN Hroom_name
-WH/CtopicN�HK:N Change room topic:�H/CtopicW topic
+WH/Cjoin Kor W/CjN HK    :N Join a new roomHK:W�   /CjN Hroom_name
+WH/CtopicN�HK:N Change room topicHK:�HW/CtopicW topic
+WH/CmeN            HK:N Perform an actionHK:            HW/Cme NHwaves
 WH/CroomsN�HK:N List available rooms
 WH/CusersN�HK:N List users
 WH/CwhoonN�HK:N List users and BBSes
 WH/CmotdN�HK:N Display the Message of the Day
 WH/Cscroll Kor CPGUPK:N Scroll the chat window
 WH/Cmentions Kor CUPK:N Reset mention counter and review mentions
-WH/Cscroll_nicksN  HK:N Scroll the nicklist
-WH/Ctoggle_nicksN  HK:N Show/hide the nicklist
-WH/Ctoggle_statsN  HK:N Toggle MRC stats view
 WH/CthemeN�HK:N Select a theme HK..........N seeHK:N H/ChelpN Htheme
 WH/CtwitN�HK:N Twit List management HK....N seeHK:N H/ChelpN Htwit
-WH/CquitN�HK:N Exit the program
+WH/CctcpN�HK:N Send a CTCP command HK.....N seeHK:N H/ChelpN Hctcp
+WH/Cquit Kor W/CqN HK    :N Exit the program
 WHCLEFTK/CRIGHTW�K:N Change your text color HK..N seeHK:N H/ChelpN Hnick
 WHCCTRLK+CDN�HK:N Clear the input line
-NFor a list of additional HSERVERN commands, see H/CquoteN Hhelp
\ No newline at end of file
+WH/ChelpN�HK:N Shows this help list HK....N seeHK:N H/ChelpN Hmore
+NFor a list of additional HSERVERN commands, see H/CquoteN Hhelp Nor H/C?
\ No newline at end of file
diff --git a/xtrn/mrc/mrc-help-more.msg b/xtrn/mrc/mrc-help-more.msg
new file mode 100644
index 0000000000000000000000000000000000000000..5f82e4becf2fd5aeff45b6bf3fdfe88d01f00f3a
--- /dev/null
+++ b/xtrn/mrc/mrc-help-more.msg
@@ -0,0 +1,4 @@
+NBH__[ HWList of additional commandsN: BH]____________________________
+WH/Cscroll_nicksN  HK:N Scroll the nicklist
+WH/Ctoggle_nicksN  HK:N Show/hide the nicklist
+WH/Ctoggle_statsN  HK:N Toggle MRC stats view
diff --git a/xtrn/mrc/mrc-help-twit.msg b/xtrn/mrc/mrc-help-twit.msg
index b2ba22b3731e7a7c5b1c97bc6bd7e3d262812aea..ba3b5d70df6c4895fb98316604da8f16199f813c 100644
--- a/xtrn/mrc/mrc-help-twit.msg
+++ b/xtrn/mrc/mrc-help-twit.msg
@@ -1,10 +1,9 @@
-NB__[ HWHow to use the twit listN: B]_______________________________
+NBH__[ HWHow to use the twit listN: B]H_______________________________
 
-N Basic twit list implemented! Hides messages from problematic 
-N chatters.
+N The Htwit list Nhides messages from problematic chatters.
 
 N Usage:
    WH/Ctwit NHadd HBnameoftwit K:W NFilters out nameoftwit's messages
    WH/Ctwit NHdel HBnameoftwit K:W Nnameoftwit's messages are re-allowed
-   WH/Ctwit NHlist           K:W NLists twits the user has added
-   WH/Ctwit NHclear          K:W NEmpty's the user's twit list
+   WH/Ctwit NHlist           K:W NLists twits you have added
+   WH/Ctwit NHclear          K:W NEmpties your twit list
diff --git a/xtrn/mrc/mrc-session.js b/xtrn/mrc/mrc-session.js
index ca6e5e351681fd014a2bcfd317885d7ca26ff07d..049d2d1cd3a7006c9c6bbda160214614100e7358 100644
--- a/xtrn/mrc/mrc-session.js
+++ b/xtrn/mrc/mrc-session.js
@@ -3,7 +3,10 @@
 // Passes traffic between an mrc-connector.js server and a client application
 // See mrc-client.js for a bad example.
 function MRC_Session(host, port, user, pass, alias) {
-
+    
+    const MRC_VER = "Multi Relay Chat JS v1.3.3 2025-04-11 [cf]";
+    const CTCP_ROOM = "ctcp_echo_channel";
+    
     const handle = new Socket();
 
     const state = {
@@ -19,7 +22,8 @@ function MRC_Session(host, port, user, pass, alias) {
         latency: '-',
         msg_color: 7,
         twit_list: [],
-        last_private_msg_from: ""
+        last_private_msg_from: "",
+        show_ctcp_req: true
     };
 
     const callbacks = {
@@ -50,6 +54,63 @@ function MRC_Session(host, port, user, pass, alias) {
             send(to_user, '', to_room, state.alias + ' ' + format("|%02d", state.msg_color) + body);
         }
     }
+    
+    function send_ctcp(to, p, s) {       
+        state.output_buffer.push({
+            from_room: CTCP_ROOM,
+            to_user: to,
+            to_site: "",
+            to_room: CTCP_ROOM,
+            body: p + " " + user + " " + s
+        });        
+        mswait(20);
+    }
+    
+    function ctcp_time(d) {
+        return format("%02d/%02d/%02d %02d:%02d", d.getMonth()+1, d.getDate(), d.getFullYear().toString().substr(-2), d.getHours(), d.getMinutes());        
+    }
+    
+    function ctcp_reply(cmd) {
+        // Future ctcp commands can be added easily by adding another
+        // string to this array, and then adding the response logic 
+        // to the switch/case structure below.
+        const CTCP_CMDS = [
+            /* 0 */ "VERSION",    
+            /* 1 */ "TIME", 
+            /* 2 */ "PING", 
+            /* 3 */ "CLIENTINFO"
+        ];
+        var reply = "";
+        switch (CTCP_CMDS.indexOf(cmd)) {
+            case 0:
+                reply = MRC_VER;
+                break;
+            case 1:
+                reply = ctcp_time(new Date());
+                break;
+            case 2:
+                // It's not clear what's supposed to be happening here.
+                // In the mystic client, PING returns the message string minus
+                // the sum of the length of the substrings within the string...
+                // which is an empty string... i.e.: no response
+                //
+                // According to https://en.wikipedia.org/wiki/Client-to-client_protocol#PING,
+                // it's /supposed/ to be the latency between two clients, 
+                // not taking the server into account. Such communication 
+                // within MRC does not presently exist.
+                //
+                // So for now, we're just going to return a "PONG" in response.
+                reply = "PONG";
+                break;
+            case 3:
+                reply = CTCP_CMDS.join(" ");
+                break;    
+            default:
+                reply = "Unsupported ctcp command";
+                break;                    
+        }
+        return reply.trim();
+    }
 
     function emit() {
         if (!Array.isArray(callbacks[arguments[0]])) return;
@@ -129,6 +190,24 @@ function MRC_Session(host, port, user, pass, alias) {
             }*/
             emit('message', msg);
         }
+        if (msg.to_room === CTCP_ROOM) {
+                        
+            const ctcp_data = msg.body.split(' ');
+            if (ctcp_data[0] === "[CTCP]" && ctcp_data.length >= 4) {
+                
+                if (state.show_ctcp_req) {
+                    emit('ctcp-msg', '* |14[CTCP-REQUEST] |15' + ctcp_data[3] + ' |07on |15' + ctcp_data[2] + ' |07from |10' + msg.from_user);
+                }
+                if (ctcp_data[2] === "*" || ctcp_data[2].toUpperCase()===user.toUpperCase() || ctcp_data[2].toUpperCase()==="#"+state.room.toUpperCase() ) {
+                    send_ctcp(ctcp_data[1], "[CTCP-REPLY]", ctcp_data[3].toUpperCase() + " " + ctcp_reply(ctcp_data[3].toUpperCase()) );
+                }
+                
+            } else if (ctcp_data[0] === "[CTCP-REPLY]" && ctcp_data.length >= 3) {
+                if (msg.to_user.toUpperCase()===user.toUpperCase()) {
+                    emit('ctcp-msg', '* |14[CTCP-REPLY] |10' + ctcp_data[1] + ' |15' + ctcp_data.slice(2).join(' ').trim());
+                }
+            }
+        }        
     }
 
     this.send_room_message = function (msg) {
@@ -138,6 +217,10 @@ function MRC_Session(host, port, user, pass, alias) {
     this.send_notme = function (msg) {
         send("NOTME", "", "", msg);
     }
+    
+    this.send_action = function (msg) {
+        send("", "", state.room, msg);
+    }
 
     this.send_private_messsage = function (user, msg) {
         msg = '|11(|03Private|11)|07 ' + msg;
@@ -243,6 +326,17 @@ function MRC_Session(host, port, user, pass, alias) {
                 this.send_command('USERLIST', 'ALL');
             }
         },
+        j: { // shorthand for join 
+            // TODO: is there an easier way to duplicate command functionality?
+            //help: 'Move to a new room: /join room_name',
+            callback: function (str) { // validate valid room name?
+                str = str.replace(/^#/, '');                
+                this.send_command(format('NEWROOM:%s:%s', state.room, str));
+                state.room = str;
+                state.nicks = [];
+                this.send_command('USERLIST', 'ALL');
+            }
+        },
         motd: {
             //help: 'Display the Message of the Day'
         },
@@ -255,7 +349,8 @@ function MRC_Session(host, port, user, pass, alias) {
                 }
             }
         },
-        t: {
+        t: { // shorthand for msg
+            // TODO: is there an easier way to duplicate command functionality?
             //help: 'Send a private message: /t nick message goes here',
             callback: function (str) {
                 const cmd = str.split(' ');
@@ -263,6 +358,7 @@ function MRC_Session(host, port, user, pass, alias) {
                     this.send_private_messsage(cmd[0], cmd.slice(1).join(' '));
                 }
             }
+            
         },
         r: {
             //help: 'Reply to last private message: /r message goes here',
@@ -285,6 +381,14 @@ function MRC_Session(host, port, user, pass, alias) {
                 handle.close();
             }
         },
+        q: { // shorthand for quit
+            // TODO: is there an easier way to duplicate command functionality?
+            //help: 'Quit the program',
+            callback: function () {
+                emit('disconnect');
+                handle.close();
+            }
+        },
         rooms: {
             //help: 'List available rooms',
             command: 'LIST'
@@ -298,7 +402,32 @@ function MRC_Session(host, port, user, pass, alias) {
             callback: function (str) {
                 this.send_command(format('NEWTOPIC:%s:%s', state.room, str));
             }
-        }          
+        },
+        me: {
+            //me: 'Send an action to the server',
+            callback: function (str) {
+                this.send_action('|15* |13' + user + ' ' + str);
+            }
+        },
+        ctcp: {
+            // outgoing ctcp request            
+            callback: function (str) {                
+                const cmd = str.split(' ');
+                var u = cmd[0];
+                if (u) {
+                    if (u === "*" || u.indexOf("#") === 0) {
+                        u = "";
+                    }
+                    send_ctcp(u, '[CTCP]', str.trim().toUpperCase());
+                } 
+            }     
+        },
+        toggle_ctcp: {
+            callback: function() {
+                state.show_ctcp_req = !state.show_ctcp_req;
+                emit('ctcp-msg', '* |14Incoming CTCP requests are now ' + (state.show_ctcp_req ? "|15shown" : "|12hidden") + '.');
+            }
+        }
     };
 
     Object.keys(commands).forEach(function (e) {