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.
- universal_mcp/applications/ahrefs/app.py +52 -198
- universal_mcp/applications/airtable/app.py +23 -122
- universal_mcp/applications/apollo/app.py +111 -464
- universal_mcp/applications/asana/app.py +417 -1567
- universal_mcp/applications/aws_s3/app.py +36 -103
- universal_mcp/applications/bill/app.py +546 -1957
- universal_mcp/applications/box/app.py +1068 -3981
- universal_mcp/applications/braze/app.py +364 -1430
- universal_mcp/applications/browser_use/app.py +2 -8
- universal_mcp/applications/cal_com_v2/app.py +207 -625
- universal_mcp/applications/calendly/app.py +61 -200
- universal_mcp/applications/canva/app.py +45 -110
- universal_mcp/applications/clickup/app.py +207 -674
- universal_mcp/applications/coda/app.py +146 -426
- universal_mcp/applications/confluence/app.py +310 -1098
- universal_mcp/applications/contentful/app.py +36 -151
- universal_mcp/applications/crustdata/app.py +28 -107
- universal_mcp/applications/dialpad/app.py +283 -756
- universal_mcp/applications/digitalocean/app.py +1766 -5777
- universal_mcp/applications/domain_checker/app.py +3 -54
- universal_mcp/applications/e2b/app.py +14 -64
- universal_mcp/applications/elevenlabs/app.py +9 -47
- universal_mcp/applications/exa/app.py +6 -17
- universal_mcp/applications/falai/app.py +24 -101
- universal_mcp/applications/figma/app.py +53 -137
- universal_mcp/applications/file_system/app.py +2 -13
- universal_mcp/applications/firecrawl/app.py +51 -152
- universal_mcp/applications/fireflies/app.py +59 -281
- universal_mcp/applications/fpl/app.py +91 -528
- 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 +52 -161
- universal_mcp/applications/github/app.py +19 -56
- universal_mcp/applications/gong/app.py +88 -248
- universal_mcp/applications/google_calendar/app.py +16 -68
- universal_mcp/applications/google_docs/app.py +88 -188
- universal_mcp/applications/google_drive/app.py +141 -463
- universal_mcp/applications/google_gemini/app.py +12 -64
- universal_mcp/applications/google_mail/app.py +28 -157
- universal_mcp/applications/google_searchconsole/app.py +15 -48
- universal_mcp/applications/google_sheet/app.py +103 -580
- universal_mcp/applications/google_sheet/helper.py +10 -37
- universal_mcp/applications/hashnode/app.py +57 -269
- universal_mcp/applications/heygen/app.py +44 -122
- universal_mcp/applications/http_tools/app.py +10 -32
- universal_mcp/applications/hubspot/api_segments/crm_api.py +460 -1573
- universal_mcp/applications/hubspot/api_segments/marketing_api.py +74 -262
- universal_mcp/applications/hubspot/app.py +23 -87
- universal_mcp/applications/jira/app.py +2071 -7986
- universal_mcp/applications/klaviyo/app.py +494 -1376
- universal_mcp/applications/linkedin/README.md +9 -2
- universal_mcp/applications/linkedin/app.py +392 -212
- universal_mcp/applications/mailchimp/app.py +450 -1605
- universal_mcp/applications/markitdown/app.py +8 -20
- universal_mcp/applications/miro/app.py +217 -699
- universal_mcp/applications/ms_teams/app.py +64 -186
- universal_mcp/applications/neon/app.py +86 -192
- universal_mcp/applications/notion/app.py +21 -36
- universal_mcp/applications/onedrive/app.py +16 -38
- universal_mcp/applications/openai/app.py +42 -165
- universal_mcp/applications/outlook/app.py +24 -84
- universal_mcp/applications/perplexity/app.py +4 -19
- universal_mcp/applications/pipedrive/app.py +832 -3142
- universal_mcp/applications/posthog/app.py +163 -432
- universal_mcp/applications/reddit/app.py +40 -139
- universal_mcp/applications/resend/app.py +41 -107
- universal_mcp/applications/retell/app.py +14 -41
- universal_mcp/applications/rocketlane/app.py +221 -934
- universal_mcp/applications/scraper/README.md +7 -4
- universal_mcp/applications/scraper/app.py +216 -102
- universal_mcp/applications/semanticscholar/app.py +22 -64
- universal_mcp/applications/semrush/app.py +43 -77
- universal_mcp/applications/sendgrid/app.py +512 -1262
- universal_mcp/applications/sentry/app.py +271 -906
- universal_mcp/applications/serpapi/app.py +40 -143
- universal_mcp/applications/sharepoint/app.py +17 -39
- universal_mcp/applications/shopify/app.py +1551 -4287
- universal_mcp/applications/shortcut/app.py +155 -417
- universal_mcp/applications/slack/app.py +50 -101
- universal_mcp/applications/spotify/app.py +126 -325
- universal_mcp/applications/supabase/app.py +104 -213
- universal_mcp/applications/tavily/app.py +1 -1
- universal_mcp/applications/trello/app.py +693 -2656
- universal_mcp/applications/twilio/app.py +14 -50
- 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 +70 -283
- universal_mcp/applications/wrike/app.py +45 -118
- universal_mcp/applications/yahoo_finance/app.py +19 -65
- universal_mcp/applications/youtube/app.py +75 -261
- universal_mcp/applications/zenquotes/app.py +2 -2
- {universal_mcp_applications-0.1.30rc2.dist-info → universal_mcp_applications-0.1.36rc2.dist-info}/METADATA +2 -2
- {universal_mcp_applications-0.1.30rc2.dist-info → universal_mcp_applications-0.1.36rc2.dist-info}/RECORD +105 -105
- {universal_mcp_applications-0.1.30rc2.dist-info → universal_mcp_applications-0.1.36rc2.dist-info}/WHEEL +0 -0
- {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
|
-
| `
|
|
13
|
-
| `
|
|
14
|
-
| `
|
|
15
|
-
| `
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
91
|
-
|
|
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
|
-
|
|
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
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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()
|