Skip to main content
Glama
dmarsters

ASCII Art & Unicode Diagrams MCP Server

by dmarsters
server.py14.9 kB
""" ASCII Art & Unicode Diagrams MCP Server Lushy Pattern 2 implementation for text-based visual generation. Uses categorical composition of aesthetic functors with zero rendering dependencies. Functors: - box_drawing: Border styles, line weights, corner treatments - ascii_shading: Character gradients, contrast, patterns - layout_composition: Canvas size, alignment, spatial organization """ from fastmcp import FastMCP from typing import Dict, Any, List, Optional, Literal from dataclasses import dataclass import math # Initialize MCP server mcp = FastMCP("ASCII Art Generator") # ============================================================================ # LAYER 1: Data Structures (Categorical Objects) # ============================================================================ @dataclass class BoxDrawingParams: """Box drawing aesthetic parameters""" line_style: Literal['light', 'heavy', 'double', 'rounded'] corner_type: Literal['sharp', 'rounded', 'beveled'] symmetry: Literal['bilateral', 'radial', 'asymmetric'] @dataclass class ShadingParams: """ASCII shading parameters""" palette_type: Literal['ascii_standard', 'blocks', 'dots', 'density', 'braille'] contrast: float # 0.0-1.0 direction: Literal['horizontal', 'vertical', 'radial', 'diagonal'] dither: bool @dataclass class LayoutParams: """Layout composition parameters""" width: int height: int horizontal_align: Literal['left', 'center', 'right'] vertical_align: Literal['top', 'middle', 'bottom'] padding: int # ============================================================================ # LAYER 2: Intentionality (Visual Vocabularies) # ============================================================================ UNICODE_SETS = { 'light': { 'horizontal': '─', 'vertical': '│', 'top_left': '┌', 'top_right': '┐', 'bottom_left': '└', 'bottom_right': '┘', 'cross': '┼', 't_down': '┬', 't_up': '┴', 't_left': '┤', 't_right': '├', 'intentionality': 'Clean, minimal, professional - optimal for documentation' }, 'heavy': { 'horizontal': '━', 'vertical': '┃', 'top_left': '┏', 'top_right': '┓', 'bottom_left': '┗', 'bottom_right': '┛', 'cross': '╋', 't_down': '┳', 't_up': '┻', 't_left': '┫', 't_right': '┣', 'intentionality': 'Bold, emphatic - draws attention and establishes hierarchy' }, 'double': { 'horizontal': '═', 'vertical': '║', 'top_left': '╔', 'top_right': '╗', 'bottom_left': '╚', 'bottom_right': '╝', 'cross': '╬', 't_down': '╦', 't_up': '╩', 't_left': '╣', 't_right': '╠', 'intentionality': 'Formal, structured - conveys authority and permanence' }, 'rounded': { 'horizontal': '─', 'vertical': '│', 'top_left': '╭', 'top_right': '╮', 'bottom_left': '╰', 'bottom_right': '╯', 'cross': '┼', 't_down': '┬', 't_up': '┴', 't_left': '┤', 't_right': '├', 'intentionality': 'Friendly, approachable - reduces visual tension' } } SHADING_PALETTES = { 'ascii_standard': { 'chars': ' .:-=+*#%@', 'texture': 'grainy', 'intentionality': 'Classic ASCII art - universally compatible' }, 'blocks': { 'chars': ' ░▒▓█', 'texture': 'smooth', 'intentionality': 'Smooth gradients - modern terminal aesthetics' }, 'dots': { 'chars': ' ·∘○●◉', 'texture': 'dotted', 'intentionality': 'Geometric precision - technical diagrams' }, 'density': { 'chars': ' .,;!lI$@', 'texture': 'dense', 'intentionality': 'High detail - complex shading' }, 'braille': { 'chars': '⠀⠁⠃⠇⠏⠟⠿⣿', 'texture': 'fine', 'intentionality': 'Ultra-fine detail - maximum resolution' } } # ============================================================================ # LAYER 3: Rendering Engine # ============================================================================ class ASCIIArtRenderer: """Deterministic ASCII art generation from categorical parameters""" def __init__( self, box_params: BoxDrawingParams, shading_params: ShadingParams, layout_params: LayoutParams ): self.box = box_params self.shading = shading_params self.layout = layout_params self.chars = UNICODE_SETS[box_params.line_style] self.palette = SHADING_PALETTES[shading_params.palette_type]['chars'] def render_bordered_box(self, title: Optional[str] = None) -> str: """Render a bordered box with optional shading""" lines = [] w = self.layout.width h = self.layout.height # Top border if title: title_display = f" {title} " title_len = len(title_display) left_line = self.chars['horizontal'] * ((w - 2 - title_len) // 2) right_line = self.chars['horizontal'] * ((w - 2 - title_len + 1) // 2) top = self.chars['top_left'] + left_line + title_display + right_line + self.chars['top_right'] else: top = self.chars['top_left'] + (self.chars['horizontal'] * (w - 2)) + self.chars['top_right'] lines.append(top) # Middle rows with shading for y in range(h - 2): row = self.chars['vertical'] for x in range(w - 2): # Calculate shading value based on direction shade_value = self._calculate_shade_value(x, y, w - 2, h - 2) # Apply contrast shade_value = self._apply_contrast(shade_value) # Map to character char_idx = int(shade_value * (len(self.palette) - 1)) char_idx = max(0, min(char_idx, len(self.palette) - 1)) row += self.palette[char_idx] row += self.chars['vertical'] lines.append(row) # Bottom border bottom = self.chars['bottom_left'] + (self.chars['horizontal'] * (w - 2)) + self.chars['bottom_right'] lines.append(bottom) return '\n'.join(lines) def _calculate_shade_value(self, x: int, y: int, width: int, height: int) -> float: """Calculate shading value 0.0-1.0 based on position and direction""" center_x = width / 2 center_y = height / 2 if self.shading.direction == 'horizontal': return x / width if width > 0 else 0 elif self.shading.direction == 'vertical': return y / height if height > 0 else 0 elif self.shading.direction == 'radial': dx = (x - center_x) / center_x if center_x > 0 else 0 dy = (y - center_y) / center_y if center_y > 0 else 0 dist = math.sqrt(dx * dx + dy * dy) return min(dist, 1.0) elif self.shading.direction == 'diagonal': return (x + y) / (width + height) if (width + height) > 0 else 0 else: return 0.5 def _apply_contrast(self, value: float) -> float: """Apply contrast adjustment to shade value""" # Apply contrast curve if self.shading.contrast < 0.5: # Reduce contrast - compress to middle range_compress = self.shading.contrast * 2 return 0.5 + (value - 0.5) * range_compress else: # Increase contrast - expand from middle range_expand = (self.shading.contrast - 0.5) * 2 + 1 if value < 0.5: return 0.5 - (0.5 - value) * range_expand else: return 0.5 + (value - 0.5) * range_expand return max(0.0, min(1.0, value)) def render_table(self, headers: List[str], rows: List[List[str]]) -> str: """Render a data table with borders""" # Calculate column widths col_widths = [len(h) for h in headers] for row in rows: for i, cell in enumerate(row): if i < len(col_widths): col_widths[i] = max(col_widths[i], len(str(cell))) lines = [] # Top border top = self.chars['top_left'] for i, width in enumerate(col_widths): top += self.chars['horizontal'] * (width + 2) if i < len(col_widths) - 1: top += self.chars['t_down'] top += self.chars['top_right'] lines.append(top) # Header row header_row = self.chars['vertical'] for i, header in enumerate(headers): header_row += f" {header.ljust(col_widths[i])} " if i < len(headers) - 1: header_row += self.chars['vertical'] header_row += self.chars['vertical'] lines.append(header_row) # Header separator sep = self.chars['t_right'] for i, width in enumerate(col_widths): sep += self.chars['horizontal'] * (width + 2) if i < len(col_widths) - 1: sep += self.chars['cross'] sep += self.chars['t_left'] lines.append(sep) # Data rows for row in rows: data_row = self.chars['vertical'] for i, cell in enumerate(row): if i < len(col_widths): data_row += f" {str(cell).ljust(col_widths[i])} " if i < len(row) - 1: data_row += self.chars['vertical'] data_row += self.chars['vertical'] lines.append(data_row) # Bottom border bottom = self.chars['bottom_left'] for i, width in enumerate(col_widths): bottom += self.chars['horizontal'] * (width + 2) if i < len(col_widths) - 1: bottom += self.chars['t_up'] bottom += self.chars['bottom_right'] lines.append(bottom) return '\n'.join(lines) # ============================================================================ # MCP TOOLS # ============================================================================ @mcp.tool() def create_ascii_box( width: int = 50, height: int = 15, title: str = "", line_style: Literal['light', 'heavy', 'double', 'rounded'] = 'light', shading_palette: Literal['ascii_standard', 'blocks', 'dots', 'density', 'braille'] = 'ascii_standard', shading_direction: Literal['horizontal', 'vertical', 'radial', 'diagonal'] = 'radial', contrast: float = 0.7 ) -> str: """ Create ASCII art box with border and optional shading. Args: width: Box width in characters (20-120) height: Box height in characters (5-50) title: Optional title text in top border line_style: Border style (light=minimal, heavy=bold, double=formal, rounded=friendly) shading_palette: Character set for shading (ascii_standard, blocks, dots, density, braille) shading_direction: Gradient direction (horizontal, vertical, radial, diagonal) contrast: Shading contrast 0.0-1.0 (low=subtle, high=dramatic) Returns: ASCII art as multi-line string Example: create_ascii_box(width=40, height=10, title="Status", line_style='double', shading_palette='blocks', contrast=0.8) """ # Clamp parameters width = max(20, min(120, width)) height = max(5, min(50, height)) contrast = max(0.0, min(1.0, contrast)) # Create parameter objects box_params = BoxDrawingParams( line_style=line_style, corner_type='sharp' if line_style in ['light', 'heavy', 'double'] else 'rounded', symmetry='bilateral' ) shading_params = ShadingParams( palette_type=shading_palette, contrast=contrast, direction=shading_direction, dither=False ) layout_params = LayoutParams( width=width, height=height, horizontal_align='center', vertical_align='middle', padding=1 ) # Render renderer = ASCIIArtRenderer(box_params, shading_params, layout_params) return renderer.render_bordered_box(title if title else None) @mcp.tool() def create_ascii_table( headers: list[str], rows: list[list[str]], line_style: Literal['light', 'heavy', 'double', 'rounded'] = 'light' ) -> str: """ Create ASCII table with headers and data rows. Args: headers: List of column headers rows: List of rows, each row is a list of cell values line_style: Border style (light, heavy, double, rounded) Returns: ASCII table as multi-line string Example: create_ascii_table( headers=["Name", "Status", "Progress"], rows=[ ["Task 1", "Complete", "100%"], ["Task 2", "Running", "65%"], ["Task 3", "Pending", "0%"] ], line_style='double' ) """ box_params = BoxDrawingParams( line_style=line_style, corner_type='sharp' if line_style != 'rounded' else 'rounded', symmetry='bilateral' ) shading_params = ShadingParams( palette_type='ascii_standard', contrast=0.5, direction='horizontal', dither=False ) layout_params = LayoutParams( width=80, height=20, horizontal_align='left', vertical_align='top', padding=1 ) renderer = ASCIIArtRenderer(box_params, shading_params, layout_params) return renderer.render_table(headers, rows) @mcp.tool() def list_ascii_styles() -> dict: """ List all available ASCII art styles with their intentionality. Returns: Dictionary of styles and their visual/semantic properties """ return { "box_styles": { style: { "sample_chars": f"{chars['top_left']}{chars['horizontal']*3}{chars['top_right']}", "intentionality": chars['intentionality'] } for style, chars in UNICODE_SETS.items() }, "shading_palettes": { name: { "characters": palette['chars'], "texture": palette['texture'], "intentionality": palette['intentionality'] } for name, palette in SHADING_PALETTES.items() } } # ============================================================================ # Run Server # ============================================================================ if __name__ == "__main__": mcp.run()

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/dmarsters/ascii-art-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server