encrypt_message
Encrypt or decrypt a message using a historically accurate Enigma machine emulator with configurable rotors, reflector, and plugboard settings. Specify the machine model and rotor order to match historical operation.
Instructions
Encrypt or decrypt a message using a specified Enigma machine configuration.
Args:
machine_model: Exact machine model name. MUST be one of: 'M3', 'M4', 'I', 'I_Norway', 'I_Sondermaschine', 'K', 'K_Swiss', 'D', 'Z', 'B_A133'. Do not add 'Enigma' prefix.
Supported models and their explicitly required reflectors:
- 'M3', 'I': UKWA, UKWB, UKWC
- 'M4': UKWBThin, UKWCThin
- 'I_Norway': UKW_EnigmaINorway
- 'I_Sondermaschine': UKW_EnigmaISonder
- 'K', 'K_Swiss', 'D': UKW_EnigmaCommercial
- 'Z': UKW_EnigmaZ
- 'B_A133': UKW_EnigmaB_A133
message: The plaintext or ciphertext to process.
- For Enigma Z: MUST contain ONLY digits (1234567890).
- For Enigma B_A133: MUST contain ONLY Swedish letters (abcdefghijklmnopqrstuvxyzåäö). Note: 'w' is strictly forbidden.
- For all other machines: MUST contain ONLY standard letters (A-Z).
- Spaces, punctuation, and special characters are strictly forbidden in all machines.
rotors: List of RotorConfig objects. MUST be ordered exactly as: [Fastest/Rightmost, Middle, Slowest/Leftmost, Greek (if M4)].
reflector: The ReflectorConfig object.
plugboard_pairs: Optional dict for plugboard connections (e.g. {"A": "B", "C": "D"}). Ignored if the machine has no plugboard.Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| machine_model | Yes | ||
| message | Yes | ||
| rotors | Yes | ||
| reflector | Yes | ||
| plugboard_pairs | No |
Implementation Reference
- src/enigmapython_mcp/server.py:102-280 (handler)The @mcp.tool() decorated function encrypt_message that implements the core Enigma encryption/decryption logic. It accepts machine_model, message, rotors, reflector, and optional plugboard_pairs. It dynamically loads the appropriate Enigma machine class, ETW, rotors, reflector, and plugboard from the enigmapython library, then processes the message using the configured machine.
@mcp.tool() def encrypt_message( machine_model: str, message: Union[str, int], rotors: List[RotorConfig], reflector: ReflectorConfig, plugboard_pairs: Dict[str, str] = None ) -> str: """ Encrypt or decrypt a message using a specified Enigma machine configuration. Args: machine_model: Exact machine model name. MUST be one of: 'M3', 'M4', 'I', 'I_Norway', 'I_Sondermaschine', 'K', 'K_Swiss', 'D', 'Z', 'B_A133'. Do not add 'Enigma' prefix. Supported models and their explicitly required reflectors: - 'M3', 'I': UKWA, UKWB, UKWC - 'M4': UKWBThin, UKWCThin - 'I_Norway': UKW_EnigmaINorway - 'I_Sondermaschine': UKW_EnigmaISonder - 'K', 'K_Swiss', 'D': UKW_EnigmaCommercial - 'Z': UKW_EnigmaZ - 'B_A133': UKW_EnigmaB_A133 message: The plaintext or ciphertext to process. - For Enigma Z: MUST contain ONLY digits (1234567890). - For Enigma B_A133: MUST contain ONLY Swedish letters (abcdefghijklmnopqrstuvxyzåäö). Note: 'w' is strictly forbidden. - For all other machines: MUST contain ONLY standard letters (A-Z). - Spaces, punctuation, and special characters are strictly forbidden in all machines. rotors: List of RotorConfig objects. MUST be ordered exactly as: [Fastest/Rightmost, Middle, Slowest/Leftmost, Greek (if M4)]. reflector: The ReflectorConfig object. plugboard_pairs: Optional dict for plugboard connections (e.g. {"A": "B", "C": "D"}). Ignored if the machine has no plugboard. """ # Map user input flexibly to known models (e.g. "Enigma I-Norway" -> "I_Norway") model_key_map = {k.lower().replace("_", "").replace("-", "").replace(" ", ""): k for k in MODELS_CONFIG.keys()} mod_clean = machine_model.lower().replace("enigma", "").replace("_", "").replace("-", "").replace(" ", "") if mod_clean not in model_key_map: raise ValueError(f"Unsupported machine model: {machine_model}") actual_model = model_key_map[mod_clean] config = MODELS_CONFIG[actual_model] prefix = config["cls"] # 1. Initialize ETW etw_cls = get_class(config["etw"]) etw = etw_cls() # 2. Initialize Reflector # Sanitize common LLM formatting mistakes (e.g. "B-Thin Reflector" -> "BTHIN") ref_raw = reflector.reflector_type ref_clean = ref_raw.upper().replace("REFLECTOR", "").replace("UKW", "").replace("-", "").replace(" ", "").replace("_", "") # Map cleaned variations to actual class names KNOWN_REFLECTORS = [ "UKWA", "UKWB", "UKWC", "UKWBThin", "UKWCThin", "UKW_EnigmaCommercial", "UKW_EnigmaINorway", "UKW_EnigmaISonder", "UKW_EnigmaB_A133" ] ref_key_map = {k.upper().replace("UKW", "").replace("_", "").replace("-", "").replace(" ", ""): k for k in KNOWN_REFLECTORS} if ref_clean in ref_key_map: ref_type = ref_key_map[ref_clean] else: ref_type = ref_raw # Fallback to exactly what the user passed reflector_cls_name = f"Reflector{ref_type}" try: reflector_cls = get_class(reflector_cls_name) except ModuleNotFoundError: try: # Maybe it's a specific reflector like ReflectorUKW_EnigmaCommercial reflector_cls = get_class(reflector.reflector_type) except ModuleNotFoundError: raise ValueError(f"Reflector class not found: {reflector_cls_name} or {reflector.reflector_type}") # CRITICAL VALIDATION: Enigma Z is the only numeric machine. It will crash if given an A-Z reflector. if actual_model == "Z" and reflector_cls.__name__ != "ReflectorUKW_EnigmaZ": raise ValueError( "Enigma Z is a purely numeric machine (1-0). It will crash if connected to an alphabetic (A-Z) reflector. " "You MUST use reflector_type='UKW_EnigmaZ' when using the Enigma Z model." ) import inspect sig = inspect.signature(reflector_cls.__init__) kwargs = {} if 'position' in sig.parameters: pos = reflector.initial_position if isinstance(pos, str): if pos.isdigit(): pos = int(pos) else: pos = ord(pos.upper()) - 65 kwargs['position'] = pos if 'ring' in sig.parameters: kwargs['ring'] = reflector.ring_setting reflector_inst = reflector_cls(**kwargs) # 3. Initialize Rotors rotor_instances = [] for r_conf in rotors: # Sanitize rotor type casing and common prefixes (e.g. "Rotor-I" -> "I", "beta" -> "Beta") r_type = r_conf.rotor_type.upper().replace("ROTOR", "").replace("-", "").replace(" ", "").replace("_", "") if r_type == "BETA": r_type = "Beta" elif r_type == "GAMMA": r_type = "Gamma" r_cls_name = f"{prefix}Rotor{r_type}" try: r_cls = get_class(r_cls_name) except ModuleNotFoundError: raise ValueError(f"Rotor class not found: {r_cls_name}. Check valid rotors for {machine_model}.") pos = r_conf.initial_position if isinstance(pos, str): if pos.isdigit(): pos = int(pos) else: pos = ord(pos.upper()) - 65 ring = r_conf.ring_setting # Instantiate rotor with position and ring rotor_instances.append(r_cls(position=pos, ring=ring)) # 4. Initialize Plugboard (if applicable) plugboard_inst = None if config["has_plugboard"]: plugboard_cls = get_class("SwappablePlugboard") plugboard_inst = plugboard_cls() if plugboard_pairs: lower_pairs = {k.lower(): v.lower() for k, v in plugboard_pairs.items()} plugboard_inst.bulk_swap(lower_pairs) # 5. Build Enigma Machine machine_cls = get_class(config["cls"]) import inspect sig = inspect.signature(machine_cls.__init__) params = list(sig.parameters.keys())[1:] # skip 'self' kwargs = {} # Assign known components for p in params: if p == "plugboard": kwargs[p] = plugboard_inst elif p == "reflector": kwargs[p] = reflector_inst elif p == "etw": kwargs[p] = etw elif p == "auto_increment_rotors": kwargs[p] = config["default_auto_increment"] elif p.startswith("rotor"): try: idx = int(p.replace("rotor", "")) - 1 if 0 <= idx < len(rotor_instances): kwargs[p] = rotor_instances[idx] else: raise ValueError(f"Machine {machine_model} expects more rotors than provided.") except ValueError: pass # Not a numbered rotor # Instantiate machine = machine_cls(**kwargs) # 6. Process message # enigmapython typically uses input_string message_str = str(message) try: return machine.input_string(message_str) except ValueError as e: error_msg = str(e) if "not in list" in error_msg: # Give the LLM a highly actionable error so it can self-correct raise ValueError( f"Invalid character error: {error_msg}. " "Enigma machines DO NOT support spaces, punctuation, or literal quotes in the message. " "Please strip all unsupported characters and pass only the raw letters/numbers." ) raise - src/enigmapython_mcp/server.py:102-102 (registration)The @mcp.tool() decorator registers encrypt_message as an MCP tool on the FastMCP server instance named 'Enigma Server'.
@mcp.tool() - Pydantic schema definitions for RotorConfig and ReflectorConfig used as input types for the encrypt_message tool.
class RotorConfig(BaseModel): rotor_type: str = Field(description="Exact Rotor identifier. Valid options: 'I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'Beta', 'Gamma'.") ring_setting: int = Field(default=0, description="Ring setting (0-25).") initial_position: Union[int, str] = Field(default=0, description="Initial position (0-25 or A-Z).") class ReflectorConfig(BaseModel): reflector_type: str = Field(description="Exact Reflector identifier. Valid options: 'UKWA', 'UKWB', 'UKWC', 'UKWBThin', 'UKWCThin', 'UKW_EnigmaCommercial', 'UKW_EnigmaINorway', 'UKW_EnigmaISonder', 'UKW_EnigmaB_A133'.") ring_setting: int = Field(default=0, description="Ring setting (0-25). Only applicable for settable/rotating reflectors.") initial_position: Union[int, str] = Field(default=0, description="Initial position (0-25 or A-Z). Only applicable for rotating reflectors.") - src/enigmapython_mcp/server.py:18-23 (helper)The get_class helper function used by encrypt_message to dynamically import classes from the enigmapython package.
def get_class(module_name: str, class_name: str = None): """Dynamically loads a class from the enigmapython package.""" if class_name is None: class_name = module_name module = importlib.import_module(f"enigmapython.{module_name}") return getattr(module, class_name) - src/enigmapython_mcp/server.py:26-87 (helper)MODELS_CONFIG dictionary mapping machine model names to their class names, ETW types, and plugboard support - used by encrypt_message to configure the Enigma machine.
MODELS_CONFIG = { "M3": { "cls": "EnigmaM3", "etw": "EtwPassthrough", "has_plugboard": True, "default_auto_increment": True }, "M4": { "cls": "EnigmaM4", "etw": "EtwPassthrough", "has_plugboard": True, "default_auto_increment": True }, "I": { "cls": "EnigmaI", "etw": "EtwPassthrough", "has_plugboard": True, "default_auto_increment": True }, "I_Norway": { "cls": "EnigmaINorway", "etw": "EtwPassthrough", "has_plugboard": True, "default_auto_increment": True }, "I_Sondermaschine": { "cls": "EnigmaISonder", "etw": "EtwPassthrough", "has_plugboard": True, "default_auto_increment": True }, "K": { "cls": "EnigmaK", "etw": "EtwQWERTZ", "has_plugboard": False, "default_auto_increment": True }, "K_Swiss": { "cls": "EnigmaKSwiss", "etw": "EtwQWERTZ", "has_plugboard": False, "default_auto_increment": True }, "D": { "cls": "EnigmaD", "etw": "EtwQWERTZ", "has_plugboard": False, "default_auto_increment": True }, "Z": { "cls": "EnigmaZ", "etw": "EnigmaZEtw", "has_plugboard": False, "default_auto_increment": True }, "B_A133": { "cls": "EnigmaB_A133", "etw": "EnigmaB_A133Etw", "has_plugboard": False, "default_auto_increment": True } }