universal-mcp-applications 0.1.32__py3-none-any.whl → 0.1.36rc2__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.
Files changed (105) hide show
  1. universal_mcp/applications/ahrefs/app.py +52 -198
  2. universal_mcp/applications/airtable/app.py +23 -122
  3. universal_mcp/applications/apollo/app.py +111 -464
  4. universal_mcp/applications/asana/app.py +417 -1567
  5. universal_mcp/applications/aws_s3/app.py +36 -103
  6. universal_mcp/applications/bill/app.py +546 -1957
  7. universal_mcp/applications/box/app.py +1068 -3981
  8. universal_mcp/applications/braze/app.py +364 -1430
  9. universal_mcp/applications/browser_use/app.py +2 -8
  10. universal_mcp/applications/cal_com_v2/app.py +207 -625
  11. universal_mcp/applications/calendly/app.py +61 -200
  12. universal_mcp/applications/canva/app.py +45 -110
  13. universal_mcp/applications/clickup/app.py +207 -674
  14. universal_mcp/applications/coda/app.py +146 -426
  15. universal_mcp/applications/confluence/app.py +310 -1098
  16. universal_mcp/applications/contentful/app.py +36 -151
  17. universal_mcp/applications/crustdata/app.py +28 -107
  18. universal_mcp/applications/dialpad/app.py +283 -756
  19. universal_mcp/applications/digitalocean/app.py +1766 -5777
  20. universal_mcp/applications/domain_checker/app.py +3 -54
  21. universal_mcp/applications/e2b/app.py +14 -64
  22. universal_mcp/applications/elevenlabs/app.py +9 -47
  23. universal_mcp/applications/exa/app.py +6 -17
  24. universal_mcp/applications/falai/app.py +24 -101
  25. universal_mcp/applications/figma/app.py +53 -137
  26. universal_mcp/applications/file_system/app.py +2 -13
  27. universal_mcp/applications/firecrawl/app.py +51 -152
  28. universal_mcp/applications/fireflies/app.py +59 -281
  29. universal_mcp/applications/fpl/app.py +91 -528
  30. universal_mcp/applications/fpl/utils/fixtures.py +15 -49
  31. universal_mcp/applications/fpl/utils/helper.py +25 -89
  32. universal_mcp/applications/fpl/utils/league_utils.py +20 -64
  33. universal_mcp/applications/ghost_content/app.py +52 -161
  34. universal_mcp/applications/github/app.py +19 -56
  35. universal_mcp/applications/gong/app.py +88 -248
  36. universal_mcp/applications/google_calendar/app.py +16 -68
  37. universal_mcp/applications/google_docs/app.py +85 -189
  38. universal_mcp/applications/google_drive/app.py +141 -463
  39. universal_mcp/applications/google_gemini/app.py +12 -64
  40. universal_mcp/applications/google_mail/app.py +28 -157
  41. universal_mcp/applications/google_searchconsole/app.py +15 -48
  42. universal_mcp/applications/google_sheet/app.py +100 -581
  43. universal_mcp/applications/google_sheet/helper.py +10 -37
  44. universal_mcp/applications/hashnode/app.py +57 -269
  45. universal_mcp/applications/heygen/app.py +44 -122
  46. universal_mcp/applications/http_tools/app.py +10 -32
  47. universal_mcp/applications/hubspot/api_segments/crm_api.py +460 -1573
  48. universal_mcp/applications/hubspot/api_segments/marketing_api.py +74 -262
  49. universal_mcp/applications/hubspot/app.py +23 -87
  50. universal_mcp/applications/jira/app.py +2071 -7986
  51. universal_mcp/applications/klaviyo/app.py +494 -1376
  52. universal_mcp/applications/linkedin/README.md +9 -2
  53. universal_mcp/applications/linkedin/app.py +240 -181
  54. universal_mcp/applications/mailchimp/app.py +450 -1605
  55. universal_mcp/applications/markitdown/app.py +8 -20
  56. universal_mcp/applications/miro/app.py +217 -699
  57. universal_mcp/applications/ms_teams/app.py +64 -186
  58. universal_mcp/applications/neon/app.py +86 -192
  59. universal_mcp/applications/notion/app.py +21 -36
  60. universal_mcp/applications/onedrive/app.py +16 -38
  61. universal_mcp/applications/openai/app.py +42 -165
  62. universal_mcp/applications/outlook/app.py +24 -84
  63. universal_mcp/applications/perplexity/app.py +4 -19
  64. universal_mcp/applications/pipedrive/app.py +832 -3142
  65. universal_mcp/applications/posthog/app.py +163 -432
  66. universal_mcp/applications/reddit/app.py +40 -139
  67. universal_mcp/applications/resend/app.py +41 -107
  68. universal_mcp/applications/retell/app.py +14 -41
  69. universal_mcp/applications/rocketlane/app.py +221 -934
  70. universal_mcp/applications/scraper/README.md +7 -4
  71. universal_mcp/applications/scraper/app.py +50 -109
  72. universal_mcp/applications/semanticscholar/app.py +22 -64
  73. universal_mcp/applications/semrush/app.py +43 -77
  74. universal_mcp/applications/sendgrid/app.py +512 -1262
  75. universal_mcp/applications/sentry/app.py +271 -906
  76. universal_mcp/applications/serpapi/app.py +40 -143
  77. universal_mcp/applications/sharepoint/app.py +17 -39
  78. universal_mcp/applications/shopify/app.py +1551 -4287
  79. universal_mcp/applications/shortcut/app.py +155 -417
  80. universal_mcp/applications/slack/app.py +33 -115
  81. universal_mcp/applications/spotify/app.py +126 -325
  82. universal_mcp/applications/supabase/app.py +104 -213
  83. universal_mcp/applications/tavily/app.py +1 -1
  84. universal_mcp/applications/trello/app.py +693 -2656
  85. universal_mcp/applications/twilio/app.py +14 -50
  86. universal_mcp/applications/twitter/api_segments/compliance_api.py +4 -14
  87. universal_mcp/applications/twitter/api_segments/dm_conversations_api.py +6 -18
  88. universal_mcp/applications/twitter/api_segments/likes_api.py +1 -3
  89. universal_mcp/applications/twitter/api_segments/lists_api.py +5 -15
  90. universal_mcp/applications/twitter/api_segments/trends_api.py +1 -3
  91. universal_mcp/applications/twitter/api_segments/tweets_api.py +9 -31
  92. universal_mcp/applications/twitter/api_segments/usage_api.py +1 -5
  93. universal_mcp/applications/twitter/api_segments/users_api.py +14 -42
  94. universal_mcp/applications/whatsapp/app.py +35 -186
  95. universal_mcp/applications/whatsapp/audio.py +2 -6
  96. universal_mcp/applications/whatsapp/whatsapp.py +17 -51
  97. universal_mcp/applications/whatsapp_business/app.py +70 -283
  98. universal_mcp/applications/wrike/app.py +45 -118
  99. universal_mcp/applications/yahoo_finance/app.py +19 -65
  100. universal_mcp/applications/youtube/app.py +75 -261
  101. universal_mcp/applications/zenquotes/app.py +2 -2
  102. {universal_mcp_applications-0.1.32.dist-info → universal_mcp_applications-0.1.36rc2.dist-info}/METADATA +2 -2
  103. {universal_mcp_applications-0.1.32.dist-info → universal_mcp_applications-0.1.36rc2.dist-info}/RECORD +105 -105
  104. {universal_mcp_applications-0.1.32.dist-info → universal_mcp_applications-0.1.36rc2.dist-info}/WHEEL +0 -0
  105. {universal_mcp_applications-0.1.32.dist-info → universal_mcp_applications-0.1.36rc2.dist-info}/licenses/LICENSE +0 -0
@@ -1,7 +1,6 @@
1
1
  import json
2
2
  import os
3
3
  from typing import Any, Callable, Literal
4
-
5
4
  from loguru import logger
6
5
  from universal_mcp.applications.application import APIApplication
7
6
  from universal_mcp.integrations import Integration
@@ -23,7 +22,6 @@ class LinkedinApp(APIApplication):
23
22
  `{"headers": {"x-api-key": "YOUR_API_KEY"}}`.
24
23
  """
25
24
  super().__init__(name="linkedin", integration=integration)
26
-
27
25
  self._base_url = None
28
26
  self.account_id = None
29
27
  if self.integration:
@@ -36,12 +34,8 @@ class LinkedinApp(APIApplication):
36
34
  if not self._base_url:
37
35
  unipile_dsn = os.getenv("UNIPILE_DSN")
38
36
  if not unipile_dsn:
39
- logger.error(
40
- "UnipileApp: UNIPILE_DSN environment variable is not set."
41
- )
42
- raise ValueError(
43
- "UnipileApp: UNIPILE_DSN environment variable is required."
44
- )
37
+ logger.error("UnipileApp: UNIPILE_DSN environment variable is not set.")
38
+ raise ValueError("UnipileApp: UNIPILE_DSN environment variable is required.")
45
39
  self._base_url = f"https://{unipile_dsn}"
46
40
  return self._base_url
47
41
 
@@ -56,27 +50,14 @@ class LinkedinApp(APIApplication):
56
50
  Overrides the base class method to use X-Api-Key.
57
51
  """
58
52
  if not self.integration:
59
- logger.warning(
60
- "UnipileApp: No integration configured, returning empty headers."
61
- )
53
+ logger.warning("UnipileApp: No integration configured, returning empty headers.")
62
54
  return {}
63
-
64
55
  api_key = os.getenv("UNIPILE_API_KEY")
65
56
  if not api_key:
66
- logger.error(
67
- "UnipileApp: API key not found in integration credentials for Unipile."
68
- )
69
- return { # Or return minimal headers if some calls might not need auth (unlikely for Unipile)
70
- "Content-Type": "application/json",
71
- "Cache-Control": "no-cache",
72
- }
73
-
57
+ logger.error("UnipileApp: API key not found in integration credentials for Unipile.")
58
+ return {"Content-Type": "application/json", "Cache-Control": "no-cache"}
74
59
  logger.debug("UnipileApp: Using X-Api-Key for authentication.")
75
- return {
76
- "x-api-key": api_key,
77
- "Content-Type": "application/json",
78
- "Cache-Control": "no-cache", # Often good practice for APIs
79
- }
60
+ return {"x-api-key": api_key, "Content-Type": "application/json", "Cache-Control": "no-cache"}
80
61
 
81
62
  def _get_search_parameter_id(self, param_type: str, keywords: str) -> str:
82
63
  """
@@ -94,29 +75,21 @@ class LinkedinApp(APIApplication):
94
75
  httpx.HTTPError: If the API request fails.
95
76
  """
96
77
  url = f"{self.base_url}/api/v1/linkedin/search/parameters"
97
- params = {
98
- "account_id": self.account_id,
99
- "keywords": keywords,
100
- "type": param_type,
101
- }
102
-
78
+ params = {"account_id": self.account_id, "keywords": keywords, "type": param_type}
103
79
  response = self._get(url, params=params)
104
80
  results = self._handle_response(response)
105
-
106
81
  items = results.get("items", [])
107
82
  if items:
108
- # Return the ID of the first result, assuming it's the most relevant
109
83
  return items[0]["id"]
110
-
111
84
  raise ValueError(f'Could not find a matching ID for {param_type}: "{keywords}"')
112
85
 
113
- def list_all_chats(
86
+ async def list_all_chats(
114
87
  self,
115
88
  unread: bool | None = None,
116
89
  cursor: str | None = None,
117
- before: str | None = None, # ISO 8601 UTC datetime
118
- after: str | None = None, # ISO 8601 UTC datetime
119
- limit: int | None = None, # 1-250
90
+ before: str | None = None,
91
+ after: str | None = None,
92
+ limit: int | None = None,
120
93
  account_type: str | None = None,
121
94
  ) -> dict[str, Any]:
122
95
  """
@@ -141,9 +114,7 @@ class LinkedinApp(APIApplication):
141
114
  """
142
115
  url = f"{self.base_url}/api/v1/chats"
143
116
  params: dict[str, Any] = {}
144
-
145
117
  params["account_id"] = self.account_id
146
-
147
118
  if unread is not None:
148
119
  params["unread"] = unread
149
120
  if cursor:
@@ -156,18 +127,16 @@ class LinkedinApp(APIApplication):
156
127
  params["limit"] = limit
157
128
  if account_type:
158
129
  params["account_type"] = account_type
159
-
160
-
161
130
  response = self._get(url, params=params)
162
131
  return response.json()
163
132
 
164
- def list_chat_messages(
133
+ async def list_chat_messages(
165
134
  self,
166
135
  chat_id: str,
167
136
  cursor: str | None = None,
168
- before: str | None = None, # ISO 8601 UTC datetime
169
- after: str | None = None, # ISO 8601 UTC datetime
170
- limit: int | None = None, # 1-250
137
+ before: str | None = None,
138
+ after: str | None = None,
139
+ limit: int | None = None,
171
140
  sender_id: str | None = None,
172
141
  ) -> dict[str, Any]:
173
142
  """
@@ -202,15 +171,10 @@ class LinkedinApp(APIApplication):
202
171
  params["limit"] = limit
203
172
  if sender_id:
204
173
  params["sender_id"] = sender_id
205
-
206
174
  response = self._get(url, params=params)
207
175
  return response.json()
208
176
 
209
- def send_chat_message(
210
- self,
211
- chat_id: str,
212
- text: str,
213
- ) -> dict[str, Any]:
177
+ async def send_chat_message(self, chat_id: str, text: str) -> dict[str, Any]:
214
178
  """
215
179
  Sends a text message to a specific chat conversation using its `chat_id`. This function creates a new message via a POST request, distinguishing it from read-only functions like `list_chat_messages`. It returns the API's response, which typically confirms the successful creation of the message.
216
180
 
@@ -230,11 +194,10 @@ class LinkedinApp(APIApplication):
230
194
  """
231
195
  url = f"{self.base_url}/api/v1/chats/{chat_id}/messages"
232
196
  payload: dict[str, Any] = {"text": text}
233
-
234
197
  response = self._post(url, data=payload)
235
198
  return response.json()
236
199
 
237
- def retrieve_chat(self, chat_id: str) -> dict[str, Any]:
200
+ async def retrieve_chat(self, chat_id: str) -> dict[str, Any]:
238
201
  """
239
202
  Retrieves a single chat's details using its Unipile or provider-specific ID. This function is distinct from `list_all_chats`, which returns a collection, by targeting one specific conversation.
240
203
 
@@ -254,16 +217,15 @@ class LinkedinApp(APIApplication):
254
217
  params: dict[str, Any] = {}
255
218
  if self.account_id:
256
219
  params["account_id"] = self.account_id
257
-
258
220
  response = self._get(url, params=params)
259
221
  return response.json()
260
222
 
261
- def list_all_messages(
223
+ async def list_all_messages(
262
224
  self,
263
225
  cursor: str | None = None,
264
- before: str | None = None, # ISO 8601 UTC datetime
265
- after: str | None = None, # ISO 8601 UTC datetime
266
- limit: int | None = None, # 1-250
226
+ before: str | None = None,
227
+ after: str | None = None,
228
+ limit: int | None = None,
267
229
  sender_id: str | None = None,
268
230
  ) -> dict[str, Any]:
269
231
  """
@@ -299,16 +261,11 @@ class LinkedinApp(APIApplication):
299
261
  params["sender_id"] = sender_id
300
262
  if self.account_id:
301
263
  params["account_id"] = self.account_id
302
-
303
264
  response = self._get(url, params=params)
304
265
  return response.json()
305
266
 
306
- def list_profile_posts(
307
- self,
308
- identifier: str, # User or Company provider internal ID
309
- cursor: str | None = None,
310
- limit: int | None = None, # 1-100 (spec says max 250)
311
- is_company: bool | None = None,
267
+ async def list_profile_posts(
268
+ self, identifier: str, cursor: str | None = None, limit: int | None = None, is_company: bool | None = None
312
269
  ) -> dict[str, Any]:
313
270
  """
314
271
  Retrieves a paginated list of posts from a specific user or company profile using their provider ID. An authorizing `account_id` is required, and the `is_company` flag must specify the entity type, distinguishing this from `retrieve_post` which fetches a single post by its own ID.
@@ -336,11 +293,10 @@ class LinkedinApp(APIApplication):
336
293
  params["limit"] = limit
337
294
  if is_company is not None:
338
295
  params["is_company"] = is_company
339
-
340
296
  response = self._get(url, params=params)
341
297
  return response.json()
342
298
 
343
- def retrieve_own_profile(self) -> dict[str, Any]:
299
+ async def retrieve_own_profile(self) -> dict[str, Any]:
344
300
  """
345
301
  Retrieves the profile details for the user associated with the Unipile account. This function targets the API's 'me' endpoint to fetch the authenticated user's profile, distinct from `retrieve_user_profile` which fetches profiles of other users by their public identifier.
346
302
 
@@ -358,7 +314,7 @@ class LinkedinApp(APIApplication):
358
314
  response = self._get(url, params=params)
359
315
  return response.json()
360
316
 
361
- def retrieve_post(self, post_id: str) -> dict[str, Any]:
317
+ async def retrieve_post(self, post_id: str) -> dict[str, Any]:
362
318
  """
363
319
  Fetches a specific post's details by its unique ID. Unlike `list_profile_posts`, which retrieves a collection of posts from a user or company profile, this function targets one specific post and returns its full object.
364
320
 
@@ -379,18 +335,14 @@ class LinkedinApp(APIApplication):
379
335
  response = self._get(url, params=params)
380
336
  return response.json()
381
337
 
382
- def list_post_comments(
383
- self,
384
- post_id: str,
385
- comment_id: str | None = None,
386
- cursor: str | None = None,
387
- limit: int | None = None,
338
+ async def list_post_comments(
339
+ self, post_id: str, comment_id: str | None = None, cursor: str | None = None, limit: int | None = None
388
340
  ) -> dict[str, Any]:
389
341
  """
390
- Fetches comments for a specific post. Providing an optional `comment_id` retrieves threaded replies instead of top-level comments. This read-only operation contrasts with `create_post_comment`, which publishes new comments, and `list_content_reactions`, which retrieves 'likes'.
342
+ Fetches comments for a specific post. Providing an optional `comment_id` retrieves threaded replies instead of top-level comments. `retrieve_post` or `list_profile_posts` can be used to obtain the `post_id` which is the social_id in their response.
391
343
 
392
344
  Args:
393
- post_id: The social ID of the post.
345
+ post_id: The social ID of the post which you get from using `retrieve_post` or `list_profile_posts` tools.
394
346
  comment_id: If provided, retrieves replies to this comment ID instead of top-level comments.
395
347
  cursor: Pagination cursor.
396
348
  limit: Number of comments to return. (OpenAPI spec shows type string, passed as string if provided).
@@ -412,15 +364,11 @@ class LinkedinApp(APIApplication):
412
364
  params["limit"] = str(limit)
413
365
  if comment_id:
414
366
  params["comment_id"] = comment_id
415
-
416
367
  response = self._get(url, params=params)
417
368
  return response.json()
418
369
 
419
- def create_post(
420
- self,
421
- text: str,
422
- mentions: list[dict[str, Any]] | None = None,
423
- external_link: str | None = None,
370
+ async def create_post(
371
+ self, text: str, mentions: list[dict[str, Any]] | None = None, external_link: str | None = None
424
372
  ) -> dict[str, Any]:
425
373
  """
426
374
  Publishes a new top-level post from the account, including text, user mentions, and an external link. This function creates original content, distinguishing it from `create_post_comment` which adds replies to existing posts.
@@ -441,26 +389,16 @@ class LinkedinApp(APIApplication):
441
389
  linkedin, post, create, share, content, api, important
442
390
  """
443
391
  url = f"{self.base_url}/api/v1/posts"
444
-
445
- params: dict[str, str] = {
446
- "account_id": self.account_id,
447
- "text": text,
448
- }
449
-
392
+ params: dict[str, str] = {"account_id": self.account_id, "text": text}
450
393
  if mentions:
451
394
  params["mentions"] = mentions
452
395
  if external_link:
453
396
  params["external_link"] = external_link
454
-
455
397
  response = self._post(url, data=params)
456
398
  return response.json()
457
399
 
458
- def list_content_reactions(
459
- self,
460
- post_id: str,
461
- comment_id: str | None = None,
462
- cursor: str | None = None,
463
- limit: int | None = None,
400
+ async def list_content_reactions(
401
+ self, post_id: str, comment_id: str | None = None, cursor: str | None = None, limit: int | None = None
464
402
  ) -> dict[str, Any]:
465
403
  """
466
404
  Retrieves a paginated list of reactions for a given post or, optionally, a specific comment. This read-only operation uses the account for the request, distinguishing it from the `create_reaction` function which adds new reactions.
@@ -488,16 +426,11 @@ class LinkedinApp(APIApplication):
488
426
  params["limit"] = limit
489
427
  if comment_id:
490
428
  params["comment_id"] = comment_id
491
-
492
429
  response = self._get(url, params=params)
493
430
  return response.json()
494
431
 
495
- def create_post_comment(
496
- self,
497
- post_social_id: str,
498
- text: str,
499
- comment_id: str | None = None, # If provided, replies to a specific comment
500
- mentions_body: list[dict[str, Any]] | None = None,
432
+ async def create_post_comment(
433
+ self, post_social_id: str, text: str, comment_id: str | None = None, mentions_body: list[dict[str, Any]] | None = None
501
434
  ) -> dict[str, Any]:
502
435
  """
503
436
  Publishes a comment on a specified post. By providing an optional `comment_id`, it creates a threaded reply to an existing comment instead of a new top-level one. This function's dual capability distinguishes it from `list_post_comments`, which only retrieves comments and their replies.
@@ -519,33 +452,21 @@ class LinkedinApp(APIApplication):
519
452
  linkedin, post, comment, create, content, api, important
520
453
  """
521
454
  url = f"{self.base_url}/api/v1/posts/{post_social_id}/comments"
522
- params: dict[str, Any] = {
523
- "account_id": self.account_id,
524
- "text": text,
525
- }
526
-
455
+ params: dict[str, Any] = {"account_id": self.account_id, "text": text}
527
456
  if comment_id:
528
457
  params["comment_id"] = comment_id
529
-
530
458
  if mentions_body:
531
459
  params = {"mentions": mentions_body}
532
-
533
460
  response = self._post(url, data=params)
534
-
535
461
  try:
536
462
  return response.json()
537
463
  except json.JSONDecodeError:
538
- return {
539
- "status": response.status_code,
540
- "message": "Comment action processed.",
541
- }
464
+ return {"status": response.status_code, "message": "Comment action processed."}
542
465
 
543
- def create_reaction(
466
+ async def create_reaction(
544
467
  self,
545
468
  post_social_id: str,
546
- reaction_type: Literal[
547
- "like", "celebrate", "love", "insightful", "funny", "support"
548
- ],
469
+ reaction_type: Literal["like", "celebrate", "love", "insightful", "funny", "support"],
549
470
  comment_id: str | None = None,
550
471
  ) -> dict[str, Any]:
551
472
  """
@@ -566,32 +487,21 @@ class LinkedinApp(APIApplication):
566
487
  linkedin, post, reaction, create, like, content, api, important
567
488
  """
568
489
  url = f"{self.base_url}/api/v1/posts/reaction"
569
-
570
- params: dict[str, str] = {
571
- "account_id": self.account_id,
572
- "post_id": post_social_id,
573
- "reaction_type": reaction_type,
574
- }
575
-
490
+ params: dict[str, str] = {"account_id": self.account_id, "post_id": post_social_id, "reaction_type": reaction_type}
576
491
  if comment_id:
577
492
  params["comment_id"] = comment_id
578
-
579
493
  response = self._post(url, data=params)
580
-
581
494
  try:
582
495
  return response.json()
583
496
  except json.JSONDecodeError:
584
- return {
585
- "status": response.status_code,
586
- "message": "Reaction action processed.",
587
- }
497
+ return {"status": response.status_code, "message": "Reaction action processed."}
588
498
 
589
- def retrieve_user_profile(self, identifier: str) -> dict[str, Any]:
499
+ async def retrieve_user_profile(self, public_identifier: str) -> dict[str, Any]:
590
500
  """
591
501
  Retrieves a specific LinkedIn user's profile using their public or internal ID. Unlike `retrieve_own_profile`, which fetches the authenticated user's details, this function targets and returns data for any specified third-party user profile on the platform.
592
502
 
593
503
  Args:
594
- identifier: Can be the provider's internal id OR the provider's public id of the requested user.For example, for https://www.linkedin.com/in/manojbajaj95/, the identifier is "manojbajaj95".
504
+ public_identifier: Extract this value from the response of `search_people` tool. The response contains a public_identifier field.For example, for https://www.linkedin.com/in/manojbajaj95/, the identifier is "manojbajaj95".
595
505
 
596
506
  Returns:
597
507
  A dictionary containing the user's profile details.
@@ -602,12 +512,12 @@ class LinkedinApp(APIApplication):
602
512
  Tags:
603
513
  linkedin, user, profile, retrieve, get, api, important
604
514
  """
605
- url = f"{self.base_url}/api/v1/users/{identifier}"
515
+ url = f"{self.base_url}/api/v1/users/{public_identifier}"
606
516
  params: dict[str, Any] = {"account_id": self.account_id}
607
517
  response = self._get(url, params=params)
608
518
  return self._handle_response(response)
609
519
 
610
- def search_people(
520
+ async def search_people(
611
521
  self,
612
522
  cursor: str | None = None,
613
523
  limit: int | None = None,
@@ -618,47 +528,43 @@ class LinkedinApp(APIApplication):
618
528
  ) -> dict[str, Any]:
619
529
  """
620
530
  Searches for LinkedIn user profiles using keywords, with optional filters for location, industry, and company. This function specifically targets the 'people' category, distinguishing it from other search methods like `search_companies` or `search_jobs` that query different entity types through the same API endpoint.
621
-
531
+
622
532
  Args:
623
533
  cursor: Pagination cursor for the next page of entries.
624
534
  limit: Number of items to return (up to 50 for Classic search).
625
535
  keywords: Keywords to search for.
626
-
536
+ location: The geographical location to filter people by (e.g., "United States").
537
+ industry: The industry to filter people by.(eg., "Information Technology and Services").
538
+ company: The company to filter people by.(e.g., "Google").
539
+
627
540
  Returns:
628
541
  A dictionary containing search results and pagination details.
629
-
542
+
630
543
  Raises:
631
544
  httpx.HTTPError: If the API request fails.
632
545
  """
633
546
  url = f"{self.base_url}/api/v1/linkedin/search"
634
-
635
547
  params: dict[str, Any] = {"account_id": self.account_id}
636
548
  if cursor:
637
549
  params["cursor"] = cursor
638
550
  if limit is not None:
639
551
  params["limit"] = limit
640
-
641
552
  payload: dict[str, Any] = {"api": "classic", "category": "people"}
642
-
643
553
  if keywords:
644
554
  payload["keywords"] = keywords
645
-
646
555
  if location:
647
556
  location_id = self._get_search_parameter_id("LOCATION", location)
648
557
  payload["location"] = [location_id]
649
-
650
558
  if industry:
651
559
  industry_id = self._get_search_parameter_id("INDUSTRY", industry)
652
560
  payload["industry"] = [industry_id]
653
-
654
561
  if company:
655
562
  company_id = self._get_search_parameter_id("COMPANY", company)
656
563
  payload["company"] = [company_id]
657
-
658
564
  response = self._post(url, params=params, data=payload)
659
565
  return self._handle_response(response)
660
566
 
661
- def search_companies(
567
+ async def search_companies(
662
568
  self,
663
569
  cursor: str | None = None,
664
570
  limit: int | None = None,
@@ -668,43 +574,39 @@ class LinkedinApp(APIApplication):
668
574
  ) -> dict[str, Any]:
669
575
  """
670
576
  Performs a paginated search for companies on LinkedIn using keywords, with optional location and industry filters. Its specific 'companies' search category distinguishes it from other methods like `search_people` or `search_posts`, ensuring that only company profiles are returned.
671
-
577
+
672
578
  Args:
673
579
  cursor: Pagination cursor for the next page of entries.
674
580
  limit: Number of items to return (up to 50 for Classic search).
675
581
  keywords: Keywords to search for.
676
-
582
+ location: The geographical location to filter companies by (e.g., "United States").
583
+ industry: The industry to filter companies by.(e.g., "Information Technology and Services").
584
+
677
585
  Returns:
678
586
  A dictionary containing search results and pagination details.
679
-
587
+
680
588
  Raises:
681
589
  httpx.HTTPError: If the API request fails.
682
590
  """
683
591
  url = f"{self.base_url}/api/v1/linkedin/search"
684
-
685
592
  params: dict[str, Any] = {"account_id": self.account_id}
686
593
  if cursor:
687
594
  params["cursor"] = cursor
688
595
  if limit is not None:
689
596
  params["limit"] = limit
690
-
691
597
  payload: dict[str, Any] = {"api": "classic", "category": "companies"}
692
-
693
598
  if keywords:
694
599
  payload["keywords"] = keywords
695
-
696
600
  if location:
697
601
  location_id = self._get_search_parameter_id("LOCATION", location)
698
602
  payload["location"] = [location_id]
699
-
700
603
  if industry:
701
604
  industry_id = self._get_search_parameter_id("INDUSTRY", industry)
702
605
  payload["industry"] = [industry_id]
703
-
704
606
  response = self._post(url, params=params, data=payload)
705
607
  return self._handle_response(response)
706
608
 
707
- def search_posts(
609
+ async def search_posts(
708
610
  self,
709
611
  cursor: str | None = None,
710
612
  limit: int | None = None,
@@ -714,41 +616,37 @@ class LinkedinApp(APIApplication):
714
616
  ) -> dict[str, Any]:
715
617
  """
716
618
  Performs a keyword-based search for LinkedIn posts, allowing filters for date and sorting by relevance. This function executes a general, platform-wide content search, distinguishing it from other search functions that target people, companies, or jobs, and from `list_profile_posts` which retrieves from a specific profile.
717
-
619
+
718
620
  Args:
719
621
  cursor: Pagination cursor for the next page of entries.
720
622
  limit: Number of items to return (up to 50 for Classic search).
721
623
  keywords: Keywords to search for.
722
624
  date_posted: Filter by when the post was posted.
723
625
  sort_by: How to sort the results.
724
-
626
+
725
627
  Returns:
726
628
  A dictionary containing search results and pagination details.
727
-
629
+
728
630
  Raises:
729
631
  httpx.HTTPError: If the API request fails.
730
632
  """
731
633
  url = f"{self.base_url}/api/v1/linkedin/search"
732
-
733
634
  params: dict[str, Any] = {"account_id": self.account_id}
734
635
  if cursor:
735
636
  params["cursor"] = cursor
736
637
  if limit is not None:
737
638
  params["limit"] = limit
738
-
739
639
  payload: dict[str, Any] = {"api": "classic", "category": "posts"}
740
-
741
640
  if keywords:
742
641
  payload["keywords"] = keywords
743
642
  if date_posted:
744
643
  payload["date_posted"] = date_posted
745
644
  if sort_by:
746
645
  payload["sort_by"] = sort_by
747
-
748
646
  response = self._post(url, params=params, data=payload)
749
647
  return self._handle_response(response)
750
648
 
751
- def search_jobs(
649
+ async def search_jobs(
752
650
  self,
753
651
  cursor: str | None = None,
754
652
  limit: int | None = None,
@@ -760,56 +658,212 @@ class LinkedinApp(APIApplication):
760
658
  ) -> dict[str, Any]:
761
659
  """
762
660
  Performs a LinkedIn search for jobs, filtering results by keywords, region, industry, and minimum salary. Unlike other search functions (`search_people`, `search_companies`), this method is specifically configured to query the 'jobs' category, providing a paginated list of relevant employment opportunities.
763
-
661
+
764
662
  Args:
765
663
  cursor: Pagination cursor for the next page of entries.
766
664
  limit: Number of items to return (up to 50 for Classic search).
767
665
  keywords: Keywords to search for.
768
- location: The geographical location to filter jobs by (e.g., "United States").
769
- sort_by: How to sort the results.
666
+ region: The geographical region to filter jobs by (e.g., "United States").
667
+ sort_by: How to sort the results.(e.g., "relevance" or "date".)
770
668
  minimum_salary_value: The minimum salary to filter for.
771
-
669
+ industry: The industry to filter jobs by.(e.g., "Software Development").
670
+
772
671
  Returns:
773
672
  A dictionary containing search results and pagination details.
774
-
673
+
775
674
  Raises:
776
675
  httpx.HTTPError: If the API request fails.
777
676
  ValueError: If the specified location is not found.
778
677
  """
779
678
  url = f"{self.base_url}/api/v1/linkedin/search"
780
-
781
679
  params: dict[str, Any] = {"account_id": self.account_id}
782
680
  if cursor:
783
681
  params["cursor"] = cursor
784
682
  if limit is not None:
785
683
  params["limit"] = limit
786
-
787
684
  payload: dict[str, Any] = {
788
685
  "api": "classic",
789
686
  "category": "jobs",
790
- "minimum_salary": {
791
- "currency": "USD",
792
- "value": minimum_salary_value,
793
- },
687
+ "minimum_salary": {"currency": "USD", "value": minimum_salary_value},
794
688
  }
795
-
796
689
  if keywords:
797
690
  payload["keywords"] = keywords
798
691
  if sort_by:
799
692
  payload["sort_by"] = sort_by
800
-
801
- # If location is provided, get its ID and add it to the payload
802
693
  if region:
803
694
  location_id = self._get_search_parameter_id("LOCATION", region)
804
695
  payload["region"] = location_id
805
-
806
696
  if industry:
807
697
  industry_id = self._get_search_parameter_id("INDUSTRY", industry)
808
698
  payload["industry"] = [industry_id]
809
-
810
699
  response = self._post(url, params=params, data=payload)
811
700
  return self._handle_response(response)
812
701
 
702
+ async def send_invitation(self, provider_id: str, user_email: str | None = None, message: str | None = None) -> dict[str, Any]:
703
+ """
704
+ Sends a connection invitation to a LinkedIn user specified by their provider ID. An optional message and the user's email can be included.
705
+
706
+ Args:
707
+ provider_id: The LinkedIn provider ID of the user to invite. This is available in response of `retrieve_user_profile` tool.
708
+ user_email: Optional. The email address of the user, which may be required by LinkedIn.
709
+ message: Optional. A personalized message to include with the invitation (max 300 characters).
710
+
711
+ Returns:
712
+ A dictionary confirming the invitation was sent.
713
+
714
+ Raises:
715
+ httpx.HTTPError: If the API request fails.
716
+ ValueError: If the message exceeds 300 characters.
717
+
718
+ Tags:
719
+ linkedin, user, invite, connect, contact, api, important
720
+ """
721
+ url = f"{self.base_url}/api/v1/users/invite"
722
+ payload: dict[str, Any] = {"account_id": self.account_id, "provider_id": provider_id}
723
+ if user_email:
724
+ payload["user_email"] = user_email
725
+ if message:
726
+ if len(message) > 300:
727
+ raise ValueError("Message cannot exceed 300 characters.")
728
+ payload["message"] = message
729
+ response = self._post(url, data=payload)
730
+ try:
731
+ return response.json()
732
+ except json.JSONDecodeError:
733
+ return {"status": response.status_code, "message": "Invitation action processed."}
734
+
735
+ async def list_sent_invitations(self, cursor: str | None = None, limit: int | None = None) -> dict[str, Any]:
736
+ """
737
+ Retrieves a paginated list of all sent connection invitations that are currently pending. This function allows for iterating through the history of outstanding connection requests made from the specified account.
738
+
739
+ Args:
740
+ cursor: A pagination cursor for retrieving the next page of entries.
741
+ limit: The number of items to return, ranging from 1 to 100. Defaults to 10 if not specified.
742
+
743
+ Returns:
744
+ A dictionary containing a list of sent invitation objects and pagination details.
745
+
746
+ Raises:
747
+ httpx.HTTPError: If the API request fails.
748
+
749
+ Tags:
750
+ linkedin, user, invite, sent, list, contacts, api
751
+ """
752
+ url = f"{self.base_url}/api/v1/users/invite/sent"
753
+ params: dict[str, Any] = {"account_id": self.account_id}
754
+ if cursor:
755
+ params["cursor"] = cursor
756
+ if limit is not None:
757
+ params["limit"] = limit
758
+ response = self._get(url, params=params)
759
+ return response.json()
760
+
761
+ async def list_received_invitations(self, cursor: str | None = None, limit: int | None = None) -> dict[str, Any]:
762
+ """
763
+ Retrieves a paginated list of all received connection invitations. This function allows for reviewing and processing incoming connection requests to the specified account.
764
+
765
+ Args:
766
+ cursor: A pagination cursor for retrieving the next page of entries.
767
+ limit: The number of items to return, ranging from 1 to 100. Defaults to 10 if not specified.
768
+
769
+ Returns:
770
+ A dictionary containing a list of received invitation objects and pagination details.
771
+
772
+ Raises:
773
+ httpx.HTTPError: If the API request fails.
774
+
775
+ Tags:
776
+ linkedin, user, invite, received, list, contacts, api
777
+ """
778
+ url = f"{self.base_url}/api/v1/users/invite/received"
779
+ params: dict[str, Any] = {"account_id": self.account_id}
780
+ if cursor:
781
+ params["cursor"] = cursor
782
+ if limit is not None:
783
+ params["limit"] = limit
784
+ response = self._get(url, params=params)
785
+ return response.json()
786
+
787
+ async def handle_received_invitation(
788
+ self, invitation_id: str, action: Literal["accept", "decline"], shared_secret: str
789
+ ) -> dict[str, Any]:
790
+ """
791
+ Accepts or declines a received LinkedIn connection invitation using its ID and a required shared secret. This function performs a POST request to update the invitation's status, distinguishing it from read-only functions like `list_received_invitations`.
792
+
793
+ Args:
794
+ invitation_id: The ID of the invitation to handle.Get this ID from the 'list_received_invitations' tool.
795
+ action: The action to perform, either "accept" or "decline".
796
+ shared_secret: The token provided by LinkedIn, retrieved from the 'list_received_invitations' tool, which is mandatory for this action.
797
+
798
+ Returns:
799
+ A dictionary confirming the action was processed.
800
+
801
+ Raises:
802
+ httpx.HTTPError: If the API request fails.
803
+
804
+ Tags:
805
+ linkedin, user, invite, received, handle, accept, decline, api
806
+ """
807
+ url = f"{self.base_url}/api/v1/users/invite/received/{invitation_id}"
808
+ payload: dict[str, Any] = {"provider": "LINKEDIN", "action": action, "shared_secret": shared_secret, "account_id": self.account_id}
809
+ response = self._post(url, data=payload)
810
+ try:
811
+ return response.json()
812
+ except json.JSONDecodeError:
813
+ return {"status": response.status_code, "message": f"Invitation action '{action}' processed."}
814
+
815
+ async def list_followers(self, cursor: str | None = None, limit: int | None = None) -> dict[str, Any]:
816
+ """
817
+ Retrieves a paginated list of all followers for the current user's account. This function is distinct from `list_following` as it shows who follows the user, not who the user follows.
818
+
819
+ Args:
820
+ cursor: A pagination cursor for retrieving the next page of entries.
821
+ limit: The number of items to return, ranging from 1 to 1000.
822
+
823
+ Returns:
824
+ A dictionary containing a list of follower objects and pagination details.
825
+
826
+ Raises:
827
+ httpx.HTTPError: If the API request fails.
828
+
829
+ Tags:
830
+ linkedin, user, followers, list, contacts, api
831
+ """
832
+ url = f"{self.base_url}/api/v1/users/followers"
833
+ params: dict[str, Any] = {"account_id": self.account_id}
834
+ if cursor:
835
+ params["cursor"] = cursor
836
+ if limit is not None:
837
+ params["limit"] = limit
838
+ response = self._get(url, params=params)
839
+ return response.json()
840
+
841
+ def list_following(self, cursor: str | None = None, limit: int | None = None) -> dict[str, Any]:
842
+ """
843
+ Retrieves a paginated list of all accounts that the current user is following. This function is the counterpart to `list_followers`, focusing on the user's outgoing connections rather than incoming ones.
844
+
845
+ Args:
846
+ cursor: A pagination cursor for retrieving the next page of entries.
847
+ limit: The number of items to return, ranging from 1 to 1000.
848
+
849
+ Returns:
850
+ A dictionary containing a list of followed account objects and pagination details.
851
+
852
+ Raises:
853
+ httpx.HTTPError: If the API request fails.
854
+
855
+ Tags:
856
+ linkedin, user, following, list, contacts, api
857
+ """
858
+ url = f"{self.base_url}/api/v1/users/following"
859
+ params: dict[str, Any] = {"account_id": self.account_id}
860
+ if cursor:
861
+ params["cursor"] = cursor
862
+ if limit is not None:
863
+ params["limit"] = limit
864
+ response = self._get(url, params=params)
865
+ return response.json()
866
+
813
867
  def list_tools(self) -> list[Callable]:
814
868
  return [
815
869
  self.list_all_chats,
@@ -830,4 +884,9 @@ class LinkedinApp(APIApplication):
830
884
  self.search_jobs,
831
885
  self.search_people,
832
886
  self.search_posts,
887
+ self.send_invitation,
888
+ self.list_sent_invitations,
889
+ self.list_received_invitations,
890
+ self.handle_received_invitation,
891
+ self.list_followers,
833
892
  ]