MUD technology hasn't really changed in the last 20 years. The Internet hasn't really changed (it's a little faster, but ... text!) and you don't need a GPU (again ... text!)
The typical MUD server loop looks like:
-
Read and buffer incoming data from all players that have any
-
Parse any complete line of commands from any player and queue events for whatever those commands are supposed to do
-
Run all events in your event queue until the next event to run is queued for sometime in the future, or the queue is empty
Part of "run events" will be actions that modify your world state, as well as generates observable text, which will queue outgoing "this is what happens" messages to the players. You don't want to have one player fail to read their socket and block the entire server, so use non-blocking mode when writing to each socket, and if the socket can't receive the data it needs, declare the player lost and disconnect them.
That's really all there's to it. You can do this in Python with no sweat. Or some typed language -- C# works fine, too.
A thing you might want to do these days is provide a web interface to the MUD. Typically, this will be done on top of websockets, or perhaps long polling. However, you can set up that service totally separately from the game itself, as a separate port/server/process. (Node.js makes building proxies like that very easy, although I hear there are good websocket libraries for C#, Java, Haskell, Python, and other languages now.)
For the basics of a game server loop in a few dozen lines of code of Python, you can check out this blog post.