universal-mcp-applications 0.1.17__py3-none-any.whl → 0.1.33__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.

Potentially problematic release.


This version of universal-mcp-applications might be problematic. Click here for more details.

Files changed (143) hide show
  1. universal_mcp/applications/BEST_PRACTICES.md +166 -0
  2. universal_mcp/applications/ahrefs/README.md +3 -3
  3. universal_mcp/applications/airtable/README.md +3 -3
  4. universal_mcp/applications/airtable/app.py +0 -1
  5. universal_mcp/applications/apollo/app.py +0 -1
  6. universal_mcp/applications/asana/README.md +3 -3
  7. universal_mcp/applications/aws_s3/README.md +29 -0
  8. universal_mcp/applications/aws_s3/app.py +40 -39
  9. universal_mcp/applications/bill/README.md +249 -0
  10. universal_mcp/applications/browser_use/README.md +1 -0
  11. universal_mcp/applications/browser_use/__init__.py +0 -0
  12. universal_mcp/applications/browser_use/app.py +71 -0
  13. universal_mcp/applications/calendly/README.md +45 -45
  14. universal_mcp/applications/calendly/app.py +125 -125
  15. universal_mcp/applications/canva/README.md +35 -35
  16. universal_mcp/applications/canva/app.py +95 -99
  17. universal_mcp/applications/clickup/README.md +4 -4
  18. universal_mcp/applications/confluence/app.py +0 -1
  19. universal_mcp/applications/contentful/README.md +1 -2
  20. universal_mcp/applications/contentful/app.py +4 -5
  21. universal_mcp/applications/crustdata/README.md +3 -3
  22. universal_mcp/applications/domain_checker/README.md +2 -2
  23. universal_mcp/applications/domain_checker/app.py +11 -15
  24. universal_mcp/applications/e2b/README.md +4 -4
  25. universal_mcp/applications/e2b/app.py +4 -4
  26. universal_mcp/applications/elevenlabs/README.md +3 -77
  27. universal_mcp/applications/elevenlabs/app.py +18 -15
  28. universal_mcp/applications/exa/README.md +7 -7
  29. universal_mcp/applications/exa/app.py +17 -17
  30. universal_mcp/applications/falai/README.md +13 -12
  31. universal_mcp/applications/falai/app.py +34 -35
  32. universal_mcp/applications/figma/README.md +3 -3
  33. universal_mcp/applications/file_system/README.md +13 -0
  34. universal_mcp/applications/file_system/app.py +9 -9
  35. universal_mcp/applications/firecrawl/README.md +9 -9
  36. universal_mcp/applications/firecrawl/app.py +46 -46
  37. universal_mcp/applications/fireflies/README.md +14 -14
  38. universal_mcp/applications/fireflies/app.py +164 -57
  39. universal_mcp/applications/fpl/README.md +12 -12
  40. universal_mcp/applications/fpl/app.py +54 -55
  41. universal_mcp/applications/ghost_content/app.py +0 -1
  42. universal_mcp/applications/github/README.md +10 -10
  43. universal_mcp/applications/github/app.py +50 -52
  44. universal_mcp/applications/google_calendar/README.md +10 -10
  45. universal_mcp/applications/google_calendar/app.py +50 -49
  46. universal_mcp/applications/google_docs/README.md +14 -14
  47. universal_mcp/applications/google_docs/app.py +307 -233
  48. universal_mcp/applications/google_drive/README.md +54 -57
  49. universal_mcp/applications/google_drive/app.py +270 -261
  50. universal_mcp/applications/google_gemini/README.md +3 -14
  51. universal_mcp/applications/google_gemini/app.py +15 -18
  52. universal_mcp/applications/google_mail/README.md +20 -20
  53. universal_mcp/applications/google_mail/app.py +110 -109
  54. universal_mcp/applications/google_searchconsole/README.md +10 -10
  55. universal_mcp/applications/google_searchconsole/app.py +37 -37
  56. universal_mcp/applications/google_sheet/README.md +25 -25
  57. universal_mcp/applications/google_sheet/app.py +270 -266
  58. universal_mcp/applications/hashnode/README.md +6 -3
  59. universal_mcp/applications/hashnode/app.py +174 -25
  60. universal_mcp/applications/http_tools/README.md +5 -5
  61. universal_mcp/applications/http_tools/app.py +10 -11
  62. universal_mcp/applications/hubspot/api_segments/__init__.py +0 -0
  63. universal_mcp/applications/hubspot/api_segments/api_segment_base.py +54 -0
  64. universal_mcp/applications/hubspot/api_segments/crm_api.py +7337 -0
  65. universal_mcp/applications/hubspot/api_segments/marketing_api.py +1467 -0
  66. universal_mcp/applications/hubspot/app.py +2 -15
  67. universal_mcp/applications/jira/app.py +0 -1
  68. universal_mcp/applications/klaviyo/README.md +0 -36
  69. universal_mcp/applications/linkedin/README.md +18 -4
  70. universal_mcp/applications/linkedin/app.py +763 -162
  71. universal_mcp/applications/mailchimp/README.md +3 -3
  72. universal_mcp/applications/markitdown/app.py +10 -5
  73. universal_mcp/applications/ms_teams/README.md +31 -31
  74. universal_mcp/applications/ms_teams/app.py +151 -151
  75. universal_mcp/applications/neon/README.md +3 -3
  76. universal_mcp/applications/onedrive/README.md +24 -0
  77. universal_mcp/applications/onedrive/__init__.py +1 -0
  78. universal_mcp/applications/onedrive/app.py +338 -0
  79. universal_mcp/applications/openai/README.md +18 -17
  80. universal_mcp/applications/openai/app.py +40 -39
  81. universal_mcp/applications/outlook/README.md +9 -9
  82. universal_mcp/applications/outlook/app.py +307 -225
  83. universal_mcp/applications/perplexity/README.md +4 -4
  84. universal_mcp/applications/perplexity/app.py +4 -4
  85. universal_mcp/applications/posthog/README.md +128 -127
  86. universal_mcp/applications/reddit/README.md +21 -124
  87. universal_mcp/applications/reddit/app.py +51 -68
  88. universal_mcp/applications/resend/README.md +29 -29
  89. universal_mcp/applications/resend/app.py +116 -117
  90. universal_mcp/applications/rocketlane/app.py +0 -1
  91. universal_mcp/applications/scraper/README.md +7 -4
  92. universal_mcp/applications/scraper/__init__.py +1 -1
  93. universal_mcp/applications/scraper/app.py +341 -103
  94. universal_mcp/applications/semrush/README.md +3 -0
  95. universal_mcp/applications/serpapi/README.md +3 -3
  96. universal_mcp/applications/serpapi/app.py +14 -14
  97. universal_mcp/applications/sharepoint/README.md +19 -0
  98. universal_mcp/applications/sharepoint/app.py +285 -173
  99. universal_mcp/applications/shopify/app.py +0 -1
  100. universal_mcp/applications/shortcut/README.md +3 -3
  101. universal_mcp/applications/slack/README.md +23 -0
  102. universal_mcp/applications/slack/app.py +79 -48
  103. universal_mcp/applications/spotify/README.md +3 -3
  104. universal_mcp/applications/supabase/README.md +3 -3
  105. universal_mcp/applications/tavily/README.md +4 -4
  106. universal_mcp/applications/tavily/app.py +4 -4
  107. universal_mcp/applications/twilio/README.md +15 -0
  108. universal_mcp/applications/twitter/README.md +92 -89
  109. universal_mcp/applications/twitter/api_segments/compliance_api.py +13 -15
  110. universal_mcp/applications/twitter/api_segments/dm_conversations_api.py +20 -20
  111. universal_mcp/applications/twitter/api_segments/dm_events_api.py +12 -12
  112. universal_mcp/applications/twitter/api_segments/likes_api.py +12 -12
  113. universal_mcp/applications/twitter/api_segments/lists_api.py +37 -39
  114. universal_mcp/applications/twitter/api_segments/spaces_api.py +24 -24
  115. universal_mcp/applications/twitter/api_segments/trends_api.py +4 -4
  116. universal_mcp/applications/twitter/api_segments/tweets_api.py +105 -105
  117. universal_mcp/applications/twitter/api_segments/usage_api.py +4 -4
  118. universal_mcp/applications/twitter/api_segments/users_api.py +136 -136
  119. universal_mcp/applications/twitter/app.py +15 -11
  120. universal_mcp/applications/whatsapp/README.md +12 -12
  121. universal_mcp/applications/whatsapp/app.py +66 -67
  122. universal_mcp/applications/whatsapp/audio.py +39 -35
  123. universal_mcp/applications/whatsapp/whatsapp.py +176 -154
  124. universal_mcp/applications/whatsapp_business/README.md +23 -23
  125. universal_mcp/applications/whatsapp_business/app.py +92 -92
  126. universal_mcp/applications/yahoo_finance/README.md +17 -0
  127. universal_mcp/applications/yahoo_finance/__init__.py +1 -0
  128. universal_mcp/applications/yahoo_finance/app.py +300 -0
  129. universal_mcp/applications/youtube/README.md +46 -46
  130. universal_mcp/applications/youtube/app.py +208 -195
  131. universal_mcp/applications/zenquotes/README.md +1 -1
  132. universal_mcp/applications/zenquotes/__init__.py +2 -0
  133. universal_mcp/applications/zenquotes/app.py +5 -5
  134. {universal_mcp_applications-0.1.17.dist-info → universal_mcp_applications-0.1.33.dist-info}/METADATA +5 -90
  135. {universal_mcp_applications-0.1.17.dist-info → universal_mcp_applications-0.1.33.dist-info}/RECORD +137 -128
  136. universal_mcp/applications/replicate/README.md +0 -18
  137. universal_mcp/applications/replicate/__init__.py +0 -1
  138. universal_mcp/applications/replicate/app.py +0 -493
  139. universal_mcp/applications/unipile/README.md +0 -28
  140. universal_mcp/applications/unipile/__init__.py +0 -1
  141. universal_mcp/applications/unipile/app.py +0 -827
  142. {universal_mcp_applications-0.1.17.dist-info → universal_mcp_applications-0.1.33.dist-info}/WHEEL +0 -0
  143. {universal_mcp_applications-0.1.17.dist-info → universal_mcp_applications-0.1.33.dist-info}/licenses/LICENSE +0 -0
@@ -1,226 +1,338 @@
1
1
  import base64
2
- import io
3
- from datetime import datetime
4
- from io import BytesIO
2
+ import os
5
3
  from typing import Any
6
4
 
7
5
  from loguru import logger
8
- from office365.graph_client import GraphClient
9
-
10
- from universal_mcp.applications.application import BaseApplication
6
+ from universal_mcp.applications.application import APIApplication
11
7
  from universal_mcp.integrations import Integration
12
8
 
13
9
 
14
- def _to_iso_optional(dt_obj: datetime | None) -> str | None:
15
- """Converts a datetime object to ISO format string, or returns None if the object is None."""
16
- if dt_obj is not None:
17
- return dt_obj.isoformat()
18
- return None
19
-
20
-
21
- class SharepointApp(BaseApplication):
10
+ class SharepointApp(APIApplication):
22
11
  """
23
- Base class for Universal MCP Applications.
12
+ Application for interacting with Microsoft SharePoint API (via Microsoft Graph).
13
+ Provides tools to manage files, folders, and access Drive information.
24
14
  """
25
15
 
26
- def __init__(self, integration: Integration = None, client=None, **kwargs) -> None:
27
- """Initializes the SharepointApp.
16
+ def __init__(self, integration: Integration | None = None, **kwargs) -> None:
17
+ super().__init__(name="sharepoint", integration=integration, **kwargs)
18
+ self.base_url = "https://graph.microsoft.com/v1.0"
19
+
20
+ def get_my_profile(self) -> dict[str, Any]:
21
+ """
22
+ Fetches the profile for the currently authenticated user, specifically retrieving their ID and user principal name. This function confirms user identity, distinguishing it from `get_drive_info`, which returns details about the SharePoint storage space (e.g., quota) rather than the user's personal profile.
23
+
24
+ Returns:
25
+ dict[str, Any]: A dictionary containing the user's id and userPrincipalName.
26
+
27
+ Raises:
28
+ HTTPStatusError: If the API request fails.
29
+
30
+ Tags:
31
+ profile, user, account
32
+ """
33
+ url = f"{self.base_url}/me"
34
+ query_params = {"$select": "id,userPrincipalName"}
35
+ response = self._get(url, params=query_params)
36
+ return self._handle_response(response)
37
+
38
+ def get_drive_info(self) -> dict[str, Any]:
39
+ """
40
+ Fetches high-level information about the user's entire SharePoint. It returns drive-wide details like the owner and storage quota, differing from `get_item_metadata` which describes a specific item, and `get_my_profile` which retrieves general user account information.
41
+
42
+ Returns:
43
+ A dictionary containing drive information.
44
+
45
+ Tags:
46
+ drive, storage, quota, info
47
+ """
48
+ url = f"{self.base_url}/me/drive"
49
+ response = self._get(url)
50
+ return self._handle_response(response)
51
+
52
+ def _list_drive_items(self, item_id: str = "root") -> dict[str, Any]:
53
+ """
54
+ Lists the files and folders in the current user's SharePoint.
55
+
28
56
  Args:
29
- client (GraphClient | None, optional): An existing GraphClient instance. If None, a new client will be created on first use.
57
+ item_id (str, optional): The ID of the folder to list. Defaults to 'root'.
58
+
59
+ Returns:
60
+ A dictionary containing the list of files and folders.
30
61
  """
31
- super().__init__(name="sharepoint", integration=integration, **kwargs)
32
- self._client = client
33
- self.integration = integration
34
- self._site_url = None
62
+ url = f"{self.base_url}/me/drive/items/{item_id}/children"
63
+ response = self._get(url)
64
+ return self._handle_response(response)
35
65
 
36
- @property
37
- def client(self):
66
+ def search_files(self, query: str) -> dict[str, Any]:
38
67
  """
39
- A lazy-loaded property that gets or creates an authenticated GraphClient instance. On its first call, it uses integration credentials to initialize the client, fetches the user's profile and root site ID, and caches the instance for subsequent use. This ensures efficient connection management.
40
-
68
+ Searches the user's entire SharePoint for files and folders matching a specified text query. This function performs a comprehensive search from the drive's root, distinguishing it from `list_files` or `list_folders` which only browse the contents of a single directory.
69
+
70
+ Args:
71
+ query (str): The search query.
72
+
41
73
  Returns:
42
- GraphClient: The authenticated GraphClient instance.
43
- """
44
- if not self.integration:
45
- raise ValueError("Integration is required")
46
- credentials = self.integration.get_credentials()
47
- if not credentials:
48
- raise ValueError("No credentials found")
49
-
50
- if not credentials.get("access_token"):
51
- raise ValueError("No access token found")
52
-
53
- def _acquire_token():
54
- """
55
- Formats stored credentials for the `GraphClient` authentication callback. It packages existing access and refresh tokens from the integration into the specific dictionary structure required by the client library for authentication, including a hardcoded 'Bearer' token type.
56
- """
57
- access_token = credentials.get("access_token")
58
- refresh_token = credentials.get("refresh_token")
59
- return {
60
- "access_token": access_token,
61
- "refresh_token": refresh_token,
62
- "token_type": "Bearer",
63
- }
64
-
65
- if self._client is None:
66
- self._client = GraphClient(token_callback=_acquire_token)
67
- # Get me
68
- me = self._client.me.get().execute_query()
69
- logger.debug(me.properties)
70
- # Get sites
71
- sites = self._client.sites.root.get().execute_query()
72
- self._site_url = sites.properties.get("id")
73
- return self._client
74
-
75
- def list_folders(self, folder_path: str | None = None) -> list[dict[str, Any]]:
76
- """
77
- Retrieves the names of all immediate subfolders within a specified directory. If a path is not provided, it defaults to listing folders in the root of the user's drive. This function is distinct from `list_documents`, which lists files.
78
-
74
+ A dictionary containing the search results.
75
+
76
+ Tags:
77
+ search, find, query, files, important
78
+ """
79
+ if not query:
80
+ raise ValueError("Search query cannot be empty.")
81
+
82
+ url = f"{self.base_url}/me/drive/root/search(q='{query}')"
83
+ response = self._get(url)
84
+ return self._handle_response(response)
85
+
86
+ def get_item_metadata(self, item_id: str) -> dict[str, Any]:
87
+ """
88
+ Fetches detailed metadata for a specific file or folder using its unique ID. It returns properties like name, size, and type. Unlike `get_document_content`, it doesn't retrieve the file's actual content, focusing solely on the item's attributes for quick inspection without a full download.
89
+
79
90
  Args:
80
- folder_path (Optional[str], optional): The path to the parent folder. If None, lists folders in the root.
81
-
91
+ item_id (str): The ID of the file or folder.
92
+
82
93
  Returns:
83
- List[Dict[str, Any]]: A list of folder names in the specified directory.
84
-
94
+ A dictionary containing the item's metadata.
95
+
85
96
  Tags:
86
- important
97
+ metadata, info, file, folder
87
98
  """
88
- if folder_path:
89
- folder = self.client.me.drive.root.get_by_path(folder_path)
90
- folders = folder.get_folders(False).execute_query()
91
- else:
92
- folders = self.client.me.drive.root.get_folders(False).execute_query()
99
+ if not item_id:
100
+ raise ValueError("Missing required parameter 'item_id'.")
93
101
 
94
- return [folder.properties.get("name") for folder in folders]
102
+ url = f"{self.base_url}/me/drive/items/{item_id}"
103
+ response = self._get(url)
104
+ return self._handle_response(response)
95
105
 
96
- def create_folder_and_list(
97
- self, folder_name: str, folder_path: str | None = None
98
- ) -> dict[str, Any]:
106
+ def create_folder(self, name: str, parent_id: str = "root") -> dict[str, Any]:
99
107
  """
100
- Creates a new folder with a given name inside a specified parent directory on SharePoint. If no path is provided, the folder is created in the root. It then returns an updated list of all folder names within that parent directory.
101
-
108
+ Creates a new folder with a specified name within a parent directory, which defaults to the root. Returns metadata for the new folder. Unlike `create_folder_and_list`, this function only creates the folder and returns its specific metadata, not the parent directory's contents.
109
+
102
110
  Args:
103
- folder_name (str): The name of the folder to create.
104
- folder_path (str | None, optional): The path to the parent folder. If None, creates in the root.
105
-
111
+ name (str): The name of the new folder.
112
+ parent_id (str, optional): The ID of the parent folder. Defaults to 'root'.
113
+
106
114
  Returns:
107
- Dict[str, Any]: The updated list of folders in the target directory.
108
-
115
+ A dictionary containing the new folder's metadata.
116
+
109
117
  Tags:
110
- important
118
+ create, folder, directory, important
111
119
  """
112
- if folder_path:
113
- folder = self.client.me.drive.root.get_by_path(folder_path)
114
- else:
115
- folder = self.client.me.drive.root
116
- folder.create_folder(folder_name).execute_query()
117
- return self.list_folders(folder_path)
120
+ if not name:
121
+ raise ValueError("Folder name cannot be empty.")
118
122
 
119
- def list_files(self, folder_path: str) -> list[dict[str, Any]]:
123
+ url = f"{self.base_url}/me/drive/items/{parent_id}/children"
124
+ data = {"name": name, "folder": {}, "@microsoft.graph.conflictBehavior": "rename"}
125
+ response = self._post(url, data=data)
126
+ return self._handle_response(response)
127
+
128
+ def delete_item(self, item_id: str) -> dict[str, Any]:
120
129
  """
121
- Retrieves files from a specified folder path. For each file, it returns a dictionary containing key metadata like its name, URL, size, creation date, and last modified date. This function specifically lists files, distinct from `list_folders` which only lists directories.
122
-
130
+ Permanently deletes a specified file or folder from SharePoint using its unique item ID. This versatile function can remove any type of drive item, distinguished from functions that only list or create specific types. A successful deletion returns an empty response, confirming the item's removal.
131
+
123
132
  Args:
124
- folder_path (str): The path to the folder whose documents are to be listed.
125
-
133
+ item_id (str): The ID of the item to delete.
134
+
126
135
  Returns:
127
- List[Dict[str, Any]]: A list of dictionaries containing document metadata.
128
-
136
+ An empty dictionary if successful.
137
+
129
138
  Tags:
130
- important
139
+ delete, remove, file, folder, important
131
140
  """
132
- folder = self.client.me.drive.root.get_by_path(folder_path)
133
- files = folder.get_files(False).execute_query()
141
+ if not item_id:
142
+ raise ValueError("Missing required parameter 'item_id'.")
143
+
144
+ url = f"{self.base_url}/me/drive/items/{item_id}"
145
+ response = self._delete(url)
146
+ return self._handle_response(response)
147
+
148
+ def download_file(self, item_id: str) -> dict[str, Any]:
149
+ """
150
+ Retrieves a temporary, pre-authenticated download URL for a specific file using its item ID. This function provides a link for subsequent download, differing from `get_document_content` which directly fetches the file's raw content. The URL is returned within a dictionary.
134
151
 
135
- return [
136
- {
137
- "name": f.name,
138
- "url": f.properties.get("ServerRelativeUrl"),
139
- "size": f.properties.get("Length"),
140
- "created": _to_iso_optional(f.properties.get("TimeCreated")),
141
- "modified": _to_iso_optional(f.properties.get("TimeLastModified")),
142
- }
143
- for f in files
144
- ]
145
-
146
- def upload_text_file(
147
- self, file_path: str, file_name: str, content: str
148
- ) -> dict[str, Any]:
149
- """
150
- Uploads string content to a new file within a specified SharePoint folder path. After creation, it returns an updated list of all documents and their metadata residing in that folder, effectively confirming the file was added successfully.
151
-
152
152
  Args:
153
- file_path (str): The path to the folder where the document will be created.
154
- file_name (str): The name of the document to create.
155
- content (str): The content to write into the document.
156
-
153
+ item_id (str): The ID of the file to download.
154
+
157
155
  Returns:
158
- Dict[str, Any]: The updated list of documents in the folder.
159
-
160
- Tags: important
156
+ A dictionary containing the download URL for the file under the key '@microsoft.graph.downloadUrl'.
157
+
158
+ Tags:
159
+ download, file, get, important
161
160
  """
162
- file = self.client.me.drive.root.get_by_path(file_path)
163
- file_io = io.StringIO(content)
164
- file_io.name = file_name
165
- file.upload_file(file_io).execute_query()
166
- return self.list_documents(file_path)
161
+ if not item_id:
162
+ raise ValueError("Missing required parameter 'item_id'.")
163
+
164
+ url = f"{self.base_url}/me/drive/items/{item_id}"
165
+ response = self._get(url)
166
+ metadata = self._handle_response(response)
167
+ download_url = metadata.get("@microsoft.graph.downloadUrl")
168
+ if not download_url:
169
+ raise ValueError("Could not retrieve download URL for the item.")
170
+ return {"download_url": download_url}
167
171
 
168
- def get_document_content(self, file_path: str) -> dict[str, Any]:
172
+ def upload_file(self, file_path: str, parent_id: str = "root", file_name: str | None = None) -> dict[str, Any]:
169
173
  """
170
- Retrieves a document's content from SharePoint. It returns a dictionary with the content, name, and size. Content is decoded as a string for text files or Base64-encoded for binary files. This is distinct from `list_documents` which only returns metadata without content.
171
-
174
+ Uploads a local binary file (under 4MB) from a given path to a specified SharePoint folder. Unlike `upload_text_file`, which uploads string content, this function reads from the filesystem. The destination filename can be customized, and it returns the new file's metadata upon completion.
175
+
172
176
  Args:
173
- file_path (str): The path to the document.
174
-
177
+ file_path (str): The local path to the file to upload.
178
+ parent_id (str, optional): The ID of the folder to upload the file to. Defaults to 'root'.
179
+ file_name (str, optional): The name to give the uploaded file. If not provided, the local filename is used.
180
+
175
181
  Returns:
176
- Dict[str, Any]: A dictionary containing the document's name, content type, content (as text or base64), and size.
177
-
178
- Tags: important
179
- """
180
- file = self.client.me.drive.root.get_by_path(file_path).get().execute_query()
181
- content_stream = BytesIO()
182
- file.download(content_stream).execute_query()
183
- content_stream.seek(0)
184
- content = content_stream.read()
185
-
186
- is_text_file = file_path.lower().endswith(
187
- (".txt", ".csv", ".json", ".xml", ".html", ".md", ".js", ".css", ".py")
188
- )
189
- content_dict = (
190
- {"content": content.decode("utf-8")}
191
- if is_text_file
192
- else {"content_base64": base64.b64encode(content).decode("ascii")}
193
- )
194
- return {
195
- "name": file_path.split("/")[-1],
196
- "content_type": "text" if is_text_file else "binary",
197
- **content_dict,
198
- "size": len(content),
199
- }
182
+ A dictionary containing the uploaded file's metadata.
200
183
 
201
- def delete_document(self, file_path: str):
184
+ Tags:
185
+ upload, file, put, important
202
186
  """
203
- Permanently deletes a specified file from SharePoint/OneDrive. The function takes the file path as an argument and returns True upon successful deletion. An exception is raised if the file is not found or the deletion fails.
204
-
187
+ if not os.path.exists(file_path):
188
+ raise FileNotFoundError(f"The file was not found at path: {file_path}")
189
+
190
+ if not file_name:
191
+ file_name = os.path.basename(file_path)
192
+
193
+ url = f"{self.base_url}/me/drive/items/{parent_id}:/{file_name}:/content"
194
+ with open(file_path, "rb") as f:
195
+ data = f.read()
196
+ response = self._put(url, data=data, content_type="application/octet-stream")
197
+ return self._handle_response(response)
198
+
199
+ def list_folders(self, item_id: str = "root") -> dict[str, Any]:
200
+ """
201
+ Retrieves a list of only the folders within a specified parent directory in SharePoint. Unlike `_list_drive_items` which returns all items, this function filters the results to exclude files. Defaults to the root directory if no parent `item_id` is provided.
202
+
203
+ Args:
204
+ item_id (str, optional): The ID of the folder to list from. Defaults to 'root'.
205
+
206
+ Returns:
207
+ A dictionary containing the list of folders.
208
+
209
+ Tags:
210
+ list, folders, directories, important
211
+ """
212
+ all_items = self._list_drive_items(item_id=item_id)
213
+ folders = [item for item in all_items.get("value", []) if "folder" in item]
214
+ return {"value": folders}
215
+
216
+ def list_files(self, item_id: str = "root") -> dict[str, Any]:
217
+ """
218
+ Retrieves a list of files within a specified SharePoint folder, defaulting to the root. Unlike `_list_drive_items` which fetches all items, this function filters the results to exclusively return items identified as files, excluding any subdirectories.
219
+
220
+ Args:
221
+ item_id (str, optional): The ID of the folder to list files from. Defaults to 'root'.
222
+
223
+ Returns:
224
+ A dictionary containing the list of files.
225
+
226
+ Tags:
227
+ list, files, documents, important
228
+ """
229
+ all_items = self._list_drive_items(item_id=item_id)
230
+ files = [item for item in all_items.get("value", []) if "file" in item]
231
+ return {"value": files}
232
+
233
+ def create_folder_and_list(self, name: str, parent_id: str = "root") -> dict[str, Any]:
234
+ """
235
+ Performs a composite action: creates a new folder, then lists all items (files and folders) within that parent directory. This confirms creation by returning the parent's updated contents, distinct from `create_folder` which only returns the new folder's metadata.
236
+
237
+ Args:
238
+ name (str): The name of the new folder.
239
+ parent_id (str, optional): The ID of the parent folder. Defaults to 'root'.
240
+
241
+ Returns:
242
+ A dictionary containing the list of items in the parent folder after creation.
243
+
244
+ Tags:
245
+ create, folder, list, important
246
+ """
247
+ self.create_folder(name=name, parent_id=parent_id)
248
+ return self._list_drive_items(item_id=parent_id)
249
+
250
+ def upload_text_file(self, content: str, parent_id: str = "root", file_name: str = "new_file.txt") -> dict[str, Any]:
251
+ """
252
+ Creates and uploads a new file to SharePoint directly from a string of text content. Unlike `upload_file`, which requires a local file path, this function is specifically for creating a text file from in-memory string data, with a customizable name and destination folder.
253
+
254
+ Args:
255
+ content (str): The text content to upload.
256
+ parent_id (str, optional): The ID of the folder to upload the file to. Defaults to 'root'.
257
+ file_name (str, optional): The name to give the uploaded file. Defaults to 'new_file.txt'.
258
+
259
+ Returns:
260
+ A dictionary containing the uploaded file's metadata.
261
+
262
+ Tags:
263
+ upload, text, file, create, important
264
+ """
265
+ if not file_name:
266
+ raise ValueError("File name cannot be empty.")
267
+
268
+ url = f"{self.base_url}/me/drive/items/{parent_id}:/{file_name}:/content"
269
+ data = content.encode("utf-8")
270
+ response = self._put(url, data=data, content_type="text/plain")
271
+ return self._handle_response(response)
272
+
273
+ def get_document_content(self, item_id: str) -> dict[str, Any]:
274
+ """
275
+ Retrieves the content of a specific file by its item ID and returns it directly as base64-encoded data. This function is distinct from `download_file`, which only provides a temporary URL for the content, and from `get_item_metadata`, which returns file attributes without the content itself. The function fetches the content by following the file's pre-authenticated download URL.
276
+
205
277
  Args:
206
- file_path (str): The path to the file to delete.
207
-
278
+ item_id (str): The ID of the file.
279
+
208
280
  Returns:
209
- bool: True if the file was deleted successfully.
210
-
281
+ dict[str, Any]: A dictionary containing the file details:
282
+ - 'type' (str): The general type of the file (e.g., "image", "audio", "video", "file").
283
+ - 'data' (str): The base64 encoded content of the file.
284
+ - 'mime_type' (str): The MIME type of the file.
285
+ - 'file_name' (str): The name of the file.
286
+
211
287
  Tags:
212
- important
288
+ get, content, read, file, important
213
289
  """
214
- file = self.client.me.drive.root.get_by_path(file_path)
215
- file.delete_object().execute_query()
216
- return True
290
+ if not item_id:
291
+ raise ValueError("Missing required parameter 'item_id'.")
292
+
293
+ metadata = self.get_item_metadata(item_id=item_id)
294
+ file_metadata = metadata.get("file")
295
+ if not file_metadata:
296
+ raise ValueError(f"Item with ID '{item_id}' is not a file.")
297
+
298
+ file_mime_type = file_metadata.get("mimeType", "application/octet-stream")
299
+ file_name = metadata.get("name")
300
+
301
+ download_url = metadata.get("@microsoft.graph.downloadUrl")
302
+ if not download_url:
303
+ logger.error(f"Could not find @microsoft.graph.downloadUrl in metadata for item {item_id}")
304
+ raise ValueError("Could not retrieve download URL for the item.")
305
+
306
+ response = self._get(download_url)
307
+
308
+ response.raise_for_status()
309
+
310
+ content = response.content
311
+
312
+ attachment_type = file_mime_type.split("/")[0] if "/" in file_mime_type else "file"
313
+ if attachment_type not in ["image", "audio", "video", "text"]:
314
+ attachment_type = "file"
315
+
316
+ return {
317
+ "type": attachment_type,
318
+ "data": content,
319
+ "mime_type": file_mime_type,
320
+ "file_name": file_name,
321
+ }
217
322
 
218
323
  def list_tools(self):
219
324
  return [
325
+ self.get_drive_info,
326
+ self.search_files,
327
+ self.get_item_metadata,
328
+ self.create_folder,
329
+ self.delete_item,
330
+ self.download_file,
331
+ self.upload_file,
332
+ self.get_my_profile,
220
333
  self.list_folders,
221
- self.create_folder_and_list,
222
334
  self.list_files,
335
+ self.create_folder_and_list,
223
336
  self.upload_text_file,
224
337
  self.get_document_content,
225
- self.delete_document,
226
- ]
338
+ ]
@@ -1,7 +1,6 @@
1
1
  from typing import Any
2
2
 
3
3
  from loguru import logger
4
-
5
4
  from universal_mcp.applications.application import APIApplication
6
5
  from universal_mcp.integrations import Integration
7
6
 
@@ -1,10 +1,10 @@
1
- # Shortcut MCP Server
1
+ # ShortcutApp MCP Server
2
2
 
3
- An MCP Server for the Shortcut API.
3
+ An MCP Server for the ShortcutApp API.
4
4
 
5
5
  ## 🛠️ Tool List
6
6
 
7
- This is automatically generated from OpenAPI schema for the Shortcut API.
7
+ This is automatically generated from OpenAPI schema for the ShortcutApp API.
8
8
 
9
9
 
10
10
  | Tool | Description |
@@ -0,0 +1,23 @@
1
+ # SlackApp MCP Server
2
+
3
+ An MCP Server for the SlackApp API.
4
+
5
+ ## 🛠️ Tool List
6
+
7
+ This is automatically generated from OpenAPI schema for the SlackApp API.
8
+
9
+
10
+ | Tool | Description |
11
+ |------|-------------|
12
+ | `chat_delete` | Deletes a specific message from a Slack channel. It identifies the message using its channel ID and timestamp (`ts`). This function is distinct from `chat_update` which modifies a message, and `chat_post_message` which sends a new one. |
13
+ | `chat_post_message` | Posts a new message to a specified Slack channel. It supports rich content formats like text, attachments, and blocks, along with options for threading and authorship. This function creates new messages, unlike `chat_update` which modifies existing ones or `chat_delete` which removes them. |
14
+ | `chat_update` | Updates a specific, existing message in a Slack channel, identified by its timestamp. It modifies content such as text, blocks, or attachments by calling the `chat.update` API endpoint, distinguishing it from functions that create (`chat_post_message`) or delete (`chat_delete`) messages. |
15
+ | `conversations_history` | Fetches the message history for a specific Slack conversation or channel. Supports filtering messages by timestamp and pagination using a cursor. This method retrieves messages *within* a conversation, unlike `conversations_list` which retrieves a list of available conversations. |
16
+ | `conversations_list` | Fetches a paginated list of channel-like conversations in a workspace. Allows filtering by type and excluding archived channels. This function lists available conversations, unlike `conversations_history` which retrieves the message history from within a specific conversation. |
17
+ | `reactions_add` | Adds a specific emoji reaction to a message in a Slack channel, identifying the message by its channel ID and timestamp. This method creates a new reaction, unlike `reactions_get` or `reactions_list` which retrieve existing reaction data for items or users. |
18
+ | `get_reactions_for_item` | Retrieves all reactions for a single item, such as a message, file, or file comment. This function targets reactions *on* a specific item, unlike `reactions_list` which gets reactions created *by* a user. |
19
+ | `get_user_reactions` | Retrieves a paginated list of items (e.g., messages, files) that a specific user has reacted to. Unlike `reactions_get`, which fetches all reactions for a single item, this function lists all items a given user has reacted to across Slack. |
20
+ | `search_messages` | Searches a Slack workspace for messages matching a specific query. It supports advanced control over results through pagination, sorting by relevance or timestamp, and an option to highlight search terms within the returned message content. |
21
+ | `team_info` | Fetches details for a Slack team, such as name and domain, by calling the `team.info` API endpoint. This function requires an authentication token and can optionally target a specific team by its ID, distinguishing it from user or channel-specific functions. |
22
+ | `get_user_info` | Fetches detailed profile information for a single Slack user, identified by their user ID. Unlike `users_list`, which retrieves all workspace members, this function targets an individual and can optionally include their locale information. It directly calls the `users.info` Slack API endpoint. |
23
+ | `users_list` | Fetches a paginated list of all users in a Slack workspace, including deactivated members. Unlike `users_info` which retrieves a single user's details, this function returns a collection and supports limiting results or including locale data through optional parameters. |