app.py CHANGED
@@ -1,6 +1,9 @@
 
1
  from typing import NamedTuple
2
 
3
  import gradio as gr
 
 
4
 
5
  from tdagent.tools.get_domain_information import (
6
  dns_enumeration_tool,
@@ -26,6 +29,21 @@ from tdagent.tools.virus_total import gr_virus_total_url_info
26
  ## Tools to load into the application interface ##
27
 
28
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
  class ToolInfo(NamedTuple):
30
  """Gradio MCP tool info."""
31
 
@@ -55,11 +73,60 @@ TOOLS = (
55
  )
56
 
57
  ## Application Interface ##
58
- gr_app = gr.TabbedInterface(
59
- interface_list=[t_info.interface for t_info in TOOLS],
60
- tab_names=[t_info.name for t_info in TOOLS],
61
- title="TDAgentTools",
62
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
63
 
64
  if __name__ == "__main__":
65
  gr_app.launch(mcp_server=True)
 
1
+ from pathlib import Path
2
  from typing import NamedTuple
3
 
4
  import gradio as gr
5
+ import gradio.themes as gr_themes
6
+ import markdown
7
 
8
  from tdagent.tools.get_domain_information import (
9
  dns_enumeration_tool,
 
29
  ## Tools to load into the application interface ##
30
 
31
 
32
+ def _read_markdown_body_as_html(path: str = "README.md") -> str:
33
+ with Path(path).open(encoding="utf-8") as f: # Default mode is "r"
34
+ lines = f.readlines()
35
+
36
+ # Skip YAML front matter if present
37
+ if lines and lines[0].strip() == "---":
38
+ for i in range(1, len(lines)):
39
+ if lines[i].strip() == "---":
40
+ lines = lines[i + 1 :] # skip metadata block
41
+ break
42
+
43
+ markdown_body = "".join(lines).strip()
44
+ return markdown.markdown(markdown_body)
45
+
46
+
47
  class ToolInfo(NamedTuple):
48
  """Gradio MCP tool info."""
49
 
 
73
  )
74
 
75
  ## Application Interface ##
76
+
77
+ custom_css = """
78
+ .main-header {
79
+ background: linear-gradient(135deg, #00a388 0%, #ffae00 100%);
80
+ padding: 30px;
81
+ border-radius: 5px;
82
+ margin-bottom: 20px;
83
+ text-align: center;
84
+ }
85
+ """
86
+ with (
87
+ gr.Blocks(
88
+ theme=gr_themes.Origin(
89
+ primary_hue="teal",
90
+ spacing_size="sm",
91
+ font="sans-serif",
92
+ ),
93
+ title="TDAgent",
94
+ fill_height=True,
95
+ fill_width=True,
96
+ css=custom_css,
97
+ ) as gr_app,
98
+ ):
99
+ gr.HTML(
100
+ """
101
+ <div class="main-header">
102
+ <h1>👩‍💻 TDAgentTools & TDAgent 👨‍💻</h1>
103
+ <p style="font-size: 1.2em; margin: 10px 0 0 0;">
104
+ Empowering Cybersecurity with Agentic AI
105
+ </p>
106
+ </div>
107
+ """,
108
+ )
109
+ with gr.Tabs():
110
+ with gr.TabItem("About"):
111
+ html_content = _read_markdown_body_as_html("README.md")
112
+ gr.Markdown(html_content)
113
+ with gr.TabItem("TDAgentTools"):
114
+ gr.TabbedInterface(
115
+ interface_list=[t_info.interface for t_info in TOOLS],
116
+ tab_names=[t_info.name for t_info in TOOLS],
117
+ title="TDAgentTools",
118
+ )
119
+ with gr.TabItem("Demo"):
120
+ gr.Markdown(
121
+ """
122
+ This is a demo of TDAgentTools, a simple MCP server.
123
+ Be carefull with using well-known urls for malware distribution
124
+ when using the url content extractor tool.
125
+ """,
126
+ )
127
+ gr.HTML(
128
+ """<iframe width="560" height="315" src="https://youtube.com/embed/c7Yg_jOD6J0" frameborder="0" allowfullscreen></iframe>""", # noqa: E501
129
+ )
130
 
131
  if __name__ == "__main__":
132
  gr_app.launch(mcp_server=True)
pyproject.toml CHANGED
@@ -19,6 +19,7 @@ dependencies = [
19
  "cachetools>=6.0.0",
20
  "dnspython>=2.7.0",
21
  "gradio[mcp]>=5.32.1",
 
22
  "python-whois>=0.9.5",
23
  "requests>=2.32.3",
24
  "vt-py~=0.21.0",
 
19
  "cachetools>=6.0.0",
20
  "dnspython>=2.7.0",
21
  "gradio[mcp]>=5.32.1",
22
+ "markdown>=3.8",
23
  "python-whois>=0.9.5",
24
  "requests>=2.32.3",
25
  "vt-py~=0.21.0",
requirements-dev.txt CHANGED
@@ -129,6 +129,8 @@ jinja2==3.1.6
129
  # via gradio
130
  license-expression==30.4.1
131
  # via cyclonedx-python-lib
 
 
132
  markdown-it-py==3.0.0
133
  # via rich
134
  markupsafe==3.0.2
 
129
  # via gradio
130
  license-expression==30.4.1
131
  # via cyclonedx-python-lib
132
+ markdown==3.8
133
+ # via tdagent
134
  markdown-it-py==3.0.0
135
  # via rich
136
  markupsafe==3.0.2
requirements.txt CHANGED
@@ -110,6 +110,8 @@ iniconfig==2.1.0
110
  # via pytest
111
  jinja2==3.1.6
112
  # via gradio
 
 
113
  markdown-it-py==3.0.0 ; sys_platform != 'emscripten'
114
  # via rich
115
  markupsafe==3.0.2
 
110
  # via pytest
111
  jinja2==3.1.6
112
  # via gradio
113
+ markdown==3.8
114
+ # via tdagent
115
  markdown-it-py==3.0.0 ; sys_platform != 'emscripten'
116
  # via rich
117
  markupsafe==3.0.2
tdagent/tools/get_domain_information.py CHANGED
@@ -325,39 +325,44 @@ def retrieve_ioc_from_threatfox(potentially_ioc: str) -> str:
325
 
326
  geo_location_tool = gr.Interface(
327
  fn=get_geolocation,
328
- inputs=["text"],
329
- outputs="json",
330
  title="Domain Associated Geolocation Finder",
331
  description="Retrieves the geolocation associated to an input ip address",
332
  theme="default",
 
333
  )
334
 
335
  dns_enumeration_tool = gr.Interface(
336
  fn=enumerate_dns,
337
- inputs=["text"],
338
- outputs="json",
339
  title="DNS record enumerator of domains",
340
  description="Retrieves several dns record types for the input domain names",
341
  theme="default",
 
342
  )
343
 
344
  scrap_subdomains_tool = gr.Interface(
345
  fn=scrap_subdomains_for_domain,
346
- inputs=["text"],
347
- outputs="json",
348
  title="Subdomains Extractor of domains",
349
  description="Retrieves the subdomains for the input domain if they are common",
350
  theme="default",
 
351
  )
352
 
353
  extractor_of_ioc_from_threatfox_tool = gr.Interface(
354
  fn=retrieve_ioc_from_threatfox,
355
- inputs=["text"],
356
- outputs="text",
357
  title="IoC information extractor associated to particular entities",
358
  description=(
359
  "If information as an Indicator of Compromise (IoC) exists "
360
  "for the input url, domain or hash, it retrieves it"
361
  ),
362
  theme="default",
 
 
363
  )
 
325
 
326
  geo_location_tool = gr.Interface(
327
  fn=get_geolocation,
328
+ inputs=gr.Textbox(label="ip"),
329
+ outputs=gr.JSON(label="Geolocation of IP"),
330
  title="Domain Associated Geolocation Finder",
331
  description="Retrieves the geolocation associated to an input ip address",
332
  theme="default",
333
+ examples=["1.0.3.255", "59.34.7.3"],
334
  )
335
 
336
  dns_enumeration_tool = gr.Interface(
337
  fn=enumerate_dns,
338
+ inputs=gr.Textbox(label="domain"),
339
+ outputs=gr.JSON(label="DNS records"),
340
  title="DNS record enumerator of domains",
341
  description="Retrieves several dns record types for the input domain names",
342
  theme="default",
343
+ examples=["owasp.org", "nist.gov"],
344
  )
345
 
346
  scrap_subdomains_tool = gr.Interface(
347
  fn=scrap_subdomains_for_domain,
348
+ inputs=gr.Textbox(label="domain"),
349
+ outputs=gr.JSON(label="Subdomains managed by domain"),
350
  title="Subdomains Extractor of domains",
351
  description="Retrieves the subdomains for the input domain if they are common",
352
  theme="default",
353
+ examples=["github.com", "netacea.com"],
354
  )
355
 
356
  extractor_of_ioc_from_threatfox_tool = gr.Interface(
357
  fn=retrieve_ioc_from_threatfox,
358
+ inputs=gr.Textbox(label="IoC - url, domains or hash"),
359
+ outputs=gr.Text(label="Entity information as an IoC"),
360
  title="IoC information extractor associated to particular entities",
361
  description=(
362
  "If information as an Indicator of Compromise (IoC) exists "
363
  "for the input url, domain or hash, it retrieves it"
364
  ),
365
  theme="default",
366
+ examples=["advertipros.com", "dev.couplesparks.com"],
367
+ example_labels=["👾 IoC 1", "👾 IoC 2"],
368
  )
tdagent/tools/get_url_content.py CHANGED
@@ -51,13 +51,16 @@ def get_url_http_content(
51
 
52
  gr_get_url_http_content = gr.Interface(
53
  fn=get_url_http_content,
54
- inputs=["text", "text"],
55
- outputs="text",
56
  title="Get the content of a URL using an HTTP GET request.",
57
  description=(
58
  "Get the content of a URL in one of the specified content types."
59
  " The server may not honor the content type and if it fails the"
60
  " reason should also be returned with the corresponding HTTP"
61
- " error code."
62
  ),
 
 
 
63
  )
 
51
 
52
  gr_get_url_http_content = gr.Interface(
53
  fn=get_url_http_content,
54
+ inputs=[gr.Textbox(label="url"), gr.Textbox(label="content type")],
55
+ outputs=gr.Text(label="content"),
56
  title="Get the content of a URL using an HTTP GET request.",
57
  description=(
58
  "Get the content of a URL in one of the specified content types."
59
  " The server may not honor the content type and if it fails the"
60
  " reason should also be returned with the corresponding HTTP"
61
+ " error code. Be wary of retrieving the content of malicious urls."
62
  ),
63
+ examples=[
64
+ ["https://google.com", "html"],
65
+ ],
66
  )
tdagent/tools/query_abuse_ip_db.py CHANGED
@@ -156,11 +156,13 @@ def query_abuseipdb(ip_address: str, days: int = 30) -> str:
156
 
157
  gr_query_abuseipdb = gr.Interface(
158
  fn=query_abuseipdb,
159
- inputs=["text"],
160
- outputs="text",
161
  title="AbuseIPDB IP Checker",
162
  description=(
163
  "Check if an IP address has been reported for abusive behavior"
164
  " using AbuseIP DB API"
165
  ),
 
 
166
  )
 
156
 
157
  gr_query_abuseipdb = gr.Interface(
158
  fn=query_abuseipdb,
159
+ inputs=gr.Textbox(label="ip"),
160
+ outputs=gr.Text(label="Report on abusive behaviour"),
161
  title="AbuseIPDB IP Checker",
162
  description=(
163
  "Check if an IP address has been reported for abusive behavior"
164
  " using AbuseIP DB API"
165
  ),
166
+ examples=["5.252.155.14", "77.239.99.248"],
167
+ example_labels=["👾 Malicious IP 1", "👾 Malicious IP 2"],
168
  )
tdagent/tools/rdap.py CHANGED
@@ -102,8 +102,9 @@ def query_rdap( # noqa: PLR0911
102
 
103
  gr_query_rdap = gr.Interface(
104
  fn=query_rdap,
105
- inputs=["text"],
106
- outputs="json",
107
  title="Get RDAP information for a given URL.",
108
  description="Query a RDAP database to gather information about a url or domain.",
 
109
  )
 
102
 
103
  gr_query_rdap = gr.Interface(
104
  fn=query_rdap,
105
+ inputs=gr.Textbox(label="url or ip"),
106
+ outputs=gr.JSON(label="Report from RDAP"),
107
  title="Get RDAP information for a given URL.",
108
  description="Query a RDAP database to gather information about a url or domain.",
109
+ examples=["8.8.8.8", "pastebin.com"],
110
  )
tdagent/tools/retrieve_from_mitre_attack.py CHANGED
@@ -42,11 +42,18 @@ def get_stix_object_of_attack_id(
42
 
43
  gr_get_stix_of_attack_id = gr.Interface(
44
  fn=get_stix_object_of_attack_id,
45
- inputs=["text", "text"],
46
- outputs="json",
 
 
 
47
  title="MITRE ATT&CK STIX information",
48
  description=(
49
  "Retrieves a specific STIX object identified by an ATT&CK ID across all ATT&CK"
50
  " matrices"
51
  ),
 
 
 
 
52
  )
 
42
 
43
  gr_get_stix_of_attack_id = gr.Interface(
44
  fn=get_stix_object_of_attack_id,
45
+ inputs=[
46
+ gr.Textbox(label="Mitre technique ID"),
47
+ gr.Textbox(label="Mitre object type"),
48
+ ],
49
+ outputs=gr.JSON(label="Mitre report"),
50
  title="MITRE ATT&CK STIX information",
51
  description=(
52
  "Retrieves a specific STIX object identified by an ATT&CK ID across all ATT&CK"
53
  " matrices"
54
  ),
55
+ examples=[
56
+ ["T1568.002", "attack-pattern"],
57
+ ["M1042", "course-of-action"],
58
+ ],
59
  )
tdagent/tools/virus_total.py CHANGED
@@ -69,8 +69,10 @@ Cache Status: Hit
69
 
70
  gr_virus_total_url_info = gr.Interface(
71
  fn=get_virus_total_url_info,
72
- inputs=["text"],
73
- outputs="text",
74
  title="VirusTotal URL Scanner",
75
  description="Get URL Info from VirusTotal URL Scanner. Scan URL is not available",
 
 
76
  )
 
69
 
70
  gr_virus_total_url_info = gr.Interface(
71
  fn=get_virus_total_url_info,
72
+ inputs=gr.Textbox(label="url"),
73
+ outputs=gr.Text(label="VirusTotal report"),
74
  title="VirusTotal URL Scanner",
75
  description="Get URL Info from VirusTotal URL Scanner. Scan URL is not available",
76
+ examples=["https://advertipros.com//?u=script", "https://google.com"],
77
+ example_labels=["👾 Malicious URL", "🧑‍💻 Benign URL"],
78
  )
uv.lock CHANGED
@@ -857,6 +857,15 @@ wheels = [
857
  { url = "https://files.pythonhosted.org/packages/53/84/8a89614b2e7eeeaf0a68a4046d6cfaea4544c8619ea02595ebeec9b2bae3/license_expression-30.4.1-py3-none-any.whl", hash = "sha256:679646bc3261a17690494a3e1cada446e5ee342dbd87dcfa4a0c24cc5dce13ee", size = 111457, upload-time = "2025-01-14T05:11:38.658Z" },
858
  ]
859
 
 
 
 
 
 
 
 
 
 
860
  [[package]]
861
  name = "markdown-it-py"
862
  version = "3.0.0"
@@ -1324,26 +1333,35 @@ wheels = [
1324
  { url = "https://files.pythonhosted.org/packages/77/1c/3f8c331d223f86ba1d0ed7d3ed7fcf1501c6f250882489cc820d2567ddbf/pandas-2.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a6872d695c896f00df46b71648eea332279ef4077a409e2fe94220208b6bb675", size = 10774764, upload-time = "2025-06-05T03:25:53.228Z" },
1325
  { url = "https://files.pythonhosted.org/packages/1b/45/d2599400fad7fe06b849bd40b52c65684bc88fbe5f0a474d0513d057a377/pandas-2.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4dd97c19bd06bc557ad787a15b6489d2614ddaab5d104a0310eb314c724b2d2", size = 11711963, upload-time = "2025-06-05T03:25:56.855Z" },
1326
  { url = "https://files.pythonhosted.org/packages/66/f8/5508bc45e994e698dbc93607ee6b9b6eb67df978dc10ee2b09df80103d9e/pandas-2.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:034abd6f3db8b9880aaee98f4f5d4dbec7c4829938463ec046517220b2f8574e", size = 12349446, upload-time = "2025-06-05T03:26:01.292Z" },
 
1327
  { url = "https://files.pythonhosted.org/packages/a1/9b/8743be105989c81fa33f8e2a4e9822ac0ad4aaf812c00fee6bb09fc814f9/pandas-2.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:39ff73ec07be5e90330cc6ff5705c651ace83374189dcdcb46e6ff54b4a72cd6", size = 13651218, upload-time = "2025-06-05T03:26:09.731Z" },
1328
  { url = "https://files.pythonhosted.org/packages/26/fa/8eeb2353f6d40974a6a9fd4081ad1700e2386cf4264a8f28542fd10b3e38/pandas-2.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:40cecc4ea5abd2921682b57532baea5588cc5f80f0231c624056b146887274d2", size = 11082485, upload-time = "2025-06-05T03:26:17.572Z" },
1329
  { url = "https://files.pythonhosted.org/packages/96/1e/ba313812a699fe37bf62e6194265a4621be11833f5fce46d9eae22acb5d7/pandas-2.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8adff9f138fc614347ff33812046787f7d43b3cef7c0f0171b3340cae333f6ca", size = 11551836, upload-time = "2025-06-05T03:26:22.784Z" },
 
1330
  { url = "https://files.pythonhosted.org/packages/ee/3e/8c0fb7e2cf4a55198466ced1ca6a9054ae3b7e7630df7757031df10001fd/pandas-2.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fa35c266c8cd1a67d75971a1912b185b492d257092bdd2709bbdebe574ed228d", size = 11788230, upload-time = "2025-06-05T03:26:27.417Z" },
1331
  { url = "https://files.pythonhosted.org/packages/14/22/b493ec614582307faf3f94989be0f7f0a71932ed6f56c9a80c0bb4a3b51e/pandas-2.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14a0cc77b0f089d2d2ffe3007db58f170dae9b9f54e569b299db871a3ab5bf46", size = 12370423, upload-time = "2025-06-05T03:26:34.142Z" },
 
1332
  { url = "https://files.pythonhosted.org/packages/95/81/b310e60d033ab64b08e66c635b94076488f0b6ce6a674379dd5b224fc51c/pandas-2.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ed16339bc354a73e0a609df36d256672c7d296f3f767ac07257801aa064ff73c", size = 13745952, upload-time = "2025-06-05T03:26:39.475Z" },
1333
  { url = "https://files.pythonhosted.org/packages/25/ac/f6ee5250a8881b55bd3aecde9b8cfddea2f2b43e3588bca68a4e9aaf46c8/pandas-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:fa07e138b3f6c04addfeaf56cc7fdb96c3b68a3fe5e5401251f231fce40a0d7a", size = 11094534, upload-time = "2025-06-05T03:26:43.23Z" },
1334
  { url = "https://files.pythonhosted.org/packages/94/46/24192607058dd607dbfacdd060a2370f6afb19c2ccb617406469b9aeb8e7/pandas-2.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2eb4728a18dcd2908c7fccf74a982e241b467d178724545a48d0caf534b38ebf", size = 11573865, upload-time = "2025-06-05T03:26:46.774Z" },
 
 
1335
  { url = "https://files.pythonhosted.org/packages/01/a5/931fc3ad333d9d87b10107d948d757d67ebcfc33b1988d5faccc39c6845c/pandas-2.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba24af48643b12ffe49b27065d3babd52702d95ab70f50e1b34f71ca703e2c0d", size = 11991493, upload-time = "2025-06-05T03:26:51.813Z" },
 
1336
  { url = "https://files.pythonhosted.org/packages/a4/0e/21eb48a3a34a7d4bac982afc2c4eb5ab09f2d988bdf29d92ba9ae8e90a79/pandas-2.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6021910b086b3ca756755e86ddc64e0ddafd5e58e076c72cb1585162e5ad259b", size = 13212406, upload-time = "2025-06-05T03:26:55.992Z" },
1337
  { url = "https://files.pythonhosted.org/packages/1f/d9/74017c4eec7a28892d8d6e31ae9de3baef71f5a5286e74e6b7aad7f8c837/pandas-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:094e271a15b579650ebf4c5155c05dcd2a14fd4fdd72cf4854b2f7ad31ea30be", size = 10976199, upload-time = "2025-06-05T03:26:59.594Z" },
1338
  { url = "https://files.pythonhosted.org/packages/d3/57/5cb75a56a4842bbd0511c3d1c79186d8315b82dac802118322b2de1194fe/pandas-2.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2c7e2fc25f89a49a11599ec1e76821322439d90820108309bf42130d2f36c983", size = 11518913, upload-time = "2025-06-05T03:27:02.757Z" },
 
1339
  { url = "https://files.pythonhosted.org/packages/e8/6a/47fd7517cd8abe72a58706aab2b99e9438360d36dcdb052cf917b7bf3bdc/pandas-2.3.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb32dc743b52467d488e7a7c8039b821da2826a9ba4f85b89ea95274f863280f", size = 11328359, upload-time = "2025-06-05T03:27:06.431Z" },
1340
  { url = "https://files.pythonhosted.org/packages/2a/b3/463bfe819ed60fb7e7ddffb4ae2ee04b887b3444feee6c19437b8f834837/pandas-2.3.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:213cd63c43263dbb522c1f8a7c9d072e25900f6975596f883f4bebd77295d4f3", size = 12024789, upload-time = "2025-06-05T03:27:09.875Z" },
 
1341
  { url = "https://files.pythonhosted.org/packages/e9/df/815d6583967001153bb27f5cf075653d69d51ad887ebbf4cfe1173a1ac58/pandas-2.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:430a63bae10b5086995db1b02694996336e5a8ac9a96b4200572b413dfdfccb9", size = 13223381, upload-time = "2025-06-05T03:27:15.641Z" },
1342
  { url = "https://files.pythonhosted.org/packages/79/88/ca5973ed07b7f484c493e941dbff990861ca55291ff7ac67c815ce347395/pandas-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:4930255e28ff5545e2ca404637bcc56f031893142773b3468dc021c6c32a1390", size = 10970135, upload-time = "2025-06-05T03:27:24.131Z" },
1343
  { url = "https://files.pythonhosted.org/packages/24/fb/0994c14d1f7909ce83f0b1fb27958135513c4f3f2528bde216180aa73bfc/pandas-2.3.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:f925f1ef673b4bd0271b1809b72b3270384f2b7d9d14a189b12b7fc02574d575", size = 12141356, upload-time = "2025-06-05T03:27:34.547Z" },
1344
  { url = "https://files.pythonhosted.org/packages/9d/a2/9b903e5962134497ac4f8a96f862ee3081cb2506f69f8e4778ce3d9c9d82/pandas-2.3.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e78ad363ddb873a631e92a3c063ade1ecfb34cae71e9a2be6ad100f875ac1042", size = 11474674, upload-time = "2025-06-05T03:27:39.448Z" },
1345
  { url = "https://files.pythonhosted.org/packages/81/3a/3806d041bce032f8de44380f866059437fb79e36d6b22c82c187e65f765b/pandas-2.3.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:951805d146922aed8357e4cc5671b8b0b9be1027f0619cea132a9f3f65f2f09c", size = 11439876, upload-time = "2025-06-05T03:27:43.652Z" },
1346
  { url = "https://files.pythonhosted.org/packages/15/aa/3fc3181d12b95da71f5c2537c3e3b3af6ab3a8c392ab41ebb766e0929bc6/pandas-2.3.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a881bc1309f3fce34696d07b00f13335c41f5f5a8770a33b09ebe23261cfc67", size = 11966182, upload-time = "2025-06-05T03:27:47.652Z" },
 
1347
  { url = "https://files.pythonhosted.org/packages/39/c2/646d2e93e0af70f4e5359d870a63584dacbc324b54d73e6b3267920ff117/pandas-2.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:bb3be958022198531eb7ec2008cfc78c5b1eed51af8600c6c5d9160d89d8d249", size = 13231847, upload-time = "2025-06-05T03:27:51.465Z" },
1348
  ]
1349
 
@@ -2151,6 +2169,7 @@ dependencies = [
2151
  { name = "cachetools" },
2152
  { name = "dnspython" },
2153
  { name = "gradio", extra = ["mcp"] },
 
2154
  { name = "python-whois" },
2155
  { name = "requests" },
2156
  { name = "vt-py" },
@@ -2178,6 +2197,7 @@ requires-dist = [
2178
  { name = "cachetools", specifier = ">=6.0.0" },
2179
  { name = "dnspython", specifier = ">=2.7.0" },
2180
  { name = "gradio", extras = ["mcp"], specifier = ">=5.32.1" },
 
2181
  { name = "python-whois", specifier = ">=0.9.5" },
2182
  { name = "requests", specifier = ">=2.32.3" },
2183
  { name = "vt-py", specifier = "~=0.21.0" },
 
857
  { url = "https://files.pythonhosted.org/packages/53/84/8a89614b2e7eeeaf0a68a4046d6cfaea4544c8619ea02595ebeec9b2bae3/license_expression-30.4.1-py3-none-any.whl", hash = "sha256:679646bc3261a17690494a3e1cada446e5ee342dbd87dcfa4a0c24cc5dce13ee", size = 111457, upload-time = "2025-01-14T05:11:38.658Z" },
858
  ]
859
 
860
+ [[package]]
861
+ name = "markdown"
862
+ version = "3.8"
863
+ source = { registry = "https://pypi.org/simple" }
864
+ sdist = { url = "https://files.pythonhosted.org/packages/2f/15/222b423b0b88689c266d9eac4e61396fe2cc53464459d6a37618ac863b24/markdown-3.8.tar.gz", hash = "sha256:7df81e63f0df5c4b24b7d156eb81e4690595239b7d70937d0409f1b0de319c6f", size = 360906, upload-time = "2025-04-11T14:42:50.928Z" }
865
+ wheels = [
866
+ { url = "https://files.pythonhosted.org/packages/51/3f/afe76f8e2246ffbc867440cbcf90525264df0e658f8a5ca1f872b3f6192a/markdown-3.8-py3-none-any.whl", hash = "sha256:794a929b79c5af141ef5ab0f2f642d0f7b1872981250230e72682346f7cc90dc", size = 106210, upload-time = "2025-04-11T14:42:49.178Z" },
867
+ ]
868
+
869
  [[package]]
870
  name = "markdown-it-py"
871
  version = "3.0.0"
 
1333
  { url = "https://files.pythonhosted.org/packages/77/1c/3f8c331d223f86ba1d0ed7d3ed7fcf1501c6f250882489cc820d2567ddbf/pandas-2.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a6872d695c896f00df46b71648eea332279ef4077a409e2fe94220208b6bb675", size = 10774764, upload-time = "2025-06-05T03:25:53.228Z" },
1334
  { url = "https://files.pythonhosted.org/packages/1b/45/d2599400fad7fe06b849bd40b52c65684bc88fbe5f0a474d0513d057a377/pandas-2.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4dd97c19bd06bc557ad787a15b6489d2614ddaab5d104a0310eb314c724b2d2", size = 11711963, upload-time = "2025-06-05T03:25:56.855Z" },
1335
  { url = "https://files.pythonhosted.org/packages/66/f8/5508bc45e994e698dbc93607ee6b9b6eb67df978dc10ee2b09df80103d9e/pandas-2.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:034abd6f3db8b9880aaee98f4f5d4dbec7c4829938463ec046517220b2f8574e", size = 12349446, upload-time = "2025-06-05T03:26:01.292Z" },
1336
+ { url = "https://files.pythonhosted.org/packages/f7/fc/17851e1b1ea0c8456ba90a2f514c35134dd56d981cf30ccdc501a0adeac4/pandas-2.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:23c2b2dc5213810208ca0b80b8666670eb4660bbfd9d45f58592cc4ddcfd62e1", size = 12920002, upload-time = "2025-06-06T00:00:07.925Z" },
1337
  { url = "https://files.pythonhosted.org/packages/a1/9b/8743be105989c81fa33f8e2a4e9822ac0ad4aaf812c00fee6bb09fc814f9/pandas-2.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:39ff73ec07be5e90330cc6ff5705c651ace83374189dcdcb46e6ff54b4a72cd6", size = 13651218, upload-time = "2025-06-05T03:26:09.731Z" },
1338
  { url = "https://files.pythonhosted.org/packages/26/fa/8eeb2353f6d40974a6a9fd4081ad1700e2386cf4264a8f28542fd10b3e38/pandas-2.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:40cecc4ea5abd2921682b57532baea5588cc5f80f0231c624056b146887274d2", size = 11082485, upload-time = "2025-06-05T03:26:17.572Z" },
1339
  { url = "https://files.pythonhosted.org/packages/96/1e/ba313812a699fe37bf62e6194265a4621be11833f5fce46d9eae22acb5d7/pandas-2.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8adff9f138fc614347ff33812046787f7d43b3cef7c0f0171b3340cae333f6ca", size = 11551836, upload-time = "2025-06-05T03:26:22.784Z" },
1340
+ { url = "https://files.pythonhosted.org/packages/1b/cc/0af9c07f8d714ea563b12383a7e5bde9479cf32413ee2f346a9c5a801f22/pandas-2.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e5f08eb9a445d07720776df6e641975665c9ea12c9d8a331e0f6890f2dcd76ef", size = 10807977, upload-time = "2025-06-05T16:50:11.109Z" },
1341
  { url = "https://files.pythonhosted.org/packages/ee/3e/8c0fb7e2cf4a55198466ced1ca6a9054ae3b7e7630df7757031df10001fd/pandas-2.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fa35c266c8cd1a67d75971a1912b185b492d257092bdd2709bbdebe574ed228d", size = 11788230, upload-time = "2025-06-05T03:26:27.417Z" },
1342
  { url = "https://files.pythonhosted.org/packages/14/22/b493ec614582307faf3f94989be0f7f0a71932ed6f56c9a80c0bb4a3b51e/pandas-2.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14a0cc77b0f089d2d2ffe3007db58f170dae9b9f54e569b299db871a3ab5bf46", size = 12370423, upload-time = "2025-06-05T03:26:34.142Z" },
1343
+ { url = "https://files.pythonhosted.org/packages/9f/74/b012addb34cda5ce855218a37b258c4e056a0b9b334d116e518d72638737/pandas-2.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c06f6f144ad0a1bf84699aeea7eff6068ca5c63ceb404798198af7eb86082e33", size = 12990594, upload-time = "2025-06-06T00:00:13.934Z" },
1344
  { url = "https://files.pythonhosted.org/packages/95/81/b310e60d033ab64b08e66c635b94076488f0b6ce6a674379dd5b224fc51c/pandas-2.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ed16339bc354a73e0a609df36d256672c7d296f3f767ac07257801aa064ff73c", size = 13745952, upload-time = "2025-06-05T03:26:39.475Z" },
1345
  { url = "https://files.pythonhosted.org/packages/25/ac/f6ee5250a8881b55bd3aecde9b8cfddea2f2b43e3588bca68a4e9aaf46c8/pandas-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:fa07e138b3f6c04addfeaf56cc7fdb96c3b68a3fe5e5401251f231fce40a0d7a", size = 11094534, upload-time = "2025-06-05T03:26:43.23Z" },
1346
  { url = "https://files.pythonhosted.org/packages/94/46/24192607058dd607dbfacdd060a2370f6afb19c2ccb617406469b9aeb8e7/pandas-2.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2eb4728a18dcd2908c7fccf74a982e241b467d178724545a48d0caf534b38ebf", size = 11573865, upload-time = "2025-06-05T03:26:46.774Z" },
1347
+ { url = "https://files.pythonhosted.org/packages/9f/cc/ae8ea3b800757a70c9fdccc68b67dc0280a6e814efcf74e4211fd5dea1ca/pandas-2.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b9d8c3187be7479ea5c3d30c32a5d73d62a621166675063b2edd21bc47614027", size = 10702154, upload-time = "2025-06-05T16:50:14.439Z" },
1348
+ { url = "https://files.pythonhosted.org/packages/d8/ba/a7883d7aab3d24c6540a2768f679e7414582cc389876d469b40ec749d78b/pandas-2.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9ff730713d4c4f2f1c860e36c005c7cefc1c7c80c21c0688fd605aa43c9fcf09", size = 11262180, upload-time = "2025-06-05T16:50:17.453Z" },
1349
  { url = "https://files.pythonhosted.org/packages/01/a5/931fc3ad333d9d87b10107d948d757d67ebcfc33b1988d5faccc39c6845c/pandas-2.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba24af48643b12ffe49b27065d3babd52702d95ab70f50e1b34f71ca703e2c0d", size = 11991493, upload-time = "2025-06-05T03:26:51.813Z" },
1350
+ { url = "https://files.pythonhosted.org/packages/d7/bf/0213986830a92d44d55153c1d69b509431a972eb73f204242988c4e66e86/pandas-2.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:404d681c698e3c8a40a61d0cd9412cc7364ab9a9cc6e144ae2992e11a2e77a20", size = 12470733, upload-time = "2025-06-06T00:00:18.651Z" },
1351
  { url = "https://files.pythonhosted.org/packages/a4/0e/21eb48a3a34a7d4bac982afc2c4eb5ab09f2d988bdf29d92ba9ae8e90a79/pandas-2.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6021910b086b3ca756755e86ddc64e0ddafd5e58e076c72cb1585162e5ad259b", size = 13212406, upload-time = "2025-06-05T03:26:55.992Z" },
1352
  { url = "https://files.pythonhosted.org/packages/1f/d9/74017c4eec7a28892d8d6e31ae9de3baef71f5a5286e74e6b7aad7f8c837/pandas-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:094e271a15b579650ebf4c5155c05dcd2a14fd4fdd72cf4854b2f7ad31ea30be", size = 10976199, upload-time = "2025-06-05T03:26:59.594Z" },
1353
  { url = "https://files.pythonhosted.org/packages/d3/57/5cb75a56a4842bbd0511c3d1c79186d8315b82dac802118322b2de1194fe/pandas-2.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2c7e2fc25f89a49a11599ec1e76821322439d90820108309bf42130d2f36c983", size = 11518913, upload-time = "2025-06-05T03:27:02.757Z" },
1354
+ { url = "https://files.pythonhosted.org/packages/05/01/0c8785610e465e4948a01a059562176e4c8088aa257e2e074db868f86d4e/pandas-2.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c6da97aeb6a6d233fb6b17986234cc723b396b50a3c6804776351994f2a658fd", size = 10655249, upload-time = "2025-06-05T16:50:20.17Z" },
1355
  { url = "https://files.pythonhosted.org/packages/e8/6a/47fd7517cd8abe72a58706aab2b99e9438360d36dcdb052cf917b7bf3bdc/pandas-2.3.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb32dc743b52467d488e7a7c8039b821da2826a9ba4f85b89ea95274f863280f", size = 11328359, upload-time = "2025-06-05T03:27:06.431Z" },
1356
  { url = "https://files.pythonhosted.org/packages/2a/b3/463bfe819ed60fb7e7ddffb4ae2ee04b887b3444feee6c19437b8f834837/pandas-2.3.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:213cd63c43263dbb522c1f8a7c9d072e25900f6975596f883f4bebd77295d4f3", size = 12024789, upload-time = "2025-06-05T03:27:09.875Z" },
1357
+ { url = "https://files.pythonhosted.org/packages/04/0c/e0704ccdb0ac40aeb3434d1c641c43d05f75c92e67525df39575ace35468/pandas-2.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1d2b33e68d0ce64e26a4acc2e72d747292084f4e8db4c847c6f5f6cbe56ed6d8", size = 12480734, upload-time = "2025-06-06T00:00:22.246Z" },
1358
  { url = "https://files.pythonhosted.org/packages/e9/df/815d6583967001153bb27f5cf075653d69d51ad887ebbf4cfe1173a1ac58/pandas-2.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:430a63bae10b5086995db1b02694996336e5a8ac9a96b4200572b413dfdfccb9", size = 13223381, upload-time = "2025-06-05T03:27:15.641Z" },
1359
  { url = "https://files.pythonhosted.org/packages/79/88/ca5973ed07b7f484c493e941dbff990861ca55291ff7ac67c815ce347395/pandas-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:4930255e28ff5545e2ca404637bcc56f031893142773b3468dc021c6c32a1390", size = 10970135, upload-time = "2025-06-05T03:27:24.131Z" },
1360
  { url = "https://files.pythonhosted.org/packages/24/fb/0994c14d1f7909ce83f0b1fb27958135513c4f3f2528bde216180aa73bfc/pandas-2.3.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:f925f1ef673b4bd0271b1809b72b3270384f2b7d9d14a189b12b7fc02574d575", size = 12141356, upload-time = "2025-06-05T03:27:34.547Z" },
1361
  { url = "https://files.pythonhosted.org/packages/9d/a2/9b903e5962134497ac4f8a96f862ee3081cb2506f69f8e4778ce3d9c9d82/pandas-2.3.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e78ad363ddb873a631e92a3c063ade1ecfb34cae71e9a2be6ad100f875ac1042", size = 11474674, upload-time = "2025-06-05T03:27:39.448Z" },
1362
  { url = "https://files.pythonhosted.org/packages/81/3a/3806d041bce032f8de44380f866059437fb79e36d6b22c82c187e65f765b/pandas-2.3.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:951805d146922aed8357e4cc5671b8b0b9be1027f0619cea132a9f3f65f2f09c", size = 11439876, upload-time = "2025-06-05T03:27:43.652Z" },
1363
  { url = "https://files.pythonhosted.org/packages/15/aa/3fc3181d12b95da71f5c2537c3e3b3af6ab3a8c392ab41ebb766e0929bc6/pandas-2.3.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a881bc1309f3fce34696d07b00f13335c41f5f5a8770a33b09ebe23261cfc67", size = 11966182, upload-time = "2025-06-05T03:27:47.652Z" },
1364
+ { url = "https://files.pythonhosted.org/packages/37/e7/e12f2d9b0a2c4a2cc86e2aabff7ccfd24f03e597d770abfa2acd313ee46b/pandas-2.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:e1991bbb96f4050b09b5f811253c4f3cf05ee89a589379aa36cd623f21a31d6f", size = 12547686, upload-time = "2025-06-06T00:00:26.142Z" },
1365
  { url = "https://files.pythonhosted.org/packages/39/c2/646d2e93e0af70f4e5359d870a63584dacbc324b54d73e6b3267920ff117/pandas-2.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:bb3be958022198531eb7ec2008cfc78c5b1eed51af8600c6c5d9160d89d8d249", size = 13231847, upload-time = "2025-06-05T03:27:51.465Z" },
1366
  ]
1367
 
 
2169
  { name = "cachetools" },
2170
  { name = "dnspython" },
2171
  { name = "gradio", extra = ["mcp"] },
2172
+ { name = "markdown" },
2173
  { name = "python-whois" },
2174
  { name = "requests" },
2175
  { name = "vt-py" },
 
2197
  { name = "cachetools", specifier = ">=6.0.0" },
2198
  { name = "dnspython", specifier = ">=2.7.0" },
2199
  { name = "gradio", extras = ["mcp"], specifier = ">=5.32.1" },
2200
+ { name = "markdown", specifier = ">=3.8" },
2201
  { name = "python-whois", specifier = ">=0.9.5" },
2202
  { name = "requests", specifier = ">=2.32.3" },
2203
  { name = "vt-py", specifier = "~=0.21.0" },