Skip to main content
Glama
server.py9.17 kB
""" MCP Char-Index Character-level string manipulation with precise index-based operations. Essential for test generation, parsing, and text processing where exact character positioning is critical. Core capabilities: - Find characters/substrings by position - Split text at exact indices - Extract ranges with index precision - Modify text by position (insert/delete/replace) - Pattern matching with position info --- This code was created with Claude AI. Repository: https://github.com/agent-hanju/char-index-mcp """ from mcp.server.fastmcp import FastMCP import re from typing import Annotated mcp = FastMCP("char-index") # ======================================== # 1. Character & Substring Finding (4) # ======================================== @mcp.tool() def find_nth_char( text: Annotated[str, "Text to search in"], char: Annotated[str, "Single character to find"], n: Annotated[int, "Which occurrence to find (1-based)"] = 1 ) -> int: """Find index of nth occurrence of a character. Returns -1 if not found.""" if len(char) != 1: raise ValueError("char must be a single character") if n < 1: raise ValueError("n must be >= 1") count = 0 for i, c in enumerate(text): if c == char: count += 1 if count == n: return i return -1 @mcp.tool() def find_all_char_indices( text: Annotated[str, "Text to search in"], char: Annotated[str, "Single character to find"] ) -> list[int]: """Find all indices where a character appears. Returns empty list if not found.""" if len(char) != 1: raise ValueError("char must be a single character") return [i for i, c in enumerate(text) if c == char] @mcp.tool() def find_nth_substring( text: Annotated[str, "Text to search in"], substring: Annotated[str, "Substring to find"], n: Annotated[int, "Which occurrence to find (1-based)"] = 1 ) -> int: """Find starting index of nth occurrence of a substring. Returns -1 if not found.""" if not substring: raise ValueError("substring cannot be empty") if n < 1: raise ValueError("n must be >= 1") count = 0 start = 0 while True: index = text.find(substring, start) if index == -1: return -1 count += 1 if count == n: return index start = index + 1 @mcp.tool() def find_all_substring_indices( text: Annotated[str, "Text to search in"], substring: Annotated[str, "Substring to find"] ) -> list[int]: """Find all starting indices where a substring appears (includes overlaps).""" if not substring: raise ValueError("substring cannot be empty") indices = [] start = 0 while True: index = text.find(substring, start) if index == -1: break indices.append(index) start = index + 1 return indices # ======================================== # 2. Splitting (1) # ======================================== @mcp.tool() def split_at_indices( text: Annotated[str, "Text to split"], indices: Annotated[list[int], "Split positions (auto-sorted & deduplicated)"] ) -> list[str]: """Split text at exact index positions. Indices auto-sorted and deduplicated.""" if not indices: return [text] # Sort and remove duplicates sorted_indices = sorted(set(indices)) # Validate for idx in sorted_indices: if idx < 0 or idx > len(text): raise ValueError(f"Index {idx} out of bounds [0, {len(text)}]") result = [] start = 0 for idx in sorted_indices: result.append(text[start:idx]) start = idx result.append(text[start:]) return result # ======================================== # 3. String Modification (3) # ======================================== @mcp.tool() def insert_at_index( text: Annotated[str, "Original text"], index: Annotated[int, "Position to insert at (negative = from end)"], insertion: Annotated[str, "Text to insert"] ) -> str: """Insert text at index position without replacing. Supports negative indices.""" if index < 0: index = max(0, len(text) + index + 1) return text[:index] + insertion + text[index:] @mcp.tool() def delete_range( text: Annotated[str, "Original text"], start: Annotated[int, "Starting index (inclusive)"], end: Annotated[int, "Ending index (exclusive)"] ) -> str: """Delete characters in range [start, end).""" return text[:start] + text[end:] @mcp.tool() def replace_range( text: Annotated[str, "Original text"], start: Annotated[int, "Starting index (inclusive)"], end: Annotated[int, "Ending index (exclusive)"], replacement: Annotated[str, "Text to replace with"] ) -> str: """Replace characters in range [start, end) with new text.""" return text[:start] + replacement + text[end:] # ======================================== # 4. Utilities (3) # ======================================== @mcp.tool() def find_regex_matches( text: Annotated[str, "Text to search in"], pattern: Annotated[str, "Regular expression pattern"] ) -> list[dict]: """Find all regex matches with positions. Returns list of {start, end, match} dicts.""" try: matches = [] for match in re.finditer(pattern, text): matches.append({ "start": match.start(), "end": match.end(), "match": match.group() }) return matches except re.error as e: raise ValueError(f"Invalid regex pattern: {e}") @mcp.tool() def extract_between_markers( text: Annotated[str, "Text to search in"], start_marker: Annotated[str, "Opening marker"], end_marker: Annotated[str, "Closing marker"], occurrence: Annotated[int, "Which occurrence to extract (1-based)"] = 1 ) -> dict: """Extract content between markers with positions. Returns dict with content and position info.""" if not start_marker or not end_marker: raise ValueError("Markers cannot be empty") if occurrence < 1: raise ValueError("occurrence must be >= 1") count = 0 search_start = 0 while True: start_idx = text.find(start_marker, search_start) if start_idx == -1: return { "content": None, "content_start": None, "content_end": None, "full_start": None, "full_end": None } content_start = start_idx + len(start_marker) end_idx = text.find(end_marker, content_start) if end_idx == -1: return { "content": None, "content_start": None, "content_end": None, "full_start": None, "full_end": None } count += 1 if count == occurrence: return { "content": text[content_start:end_idx], "content_start": content_start, "content_end": end_idx, "full_start": start_idx, "full_end": end_idx + len(end_marker) } search_start = content_start @mcp.tool() def count_chars( text: Annotated[str, "Text to analyze"] ) -> dict: """Count character statistics. Returns dict with total, without_spaces, letters, digits, spaces, special.""" return { "total": len(text), "without_spaces": len(text.replace(" ", "")), "letters": sum(1 for c in text if c.isalpha()), "digits": sum(1 for c in text if c.isdigit()), "spaces": sum(1 for c in text if c.isspace()), "special": sum(1 for c in text if not c.isalnum() and not c.isspace()) } # ======================================== # 5. Batch Processing (1) - 핵심 통합 # ======================================== @mcp.tool() def extract_substrings( text: Annotated[str, "Text to extract from"], ranges: Annotated[list[dict], "List of ranges with 'start' (required) and 'end' (optional). Negative indices supported"] ) -> list[dict]: """Extract substrings by index ranges. Supports negative indices and omitting end. Returns list of {start, end, substring, length} dicts.""" results = [] text_len = len(text) for r in ranges: start = r["start"] end = r.get("end", None) # Normalize negative indices if start < 0: start = max(0, text_len + start) if end is not None and end < 0: end = max(0, text_len + end) # Extract substring substring = text[start:end] actual_end = end if end is not None else text_len results.append({ "start": start, "end": actual_end, "substring": substring, "length": len(substring) }) return results def main(): """Entry point for the char-index-mcp CLI.""" mcp.run() if __name__ == "__main__": main()

Implementation Reference

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/agent-hanju/char-index-mcp'

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