Simple Command system
The biggest issue I see is you have to manually register each and every command. Why bother? Why not just get a list of all commands that implement ICommand through Reflection? Speed shouldn't be a huge problem because I assume you do this on some sort of start-up.

var baseCommandInterface = typeof(ICommand);
var commands = AppDomain.CurrentDomain.GetAssemblies()
    .SelectMany(s =&gt; s.GetTypes())
    .Where(p =&gt; baseCommandInterface.IsAssignableFrom(p));


Now just enumerate commands and cast and operate on them. Life should be a lot easier this way.



Next, why bother with a bool and int for cool downs? Just use an int?, if it's null then there's no cool down, if its &gt;0 then that's the cool down time. Pretty simple.



You use the term Command way too many times for my tastes.

internal interface ICommand
{
    void Process(Player player);
    string PermissionRequired { get; }
    int NumberOfParameters { get; }
    string Parameters { get; }
    string Description { get; }
    int? CooldownMs { get; }
}


Even then, NumberOfParameters is truly unnecessary. I'd bet there is a separator of some sort between commands in the Parameters string, which really shouldn't be a string, but should be something like the following:

public class CommandParameter
{
    public string Name { get; set; }
    public Type Type { get; set; }
    public string Description { get; set; }
}


Then Parameters becomes an IEnumerable&lt;CommandParameter&gt; which means you can use Parameters.Count in place of NumberOfParameters. This also means you can extrapolate further and define an abstract class for this, and add a CanProcess(Player player) method:

public abstract class BaseCommand : ICommand
{
    private List&lt;CommandParameter&gt; _parameters = new List&lt;CommandParameter&gt;();
    public IEnumerable&lt;CommandParameter&gt; Parameters =&gt; _parameters;

    public int? Cooldown { get; protected set; }
    public string Description { get; protected set; }
    public string PermissionRequired { get; protected set; }

    public abstract void ProcessCommand(Player player);

    public bool CanProcess(Player player)
    {
        // Check cool down, permissions, ban list, etc.
    }
}


Then in an actual command:

public class InfoCommand : BaseCommand
{
    public InfoCommand()
    {
        Cooldown = 5000;
        Description = "Lets you view information about the server.";
    }

    public override void ProcessCommand(Player player)
    {
        if (!CanProcess(player))
        {
            // I'm throwing an exception, but you could just `return` and send a message or whatever
            throw new InvalidOperationException("You have either exceeded the cool down, do not have permissions, or are banned. Please try again later.");
        }

        // Process our command here
    }
}


Then you created your own system that requires boilerplate code, and extracted that boilerplate code away.