universal-mcp-applications 0.1.30rc2__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 +88 -188
  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 +103 -580
  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 +392 -212
  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 +216 -102
  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 +50 -101
  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.30rc2.dist-info → universal_mcp_applications-0.1.36rc2.dist-info}/METADATA +2 -2
  103. {universal_mcp_applications-0.1.30rc2.dist-info → universal_mcp_applications-0.1.36rc2.dist-info}/RECORD +105 -105
  104. {universal_mcp_applications-0.1.30rc2.dist-info → universal_mcp_applications-0.1.36rc2.dist-info}/WHEEL +0 -0
  105. {universal_mcp_applications-0.1.30rc2.dist-info → universal_mcp_applications-0.1.36rc2.dist-info}/licenses/LICENSE +0 -0
@@ -9,7 +9,10 @@ This is automatically generated from OpenAPI schema for the ScraperApp API.
9
9
 
10
10
  | Tool | Description |
11
11
  |------|-------------|
12
- | `linkedin_post_search` | Performs a general LinkedIn search for posts using keywords and filters like date and content type. It supports pagination and can utilize either the 'classic' or 'sales_navigator' API, searching broadly across the platform rather than fetching posts from a specific user's profile. |
13
- | `linkedin_list_profile_posts` | Fetches a paginated list of all LinkedIn posts from a specific user or company profile using their unique identifier. This function retrieves content directly from a profile, unlike `linkedin_post_search` which finds posts across LinkedIn based on keywords and other filters. |
14
- | `linkedin_retrieve_profile` | Retrieves a specific LinkedIn user's profile by their unique identifier, which can be an internal provider ID or a public username. This function simplifies data access by delegating the actual profile retrieval request to the integrated Unipile application, distinct from functions that list posts or comments. |
15
- | `linkedin_list_post_comments` | Fetches comments for a specified LinkedIn post. If a `comment_id` is provided, it retrieves replies to that comment instead of top-level comments. This function supports pagination and specifically targets comments, unlike others in the class that search for or list entire posts. |
12
+ | `linkedin_list_profile_posts` | Fetches a paginated list of posts from a specific user or company profile using its provider ID. The `is_company` flag must specify the entity type. Unlike `linkedin_search_posts`, this function directly retrieves content from a known profile's feed instead of performing a global keyword search. |
13
+ | `linkedin_retrieve_profile` | Fetches a specific LinkedIn user's profile using their public or internal ID. Unlike `linkedin_search_people`, which discovers multiple users via keywords, this function targets and retrieves detailed data for a single, known individual based on a direct identifier. |
14
+ | `linkedin_list_post_comments` | Fetches a paginated list of comments for a specified LinkedIn post. It can retrieve either top-level comments or threaded replies if an optional `comment_id` is provided. This is a read-only operation, distinct from functions that search for posts or list user-specific content. |
15
+ | `linkedin_search_people` | Performs a paginated search for people on LinkedIn, distinct from searches for companies or jobs. It filters results using keywords, location, industry, and company, internally converting filter names like 'United States' into their required API IDs before making the request. |
16
+ | `linkedin_search_companies` | Executes a paginated LinkedIn search for companies, filtering by optional keywords, location, and industry. Unlike `linkedin_search_people` or `linkedin_search_jobs`, this function specifically sets the API search category to 'companies' to ensure that only company profiles are returned in the search results. |
17
+ | `linkedin_search_posts` | Performs a keyword-based search for LinkedIn posts, allowing results to be filtered by date and sorted by relevance. This function specifically queries the 'posts' category, distinguishing it from other search methods in the class that target people, companies, or jobs, and returns relevant content. |
18
+ | `linkedin_search_jobs` | Executes a LinkedIn search specifically for job listings using keywords and filters like region, industry, and minimum salary. Unlike other search functions targeting people or companies, this is specialized for job listings and converts friendly filter names (e.g., "United States") into their required API IDs. |
@@ -2,13 +2,12 @@ import os
2
2
  from dotenv import load_dotenv
3
3
 
4
4
  load_dotenv()
5
-
6
5
  from typing import Any, Literal
7
-
8
6
  from loguru import logger
9
7
  from universal_mcp.applications.application import APIApplication
10
8
  from universal_mcp.integrations import Integration
11
9
 
10
+
12
11
  class ScraperApp(APIApplication):
13
12
  """
14
13
  Application for interacting with LinkedIn API.
@@ -29,12 +28,8 @@ class ScraperApp(APIApplication):
29
28
  if not self._base_url:
30
29
  unipile_dsn = os.getenv("UNIPILE_DSN")
31
30
  if not unipile_dsn:
32
- logger.error(
33
- "UnipileApp: UNIPILE_DSN environment variable is not set."
34
- )
35
- raise ValueError(
36
- "UnipileApp: UNIPILE_DSN environment variable is required."
37
- )
31
+ logger.error("UnipileApp: UNIPILE_DSN environment variable is not set.")
32
+ raise ValueError("UnipileApp: UNIPILE_DSN environment variable is required.")
38
33
  self._base_url = f"https://{unipile_dsn}"
39
34
  return self._base_url
40
35
 
@@ -49,104 +44,44 @@ class ScraperApp(APIApplication):
49
44
  Overrides the base class method to use X-Api-Key.
50
45
  """
51
46
  if not self.integration:
52
- logger.warning(
53
- "UnipileApp: No integration configured, returning empty headers."
54
- )
47
+ logger.warning("UnipileApp: No integration configured, returning empty headers.")
55
48
  return {}
56
-
57
49
  api_key = os.getenv("UNIPILE_API_KEY")
58
50
  if not api_key:
59
- logger.error(
60
- "UnipileApp: API key not found in integration credentials for Unipile."
61
- )
62
- return { # Or return minimal headers if some calls might not need auth (unlikely for Unipile)
63
- "Content-Type": "application/json",
64
- "Cache-Control": "no-cache",
65
- }
66
-
51
+ logger.error("UnipileApp: API key not found in integration credentials for Unipile.")
52
+ return {"Content-Type": "application/json", "Cache-Control": "no-cache"}
67
53
  logger.debug("UnipileApp: Using X-Api-Key for authentication.")
68
- return {
69
- "x-api-key": api_key,
70
- "Content-Type": "application/json",
71
- "Cache-Control": "no-cache", # Often good practice for APIs
72
- }
54
+ return {"x-api-key": api_key, "Content-Type": "application/json", "Cache-Control": "no-cache"}
73
55
 
74
- def linkedin_search(
75
- self,
76
- category: Literal["people", "companies", "posts", "jobs"],
77
- cursor: str | None = None,
78
- limit: int | None = None,
79
- keywords: str | None = None,
80
- date_posted: Literal["past_day", "past_week", "past_month"] | None = None,
81
- sort_by: Literal["relevance", "date"] = "relevance",
82
- minimum_salary_value: int = 40,
83
- ) -> dict[str, Any]:
56
+ def _get_search_parameter_id(self, param_type: str, keywords: str) -> str:
84
57
  """
85
- Performs a comprehensive LinkedIn search for people, companies, posts, or jobs using keywords.
86
- Supports pagination and targets either the classic or Sales Navigator API for posts.
87
- For people, companies, and jobs, it uses the classic API.
58
+ Retrieves the ID for a given LinkedIn search parameter by its name.
88
59
 
89
60
  Args:
90
- category: Type of search to perform. Valid values are "people", "companies", "posts", or "jobs".
91
- cursor: Pagination cursor for the next page of entries.
92
- limit: Number of items to return (up to 50 for Classic search).
93
- keywords: Keywords to search for.
94
- date_posted: Filter by when the post was posted (posts only). Valid values are "past_day", "past_week", or "past_month".
95
- sort_by: How to sort the results (for posts and jobs). Valid values are "relevance" or "date".
96
- minimum_salary_value: The minimum salary to filter for (jobs only).
61
+ param_type: The type of parameter to search for (e.g., "LOCATION", "COMPANY").
62
+ keywords: The name of the parameter to find (e.g., "United States").
97
63
 
98
64
  Returns:
99
- A dictionary containing search results and pagination details.
65
+ The corresponding ID for the search parameter.
100
66
 
101
67
  Raises:
68
+ ValueError: If no exact match for the keywords is found.
102
69
  httpx.HTTPError: If the API request fails.
103
- ValueError: If the category is empty.
104
-
105
- Tags:
106
- linkedin, search, people, companies, posts, jobs, api, important
107
70
  """
108
- if not category:
109
- raise ValueError("Category cannot be empty.")
110
-
111
- url = f"{self.base_url}/api/v1/linkedin/search"
112
-
113
- params: dict[str, Any] = {"account_id": self.account_id}
114
- if cursor:
115
- params["cursor"] = cursor
116
- if limit is not None:
117
- params["limit"] = limit
118
-
119
- payload: dict[str, Any] = {"api": "classic", "category": category}
120
-
121
- if keywords:
122
- payload["keywords"] = keywords
123
-
124
- if category == "posts":
125
- if date_posted:
126
- payload["date_posted"] = date_posted
127
- if sort_by:
128
- payload["sort_by"] = sort_by
129
-
130
- elif category == "jobs":
131
- payload["minimum_salary"] = {
132
- "currency": "USD",
133
- "value": minimum_salary_value,
134
- }
135
- if sort_by:
136
- payload["sort_by"] = sort_by
137
-
138
- response = self._post(url, params=params, data=payload)
139
- return self._handle_response(response)
140
-
141
- def linkedin_list_profile_posts(
142
- self,
143
- identifier: str, # User or Company provider internal ID
144
- cursor: str | None = None,
145
- limit: int | None = None, # 1-100 (spec says max 250)
146
- is_company: bool | None = None,
71
+ url = f"{self.base_url}/api/v1/linkedin/search/parameters"
72
+ params = {"account_id": self.account_id, "keywords": keywords, "type": param_type}
73
+ response = self._get(url, params=params)
74
+ results = self._handle_response(response)
75
+ items = results.get("items", [])
76
+ if items:
77
+ return items[0]["id"]
78
+ raise ValueError(f'Could not find a matching ID for {param_type}: "{keywords}"')
79
+
80
+ async def linkedin_list_profile_posts(
81
+ self, identifier: str, cursor: str | None = None, limit: int | None = None, is_company: bool | None = None
147
82
  ) -> dict[str, Any]:
148
83
  """
149
- 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.
84
+ Fetches a paginated list of posts from a specific user or company profile using its provider ID. The `is_company` flag must specify the entity type. Unlike `linkedin_search_posts`, this function directly retrieves content from a known profile's feed instead of performing a global keyword search.
150
85
 
151
86
  Args:
152
87
  identifier: The entity's provider internal ID (LinkedIn ID).
@@ -171,13 +106,12 @@ class ScraperApp(APIApplication):
171
106
  params["limit"] = limit
172
107
  if is_company is not None:
173
108
  params["is_company"] = is_company
174
-
175
109
  response = self._get(url, params=params)
176
110
  return response.json()
177
111
 
178
- def linkedin_retrieve_profile(self, identifier: str) -> dict[str, Any]:
112
+ async def linkedin_retrieve_profile(self, identifier: str) -> dict[str, Any]:
179
113
  """
180
- 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.
114
+ Fetches a specific LinkedIn user's profile using their public or internal ID. Unlike `linkedin_search_people`, which discovers multiple users via keywords, this function targets and retrieves detailed data for a single, known individual based on a direct identifier.
181
115
 
182
116
  Args:
183
117
  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".
@@ -196,15 +130,11 @@ class ScraperApp(APIApplication):
196
130
  response = self._get(url, params=params)
197
131
  return self._handle_response(response)
198
132
 
199
- def linkedin_list_post_comments(
200
- self,
201
- post_id: str,
202
- comment_id: str | None = None,
203
- cursor: str | None = None,
204
- limit: int | None = None,
133
+ async def linkedin_list_post_comments(
134
+ self, post_id: str, comment_id: str | None = None, cursor: str | None = None, limit: int | None = None
205
135
  ) -> dict[str, Any]:
206
136
  """
207
- 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'.
137
+ Fetches a paginated list of comments for a specified LinkedIn post. It can retrieve either top-level comments or threaded replies if an optional `comment_id` is provided. This is a read-only operation, distinct from functions that search for posts or list user-specific content.
208
138
 
209
139
  Args:
210
140
  post_id: The social ID of the post.
@@ -229,10 +159,191 @@ class ScraperApp(APIApplication):
229
159
  params["limit"] = str(limit)
230
160
  if comment_id:
231
161
  params["comment_id"] = comment_id
232
-
233
162
  response = self._get(url, params=params)
234
163
  return response.json()
235
164
 
165
+ async def linkedin_search_people(
166
+ self,
167
+ cursor: str | None = None,
168
+ limit: int | None = None,
169
+ keywords: str | None = None,
170
+ location: str | None = None,
171
+ industry: str | None = None,
172
+ company: str | None = None,
173
+ ) -> dict[str, Any]:
174
+ """
175
+ Performs a paginated search for people on LinkedIn, distinct from searches for companies or jobs. It filters results using keywords, location, industry, and company, internally converting filter names like 'United States' into their required API IDs before making the request.
176
+
177
+ Args:
178
+ cursor: Pagination cursor for the next page of entries.
179
+ limit: Number of items to return (up to 50 for Classic search).
180
+ keywords: Keywords to search for.
181
+ location: The geographical location to filter people by (e.g., "United States").
182
+ industry: The industry to filter people by.(e.g., "Software Development".)
183
+ company: The company to filter people by.(e.g., "Google".)
184
+
185
+ Returns:
186
+ A dictionary containing search results and pagination details.
187
+
188
+ Raises:
189
+ httpx.HTTPError: If the API request fails.
190
+ """
191
+ url = f"{self.base_url}/api/v1/linkedin/search"
192
+ params: dict[str, Any] = {"account_id": self.account_id}
193
+ if cursor:
194
+ params["cursor"] = cursor
195
+ if limit is not None:
196
+ params["limit"] = limit
197
+ payload: dict[str, Any] = {"api": "classic", "category": "people"}
198
+ if keywords:
199
+ payload["keywords"] = keywords
200
+ if location:
201
+ location_id = self._get_search_parameter_id("LOCATION", location)
202
+ payload["location"] = [location_id]
203
+ if industry:
204
+ industry_id = self._get_search_parameter_id("INDUSTRY", industry)
205
+ payload["industry"] = [industry_id]
206
+ if company:
207
+ company_id = self._get_search_parameter_id("COMPANY", company)
208
+ payload["company"] = [company_id]
209
+ response = self._post(url, params=params, data=payload)
210
+ return self._handle_response(response)
211
+
212
+ async def linkedin_search_companies(
213
+ self,
214
+ cursor: str | None = None,
215
+ limit: int | None = None,
216
+ keywords: str | None = None,
217
+ location: str | None = None,
218
+ industry: str | None = None,
219
+ ) -> dict[str, Any]:
220
+ """
221
+ Executes a paginated LinkedIn search for companies, filtering by optional keywords, location, and industry. Unlike `linkedin_search_people` or `linkedin_search_jobs`, this function specifically sets the API search category to 'companies' to ensure that only company profiles are returned in the search results.
222
+
223
+ Args:
224
+ cursor: Pagination cursor for the next page of entries.
225
+ limit: Number of items to return (up to 50 for Classic search).
226
+ keywords: Keywords to search for.
227
+ location: The geographical location to filter companies by (e.g., "United States").
228
+ industry: The industry to filter companies by.(e.g., "Software Development".)
229
+
230
+ Returns:
231
+ A dictionary containing search results and pagination details.
232
+
233
+ Raises:
234
+ httpx.HTTPError: If the API request fails.
235
+ """
236
+ url = f"{self.base_url}/api/v1/linkedin/search"
237
+ params: dict[str, Any] = {"account_id": self.account_id}
238
+ if cursor:
239
+ params["cursor"] = cursor
240
+ if limit is not None:
241
+ params["limit"] = limit
242
+ payload: dict[str, Any] = {"api": "classic", "category": "companies"}
243
+ if keywords:
244
+ payload["keywords"] = keywords
245
+ if location:
246
+ location_id = self._get_search_parameter_id("LOCATION", location)
247
+ payload["location"] = [location_id]
248
+ if industry:
249
+ industry_id = self._get_search_parameter_id("INDUSTRY", industry)
250
+ payload["industry"] = [industry_id]
251
+ response = self._post(url, params=params, data=payload)
252
+ return self._handle_response(response)
253
+
254
+ async def linkedin_search_posts(
255
+ self,
256
+ cursor: str | None = None,
257
+ limit: int | None = None,
258
+ keywords: str | None = None,
259
+ date_posted: Literal["past_day", "past_week", "past_month"] | None = None,
260
+ sort_by: Literal["relevance", "date"] = "relevance",
261
+ ) -> dict[str, Any]:
262
+ """
263
+ Performs a keyword-based search for LinkedIn posts, allowing results to be filtered by date and sorted by relevance. This function specifically queries the 'posts' category, distinguishing it from other search methods in the class that target people, companies, or jobs, and returns relevant content.
264
+
265
+ Args:
266
+ cursor: Pagination cursor for the next page of entries.
267
+ limit: Number of items to return (up to 50 for Classic search).
268
+ keywords: Keywords to search for.
269
+ date_posted: Filter by when the post was posted.
270
+ sort_by: How to sort the results.
271
+
272
+ Returns:
273
+ A dictionary containing search results and pagination details.
274
+
275
+ Raises:
276
+ httpx.HTTPError: If the API request fails.
277
+ """
278
+ url = f"{self.base_url}/api/v1/linkedin/search"
279
+ params: dict[str, Any] = {"account_id": self.account_id}
280
+ if cursor:
281
+ params["cursor"] = cursor
282
+ if limit is not None:
283
+ params["limit"] = limit
284
+ payload: dict[str, Any] = {"api": "classic", "category": "posts"}
285
+ if keywords:
286
+ payload["keywords"] = keywords
287
+ if date_posted:
288
+ payload["date_posted"] = date_posted
289
+ if sort_by:
290
+ payload["sort_by"] = sort_by
291
+ response = self._post(url, params=params, data=payload)
292
+ return self._handle_response(response)
293
+
294
+ async def linkedin_search_jobs(
295
+ self,
296
+ cursor: str | None = None,
297
+ limit: int | None = None,
298
+ keywords: str | None = None,
299
+ region: str | None = None,
300
+ sort_by: Literal["relevance", "date"] = "relevance",
301
+ minimum_salary_value: int = 40,
302
+ industry: str | None = None,
303
+ ) -> dict[str, Any]:
304
+ """
305
+ Executes a LinkedIn search specifically for job listings using keywords and filters like region, industry, and minimum salary. Unlike other search functions targeting people or companies, this is specialized for job listings and converts friendly filter names (e.g., "United States") into their required API IDs.
306
+
307
+ Args:
308
+ cursor: Pagination cursor for the next page of entries.
309
+ limit: Number of items to return (up to 50 for Classic search).
310
+ keywords: Keywords to search for.
311
+ region: The geographical region to filter jobs by (e.g., "United States").
312
+ sort_by: How to sort the results.(e.g., "relevance" or "date".)
313
+ minimum_salary_value: The minimum salary to filter for.
314
+ industry: The industry to filter jobs by. (e.g., "Software Development".)
315
+
316
+ Returns:
317
+ A dictionary containing search results and pagination details.
318
+
319
+ Raises:
320
+ httpx.HTTPError: If the API request fails.
321
+ ValueError: If the specified location is not found.
322
+ """
323
+ url = f"{self.base_url}/api/v1/linkedin/search"
324
+ params: dict[str, Any] = {"account_id": self.account_id}
325
+ if cursor:
326
+ params["cursor"] = cursor
327
+ if limit is not None:
328
+ params["limit"] = limit
329
+ payload: dict[str, Any] = {
330
+ "api": "classic",
331
+ "category": "jobs",
332
+ "minimum_salary": {"currency": "USD", "value": minimum_salary_value},
333
+ }
334
+ if keywords:
335
+ payload["keywords"] = keywords
336
+ if sort_by:
337
+ payload["sort_by"] = sort_by
338
+ if region:
339
+ location_id = self._get_search_parameter_id("LOCATION", region)
340
+ payload["region"] = location_id
341
+ if industry:
342
+ industry_id = self._get_search_parameter_id("INDUSTRY", industry)
343
+ payload["industry"] = [industry_id]
344
+ response = self._post(url, params=params, data=payload)
345
+ return self._handle_response(response)
346
+
236
347
  def list_tools(self):
237
348
  """
238
349
  Returns a list of available tools/functions in this application.
@@ -241,8 +352,11 @@ class ScraperApp(APIApplication):
241
352
  A list of functions that can be used as tools.
242
353
  """
243
354
  return [
244
- self.linkedin_search,
245
355
  self.linkedin_list_profile_posts,
246
356
  self.linkedin_retrieve_profile,
247
357
  self.linkedin_list_post_comments,
358
+ self.linkedin_search_people,
359
+ self.linkedin_search_companies,
360
+ self.linkedin_search_posts,
361
+ self.linkedin_search_jobs,
248
362
  ]
@@ -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 SemanticscholarApp(APIApplication):
9
8
  super().__init__(name="semanticscholar", integration=integration, **kwargs)
10
9
  self.base_url = "/graph/v1"
11
10
 
12
- def post_graph_get_authors(self, fields=None, ids=None) -> dict[str, Any]:
11
+ async def post_graph_get_authors(self, fields=None, ids=None) -> dict[str, Any]:
13
12
  """
14
13
  Creates a batch of authors using the provided JSON data in the request body, optionally specifying fields to include in the response via a query parameter.
15
14
 
@@ -23,9 +22,7 @@ class SemanticscholarApp(APIApplication):
23
22
  Tags:
24
23
  Author Data
25
24
  """
26
- request_body = {
27
- "ids": ids,
28
- }
25
+ request_body = {"ids": ids}
29
26
  request_body = {k: v for k, v in request_body.items() if v is not None}
30
27
  url = f"{self.base_url}/author/batch"
31
28
  query_params = {k: v for k, v in [("fields", fields)] if v is not None}
@@ -33,9 +30,7 @@ class SemanticscholarApp(APIApplication):
33
30
  response.raise_for_status()
34
31
  return response.json()
35
32
 
36
- def get_graph_get_author_search(
37
- self, query, offset=None, limit=None, fields=None
38
- ) -> dict[str, Any]:
33
+ async def get_graph_get_author_search(self, query, offset=None, limit=None, fields=None) -> dict[str, Any]:
39
34
  """
40
35
  Searches for authors based on a query string with optional pagination and field selection parameters.
41
36
 
@@ -52,21 +47,12 @@ class SemanticscholarApp(APIApplication):
52
47
  Author Data
53
48
  """
54
49
  url = f"{self.base_url}/author/search"
55
- query_params = {
56
- k: v
57
- for k, v in [
58
- ("offset", offset),
59
- ("limit", limit),
60
- ("fields", fields),
61
- ("query", query),
62
- ]
63
- if v is not None
64
- }
50
+ query_params = {k: v for k, v in [("offset", offset), ("limit", limit), ("fields", fields), ("query", query)] if v is not None}
65
51
  response = self._get(url, params=query_params)
66
52
  response.raise_for_status()
67
53
  return response.json()
68
54
 
69
- def get_graph_get_author(self, author_id, fields=None) -> dict[str, Any]:
55
+ async def get_graph_get_author(self, author_id, fields=None) -> dict[str, Any]:
70
56
  """
71
57
  Retrieves the profile information for a specific author identified by the `author_id` and returns it with optional fields specified in the `fields` query parameter.
72
58
 
@@ -88,9 +74,7 @@ class SemanticscholarApp(APIApplication):
88
74
  response.raise_for_status()
89
75
  return response.json()
90
76
 
91
- def get_graph_get_author_papers(
92
- self, author_id, offset=None, limit=None, fields=None
93
- ) -> dict[str, Any]:
77
+ async def get_graph_get_author_papers(self, author_id, offset=None, limit=None, fields=None) -> dict[str, Any]:
94
78
  """
95
79
  Retrieves a paginated list of papers authored by the specified author, with optional field selection.
96
80
 
@@ -109,16 +93,12 @@ class SemanticscholarApp(APIApplication):
109
93
  if author_id is None:
110
94
  raise ValueError("Missing required parameter 'author_id'")
111
95
  url = f"{self.base_url}/author/{author_id}/papers"
112
- query_params = {
113
- k: v
114
- for k, v in [("offset", offset), ("limit", limit), ("fields", fields)]
115
- if v is not None
116
- }
96
+ query_params = {k: v for k, v in [("offset", offset), ("limit", limit), ("fields", fields)] if v is not None}
117
97
  response = self._get(url, params=query_params)
118
98
  response.raise_for_status()
119
99
  return response.json()
120
100
 
121
- def get_graph_get_paper_autocomplete(self, query) -> dict[str, Any]:
101
+ async def get_graph_get_paper_autocomplete(self, query) -> dict[str, Any]:
122
102
  """
123
103
  Provides an autocomplete suggestion list based on a required query string parameter.
124
104
 
@@ -137,7 +117,7 @@ class SemanticscholarApp(APIApplication):
137
117
  response.raise_for_status()
138
118
  return response.json()
139
119
 
140
- def post_graph_get_papers(self, fields=None, ids=None) -> dict[str, Any]:
120
+ async def post_graph_get_papers(self, fields=None, ids=None) -> dict[str, Any]:
141
121
  """
142
122
  Creates a batch of papers using JSON data in the request body and optionally specifies fields to include in the response.
143
123
 
@@ -151,9 +131,7 @@ class SemanticscholarApp(APIApplication):
151
131
  Tags:
152
132
  Paper Data
153
133
  """
154
- request_body = {
155
- "ids": ids,
156
- }
134
+ request_body = {"ids": ids}
157
135
  request_body = {k: v for k, v in request_body.items() if v is not None}
158
136
  url = f"{self.base_url}/paper/batch"
159
137
  query_params = {k: v for k, v in [("fields", fields)] if v is not None}
@@ -161,7 +139,7 @@ class SemanticscholarApp(APIApplication):
161
139
  response.raise_for_status()
162
140
  return response.json()
163
141
 
164
- def get_graph_paper_relevance_search(
142
+ async def get_graph_paper_relevance_search(
165
143
  self,
166
144
  query,
167
145
  fields=None,
@@ -219,7 +197,7 @@ class SemanticscholarApp(APIApplication):
219
197
  response.raise_for_status()
220
198
  return response.json()
221
199
 
222
- def get_graph_paper_bulk_search(
200
+ async def get_graph_paper_bulk_search(
223
201
  self,
224
202
  query,
225
203
  token=None,
@@ -277,7 +255,7 @@ class SemanticscholarApp(APIApplication):
277
255
  response.raise_for_status()
278
256
  return response.json()
279
257
 
280
- def get_graph_paper_title_search(
258
+ async def get_graph_paper_title_search(
281
259
  self,
282
260
  query,
283
261
  fields=None,
@@ -329,7 +307,7 @@ class SemanticscholarApp(APIApplication):
329
307
  response.raise_for_status()
330
308
  return response.json()
331
309
 
332
- def get_graph_get_paper(self, paper_id, fields=None) -> dict[str, Any]:
310
+ async def get_graph_get_paper(self, paper_id, fields=None) -> dict[str, Any]:
333
311
  """
334
312
  Retrieves details of a paper by its ID, optionally specifying fields to include in the response.
335
313
 
@@ -351,9 +329,7 @@ class SemanticscholarApp(APIApplication):
351
329
  response.raise_for_status()
352
330
  return response.json()
353
331
 
354
- def get_graph_get_paper_authors(
355
- self, paper_id, offset=None, limit=None, fields=None
356
- ) -> dict[str, Any]:
332
+ async def get_graph_get_paper_authors(self, paper_id, offset=None, limit=None, fields=None) -> dict[str, Any]:
357
333
  """
358
334
  Retrieves a list of authors for a specific paper identified by the `paper_id`, allowing optional parameters for offset, limit, and fields to customize the response.
359
335
 
@@ -372,18 +348,12 @@ class SemanticscholarApp(APIApplication):
372
348
  if paper_id is None:
373
349
  raise ValueError("Missing required parameter 'paper_id'")
374
350
  url = f"{self.base_url}/paper/{paper_id}/authors"
375
- query_params = {
376
- k: v
377
- for k, v in [("offset", offset), ("limit", limit), ("fields", fields)]
378
- if v is not None
379
- }
351
+ query_params = {k: v for k, v in [("offset", offset), ("limit", limit), ("fields", fields)] if v is not None}
380
352
  response = self._get(url, params=query_params)
381
353
  response.raise_for_status()
382
354
  return response.json()
383
355
 
384
- def get_graph_get_paper_citations(
385
- self, paper_id, offset=None, limit=None, fields=None
386
- ) -> dict[str, Any]:
356
+ async def get_graph_get_paper_citations(self, paper_id, offset=None, limit=None, fields=None) -> dict[str, Any]:
387
357
  """
388
358
  Retrieves a list of citations for a specific paper, identified by its paper ID, with optional parameters for offset, limit, and fields.
389
359
 
@@ -402,18 +372,12 @@ class SemanticscholarApp(APIApplication):
402
372
  if paper_id is None:
403
373
  raise ValueError("Missing required parameter 'paper_id'")
404
374
  url = f"{self.base_url}/paper/{paper_id}/citations"
405
- query_params = {
406
- k: v
407
- for k, v in [("offset", offset), ("limit", limit), ("fields", fields)]
408
- if v is not None
409
- }
375
+ query_params = {k: v for k, v in [("offset", offset), ("limit", limit), ("fields", fields)] if v is not None}
410
376
  response = self._get(url, params=query_params)
411
377
  response.raise_for_status()
412
378
  return response.json()
413
379
 
414
- def get_graph_get_paper_references(
415
- self, paper_id, offset=None, limit=None, fields=None
416
- ) -> dict[str, Any]:
380
+ async def get_graph_get_paper_references(self, paper_id, offset=None, limit=None, fields=None) -> dict[str, Any]:
417
381
  """
418
382
  Retrieves references for a specific paper by its ID using the "GET" method and allows optional filtering by offset, limit, and fields for customizable output.
419
383
 
@@ -432,16 +396,12 @@ class SemanticscholarApp(APIApplication):
432
396
  if paper_id is None:
433
397
  raise ValueError("Missing required parameter 'paper_id'")
434
398
  url = f"{self.base_url}/paper/{paper_id}/references"
435
- query_params = {
436
- k: v
437
- for k, v in [("offset", offset), ("limit", limit), ("fields", fields)]
438
- if v is not None
439
- }
399
+ query_params = {k: v for k, v in [("offset", offset), ("limit", limit), ("fields", fields)] if v is not None}
440
400
  response = self._get(url, params=query_params)
441
401
  response.raise_for_status()
442
402
  return response.json()
443
403
 
444
- def get_snippet_search(self, query, limit=None) -> dict[str, Any]:
404
+ async def get_snippet_search(self, query, limit=None) -> dict[str, Any]:
445
405
  """
446
406
  Retrieves a list of search results based on a specified query string, optionally limited by a user-defined number of results, using the "GET" method at the "/snippet/search" endpoint.
447
407
 
@@ -456,9 +416,7 @@ class SemanticscholarApp(APIApplication):
456
416
  Snippet Text
457
417
  """
458
418
  url = f"{self.base_url}/snippet/search"
459
- query_params = {
460
- k: v for k, v in [("query", query), ("limit", limit)] if v is not None
461
- }
419
+ query_params = {k: v for k, v in [("query", query), ("limit", limit)] if v is not None}
462
420
  response = self._get(url, params=query_params)
463
421
  response.raise_for_status()
464
422
  return response.json()