mcp-sharepoint-us 2.0.14__py3-none-any.whl → 2.0.16__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 mcp-sharepoint-us might be problematic. Click here for more details.

@@ -5,9 +5,13 @@ Primary API for all SharePoint operations in Azure Government Cloud.
5
5
  import os
6
6
  import logging
7
7
  import asyncio
8
+ import socket
9
+ import ssl
8
10
  from typing import Optional, Dict, Any, List
9
11
  from urllib.parse import urlparse, quote
10
12
  import requests
13
+ from requests.adapters import HTTPAdapter
14
+ from urllib3.util.retry import Retry
11
15
 
12
16
  logger = logging.getLogger(__name__)
13
17
 
@@ -40,6 +44,126 @@ class GraphAPIClient:
40
44
  self.graph_endpoint = "https://graph.microsoft.com/v1.0"
41
45
  logger.info("Using Microsoft Graph Commercial endpoint")
42
46
 
47
+ # Create a requests session with retry logic
48
+ self._session = self._create_session()
49
+
50
+ def _create_session(self) -> requests.Session:
51
+ """
52
+ Create a requests session with retry logic and connection pooling.
53
+ """
54
+ session = requests.Session()
55
+
56
+ # Configure retry strategy for transient errors
57
+ retry_strategy = Retry(
58
+ total=3, # Total number of retries
59
+ backoff_factor=1, # Wait 1, 2, 4 seconds between retries
60
+ status_forcelist=[429, 500, 502, 503, 504], # Retry on these HTTP status codes
61
+ allowed_methods=["HEAD", "GET", "PUT", "DELETE", "OPTIONS", "TRACE", "POST"]
62
+ )
63
+
64
+ adapter = HTTPAdapter(max_retries=retry_strategy, pool_connections=10, pool_maxsize=10)
65
+ session.mount("http://", adapter)
66
+ session.mount("https://", adapter)
67
+
68
+ logger.debug("Created requests session with retry logic and connection pooling")
69
+ return session
70
+
71
+ def _diagnose_connectivity(self, url: str) -> None:
72
+ """
73
+ Perform detailed connectivity diagnostics for a URL.
74
+
75
+ Args:
76
+ url: The URL to diagnose
77
+ """
78
+ parsed = urlparse(url)
79
+ hostname = parsed.hostname
80
+ port = parsed.port or (443 if parsed.scheme == "https" else 80)
81
+
82
+ logger.info(f"=== CONNECTIVITY DIAGNOSTICS for {hostname} ===")
83
+
84
+ # 1. DNS Resolution
85
+ try:
86
+ logger.info(f"[DNS] Resolving {hostname}...")
87
+ ip_addresses = socket.getaddrinfo(hostname, port, socket.AF_UNSPEC, socket.SOCK_STREAM)
88
+ for family, socktype, proto, canonname, sockaddr in ip_addresses:
89
+ family_name = "IPv4" if family == socket.AF_INET else "IPv6"
90
+ logger.info(f"[DNS] ✓ Resolved to {sockaddr[0]} ({family_name})")
91
+ except socket.gaierror as e:
92
+ logger.error(f"[DNS] ✗ DNS resolution failed: {e}")
93
+ return
94
+ except Exception as e:
95
+ logger.error(f"[DNS] ✗ Unexpected error during DNS resolution: {e}")
96
+ return
97
+
98
+ # 2. TCP Connection Test
99
+ try:
100
+ logger.info(f"[TCP] Testing TCP connection to {hostname}:{port}...")
101
+ with socket.create_connection((hostname, port), timeout=10) as sock:
102
+ logger.info(f"[TCP] ✓ TCP connection successful")
103
+ peer_name = sock.getpeername()
104
+ logger.info(f"[TCP] Connected to {peer_name[0]}:{peer_name[1]}")
105
+
106
+ # 3. SSL/TLS Test (if HTTPS)
107
+ if parsed.scheme == "https":
108
+ logger.info(f"[TLS] Testing TLS handshake to {hostname}...")
109
+ logger.info(f"[TLS] This will attempt to establish encrypted HTTPS connection")
110
+ context = ssl.create_default_context()
111
+ try:
112
+ with context.wrap_socket(sock, server_hostname=hostname) as ssock:
113
+ logger.info(f"[TLS] ✓ TLS handshake successful")
114
+ logger.info(f"[TLS] Protocol: {ssock.version()}")
115
+ cipher = ssock.cipher()
116
+ if cipher:
117
+ logger.info(f"[TLS] Cipher: {cipher[0]} (bits: {cipher[2]})")
118
+
119
+ # Get certificate info
120
+ cert = ssock.getpeercert()
121
+ if cert:
122
+ subject = dict(x[0] for x in cert['subject'])
123
+ logger.info(f"[TLS] Certificate subject: {subject.get('commonName', 'N/A')}")
124
+ logger.info(f"[TLS] Certificate issuer: {dict(x[0] for x in cert['issuer']).get('organizationName', 'N/A')}")
125
+ except ssl.SSLError as e:
126
+ logger.error(f"[TLS] ✗ TLS/SSL handshake failed: {e}")
127
+ logger.error(f"[TLS] This could indicate:")
128
+ logger.error(f"[TLS] - Certificate validation failure")
129
+ logger.error(f"[TLS] - TLS version mismatch")
130
+ logger.error(f"[TLS] - Cipher suite incompatibility")
131
+ return
132
+ except ConnectionResetError as e:
133
+ logger.error(f"[TLS] ✗ Connection reset during TLS handshake")
134
+ logger.error(f"[TLS] TCP connection was established BUT connection dropped during TLS negotiation")
135
+ logger.error(f"[TLS] This indicates:")
136
+ logger.error(f"[TLS] - Firewall is doing deep packet inspection (DPI)")
137
+ logger.error(f"[TLS] - Firewall is blocking TLS connections to {hostname}")
138
+ logger.error(f"[TLS] - SNI (Server Name Indication) filtering is active")
139
+ logger.error(f"[TLS]")
140
+ logger.error(f"[TLS] SOLUTION: Ask network team to whitelist {hostname} in firewall")
141
+ logger.error(f"[TLS] The firewall needs to allow TLS/HTTPS traffic to this endpoint")
142
+ return
143
+ except socket.timeout:
144
+ logger.error(f"[TCP] ✗ Connection timeout after 10 seconds")
145
+ return
146
+ except ConnectionRefusedError:
147
+ logger.error(f"[TCP] ✗ Connection refused by server")
148
+ return
149
+ except ConnectionResetError:
150
+ logger.error(f"[TCP] ✗ Connection reset by peer during TCP handshake")
151
+ return
152
+ except Exception as e:
153
+ logger.error(f"[TCP] ✗ Connection failed: {type(e).__name__}: {e}")
154
+ return
155
+
156
+ # 4. HTTP Basic Connectivity Test
157
+ try:
158
+ logger.info(f"[HTTP] Testing basic HTTP GET to {parsed.scheme}://{hostname}/")
159
+ test_url = f"{parsed.scheme}://{hostname}/"
160
+ response = self._session.get(test_url, timeout=10)
161
+ logger.info(f"[HTTP] ✓ Basic HTTP request successful (status: {response.status_code})")
162
+ except requests.exceptions.RequestException as e:
163
+ logger.error(f"[HTTP] ✗ Basic HTTP request failed: {type(e).__name__}: {e}")
164
+
165
+ logger.info(f"=== END DIAGNOSTICS ===\n")
166
+
43
167
  def _get_headers(self) -> Dict[str, str]:
44
168
  """Get authorization headers with access token."""
45
169
  logger.debug("Getting authorization headers...")
@@ -109,16 +233,66 @@ class GraphAPIClient:
109
233
  url = f"{self.graph_endpoint}/sites/{hostname}:/{path}"
110
234
 
111
235
  logger.info(f"Fetching site ID from: {url}")
236
+
237
+ # Get headers and log sanitized version
238
+ headers = self._get_headers()
239
+ sanitized_headers = {k: (v[:20] + "..." if k == "Authorization" else v) for k, v in headers.items()}
240
+ logger.debug(f"Request headers: {sanitized_headers}")
241
+
112
242
  try:
113
- response = requests.get(url, headers=self._get_headers(), timeout=30)
114
- logger.debug(f"Response status: {response.status_code}")
243
+ # Make the request
244
+ logger.debug(f"Sending GET request to: {url}")
245
+ logger.debug(f"Timeout: 30 seconds")
246
+
247
+ response = self._session.get(url, headers=headers, timeout=30)
248
+
249
+ logger.debug(f"Response received - Status: {response.status_code}")
250
+ logger.debug(f"Response headers: {dict(response.headers)}")
251
+ logger.debug(f"Response encoding: {response.encoding}")
252
+
115
253
  self._handle_response(response)
116
254
 
117
255
  self._site_id = response.json()["id"]
118
- logger.info(f"Retrieved site ID: {self._site_id}")
256
+ logger.info(f"Retrieved site ID: {self._site_id}")
119
257
  return self._site_id
258
+
259
+ except requests.exceptions.ConnectionError as e:
260
+ logger.error(f"✗ ConnectionError getting site ID: {e}", exc_info=True)
261
+ logger.error("This indicates the connection was established but then dropped.")
262
+ logger.error("Running comprehensive diagnostics to identify the exact failure point...")
263
+ logger.error("")
264
+
265
+ # Run diagnostics to help identify the issue
266
+ self._diagnose_connectivity(url)
267
+
268
+ logger.error("")
269
+ logger.error("=" * 70)
270
+ logger.error("DIAGNOSIS COMPLETE")
271
+ logger.error("=" * 70)
272
+ logger.error("")
273
+ logger.error("Most common causes of 'Connection reset by peer':")
274
+ logger.error("")
275
+ logger.error("1. ⚠️ FIREWALL BLOCKING HTTPS/TLS (Most likely based on symptoms)")
276
+ logger.error(" - TCP connection succeeds")
277
+ logger.error(" - Connection drops during TLS handshake")
278
+ logger.error(" - Indicates deep packet inspection (DPI) is active")
279
+ logger.error(" - Solution: Ask network team to whitelist graph.microsoft.us")
280
+ logger.error("")
281
+ logger.error("2. Proxy configuration needed")
282
+ logger.error(" - Set HTTP_PROXY and HTTPS_PROXY environment variables")
283
+ logger.error("")
284
+ logger.error("3. SSL/TLS version or certificate issue")
285
+ logger.error(" - Less likely if TCP connects successfully")
286
+ logger.error("")
287
+ logger.error("=" * 70)
288
+ raise
289
+
290
+ except requests.exceptions.Timeout:
291
+ logger.error(f"✗ Request timeout after 30 seconds", exc_info=True)
292
+ raise
293
+
120
294
  except requests.exceptions.RequestException as e:
121
- logger.error(f"Network error getting site ID: {type(e).__name__}: {e}", exc_info=True)
295
+ logger.error(f"Network error getting site ID: {type(e).__name__}: {e}", exc_info=True)
122
296
  raise
123
297
 
124
298
  def _get_drive_id(self) -> str:
@@ -134,16 +308,24 @@ class GraphAPIClient:
134
308
  url = f"{self.graph_endpoint}/sites/{site_id}/drive"
135
309
 
136
310
  logger.info(f"Fetching drive ID from: {url}")
311
+
137
312
  try:
138
- response = requests.get(url, headers=self._get_headers(), timeout=30)
139
- logger.debug(f"Response status: {response.status_code}")
313
+ logger.debug(f"Sending GET request to: {url}")
314
+ response = self._session.get(url, headers=self._get_headers(), timeout=30)
315
+
316
+ logger.debug(f"Response received - Status: {response.status_code}")
140
317
  self._handle_response(response)
141
318
 
142
319
  self._drive_id = response.json()["id"]
143
- logger.info(f"Retrieved drive ID: {self._drive_id}")
320
+ logger.info(f"Retrieved drive ID: {self._drive_id}")
144
321
  return self._drive_id
322
+
323
+ except requests.exceptions.ConnectionError as e:
324
+ logger.error(f"✗ ConnectionError getting drive ID: {e}", exc_info=True)
325
+ raise
326
+
145
327
  except requests.exceptions.RequestException as e:
146
- logger.error(f"Network error getting drive ID: {type(e).__name__}: {e}", exc_info=True)
328
+ logger.error(f"Network error getting drive ID: {type(e).__name__}: {e}", exc_info=True)
147
329
  raise
148
330
 
149
331
  def list_folders(self, folder_path: str = "") -> List[Dict[str, Any]]:
@@ -169,7 +351,7 @@ class GraphAPIClient:
169
351
 
170
352
  logger.info(f"Fetching folders from: {url}")
171
353
  try:
172
- response = requests.get(url, headers=self._get_headers(), timeout=30)
354
+ response = self._session.get(url, headers=self._get_headers(), timeout=30)
173
355
  logger.debug(f"Response status: {response.status_code}")
174
356
  self._handle_response(response)
175
357
 
@@ -213,7 +395,7 @@ class GraphAPIClient:
213
395
 
214
396
  logger.info(f"Fetching documents from: {url}")
215
397
  try:
216
- response = requests.get(url, headers=self._get_headers(), timeout=30)
398
+ response = self._session.get(url, headers=self._get_headers(), timeout=30)
217
399
  logger.debug(f"Response status: {response.status_code}")
218
400
  self._handle_response(response)
219
401
 
@@ -255,7 +437,7 @@ class GraphAPIClient:
255
437
 
256
438
  logger.info(f"Fetching file content from: {url}")
257
439
  try:
258
- response = requests.get(url, headers=self._get_headers(), timeout=60)
440
+ response = self._session.get(url, headers=self._get_headers(), timeout=60)
259
441
  logger.debug(f"Response status: {response.status_code}")
260
442
  self._handle_response(response)
261
443
 
@@ -294,7 +476,7 @@ class GraphAPIClient:
294
476
  headers["Content-Type"] = "application/octet-stream"
295
477
 
296
478
  try:
297
- response = requests.put(url, headers=headers, data=content, timeout=120)
479
+ response = self._session.put(url, headers=headers, data=content, timeout=120)
298
480
  logger.debug(f"Response status: {response.status_code}")
299
481
  self._handle_response(response)
300
482
 
@@ -320,7 +502,7 @@ class GraphAPIClient:
320
502
 
321
503
  logger.info(f"Deleting from: {url}")
322
504
  try:
323
- response = requests.delete(url, headers=self._get_headers(), timeout=30)
505
+ response = self._session.delete(url, headers=self._get_headers(), timeout=30)
324
506
  logger.debug(f"Response status: {response.status_code}")
325
507
  self._handle_response(response)
326
508
 
@@ -358,7 +540,7 @@ class GraphAPIClient:
358
540
  }
359
541
 
360
542
  try:
361
- response = requests.post(url, headers=self._get_headers(), json=payload, timeout=30)
543
+ response = self._session.post(url, headers=self._get_headers(), json=payload, timeout=30)
362
544
  logger.debug(f"Response status: {response.status_code}")
363
545
  self._handle_response(response)
364
546
 
@@ -384,7 +566,7 @@ class GraphAPIClient:
384
566
 
385
567
  logger.info(f"Deleting folder from: {url}")
386
568
  try:
387
- response = requests.delete(url, headers=self._get_headers(), timeout=30)
569
+ response = self._session.delete(url, headers=self._get_headers(), timeout=30)
388
570
  logger.debug(f"Response status: {response.status_code}")
389
571
  self._handle_response(response)
390
572
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mcp-sharepoint-us
3
- Version: 2.0.14
3
+ Version: 2.0.16
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
@@ -0,0 +1,10 @@
1
+ mcp_sharepoint/__init__.py,sha256=sSJtlX91mBQ4fM12R8XK7Vrkkr3YPJqriE8LZP157vM,20969
2
+ mcp_sharepoint/__main__.py,sha256=4iVDdDZx4rQ4Zo-x0RaCrT-NKeGObIz_ks3YF8di2nA,132
3
+ mcp_sharepoint/auth.py,sha256=fwOCsg1pv0cN26hNlsHhJhGckeDkJCiXZrMmiBn9jf4,18156
4
+ mcp_sharepoint/graph_api.py,sha256=KxKQNafkOJDY312nNMFAUKrsjFIUppMkX--0mFdog9Q,23402
5
+ mcp_sharepoint_us-2.0.16.dist-info/licenses/LICENSE,sha256=SRM8juGH4GjIqnl5rrp-P-S5mW5h2mINOPx5-wOZG6s,1112
6
+ mcp_sharepoint_us-2.0.16.dist-info/METADATA,sha256=qYGUEmc8mv05PW7Ainq-Pp5LB-xWv_fjLpv8Y2ZUbtE,11402
7
+ mcp_sharepoint_us-2.0.16.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
8
+ mcp_sharepoint_us-2.0.16.dist-info/entry_points.txt,sha256=UZOa_7OLI41rmsErbvnSz9RahPMGQVcqZUFMphOcjbY,57
9
+ mcp_sharepoint_us-2.0.16.dist-info/top_level.txt,sha256=R6mRoWe61lz4kUSKGV6S2XVbE7825xfC_J-ouZIYpuo,15
10
+ mcp_sharepoint_us-2.0.16.dist-info/RECORD,,
@@ -1,10 +0,0 @@
1
- mcp_sharepoint/__init__.py,sha256=sSJtlX91mBQ4fM12R8XK7Vrkkr3YPJqriE8LZP157vM,20969
2
- mcp_sharepoint/__main__.py,sha256=4iVDdDZx4rQ4Zo-x0RaCrT-NKeGObIz_ks3YF8di2nA,132
3
- mcp_sharepoint/auth.py,sha256=fwOCsg1pv0cN26hNlsHhJhGckeDkJCiXZrMmiBn9jf4,18156
4
- mcp_sharepoint/graph_api.py,sha256=GNZXKTyTKqSL9t4AAyUSszmhulyJ5cX4e5tlxpYUuYM,14510
5
- mcp_sharepoint_us-2.0.14.dist-info/licenses/LICENSE,sha256=SRM8juGH4GjIqnl5rrp-P-S5mW5h2mINOPx5-wOZG6s,1112
6
- mcp_sharepoint_us-2.0.14.dist-info/METADATA,sha256=reWorSnGr5fY68_KV1OK4kTj2Me8aYBRzM-lgt6Jykw,11402
7
- mcp_sharepoint_us-2.0.14.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
8
- mcp_sharepoint_us-2.0.14.dist-info/entry_points.txt,sha256=UZOa_7OLI41rmsErbvnSz9RahPMGQVcqZUFMphOcjbY,57
9
- mcp_sharepoint_us-2.0.14.dist-info/top_level.txt,sha256=R6mRoWe61lz4kUSKGV6S2XVbE7825xfC_J-ouZIYpuo,15
10
- mcp_sharepoint_us-2.0.14.dist-info/RECORD,,