mcp-sharepoint-us 2.0.13__tar.gz → 2.0.14__tar.gz

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 mcp-sharepoint-us might be problematic. Click here for more details.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mcp-sharepoint-us
3
- Version: 2.0.13
3
+ Version: 2.0.14
4
4
  Summary: SharePoint MCP Server with Microsoft Graph API
5
5
  License: MIT
6
6
  Project-URL: Homepage, https://github.com/mdev26/mcp-sharepoint-us
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "mcp-sharepoint-us"
7
- version = "2.0.13"
7
+ version = "2.0.14"
8
8
  description = "SharePoint MCP Server with Microsoft Graph API"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -35,6 +35,7 @@ def ensure_context(func):
35
35
  global graph_client, authenticator
36
36
  if graph_client is None:
37
37
  try:
38
+ logger.info("Initializing Graph API client...")
38
39
  from .auth import SharePointAuthenticator
39
40
 
40
41
  # Get credentials
@@ -44,6 +45,11 @@ def ensure_context(func):
44
45
  tenant_id = os.getenv("SHP_TENANT_ID")
45
46
  cloud = "government" if ".sharepoint.us" in site_url else "commercial"
46
47
 
48
+ logger.info(f"Site URL: {site_url}")
49
+ logger.info(f"Tenant ID: {tenant_id}")
50
+ logger.info(f"Client ID: {client_id}")
51
+ logger.info(f"Cloud: {cloud}")
52
+
47
53
  # Create shared authenticator
48
54
  authenticator = SharePointAuthenticator(
49
55
  site_url=site_url,
@@ -52,11 +58,15 @@ def ensure_context(func):
52
58
  tenant_id=tenant_id,
53
59
  cloud=cloud
54
60
  )
61
+ logger.info("Authenticator created successfully")
55
62
 
56
63
  # Create Graph API client with direct token access
57
64
  def get_token():
58
65
  """Get access token for Graph API"""
59
- return authenticator.get_access_token()
66
+ logger.debug("Token callback invoked")
67
+ token = authenticator.get_access_token()
68
+ logger.debug(f"Token acquired (length: {len(token)})")
69
+ return token
60
70
 
61
71
  graph_client = GraphAPIClient(
62
72
  site_url=site_url,
@@ -65,7 +75,7 @@ def ensure_context(func):
65
75
  logger.info("Graph API client initialized successfully")
66
76
 
67
77
  except Exception as e:
68
- logger.error(f"Failed to initialize Graph API client: {e}")
78
+ logger.error(f"Failed to initialize Graph API client: {e}", exc_info=True)
69
79
  raise RuntimeError(
70
80
  f"Graph API authentication failed: {e}. "
71
81
  "Please check your environment variables and ensure:\n"
@@ -321,28 +331,42 @@ async def call_tool(name: str, arguments: dict) -> list[TextContent]:
321
331
 
322
332
 
323
333
  async def test_connection() -> list[TextContent]:
324
- """Test SharePoint connection"""
334
+ """Test SharePoint connection using Microsoft Graph API"""
325
335
  try:
326
- web = ctx.web.get().execute_query()
327
- auth_method = os.getenv("SHP_AUTH_METHOD", "msal")
328
-
336
+ logger.info("Testing Graph API connection...")
337
+
338
+ # Try to get site ID and drive ID
339
+ site_id = await asyncio.to_thread(graph_client._get_site_id)
340
+ drive_id = await asyncio.to_thread(graph_client._get_drive_id)
341
+
342
+ auth_method = "msal (Microsoft Graph API)"
343
+
344
+ logger.info(f"✓ Connection test successful - Site ID: {site_id}, Drive ID: {drive_id}")
345
+
329
346
  return [TextContent(
330
347
  type="text",
331
- text=f"✓ Successfully connected to SharePoint!\n\n"
332
- f"Site Title: {web.title}\n"
333
- f"Site URL: {web.url}\n"
334
- f"Authentication Method: {auth_method.upper()}\n"
348
+ text=f"✓ Successfully connected to SharePoint via Microsoft Graph API!\n\n"
349
+ f"Site URL: {graph_client.site_url}\n"
350
+ f"Graph Endpoint: {graph_client.graph_endpoint}\n"
351
+ f"Site ID: {site_id}\n"
352
+ f"Drive ID: {drive_id}\n"
353
+ f"Authentication Method: {auth_method}\n"
335
354
  f"Tenant ID: {os.getenv('SHP_TENANT_ID')}\n\n"
336
- f"Connection is working correctly with modern Azure AD authentication."
355
+ f"Connection is working correctly with Microsoft Graph API."
337
356
  )]
338
357
  except Exception as e:
358
+ logger.error(f"✗ Connection test failed: {str(e)}", exc_info=True)
339
359
  return [TextContent(
340
360
  type="text",
341
361
  text=f"✗ Connection failed: {str(e)}\n\n"
342
362
  f"This usually means:\n"
343
363
  f"1. Your credentials are incorrect\n"
344
- f"2. Your app doesn't have proper SharePoint permissions\n"
345
- f"3. You're using legacy auth on a new tenant (set SHP_AUTH_METHOD=msal)"
364
+ f"2. Your app doesn't have proper Microsoft Graph permissions\n"
365
+ f"3. Network connectivity issues\n"
366
+ f"4. Azure AD app registration is missing required permissions:\n"
367
+ f" - Sites.Read.All\n"
368
+ f" - Files.ReadWrite.All\n\n"
369
+ f"Check the logs for more details."
346
370
  )]
347
371
 
348
372
 
@@ -226,16 +226,22 @@ class SharePointAuthenticator:
226
226
 
227
227
  now = int(time.time())
228
228
  if self._access_token and now < (self._access_token_exp - 60):
229
+ logger.debug("Using cached access token")
229
230
  return self._access_token
230
231
 
232
+ logger.info(f"Acquiring new access token from {self._authority_url}")
233
+ logger.debug(f"Scopes: {self._scopes}")
234
+
231
235
  last_err = None
232
236
  for attempt in range(1, 6): # 5 attempts
233
237
  try:
238
+ logger.debug(f"Token acquisition attempt {attempt}/5")
234
239
  result = self._msal_app.acquire_token_for_client(scopes=self._scopes)
235
240
 
236
241
  if "access_token" not in result:
237
242
  error_desc = result.get("error_description", "Unknown error")
238
243
  error = result.get("error", "Unknown")
244
+ logger.error(f"Token acquisition failed: {error} - {error_desc}")
239
245
  raise ValueError(
240
246
  f"Failed to acquire token: {error} - {error_desc}\n"
241
247
  f"Authority: {self._authority_url}\n"
@@ -249,11 +255,13 @@ class SharePointAuthenticator:
249
255
  self._access_token = token
250
256
  self._access_token_exp = int(time.time()) + expires_in
251
257
 
252
- logger.info(f"Successfully acquired Graph API token")
258
+ logger.info(f"Successfully acquired Graph API token (expires in {expires_in}s)")
259
+ logger.debug(f"Token length: {len(token)}, starts with: {token[:20]}...")
253
260
  return token
254
261
 
255
262
  except Exception as e:
256
263
  last_err = e
264
+ logger.error(f"Token acquisition attempt {attempt}/5 failed: {type(e).__name__}: {e}")
257
265
  # Exponential backoff with jitter
258
266
  sleep_s = min(8.0, (2 ** (attempt - 1)) * 0.5) + random.random() * 0.25
259
267
  logger.warning(
@@ -42,6 +42,7 @@ class GraphAPIClient:
42
42
 
43
43
  def _get_headers(self) -> Dict[str, str]:
44
44
  """Get authorization headers with access token."""
45
+ logger.debug("Getting authorization headers...")
45
46
  token_obj = self.token_callback()
46
47
  # Handle both TokenResponse objects and plain strings
47
48
  if hasattr(token_obj, 'accessToken'):
@@ -49,6 +50,8 @@ class GraphAPIClient:
49
50
  else:
50
51
  token = str(token_obj)
51
52
 
53
+ logger.debug(f"Token acquired for headers (length: {len(token)}, starts with: {token[:20]}...)")
54
+
52
55
  return {
53
56
  "Authorization": f"Bearer {token}",
54
57
  "Accept": "application/json",
@@ -91,6 +94,7 @@ class GraphAPIClient:
91
94
  Caches the result for reuse.
92
95
  """
93
96
  if self._site_id:
97
+ logger.debug(f"Using cached site ID: {self._site_id}")
94
98
  return self._site_id
95
99
 
96
100
  parsed = urlparse(self.site_url)
@@ -104,12 +108,18 @@ class GraphAPIClient:
104
108
  else:
105
109
  url = f"{self.graph_endpoint}/sites/{hostname}:/{path}"
106
110
 
107
- response = requests.get(url, headers=self._get_headers())
108
- self._handle_response(response)
111
+ logger.info(f"Fetching site ID from: {url}")
112
+ try:
113
+ response = requests.get(url, headers=self._get_headers(), timeout=30)
114
+ logger.debug(f"Response status: {response.status_code}")
115
+ self._handle_response(response)
109
116
 
110
- self._site_id = response.json()["id"]
111
- logger.info(f"Retrieved site ID: {self._site_id}")
112
- return self._site_id
117
+ self._site_id = response.json()["id"]
118
+ logger.info(f"Retrieved site ID: {self._site_id}")
119
+ return self._site_id
120
+ except requests.exceptions.RequestException as e:
121
+ logger.error(f"Network error getting site ID: {type(e).__name__}: {e}", exc_info=True)
122
+ raise
113
123
 
114
124
  def _get_drive_id(self) -> str:
115
125
  """
@@ -117,17 +127,24 @@ class GraphAPIClient:
117
127
  Caches the result for reuse.
118
128
  """
119
129
  if self._drive_id:
130
+ logger.debug(f"Using cached drive ID: {self._drive_id}")
120
131
  return self._drive_id
121
132
 
122
133
  site_id = self._get_site_id()
123
134
  url = f"{self.graph_endpoint}/sites/{site_id}/drive"
124
135
 
125
- response = requests.get(url, headers=self._get_headers())
126
- self._handle_response(response)
136
+ logger.info(f"Fetching drive ID from: {url}")
137
+ try:
138
+ response = requests.get(url, headers=self._get_headers(), timeout=30)
139
+ logger.debug(f"Response status: {response.status_code}")
140
+ self._handle_response(response)
127
141
 
128
- self._drive_id = response.json()["id"]
129
- logger.info(f"Retrieved drive ID: {self._drive_id}")
130
- return self._drive_id
142
+ self._drive_id = response.json()["id"]
143
+ logger.info(f"Retrieved drive ID: {self._drive_id}")
144
+ return self._drive_id
145
+ except requests.exceptions.RequestException as e:
146
+ logger.error(f"Network error getting drive ID: {type(e).__name__}: {e}", exc_info=True)
147
+ raise
131
148
 
132
149
  def list_folders(self, folder_path: str = "") -> List[Dict[str, Any]]:
133
150
  """
@@ -139,6 +156,7 @@ class GraphAPIClient:
139
156
  Returns:
140
157
  List of folder objects with name, id, webUrl
141
158
  """
159
+ logger.info(f"Listing folders in '{folder_path}'")
142
160
  site_id = self._get_site_id()
143
161
  drive_id = self._get_drive_id()
144
162
 
@@ -149,23 +167,29 @@ class GraphAPIClient:
149
167
  else:
150
168
  url = f"{self.graph_endpoint}/sites/{site_id}/drives/{drive_id}/root/children"
151
169
 
152
- response = requests.get(url, headers=self._get_headers())
153
- self._handle_response(response)
154
-
155
- items = response.json().get("value", [])
156
- # Filter to only folders
157
- folders = [
158
- {
159
- "name": item["name"],
160
- "id": item["id"],
161
- "webUrl": item.get("webUrl", ""),
162
- }
163
- for item in items
164
- if "folder" in item
165
- ]
166
-
167
- logger.info(f"Found {len(folders)} folders in '{folder_path}'")
168
- return folders
170
+ logger.info(f"Fetching folders from: {url}")
171
+ try:
172
+ response = requests.get(url, headers=self._get_headers(), timeout=30)
173
+ logger.debug(f"Response status: {response.status_code}")
174
+ self._handle_response(response)
175
+
176
+ items = response.json().get("value", [])
177
+ # Filter to only folders
178
+ folders = [
179
+ {
180
+ "name": item["name"],
181
+ "id": item["id"],
182
+ "webUrl": item.get("webUrl", ""),
183
+ }
184
+ for item in items
185
+ if "folder" in item
186
+ ]
187
+
188
+ logger.info(f"Found {len(folders)} folders in '{folder_path}'")
189
+ return folders
190
+ except requests.exceptions.RequestException as e:
191
+ logger.error(f"Network error listing folders: {type(e).__name__}: {e}", exc_info=True)
192
+ raise
169
193
 
170
194
  def list_documents(self, folder_path: str = "") -> List[Dict[str, Any]]:
171
195
  """
@@ -177,6 +201,7 @@ class GraphAPIClient:
177
201
  Returns:
178
202
  List of file objects with name, id, size, webUrl
179
203
  """
204
+ logger.info(f"Listing documents in '{folder_path}'")
180
205
  site_id = self._get_site_id()
181
206
  drive_id = self._get_drive_id()
182
207
 
@@ -186,24 +211,30 @@ class GraphAPIClient:
186
211
  else:
187
212
  url = f"{self.graph_endpoint}/sites/{site_id}/drives/{drive_id}/root/children"
188
213
 
189
- response = requests.get(url, headers=self._get_headers())
190
- self._handle_response(response)
191
-
192
- items = response.json().get("value", [])
193
- # Filter to only files
194
- files = [
195
- {
196
- "name": item["name"],
197
- "id": item["id"],
198
- "size": item.get("size", 0),
199
- "webUrl": item.get("webUrl", ""),
200
- }
201
- for item in items
202
- if "file" in item
203
- ]
204
-
205
- logger.info(f"Found {len(files)} files in '{folder_path}'")
206
- return files
214
+ logger.info(f"Fetching documents from: {url}")
215
+ try:
216
+ response = requests.get(url, headers=self._get_headers(), timeout=30)
217
+ logger.debug(f"Response status: {response.status_code}")
218
+ self._handle_response(response)
219
+
220
+ items = response.json().get("value", [])
221
+ # Filter to only files
222
+ files = [
223
+ {
224
+ "name": item["name"],
225
+ "id": item["id"],
226
+ "size": item.get("size", 0),
227
+ "webUrl": item.get("webUrl", ""),
228
+ }
229
+ for item in items
230
+ if "file" in item
231
+ ]
232
+
233
+ logger.info(f"Found {len(files)} files in '{folder_path}'")
234
+ return files
235
+ except requests.exceptions.RequestException as e:
236
+ logger.error(f"Network error listing documents: {type(e).__name__}: {e}", exc_info=True)
237
+ raise
207
238
 
208
239
  def get_file_content(self, file_path: str) -> bytes:
209
240
  """
@@ -215,17 +246,24 @@ class GraphAPIClient:
215
246
  Returns:
216
247
  File content as bytes
217
248
  """
249
+ logger.info(f"Getting content for file '{file_path}'")
218
250
  site_id = self._get_site_id()
219
251
  drive_id = self._get_drive_id()
220
252
 
221
253
  encoded_path = quote(file_path)
222
254
  url = f"{self.graph_endpoint}/sites/{site_id}/drives/{drive_id}/root:/{encoded_path}:/content"
223
255
 
224
- response = requests.get(url, headers=self._get_headers())
225
- self._handle_response(response)
256
+ logger.info(f"Fetching file content from: {url}")
257
+ try:
258
+ response = requests.get(url, headers=self._get_headers(), timeout=60)
259
+ logger.debug(f"Response status: {response.status_code}")
260
+ self._handle_response(response)
226
261
 
227
- logger.info(f"Retrieved content for '{file_path}' ({len(response.content)} bytes)")
228
- return response.content
262
+ logger.info(f"Retrieved content for '{file_path}' ({len(response.content)} bytes)")
263
+ return response.content
264
+ except requests.exceptions.RequestException as e:
265
+ logger.error(f"Network error getting file content: {type(e).__name__}: {e}", exc_info=True)
266
+ raise
229
267
 
230
268
  def upload_file(self, folder_path: str, file_name: str, content: bytes) -> Dict[str, Any]:
231
269
  """
@@ -239,6 +277,7 @@ class GraphAPIClient:
239
277
  Returns:
240
278
  File metadata
241
279
  """
280
+ logger.info(f"Uploading file '{file_name}' to '{folder_path}' ({len(content)} bytes)")
242
281
  site_id = self._get_site_id()
243
282
  drive_id = self._get_drive_id()
244
283
 
@@ -250,14 +289,20 @@ class GraphAPIClient:
250
289
  encoded_path = quote(full_path)
251
290
  url = f"{self.graph_endpoint}/sites/{site_id}/drives/{drive_id}/root:/{encoded_path}:/content"
252
291
 
292
+ logger.info(f"Uploading to: {url}")
253
293
  headers = self._get_headers()
254
294
  headers["Content-Type"] = "application/octet-stream"
255
295
 
256
- response = requests.put(url, headers=headers, data=content)
257
- self._handle_response(response)
296
+ try:
297
+ response = requests.put(url, headers=headers, data=content, timeout=120)
298
+ logger.debug(f"Response status: {response.status_code}")
299
+ self._handle_response(response)
258
300
 
259
- logger.info(f"Uploaded '{file_name}' to '{folder_path}'")
260
- return response.json()
301
+ logger.info(f"Successfully uploaded '{file_name}' to '{folder_path}'")
302
+ return response.json()
303
+ except requests.exceptions.RequestException as e:
304
+ logger.error(f"Network error uploading file: {type(e).__name__}: {e}", exc_info=True)
305
+ raise
261
306
 
262
307
  def delete_file(self, file_path: str) -> None:
263
308
  """
@@ -266,16 +311,23 @@ class GraphAPIClient:
266
311
  Args:
267
312
  file_path: Relative path to the file
268
313
  """
314
+ logger.info(f"Deleting file '{file_path}'")
269
315
  site_id = self._get_site_id()
270
316
  drive_id = self._get_drive_id()
271
317
 
272
318
  encoded_path = quote(file_path)
273
319
  url = f"{self.graph_endpoint}/sites/{site_id}/drives/{drive_id}/root:/{encoded_path}"
274
320
 
275
- response = requests.delete(url, headers=self._get_headers())
276
- self._handle_response(response)
321
+ logger.info(f"Deleting from: {url}")
322
+ try:
323
+ response = requests.delete(url, headers=self._get_headers(), timeout=30)
324
+ logger.debug(f"Response status: {response.status_code}")
325
+ self._handle_response(response)
277
326
 
278
- logger.info(f"Deleted '{file_path}'")
327
+ logger.info(f"Successfully deleted '{file_path}'")
328
+ except requests.exceptions.RequestException as e:
329
+ logger.error(f"Network error deleting file: {type(e).__name__}: {e}", exc_info=True)
330
+ raise
279
331
 
280
332
  def create_folder(self, parent_path: str, folder_name: str) -> Dict[str, Any]:
281
333
  """
@@ -288,6 +340,7 @@ class GraphAPIClient:
288
340
  Returns:
289
341
  Folder metadata
290
342
  """
343
+ logger.info(f"Creating folder '{folder_name}' in '{parent_path}'")
291
344
  site_id = self._get_site_id()
292
345
  drive_id = self._get_drive_id()
293
346
 
@@ -297,17 +350,23 @@ class GraphAPIClient:
297
350
  else:
298
351
  url = f"{self.graph_endpoint}/sites/{site_id}/drives/{drive_id}/root/children"
299
352
 
353
+ logger.info(f"Creating folder at: {url}")
300
354
  payload = {
301
355
  "name": folder_name,
302
356
  "folder": {},
303
357
  "@microsoft.graph.conflictBehavior": "fail"
304
358
  }
305
359
 
306
- response = requests.post(url, headers=self._get_headers(), json=payload)
307
- self._handle_response(response)
360
+ try:
361
+ response = requests.post(url, headers=self._get_headers(), json=payload, timeout=30)
362
+ logger.debug(f"Response status: {response.status_code}")
363
+ self._handle_response(response)
308
364
 
309
- logger.info(f"Created folder '{folder_name}' in '{parent_path}'")
310
- return response.json()
365
+ logger.info(f"Successfully created folder '{folder_name}' in '{parent_path}'")
366
+ return response.json()
367
+ except requests.exceptions.RequestException as e:
368
+ logger.error(f"Network error creating folder: {type(e).__name__}: {e}", exc_info=True)
369
+ raise
311
370
 
312
371
  def delete_folder(self, folder_path: str) -> None:
313
372
  """
@@ -316,13 +375,20 @@ class GraphAPIClient:
316
375
  Args:
317
376
  folder_path: Relative path to the folder
318
377
  """
378
+ logger.info(f"Deleting folder '{folder_path}'")
319
379
  site_id = self._get_site_id()
320
380
  drive_id = self._get_drive_id()
321
381
 
322
382
  encoded_path = quote(folder_path)
323
383
  url = f"{self.graph_endpoint}/sites/{site_id}/drives/{drive_id}/root:/{encoded_path}"
324
384
 
325
- response = requests.delete(url, headers=self._get_headers())
326
- self._handle_response(response)
327
-
328
- logger.info(f"Deleted folder '{folder_path}'")
385
+ logger.info(f"Deleting folder from: {url}")
386
+ try:
387
+ response = requests.delete(url, headers=self._get_headers(), timeout=30)
388
+ logger.debug(f"Response status: {response.status_code}")
389
+ self._handle_response(response)
390
+
391
+ logger.info(f"Successfully deleted folder '{folder_path}'")
392
+ except requests.exceptions.RequestException as e:
393
+ logger.error(f"Network error deleting folder: {type(e).__name__}: {e}", exc_info=True)
394
+ raise
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mcp-sharepoint-us
3
- Version: 2.0.13
3
+ Version: 2.0.14
4
4
  Summary: SharePoint MCP Server with Microsoft Graph API
5
5
  License: MIT
6
6
  Project-URL: Homepage, https://github.com/mdev26/mcp-sharepoint-us