d365fo-client 0.2.4__py3-none-any.whl → 0.3.0__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.
- d365fo_client/__init__.py +7 -1
- d365fo_client/auth.py +9 -21
- d365fo_client/cli.py +25 -13
- d365fo_client/client.py +8 -4
- d365fo_client/config.py +52 -30
- d365fo_client/credential_sources.py +5 -0
- d365fo_client/main.py +1 -1
- d365fo_client/mcp/__init__.py +3 -1
- d365fo_client/mcp/auth_server/__init__.py +5 -0
- d365fo_client/mcp/auth_server/auth/__init__.py +30 -0
- d365fo_client/mcp/auth_server/auth/auth.py +372 -0
- d365fo_client/mcp/auth_server/auth/oauth_proxy.py +989 -0
- d365fo_client/mcp/auth_server/auth/providers/__init__.py +0 -0
- d365fo_client/mcp/auth_server/auth/providers/azure.py +325 -0
- d365fo_client/mcp/auth_server/auth/providers/bearer.py +25 -0
- d365fo_client/mcp/auth_server/auth/providers/jwt.py +547 -0
- d365fo_client/mcp/auth_server/auth/redirect_validation.py +65 -0
- d365fo_client/mcp/auth_server/dependencies.py +136 -0
- d365fo_client/mcp/client_manager.py +16 -67
- d365fo_client/mcp/fastmcp_main.py +358 -0
- d365fo_client/mcp/fastmcp_server.py +598 -0
- d365fo_client/mcp/fastmcp_utils.py +431 -0
- d365fo_client/mcp/main.py +40 -13
- d365fo_client/mcp/mixins/__init__.py +24 -0
- d365fo_client/mcp/mixins/base_tools_mixin.py +55 -0
- d365fo_client/mcp/mixins/connection_tools_mixin.py +50 -0
- d365fo_client/mcp/mixins/crud_tools_mixin.py +311 -0
- d365fo_client/mcp/mixins/database_tools_mixin.py +685 -0
- d365fo_client/mcp/mixins/label_tools_mixin.py +87 -0
- d365fo_client/mcp/mixins/metadata_tools_mixin.py +565 -0
- d365fo_client/mcp/mixins/performance_tools_mixin.py +109 -0
- d365fo_client/mcp/mixins/profile_tools_mixin.py +713 -0
- d365fo_client/mcp/mixins/sync_tools_mixin.py +321 -0
- d365fo_client/mcp/prompts/action_execution.py +1 -1
- d365fo_client/mcp/prompts/sequence_analysis.py +1 -1
- d365fo_client/mcp/tools/crud_tools.py +3 -3
- d365fo_client/mcp/tools/sync_tools.py +1 -1
- d365fo_client/mcp/utilities/__init__.py +1 -0
- d365fo_client/mcp/utilities/auth.py +34 -0
- d365fo_client/mcp/utilities/logging.py +58 -0
- d365fo_client/mcp/utilities/types.py +426 -0
- d365fo_client/metadata_v2/sync_manager_v2.py +2 -0
- d365fo_client/metadata_v2/sync_session_manager.py +7 -7
- d365fo_client/models.py +139 -139
- d365fo_client/output.py +2 -2
- d365fo_client/profile_manager.py +62 -27
- d365fo_client/profiles.py +118 -113
- d365fo_client/settings.py +355 -0
- d365fo_client/sync_models.py +85 -2
- d365fo_client/utils.py +2 -1
- {d365fo_client-0.2.4.dist-info → d365fo_client-0.3.0.dist-info}/METADATA +273 -18
- d365fo_client-0.3.0.dist-info/RECORD +84 -0
- d365fo_client-0.3.0.dist-info/entry_points.txt +4 -0
- d365fo_client-0.2.4.dist-info/RECORD +0 -56
- d365fo_client-0.2.4.dist-info/entry_points.txt +0 -3
- {d365fo_client-0.2.4.dist-info → d365fo_client-0.3.0.dist-info}/WHEEL +0 -0
- {d365fo_client-0.2.4.dist-info → d365fo_client-0.3.0.dist-info}/licenses/LICENSE +0 -0
- {d365fo_client-0.2.4.dist-info → d365fo_client-0.3.0.dist-info}/top_level.txt +0 -0
d365fo_client/__init__.py
CHANGED
@@ -172,7 +172,7 @@ from .labels import resolve_labels_generic, resolve_labels_generic_with_cache
|
|
172
172
|
from .main import main
|
173
173
|
|
174
174
|
# MCP Server
|
175
|
-
from .mcp import D365FOClientManager, D365FOMCPServer
|
175
|
+
from .mcp import D365FOClientManager, D365FOMCPServer, FastD365FOMCPServer
|
176
176
|
|
177
177
|
# V2 Metadata Cache (recommended - now the only implementation)
|
178
178
|
from .metadata_v2 import MetadataCacheV2, VersionAwareSearchEngine
|
@@ -195,6 +195,7 @@ from .models import (
|
|
195
195
|
from .output import OutputFormatter
|
196
196
|
from .profile_manager import ProfileManager
|
197
197
|
from .profiles import Profile
|
198
|
+
from .settings import D365FOSettings, get_settings, reset_settings
|
198
199
|
from .utils import (
|
199
200
|
ensure_directory_exists,
|
200
201
|
extract_domain_from_url,
|
@@ -250,11 +251,16 @@ __all__ = [
|
|
250
251
|
"Profile",
|
251
252
|
"ProfileManager",
|
252
253
|
"CLIManager",
|
254
|
+
# Settings
|
255
|
+
"D365FOSettings",
|
256
|
+
"get_settings",
|
257
|
+
"reset_settings",
|
253
258
|
# Legacy aliases
|
254
259
|
"CLIProfile",
|
255
260
|
"EnvironmentProfile",
|
256
261
|
# MCP Server
|
257
262
|
"D365FOMCPServer",
|
263
|
+
"FastD365FOMCPServer",
|
258
264
|
"D365FOClientManager",
|
259
265
|
# Entry point
|
260
266
|
"main",
|
d365fo_client/auth.py
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
"""Authentication utilities for D365 F&O client."""
|
2
2
|
|
3
3
|
from datetime import datetime
|
4
|
-
from typing import Optional
|
4
|
+
from typing import Optional, Union
|
5
5
|
|
6
6
|
from azure.identity import ClientSecretCredential, DefaultAzureCredential
|
7
7
|
|
@@ -22,13 +22,13 @@ class AuthenticationManager:
|
|
22
22
|
self._token = None
|
23
23
|
self._token_expires = None
|
24
24
|
self._credential_manager = CredentialManager()
|
25
|
-
self.credential = None # Will be set by _setup_credentials
|
25
|
+
self.credential: Optional[Union[ClientSecretCredential, DefaultAzureCredential]] = None # Will be set by _setup_credentials
|
26
26
|
|
27
27
|
async def _setup_credentials(self):
|
28
28
|
"""Setup authentication credentials with support for credential sources"""
|
29
29
|
|
30
30
|
# Check if credential source is specified in config
|
31
|
-
credential_source =
|
31
|
+
credential_source = self.config.credential_source
|
32
32
|
|
33
33
|
if credential_source is not None:
|
34
34
|
# Use credential source to get credentials
|
@@ -46,22 +46,8 @@ class AuthenticationManager:
|
|
46
46
|
|
47
47
|
# Fallback to existing logic for backward compatibility
|
48
48
|
|
49
|
-
|
50
|
-
|
51
|
-
and self.config.client_secret
|
52
|
-
and self.config.tenant_id
|
53
|
-
):
|
54
|
-
self.credential = ClientSecretCredential(
|
55
|
-
tenant_id=self.config.tenant_id,
|
56
|
-
client_id=self.config.client_id,
|
57
|
-
client_secret=self.config.client_secret,
|
58
|
-
)
|
59
|
-
elif self.config.use_default_credentials:
|
60
|
-
self.credential = DefaultAzureCredential()
|
61
|
-
else:
|
62
|
-
raise ValueError(
|
63
|
-
"Must provide either use_default_credentials=True, client credentials, or credential_source"
|
64
|
-
)
|
49
|
+
self.credential = DefaultAzureCredential()
|
50
|
+
|
65
51
|
|
66
52
|
async def get_token(self) -> str:
|
67
53
|
"""Get authentication token
|
@@ -77,6 +63,9 @@ class AuthenticationManager:
|
|
77
63
|
if self.credential is None:
|
78
64
|
await self._setup_credentials()
|
79
65
|
|
66
|
+
if self.credential is None:
|
67
|
+
raise ValueError("Authentication credentials are not set up.")
|
68
|
+
|
80
69
|
if (
|
81
70
|
self._token
|
82
71
|
and self._token_expires
|
@@ -86,8 +75,7 @@ class AuthenticationManager:
|
|
86
75
|
|
87
76
|
# Try different scopes
|
88
77
|
scopes_to_try = [
|
89
|
-
f"{self.config.base_url}/.default",
|
90
|
-
f"{self.config.client_id}/.default" if self.config.client_id else None,
|
78
|
+
f"{self.config.base_url.rstrip('/')}/.default",
|
91
79
|
]
|
92
80
|
|
93
81
|
for scope in scopes_to_try:
|
d365fo_client/cli.py
CHANGED
@@ -532,10 +532,13 @@ class CLIManager:
|
|
532
532
|
|
533
533
|
profile_list = []
|
534
534
|
for name, profile in profiles.items():
|
535
|
+
# Determine auth mode based on credential source
|
536
|
+
auth_mode = "default" if profile.credential_source is None else "explicit"
|
537
|
+
|
535
538
|
profile_info = {
|
536
539
|
"name": name,
|
537
540
|
"base_url": profile.base_url,
|
538
|
-
"auth_mode":
|
541
|
+
"auth_mode": auth_mode,
|
539
542
|
"default": (
|
540
543
|
"✓" if default_profile and default_profile.name == name else ""
|
541
544
|
),
|
@@ -563,22 +566,22 @@ class CLIManager:
|
|
563
566
|
return 1
|
564
567
|
|
565
568
|
# Convert profile to dict for display
|
569
|
+
auth_mode = "default" if profile.credential_source is None else "explicit"
|
570
|
+
|
566
571
|
profile_dict = {
|
567
572
|
"name": profile.name,
|
568
573
|
"base_url": profile.base_url,
|
569
|
-
"auth_mode":
|
574
|
+
"auth_mode": auth_mode,
|
570
575
|
"verify_ssl": profile.verify_ssl,
|
571
576
|
"output_format": profile.output_format,
|
572
|
-
"label_cache": profile.
|
573
|
-
"label_expiry": profile.
|
577
|
+
"label_cache": profile.use_label_cache,
|
578
|
+
"label_expiry": profile.label_cache_expiry_minutes,
|
574
579
|
"language": profile.language,
|
575
580
|
}
|
576
581
|
|
577
|
-
# Only show
|
578
|
-
if profile.
|
579
|
-
profile_dict["
|
580
|
-
if profile.tenant_id:
|
581
|
-
profile_dict["tenant_id"] = profile.tenant_id
|
582
|
+
# Only show credential source info if it exists
|
583
|
+
if profile.credential_source:
|
584
|
+
profile_dict["credential_source"] = profile.credential_source.source_type
|
582
585
|
|
583
586
|
output = self.output_formatter.format_output(profile_dict)
|
584
587
|
print(output)
|
@@ -607,14 +610,23 @@ class CLIManager:
|
|
607
610
|
print(format_error_message(f"Profile already exists: {profile_name}"))
|
608
611
|
return 1
|
609
612
|
|
613
|
+
# Handle legacy credential parameters
|
614
|
+
auth_mode = getattr(args, "auth_mode", "default")
|
615
|
+
client_id = getattr(args, "client_id", None)
|
616
|
+
client_secret = getattr(args, "client_secret", None)
|
617
|
+
tenant_id = getattr(args, "tenant_id", None)
|
618
|
+
|
619
|
+
# Create credential source from legacy parameters if needed
|
620
|
+
credential_source = None
|
621
|
+
if auth_mode != "default" and all([client_id, client_secret, tenant_id]):
|
622
|
+
from .credential_sources import EnvironmentCredentialSource
|
623
|
+
credential_source = EnvironmentCredentialSource()
|
624
|
+
|
610
625
|
# Create new profile
|
611
626
|
profile = Profile(
|
612
627
|
name=profile_name,
|
613
628
|
base_url=base_url,
|
614
|
-
|
615
|
-
client_id=getattr(args, "client_id", None),
|
616
|
-
client_secret=getattr(args, "client_secret", None),
|
617
|
-
tenant_id=getattr(args, "tenant_id", None),
|
629
|
+
credential_source=credential_source,
|
618
630
|
verify_ssl=getattr(args, "verify_ssl", True),
|
619
631
|
output_format=getattr(args, "output_format", "table"),
|
620
632
|
use_label_cache=getattr(args, "label_cache", True),
|
d365fo_client/client.py
CHANGED
@@ -5,6 +5,8 @@ import logging
|
|
5
5
|
from pathlib import Path
|
6
6
|
from typing import Any, Dict, List, Optional, Union
|
7
7
|
|
8
|
+
from d365fo_client.utils import get_default_cache_directory
|
9
|
+
|
8
10
|
from .auth import AuthenticationManager
|
9
11
|
from .crud import CrudOperations
|
10
12
|
from .exceptions import FOClientError
|
@@ -72,6 +74,7 @@ class FOClient:
|
|
72
74
|
self.metadata_api_ops = MetadataAPIOperations(
|
73
75
|
self.session_manager, self.metadata_url, self.label_ops
|
74
76
|
)
|
77
|
+
|
75
78
|
|
76
79
|
async def close(self):
|
77
80
|
"""Close the client session"""
|
@@ -94,14 +97,15 @@ class FOClient:
|
|
94
97
|
if not self._metadata_initialized and self.config.enable_metadata_cache:
|
95
98
|
try:
|
96
99
|
|
97
|
-
cache_dir = Path(self.config.metadata_cache_dir)
|
100
|
+
cache_dir = Path(self.config.metadata_cache_dir or get_default_cache_directory())
|
98
101
|
|
99
102
|
# Initialize metadata cache v2
|
100
103
|
self.metadata_cache = MetadataCacheV2(
|
101
104
|
cache_dir, self.config.base_url, self.metadata_api_ops
|
102
105
|
)
|
103
106
|
# Initialize label operations v2 with cache support
|
104
|
-
|
107
|
+
|
108
|
+
self.label_ops.set_label_cache(self.metadata_cache)
|
105
109
|
|
106
110
|
await self.metadata_cache.initialize()
|
107
111
|
|
@@ -349,7 +353,7 @@ class FOClient:
|
|
349
353
|
return False
|
350
354
|
|
351
355
|
# Perform sync using the new sync manager
|
352
|
-
from .
|
356
|
+
from .sync_models import SyncStrategy
|
353
357
|
|
354
358
|
strategy = SyncStrategy.FULL if force_refresh else SyncStrategy.INCREMENTAL
|
355
359
|
|
@@ -922,7 +926,7 @@ class FOClient:
|
|
922
926
|
use_cache_first=use_cache_first,
|
923
927
|
)
|
924
928
|
|
925
|
-
return await resolve_labels_generic(entity, self.label_ops)
|
929
|
+
return await resolve_labels_generic(entity, self.label_ops) #type: ignore
|
926
930
|
|
927
931
|
async def get_all_public_entities_with_details(
|
928
932
|
self, resolve_labels: bool = False, language: str = "en-US"
|
d365fo_client/config.py
CHANGED
@@ -92,7 +92,7 @@ class ConfigManager:
|
|
92
92
|
|
93
93
|
profile_data = profiles[profile_name]
|
94
94
|
try:
|
95
|
-
return Profile.
|
95
|
+
return Profile.create_from_dict(profile_name, profile_data)
|
96
96
|
except Exception as e:
|
97
97
|
logger.error(f"Error loading profile {profile_name}: {e}")
|
98
98
|
return None
|
@@ -141,7 +141,7 @@ class ConfigManager:
|
|
141
141
|
profiles = {}
|
142
142
|
for name, data in self._config_data.get("profiles", {}).items():
|
143
143
|
try:
|
144
|
-
profiles[name] = Profile.
|
144
|
+
profiles[name] = Profile.create_from_dict(name, data)
|
145
145
|
except Exception as e:
|
146
146
|
logger.error(f"Error loading profile {name}: {e}")
|
147
147
|
continue
|
@@ -190,15 +190,19 @@ class ConfigManager:
|
|
190
190
|
# Start with defaults
|
191
191
|
config_params = {
|
192
192
|
"base_url": None,
|
193
|
-
"use_default_credentials": True,
|
194
|
-
"client_id": None,
|
195
|
-
"client_secret": None,
|
196
|
-
"tenant_id": None,
|
197
193
|
"verify_ssl": True,
|
198
194
|
"use_label_cache": True,
|
199
195
|
"label_cache_expiry_minutes": 60,
|
200
196
|
"use_cache_first": True,
|
201
197
|
"timeout": 60,
|
198
|
+
"credential_source": None,
|
199
|
+
}
|
200
|
+
|
201
|
+
# Temporary variables for legacy credential handling
|
202
|
+
legacy_credentials: Dict[str, Optional[str]] = {
|
203
|
+
"client_id": None,
|
204
|
+
"client_secret": None,
|
205
|
+
"tenant_id": None,
|
202
206
|
}
|
203
207
|
|
204
208
|
# Apply profile settings if specified
|
@@ -211,31 +215,36 @@ class ConfigManager:
|
|
211
215
|
profile = self.get_default_profile()
|
212
216
|
|
213
217
|
if profile:
|
218
|
+
# Convert profile to client config and extract relevant parameters
|
219
|
+
client_config = profile.to_client_config()
|
214
220
|
config_params.update(
|
215
221
|
{
|
216
|
-
"base_url":
|
217
|
-
"
|
218
|
-
"
|
219
|
-
"
|
220
|
-
"
|
221
|
-
"
|
222
|
-
"
|
223
|
-
"use_cache_first": profile.use_cache_first,
|
224
|
-
"timeout": profile.timeout,
|
222
|
+
"base_url": client_config.base_url,
|
223
|
+
"verify_ssl": client_config.verify_ssl,
|
224
|
+
"use_label_cache": client_config.use_label_cache,
|
225
|
+
"label_cache_expiry_minutes": client_config.label_cache_expiry_minutes,
|
226
|
+
"use_cache_first": client_config.use_cache_first,
|
227
|
+
"timeout": client_config.timeout,
|
228
|
+
"credential_source": client_config.credential_source,
|
225
229
|
}
|
226
230
|
)
|
227
231
|
|
228
232
|
# Apply environment variables
|
229
233
|
env_mappings = {
|
230
234
|
"D365FO_BASE_URL": "base_url",
|
231
|
-
"D365FO_CLIENT_ID": "client_id",
|
232
|
-
"D365FO_CLIENT_SECRET": "client_secret",
|
233
|
-
"D365FO_TENANT_ID": "tenant_id",
|
234
235
|
"D365FO_VERIFY_SSL": "verify_ssl",
|
235
236
|
"D365FO_LABEL_CACHE": "use_label_cache",
|
236
237
|
"D365FO_LABEL_EXPIRY": "label_cache_expiry_minutes",
|
237
238
|
"D365FO_USE_CACHE_FIRST": "use_cache_first",
|
238
239
|
"D365FO_TIMEOUT": "timeout",
|
240
|
+
"D365FO_CACHE_DIR": "metadata_cache_dir",
|
241
|
+
}
|
242
|
+
|
243
|
+
# Environment variables for legacy credentials
|
244
|
+
legacy_env_mappings = {
|
245
|
+
"D365FO_CLIENT_ID": "client_id",
|
246
|
+
"D365FO_CLIENT_SECRET": "client_secret",
|
247
|
+
"D365FO_TENANT_ID": "tenant_id",
|
239
248
|
}
|
240
249
|
|
241
250
|
for env_var, param_name in env_mappings.items():
|
@@ -258,12 +267,15 @@ class ConfigManager:
|
|
258
267
|
else:
|
259
268
|
config_params[param_name] = env_value
|
260
269
|
|
270
|
+
# Handle legacy credential environment variables
|
271
|
+
for env_var, param_name in legacy_env_mappings.items():
|
272
|
+
env_value = os.getenv(env_var)
|
273
|
+
if env_value:
|
274
|
+
legacy_credentials[param_name] = env_value
|
275
|
+
|
261
276
|
# Apply command line arguments (highest precedence)
|
262
277
|
arg_mappings = {
|
263
278
|
"base_url": "base_url",
|
264
|
-
"client_id": "client_id",
|
265
|
-
"client_secret": "client_secret",
|
266
|
-
"tenant_id": "tenant_id",
|
267
279
|
"verify_ssl": "verify_ssl",
|
268
280
|
"label_cache": "use_label_cache",
|
269
281
|
"label_expiry": "label_cache_expiry_minutes",
|
@@ -271,20 +283,30 @@ class ConfigManager:
|
|
271
283
|
"timeout": "timeout",
|
272
284
|
}
|
273
285
|
|
286
|
+
# Legacy credential CLI args
|
287
|
+
legacy_arg_mappings = {
|
288
|
+
"client_id": "client_id",
|
289
|
+
"client_secret": "client_secret",
|
290
|
+
"tenant_id": "tenant_id",
|
291
|
+
}
|
292
|
+
|
274
293
|
for arg_name, param_name in arg_mappings.items():
|
275
294
|
arg_value = getattr(args, arg_name, None)
|
276
295
|
if arg_value is not None:
|
277
296
|
config_params[param_name] = arg_value
|
278
297
|
|
279
|
-
#
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
):
|
287
|
-
|
298
|
+
# Handle legacy credential CLI arguments
|
299
|
+
for arg_name, param_name in legacy_arg_mappings.items():
|
300
|
+
arg_value = getattr(args, arg_name, None)
|
301
|
+
if arg_value is not None:
|
302
|
+
legacy_credentials[param_name] = arg_value
|
303
|
+
|
304
|
+
# Create credential source from legacy credentials if provided
|
305
|
+
if any(legacy_credentials.values()) and config_params["credential_source"] is None:
|
306
|
+
# Check if we have all required credentials
|
307
|
+
if all(legacy_credentials.values()):
|
308
|
+
from .credential_sources import EnvironmentCredentialSource
|
309
|
+
config_params["credential_source"] = EnvironmentCredentialSource()
|
288
310
|
|
289
311
|
return FOClientConfig(**config_params)
|
290
312
|
|
@@ -210,6 +210,8 @@ class EnvironmentCredentialProvider(CredentialProvider):
|
|
210
210
|
raise ValueError(f"Missing required environment variables: {', '.join(missing_vars)}")
|
211
211
|
|
212
212
|
logger.debug(f"Retrieved credentials from environment variables: {source.client_id_var}, {source.client_secret_var}, {source.tenant_id_var}")
|
213
|
+
assert client_id and client_secret and tenant_id # For type checker
|
214
|
+
|
213
215
|
return client_id, client_secret, tenant_id
|
214
216
|
|
215
217
|
|
@@ -252,6 +254,7 @@ class KeyVaultCredentialProvider(CredentialProvider):
|
|
252
254
|
raise ValueError("One or more secrets retrieved from Key Vault are empty")
|
253
255
|
|
254
256
|
logger.debug(f"Retrieved credentials from Key Vault: {source.vault_url}")
|
257
|
+
assert client_id and client_secret and tenant_id # For type checker
|
255
258
|
return client_id, client_secret, tenant_id
|
256
259
|
|
257
260
|
except Exception as e:
|
@@ -282,6 +285,8 @@ class KeyVaultCredentialProvider(CredentialProvider):
|
|
282
285
|
if not all([source.keyvault_client_id, source.keyvault_client_secret, source.keyvault_tenant_id]):
|
283
286
|
raise ValueError("Key Vault client_secret authentication requires keyvault_client_id, keyvault_client_secret, and keyvault_tenant_id")
|
284
287
|
|
288
|
+
assert source.keyvault_client_id and source.keyvault_client_secret and source.keyvault_tenant_id # For type checker
|
289
|
+
|
285
290
|
credential = ClientSecretCredential(
|
286
291
|
tenant_id=source.keyvault_tenant_id,
|
287
292
|
client_id=source.keyvault_client_id,
|
d365fo_client/main.py
CHANGED
@@ -12,7 +12,7 @@ async def example_usage():
|
|
12
12
|
"""Example usage of the F&O client with label functionality"""
|
13
13
|
config = FOClientConfig(
|
14
14
|
base_url="https://usnconeboxax1aos.cloud.onebox.dynamics.com",
|
15
|
-
|
15
|
+
|
16
16
|
verify_ssl=False,
|
17
17
|
use_label_cache=True,
|
18
18
|
label_cache_expiry_minutes=60,
|
d365fo_client/mcp/__init__.py
CHANGED
@@ -8,9 +8,11 @@ integration workflows through standardized MCP protocol.
|
|
8
8
|
"""
|
9
9
|
|
10
10
|
from .client_manager import D365FOClientManager
|
11
|
+
from .fastmcp_server import FastD365FOMCPServer
|
11
12
|
from .server import D365FOMCPServer
|
12
13
|
|
13
14
|
__all__ = [
|
14
|
-
"D365FOMCPServer",
|
15
|
+
"D365FOMCPServer", # Legacy MCP server (backward compatibility)
|
16
|
+
"FastD365FOMCPServer", # New FastMCP server with multi-transport support
|
15
17
|
"D365FOClientManager",
|
16
18
|
]
|
@@ -0,0 +1,30 @@
|
|
1
|
+
from .auth import (
|
2
|
+
OAuthProvider,
|
3
|
+
TokenVerifier,
|
4
|
+
RemoteAuthProvider,
|
5
|
+
AccessToken,
|
6
|
+
AuthProvider,
|
7
|
+
)
|
8
|
+
from .providers.jwt import JWTVerifier, StaticTokenVerifier
|
9
|
+
from .oauth_proxy import OAuthProxy
|
10
|
+
|
11
|
+
|
12
|
+
__all__ = [
|
13
|
+
"AuthProvider",
|
14
|
+
"OAuthProvider",
|
15
|
+
"TokenVerifier",
|
16
|
+
"JWTVerifier",
|
17
|
+
"StaticTokenVerifier",
|
18
|
+
"RemoteAuthProvider",
|
19
|
+
"AccessToken",
|
20
|
+
"OAuthProxy",
|
21
|
+
]
|
22
|
+
|
23
|
+
|
24
|
+
def __getattr__(name: str):
|
25
|
+
# Defer import because it raises a deprecation warning
|
26
|
+
if name == "BearerAuthProvider":
|
27
|
+
from .providers.bearer import BearerAuthProvider
|
28
|
+
|
29
|
+
return BearerAuthProvider
|
30
|
+
raise AttributeError(f"module '{__name__}' has no attribute '{name}'")
|