mcp-sharepoint-us 2.0.14__tar.gz → 2.0.16__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.
- {mcp_sharepoint_us-2.0.14 → mcp_sharepoint_us-2.0.16}/PKG-INFO +1 -1
- {mcp_sharepoint_us-2.0.14 → mcp_sharepoint_us-2.0.16}/pyproject.toml +1 -1
- {mcp_sharepoint_us-2.0.14 → mcp_sharepoint_us-2.0.16}/src/mcp_sharepoint/graph_api.py +197 -15
- {mcp_sharepoint_us-2.0.14 → mcp_sharepoint_us-2.0.16}/src/mcp_sharepoint_us.egg-info/PKG-INFO +1 -1
- {mcp_sharepoint_us-2.0.14 → mcp_sharepoint_us-2.0.16}/LICENSE +0 -0
- {mcp_sharepoint_us-2.0.14 → mcp_sharepoint_us-2.0.16}/README.md +0 -0
- {mcp_sharepoint_us-2.0.14 → mcp_sharepoint_us-2.0.16}/setup.cfg +0 -0
- {mcp_sharepoint_us-2.0.14 → mcp_sharepoint_us-2.0.16}/src/mcp_sharepoint/__init__.py +0 -0
- {mcp_sharepoint_us-2.0.14 → mcp_sharepoint_us-2.0.16}/src/mcp_sharepoint/__main__.py +0 -0
- {mcp_sharepoint_us-2.0.14 → mcp_sharepoint_us-2.0.16}/src/mcp_sharepoint/auth.py +0 -0
- {mcp_sharepoint_us-2.0.14 → mcp_sharepoint_us-2.0.16}/src/mcp_sharepoint_us.egg-info/SOURCES.txt +0 -0
- {mcp_sharepoint_us-2.0.14 → mcp_sharepoint_us-2.0.16}/src/mcp_sharepoint_us.egg-info/dependency_links.txt +0 -0
- {mcp_sharepoint_us-2.0.14 → mcp_sharepoint_us-2.0.16}/src/mcp_sharepoint_us.egg-info/entry_points.txt +0 -0
- {mcp_sharepoint_us-2.0.14 → mcp_sharepoint_us-2.0.16}/src/mcp_sharepoint_us.egg-info/requires.txt +0 -0
- {mcp_sharepoint_us-2.0.14 → mcp_sharepoint_us-2.0.16}/src/mcp_sharepoint_us.egg-info/top_level.txt +0 -0
|
@@ -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
|
-
|
|
114
|
-
logger.debug(f"
|
|
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
|
-
|
|
139
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mcp_sharepoint_us-2.0.14 → mcp_sharepoint_us-2.0.16}/src/mcp_sharepoint_us.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mcp_sharepoint_us-2.0.14 → mcp_sharepoint_us-2.0.16}/src/mcp_sharepoint_us.egg-info/requires.txt
RENAMED
|
File without changes
|
{mcp_sharepoint_us-2.0.14 → mcp_sharepoint_us-2.0.16}/src/mcp_sharepoint_us.egg-info/top_level.txt
RENAMED
|
File without changes
|