universal-mcp-applications 0.1.33__py3-none-any.whl → 0.1.39rc16__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of universal-mcp-applications might be problematic. Click here for more details.
- universal_mcp/applications/BEST_PRACTICES.md +1 -1
- universal_mcp/applications/ahrefs/app.py +92 -238
- universal_mcp/applications/airtable/app.py +36 -135
- universal_mcp/applications/apollo/app.py +124 -477
- universal_mcp/applications/asana/app.py +605 -1755
- universal_mcp/applications/aws_s3/app.py +63 -119
- universal_mcp/applications/bill/app.py +644 -2055
- universal_mcp/applications/box/app.py +1246 -4159
- universal_mcp/applications/braze/app.py +410 -1476
- universal_mcp/applications/browser_use/README.md +15 -1
- universal_mcp/applications/browser_use/__init__.py +1 -0
- universal_mcp/applications/browser_use/app.py +91 -26
- universal_mcp/applications/cal_com_v2/app.py +207 -625
- universal_mcp/applications/calendly/app.py +103 -242
- universal_mcp/applications/canva/app.py +75 -140
- universal_mcp/applications/clickup/app.py +331 -798
- universal_mcp/applications/coda/app.py +240 -520
- universal_mcp/applications/confluence/app.py +497 -1285
- universal_mcp/applications/contentful/app.py +40 -155
- universal_mcp/applications/crustdata/app.py +44 -123
- universal_mcp/applications/dialpad/app.py +451 -924
- universal_mcp/applications/digitalocean/app.py +2071 -6082
- universal_mcp/applications/domain_checker/app.py +3 -54
- universal_mcp/applications/e2b/app.py +17 -68
- universal_mcp/applications/elevenlabs/README.md +27 -3
- universal_mcp/applications/elevenlabs/app.py +741 -74
- universal_mcp/applications/exa/README.md +8 -4
- universal_mcp/applications/exa/app.py +415 -186
- universal_mcp/applications/falai/README.md +5 -7
- universal_mcp/applications/falai/app.py +156 -232
- universal_mcp/applications/figma/app.py +91 -175
- universal_mcp/applications/file_system/app.py +2 -13
- universal_mcp/applications/firecrawl/app.py +198 -176
- universal_mcp/applications/fireflies/app.py +59 -281
- universal_mcp/applications/fpl/app.py +92 -529
- universal_mcp/applications/fpl/utils/fixtures.py +15 -49
- universal_mcp/applications/fpl/utils/helper.py +25 -89
- universal_mcp/applications/fpl/utils/league_utils.py +20 -64
- universal_mcp/applications/ghost_content/app.py +70 -179
- universal_mcp/applications/github/app.py +30 -67
- universal_mcp/applications/gong/app.py +142 -302
- universal_mcp/applications/google_calendar/app.py +26 -78
- universal_mcp/applications/google_docs/README.md +15 -14
- universal_mcp/applications/google_docs/app.py +103 -206
- universal_mcp/applications/google_drive/app.py +194 -793
- universal_mcp/applications/google_gemini/app.py +68 -59
- universal_mcp/applications/google_mail/README.md +1 -0
- universal_mcp/applications/google_mail/app.py +93 -214
- universal_mcp/applications/google_searchconsole/app.py +25 -58
- universal_mcp/applications/google_sheet/README.md +2 -1
- universal_mcp/applications/google_sheet/app.py +226 -624
- universal_mcp/applications/google_sheet/helper.py +26 -53
- universal_mcp/applications/hashnode/app.py +57 -269
- universal_mcp/applications/heygen/README.md +10 -32
- universal_mcp/applications/heygen/app.py +339 -811
- universal_mcp/applications/http_tools/app.py +10 -32
- universal_mcp/applications/hubspot/README.md +1 -1
- universal_mcp/applications/hubspot/app.py +7508 -99
- universal_mcp/applications/jira/app.py +2419 -8334
- universal_mcp/applications/klaviyo/app.py +739 -1621
- universal_mcp/applications/linkedin/README.md +18 -1
- universal_mcp/applications/linkedin/app.py +729 -251
- universal_mcp/applications/mailchimp/app.py +696 -1851
- universal_mcp/applications/markitdown/app.py +8 -20
- universal_mcp/applications/miro/app.py +333 -815
- universal_mcp/applications/ms_teams/app.py +420 -1407
- universal_mcp/applications/neon/app.py +144 -250
- universal_mcp/applications/notion/app.py +38 -53
- universal_mcp/applications/onedrive/app.py +26 -48
- universal_mcp/applications/openai/app.py +43 -166
- universal_mcp/applications/outlook/README.md +22 -9
- universal_mcp/applications/outlook/app.py +403 -141
- universal_mcp/applications/perplexity/README.md +2 -1
- universal_mcp/applications/perplexity/app.py +161 -20
- universal_mcp/applications/pipedrive/app.py +1021 -3331
- universal_mcp/applications/posthog/app.py +272 -541
- universal_mcp/applications/reddit/app.py +65 -164
- universal_mcp/applications/resend/app.py +72 -139
- universal_mcp/applications/retell/app.py +23 -50
- universal_mcp/applications/rocketlane/app.py +252 -965
- universal_mcp/applications/scraper/app.py +114 -142
- universal_mcp/applications/semanticscholar/app.py +36 -78
- universal_mcp/applications/semrush/app.py +44 -78
- universal_mcp/applications/sendgrid/app.py +826 -1576
- universal_mcp/applications/sentry/app.py +444 -1079
- universal_mcp/applications/serpapi/app.py +44 -146
- universal_mcp/applications/sharepoint/app.py +27 -49
- universal_mcp/applications/shopify/app.py +1748 -4486
- universal_mcp/applications/shortcut/app.py +275 -536
- universal_mcp/applications/slack/app.py +43 -125
- universal_mcp/applications/spotify/app.py +206 -405
- universal_mcp/applications/supabase/app.py +174 -283
- universal_mcp/applications/tavily/app.py +2 -2
- universal_mcp/applications/trello/app.py +853 -2816
- universal_mcp/applications/twilio/app.py +27 -62
- universal_mcp/applications/twitter/api_segments/compliance_api.py +4 -14
- universal_mcp/applications/twitter/api_segments/dm_conversations_api.py +6 -18
- universal_mcp/applications/twitter/api_segments/likes_api.py +1 -3
- universal_mcp/applications/twitter/api_segments/lists_api.py +5 -15
- universal_mcp/applications/twitter/api_segments/trends_api.py +1 -3
- universal_mcp/applications/twitter/api_segments/tweets_api.py +9 -31
- universal_mcp/applications/twitter/api_segments/usage_api.py +1 -5
- universal_mcp/applications/twitter/api_segments/users_api.py +14 -42
- universal_mcp/applications/whatsapp/app.py +35 -186
- universal_mcp/applications/whatsapp/audio.py +2 -6
- universal_mcp/applications/whatsapp/whatsapp.py +17 -51
- universal_mcp/applications/whatsapp_business/app.py +86 -299
- universal_mcp/applications/wrike/app.py +80 -153
- universal_mcp/applications/yahoo_finance/app.py +19 -65
- universal_mcp/applications/youtube/app.py +120 -306
- universal_mcp/applications/zenquotes/app.py +3 -3
- {universal_mcp_applications-0.1.33.dist-info → universal_mcp_applications-0.1.39rc16.dist-info}/METADATA +4 -2
- {universal_mcp_applications-0.1.33.dist-info → universal_mcp_applications-0.1.39rc16.dist-info}/RECORD +115 -119
- {universal_mcp_applications-0.1.33.dist-info → universal_mcp_applications-0.1.39rc16.dist-info}/WHEEL +1 -1
- universal_mcp/applications/hubspot/api_segments/__init__.py +0 -0
- universal_mcp/applications/hubspot/api_segments/api_segment_base.py +0 -54
- universal_mcp/applications/hubspot/api_segments/crm_api.py +0 -7337
- universal_mcp/applications/hubspot/api_segments/marketing_api.py +0 -1467
- {universal_mcp_applications-0.1.33.dist-info → universal_mcp_applications-0.1.39rc16.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
from typing import Any
|
|
2
|
-
|
|
3
2
|
from universal_mcp.applications.application import APIApplication
|
|
4
3
|
from universal_mcp.integrations import Integration
|
|
5
4
|
|
|
@@ -9,7 +8,7 @@ class SlackApp(APIApplication):
|
|
|
9
8
|
super().__init__(name="slack", integration=integration, **kwargs)
|
|
10
9
|
self.base_url = "https://slack.com/api"
|
|
11
10
|
|
|
12
|
-
def
|
|
11
|
+
async def _aget_headers(self) -> dict[str, str]:
|
|
13
12
|
"""
|
|
14
13
|
Get headers for Slack API requests.
|
|
15
14
|
Prioritizes user-scoped access token from raw.authed_user.access_token
|
|
@@ -17,35 +16,22 @@ class SlackApp(APIApplication):
|
|
|
17
16
|
"""
|
|
18
17
|
if not self.integration:
|
|
19
18
|
raise ValueError("Integration not configured for SlackApp")
|
|
20
|
-
|
|
21
|
-
credentials = self.integration.get_credentials()
|
|
19
|
+
credentials = await self.integration.get_credentials_async()
|
|
22
20
|
if not credentials:
|
|
23
21
|
raise ValueError("No credentials found for Slack integration")
|
|
24
|
-
|
|
25
22
|
access_token = None
|
|
26
|
-
raw = credentials.get(
|
|
27
|
-
if isinstance(raw, dict) and
|
|
28
|
-
authed_user = raw.get(
|
|
23
|
+
raw = credentials.get("raw", {})
|
|
24
|
+
if isinstance(raw, dict) and "authed_user" in raw:
|
|
25
|
+
authed_user = raw.get("authed_user", {})
|
|
29
26
|
if isinstance(authed_user, dict):
|
|
30
|
-
access_token = authed_user.get(
|
|
31
|
-
|
|
27
|
+
access_token = authed_user.get("access_token")
|
|
32
28
|
if not access_token:
|
|
33
|
-
access_token = credentials.get(
|
|
34
|
-
|
|
29
|
+
access_token = credentials.get("access_token")
|
|
35
30
|
if not access_token:
|
|
36
31
|
raise ValueError("Access token not found in Slack credentials")
|
|
37
|
-
|
|
38
|
-
return {
|
|
39
|
-
"Authorization": f"Bearer {access_token}",
|
|
40
|
-
"Content-Type": "application/json",
|
|
41
|
-
}
|
|
32
|
+
return {"Authorization": f"Bearer {access_token}", "Content-Type": "application/json"}
|
|
42
33
|
|
|
43
|
-
def chat_delete(
|
|
44
|
-
self,
|
|
45
|
-
as_user: bool | None = None,
|
|
46
|
-
channel: str | None = None,
|
|
47
|
-
ts: float | None = None,
|
|
48
|
-
) -> dict[str, Any]:
|
|
34
|
+
async def chat_delete(self, as_user: bool | None = None, channel: str | None = None, ts: float | None = None) -> dict[str, Any]:
|
|
49
35
|
"""
|
|
50
36
|
Deletes a specific message from a Slack channel. It identifies the message using its channel ID and timestamp (`ts`). This function is distinct from `chat_update` which modifies a message, and `chat_post_message` which sends a new one.
|
|
51
37
|
|
|
@@ -64,25 +50,14 @@ class SlackApp(APIApplication):
|
|
|
64
50
|
chat
|
|
65
51
|
"""
|
|
66
52
|
request_body_data = None
|
|
67
|
-
request_body_data = {
|
|
68
|
-
|
|
69
|
-
"channel": channel,
|
|
70
|
-
"ts": ts,
|
|
71
|
-
}
|
|
72
|
-
request_body_data = {
|
|
73
|
-
k: v for k, v in request_body_data.items() if v is not None
|
|
74
|
-
}
|
|
53
|
+
request_body_data = {"as_user": as_user, "channel": channel, "ts": ts}
|
|
54
|
+
request_body_data = {k: v for k, v in request_body_data.items() if v is not None}
|
|
75
55
|
url = f"{self.base_url}/chat.delete"
|
|
76
56
|
query_params = {}
|
|
77
|
-
response = self.
|
|
78
|
-
url,
|
|
79
|
-
data=request_body_data,
|
|
80
|
-
params=query_params,
|
|
81
|
-
content_type="application/x-www-form-urlencoded",
|
|
82
|
-
)
|
|
57
|
+
response = await self._apost(url, data=request_body_data, params=query_params, content_type="application/x-www-form-urlencoded")
|
|
83
58
|
return self._handle_response(response)
|
|
84
59
|
|
|
85
|
-
def chat_post_message(
|
|
60
|
+
async def chat_post_message(
|
|
86
61
|
self,
|
|
87
62
|
as_user: bool | None = None,
|
|
88
63
|
attachments: str | None = None,
|
|
@@ -147,20 +122,13 @@ class SlackApp(APIApplication):
|
|
|
147
122
|
"unfurl_media": unfurl_media,
|
|
148
123
|
"username": username,
|
|
149
124
|
}
|
|
150
|
-
request_body_data = {
|
|
151
|
-
k: v for k, v in request_body_data.items() if v is not None
|
|
152
|
-
}
|
|
125
|
+
request_body_data = {k: v for k, v in request_body_data.items() if v is not None}
|
|
153
126
|
url = f"{self.base_url}/chat.postMessage"
|
|
154
127
|
query_params = {}
|
|
155
|
-
response = self.
|
|
156
|
-
url,
|
|
157
|
-
data=request_body_data,
|
|
158
|
-
params=query_params,
|
|
159
|
-
content_type="application/x-www-form-urlencoded",
|
|
160
|
-
)
|
|
128
|
+
response = await self._apost(url, data=request_body_data, params=query_params, content_type="application/x-www-form-urlencoded")
|
|
161
129
|
return self._handle_response(response)
|
|
162
130
|
|
|
163
|
-
def chat_update(
|
|
131
|
+
async def chat_update(
|
|
164
132
|
self,
|
|
165
133
|
as_user: str | None = None,
|
|
166
134
|
attachments: str | None = None,
|
|
@@ -204,20 +172,13 @@ class SlackApp(APIApplication):
|
|
|
204
172
|
"text": text,
|
|
205
173
|
"ts": ts,
|
|
206
174
|
}
|
|
207
|
-
request_body_data = {
|
|
208
|
-
k: v for k, v in request_body_data.items() if v is not None
|
|
209
|
-
}
|
|
175
|
+
request_body_data = {k: v for k, v in request_body_data.items() if v is not None}
|
|
210
176
|
url = f"{self.base_url}/chat.update"
|
|
211
177
|
query_params = {}
|
|
212
|
-
response = self.
|
|
213
|
-
url,
|
|
214
|
-
data=request_body_data,
|
|
215
|
-
params=query_params,
|
|
216
|
-
content_type="application/x-www-form-urlencoded",
|
|
217
|
-
)
|
|
178
|
+
response = await self._apost(url, data=request_body_data, params=query_params, content_type="application/x-www-form-urlencoded")
|
|
218
179
|
return self._handle_response(response)
|
|
219
180
|
|
|
220
|
-
def conversations_history(
|
|
181
|
+
async def conversations_history(
|
|
221
182
|
self,
|
|
222
183
|
token: str | None = None,
|
|
223
184
|
channel: str | None = None,
|
|
@@ -262,10 +223,10 @@ class SlackApp(APIApplication):
|
|
|
262
223
|
]
|
|
263
224
|
if v is not None
|
|
264
225
|
}
|
|
265
|
-
response = self.
|
|
226
|
+
response = await self._aget(url, params=query_params)
|
|
266
227
|
return self._handle_response(response)
|
|
267
228
|
|
|
268
|
-
def conversations_list(
|
|
229
|
+
async def conversations_list(
|
|
269
230
|
self,
|
|
270
231
|
token: str | None = None,
|
|
271
232
|
exclude_archived: bool | None = None,
|
|
@@ -295,19 +256,13 @@ class SlackApp(APIApplication):
|
|
|
295
256
|
url = f"{self.base_url}/conversations.list"
|
|
296
257
|
query_params = {
|
|
297
258
|
k: v
|
|
298
|
-
for k, v in [
|
|
299
|
-
("token", token),
|
|
300
|
-
("exclude_archived", exclude_archived),
|
|
301
|
-
("types", types),
|
|
302
|
-
("limit", limit),
|
|
303
|
-
("cursor", cursor),
|
|
304
|
-
]
|
|
259
|
+
for k, v in [("token", token), ("exclude_archived", exclude_archived), ("types", types), ("limit", limit), ("cursor", cursor)]
|
|
305
260
|
if v is not None
|
|
306
261
|
}
|
|
307
|
-
response = self.
|
|
262
|
+
response = await self._aget(url, params=query_params)
|
|
308
263
|
return self._handle_response(response)
|
|
309
264
|
|
|
310
|
-
def reactions_add(self, channel: str, name: str, timestamp: str) -> dict[str, Any]:
|
|
265
|
+
async def reactions_add(self, channel: str, name: str, timestamp: str) -> dict[str, Any]:
|
|
311
266
|
"""
|
|
312
267
|
Adds a specific emoji reaction to a message in a Slack channel, identifying the message by its channel ID and timestamp. This method creates a new reaction, unlike `reactions_get` or `reactions_list` which retrieve existing reaction data for items or users.
|
|
313
268
|
|
|
@@ -326,25 +281,14 @@ class SlackApp(APIApplication):
|
|
|
326
281
|
reactions
|
|
327
282
|
"""
|
|
328
283
|
request_body_data = None
|
|
329
|
-
request_body_data = {
|
|
330
|
-
|
|
331
|
-
"name": name,
|
|
332
|
-
"timestamp": timestamp,
|
|
333
|
-
}
|
|
334
|
-
request_body_data = {
|
|
335
|
-
k: v for k, v in request_body_data.items() if v is not None
|
|
336
|
-
}
|
|
284
|
+
request_body_data = {"channel": channel, "name": name, "timestamp": timestamp}
|
|
285
|
+
request_body_data = {k: v for k, v in request_body_data.items() if v is not None}
|
|
337
286
|
url = f"{self.base_url}/reactions.add"
|
|
338
287
|
query_params = {}
|
|
339
|
-
response = self.
|
|
340
|
-
url,
|
|
341
|
-
data=request_body_data,
|
|
342
|
-
params=query_params,
|
|
343
|
-
content_type="application/x-www-form-urlencoded",
|
|
344
|
-
)
|
|
288
|
+
response = await self._apost(url, data=request_body_data, params=query_params, content_type="application/x-www-form-urlencoded")
|
|
345
289
|
return self._handle_response(response)
|
|
346
290
|
|
|
347
|
-
def get_reactions_for_item(
|
|
291
|
+
async def get_reactions_for_item(
|
|
348
292
|
self,
|
|
349
293
|
token: str,
|
|
350
294
|
channel: str | None = None,
|
|
@@ -386,10 +330,10 @@ class SlackApp(APIApplication):
|
|
|
386
330
|
]
|
|
387
331
|
if v is not None
|
|
388
332
|
}
|
|
389
|
-
response = self.
|
|
333
|
+
response = await self._aget(url, params=query_params)
|
|
390
334
|
return self._handle_response(response)
|
|
391
335
|
|
|
392
|
-
def get_user_reactions(
|
|
336
|
+
async def get_user_reactions(
|
|
393
337
|
self,
|
|
394
338
|
token: str,
|
|
395
339
|
user: str | None = None,
|
|
@@ -434,10 +378,10 @@ class SlackApp(APIApplication):
|
|
|
434
378
|
]
|
|
435
379
|
if v is not None
|
|
436
380
|
}
|
|
437
|
-
response = self.
|
|
381
|
+
response = await self._aget(url, params=query_params)
|
|
438
382
|
return self._handle_response(response)
|
|
439
383
|
|
|
440
|
-
def search_messages(
|
|
384
|
+
async def search_messages(
|
|
441
385
|
self,
|
|
442
386
|
token: str,
|
|
443
387
|
query: str,
|
|
@@ -482,10 +426,10 @@ class SlackApp(APIApplication):
|
|
|
482
426
|
]
|
|
483
427
|
if v is not None
|
|
484
428
|
}
|
|
485
|
-
response = self.
|
|
429
|
+
response = await self._aget(url, params=query_params)
|
|
486
430
|
return self._handle_response(response)
|
|
487
431
|
|
|
488
|
-
def team_info(self, token: str, team: str | None = None) -> dict[str, Any]:
|
|
432
|
+
async def team_info(self, token: str, team: str | None = None) -> dict[str, Any]:
|
|
489
433
|
"""
|
|
490
434
|
Fetches details for a Slack team, such as name and domain, by calling the `team.info` API endpoint. This function requires an authentication token and can optionally target a specific team by its ID, distinguishing it from user or channel-specific functions.
|
|
491
435
|
|
|
@@ -503,18 +447,11 @@ class SlackApp(APIApplication):
|
|
|
503
447
|
team
|
|
504
448
|
"""
|
|
505
449
|
url = f"{self.base_url}/team.info"
|
|
506
|
-
query_params = {
|
|
507
|
-
|
|
508
|
-
}
|
|
509
|
-
response = self._get(url, params=query_params)
|
|
450
|
+
query_params = {k: v for k, v in [("token", token), ("team", team)] if v is not None}
|
|
451
|
+
response = await self._aget(url, params=query_params)
|
|
510
452
|
return self._handle_response(response)
|
|
511
453
|
|
|
512
|
-
def get_user_info(
|
|
513
|
-
self,
|
|
514
|
-
token: str,
|
|
515
|
-
include_locale: bool | None = None,
|
|
516
|
-
user: str | None = None,
|
|
517
|
-
) -> dict[str, Any]:
|
|
454
|
+
async def get_user_info(self, token: str, include_locale: bool | None = None, user: str | None = None) -> dict[str, Any]:
|
|
518
455
|
"""
|
|
519
456
|
Fetches detailed profile information for a single Slack user, identified by their user ID. Unlike `users_list`, which retrieves all workspace members, this function targets an individual and can optionally include their locale information. It directly calls the `users.info` Slack API endpoint.
|
|
520
457
|
|
|
@@ -533,24 +470,12 @@ class SlackApp(APIApplication):
|
|
|
533
470
|
users, important
|
|
534
471
|
"""
|
|
535
472
|
url = f"{self.base_url}/users.info"
|
|
536
|
-
query_params = {
|
|
537
|
-
|
|
538
|
-
for k, v in [
|
|
539
|
-
("token", token),
|
|
540
|
-
("include_locale", include_locale),
|
|
541
|
-
("user", user),
|
|
542
|
-
]
|
|
543
|
-
if v is not None
|
|
544
|
-
}
|
|
545
|
-
response = self._get(url, params=query_params)
|
|
473
|
+
query_params = {k: v for k, v in [("token", token), ("include_locale", include_locale), ("user", user)] if v is not None}
|
|
474
|
+
response = await self._aget(url, params=query_params)
|
|
546
475
|
return self._handle_response(response)
|
|
547
476
|
|
|
548
|
-
def users_list(
|
|
549
|
-
self,
|
|
550
|
-
token: str | None = None,
|
|
551
|
-
limit: int | None = None,
|
|
552
|
-
cursor: str | None = None,
|
|
553
|
-
include_locale: bool | None = None,
|
|
477
|
+
async def users_list(
|
|
478
|
+
self, token: str | None = None, limit: int | None = None, cursor: str | None = None, include_locale: bool | None = None
|
|
554
479
|
) -> dict[str, Any]:
|
|
555
480
|
"""
|
|
556
481
|
Fetches a paginated list of all users in a Slack workspace, including deactivated members. Unlike `users_info` which retrieves a single user's details, this function returns a collection and supports limiting results or including locale data through optional parameters.
|
|
@@ -572,16 +497,9 @@ class SlackApp(APIApplication):
|
|
|
572
497
|
"""
|
|
573
498
|
url = f"{self.base_url}/users.list"
|
|
574
499
|
query_params = {
|
|
575
|
-
k: v
|
|
576
|
-
for k, v in [
|
|
577
|
-
("token", token),
|
|
578
|
-
("limit", limit),
|
|
579
|
-
("cursor", cursor),
|
|
580
|
-
("include_locale", include_locale),
|
|
581
|
-
]
|
|
582
|
-
if v is not None
|
|
500
|
+
k: v for k, v in [("token", token), ("limit", limit), ("cursor", cursor), ("include_locale", include_locale)] if v is not None
|
|
583
501
|
}
|
|
584
|
-
response = self.
|
|
502
|
+
response = await self._aget(url, params=query_params)
|
|
585
503
|
return self._handle_response(response)
|
|
586
504
|
|
|
587
505
|
def list_tools(self):
|