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.
- universal_mcp/applications/ahrefs/README.md +51 -0
- universal_mcp/applications/ahrefs/__init__.py +1 -0
- universal_mcp/applications/ahrefs/app.py +2291 -0
- universal_mcp/applications/airtable/README.md +22 -0
- universal_mcp/applications/airtable/__init__.py +1 -0
- universal_mcp/applications/airtable/app.py +479 -0
- universal_mcp/applications/apollo/README.md +44 -0
- universal_mcp/applications/apollo/__init__.py +1 -0
- universal_mcp/applications/apollo/app.py +1847 -0
- universal_mcp/applications/asana/README.md +199 -0
- universal_mcp/applications/asana/__init__.py +1 -0
- universal_mcp/applications/asana/app.py +9509 -0
- universal_mcp/applications/aws-s3/README.md +0 -0
- universal_mcp/applications/aws-s3/__init__.py +1 -0
- universal_mcp/applications/aws-s3/app.py +552 -0
- universal_mcp/applications/bill/README.md +0 -0
- universal_mcp/applications/bill/__init__.py +1 -0
- universal_mcp/applications/bill/app.py +8705 -0
- universal_mcp/applications/box/README.md +307 -0
- universal_mcp/applications/box/__init__.py +1 -0
- universal_mcp/applications/box/app.py +15987 -0
- universal_mcp/applications/braze/README.md +106 -0
- universal_mcp/applications/braze/__init__.py +1 -0
- universal_mcp/applications/braze/app.py +4754 -0
- universal_mcp/applications/cal-com-v2/README.md +150 -0
- universal_mcp/applications/cal-com-v2/__init__.py +1 -0
- universal_mcp/applications/cal-com-v2/app.py +5541 -0
- universal_mcp/applications/calendly/README.md +53 -0
- universal_mcp/applications/calendly/__init__.py +1 -0
- universal_mcp/applications/calendly/app.py +1436 -0
- universal_mcp/applications/canva/README.md +43 -0
- universal_mcp/applications/canva/__init__.py +1 -0
- universal_mcp/applications/canva/app.py +941 -0
- universal_mcp/applications/clickup/README.md +135 -0
- universal_mcp/applications/clickup/__init__.py +1 -0
- universal_mcp/applications/clickup/app.py +5009 -0
- universal_mcp/applications/coda/README.md +108 -0
- universal_mcp/applications/coda/__init__.py +1 -0
- universal_mcp/applications/coda/app.py +3671 -0
- universal_mcp/applications/confluence/README.md +198 -0
- universal_mcp/applications/confluence/__init__.py +1 -0
- universal_mcp/applications/confluence/app.py +6273 -0
- universal_mcp/applications/contentful/README.md +17 -0
- universal_mcp/applications/contentful/__init__.py +1 -0
- universal_mcp/applications/contentful/app.py +364 -0
- universal_mcp/applications/crustdata/README.md +25 -0
- universal_mcp/applications/crustdata/__init__.py +1 -0
- universal_mcp/applications/crustdata/app.py +586 -0
- universal_mcp/applications/dialpad/README.md +202 -0
- universal_mcp/applications/dialpad/__init__.py +1 -0
- universal_mcp/applications/dialpad/app.py +5949 -0
- universal_mcp/applications/digitalocean/README.md +463 -0
- universal_mcp/applications/digitalocean/__init__.py +1 -0
- universal_mcp/applications/digitalocean/app.py +20835 -0
- universal_mcp/applications/domain-checker/README.md +13 -0
- universal_mcp/applications/domain-checker/__init__.py +1 -0
- universal_mcp/applications/domain-checker/app.py +265 -0
- universal_mcp/applications/e2b/README.md +12 -0
- universal_mcp/applications/e2b/__init__.py +1 -0
- universal_mcp/applications/e2b/app.py +187 -0
- universal_mcp/applications/elevenlabs/README.md +88 -0
- universal_mcp/applications/elevenlabs/__init__.py +1 -0
- universal_mcp/applications/elevenlabs/app.py +3235 -0
- universal_mcp/applications/exa/README.md +15 -0
- universal_mcp/applications/exa/__init__.py +1 -0
- universal_mcp/applications/exa/app.py +221 -0
- universal_mcp/applications/falai/README.md +17 -0
- universal_mcp/applications/falai/__init__.py +1 -0
- universal_mcp/applications/falai/app.py +331 -0
- universal_mcp/applications/figma/README.md +49 -0
- universal_mcp/applications/figma/__init__.py +1 -0
- universal_mcp/applications/figma/app.py +1090 -0
- universal_mcp/applications/firecrawl/README.md +20 -0
- universal_mcp/applications/firecrawl/__init__.py +1 -0
- universal_mcp/applications/firecrawl/app.py +514 -0
- universal_mcp/applications/fireflies/README.md +25 -0
- universal_mcp/applications/fireflies/__init__.py +1 -0
- universal_mcp/applications/fireflies/app.py +506 -0
- universal_mcp/applications/fpl/README.md +23 -0
- universal_mcp/applications/fpl/__init__.py +1 -0
- universal_mcp/applications/fpl/app.py +1327 -0
- universal_mcp/applications/fpl/utils/api.py +142 -0
- universal_mcp/applications/fpl/utils/fixtures.py +629 -0
- universal_mcp/applications/fpl/utils/helper.py +982 -0
- universal_mcp/applications/fpl/utils/league_utils.py +546 -0
- universal_mcp/applications/fpl/utils/position_utils.py +68 -0
- universal_mcp/applications/ghost-content/README.md +25 -0
- universal_mcp/applications/ghost-content/__init__.py +1 -0
- universal_mcp/applications/ghost-content/app.py +654 -0
- universal_mcp/applications/github/README.md +1049 -0
- universal_mcp/applications/github/__init__.py +1 -0
- universal_mcp/applications/github/app.py +50600 -0
- universal_mcp/applications/gong/README.md +63 -0
- universal_mcp/applications/gong/__init__.py +1 -0
- universal_mcp/applications/gong/app.py +2297 -0
- universal_mcp/applications/google-ads/README.md +0 -0
- universal_mcp/applications/google-ads/__init__.py +1 -0
- universal_mcp/applications/google-ads/app.py +23 -0
- universal_mcp/applications/google-calendar/README.md +21 -0
- universal_mcp/applications/google-calendar/__init__.py +1 -0
- universal_mcp/applications/google-calendar/app.py +574 -0
- universal_mcp/applications/google-docs/README.md +25 -0
- universal_mcp/applications/google-docs/__init__.py +1 -0
- universal_mcp/applications/google-docs/app.py +760 -0
- universal_mcp/applications/google-drive/README.md +68 -0
- universal_mcp/applications/google-drive/__init__.py +1 -0
- universal_mcp/applications/google-drive/app.py +4936 -0
- universal_mcp/applications/google-gemini/README.md +25 -0
- universal_mcp/applications/google-gemini/__init__.py +1 -0
- universal_mcp/applications/google-gemini/app.py +663 -0
- universal_mcp/applications/google-mail/README.md +31 -0
- universal_mcp/applications/google-mail/__init__.py +1 -0
- universal_mcp/applications/google-mail/app.py +1354 -0
- universal_mcp/applications/google-searchconsole/README.md +21 -0
- universal_mcp/applications/google-searchconsole/__init__.py +1 -0
- universal_mcp/applications/google-searchconsole/app.py +320 -0
- universal_mcp/applications/google-sheet/README.md +36 -0
- universal_mcp/applications/google-sheet/__init__.py +1 -0
- universal_mcp/applications/google-sheet/app.py +1941 -0
- universal_mcp/applications/hashnode/README.md +20 -0
- universal_mcp/applications/hashnode/__init__.py +1 -0
- universal_mcp/applications/hashnode/app.py +455 -0
- universal_mcp/applications/heygen/README.md +44 -0
- universal_mcp/applications/heygen/__init__.py +1 -0
- universal_mcp/applications/heygen/app.py +961 -0
- universal_mcp/applications/http-tools/README.md +16 -0
- universal_mcp/applications/http-tools/__init__.py +1 -0
- universal_mcp/applications/http-tools/app.py +153 -0
- universal_mcp/applications/hubspot/README.md +239 -0
- universal_mcp/applications/hubspot/__init__.py +1 -0
- universal_mcp/applications/hubspot/app.py +416 -0
- universal_mcp/applications/jira/README.md +600 -0
- universal_mcp/applications/jira/__init__.py +1 -0
- universal_mcp/applications/jira/app.py +28804 -0
- universal_mcp/applications/klaviyo/README.md +313 -0
- universal_mcp/applications/klaviyo/__init__.py +1 -0
- universal_mcp/applications/klaviyo/app.py +11236 -0
- universal_mcp/applications/linkedin/README.md +15 -0
- universal_mcp/applications/linkedin/__init__.py +1 -0
- universal_mcp/applications/linkedin/app.py +243 -0
- universal_mcp/applications/mailchimp/README.md +281 -0
- universal_mcp/applications/mailchimp/__init__.py +1 -0
- universal_mcp/applications/mailchimp/app.py +10937 -0
- universal_mcp/applications/markitdown/README.md +12 -0
- universal_mcp/applications/markitdown/__init__.py +1 -0
- universal_mcp/applications/markitdown/app.py +63 -0
- universal_mcp/applications/miro/README.md +151 -0
- universal_mcp/applications/miro/__init__.py +1 -0
- universal_mcp/applications/miro/app.py +5429 -0
- universal_mcp/applications/ms-teams/README.md +42 -0
- universal_mcp/applications/ms-teams/__init__.py +1 -0
- universal_mcp/applications/ms-teams/app.py +1823 -0
- universal_mcp/applications/neon/README.md +74 -0
- universal_mcp/applications/neon/__init__.py +1 -0
- universal_mcp/applications/neon/app.py +2018 -0
- universal_mcp/applications/notion/README.md +30 -0
- universal_mcp/applications/notion/__init__.py +1 -0
- universal_mcp/applications/notion/app.py +527 -0
- universal_mcp/applications/openai/README.md +22 -0
- universal_mcp/applications/openai/__init__.py +1 -0
- universal_mcp/applications/openai/app.py +759 -0
- universal_mcp/applications/outlook/README.md +20 -0
- universal_mcp/applications/outlook/__init__.py +1 -0
- universal_mcp/applications/outlook/app.py +444 -0
- universal_mcp/applications/perplexity/README.md +12 -0
- universal_mcp/applications/perplexity/__init__.py +1 -0
- universal_mcp/applications/perplexity/app.py +65 -0
- universal_mcp/applications/pipedrive/README.md +284 -0
- universal_mcp/applications/pipedrive/__init__.py +1 -0
- universal_mcp/applications/pipedrive/app.py +12924 -0
- universal_mcp/applications/posthog/README.md +132 -0
- universal_mcp/applications/posthog/__init__.py +1 -0
- universal_mcp/applications/posthog/app.py +7125 -0
- universal_mcp/applications/reddit/README.md +135 -0
- universal_mcp/applications/reddit/__init__.py +1 -0
- universal_mcp/applications/reddit/app.py +4652 -0
- universal_mcp/applications/replicate/README.md +18 -0
- universal_mcp/applications/replicate/__init__.py +1 -0
- universal_mcp/applications/replicate/app.py +495 -0
- universal_mcp/applications/resend/README.md +40 -0
- universal_mcp/applications/resend/__init__.py +1 -0
- universal_mcp/applications/resend/app.py +881 -0
- universal_mcp/applications/retell/README.md +21 -0
- universal_mcp/applications/retell/__init__.py +1 -0
- universal_mcp/applications/retell/app.py +333 -0
- universal_mcp/applications/rocketlane/README.md +70 -0
- universal_mcp/applications/rocketlane/__init__.py +1 -0
- universal_mcp/applications/rocketlane/app.py +4346 -0
- universal_mcp/applications/semanticscholar/README.md +25 -0
- universal_mcp/applications/semanticscholar/__init__.py +1 -0
- universal_mcp/applications/semanticscholar/app.py +482 -0
- universal_mcp/applications/semrush/README.md +44 -0
- universal_mcp/applications/semrush/__init__.py +1 -0
- universal_mcp/applications/semrush/app.py +2081 -0
- universal_mcp/applications/sendgrid/README.md +362 -0
- universal_mcp/applications/sendgrid/__init__.py +1 -0
- universal_mcp/applications/sendgrid/app.py +9752 -0
- universal_mcp/applications/sentry/README.md +186 -0
- universal_mcp/applications/sentry/__init__.py +1 -0
- universal_mcp/applications/sentry/app.py +7471 -0
- universal_mcp/applications/serpapi/README.md +14 -0
- universal_mcp/applications/serpapi/__init__.py +1 -0
- universal_mcp/applications/serpapi/app.py +293 -0
- universal_mcp/applications/sharepoint/README.md +0 -0
- universal_mcp/applications/sharepoint/__init__.py +1 -0
- universal_mcp/applications/sharepoint/app.py +215 -0
- universal_mcp/applications/shopify/README.md +321 -0
- universal_mcp/applications/shopify/__init__.py +1 -0
- universal_mcp/applications/shopify/app.py +15392 -0
- universal_mcp/applications/shortcut/README.md +128 -0
- universal_mcp/applications/shortcut/__init__.py +1 -0
- universal_mcp/applications/shortcut/app.py +4478 -0
- universal_mcp/applications/slack/README.md +0 -0
- universal_mcp/applications/slack/__init__.py +1 -0
- universal_mcp/applications/slack/app.py +570 -0
- universal_mcp/applications/spotify/README.md +91 -0
- universal_mcp/applications/spotify/__init__.py +1 -0
- universal_mcp/applications/spotify/app.py +2526 -0
- universal_mcp/applications/supabase/README.md +87 -0
- universal_mcp/applications/supabase/__init__.py +1 -0
- universal_mcp/applications/supabase/app.py +2970 -0
- universal_mcp/applications/tavily/README.md +12 -0
- universal_mcp/applications/tavily/__init__.py +1 -0
- universal_mcp/applications/tavily/app.py +51 -0
- universal_mcp/applications/trello/README.md +266 -0
- universal_mcp/applications/trello/__init__.py +1 -0
- universal_mcp/applications/trello/app.py +10875 -0
- universal_mcp/applications/twillo/README.md +0 -0
- universal_mcp/applications/twillo/__init__.py +1 -0
- universal_mcp/applications/twillo/app.py +269 -0
- universal_mcp/applications/twitter/README.md +100 -0
- universal_mcp/applications/twitter/__init__.py +1 -0
- universal_mcp/applications/twitter/api_segments/__init__.py +0 -0
- universal_mcp/applications/twitter/api_segments/api_segment_base.py +51 -0
- universal_mcp/applications/twitter/api_segments/compliance_api.py +122 -0
- universal_mcp/applications/twitter/api_segments/dm_conversations_api.py +255 -0
- universal_mcp/applications/twitter/api_segments/dm_events_api.py +140 -0
- universal_mcp/applications/twitter/api_segments/likes_api.py +159 -0
- universal_mcp/applications/twitter/api_segments/lists_api.py +395 -0
- universal_mcp/applications/twitter/api_segments/openapi_json_api.py +34 -0
- universal_mcp/applications/twitter/api_segments/spaces_api.py +309 -0
- universal_mcp/applications/twitter/api_segments/trends_api.py +40 -0
- universal_mcp/applications/twitter/api_segments/tweets_api.py +1403 -0
- universal_mcp/applications/twitter/api_segments/usage_api.py +40 -0
- universal_mcp/applications/twitter/api_segments/users_api.py +1498 -0
- universal_mcp/applications/twitter/app.py +46 -0
- universal_mcp/applications/unipile/README.md +28 -0
- universal_mcp/applications/unipile/__init__.py +1 -0
- universal_mcp/applications/unipile/app.py +829 -0
- universal_mcp/applications/whatsapp/README.md +23 -0
- universal_mcp/applications/whatsapp/__init__.py +1 -0
- universal_mcp/applications/whatsapp/app.py +595 -0
- universal_mcp/applications/whatsapp-business/README.md +34 -0
- universal_mcp/applications/whatsapp-business/__init__.py +1 -0
- universal_mcp/applications/whatsapp-business/app.py +1065 -0
- universal_mcp/applications/wrike/README.md +46 -0
- universal_mcp/applications/wrike/__init__.py +1 -0
- universal_mcp/applications/wrike/app.py +1583 -0
- universal_mcp/applications/youtube/README.md +57 -0
- universal_mcp/applications/youtube/__init__.py +1 -0
- universal_mcp/applications/youtube/app.py +1696 -0
- universal_mcp/applications/zenquotes/README.md +12 -0
- universal_mcp/applications/zenquotes/__init__.py +1 -0
- universal_mcp/applications/zenquotes/app.py +31 -0
- universal_mcp_applications-0.1.1.dist-info/METADATA +172 -0
- universal_mcp_applications-0.1.1.dist-info/RECORD +268 -0
- universal_mcp_applications-0.1.1.dist-info/WHEEL +4 -0
- 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
|