pvidal66 commited on
Commit
68be937
·
1 Parent(s): 6b167f1
Files changed (4) hide show
  1. .gitignore +11 -0
  2. app.py +97 -102
  3. logs/mcp.log +270 -0
  4. text_tools.py +472 -0
.gitignore ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ __pycache__/
2
+ *.py[cod]
3
+ .pytest_cache/
4
+ .venv*/
5
+ .env
6
+ .env.*
7
+ !.env.example
8
+ !.env.local.example
9
+ !.env.production.example
10
+ config/auth.yaml
11
+ surveys/*/*.csv
app.py CHANGED
@@ -1,119 +1,114 @@
1
- import gradio as gr
2
  import json
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
 
4
- def analyze_text(text: str) -> str:
5
- """Analyze text and return statistics.
6
-
7
- Args:
8
- text: The input text to analyze
9
-
10
- Returns:
11
- JSON string with analysis results
12
- """
13
- words = text.split()
14
- chars = len(text)
15
- chars_no_spaces = len(text.replace(" ", ""))
16
- sentences = text.count(".") + text.count("!") + text.count("?")
17
-
18
- avg_word_length = round(chars_no_spaces / len(words), 2) if words else 0
19
- avg_sentence_length = round(len(words) / max(sentences, 1), 2)
20
-
21
- return json.dumps({
22
- "total_characters": chars,
23
- "characters_without_spaces": chars_no_spaces,
24
- "total_words": len(words),
25
- "total_sentences": max(sentences, 1),
26
- "average_word_length": avg_word_length,
27
- "average_sentence_length": avg_sentence_length
28
- }, indent=2)
29
-
30
- def extract_keywords(text: str, count: int = 5) -> str:
31
- """Extract keywords (most common words) from text.
32
-
33
- Args:
34
- text: The input text
35
- count: Number of keywords to return (default 5)
36
-
37
- Returns:
38
- JSON string with keywords and frequencies
39
- """
40
- stopwords = {
41
- "the", "a", "an", "and", "or", "but", "in", "on", "at", "to", "for",
42
- "of", "with", "is", "are", "was", "were", "be", "been", "by", "from"
43
- }
44
-
45
- words = text.lower().split()
46
- filtered = [w.strip(".,!?;:") for w in words if w.lower() not in stopwords]
47
-
48
- from collections import Counter
49
- word_freq = Counter(filtered)
50
- top_words = word_freq.most_common(count)
51
-
52
- return json.dumps({
53
- "keywords": [{"word": w, "frequency": f} for w, f in top_words]
54
- }, indent=2)
55
-
56
- def check_reading_level(text: str) -> str:
57
- """Estimate reading difficulty level.
58
-
59
- Args:
60
- text: The input text
61
-
62
- Returns:
63
- JSON string with reading level estimate
64
- """
65
- sentences = max(text.count(".") + text.count("!") + text.count("?"), 1)
66
- words = len(text.split())
67
- vowels = "aeiou"
68
- syllables = sum(1 for c in text.lower() if c in vowels)
69
-
70
- if words == 0:
71
- return json.dumps({"error": "No text to analyze"})
72
-
73
- grade = max(0, (0.39 * (words / sentences)) + (11.8 * (syllables / words)) - 15.59)
74
-
75
- if grade < 6:
76
- level = "Elementary School"
77
- elif grade < 9:
78
- level = "Middle School"
79
- elif grade < 13:
80
- level = "High School"
81
- else:
82
- level = "College/Academic"
83
-
84
- return json.dumps({
85
- "grade_level": round(grade, 1),
86
- "reading_level": level
87
- }, indent=2)
88
-
89
- # Create web UI
90
  with gr.Blocks(title="Text Processor") as demo:
91
  gr.Markdown("# Text Processing Tools")
92
- gr.Markdown("Analyze text statistics, extract keywords, and check reading difficulty.")
93
-
 
 
 
 
94
  with gr.Tab("Analyze Text"):
95
- text_input1 = gr.Textbox(
96
- label="Enter text",
97
- lines=8,
98
- placeholder="Paste your text here..."
99
- )
100
  analysis_output = gr.Textbox(label="Analysis Results", lines=8)
101
  gr.Button("Analyze", size="lg").click(analyze_text, text_input1, analysis_output)
102
-
103
  with gr.Tab("Extract Keywords"):
104
  text_input2 = gr.Textbox(label="Enter text", lines=8)
105
  count_input = gr.Slider(1, 20, value=5, step=1, label="Number of keywords")
106
  keywords_output = gr.Textbox(label="Keywords", lines=8)
107
- gr.Button("Extract", size="lg").click(
108
- extract_keywords,
109
- [text_input2, count_input],
110
- keywords_output
111
- )
112
-
113
  with gr.Tab("Reading Level"):
114
  text_input3 = gr.Textbox(label="Enter text", lines=8)
115
  level_output = gr.Textbox(label="Reading Level Analysis", lines=5)
116
  gr.Button("Check Level", size="lg").click(check_reading_level, text_input3, level_output)
117
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
118
  if __name__ == "__main__":
119
- demo.launch(mcp_server=True)
 
 
 
1
  import json
2
+ import logging
3
+ from datetime import datetime, timezone
4
+ from logging.handlers import RotatingFileHandler
5
+ from pathlib import Path
6
+
7
+ import gradio as gr
8
+
9
+ from text_tools import (
10
+ analyze_sentiment,
11
+ analyze_text,
12
+ check_reading_level,
13
+ detect_language,
14
+ extract_keywords,
15
+ readability_advice,
16
+ spell_check,
17
+ summarize_text,
18
+ )
19
+
20
+
21
+ LOG_DIR = Path("logs")
22
+ LOG_FILE = LOG_DIR / "mcp.log"
23
+
24
+
25
+ def _utc_now_iso() -> str:
26
+ return datetime.now(timezone.utc).isoformat()
27
+
28
+
29
+ class JsonLineFormatter(logging.Formatter):
30
+ def format(self, record):
31
+ message = record.getMessage()
32
+ try:
33
+ payload = json.loads(message)
34
+ payload["level"] = record.levelname
35
+ payload["logger"] = record.name
36
+ return json.dumps(payload, ensure_ascii=False)
37
+ except json.JSONDecodeError:
38
+ return json.dumps({
39
+ "timestamp": _utc_now_iso(),
40
+ "level": record.levelname,
41
+ "logger": record.name,
42
+ "message": message,
43
+ }, ensure_ascii=False)
44
+
45
+
46
+ def _configure_logging() -> None:
47
+ LOG_DIR.mkdir(parents=True, exist_ok=True)
48
+ root_logger = logging.getLogger()
49
+ root_logger.setLevel(logging.INFO)
50
+
51
+ text_formatter = logging.Formatter("%(asctime)s %(levelname)s %(name)s %(message)s")
52
+
53
+ stream_handler = logging.StreamHandler()
54
+ stream_handler.setFormatter(text_formatter)
55
+ root_logger.addHandler(stream_handler)
56
+
57
+ file_handler = RotatingFileHandler(LOG_FILE, maxBytes=1_000_000, backupCount=3)
58
+ file_handler.setFormatter(JsonLineFormatter())
59
+ root_logger.addHandler(file_handler)
60
+
61
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
62
  with gr.Blocks(title="Text Processor") as demo:
63
  gr.Markdown("# Text Processing Tools")
64
+ gr.Markdown(
65
+ "Analyze text statistics, extract keywords, check reading difficulty, "
66
+ "detect sentiment, identify language, summarize text, check spelling, "
67
+ "and suggest readability improvements."
68
+ )
69
+
70
  with gr.Tab("Analyze Text"):
71
+ text_input1 = gr.Textbox(label="Enter text", lines=8, placeholder="Paste your text here...")
 
 
 
 
72
  analysis_output = gr.Textbox(label="Analysis Results", lines=8)
73
  gr.Button("Analyze", size="lg").click(analyze_text, text_input1, analysis_output)
74
+
75
  with gr.Tab("Extract Keywords"):
76
  text_input2 = gr.Textbox(label="Enter text", lines=8)
77
  count_input = gr.Slider(1, 20, value=5, step=1, label="Number of keywords")
78
  keywords_output = gr.Textbox(label="Keywords", lines=8)
79
+ gr.Button("Extract", size="lg").click(extract_keywords, [text_input2, count_input], keywords_output)
80
+
 
 
 
 
81
  with gr.Tab("Reading Level"):
82
  text_input3 = gr.Textbox(label="Enter text", lines=8)
83
  level_output = gr.Textbox(label="Reading Level Analysis", lines=5)
84
  gr.Button("Check Level", size="lg").click(check_reading_level, text_input3, level_output)
85
 
86
+ with gr.Tab("Sentiment"):
87
+ text_input4 = gr.Textbox(label="Enter text", lines=8)
88
+ sentiment_output = gr.Textbox(label="Sentiment Analysis", lines=6)
89
+ gr.Button("Analyze Sentiment", size="lg").click(analyze_sentiment, text_input4, sentiment_output)
90
+
91
+ with gr.Tab("Language"):
92
+ text_input5 = gr.Textbox(label="Enter text", lines=8)
93
+ language_output = gr.Textbox(label="Language Detection", lines=8)
94
+ gr.Button("Detect Language", size="lg").click(detect_language, text_input5, language_output)
95
+
96
+ with gr.Tab("Summary"):
97
+ text_input6 = gr.Textbox(label="Enter text", lines=8)
98
+ summary_output = gr.Textbox(label="Summary", lines=8)
99
+ gr.Button("Summarize", size="lg").click(summarize_text, text_input6, summary_output)
100
+
101
+ with gr.Tab("Spell Check"):
102
+ text_input7 = gr.Textbox(label="Enter text", lines=8)
103
+ spell_output = gr.Textbox(label="Spelling Analysis", lines=10)
104
+ gr.Button("Check Spelling", size="lg").click(spell_check, text_input7, spell_output)
105
+
106
+ with gr.Tab("Readability Tips"):
107
+ text_input8 = gr.Textbox(label="Enter text", lines=8)
108
+ tips_output = gr.Textbox(label="Readability Advice", lines=10)
109
+ gr.Button("Get Advice", size="lg").click(readability_advice, text_input8, tips_output)
110
+
111
+
112
  if __name__ == "__main__":
113
+ _configure_logging()
114
+ demo.launch(mcp_server=True)
logs/mcp.log ADDED
@@ -0,0 +1,270 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {"timestamp": "2026-05-26T10:14:54.326881+00:00", "level": "INFO", "logger": "httpx", "message": "HTTP Request: HEAD https://huggingface.co/api/telemetry/https%3A/api.gradio.app/gradio-initiated-analytics \"HTTP/1.1 200 OK\""}
2
+ {"timestamp": "2026-05-26T10:14:54.371366+00:00", "level": "INFO", "logger": "mcp.server.streamable_http_manager", "message": "StreamableHTTP session manager started"}
3
+ {"timestamp": "2026-05-26T10:14:54.402389+00:00", "level": "INFO", "logger": "httpx", "message": "HTTP Request: GET http://127.0.0.1:7861/gradio_api/startup-events \"HTTP/1.1 200 OK\""}
4
+ {"timestamp": "2026-05-26T10:14:54.432111+00:00", "level": "INFO", "logger": "httpx", "message": "HTTP Request: HEAD http://127.0.0.1:7861/ \"HTTP/1.1 200 OK\""}
5
+ {"timestamp": "2026-05-26T10:14:54.567173+00:00", "level": "INFO", "logger": "httpx", "message": "HTTP Request: HEAD https://huggingface.co/api/telemetry/https%3A/api.gradio.app/gradio-launched-telemetry \"HTTP/1.1 200 OK\""}
6
+ {"timestamp": "2026-05-26T10:14:54.943604+00:00", "level": "INFO", "logger": "httpx", "message": "HTTP Request: GET https://api.gradio.app/pkg-version \"HTTP/1.1 200 OK\""}
7
+ {"timestamp": "2026-05-26T10:15:10.032329+00:00", "level": "INFO", "logger": "mcp.server.lowlevel.server", "message": "Processing request of type CallToolRequest"}
8
+ {"timestamp": "2026-05-26T10:15:10.063203+00:00", "level": "INFO", "logger": "httpx", "message": "HTTP Request: GET http://127.0.0.1:7861/config \"HTTP/1.1 200 OK\""}
9
+ {"timestamp": "2026-05-26T10:15:10.077049+00:00", "level": "INFO", "logger": "httpx", "message": "HTTP Request: GET http://127.0.0.1:7861/gradio_api/info?serialize=False \"HTTP/1.1 200 OK\""}
10
+ {"timestamp": "2026-05-26T10:15:10.099769+00:00", "level": "INFO", "logger": "httpx", "message": "HTTP Request: GET http://127.0.0.1:7861/gradio_api/heartbeat/12c1a69b-5b3b-4255-bc1b-e60013249c0a \"HTTP/1.1 200 OK\""}
11
+ {"timestamp": "2026-05-26T10:15:10.100499+00:00", "level": "INFO", "logger": "httpx", "message": "HTTP Request: POST http://127.0.0.1:7861/gradio_api/queue/join \"HTTP/1.1 200 OK\""}
12
+ {"timestamp": "2026-05-26T10:15:10.099617+00:00", "tool": "check_reading_level", "event": "completed", "status": "success", "duration_ms": 0.09, "chars": 476, "words": 87, "lines": 1, "result_chars": 64, "level": "INFO", "logger": "__main__"}
13
+ {"timestamp": "2026-05-26T10:15:10.111882+00:00", "level": "INFO", "logger": "httpx", "message": "HTTP Request: GET http://127.0.0.1:7861/gradio_api/queue/data?session_hash=12c1a69b-5b3b-4255-bc1b-e60013249c0a \"HTTP/1.1 200 OK\""}
14
+ {"timestamp": "2026-05-26T10:15:10.243705+00:00", "level": "INFO", "logger": "httpx", "message": "HTTP Request: HEAD https://huggingface.co/api/telemetry/py_client/initiated \"HTTP/1.1 200 OK\""}
15
+ {"timestamp": "2026-05-26T10:15:10.662165+00:00", "level": "INFO", "logger": "mcp.server.streamable_http", "message": "Terminating session: None"}
16
+ {"timestamp": "2026-05-26T10:15:14.055034+00:00", "level": "INFO", "logger": "mcp.server.lowlevel.server", "message": "Processing request of type CallToolRequest"}
17
+ {"timestamp": "2026-05-26T10:15:14.062146+00:00", "level": "INFO", "logger": "httpx", "message": "HTTP Request: POST http://127.0.0.1:7861/gradio_api/queue/join \"HTTP/1.1 200 OK\""}
18
+ {"timestamp": "2026-05-26T10:15:14.062793+00:00", "tool": "analyze_text", "event": "completed", "status": "success", "duration_ms": 0.05, "chars": 476, "words": 87, "lines": 1, "result_chars": 176, "level": "INFO", "logger": "__main__"}
19
+ {"timestamp": "2026-05-26T10:15:14.077264+00:00", "level": "INFO", "logger": "httpx", "message": "HTTP Request: GET http://127.0.0.1:7861/gradio_api/queue/data?session_hash=12c1a69b-5b3b-4255-bc1b-e60013249c0a \"HTTP/1.1 200 OK\""}
20
+ {"timestamp": "2026-05-26T10:15:14.618727+00:00", "level": "INFO", "logger": "mcp.server.streamable_http", "message": "Terminating session: None"}
21
+ {"timestamp": "2026-05-26T10:21:16.175198+00:00", "level": "INFO", "logger": "mcp.server.streamable_http", "message": "Terminating session: None"}
22
+ {"timestamp": "2026-05-26T10:21:16.209298+00:00", "level": "INFO", "logger": "mcp.server.streamable_http", "message": "Terminating session: None"}
23
+ {"timestamp": "2026-05-26T10:21:16.245987+00:00", "level": "INFO", "logger": "mcp.server.lowlevel.server", "message": "Processing request of type ListToolsRequest"}
24
+ {"timestamp": "2026-05-26T10:21:16.247345+00:00", "level": "INFO", "logger": "mcp.server.streamable_http", "message": "Terminating session: None"}
25
+ {"timestamp": "2026-05-26T10:21:16.280542+00:00", "level": "INFO", "logger": "mcp.server.lowlevel.server", "message": "Processing request of type ListResourcesRequest"}
26
+ {"timestamp": "2026-05-26T10:21:16.281714+00:00", "level": "INFO", "logger": "mcp.server.streamable_http", "message": "Terminating session: None"}
27
+ {"timestamp": "2026-05-26T10:21:16.322247+00:00", "level": "INFO", "logger": "mcp.server.lowlevel.server", "message": "Processing request of type ListResourceTemplatesRequest"}
28
+ {"timestamp": "2026-05-26T10:21:16.323348+00:00", "level": "INFO", "logger": "mcp.server.streamable_http", "message": "Terminating session: None"}
29
+ {"timestamp": "2026-05-26T10:23:13.096334+00:00", "level": "INFO", "logger": "httpx", "message": "HTTP Request: HEAD https://huggingface.co/api/telemetry/https%3A/api.gradio.app/gradio-initiated-analytics \"HTTP/1.1 200 OK\""}
30
+ {"timestamp": "2026-05-26T10:23:13.115011+00:00", "level": "INFO", "logger": "mcp.server.streamable_http_manager", "message": "StreamableHTTP session manager started"}
31
+ {"timestamp": "2026-05-26T10:23:13.147054+00:00", "level": "INFO", "logger": "httpx", "message": "HTTP Request: GET http://127.0.0.1:7861/gradio_api/startup-events \"HTTP/1.1 200 OK\""}
32
+ {"timestamp": "2026-05-26T10:23:13.179491+00:00", "level": "INFO", "logger": "httpx", "message": "HTTP Request: HEAD http://127.0.0.1:7861/ \"HTTP/1.1 200 OK\""}
33
+ {"timestamp": "2026-05-26T10:23:13.311614+00:00", "level": "INFO", "logger": "httpx", "message": "HTTP Request: HEAD https://huggingface.co/api/telemetry/https%3A/api.gradio.app/gradio-launched-telemetry \"HTTP/1.1 200 OK\""}
34
+ {"timestamp": "2026-05-26T10:23:13.728785+00:00", "level": "INFO", "logger": "httpx", "message": "HTTP Request: GET https://api.gradio.app/pkg-version \"HTTP/1.1 200 OK\""}
35
+ {"timestamp": "2026-05-26T10:23:56.588000+00:00", "level": "INFO", "logger": "mcp.server.streamable_http", "message": "Terminating session: None"}
36
+ {"timestamp": "2026-05-26T10:24:17.341357+00:00", "tool": "analyze_sentiment", "event": "completed", "status": "success", "duration_ms": 3.34, "chars": 476, "words": 87, "lines": 1, "result_chars": 135, "level": "INFO", "logger": "__main__"}
37
+ {"timestamp": "2026-05-26T10:26:41.891812+00:00", "tool": "detect_language", "event": "completed", "status": "success", "duration_ms": 0.54, "chars": 476, "words": 87, "lines": 1, "result_chars": 521, "level": "INFO", "logger": "__main__"}
38
+ {"timestamp": "2026-05-26T10:27:06.544749+00:00", "level": "INFO", "logger": "mcp.server.streamable_http", "message": "Terminating session: None"}
39
+ {"timestamp": "2026-05-26T10:27:06.571167+00:00", "level": "INFO", "logger": "mcp.server.streamable_http", "message": "Terminating session: None"}
40
+ {"timestamp": "2026-05-26T10:27:06.597561+00:00", "level": "INFO", "logger": "mcp.server.lowlevel.server", "message": "Processing request of type ListToolsRequest"}
41
+ {"timestamp": "2026-05-26T10:27:06.600981+00:00", "level": "INFO", "logger": "mcp.server.streamable_http", "message": "Terminating session: None"}
42
+ {"timestamp": "2026-05-26T10:27:36.965020+00:00", "level": "INFO", "logger": "mcp.server.lowlevel.server", "message": "Processing request of type ListResourcesRequest"}
43
+ {"timestamp": "2026-05-26T10:27:36.966339+00:00", "level": "INFO", "logger": "mcp.server.streamable_http", "message": "Terminating session: None"}
44
+ {"timestamp": "2026-05-26T10:27:37.006144+00:00", "level": "INFO", "logger": "mcp.server.lowlevel.server", "message": "Processing request of type ListResourceTemplatesRequest"}
45
+ {"timestamp": "2026-05-26T10:27:37.009088+00:00", "level": "INFO", "logger": "mcp.server.streamable_http", "message": "Terminating session: None"}
46
+ {"timestamp": "2026-05-26T11:28:42.989836+00:00", "level": "INFO", "logger": "mcp.server.streamable_http_manager", "message": "StreamableHTTP session manager shutting down"}
47
+ {"timestamp": "2026-05-26T11:28:47.944031+00:00", "level": "INFO", "logger": "httpx", "message": "HTTP Request: HEAD https://huggingface.co/api/telemetry/https%3A/api.gradio.app/gradio-initiated-analytics \"HTTP/1.1 200 OK\""}
48
+ {"timestamp": "2026-05-26T11:28:47.989453+00:00", "level": "INFO", "logger": "mcp.server.streamable_http_manager", "message": "StreamableHTTP session manager started"}
49
+ {"timestamp": "2026-05-26T11:28:48.017475+00:00", "level": "INFO", "logger": "httpx", "message": "HTTP Request: GET http://127.0.0.1:7861/gradio_api/startup-events \"HTTP/1.1 200 OK\""}
50
+ {"timestamp": "2026-05-26T11:28:48.043402+00:00", "level": "INFO", "logger": "httpx", "message": "HTTP Request: HEAD http://127.0.0.1:7861/ \"HTTP/1.1 200 OK\""}
51
+ {"timestamp": "2026-05-26T11:28:48.179020+00:00", "level": "INFO", "logger": "httpx", "message": "HTTP Request: HEAD https://huggingface.co/api/telemetry/https%3A/api.gradio.app/gradio-launched-telemetry \"HTTP/1.1 200 OK\""}
52
+ {"timestamp": "2026-05-26T11:28:49.131014+00:00", "level": "INFO", "logger": "httpx", "message": "HTTP Request: GET https://api.gradio.app/pkg-version \"HTTP/1.1 200 OK\""}
53
+ {"timestamp": "2026-05-26T11:29:19.111124+00:00", "level": "INFO", "logger": "mcp.server.streamable_http", "message": "Terminating session: None"}
54
+ {"timestamp": "2026-05-26T11:29:19.150167+00:00", "level": "INFO", "logger": "mcp.server.streamable_http", "message": "Terminating session: None"}
55
+ {"timestamp": "2026-05-26T11:29:19.180151+00:00", "level": "INFO", "logger": "mcp.server.lowlevel.server", "message": "Processing request of type ListToolsRequest"}
56
+ {"timestamp": "2026-05-26T11:29:19.182793+00:00", "level": "INFO", "logger": "mcp.server.streamable_http", "message": "Terminating session: None"}
57
+ {"timestamp": "2026-05-26T11:29:19.474230+00:00", "level": "INFO", "logger": "mcp.server.lowlevel.server", "message": "Processing request of type ListResourcesRequest"}
58
+ {"timestamp": "2026-05-26T11:29:19.476155+00:00", "level": "INFO", "logger": "mcp.server.streamable_http", "message": "Terminating session: None"}
59
+ {"timestamp": "2026-05-26T11:29:19.535098+00:00", "level": "INFO", "logger": "mcp.server.lowlevel.server", "message": "Processing request of type ListResourceTemplatesRequest"}
60
+ {"timestamp": "2026-05-26T11:29:19.537865+00:00", "level": "INFO", "logger": "mcp.server.streamable_http", "message": "Terminating session: None"}
61
+ {"timestamp": "2026-05-26T12:18:13.319207+00:00", "level": "INFO", "logger": "mcp.server.streamable_http", "message": "Terminating session: None"}
62
+ {"timestamp": "2026-05-26T12:18:13.377037+00:00", "level": "INFO", "logger": "mcp.server.streamable_http", "message": "Terminating session: None"}
63
+ {"timestamp": "2026-05-26T12:18:13.429377+00:00", "level": "INFO", "logger": "mcp.server.lowlevel.server", "message": "Processing request of type ListToolsRequest"}
64
+ {"timestamp": "2026-05-26T12:18:13.438532+00:00", "level": "INFO", "logger": "mcp.server.streamable_http", "message": "Terminating session: None"}
65
+ {"timestamp": "2026-05-26T12:18:13.488139+00:00", "level": "INFO", "logger": "mcp.server.lowlevel.server", "message": "Processing request of type ListResourceTemplatesRequest"}
66
+ {"timestamp": "2026-05-26T12:18:13.489587+00:00", "level": "INFO", "logger": "mcp.server.streamable_http", "message": "Terminating session: None"}
67
+ {"timestamp": "2026-05-26T12:18:13.514028+00:00", "level": "INFO", "logger": "mcp.server.lowlevel.server", "message": "Processing request of type ListResourcesRequest"}
68
+ {"timestamp": "2026-05-26T12:18:13.515649+00:00", "level": "INFO", "logger": "mcp.server.streamable_http", "message": "Terminating session: None"}
69
+ {"timestamp": "2026-05-26T12:19:13.019523+00:00", "level": "INFO", "logger": "mcp.server.streamable_http", "message": "Terminating session: None"}
70
+ {"timestamp": "2026-05-26T12:19:13.063144+00:00", "level": "INFO", "logger": "mcp.server.streamable_http", "message": "Terminating session: None"}
71
+ {"timestamp": "2026-05-26T12:19:13.116141+00:00", "level": "INFO", "logger": "mcp.server.lowlevel.server", "message": "Processing request of type ListToolsRequest"}
72
+ {"timestamp": "2026-05-26T12:19:13.117797+00:00", "level": "INFO", "logger": "mcp.server.streamable_http", "message": "Terminating session: None"}
73
+ {"timestamp": "2026-05-26T12:19:13.178390+00:00", "level": "INFO", "logger": "mcp.server.lowlevel.server", "message": "Processing request of type ListResourcesRequest"}
74
+ {"timestamp": "2026-05-26T12:19:13.179582+00:00", "level": "INFO", "logger": "mcp.server.streamable_http", "message": "Terminating session: None"}
75
+ {"timestamp": "2026-05-26T12:19:13.231273+00:00", "level": "INFO", "logger": "mcp.server.lowlevel.server", "message": "Processing request of type ListResourceTemplatesRequest"}
76
+ {"timestamp": "2026-05-26T12:19:13.232187+00:00", "level": "INFO", "logger": "mcp.server.streamable_http", "message": "Terminating session: None"}
77
+ {"timestamp": "2026-05-26T12:20:56.932900+00:00", "level": "INFO", "logger": "mcp.server.streamable_http", "message": "Terminating session: None"}
78
+ {"timestamp": "2026-05-26T12:20:56.966812+00:00", "level": "INFO", "logger": "mcp.server.streamable_http", "message": "Terminating session: None"}
79
+ {"timestamp": "2026-05-26T12:20:57.025791+00:00", "level": "INFO", "logger": "mcp.server.lowlevel.server", "message": "Processing request of type ListToolsRequest"}
80
+ {"timestamp": "2026-05-26T12:20:57.027432+00:00", "level": "INFO", "logger": "mcp.server.streamable_http", "message": "Terminating session: None"}
81
+ {"timestamp": "2026-05-26T12:20:57.628747+00:00", "level": "INFO", "logger": "mcp.server.lowlevel.server", "message": "Processing request of type ListResourceTemplatesRequest"}
82
+ {"timestamp": "2026-05-26T12:20:57.631095+00:00", "level": "INFO", "logger": "mcp.server.streamable_http", "message": "Terminating session: None"}
83
+ {"timestamp": "2026-05-26T12:20:57.670654+00:00", "level": "INFO", "logger": "mcp.server.lowlevel.server", "message": "Processing request of type ListResourcesRequest"}
84
+ {"timestamp": "2026-05-26T12:20:57.671949+00:00", "level": "INFO", "logger": "mcp.server.streamable_http", "message": "Terminating session: None"}
85
+ {"timestamp": "2026-05-26T12:32:42.747778+00:00", "level": "INFO", "logger": "mcp.server.streamable_http", "message": "Terminating session: None"}
86
+ {"timestamp": "2026-05-26T12:32:42.813482+00:00", "level": "INFO", "logger": "mcp.server.streamable_http", "message": "Terminating session: None"}
87
+ {"timestamp": "2026-05-26T12:32:42.929451+00:00", "level": "INFO", "logger": "mcp.server.lowlevel.server", "message": "Processing request of type ListToolsRequest"}
88
+ {"timestamp": "2026-05-26T12:32:42.932163+00:00", "level": "INFO", "logger": "mcp.server.streamable_http", "message": "Terminating session: None"}
89
+ {"timestamp": "2026-05-26T12:32:43.059745+00:00", "level": "INFO", "logger": "mcp.server.lowlevel.server", "message": "Processing request of type ListResourcesRequest"}
90
+ {"timestamp": "2026-05-26T12:32:43.060699+00:00", "level": "INFO", "logger": "mcp.server.streamable_http", "message": "Terminating session: None"}
91
+ {"timestamp": "2026-05-26T12:32:43.074022+00:00", "level": "INFO", "logger": "mcp.server.lowlevel.server", "message": "Processing request of type ListResourceTemplatesRequest"}
92
+ {"timestamp": "2026-05-26T12:32:43.075004+00:00", "level": "INFO", "logger": "mcp.server.streamable_http", "message": "Terminating session: None"}
93
+ {"timestamp": "2026-05-26T12:42:03.000546+00:00", "level": "INFO", "logger": "mcp.server.streamable_http", "message": "Terminating session: None"}
94
+ {"timestamp": "2026-05-26T12:42:03.036371+00:00", "level": "INFO", "logger": "mcp.server.streamable_http", "message": "Terminating session: None"}
95
+ {"timestamp": "2026-05-26T12:42:03.086056+00:00", "level": "INFO", "logger": "mcp.server.lowlevel.server", "message": "Processing request of type ListToolsRequest"}
96
+ {"timestamp": "2026-05-26T12:42:03.088514+00:00", "level": "INFO", "logger": "mcp.server.streamable_http", "message": "Terminating session: None"}
97
+ {"timestamp": "2026-05-26T12:42:03.170030+00:00", "level": "INFO", "logger": "mcp.server.lowlevel.server", "message": "Processing request of type ListResourcesRequest"}
98
+ {"timestamp": "2026-05-26T12:42:03.171018+00:00", "level": "INFO", "logger": "mcp.server.streamable_http", "message": "Terminating session: None"}
99
+ {"timestamp": "2026-05-26T12:42:03.187364+00:00", "level": "INFO", "logger": "mcp.server.lowlevel.server", "message": "Processing request of type ListResourceTemplatesRequest"}
100
+ {"timestamp": "2026-05-26T12:42:03.188214+00:00", "level": "INFO", "logger": "mcp.server.streamable_http", "message": "Terminating session: None"}
101
+ {"timestamp": "2026-05-26T12:42:08.413771+00:00", "level": "INFO", "logger": "mcp.server.streamable_http", "message": "Terminating session: None"}
102
+ {"timestamp": "2026-05-26T12:42:08.460631+00:00", "level": "INFO", "logger": "mcp.server.streamable_http", "message": "Terminating session: None"}
103
+ {"timestamp": "2026-05-26T12:42:08.489274+00:00", "level": "INFO", "logger": "mcp.server.lowlevel.server", "message": "Processing request of type ListToolsRequest"}
104
+ {"timestamp": "2026-05-26T12:42:08.490806+00:00", "level": "INFO", "logger": "mcp.server.streamable_http", "message": "Terminating session: None"}
105
+ {"timestamp": "2026-05-26T12:42:19.228293+00:00", "level": "INFO", "logger": "mcp.server.streamable_http", "message": "Terminating session: None"}
106
+ {"timestamp": "2026-05-26T12:42:19.262659+00:00", "level": "INFO", "logger": "mcp.server.streamable_http", "message": "Terminating session: None"}
107
+ {"timestamp": "2026-05-26T12:42:19.296168+00:00", "level": "INFO", "logger": "mcp.server.lowlevel.server", "message": "Processing request of type ListToolsRequest"}
108
+ {"timestamp": "2026-05-26T12:42:19.297673+00:00", "level": "INFO", "logger": "mcp.server.streamable_http", "message": "Terminating session: None"}
109
+ {"timestamp": "2026-05-26T12:46:18.806071+00:00", "level": "INFO", "logger": "mcp.server.streamable_http", "message": "Terminating session: None"}
110
+ {"timestamp": "2026-05-26T12:46:18.848549+00:00", "level": "INFO", "logger": "mcp.server.streamable_http", "message": "Terminating session: None"}
111
+ {"timestamp": "2026-05-26T12:46:18.882281+00:00", "level": "INFO", "logger": "mcp.server.lowlevel.server", "message": "Processing request of type ListToolsRequest"}
112
+ {"timestamp": "2026-05-26T12:46:18.884477+00:00", "level": "INFO", "logger": "mcp.server.streamable_http", "message": "Terminating session: None"}
113
+ {"timestamp": "2026-05-26T12:46:18.902560+00:00", "level": "INFO", "logger": "mcp.server.lowlevel.server", "message": "Processing request of type ListResourceTemplatesRequest"}
114
+ {"timestamp": "2026-05-26T12:46:18.903602+00:00", "level": "INFO", "logger": "mcp.server.streamable_http", "message": "Terminating session: None"}
115
+ {"timestamp": "2026-05-26T12:46:18.917741+00:00", "level": "INFO", "logger": "mcp.server.lowlevel.server", "message": "Processing request of type ListResourcesRequest"}
116
+ {"timestamp": "2026-05-26T12:46:18.918705+00:00", "level": "INFO", "logger": "mcp.server.streamable_http", "message": "Terminating session: None"}
117
+ {"timestamp": "2026-05-26T12:46:21.010404+00:00", "level": "INFO", "logger": "mcp.server.streamable_http", "message": "Terminating session: None"}
118
+ {"timestamp": "2026-05-26T12:46:21.097014+00:00", "level": "INFO", "logger": "mcp.server.streamable_http", "message": "Terminating session: None"}
119
+ {"timestamp": "2026-05-26T12:46:21.159892+00:00", "level": "INFO", "logger": "mcp.server.lowlevel.server", "message": "Processing request of type ListToolsRequest"}
120
+ {"timestamp": "2026-05-26T12:46:21.162273+00:00", "level": "INFO", "logger": "mcp.server.streamable_http", "message": "Terminating session: None"}
121
+ {"timestamp": "2026-05-26T12:46:39.527947+00:00", "level": "INFO", "logger": "mcp.server.streamable_http", "message": "Terminating session: None"}
122
+ {"timestamp": "2026-05-26T12:46:39.557063+00:00", "level": "INFO", "logger": "mcp.server.streamable_http", "message": "Terminating session: None"}
123
+ {"timestamp": "2026-05-26T12:46:39.586775+00:00", "level": "INFO", "logger": "mcp.server.lowlevel.server", "message": "Processing request of type ListToolsRequest"}
124
+ {"timestamp": "2026-05-26T12:46:40.257973+00:00", "level": "INFO", "logger": "mcp.server.streamable_http", "message": "Terminating session: None"}
125
+ {"timestamp": "2026-05-26T12:47:17.297895+00:00", "level": "INFO", "logger": "mcp.server.streamable_http", "message": "Terminating session: None"}
126
+ {"timestamp": "2026-05-26T12:47:17.329211+00:00", "level": "INFO", "logger": "mcp.server.streamable_http", "message": "Terminating session: None"}
127
+ {"timestamp": "2026-05-26T12:47:17.376073+00:00", "level": "INFO", "logger": "mcp.server.lowlevel.server", "message": "Processing request of type ListToolsRequest"}
128
+ {"timestamp": "2026-05-26T12:47:17.378358+00:00", "level": "INFO", "logger": "mcp.server.streamable_http", "message": "Terminating session: None"}
129
+ {"timestamp": "2026-05-26T12:47:18.029361+00:00", "level": "INFO", "logger": "mcp.server.lowlevel.server", "message": "Processing request of type ListResourceTemplatesRequest"}
130
+ {"timestamp": "2026-05-26T12:47:18.031473+00:00", "level": "INFO", "logger": "mcp.server.streamable_http", "message": "Terminating session: None"}
131
+ {"timestamp": "2026-05-26T12:47:18.069907+00:00", "level": "INFO", "logger": "mcp.server.lowlevel.server", "message": "Processing request of type ListResourcesRequest"}
132
+ {"timestamp": "2026-05-26T12:47:18.070795+00:00", "level": "INFO", "logger": "mcp.server.streamable_http", "message": "Terminating session: None"}
133
+ {"timestamp": "2026-05-26T12:53:06.660021+00:00", "level": "INFO", "logger": "mcp.server.streamable_http", "message": "Terminating session: None"}
134
+ {"timestamp": "2026-05-26T12:53:06.693287+00:00", "level": "INFO", "logger": "mcp.server.streamable_http", "message": "Terminating session: None"}
135
+ {"timestamp": "2026-05-26T12:53:06.730785+00:00", "level": "INFO", "logger": "mcp.server.lowlevel.server", "message": "Processing request of type ListToolsRequest"}
136
+ {"timestamp": "2026-05-26T12:53:06.732431+00:00", "level": "INFO", "logger": "mcp.server.streamable_http", "message": "Terminating session: None"}
137
+ {"timestamp": "2026-05-26T12:53:06.755704+00:00", "level": "INFO", "logger": "mcp.server.lowlevel.server", "message": "Processing request of type ListResourcesRequest"}
138
+ {"timestamp": "2026-05-26T12:53:06.756751+00:00", "level": "INFO", "logger": "mcp.server.streamable_http", "message": "Terminating session: None"}
139
+ {"timestamp": "2026-05-26T12:53:06.778895+00:00", "level": "INFO", "logger": "mcp.server.lowlevel.server", "message": "Processing request of type ListResourceTemplatesRequest"}
140
+ {"timestamp": "2026-05-26T12:53:06.779707+00:00", "level": "INFO", "logger": "mcp.server.streamable_http", "message": "Terminating session: None"}
141
+ {"timestamp": "2026-05-26T12:55:34.743292+00:00", "level": "INFO", "logger": "mcp.server.streamable_http", "message": "Terminating session: None"}
142
+ {"timestamp": "2026-05-26T12:55:34.854063+00:00", "level": "INFO", "logger": "mcp.server.streamable_http", "message": "Terminating session: None"}
143
+ {"timestamp": "2026-05-26T12:55:34.905122+00:00", "level": "INFO", "logger": "mcp.server.lowlevel.server", "message": "Processing request of type ListToolsRequest"}
144
+ {"timestamp": "2026-05-26T12:55:34.906759+00:00", "level": "INFO", "logger": "mcp.server.streamable_http", "message": "Terminating session: None"}
145
+ {"timestamp": "2026-05-26T13:01:07.526451+00:00", "level": "INFO", "logger": "mcp.server.streamable_http", "message": "Terminating session: None"}
146
+ {"timestamp": "2026-05-26T13:01:07.562603+00:00", "level": "INFO", "logger": "mcp.server.streamable_http", "message": "Terminating session: None"}
147
+ {"timestamp": "2026-05-26T13:01:07.594352+00:00", "level": "INFO", "logger": "mcp.server.lowlevel.server", "message": "Processing request of type ListToolsRequest"}
148
+ {"timestamp": "2026-05-26T13:01:07.596358+00:00", "level": "INFO", "logger": "mcp.server.streamable_http", "message": "Terminating session: None"}
149
+ {"timestamp": "2026-05-26T13:01:07.613070+00:00", "level": "INFO", "logger": "mcp.server.lowlevel.server", "message": "Processing request of type ListResourcesRequest"}
150
+ {"timestamp": "2026-05-26T13:01:07.614056+00:00", "level": "INFO", "logger": "mcp.server.streamable_http", "message": "Terminating session: None"}
151
+ {"timestamp": "2026-05-26T13:01:07.635162+00:00", "level": "INFO", "logger": "mcp.server.lowlevel.server", "message": "Processing request of type ListResourceTemplatesRequest"}
152
+ {"timestamp": "2026-05-26T13:01:07.636264+00:00", "level": "INFO", "logger": "mcp.server.streamable_http", "message": "Terminating session: None"}
153
+ {"timestamp": "2026-05-26T13:37:45.511967+00:00", "level": "INFO", "logger": "mcp.server.streamable_http_manager", "message": "StreamableHTTP session manager started"}
154
+ {"timestamp": "2026-05-26T13:37:45.550642+00:00", "level": "INFO", "logger": "httpx", "message": "HTTP Request: GET http://127.0.0.1:7860/gradio_api/startup-events \"HTTP/1.1 200 OK\""}
155
+ {"timestamp": "2026-05-26T13:37:45.588351+00:00", "level": "INFO", "logger": "httpx", "message": "HTTP Request: HEAD http://127.0.0.1:7860/ \"HTTP/1.1 200 OK\""}
156
+ {"timestamp": "2026-05-26T13:37:45.721114+00:00", "level": "INFO", "logger": "httpx", "message": "HTTP Request: HEAD https://huggingface.co/api/telemetry/https%3A/api.gradio.app/gradio-launched-telemetry \"HTTP/1.1 200 OK\""}
157
+ {"timestamp": "2026-05-26T13:37:46.714082+00:00", "level": "INFO", "logger": "httpx", "message": "HTTP Request: GET https://api.gradio.app/pkg-version \"HTTP/1.1 200 OK\""}
158
+ {"timestamp": "2026-05-26T13:38:25.184260+00:00", "level": "INFO", "logger": "mcp.server.lowlevel.server", "message": "Processing request of type CallToolRequest"}
159
+ {"timestamp": "2026-05-26T13:38:25.205170+00:00", "level": "INFO", "logger": "httpx", "message": "HTTP Request: GET http://127.0.0.1:7860/config \"HTTP/1.1 200 OK\""}
160
+ {"timestamp": "2026-05-26T13:38:25.219571+00:00", "level": "INFO", "logger": "httpx", "message": "HTTP Request: GET http://127.0.0.1:7860/gradio_api/info?serialize=False \"HTTP/1.1 200 OK\""}
161
+ {"timestamp": "2026-05-26T13:38:25.239654+00:00", "level": "INFO", "logger": "httpx", "message": "HTTP Request: GET http://127.0.0.1:7860/gradio_api/heartbeat/b337fb51-848e-4e7b-87c1-439838f89846 \"HTTP/1.1 200 OK\""}
162
+ {"timestamp": "2026-05-26T13:38:25.242817+00:00", "level": "INFO", "logger": "httpx", "message": "HTTP Request: POST http://127.0.0.1:7860/gradio_api/queue/join \"HTTP/1.1 200 OK\""}
163
+ {"timestamp": "2026-05-26T13:38:25.243402+00:00", "tool": "analyze_text", "event": "completed", "status": "success", "duration_ms": 0.1, "chars": 273, "words": 48, "lines": 3, "result_chars": 176, "level": "INFO", "logger": "text_tools"}
164
+ {"timestamp": "2026-05-26T13:38:25.262026+00:00", "level": "INFO", "logger": "httpx", "message": "HTTP Request: GET http://127.0.0.1:7860/gradio_api/queue/data?session_hash=b337fb51-848e-4e7b-87c1-439838f89846 \"HTTP/1.1 200 OK\""}
165
+ {"timestamp": "2026-05-26T13:38:25.384818+00:00", "level": "INFO", "logger": "httpx", "message": "HTTP Request: HEAD https://huggingface.co/api/telemetry/py_client/initiated \"HTTP/1.1 200 OK\""}
166
+ {"timestamp": "2026-05-26T13:38:25.807323+00:00", "level": "INFO", "logger": "mcp.server.streamable_http", "message": "Terminating session: None"}
167
+ {"timestamp": "2026-05-26T13:39:25.508192+00:00", "level": "INFO", "logger": "mcp.server.streamable_http", "message": "Terminating session: None"}
168
+ {"timestamp": "2026-05-26T13:39:25.538097+00:00", "level": "INFO", "logger": "mcp.server.streamable_http", "message": "Terminating session: None"}
169
+ {"timestamp": "2026-05-26T13:39:25.557478+00:00", "level": "INFO", "logger": "mcp.server.lowlevel.server", "message": "Processing request of type ListToolsRequest"}
170
+ {"timestamp": "2026-05-26T13:39:25.559064+00:00", "level": "INFO", "logger": "mcp.server.streamable_http", "message": "Terminating session: None"}
171
+ {"timestamp": "2026-05-26T13:39:25.618145+00:00", "level": "INFO", "logger": "mcp.server.lowlevel.server", "message": "Processing request of type ListResourcesRequest"}
172
+ {"timestamp": "2026-05-26T13:39:25.619387+00:00", "level": "INFO", "logger": "mcp.server.streamable_http", "message": "Terminating session: None"}
173
+ {"timestamp": "2026-05-26T13:39:25.667038+00:00", "level": "INFO", "logger": "mcp.server.lowlevel.server", "message": "Processing request of type ListResourceTemplatesRequest"}
174
+ {"timestamp": "2026-05-26T13:39:25.668109+00:00", "level": "INFO", "logger": "mcp.server.streamable_http", "message": "Terminating session: None"}
175
+ {"timestamp": "2026-05-26T13:39:50.156389+00:00", "level": "INFO", "logger": "mcp.server.lowlevel.server", "message": "Processing request of type CallToolRequest"}
176
+ {"timestamp": "2026-05-26T13:39:50.168000+00:00", "level": "INFO", "logger": "httpx", "message": "HTTP Request: POST http://127.0.0.1:7860/gradio_api/queue/join \"HTTP/1.1 200 OK\""}
177
+ {"timestamp": "2026-05-26T13:39:50.168294+00:00", "tool": "extract_keywords", "event": "completed", "status": "success", "duration_ms": 0.17, "chars": 273, "words": 48, "lines": 3, "result_chars": 585, "level": "INFO", "logger": "text_tools"}
178
+ {"timestamp": "2026-05-26T13:39:50.180605+00:00", "level": "INFO", "logger": "httpx", "message": "HTTP Request: GET http://127.0.0.1:7860/gradio_api/queue/data?session_hash=b337fb51-848e-4e7b-87c1-439838f89846 \"HTTP/1.1 200 OK\""}
179
+ {"timestamp": "2026-05-26T13:39:50.725960+00:00", "level": "INFO", "logger": "mcp.server.streamable_http", "message": "Terminating session: None"}
180
+ {"timestamp": "2026-05-26T13:46:18.385676+00:00", "level": "INFO", "logger": "mcp.server.streamable_http", "message": "Terminating session: None"}
181
+ {"timestamp": "2026-05-26T13:46:18.407142+00:00", "level": "INFO", "logger": "mcp.server.streamable_http", "message": "Terminating session: None"}
182
+ {"timestamp": "2026-05-26T13:46:18.422290+00:00", "level": "INFO", "logger": "mcp.server.lowlevel.server", "message": "Processing request of type ListToolsRequest"}
183
+ {"timestamp": "2026-05-26T13:46:18.424384+00:00", "level": "INFO", "logger": "mcp.server.streamable_http", "message": "Terminating session: None"}
184
+ {"timestamp": "2026-05-26T13:46:18.586203+00:00", "level": "INFO", "logger": "mcp.server.lowlevel.server", "message": "Processing request of type ListResourcesRequest"}
185
+ {"timestamp": "2026-05-26T13:46:18.587925+00:00", "level": "INFO", "logger": "mcp.server.streamable_http", "message": "Terminating session: None"}
186
+ {"timestamp": "2026-05-26T13:46:18.608353+00:00", "level": "INFO", "logger": "mcp.server.lowlevel.server", "message": "Processing request of type ListResourceTemplatesRequest"}
187
+ {"timestamp": "2026-05-26T13:46:18.609389+00:00", "level": "INFO", "logger": "mcp.server.streamable_http", "message": "Terminating session: None"}
188
+ {"timestamp": "2026-05-26T13:47:12.151012+00:00", "level": "INFO", "logger": "mcp.server.streamable_http", "message": "Terminating session: None"}
189
+ {"timestamp": "2026-05-26T13:47:12.174477+00:00", "level": "INFO", "logger": "mcp.server.streamable_http", "message": "Terminating session: None"}
190
+ {"timestamp": "2026-05-26T13:47:12.195317+00:00", "level": "INFO", "logger": "mcp.server.lowlevel.server", "message": "Processing request of type ListToolsRequest"}
191
+ {"timestamp": "2026-05-26T13:47:12.196974+00:00", "level": "INFO", "logger": "mcp.server.streamable_http", "message": "Terminating session: None"}
192
+ {"timestamp": "2026-05-26T13:47:12.875276+00:00", "level": "INFO", "logger": "mcp.server.lowlevel.server", "message": "Processing request of type ListResourcesRequest"}
193
+ {"timestamp": "2026-05-26T13:47:12.877682+00:00", "level": "INFO", "logger": "mcp.server.streamable_http", "message": "Terminating session: None"}
194
+ {"timestamp": "2026-05-26T13:47:12.902043+00:00", "level": "INFO", "logger": "mcp.server.lowlevel.server", "message": "Processing request of type ListResourceTemplatesRequest"}
195
+ {"timestamp": "2026-05-26T13:47:12.903919+00:00", "level": "INFO", "logger": "mcp.server.streamable_http", "message": "Terminating session: None"}
196
+ {"timestamp": "2026-05-26T13:47:13.682698+00:00", "level": "INFO", "logger": "mcp.server.streamable_http", "message": "Terminating session: None"}
197
+ {"timestamp": "2026-05-26T13:47:13.712460+00:00", "level": "INFO", "logger": "mcp.server.streamable_http", "message": "Terminating session: None"}
198
+ {"timestamp": "2026-05-26T13:47:13.740515+00:00", "level": "INFO", "logger": "mcp.server.lowlevel.server", "message": "Processing request of type ListToolsRequest"}
199
+ {"timestamp": "2026-05-26T13:47:13.741958+00:00", "level": "INFO", "logger": "mcp.server.streamable_http", "message": "Terminating session: None"}
200
+ {"timestamp": "2026-05-26T13:47:50.850426+00:00", "level": "INFO", "logger": "mcp.server.streamable_http", "message": "Terminating session: None"}
201
+ {"timestamp": "2026-05-26T13:47:50.869949+00:00", "level": "INFO", "logger": "mcp.server.streamable_http", "message": "Terminating session: None"}
202
+ {"timestamp": "2026-05-26T13:47:50.894119+00:00", "level": "INFO", "logger": "mcp.server.lowlevel.server", "message": "Processing request of type ListToolsRequest"}
203
+ {"timestamp": "2026-05-26T13:47:50.896351+00:00", "level": "INFO", "logger": "mcp.server.streamable_http", "message": "Terminating session: None"}
204
+ {"timestamp": "2026-05-26T13:49:10.555970+00:00", "level": "INFO", "logger": "mcp.server.streamable_http", "message": "Terminating session: None"}
205
+ {"timestamp": "2026-05-26T13:49:10.579189+00:00", "level": "INFO", "logger": "mcp.server.streamable_http", "message": "Terminating session: None"}
206
+ {"timestamp": "2026-05-26T13:49:10.600677+00:00", "level": "INFO", "logger": "mcp.server.lowlevel.server", "message": "Processing request of type ListToolsRequest"}
207
+ {"timestamp": "2026-05-26T13:49:10.603817+00:00", "level": "INFO", "logger": "mcp.server.streamable_http", "message": "Terminating session: None"}
208
+ {"timestamp": "2026-05-26T13:51:02.627318+00:00", "level": "INFO", "logger": "mcp.server.lowlevel.server", "message": "Processing request of type CallToolRequest"}
209
+ {"timestamp": "2026-05-26T13:51:02.642952+00:00", "level": "INFO", "logger": "httpx", "message": "HTTP Request: POST http://127.0.0.1:7860/gradio_api/queue/join \"HTTP/1.1 200 OK\""}
210
+ {"timestamp": "2026-05-26T13:51:02.642299+00:00", "tool": "analyze_text", "event": "completed", "status": "success", "duration_ms": 0.11, "chars": 313, "words": 55, "lines": 1, "result_chars": 176, "level": "INFO", "logger": "text_tools"}
211
+ {"timestamp": "2026-05-26T13:51:02.663240+00:00", "level": "INFO", "logger": "httpx", "message": "HTTP Request: GET http://127.0.0.1:7860/gradio_api/queue/data?session_hash=b337fb51-848e-4e7b-87c1-439838f89846 \"HTTP/1.1 200 OK\""}
212
+ {"timestamp": "2026-05-26T13:51:03.202193+00:00", "level": "INFO", "logger": "mcp.server.streamable_http", "message": "Terminating session: None"}
213
+ {"timestamp": "2026-05-26T13:51:07.296204+00:00", "level": "INFO", "logger": "mcp.server.lowlevel.server", "message": "Processing request of type CallToolRequest"}
214
+ {"timestamp": "2026-05-26T13:51:07.312900+00:00", "level": "INFO", "logger": "httpx", "message": "HTTP Request: POST http://127.0.0.1:7860/gradio_api/queue/join \"HTTP/1.1 200 OK\""}
215
+ {"timestamp": "2026-05-26T13:51:07.316730+00:00", "tool": "extract_keywords", "event": "completed", "status": "success", "duration_ms": 0.29, "chars": 313, "words": 55, "lines": 1, "result_chars": 576, "level": "INFO", "logger": "text_tools"}
216
+ {"timestamp": "2026-05-26T13:51:07.322468+00:00", "level": "INFO", "logger": "httpx", "message": "HTTP Request: GET http://127.0.0.1:7860/gradio_api/queue/data?session_hash=b337fb51-848e-4e7b-87c1-439838f89846 \"HTTP/1.1 200 OK\""}
217
+ {"timestamp": "2026-05-26T13:51:07.871150+00:00", "level": "INFO", "logger": "mcp.server.streamable_http", "message": "Terminating session: None"}
218
+ {"timestamp": "2026-05-26T13:51:10.772131+00:00", "level": "INFO", "logger": "mcp.server.lowlevel.server", "message": "Processing request of type CallToolRequest"}
219
+ {"timestamp": "2026-05-26T13:51:10.782465+00:00", "level": "INFO", "logger": "httpx", "message": "HTTP Request: POST http://127.0.0.1:7860/gradio_api/queue/join \"HTTP/1.1 200 OK\""}
220
+ {"timestamp": "2026-05-26T13:51:10.782130+00:00", "tool": "check_reading_level", "event": "completed", "status": "success", "duration_ms": 0.08, "chars": 313, "words": 55, "lines": 1, "result_chars": 64, "level": "INFO", "logger": "text_tools"}
221
+ {"timestamp": "2026-05-26T13:51:10.795252+00:00", "level": "INFO", "logger": "httpx", "message": "HTTP Request: GET http://127.0.0.1:7860/gradio_api/queue/data?session_hash=b337fb51-848e-4e7b-87c1-439838f89846 \"HTTP/1.1 200 OK\""}
222
+ {"timestamp": "2026-05-26T13:51:11.342098+00:00", "level": "INFO", "logger": "mcp.server.streamable_http", "message": "Terminating session: None"}
223
+ {"timestamp": "2026-05-26T13:52:26.623087+00:00", "level": "INFO", "logger": "httpx", "message": "HTTP Request: HEAD https://huggingface.co/api/telemetry/https%3A/api.gradio.app/gradio-initiated-analytics \"HTTP/1.1 200 OK\""}
224
+ {"timestamp": "2026-05-26T13:52:26.692241+00:00", "level": "INFO", "logger": "mcp.server.streamable_http_manager", "message": "StreamableHTTP session manager started"}
225
+ {"timestamp": "2026-05-26T13:52:26.727603+00:00", "level": "INFO", "logger": "httpx", "message": "HTTP Request: GET http://127.0.0.1:7860/gradio_api/startup-events \"HTTP/1.1 200 OK\""}
226
+ {"timestamp": "2026-05-26T13:52:26.764866+00:00", "level": "INFO", "logger": "httpx", "message": "HTTP Request: HEAD http://127.0.0.1:7860/ \"HTTP/1.1 200 OK\""}
227
+ {"timestamp": "2026-05-26T13:52:26.899545+00:00", "level": "INFO", "logger": "httpx", "message": "HTTP Request: HEAD https://huggingface.co/api/telemetry/https%3A/api.gradio.app/gradio-launched-telemetry \"HTTP/1.1 200 OK\""}
228
+ {"timestamp": "2026-05-26T13:52:27.223868+00:00", "level": "INFO", "logger": "httpx", "message": "HTTP Request: GET https://api.gradio.app/pkg-version \"HTTP/1.1 200 OK\""}
229
+ {"timestamp": "2026-05-26T13:53:52.708442+00:00", "level": "INFO", "logger": "mcp.server.streamable_http", "message": "Terminating session: None"}
230
+ {"timestamp": "2026-05-26T13:53:52.750211+00:00", "level": "INFO", "logger": "mcp.server.streamable_http", "message": "Terminating session: None"}
231
+ {"timestamp": "2026-05-26T13:53:52.766435+00:00", "level": "INFO", "logger": "mcp.server.lowlevel.server", "message": "Processing request of type ListToolsRequest"}
232
+ {"timestamp": "2026-05-26T13:53:52.769355+00:00", "level": "INFO", "logger": "mcp.server.streamable_http", "message": "Terminating session: None"}
233
+ {"timestamp": "2026-05-26T13:53:53.189922+00:00", "level": "INFO", "logger": "mcp.server.lowlevel.server", "message": "Processing request of type ListResourcesRequest"}
234
+ {"timestamp": "2026-05-26T13:53:53.191028+00:00", "level": "INFO", "logger": "mcp.server.streamable_http", "message": "Terminating session: None"}
235
+ {"timestamp": "2026-05-26T13:53:53.206160+00:00", "level": "INFO", "logger": "mcp.server.lowlevel.server", "message": "Processing request of type ListResourceTemplatesRequest"}
236
+ {"timestamp": "2026-05-26T13:53:53.207297+00:00", "level": "INFO", "logger": "mcp.server.streamable_http", "message": "Terminating session: None"}
237
+ {"timestamp": "2026-05-26T13:57:01.096515+00:00", "level": "INFO", "logger": "mcp.server.lowlevel.server", "message": "Processing request of type CallToolRequest"}
238
+ {"timestamp": "2026-05-26T13:57:01.128081+00:00", "level": "INFO", "logger": "httpx", "message": "HTTP Request: GET http://127.0.0.1:7860/config \"HTTP/1.1 200 OK\""}
239
+ {"timestamp": "2026-05-26T13:57:01.139655+00:00", "level": "INFO", "logger": "httpx", "message": "HTTP Request: GET http://127.0.0.1:7860/gradio_api/info?serialize=False \"HTTP/1.1 200 OK\""}
240
+ {"timestamp": "2026-05-26T13:57:01.154258+00:00", "level": "INFO", "logger": "httpx", "message": "HTTP Request: GET http://127.0.0.1:7860/gradio_api/heartbeat/f0251319-219e-46f9-b220-1caa6c21a9c9 \"HTTP/1.1 200 OK\""}
241
+ {"timestamp": "2026-05-26T13:57:01.163872+00:00", "level": "INFO", "logger": "httpx", "message": "HTTP Request: POST http://127.0.0.1:7860/gradio_api/queue/join \"HTTP/1.1 200 OK\""}
242
+ {"timestamp": "2026-05-26T13:57:01.163397+00:00", "tool": "analyze_text", "event": "completed", "status": "success", "duration_ms": 0.08, "chars": 315, "words": 55, "lines": 3, "result_chars": 176, "level": "INFO", "logger": "text_tools"}
243
+ {"timestamp": "2026-05-26T13:57:01.178951+00:00", "level": "INFO", "logger": "httpx", "message": "HTTP Request: GET http://127.0.0.1:7860/gradio_api/queue/data?session_hash=f0251319-219e-46f9-b220-1caa6c21a9c9 \"HTTP/1.1 200 OK\""}
244
+ {"timestamp": "2026-05-26T13:57:01.343089+00:00", "level": "INFO", "logger": "httpx", "message": "HTTP Request: HEAD https://huggingface.co/api/telemetry/py_client/initiated \"HTTP/1.1 200 OK\""}
245
+ {"timestamp": "2026-05-26T13:57:01.725157+00:00", "level": "INFO", "logger": "mcp.server.streamable_http", "message": "Terminating session: None"}
246
+ {"timestamp": "2026-05-26T13:57:36.045952+00:00", "level": "INFO", "logger": "mcp.server.lowlevel.server", "message": "Processing request of type CallToolRequest"}
247
+ {"timestamp": "2026-05-26T13:57:36.053904+00:00", "level": "INFO", "logger": "httpx", "message": "HTTP Request: POST http://127.0.0.1:7860/gradio_api/queue/join \"HTTP/1.1 200 OK\""}
248
+ {"timestamp": "2026-05-26T13:57:36.055872+00:00", "tool": "extract_keywords", "event": "completed", "status": "success", "duration_ms": 0.13, "chars": 315, "words": 55, "lines": 3, "result_chars": 577, "level": "INFO", "logger": "text_tools"}
249
+ {"timestamp": "2026-05-26T13:57:36.065960+00:00", "level": "INFO", "logger": "httpx", "message": "HTTP Request: GET http://127.0.0.1:7860/gradio_api/queue/data?session_hash=f0251319-219e-46f9-b220-1caa6c21a9c9 \"HTTP/1.1 200 OK\""}
250
+ {"timestamp": "2026-05-26T13:57:36.612140+00:00", "level": "INFO", "logger": "mcp.server.streamable_http", "message": "Terminating session: None"}
251
+ {"timestamp": "2026-05-26T13:57:38.825342+00:00", "level": "INFO", "logger": "mcp.server.lowlevel.server", "message": "Processing request of type CallToolRequest"}
252
+ {"timestamp": "2026-05-26T13:57:38.833412+00:00", "level": "INFO", "logger": "httpx", "message": "HTTP Request: POST http://127.0.0.1:7860/gradio_api/queue/join \"HTTP/1.1 200 OK\""}
253
+ {"timestamp": "2026-05-26T13:57:38.833612+00:00", "tool": "check_reading_level", "event": "completed", "status": "success", "duration_ms": 0.08, "chars": 315, "words": 55, "lines": 3, "result_chars": 64, "level": "INFO", "logger": "text_tools"}
254
+ {"timestamp": "2026-05-26T13:57:38.844484+00:00", "level": "INFO", "logger": "httpx", "message": "HTTP Request: GET http://127.0.0.1:7860/gradio_api/queue/data?session_hash=f0251319-219e-46f9-b220-1caa6c21a9c9 \"HTTP/1.1 200 OK\""}
255
+ {"timestamp": "2026-05-26T13:57:39.389627+00:00", "level": "INFO", "logger": "mcp.server.streamable_http", "message": "Terminating session: None"}
256
+ {"timestamp": "2026-05-26T13:58:12.369874+00:00", "level": "INFO", "logger": "mcp.server.lowlevel.server", "message": "Processing request of type CallToolRequest"}
257
+ {"timestamp": "2026-05-26T13:58:12.380988+00:00", "level": "INFO", "logger": "httpx", "message": "HTTP Request: POST http://127.0.0.1:7860/gradio_api/queue/join \"HTTP/1.1 200 OK\""}
258
+ {"timestamp": "2026-05-26T13:58:12.380679+00:00", "tool": "analyze_text", "event": "completed", "status": "success", "duration_ms": 0.06, "chars": 315, "words": 55, "lines": 3, "result_chars": 176, "level": "INFO", "logger": "text_tools"}
259
+ {"timestamp": "2026-05-26T13:58:12.394994+00:00", "level": "INFO", "logger": "httpx", "message": "HTTP Request: GET http://127.0.0.1:7860/gradio_api/queue/data?session_hash=f0251319-219e-46f9-b220-1caa6c21a9c9 \"HTTP/1.1 200 OK\""}
260
+ {"timestamp": "2026-05-26T13:58:12.939824+00:00", "level": "INFO", "logger": "mcp.server.streamable_http", "message": "Terminating session: None"}
261
+ {"timestamp": "2026-05-26T13:58:28.559871+00:00", "level": "INFO", "logger": "mcp.server.lowlevel.server", "message": "Processing request of type CallToolRequest"}
262
+ {"timestamp": "2026-05-26T13:58:28.569748+00:00", "level": "INFO", "logger": "httpx", "message": "HTTP Request: POST http://127.0.0.1:7860/gradio_api/queue/join \"HTTP/1.1 200 OK\""}
263
+ {"timestamp": "2026-05-26T13:58:28.572372+00:00", "tool": "extract_keywords", "event": "completed", "status": "success", "duration_ms": 0.22, "chars": 315, "words": 55, "lines": 3, "result_chars": 577, "level": "INFO", "logger": "text_tools"}
264
+ {"timestamp": "2026-05-26T13:58:28.588689+00:00", "level": "INFO", "logger": "httpx", "message": "HTTP Request: GET http://127.0.0.1:7860/gradio_api/queue/data?session_hash=f0251319-219e-46f9-b220-1caa6c21a9c9 \"HTTP/1.1 200 OK\""}
265
+ {"timestamp": "2026-05-26T13:58:29.127947+00:00", "level": "INFO", "logger": "mcp.server.streamable_http", "message": "Terminating session: None"}
266
+ {"timestamp": "2026-05-26T13:58:38.412009+00:00", "level": "INFO", "logger": "mcp.server.lowlevel.server", "message": "Processing request of type CallToolRequest"}
267
+ {"timestamp": "2026-05-26T13:58:38.417847+00:00", "level": "INFO", "logger": "httpx", "message": "HTTP Request: POST http://127.0.0.1:7860/gradio_api/queue/join \"HTTP/1.1 200 OK\""}
268
+ {"timestamp": "2026-05-26T13:58:38.421093+00:00", "tool": "check_reading_level", "event": "completed", "status": "success", "duration_ms": 0.06, "chars": 315, "words": 55, "lines": 3, "result_chars": 64, "level": "INFO", "logger": "text_tools"}
269
+ {"timestamp": "2026-05-26T13:58:38.427623+00:00", "level": "INFO", "logger": "httpx", "message": "HTTP Request: GET http://127.0.0.1:7860/gradio_api/queue/data?session_hash=f0251319-219e-46f9-b220-1caa6c21a9c9 \"HTTP/1.1 200 OK\""}
270
+ {"timestamp": "2026-05-26T13:58:38.975364+00:00", "level": "INFO", "logger": "mcp.server.streamable_http", "message": "Terminating session: None"}
text_tools.py ADDED
@@ -0,0 +1,472 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import logging
3
+ import re
4
+ import time
5
+ import unicodedata
6
+ from collections import Counter
7
+ from datetime import datetime, timezone
8
+ from difflib import get_close_matches
9
+ from functools import wraps
10
+
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+ POSITIVE_WORDS = {
15
+ "alegre", "amor", "bueno", "buen", "bonito", "brillante", "calma", "feliz",
16
+ "genial", "grande", "hermoso", "ideal", "increible", "maravilloso", "mejor",
17
+ "optimista", "paz", "positivo", "sano", "satisfecho", "triunfo", "victoria",
18
+ }
19
+ NEGATIVE_WORDS = {
20
+ "odio", "malo", "mal", "triste", "angustia", "asco", "depresion", "dolor",
21
+ "enfermo", "fracaso", "furia", "horrible", "miedo", "negativo", "pesimo",
22
+ "rabia", "sufrimiento", "terrible", "vacio", "desamor", "desesperacion",
23
+ }
24
+ LANGUAGE_STOPWORDS = {
25
+ "es": {
26
+ "el", "la", "los", "las", "de", "del", "y", "que", "en", "un", "una", "por",
27
+ "con", "para", "no", "se", "es", "al", "lo", "como", "mas", "pero",
28
+ "sus", "ya", "o", "si", "entre", "cuando", "muy", "sin", "sobre", "me", "mi",
29
+ },
30
+ "en": {
31
+ "the", "and", "to", "of", "a", "in", "is", "it", "you", "that", "he", "was",
32
+ "for", "on", "are", "with", "as", "i", "his", "they", "be", "at", "one", "have",
33
+ },
34
+ "fr": {
35
+ "le", "la", "les", "de", "des", "et", "un", "une", "en", "que", "est", "pour",
36
+ "dans", "pas", "qui", "du", "au", "sur", "ce", "il", "elle", "nous", "vous",
37
+ },
38
+ "pt": {
39
+ "o", "a", "os", "as", "de", "do", "da", "e", "em", "que", "um", "uma", "para",
40
+ "com", "nao", "por", "como", "mais", "mas", "se", "dos", "das", "no",
41
+ },
42
+ "it": {
43
+ "il", "la", "lo", "gli", "le", "di", "e", "che", "un", "una", "per", "con",
44
+ "non", "come", "piu", "su", "del", "della", "nel", "nei", "da", "al",
45
+ },
46
+ }
47
+
48
+ SPANISH_COMMON_WORDS = {
49
+ "agua", "ahora", "aire", "alguien", "algo", "amigo", "amor", "antes", "aqui",
50
+ "aun", "bajo", "bastante", "bien", "cabe", "cada", "camino", "casa", "cerca",
51
+ "ciudad", "claro", "comer", "cosa", "cuerpo", "dia", "diez", "donde", "dueno",
52
+ "ejemplo", "entonces", "escribir", "escuela", "este", "familia", "feliz", "fuerza",
53
+ "gente", "grande", "hablar", "hacer", "hombre", "hoy", "idea", "jugar", "lugar",
54
+ "mano", "mar", "mientras", "mundo", "nada", "niño", "noches", "nombre", "noche",
55
+ "octubre", "ojo", "pais", "palabra", "paz", "persona", "problema", "puede", "pueblo",
56
+ "querer", "razon", "salir", "saber", "sala", "sierra", "soria", "tarde", "tiempo",
57
+ "tierra", "trabajo", "todo", "trigo", "ultramar", "vida", "viaje", "viajero", "voz",
58
+ }
59
+ ENGLISH_COMMON_WORDS = {
60
+ "about", "after", "again", "air", "all", "also", "am", "an", "and", "any", "are",
61
+ "back", "be", "because", "before", "big", "book", "but", "by", "can", "come",
62
+ "day", "do", "down", "each", "end", "even", "family", "for", "from", "give",
63
+ "good", "great", "had", "has", "have", "he", "her", "here", "his", "home", "house",
64
+ "how", "i", "if", "in", "into", "it", "its", "just", "know", "like", "little",
65
+ "look", "make", "man", "many", "me", "more", "most", "my", "new", "no", "not",
66
+ "now", "one", "or", "our", "out", "people", "place", "said", "see", "she", "so",
67
+ "some", "time", "to", "two", "up", "us", "very", "was", "we", "well", "what",
68
+ "when", "where", "which", "who", "why", "will", "with", "work", "world", "you",
69
+ }
70
+
71
+
72
+ def _summarize_text(text: str) -> dict:
73
+ words = text.split()
74
+ return {
75
+ "chars": len(text),
76
+ "words": len(words),
77
+ "lines": text.count("\n") + 1 if text else 0,
78
+ }
79
+
80
+
81
+ def _normalize_word(word: str) -> str:
82
+ word = word.lower().strip()
83
+ word = re.sub(r"^[^\wáéíóúüñ]+|[^\wáéíóúüñ]+$", "", word, flags=re.UNICODE)
84
+ normalized = unicodedata.normalize("NFD", word)
85
+ return "".join(char for char in normalized if unicodedata.category(char) != "Mn")
86
+
87
+
88
+ def _normalize_words_from_source(words: set[str]) -> set[str]:
89
+ return {_normalize_word(word) for word in words}
90
+
91
+
92
+ SPELLING_LEXICONS = {
93
+ "es": (
94
+ _normalize_words_from_source(LANGUAGE_STOPWORDS["es"])
95
+ | _normalize_words_from_source(SPANISH_COMMON_WORDS)
96
+ | _normalize_words_from_source(POSITIVE_WORDS)
97
+ | _normalize_words_from_source(NEGATIVE_WORDS)
98
+ ),
99
+ "en": (
100
+ _normalize_words_from_source(LANGUAGE_STOPWORDS["en"])
101
+ | _normalize_words_from_source(ENGLISH_COMMON_WORDS)
102
+ | _normalize_words_from_source(POSITIVE_WORDS)
103
+ | _normalize_words_from_source(NEGATIVE_WORDS)
104
+ ),
105
+ }
106
+
107
+
108
+ def _utc_now_iso() -> str:
109
+ return datetime.now(timezone.utc).isoformat()
110
+
111
+
112
+ def _json_log(level: int, payload: dict) -> None:
113
+ logger.log(level, json.dumps(payload, ensure_ascii=False, separators=(",", ":")))
114
+
115
+
116
+ def _log_tool(name: str):
117
+ def decorator(func):
118
+ @wraps(func)
119
+ def wrapper(*args, **kwargs):
120
+ metadata = {}
121
+ if args and isinstance(args[0], str):
122
+ metadata.update(_summarize_text(args[0]))
123
+ if "count" in kwargs:
124
+ metadata["count"] = kwargs["count"]
125
+
126
+ start = time.perf_counter()
127
+ try:
128
+ result = func(*args, **kwargs)
129
+ duration_ms = round((time.perf_counter() - start) * 1000, 2)
130
+ _json_log(logging.INFO, {
131
+ "timestamp": _utc_now_iso(),
132
+ "tool": name,
133
+ "event": "completed",
134
+ "status": "success",
135
+ "duration_ms": duration_ms,
136
+ **metadata,
137
+ "result_chars": len(result) if isinstance(result, str) else None,
138
+ })
139
+ return result
140
+ except Exception:
141
+ duration_ms = round((time.perf_counter() - start) * 1000, 2)
142
+ _json_log(logging.ERROR, {
143
+ "timestamp": _utc_now_iso(),
144
+ "tool": name,
145
+ "event": "completed",
146
+ "status": "error",
147
+ "duration_ms": duration_ms,
148
+ **metadata,
149
+ })
150
+ raise
151
+
152
+ return wrapper
153
+
154
+ return decorator
155
+
156
+
157
+ def _split_sentences(text: str) -> list[str]:
158
+ cleaned = text.strip()
159
+ if not cleaned:
160
+ return []
161
+ sentences = re.split(r"(?<=[.!?])\s+", cleaned)
162
+ return [sentence.strip() for sentence in sentences if sentence.strip()]
163
+
164
+
165
+ def _extract_tokens(text: str) -> list[str]:
166
+ return [token for token in re.findall(r"[A-Za-zÁÉÍÓÚÜÑáéíóúüñ]+", text)]
167
+
168
+
169
+ def _language_info(text: str) -> dict:
170
+ tokens = [_normalize_word(token) for token in _extract_tokens(text)]
171
+ tokens = [token for token in tokens if token]
172
+ token_set = set(tokens)
173
+
174
+ scores = {}
175
+ evidence = {}
176
+ accent_signals = {
177
+ "es": {"ñ", "á", "é", "í", "ó", "ú", "¿", "¡"},
178
+ "fr": {"à", "â", "ç", "é", "è", "ê", "ë", "î", "ï", "ô", "ù", "û", "ü"},
179
+ "pt": {"ã", "ç", "á", "à", "â", "é", "ê", "í", "ó", "ô", "õ", "ú"},
180
+ "it": {"à", "è", "é", "ì", "ò", "ù"},
181
+ }
182
+
183
+ lowered_text = text.lower()
184
+ for language, stopwords in LANGUAGE_STOPWORDS.items():
185
+ stopword_hits = sum(1 for token in token_set if token in stopwords)
186
+ accent_hits = sum(1 for char in lowered_text if char in accent_signals.get(language, set()))
187
+ scores[language] = stopword_hits + (accent_hits * 0.5)
188
+ evidence[language] = {
189
+ "stopword_hits": stopword_hits,
190
+ "accent_hits": accent_hits,
191
+ }
192
+
193
+ best_language = max(scores, key=scores.get) if scores else "unknown"
194
+ best_score = scores.get(best_language, 0)
195
+ second_best = sorted(scores.values(), reverse=True)[1] if len(scores) > 1 else 0
196
+ confidence = round(((best_score - second_best) / max(best_score, 1)) * 100, 1) if best_score else 0.0
197
+
198
+ if best_score == 0:
199
+ detected = "unknown"
200
+ confidence = 0.0
201
+ else:
202
+ detected = best_language
203
+
204
+ return {
205
+ "language": detected,
206
+ "confidence": confidence,
207
+ "scores": scores,
208
+ "evidence": evidence,
209
+ "token_count": len(tokens),
210
+ "tokens": tokens,
211
+ }
212
+
213
+
214
+ def _sentence_stopwords(language: str) -> set[str]:
215
+ return LANGUAGE_STOPWORDS.get(language, LANGUAGE_STOPWORDS["es"])
216
+
217
+
218
+ def _spell_lexicon(language: str) -> set[str]:
219
+ if language in SPELLING_LEXICONS:
220
+ return SPELLING_LEXICONS[language]
221
+ return SPELLING_LEXICONS["es"] | SPELLING_LEXICONS["en"]
222
+
223
+
224
+ def _clean_result_sentences(sentences: list[str]) -> list[str]:
225
+ return [sentence.strip() for sentence in sentences if sentence.strip()]
226
+
227
+
228
+ @_log_tool("analyze_text")
229
+ def analyze_text(text: str) -> str:
230
+ words = text.split()
231
+ chars = len(text)
232
+ chars_no_spaces = len(text.replace(" ", ""))
233
+ sentences = text.count(".") + text.count("!") + text.count("?")
234
+
235
+ avg_word_length = round(chars_no_spaces / len(words), 2) if words else 0
236
+ avg_sentence_length = round(len(words) / max(sentences, 1), 2)
237
+
238
+ return json.dumps({
239
+ "total_characters": chars,
240
+ "characters_without_spaces": chars_no_spaces,
241
+ "total_words": len(words),
242
+ "total_sentences": max(sentences, 1),
243
+ "average_word_length": avg_word_length,
244
+ "average_sentence_length": avg_sentence_length,
245
+ }, indent=2, ensure_ascii=False)
246
+
247
+
248
+ @_log_tool("extract_keywords")
249
+ def extract_keywords(text: str, count: int = 5) -> str:
250
+ stopwords = {
251
+ "the", "a", "an", "and", "or", "but", "in", "on", "at", "to", "for",
252
+ "of", "with", "is", "are", "was", "were", "be", "been", "by", "from",
253
+ }
254
+
255
+ words = text.lower().split()
256
+ filtered = [w.strip(".,!?;:") for w in words if w.lower() not in stopwords]
257
+
258
+ word_freq = Counter(filtered)
259
+ top_words = word_freq.most_common(count)
260
+
261
+ return json.dumps({
262
+ "keywords": [{"word": w, "frequency": f} for w, f in top_words]
263
+ }, indent=2, ensure_ascii=False)
264
+
265
+
266
+ @_log_tool("check_reading_level")
267
+ def check_reading_level(text: str) -> str:
268
+ sentences = max(text.count(".") + text.count("!") + text.count("?"), 1)
269
+ words = len(text.split())
270
+ vowels = "aeiou"
271
+ syllables = sum(1 for c in text.lower() if c in vowels)
272
+
273
+ if words == 0:
274
+ return json.dumps({"error": "No text to analyze"}, ensure_ascii=False)
275
+
276
+ grade = max(0, (0.39 * (words / sentences)) + (11.8 * (syllables / words)) - 15.59)
277
+
278
+ if grade < 6:
279
+ level = "Elementary School"
280
+ elif grade < 9:
281
+ level = "Middle School"
282
+ elif grade < 13:
283
+ level = "High School"
284
+ else:
285
+ level = "College/Academic"
286
+
287
+ return json.dumps({
288
+ "grade_level": round(grade, 1),
289
+ "reading_level": level,
290
+ }, indent=2, ensure_ascii=False)
291
+
292
+
293
+ @_log_tool("analyze_sentiment")
294
+ def analyze_sentiment(text: str) -> str:
295
+ tokens = [_normalize_word(token) for token in _extract_tokens(text)]
296
+ tokens = [token for token in tokens if token]
297
+
298
+ positive_hits = sum(1 for token in tokens if token in POSITIVE_WORDS)
299
+ negative_hits = sum(1 for token in tokens if token in NEGATIVE_WORDS)
300
+ total_hits = positive_hits + negative_hits
301
+
302
+ if total_hits == 0:
303
+ label = "neutral"
304
+ score = 0
305
+ else:
306
+ score = positive_hits - negative_hits
307
+ if score > 0:
308
+ label = "positive"
309
+ elif score < 0:
310
+ label = "negative"
311
+ else:
312
+ label = "neutral"
313
+
314
+ confidence = round((abs(score) / total_hits) * 100, 1) if total_hits else 0.0
315
+
316
+ return json.dumps({
317
+ "sentiment": label,
318
+ "score": score,
319
+ "confidence": confidence,
320
+ "positive_hits": positive_hits,
321
+ "negative_hits": negative_hits,
322
+ "total_scored_tokens": total_hits,
323
+ }, indent=2, ensure_ascii=False)
324
+
325
+
326
+ @_log_tool("detect_language")
327
+ def detect_language(text: str) -> str:
328
+ info = _language_info(text)
329
+ return json.dumps({
330
+ "language": info["language"],
331
+ "confidence": info["confidence"],
332
+ "scores": info["scores"],
333
+ "evidence": info["evidence"],
334
+ "token_count": info["token_count"],
335
+ }, indent=2, ensure_ascii=False)
336
+
337
+
338
+ @_log_tool("summarize_text")
339
+ def summarize_text(text: str) -> str:
340
+ sentences = _clean_result_sentences(_split_sentences(text))
341
+ if not sentences:
342
+ return json.dumps({"error": "No text to summarize"}, ensure_ascii=False)
343
+
344
+ language = _language_info(text)["language"]
345
+ stopwords = _sentence_stopwords(language)
346
+ tokens = [_normalize_word(token) for token in _extract_tokens(text)]
347
+ tokens = [token for token in tokens if token and token not in stopwords]
348
+
349
+ if len(sentences) <= 2:
350
+ summary = " ".join(sentences)
351
+ return json.dumps({
352
+ "summary": summary,
353
+ "method": "direct",
354
+ "source_sentences": len(sentences),
355
+ "selected_sentences": len(sentences),
356
+ }, indent=2, ensure_ascii=False)
357
+
358
+ frequency = Counter(tokens)
359
+ sentence_scores = []
360
+ for index, sentence in enumerate(sentences):
361
+ sentence_tokens = [_normalize_word(token) for token in _extract_tokens(sentence)]
362
+ sentence_tokens = [token for token in sentence_tokens if token and token not in stopwords]
363
+ if not sentence_tokens:
364
+ continue
365
+ score = sum(frequency[token] for token in sentence_tokens) / (len(sentence_tokens) ** 0.5)
366
+ sentence_scores.append((index, score, sentence))
367
+
368
+ if not sentence_scores:
369
+ summary_sentences = sentences[:2]
370
+ else:
371
+ max_sentences = 1 if len(sentences) < 4 else 2
372
+ selected_indexes = sorted(
373
+ index for index, _, _ in sorted(sentence_scores, key=lambda item: item[1], reverse=True)[:max_sentences]
374
+ )
375
+ summary_sentences = [sentences[index] for index in selected_indexes]
376
+
377
+ summary = " ".join(summary_sentences)
378
+ if len(summary) > 320:
379
+ summary = summary[:317].rsplit(" ", 1)[0] + "..."
380
+
381
+ return json.dumps({
382
+ "summary": summary,
383
+ "method": "extractive",
384
+ "source_sentences": len(sentences),
385
+ "selected_sentences": len(summary_sentences),
386
+ }, indent=2, ensure_ascii=False)
387
+
388
+
389
+ @_log_tool("spell_check")
390
+ def spell_check(text: str) -> str:
391
+ info = _language_info(text)
392
+ language = info["language"] if info["language"] in {"es", "en"} else "es"
393
+ lexicon = _spell_lexicon(language)
394
+
395
+ raw_words = re.findall(r"[A-Za-zÁÉÍÓÚÜÑáéíóúüñ]+", text)
396
+ misspellings = []
397
+
398
+ for raw_word in raw_words:
399
+ normalized = _normalize_word(raw_word)
400
+ if not normalized:
401
+ continue
402
+ if normalized in lexicon:
403
+ continue
404
+ if raw_word[:1].isupper() and raw_word[1:].islower():
405
+ continue
406
+ if len(normalized) <= 2:
407
+ continue
408
+
409
+ suggestions = get_close_matches(normalized, list(lexicon), n=3, cutoff=0.84)
410
+ misspellings.append({
411
+ "word": raw_word,
412
+ "normalized": normalized,
413
+ "suggestions": suggestions,
414
+ })
415
+
416
+ unique_misspellings = []
417
+ seen = set()
418
+ for item in misspellings:
419
+ key = item["normalized"]
420
+ if key in seen:
421
+ continue
422
+ seen.add(key)
423
+ unique_misspellings.append(item)
424
+
425
+ return json.dumps({
426
+ "language": language,
427
+ "misspelled_words": unique_misspellings,
428
+ "misspelled_count": len(unique_misspellings),
429
+ "dictionary_mode": "heuristic",
430
+ "note": "Heuristic spell checker; proper nouns and technical terms may require manual review.",
431
+ }, indent=2, ensure_ascii=False)
432
+
433
+
434
+ @_log_tool("readability_advice")
435
+ def readability_advice(text: str) -> str:
436
+ words = text.split()
437
+ sentences = _clean_result_sentences(_split_sentences(text))
438
+ sentence_count = max(len(sentences), 1)
439
+ word_count = len(words)
440
+ char_count = len(text)
441
+ avg_sentence_length = round(word_count / sentence_count, 2)
442
+ avg_word_length = round((len(text.replace(" ", "")) / word_count), 2) if word_count else 0
443
+ long_words = sum(1 for word in words if len(_normalize_word(word)) >= 12)
444
+ paragraphs = [paragraph for paragraph in text.split("\n\n") if paragraph.strip()]
445
+
446
+ advice = []
447
+ if avg_sentence_length > 24:
448
+ advice.append("Divide las oraciones largas para reducir la carga cognitiva.")
449
+ if avg_sentence_length > 35:
450
+ advice.append("Hay oraciones muy extensas; considera fragmentarlas en dos o más frases.")
451
+ if avg_word_length > 6:
452
+ advice.append("Sustituye palabras largas o rebuscadas por términos más directos cuando sea posible.")
453
+ if long_words / max(word_count, 1) > 0.2:
454
+ advice.append("Hay muchos términos largos; mezcla vocabulario complejo con palabras más simples.")
455
+ if word_count > 120 and len(paragraphs) <= 1:
456
+ advice.append("Se beneficiaría de párrafos más cortos y espacios visuales entre ideas.")
457
+ if sentence_count < 3 and word_count > 80:
458
+ advice.append("Intenta repartir el contenido en más frases para mejorar el ritmo de lectura.")
459
+ if not advice:
460
+ advice.append("La claridad general es buena; solo revisa si puedes simplificar alguna frase puntual.")
461
+
462
+ return json.dumps({
463
+ "metrics": {
464
+ "characters": char_count,
465
+ "words": word_count,
466
+ "sentences": sentence_count,
467
+ "average_sentence_length": avg_sentence_length,
468
+ "average_word_length": avg_word_length,
469
+ "long_word_ratio": round(long_words / max(word_count, 1), 3),
470
+ },
471
+ "advice": advice,
472
+ }, indent=2, ensure_ascii=False)