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,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.
|