string-utils-ai-mcp 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
server.py ADDED
@@ -0,0 +1,164 @@
1
+ """
2
+ String Utils AI MCP Server
3
+ String manipulation and transformation tools powered by MEOK AI Labs.
4
+ """
5
+
6
+
7
+ import sys, os
8
+ sys.path.insert(0, os.path.expanduser('~/clawd/meok-labs-engine/shared'))
9
+ from auth_middleware import check_access
10
+
11
+ import re
12
+ import time
13
+ import unicodedata
14
+ from collections import defaultdict
15
+ from mcp.server.fastmcp import FastMCP
16
+
17
+ mcp = FastMCP("string-utils-ai", instructions="MEOK AI Labs MCP Server")
18
+
19
+ _call_counts: dict[str, list[float]] = defaultdict(list)
20
+ FREE_TIER_LIMIT = 50
21
+ WINDOW = 86400
22
+
23
+
24
+ def _check_rate_limit(tool_name: str) -> None:
25
+ now = time.time()
26
+ _call_counts[tool_name] = [t for t in _call_counts[tool_name] if now - t < WINDOW]
27
+ if len(_call_counts[tool_name]) >= FREE_TIER_LIMIT:
28
+ raise ValueError(f"Rate limit exceeded for {tool_name}. Free tier: {FREE_TIER_LIMIT}/day. Upgrade at https://meok.ai/pricing")
29
+ _call_counts[tool_name].append(now)
30
+
31
+
32
+ @mcp.tool()
33
+ def slugify(text: str, separator: str = "-", max_length: int = 80, lowercase: bool = True, api_key: str = "") -> dict:
34
+ """Convert text to a URL-friendly slug.
35
+
36
+ Args:
37
+ text: Text to slugify
38
+ separator: Word separator (default '-')
39
+ max_length: Maximum slug length (default 80)
40
+ lowercase: Convert to lowercase (default True)
41
+ """
42
+ allowed, msg, tier = check_access(api_key)
43
+ if not allowed:
44
+ return {"error": msg, "upgrade_url": "https://buy.stripe.com/14A4gB3K4eUWgYR56o8k836"}
45
+
46
+ _check_rate_limit("slugify")
47
+ slug = unicodedata.normalize('NFKD', text).encode('ascii', 'ignore').decode('ascii')
48
+ if lowercase:
49
+ slug = slug.lower()
50
+ slug = re.sub(r'[^\w\s-]', '', slug)
51
+ slug = re.sub(r'[-\s]+', separator, slug).strip(separator)
52
+ if max_length and len(slug) > max_length:
53
+ slug = slug[:max_length].rstrip(separator)
54
+ return {"slug": slug, "original": text, "length": len(slug)}
55
+
56
+
57
+ @mcp.tool()
58
+ def camel_to_snake(text: str, direction: str = "camel_to_snake", api_key: str = "") -> dict:
59
+ """Convert between camelCase, snake_case, kebab-case, and PascalCase.
60
+
61
+ Args:
62
+ text: String to convert
63
+ direction: Conversion type - 'camel_to_snake', 'snake_to_camel', 'to_kebab', 'to_pascal', 'to_constant'
64
+ """
65
+ allowed, msg, tier = check_access(api_key)
66
+ if not allowed:
67
+ return {"error": msg, "upgrade_url": "https://buy.stripe.com/14A4gB3K4eUWgYR56o8k836"}
68
+
69
+ _check_rate_limit("camel_to_snake")
70
+ # First normalize to words
71
+ words = []
72
+ if '_' in text:
73
+ words = [w for w in text.split('_') if w]
74
+ elif '-' in text:
75
+ words = [w for w in text.split('-') if w]
76
+ else:
77
+ parts = re.sub(r'([A-Z])', r' \1', text).strip().split()
78
+ words = [w.lower() for w in parts if w]
79
+ if not words:
80
+ words = [text.lower()]
81
+ results = {}
82
+ results["snake_case"] = '_'.join(w.lower() for w in words)
83
+ results["camelCase"] = words[0].lower() + ''.join(w.capitalize() for w in words[1:])
84
+ results["PascalCase"] = ''.join(w.capitalize() for w in words)
85
+ results["kebab-case"] = '-'.join(w.lower() for w in words)
86
+ results["CONSTANT_CASE"] = '_'.join(w.upper() for w in words)
87
+ target_map = {"camel_to_snake": "snake_case", "snake_to_camel": "camelCase",
88
+ "to_kebab": "kebab-case", "to_pascal": "PascalCase", "to_constant": "CONSTANT_CASE"}
89
+ target = target_map.get(direction, "snake_case")
90
+ return {"result": results[target], "direction": direction, "all_formats": results, "words": words}
91
+
92
+
93
+ @mcp.tool()
94
+ def truncate_smart(text: str, max_length: int = 100, suffix: str = "...", preserve_words: bool = True, api_key: str = "") -> dict:
95
+ """Smartly truncate text at word boundaries with a suffix.
96
+
97
+ Args:
98
+ text: Text to truncate
99
+ max_length: Maximum length including suffix (default 100)
100
+ suffix: Suffix to append when truncated (default '...')
101
+ preserve_words: Don't break mid-word (default True)
102
+ """
103
+ allowed, msg, tier = check_access(api_key)
104
+ if not allowed:
105
+ return {"error": msg, "upgrade_url": "https://buy.stripe.com/14A4gB3K4eUWgYR56o8k836"}
106
+
107
+ _check_rate_limit("truncate_smart")
108
+ if len(text) <= max_length:
109
+ return {"text": text, "truncated": False, "original_length": len(text)}
110
+ target_len = max_length - len(suffix)
111
+ if target_len <= 0:
112
+ return {"text": suffix[:max_length], "truncated": True, "original_length": len(text)}
113
+ truncated = text[:target_len]
114
+ if preserve_words and ' ' in truncated:
115
+ last_space = truncated.rfind(' ')
116
+ if last_space > target_len * 0.5:
117
+ truncated = truncated[:last_space]
118
+ truncated = truncated.rstrip(' .,;:!?-')
119
+ result = truncated + suffix
120
+ return {"text": result, "truncated": True, "original_length": len(text),
121
+ "result_length": len(result), "chars_removed": len(text) - len(truncated)}
122
+
123
+
124
+ @mcp.tool()
125
+ def extract_numbers(text: str, include_decimals: bool = True, include_negative: bool = True, api_key: str = "") -> dict:
126
+ """Extract all numbers from text.
127
+
128
+ Args:
129
+ text: Text to extract numbers from
130
+ include_decimals: Include decimal numbers (default True)
131
+ include_negative: Include negative numbers (default True)
132
+ """
133
+ allowed, msg, tier = check_access(api_key)
134
+ if not allowed:
135
+ return {"error": msg, "upgrade_url": "https://buy.stripe.com/14A4gB3K4eUWgYR56o8k836"}
136
+
137
+ _check_rate_limit("extract_numbers")
138
+ if include_negative and include_decimals:
139
+ pattern = r'-?\d+\.?\d*'
140
+ elif include_decimals:
141
+ pattern = r'\d+\.?\d*'
142
+ elif include_negative:
143
+ pattern = r'-?\d+'
144
+ else:
145
+ pattern = r'\d+'
146
+ matches = re.findall(pattern, text)
147
+ numbers = []
148
+ for m in matches:
149
+ try:
150
+ if '.' in m:
151
+ numbers.append(float(m))
152
+ else:
153
+ numbers.append(int(m))
154
+ except ValueError:
155
+ pass
156
+ stats = {}
157
+ if numbers:
158
+ stats = {"min": min(numbers), "max": max(numbers),
159
+ "sum": sum(numbers), "average": sum(numbers) / len(numbers)}
160
+ return {"numbers": numbers, "count": len(numbers), "statistics": stats}
161
+
162
+
163
+ if __name__ == "__main__":
164
+ mcp.run()
@@ -0,0 +1,28 @@
1
+ Metadata-Version: 2.4
2
+ Name: string-utils-ai-mcp
3
+ Version: 1.0.0
4
+ Summary: String Utils Ai tools for AI agents. Capabilities: slugify, camel to snake, truncate smart. Built by MEOK AI Labs.
5
+ Project-URL: Homepage, https://meok.ai
6
+ Project-URL: Repository, https://github.com/CSOAI-ORG/string-utils-ai-mcp
7
+ Author-email: MEOK AI Labs <nicholas@meok.ai>
8
+ License: MIT License
9
+
10
+ Copyright (c) 2026 MEOK AI Labs
11
+
12
+ Permission is hereby granted, free of charge, to any person obtaining a copy
13
+ of this software and associated documentation files (the "Software"), to deal
14
+ in the Software without restriction, including without limitation the rights
15
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16
+ copies of the Software, and to permit persons to whom the Software is
17
+ furnished to do so, subject to the following conditions:
18
+
19
+ The above copyright notice and this permission notice shall be included in all
20
+ copies or substantial portions of the Software.
21
+ License-File: LICENSE
22
+ Keywords: ai,mcp,meok,string,utils
23
+ Classifier: License :: OSI Approved :: MIT License
24
+ Classifier: Operating System :: OS Independent
25
+ Classifier: Programming Language :: Python :: 3
26
+ Classifier: Topic :: Software Development :: Libraries
27
+ Requires-Python: >=3.10
28
+ Requires-Dist: mcp>=1.0.0
@@ -0,0 +1,6 @@
1
+ server.py,sha256=xMB0PFqmBs-zx3M0GvSZLLENTSBR09rGySjVcW3ueEM,6217
2
+ string_utils_ai_mcp-1.0.0.dist-info/METADATA,sha256=r4YXRH1qXDkpOFBzyGqLu_R58N-OWHHy2qnNCXY5T3k,1366
3
+ string_utils_ai_mcp-1.0.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
4
+ string_utils_ai_mcp-1.0.0.dist-info/entry_points.txt,sha256=NA1I-RMxbZ4h2q8u95csY9YH4VNPUxTwl2rkKYn9U-E,52
5
+ string_utils_ai_mcp-1.0.0.dist-info/licenses/LICENSE,sha256=ibFbFVuWMg3hkFJtLijRTUi6DDoUbdR4oE78M6MKq-I,607
6
+ string_utils_ai_mcp-1.0.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.27.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ string_utils_ai_mcp = server:main
@@ -0,0 +1,13 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 MEOK AI Labs
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.