universal-mcp-applications 0.1.1__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 (268) hide show
  1. universal_mcp/applications/ahrefs/README.md +51 -0
  2. universal_mcp/applications/ahrefs/__init__.py +1 -0
  3. universal_mcp/applications/ahrefs/app.py +2291 -0
  4. universal_mcp/applications/airtable/README.md +22 -0
  5. universal_mcp/applications/airtable/__init__.py +1 -0
  6. universal_mcp/applications/airtable/app.py +479 -0
  7. universal_mcp/applications/apollo/README.md +44 -0
  8. universal_mcp/applications/apollo/__init__.py +1 -0
  9. universal_mcp/applications/apollo/app.py +1847 -0
  10. universal_mcp/applications/asana/README.md +199 -0
  11. universal_mcp/applications/asana/__init__.py +1 -0
  12. universal_mcp/applications/asana/app.py +9509 -0
  13. universal_mcp/applications/aws-s3/README.md +0 -0
  14. universal_mcp/applications/aws-s3/__init__.py +1 -0
  15. universal_mcp/applications/aws-s3/app.py +552 -0
  16. universal_mcp/applications/bill/README.md +0 -0
  17. universal_mcp/applications/bill/__init__.py +1 -0
  18. universal_mcp/applications/bill/app.py +8705 -0
  19. universal_mcp/applications/box/README.md +307 -0
  20. universal_mcp/applications/box/__init__.py +1 -0
  21. universal_mcp/applications/box/app.py +15987 -0
  22. universal_mcp/applications/braze/README.md +106 -0
  23. universal_mcp/applications/braze/__init__.py +1 -0
  24. universal_mcp/applications/braze/app.py +4754 -0
  25. universal_mcp/applications/cal-com-v2/README.md +150 -0
  26. universal_mcp/applications/cal-com-v2/__init__.py +1 -0
  27. universal_mcp/applications/cal-com-v2/app.py +5541 -0
  28. universal_mcp/applications/calendly/README.md +53 -0
  29. universal_mcp/applications/calendly/__init__.py +1 -0
  30. universal_mcp/applications/calendly/app.py +1436 -0
  31. universal_mcp/applications/canva/README.md +43 -0
  32. universal_mcp/applications/canva/__init__.py +1 -0
  33. universal_mcp/applications/canva/app.py +941 -0
  34. universal_mcp/applications/clickup/README.md +135 -0
  35. universal_mcp/applications/clickup/__init__.py +1 -0
  36. universal_mcp/applications/clickup/app.py +5009 -0
  37. universal_mcp/applications/coda/README.md +108 -0
  38. universal_mcp/applications/coda/__init__.py +1 -0
  39. universal_mcp/applications/coda/app.py +3671 -0
  40. universal_mcp/applications/confluence/README.md +198 -0
  41. universal_mcp/applications/confluence/__init__.py +1 -0
  42. universal_mcp/applications/confluence/app.py +6273 -0
  43. universal_mcp/applications/contentful/README.md +17 -0
  44. universal_mcp/applications/contentful/__init__.py +1 -0
  45. universal_mcp/applications/contentful/app.py +364 -0
  46. universal_mcp/applications/crustdata/README.md +25 -0
  47. universal_mcp/applications/crustdata/__init__.py +1 -0
  48. universal_mcp/applications/crustdata/app.py +586 -0
  49. universal_mcp/applications/dialpad/README.md +202 -0
  50. universal_mcp/applications/dialpad/__init__.py +1 -0
  51. universal_mcp/applications/dialpad/app.py +5949 -0
  52. universal_mcp/applications/digitalocean/README.md +463 -0
  53. universal_mcp/applications/digitalocean/__init__.py +1 -0
  54. universal_mcp/applications/digitalocean/app.py +20835 -0
  55. universal_mcp/applications/domain-checker/README.md +13 -0
  56. universal_mcp/applications/domain-checker/__init__.py +1 -0
  57. universal_mcp/applications/domain-checker/app.py +265 -0
  58. universal_mcp/applications/e2b/README.md +12 -0
  59. universal_mcp/applications/e2b/__init__.py +1 -0
  60. universal_mcp/applications/e2b/app.py +187 -0
  61. universal_mcp/applications/elevenlabs/README.md +88 -0
  62. universal_mcp/applications/elevenlabs/__init__.py +1 -0
  63. universal_mcp/applications/elevenlabs/app.py +3235 -0
  64. universal_mcp/applications/exa/README.md +15 -0
  65. universal_mcp/applications/exa/__init__.py +1 -0
  66. universal_mcp/applications/exa/app.py +221 -0
  67. universal_mcp/applications/falai/README.md +17 -0
  68. universal_mcp/applications/falai/__init__.py +1 -0
  69. universal_mcp/applications/falai/app.py +331 -0
  70. universal_mcp/applications/figma/README.md +49 -0
  71. universal_mcp/applications/figma/__init__.py +1 -0
  72. universal_mcp/applications/figma/app.py +1090 -0
  73. universal_mcp/applications/firecrawl/README.md +20 -0
  74. universal_mcp/applications/firecrawl/__init__.py +1 -0
  75. universal_mcp/applications/firecrawl/app.py +514 -0
  76. universal_mcp/applications/fireflies/README.md +25 -0
  77. universal_mcp/applications/fireflies/__init__.py +1 -0
  78. universal_mcp/applications/fireflies/app.py +506 -0
  79. universal_mcp/applications/fpl/README.md +23 -0
  80. universal_mcp/applications/fpl/__init__.py +1 -0
  81. universal_mcp/applications/fpl/app.py +1327 -0
  82. universal_mcp/applications/fpl/utils/api.py +142 -0
  83. universal_mcp/applications/fpl/utils/fixtures.py +629 -0
  84. universal_mcp/applications/fpl/utils/helper.py +982 -0
  85. universal_mcp/applications/fpl/utils/league_utils.py +546 -0
  86. universal_mcp/applications/fpl/utils/position_utils.py +68 -0
  87. universal_mcp/applications/ghost-content/README.md +25 -0
  88. universal_mcp/applications/ghost-content/__init__.py +1 -0
  89. universal_mcp/applications/ghost-content/app.py +654 -0
  90. universal_mcp/applications/github/README.md +1049 -0
  91. universal_mcp/applications/github/__init__.py +1 -0
  92. universal_mcp/applications/github/app.py +50600 -0
  93. universal_mcp/applications/gong/README.md +63 -0
  94. universal_mcp/applications/gong/__init__.py +1 -0
  95. universal_mcp/applications/gong/app.py +2297 -0
  96. universal_mcp/applications/google-ads/README.md +0 -0
  97. universal_mcp/applications/google-ads/__init__.py +1 -0
  98. universal_mcp/applications/google-ads/app.py +23 -0
  99. universal_mcp/applications/google-calendar/README.md +21 -0
  100. universal_mcp/applications/google-calendar/__init__.py +1 -0
  101. universal_mcp/applications/google-calendar/app.py +574 -0
  102. universal_mcp/applications/google-docs/README.md +25 -0
  103. universal_mcp/applications/google-docs/__init__.py +1 -0
  104. universal_mcp/applications/google-docs/app.py +760 -0
  105. universal_mcp/applications/google-drive/README.md +68 -0
  106. universal_mcp/applications/google-drive/__init__.py +1 -0
  107. universal_mcp/applications/google-drive/app.py +4936 -0
  108. universal_mcp/applications/google-gemini/README.md +25 -0
  109. universal_mcp/applications/google-gemini/__init__.py +1 -0
  110. universal_mcp/applications/google-gemini/app.py +663 -0
  111. universal_mcp/applications/google-mail/README.md +31 -0
  112. universal_mcp/applications/google-mail/__init__.py +1 -0
  113. universal_mcp/applications/google-mail/app.py +1354 -0
  114. universal_mcp/applications/google-searchconsole/README.md +21 -0
  115. universal_mcp/applications/google-searchconsole/__init__.py +1 -0
  116. universal_mcp/applications/google-searchconsole/app.py +320 -0
  117. universal_mcp/applications/google-sheet/README.md +36 -0
  118. universal_mcp/applications/google-sheet/__init__.py +1 -0
  119. universal_mcp/applications/google-sheet/app.py +1941 -0
  120. universal_mcp/applications/hashnode/README.md +20 -0
  121. universal_mcp/applications/hashnode/__init__.py +1 -0
  122. universal_mcp/applications/hashnode/app.py +455 -0
  123. universal_mcp/applications/heygen/README.md +44 -0
  124. universal_mcp/applications/heygen/__init__.py +1 -0
  125. universal_mcp/applications/heygen/app.py +961 -0
  126. universal_mcp/applications/http-tools/README.md +16 -0
  127. universal_mcp/applications/http-tools/__init__.py +1 -0
  128. universal_mcp/applications/http-tools/app.py +153 -0
  129. universal_mcp/applications/hubspot/README.md +239 -0
  130. universal_mcp/applications/hubspot/__init__.py +1 -0
  131. universal_mcp/applications/hubspot/app.py +416 -0
  132. universal_mcp/applications/jira/README.md +600 -0
  133. universal_mcp/applications/jira/__init__.py +1 -0
  134. universal_mcp/applications/jira/app.py +28804 -0
  135. universal_mcp/applications/klaviyo/README.md +313 -0
  136. universal_mcp/applications/klaviyo/__init__.py +1 -0
  137. universal_mcp/applications/klaviyo/app.py +11236 -0
  138. universal_mcp/applications/linkedin/README.md +15 -0
  139. universal_mcp/applications/linkedin/__init__.py +1 -0
  140. universal_mcp/applications/linkedin/app.py +243 -0
  141. universal_mcp/applications/mailchimp/README.md +281 -0
  142. universal_mcp/applications/mailchimp/__init__.py +1 -0
  143. universal_mcp/applications/mailchimp/app.py +10937 -0
  144. universal_mcp/applications/markitdown/README.md +12 -0
  145. universal_mcp/applications/markitdown/__init__.py +1 -0
  146. universal_mcp/applications/markitdown/app.py +63 -0
  147. universal_mcp/applications/miro/README.md +151 -0
  148. universal_mcp/applications/miro/__init__.py +1 -0
  149. universal_mcp/applications/miro/app.py +5429 -0
  150. universal_mcp/applications/ms-teams/README.md +42 -0
  151. universal_mcp/applications/ms-teams/__init__.py +1 -0
  152. universal_mcp/applications/ms-teams/app.py +1823 -0
  153. universal_mcp/applications/neon/README.md +74 -0
  154. universal_mcp/applications/neon/__init__.py +1 -0
  155. universal_mcp/applications/neon/app.py +2018 -0
  156. universal_mcp/applications/notion/README.md +30 -0
  157. universal_mcp/applications/notion/__init__.py +1 -0
  158. universal_mcp/applications/notion/app.py +527 -0
  159. universal_mcp/applications/openai/README.md +22 -0
  160. universal_mcp/applications/openai/__init__.py +1 -0
  161. universal_mcp/applications/openai/app.py +759 -0
  162. universal_mcp/applications/outlook/README.md +20 -0
  163. universal_mcp/applications/outlook/__init__.py +1 -0
  164. universal_mcp/applications/outlook/app.py +444 -0
  165. universal_mcp/applications/perplexity/README.md +12 -0
  166. universal_mcp/applications/perplexity/__init__.py +1 -0
  167. universal_mcp/applications/perplexity/app.py +65 -0
  168. universal_mcp/applications/pipedrive/README.md +284 -0
  169. universal_mcp/applications/pipedrive/__init__.py +1 -0
  170. universal_mcp/applications/pipedrive/app.py +12924 -0
  171. universal_mcp/applications/posthog/README.md +132 -0
  172. universal_mcp/applications/posthog/__init__.py +1 -0
  173. universal_mcp/applications/posthog/app.py +7125 -0
  174. universal_mcp/applications/reddit/README.md +135 -0
  175. universal_mcp/applications/reddit/__init__.py +1 -0
  176. universal_mcp/applications/reddit/app.py +4652 -0
  177. universal_mcp/applications/replicate/README.md +18 -0
  178. universal_mcp/applications/replicate/__init__.py +1 -0
  179. universal_mcp/applications/replicate/app.py +495 -0
  180. universal_mcp/applications/resend/README.md +40 -0
  181. universal_mcp/applications/resend/__init__.py +1 -0
  182. universal_mcp/applications/resend/app.py +881 -0
  183. universal_mcp/applications/retell/README.md +21 -0
  184. universal_mcp/applications/retell/__init__.py +1 -0
  185. universal_mcp/applications/retell/app.py +333 -0
  186. universal_mcp/applications/rocketlane/README.md +70 -0
  187. universal_mcp/applications/rocketlane/__init__.py +1 -0
  188. universal_mcp/applications/rocketlane/app.py +4346 -0
  189. universal_mcp/applications/semanticscholar/README.md +25 -0
  190. universal_mcp/applications/semanticscholar/__init__.py +1 -0
  191. universal_mcp/applications/semanticscholar/app.py +482 -0
  192. universal_mcp/applications/semrush/README.md +44 -0
  193. universal_mcp/applications/semrush/__init__.py +1 -0
  194. universal_mcp/applications/semrush/app.py +2081 -0
  195. universal_mcp/applications/sendgrid/README.md +362 -0
  196. universal_mcp/applications/sendgrid/__init__.py +1 -0
  197. universal_mcp/applications/sendgrid/app.py +9752 -0
  198. universal_mcp/applications/sentry/README.md +186 -0
  199. universal_mcp/applications/sentry/__init__.py +1 -0
  200. universal_mcp/applications/sentry/app.py +7471 -0
  201. universal_mcp/applications/serpapi/README.md +14 -0
  202. universal_mcp/applications/serpapi/__init__.py +1 -0
  203. universal_mcp/applications/serpapi/app.py +293 -0
  204. universal_mcp/applications/sharepoint/README.md +0 -0
  205. universal_mcp/applications/sharepoint/__init__.py +1 -0
  206. universal_mcp/applications/sharepoint/app.py +215 -0
  207. universal_mcp/applications/shopify/README.md +321 -0
  208. universal_mcp/applications/shopify/__init__.py +1 -0
  209. universal_mcp/applications/shopify/app.py +15392 -0
  210. universal_mcp/applications/shortcut/README.md +128 -0
  211. universal_mcp/applications/shortcut/__init__.py +1 -0
  212. universal_mcp/applications/shortcut/app.py +4478 -0
  213. universal_mcp/applications/slack/README.md +0 -0
  214. universal_mcp/applications/slack/__init__.py +1 -0
  215. universal_mcp/applications/slack/app.py +570 -0
  216. universal_mcp/applications/spotify/README.md +91 -0
  217. universal_mcp/applications/spotify/__init__.py +1 -0
  218. universal_mcp/applications/spotify/app.py +2526 -0
  219. universal_mcp/applications/supabase/README.md +87 -0
  220. universal_mcp/applications/supabase/__init__.py +1 -0
  221. universal_mcp/applications/supabase/app.py +2970 -0
  222. universal_mcp/applications/tavily/README.md +12 -0
  223. universal_mcp/applications/tavily/__init__.py +1 -0
  224. universal_mcp/applications/tavily/app.py +51 -0
  225. universal_mcp/applications/trello/README.md +266 -0
  226. universal_mcp/applications/trello/__init__.py +1 -0
  227. universal_mcp/applications/trello/app.py +10875 -0
  228. universal_mcp/applications/twillo/README.md +0 -0
  229. universal_mcp/applications/twillo/__init__.py +1 -0
  230. universal_mcp/applications/twillo/app.py +269 -0
  231. universal_mcp/applications/twitter/README.md +100 -0
  232. universal_mcp/applications/twitter/__init__.py +1 -0
  233. universal_mcp/applications/twitter/api_segments/__init__.py +0 -0
  234. universal_mcp/applications/twitter/api_segments/api_segment_base.py +51 -0
  235. universal_mcp/applications/twitter/api_segments/compliance_api.py +122 -0
  236. universal_mcp/applications/twitter/api_segments/dm_conversations_api.py +255 -0
  237. universal_mcp/applications/twitter/api_segments/dm_events_api.py +140 -0
  238. universal_mcp/applications/twitter/api_segments/likes_api.py +159 -0
  239. universal_mcp/applications/twitter/api_segments/lists_api.py +395 -0
  240. universal_mcp/applications/twitter/api_segments/openapi_json_api.py +34 -0
  241. universal_mcp/applications/twitter/api_segments/spaces_api.py +309 -0
  242. universal_mcp/applications/twitter/api_segments/trends_api.py +40 -0
  243. universal_mcp/applications/twitter/api_segments/tweets_api.py +1403 -0
  244. universal_mcp/applications/twitter/api_segments/usage_api.py +40 -0
  245. universal_mcp/applications/twitter/api_segments/users_api.py +1498 -0
  246. universal_mcp/applications/twitter/app.py +46 -0
  247. universal_mcp/applications/unipile/README.md +28 -0
  248. universal_mcp/applications/unipile/__init__.py +1 -0
  249. universal_mcp/applications/unipile/app.py +829 -0
  250. universal_mcp/applications/whatsapp/README.md +23 -0
  251. universal_mcp/applications/whatsapp/__init__.py +1 -0
  252. universal_mcp/applications/whatsapp/app.py +595 -0
  253. universal_mcp/applications/whatsapp-business/README.md +34 -0
  254. universal_mcp/applications/whatsapp-business/__init__.py +1 -0
  255. universal_mcp/applications/whatsapp-business/app.py +1065 -0
  256. universal_mcp/applications/wrike/README.md +46 -0
  257. universal_mcp/applications/wrike/__init__.py +1 -0
  258. universal_mcp/applications/wrike/app.py +1583 -0
  259. universal_mcp/applications/youtube/README.md +57 -0
  260. universal_mcp/applications/youtube/__init__.py +1 -0
  261. universal_mcp/applications/youtube/app.py +1696 -0
  262. universal_mcp/applications/zenquotes/README.md +12 -0
  263. universal_mcp/applications/zenquotes/__init__.py +1 -0
  264. universal_mcp/applications/zenquotes/app.py +31 -0
  265. universal_mcp_applications-0.1.1.dist-info/METADATA +172 -0
  266. universal_mcp_applications-0.1.1.dist-info/RECORD +268 -0
  267. universal_mcp_applications-0.1.1.dist-info/WHEEL +4 -0
  268. universal_mcp_applications-0.1.1.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,17 @@
1
+ # ContentfulApp MCP Server
2
+
3
+ An MCP Server for the ContentfulApp API.
4
+
5
+ ## 🛠️ Tool List
6
+
7
+ This is automatically generated from OpenAPI schema for the ContentfulApp API.
8
+
9
+
10
+ | Tool | Description |
11
+ |------|-------------|
12
+ | `get_entry` | Fetches a single entry of a specified content type by its ID. |
13
+ | `get_entries_collection` | Fetches a collection of entries of a specified content type. |
14
+ | `get_asset` | Fetches a single asset by its ID. |
15
+ | `get_assets_collection` | Fetches a collection of assets. |
16
+ | `execute_graphql_query` | Executes an arbitrary GraphQL query. |
17
+ | `execute_graphql_mutation` | Executes an arbitrary GraphQL mutation. |
@@ -0,0 +1 @@
1
+ from .app import ContentfulApp
@@ -0,0 +1,364 @@
1
+ from collections.abc import Callable
2
+ from typing import Any
3
+
4
+ from loguru import logger
5
+ from universal_mcp.applications import GraphQLApplication
6
+ from universal_mcp.exceptions import NotAuthorizedError
7
+ from universal_mcp.integrations import Integration
8
+
9
+
10
+ class ContentfulApp(GraphQLApplication):
11
+ def __init__(
12
+ self,
13
+ integration: Integration | None = None,
14
+ **kwargs: Any,
15
+ ) -> None:
16
+ self.space_id: str | None = None
17
+ self.environment_id: str = "master" # Default Contentful environment
18
+ self._access_token: str | None = None
19
+ self._is_eu_customer: bool = False # Default data center
20
+ self._credentials_loaded: bool = False # Flag for lazy loading
21
+ default_base_url = "https://graphql.contentful.com"
22
+
23
+ super().__init__(
24
+ name="contentful",
25
+ base_url=default_base_url,
26
+ integration=integration,
27
+ **kwargs,
28
+ )
29
+
30
+ def _load_credentials_and_construct_url(self) -> bool:
31
+ """
32
+ Loads credentials from the integration, constructs the precise API URL,
33
+ and prepares the instance for API calls. Runs only once.
34
+
35
+ Returns:
36
+ bool: True if setup was successful, False otherwise.
37
+ """
38
+ if self._credentials_loaded:
39
+ return True
40
+
41
+ logger.debug("Attempting to load Contentful credentials and construct URL...")
42
+
43
+ if not self.integration:
44
+ logger.error(
45
+ "Contentful integration not configured. Cannot load credentials or construct URL."
46
+ )
47
+ # Mark as 'loaded' to prevent retries, even though it failed.
48
+ self._credentials_loaded = True
49
+ return False
50
+
51
+ try:
52
+ credentials = self.integration.get_credentials()
53
+ except NotAuthorizedError as e:
54
+ logger.error(
55
+ f"Authorization required or credentials unavailable for Contentful: {e.message}"
56
+ )
57
+ self._credentials_loaded = True # Prevent retries
58
+ return False
59
+ except Exception as e:
60
+ logger.error(
61
+ f"Failed to get credentials from integration: {e}", exc_info=True
62
+ )
63
+ self._credentials_loaded = True # Prevent retries
64
+ return False
65
+
66
+ # --- Extract Credentials ---
67
+ self.space_id = credentials.get("space_id")
68
+ # Prefer access_token, fallback to api_key for naming flexibility
69
+ self._access_token = credentials.get("access_token") or credentials.get(
70
+ "api_key"
71
+ )
72
+ self.environment_id = credentials.get(
73
+ "environment_id", "master"
74
+ ) # Use default if not specified
75
+ self._is_eu_customer = credentials.get(
76
+ "is_eu_customer", False
77
+ ) # Use default if not specified
78
+
79
+ # --- Validate Required Credentials ---
80
+ missing_creds = []
81
+ if not self.space_id:
82
+ missing_creds.append("'space_id'")
83
+ if not self._access_token:
84
+ missing_creds.append("'access_token' or 'api_key'")
85
+
86
+ if missing_creds:
87
+ logger.error(
88
+ f"Missing required Contentful credentials in integration: {', '.join(missing_creds)}. "
89
+ "API calls will fail."
90
+ )
91
+ self._credentials_loaded = True # Prevent retries
92
+ return False
93
+
94
+ # --- Construct Final Base URL ---
95
+ contentful_api_domain = (
96
+ "graphql.eu.contentful.com"
97
+ if self._is_eu_customer
98
+ else "graphql.contentful.com"
99
+ )
100
+ # Update self.base_url which was initially set to the default by super().__init__
101
+ self.base_url = f"https://{contentful_api_domain}/content/v1/spaces/{self.space_id}/environments/{self.environment_id}"
102
+
103
+ # --- Force GraphQL Client Re-initialization ---
104
+ # Reset the internal client instance of the base class.
105
+ # The next time self.client property is accessed (e.g., in self.query),
106
+ # it will be recreated using the new self.base_url and fresh headers
107
+ # obtained via self._get_headers() (which will now find self._access_token).
108
+ self._client = None
109
+
110
+ logger.info(
111
+ f"Contentful credentials loaded and URL constructed successfully. "
112
+ f"Space: '{self.space_id}', Environment: '{self.environment_id}'. "
113
+ f"Base URL: {self.base_url}"
114
+ )
115
+ self._credentials_loaded = True
116
+ return True
117
+
118
+ # We rely on the base GraphQLApplication._get_headers() which looks for
119
+ # 'access_token' or 'api_key' and creates the Bearer token header.
120
+ # No override needed here as long as _load_credentials_and_construct_url
121
+ # correctly populates self._access_token before the client is used.
122
+
123
+ @staticmethod
124
+ def _to_camel_case(s: str) -> str:
125
+ """Converts a string to camelCase based on Contentful's typical ID to GraphQL name conversion."""
126
+ s = s.replace("-", " ").replace("_", " ")
127
+ parts = s.split()
128
+ if not parts:
129
+ return ""
130
+ if len(parts) == 1 and parts[0] == s and s:
131
+ return s[0].lower() + s[1:] if len(s) > 1 else s.lower()
132
+ return parts[0].lower() + "".join(word.capitalize() for word in parts[1:])
133
+
134
+ @staticmethod
135
+ def _to_pascal_case(s: str) -> str:
136
+ """Converts a string to PascalCase based on Contentful's typical ID to GraphQL name conversion."""
137
+ s = s.replace("-", " ").replace("_", " ")
138
+ parts = s.split()
139
+ if not parts:
140
+ return ""
141
+ if len(parts) == 1 and parts[0] == s and s:
142
+ return s[0].upper() + s[1:] if len(s) > 1 else s.upper()
143
+ return "".join(word.capitalize() for word in parts)
144
+
145
+ def _ensure_loaded(self) -> bool:
146
+ """Internal helper to trigger lazy loading and check status."""
147
+ if not self._credentials_loaded:
148
+ return self._load_credentials_and_construct_url()
149
+ # If already marked as loaded, check if essential parts are actually set (robustness check)
150
+ return bool(self.base_url and self.space_id and self._access_token)
151
+
152
+ # --- Tool Methods ---
153
+
154
+ def get_entry(
155
+ self,
156
+ content_type_id: str,
157
+ entry_id: str,
158
+ fields_to_select: str,
159
+ locale: str | None = None,
160
+ preview: bool = False,
161
+ ) -> dict[str, Any]:
162
+ """
163
+ Fetches a single entry of a specified content type by its ID.
164
+ (See original docstring for details)
165
+ """
166
+ if not self._ensure_loaded():
167
+ return {
168
+ "error": "Failed to initialize ContentfulApp. Check credentials and configuration."
169
+ }
170
+
171
+ query_field = self._to_camel_case(content_type_id)
172
+ logger.debug(
173
+ f"Fetching entry for content_type_id='{content_type_id}' (query field='{query_field}'), entry_id='{entry_id}'"
174
+ )
175
+ query_gql = f"""
176
+ query GetEntryById($id: String!, $locale: String, $preview: Boolean) {{
177
+ {query_field}(id: $id, locale: $locale, preview: $preview) {{
178
+ {fields_to_select}
179
+ }}
180
+ }}
181
+ """
182
+ variables: dict[str, Any] = {"id": entry_id, "preview": preview}
183
+ if locale:
184
+ variables["locale"] = locale
185
+
186
+ # Call the base class query method which uses the configured client
187
+ try:
188
+ return self.query(query_gql, variables=variables)
189
+ except Exception as e:
190
+ logger.error(f"Error executing get_entry query: {e}", exc_info=True)
191
+ return {"error": f"Failed to get entry: {e}"}
192
+
193
+ def get_entries_collection(
194
+ self,
195
+ content_type_id: str,
196
+ fields_to_select_for_item: str,
197
+ limit: int | None = None,
198
+ skip: int | None = None,
199
+ where: dict[str, Any] | None = None,
200
+ order: list[str] | None = None,
201
+ locale: str | None = None,
202
+ preview: bool = False,
203
+ ) -> dict[str, Any]:
204
+ """
205
+ Fetches a collection of entries of a specified content type.
206
+ (See original docstring for details)
207
+ """
208
+ if not self._ensure_loaded():
209
+ return {
210
+ "error": "Failed to initialize ContentfulApp. Check credentials and configuration."
211
+ }
212
+
213
+ collection_field = self._to_camel_case(content_type_id) + "Collection"
214
+ filter_type = self._to_pascal_case(content_type_id) + "Filter"
215
+ order_enum_type = self._to_pascal_case(content_type_id) + "Order"
216
+ logger.debug(
217
+ f"Fetching collection for content_type_id='{content_type_id}' "
218
+ f"(collection field='{collection_field}', filter type='{filter_type}', order enum='{order_enum_type}')"
219
+ )
220
+ query_gql = f"""
221
+ query GetEntries(
222
+ $limit: Int, $skip: Int, $where: {filter_type}, $order: [{order_enum_type}!], $locale: String, $preview: Boolean
223
+ ) {{
224
+ {collection_field}(
225
+ limit: $limit, skip: $skip, where: $where, order: $order, locale: $locale, preview: $preview
226
+ ) {{
227
+ total skip limit items {{ {fields_to_select_for_item} }}
228
+ }}
229
+ }}
230
+ """
231
+ variables: dict[str, Any] = {"preview": preview}
232
+ if limit is not None:
233
+ variables["limit"] = limit
234
+ if skip is not None:
235
+ variables["skip"] = skip
236
+ if where:
237
+ variables["where"] = where
238
+ if order:
239
+ variables["order"] = order
240
+ if locale:
241
+ variables["locale"] = locale
242
+
243
+ try:
244
+ return self.query(query_gql, variables=variables)
245
+ except Exception as e:
246
+ logger.error(
247
+ f"Error executing get_entries_collection query: {e}", exc_info=True
248
+ )
249
+ return {"error": f"Failed to get entries collection: {e}"}
250
+
251
+ def get_asset(
252
+ self,
253
+ asset_id: str,
254
+ fields_to_select: str = "sys { id } url title description fileName contentType size width height",
255
+ preview: bool = False,
256
+ ) -> dict[str, Any]:
257
+ """
258
+ Fetches a single asset by its ID.
259
+ (See original docstring for details)
260
+ """
261
+ if not self._ensure_loaded():
262
+ return {
263
+ "error": "Failed to initialize ContentfulApp. Check credentials and configuration."
264
+ }
265
+
266
+ logger.debug(f"Fetching asset_id='{asset_id}'")
267
+ query_gql = f"""
268
+ query GetAssetById($id: String!, $preview: Boolean) {{
269
+ asset(id: $id, preview: $preview) {{ {fields_to_select} }}
270
+ }}
271
+ """
272
+ variables: dict[str, Any] = {"id": asset_id, "preview": preview}
273
+
274
+ try:
275
+ return self.query(query_gql, variables=variables)
276
+ except Exception as e:
277
+ logger.error(f"Error executing get_asset query: {e}", exc_info=True)
278
+ return {"error": f"Failed to get asset: {e}"}
279
+
280
+ def get_assets_collection(
281
+ self,
282
+ fields_to_select_for_item: str = "sys { id } url title description fileName contentType size width height",
283
+ limit: int | None = None,
284
+ skip: int | None = None,
285
+ where: dict[str, Any] | None = None,
286
+ order: list[str] | None = None,
287
+ locale: str | None = None,
288
+ preview: bool = False,
289
+ ) -> dict[str, Any]:
290
+ """
291
+ Fetches a collection of assets.
292
+ (See original docstring for details)
293
+ """
294
+ if not self._ensure_loaded():
295
+ return {
296
+ "error": "Failed to initialize ContentfulApp. Check credentials and configuration."
297
+ }
298
+
299
+ logger.debug("Fetching assets collection")
300
+ query_gql = f"""
301
+ query GetAssets(
302
+ $limit: Int, $skip: Int, $where: AssetFilter, $order: [AssetOrder!], $locale: String, $preview: Boolean
303
+ ) {{
304
+ assetCollection(
305
+ limit: $limit, skip: $skip, where: $where, order: $order, locale: $locale, preview: $preview
306
+ ) {{
307
+ total skip limit items {{ {fields_to_select_for_item} }}
308
+ }}
309
+ }}
310
+ """
311
+ variables: dict[str, Any] = {"preview": preview}
312
+ if limit is not None:
313
+ variables["limit"] = limit
314
+ if skip is not None:
315
+ variables["skip"] = skip
316
+ if where:
317
+ variables["where"] = where
318
+ if order:
319
+ variables["order"] = order
320
+ if locale:
321
+ variables["locale"] = locale
322
+
323
+ try:
324
+ return self.query(query_gql, variables=variables)
325
+ except Exception as e:
326
+ logger.error(
327
+ f"Error executing get_assets_collection query: {e}", exc_info=True
328
+ )
329
+ return {"error": f"Failed to get assets collection: {e}"}
330
+
331
+ def execute_graphql_query(
332
+ self, query_string: str, variables: dict[str, Any] | None = None
333
+ ) -> dict[str, Any]:
334
+ """
335
+ Executes an arbitrary GraphQL query against the configured Contentful space/environment.
336
+
337
+ Args:
338
+ query_string: The GraphQL query string.
339
+ variables: Optional dictionary of variables for the query.
340
+
341
+ Returns:
342
+ The result of the query, or an error dictionary.
343
+ """
344
+ if not self._ensure_loaded():
345
+ return {
346
+ "error": "Failed to initialize ContentfulApp. Check credentials and configuration."
347
+ }
348
+
349
+ logger.debug(f"Executing custom GraphQL query with variables: {variables}")
350
+ try:
351
+ return self.query(query_string, variables=variables)
352
+ except Exception as e:
353
+ logger.error(f"Error executing custom GraphQL query: {e}", exc_info=True)
354
+ return {"error": f"Failed to execute custom query: {e}"}
355
+
356
+ def list_tools(self) -> list[Callable]:
357
+ """Returns a list of methods exposed as tools."""
358
+ return [
359
+ self.get_entry,
360
+ self.get_entries_collection,
361
+ self.get_asset,
362
+ self.get_assets_collection,
363
+ self.execute_graphql_query,
364
+ ]
@@ -0,0 +1,25 @@
1
+ # Crustdata MCP Server
2
+
3
+ An MCP Server for the Crustdata API.
4
+
5
+ ## 🛠️ Tool List
6
+
7
+ This is automatically generated from OpenAPI schema for the Crustdata API.
8
+
9
+
10
+ | Tool | Description |
11
+ |------|-------------|
12
+ | `screen_companies` | Screens companies based on specified metrics, filters, sorting, and pagination parameters, and returns the result as a JSON-compatible dictionary. |
13
+ | `get_headcount_timeseries` | Retrieve headcount timeseries data from the data lab endpoint using the provided filters, pagination, and sorting options. |
14
+ | `get_headcount_by_facet_timeseries` | Retrieves headcount timeseries data aggregated by specified facets using provided filters and sorting options. |
15
+ | `get_funding_milestone_timeseries` | Retrieves a time series of funding milestone data based on specified filters, pagination, and sorting options. |
16
+ | `get_decision_makers` | Retrieves decision makers based on specified filters and parameters. |
17
+ | `get_web_traffic` | Retrieves web traffic data based on provided filters, pagination, and sorting criteria. |
18
+ | `get_investor_portfolio` | Retrieves the investment portfolio information for a specified investor. |
19
+ | `get_job_listings` | Retrieves job listings data based on specified parameters. |
20
+ | `search_persons` | Submits a search request for persons associated with a given asynchronous job and returns the search results as a dictionary. |
21
+ | `search_companies` | Searches for companies using specified filters and pagination parameters. |
22
+ | `enrich_person` | Retrieves enriched person data from LinkedIn profile using the provided profile URL, enrichment mode, and requested fields. |
23
+ | `enrich_company` | Retrieves enriched company data using the provided company domain and enrichment mode. |
24
+ | `get_linked_in_posts` | Fetches LinkedIn posts for a specified company using its LinkedIn URL. |
25
+ | `search_linked_in_posts` | Searches LinkedIn posts using the provided keyword and filters, returning the search results as a dictionary. |
@@ -0,0 +1 @@
1
+ from .app import CrustdataApp