d365fo-client 0.2.2__py3-none-any.whl → 0.2.4__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/auth.py +48 -9
- d365fo_client/client.py +40 -20
- d365fo_client/credential_sources.py +431 -0
- d365fo_client/mcp/client_manager.py +8 -0
- d365fo_client/mcp/main.py +39 -17
- d365fo_client/mcp/server.py +69 -22
- d365fo_client/mcp/tools/__init__.py +2 -0
- d365fo_client/mcp/tools/profile_tools.py +261 -2
- d365fo_client/mcp/tools/sync_tools.py +503 -0
- d365fo_client/metadata_api.py +67 -0
- d365fo_client/metadata_v2/cache_v2.py +11 -9
- d365fo_client/metadata_v2/global_version_manager.py +2 -4
- d365fo_client/metadata_v2/sync_manager_v2.py +1 -1
- d365fo_client/metadata_v2/sync_session_manager.py +1043 -0
- d365fo_client/models.py +22 -3
- d365fo_client/profile_manager.py +7 -1
- d365fo_client/profiles.py +28 -1
- d365fo_client/sync_models.py +181 -0
- {d365fo_client-0.2.2.dist-info → d365fo_client-0.2.4.dist-info}/METADATA +1011 -784
- {d365fo_client-0.2.2.dist-info → d365fo_client-0.2.4.dist-info}/RECORD +24 -20
- {d365fo_client-0.2.2.dist-info → d365fo_client-0.2.4.dist-info}/WHEEL +0 -0
- {d365fo_client-0.2.2.dist-info → d365fo_client-0.2.4.dist-info}/entry_points.txt +0 -0
- {d365fo_client-0.2.2.dist-info → d365fo_client-0.2.4.dist-info}/licenses/LICENSE +0 -0
- {d365fo_client-0.2.2.dist-info → d365fo_client-0.2.4.dist-info}/top_level.txt +0 -0
d365fo_client/mcp/main.py
CHANGED
@@ -43,28 +43,50 @@ def setup_logging(level: str = "INFO") -> None:
|
|
43
43
|
|
44
44
|
def load_config() -> Dict[str, Any]:
|
45
45
|
"""Load configuration from environment and config files.
|
46
|
+
|
47
|
+
Handles three startup scenarios:
|
48
|
+
1. No environment variables: Profile-only mode
|
49
|
+
2. D365FO_BASE_URL only: Default auth mode
|
50
|
+
3. Full variables: Client credentials mode
|
46
51
|
|
47
52
|
Returns:
|
48
|
-
Configuration dictionary
|
53
|
+
Configuration dictionary with startup_mode indicator
|
49
54
|
"""
|
50
55
|
config = {}
|
51
|
-
|
52
|
-
#
|
53
|
-
|
56
|
+
|
57
|
+
# Get environment variables
|
58
|
+
base_url = os.getenv("D365FO_BASE_URL")
|
59
|
+
client_id = os.getenv("D365FO_CLIENT_ID")
|
60
|
+
client_secret = os.getenv("D365FO_CLIENT_SECRET")
|
61
|
+
tenant_id = os.getenv("D365FO_TENANT_ID")
|
62
|
+
|
63
|
+
# Determine startup mode based on available environment variables
|
64
|
+
if not base_url:
|
65
|
+
# Scenario 1: No environment variables - profile-only mode
|
66
|
+
config["startup_mode"] = "profile_only"
|
67
|
+
config["has_base_url"] = False
|
68
|
+
logging.info("Startup mode: profile-only (no D365FO_BASE_URL provided)")
|
69
|
+
|
70
|
+
elif base_url and not (client_id and client_secret and tenant_id):
|
71
|
+
# Scenario 2: Only base URL - default authentication
|
72
|
+
config["startup_mode"] = "default_auth"
|
73
|
+
config["has_base_url"] = True
|
54
74
|
config.setdefault("default_environment", {})["base_url"] = base_url
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
config
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
75
|
+
config["default_environment"]["use_default_credentials"] = True
|
76
|
+
logging.info("Startup mode: default authentication (D365FO_BASE_URL provided)")
|
77
|
+
|
78
|
+
else:
|
79
|
+
# Scenario 3: Full credentials - client credentials authentication
|
80
|
+
config["startup_mode"] = "client_credentials"
|
81
|
+
config["has_base_url"] = True
|
82
|
+
config.setdefault("default_environment", {}).update({
|
83
|
+
"base_url": base_url,
|
84
|
+
"client_id": client_id,
|
85
|
+
"client_secret": client_secret,
|
86
|
+
"tenant_id": tenant_id,
|
87
|
+
"use_default_credentials": False
|
88
|
+
})
|
89
|
+
logging.info("Startup mode: client credentials (full D365FO environment variables provided)")
|
68
90
|
|
69
91
|
return config
|
70
92
|
|
d365fo_client/mcp/server.py
CHANGED
@@ -8,6 +8,8 @@ from typing import Any, Dict, List, Optional
|
|
8
8
|
from mcp import GetPromptResult, Resource, Tool
|
9
9
|
from mcp.server import InitializationOptions, Server
|
10
10
|
|
11
|
+
from d365fo_client.credential_sources import CredentialSource, EnvironmentCredentialSource
|
12
|
+
|
11
13
|
from .. import __version__
|
12
14
|
from ..profile_manager import ProfileManager
|
13
15
|
from mcp.server.lowlevel.server import NotificationOptions
|
@@ -23,7 +25,7 @@ from .resources import (
|
|
23
25
|
MetadataResourceHandler,
|
24
26
|
QueryResourceHandler,
|
25
27
|
)
|
26
|
-
from .tools import ConnectionTools, CrudTools, DatabaseTools, LabelTools, MetadataTools, ProfileTools
|
28
|
+
from .tools import ConnectionTools, CrudTools, DatabaseTools, LabelTools, MetadataTools, ProfileTools, SyncTools
|
27
29
|
|
28
30
|
logger = logging.getLogger(__name__)
|
29
31
|
|
@@ -56,6 +58,7 @@ class D365FOMCPServer:
|
|
56
58
|
self.label_tools = LabelTools(self.client_manager)
|
57
59
|
self.profile_tools = ProfileTools(self.client_manager)
|
58
60
|
self.database_tools = DatabaseTools(self.client_manager)
|
61
|
+
self.sync_tools = SyncTools(self.client_manager)
|
59
62
|
|
60
63
|
# Tool registry for execution
|
61
64
|
self.tool_registry = {}
|
@@ -215,6 +218,10 @@ class D365FOMCPServer:
|
|
215
218
|
database_tools = self.database_tools.get_tools()
|
216
219
|
tools.extend(database_tools)
|
217
220
|
|
221
|
+
# Add sync tools
|
222
|
+
sync_tools = self.sync_tools.get_tools()
|
223
|
+
tools.extend(sync_tools)
|
224
|
+
|
218
225
|
# Register tools for execution
|
219
226
|
for tool in tools:
|
220
227
|
self.tool_registry[tool.name] = tool
|
@@ -310,6 +317,16 @@ class D365FOMCPServer:
|
|
310
317
|
return await self.database_tools.execute_get_table_info(arguments)
|
311
318
|
elif name == "d365fo_get_database_statistics":
|
312
319
|
return await self.database_tools.execute_get_database_statistics(arguments)
|
320
|
+
elif name == "d365fo_start_sync":
|
321
|
+
return await self.sync_tools.execute_start_sync(arguments)
|
322
|
+
elif name == "d365fo_get_sync_progress":
|
323
|
+
return await self.sync_tools.execute_get_sync_progress(arguments)
|
324
|
+
elif name == "d365fo_cancel_sync":
|
325
|
+
return await self.sync_tools.execute_cancel_sync(arguments)
|
326
|
+
elif name == "d365fo_list_sync_sessions":
|
327
|
+
return await self.sync_tools.execute_list_sync_sessions(arguments)
|
328
|
+
elif name == "d365fo_get_sync_history":
|
329
|
+
return await self.sync_tools.execute_get_sync_history(arguments)
|
313
330
|
else:
|
314
331
|
raise ValueError(f"Unknown tool: {name}")
|
315
332
|
|
@@ -358,24 +375,34 @@ class D365FOMCPServer:
|
|
358
375
|
async def _startup_initialization(self):
|
359
376
|
"""Perform startup initialization based on configuration."""
|
360
377
|
try:
|
361
|
-
|
362
|
-
has_base_url = self.config.get("has_base_url", False)
|
378
|
+
startup_mode = self.config.get("startup_mode", "profile_only")
|
363
379
|
|
364
|
-
if
|
365
|
-
logger.info("
|
380
|
+
if startup_mode == "profile_only":
|
381
|
+
logger.info("Server started in profile-only mode")
|
382
|
+
logger.info("No environment variables configured - use profile management tools to configure D365FO connections")
|
383
|
+
|
384
|
+
elif startup_mode == "default_auth":
|
385
|
+
logger.info("Server started with default authentication mode")
|
386
|
+
logger.info("D365FO_BASE_URL configured - performing health checks and creating default profile with default auth")
|
366
387
|
|
367
|
-
# Perform health checks
|
388
|
+
# Perform health checks and create default profile
|
368
389
|
await self._startup_health_checks()
|
390
|
+
await self._create_default_profile_if_needed()
|
391
|
+
|
392
|
+
elif startup_mode == "client_credentials":
|
393
|
+
logger.info("Server started with client credentials authentication mode")
|
394
|
+
logger.info("Full D365FO environment variables configured - performing health checks and creating default profile with client credentials")
|
369
395
|
|
370
|
-
#
|
396
|
+
# Perform health checks and create default profile
|
397
|
+
await self._startup_health_checks()
|
371
398
|
await self._create_default_profile_if_needed()
|
399
|
+
|
372
400
|
else:
|
373
|
-
logger.
|
374
|
-
logger.info("Use profile management tools to configure D365FO connections")
|
401
|
+
logger.warning(f"Unknown startup mode: {startup_mode}")
|
375
402
|
|
376
403
|
except Exception as e:
|
377
404
|
logger.error(f"Startup initialization failed: {e}")
|
378
|
-
# Don't fail startup on initialization failures
|
405
|
+
# Don't fail startup on initialization failures - allow server to start in profile-only mode
|
379
406
|
|
380
407
|
async def _create_default_profile_if_needed(self):
|
381
408
|
"""Create a default profile from environment variables if needed."""
|
@@ -386,20 +413,30 @@ class D365FOMCPServer:
|
|
386
413
|
logger.info(f"Default profile already exists: {existing_default.name}")
|
387
414
|
return
|
388
415
|
|
389
|
-
# Get environment variables
|
416
|
+
# Get environment variables with correct names
|
390
417
|
base_url = os.getenv("D365FO_BASE_URL")
|
391
|
-
client_id = os.getenv("
|
392
|
-
client_secret = os.getenv("
|
393
|
-
tenant_id = os.getenv("
|
418
|
+
client_id = os.getenv("D365FO_CLIENT_ID")
|
419
|
+
client_secret = os.getenv("D365FO_CLIENT_SECRET")
|
420
|
+
tenant_id = os.getenv("D365FO_TENANT_ID")
|
394
421
|
|
395
422
|
if not base_url:
|
396
423
|
logger.warning("Cannot create default profile - D365FO_BASE_URL not set")
|
397
424
|
return
|
398
425
|
|
399
|
-
# Determine authentication mode
|
400
|
-
|
401
|
-
|
426
|
+
# Determine authentication mode based on startup mode
|
427
|
+
startup_mode = self.config.get("startup_mode", "profile_only")
|
428
|
+
|
429
|
+
if startup_mode == "client_credentials":
|
402
430
|
auth_mode = "client_credentials"
|
431
|
+
if not all([client_id, client_secret, tenant_id]):
|
432
|
+
logger.error("Client credentials mode requires D365FO_CLIENT_ID, D365FO_CLIENT_SECRET, and D365FO_TENANT_ID")
|
433
|
+
return
|
434
|
+
else:
|
435
|
+
auth_mode = "default"
|
436
|
+
# Clear client credentials for default auth mode
|
437
|
+
client_id = None
|
438
|
+
client_secret = None
|
439
|
+
tenant_id = None
|
403
440
|
|
404
441
|
# Create default profile with unique name
|
405
442
|
profile_name = "default-from-env"
|
@@ -411,17 +448,22 @@ class D365FOMCPServer:
|
|
411
448
|
self.profile_manager.set_default_profile(profile_name)
|
412
449
|
return
|
413
450
|
|
451
|
+
credential_source = None
|
452
|
+
if startup_mode == "client_credentials":
|
453
|
+
credential_source = EnvironmentCredentialSource()
|
454
|
+
|
414
455
|
success = self.profile_manager.create_profile(
|
415
456
|
name=profile_name,
|
416
457
|
base_url=base_url,
|
417
458
|
auth_mode=auth_mode,
|
418
|
-
client_id=
|
419
|
-
client_secret=
|
420
|
-
tenant_id=
|
421
|
-
description="Auto-created from environment variables at startup",
|
459
|
+
client_id=None, #use from env var
|
460
|
+
client_secret=None, #use from env var
|
461
|
+
tenant_id=None, #use from env var
|
462
|
+
description=f"Auto-created from environment variables at startup (mode: {startup_mode})",
|
422
463
|
use_label_cache=True,
|
423
464
|
timeout=60,
|
424
|
-
verify_ssl=True
|
465
|
+
verify_ssl=True,
|
466
|
+
credential_source=credential_source
|
425
467
|
)
|
426
468
|
|
427
469
|
if success:
|
@@ -430,6 +472,11 @@ class D365FOMCPServer:
|
|
430
472
|
logger.info(f"Created and set default profile: {profile_name}")
|
431
473
|
logger.info(f"Profile configured for: {base_url}")
|
432
474
|
logger.info(f"Authentication mode: {auth_mode}")
|
475
|
+
|
476
|
+
if auth_mode == "client_credentials":
|
477
|
+
logger.info(f"Client ID: {client_id}")
|
478
|
+
logger.info(f"Tenant ID: {tenant_id}")
|
479
|
+
|
433
480
|
else:
|
434
481
|
logger.warning(f"Failed to create default profile: {profile_name}")
|
435
482
|
|
@@ -6,6 +6,7 @@ from .database_tools import DatabaseTools
|
|
6
6
|
from .label_tools import LabelTools
|
7
7
|
from .metadata_tools import MetadataTools
|
8
8
|
from .profile_tools import ProfileTools
|
9
|
+
from .sync_tools import SyncTools
|
9
10
|
|
10
11
|
__all__ = [
|
11
12
|
"ConnectionTools",
|
@@ -14,4 +15,5 @@ __all__ = [
|
|
14
15
|
"LabelTools",
|
15
16
|
"ProfileTools",
|
16
17
|
"DatabaseTools",
|
18
|
+
"SyncTools",
|
17
19
|
]
|
@@ -96,6 +96,81 @@ class ProfileTools:
|
|
96
96
|
"type": "string",
|
97
97
|
"description": "Azure tenant ID (for client_credentials auth)",
|
98
98
|
},
|
99
|
+
"credentialSource": {
|
100
|
+
"type": "object",
|
101
|
+
"description": "Credential source configuration",
|
102
|
+
"properties": {
|
103
|
+
"sourceType": {
|
104
|
+
"type": "string",
|
105
|
+
"description": "Type of credential source",
|
106
|
+
"enum": ["environment", "keyvault"]
|
107
|
+
},
|
108
|
+
"clientIdVar": {
|
109
|
+
"type": "string",
|
110
|
+
"description": "Environment variable name for client ID (environment source)",
|
111
|
+
"default": "D365FO_CLIENT_ID"
|
112
|
+
},
|
113
|
+
"clientSecretVar": {
|
114
|
+
"type": "string",
|
115
|
+
"description": "Environment variable name for client secret (environment source)",
|
116
|
+
"default": "D365FO_CLIENT_SECRET"
|
117
|
+
},
|
118
|
+
"tenantIdVar": {
|
119
|
+
"type": "string",
|
120
|
+
"description": "Environment variable name for tenant ID (environment source)",
|
121
|
+
"default": "D365FO_TENANT_ID"
|
122
|
+
},
|
123
|
+
"vaultUrl": {
|
124
|
+
"type": "string",
|
125
|
+
"description": "Azure Key Vault URL (keyvault source)"
|
126
|
+
},
|
127
|
+
"clientIdSecretName": {
|
128
|
+
"type": "string",
|
129
|
+
"description": "Key Vault secret name for client ID (keyvault source)",
|
130
|
+
"default": "D365FO_CLIENT_ID"
|
131
|
+
},
|
132
|
+
"clientSecretSecretName": {
|
133
|
+
"type": "string",
|
134
|
+
"description": "Key Vault secret name for client secret (keyvault source)",
|
135
|
+
"default": "D365FO_CLIENT_SECRET"
|
136
|
+
},
|
137
|
+
"tenantIdSecretName": {
|
138
|
+
"type": "string",
|
139
|
+
"description": "Key Vault secret name for tenant ID (keyvault source)",
|
140
|
+
"default": "D365FO_TENANT_ID"
|
141
|
+
},
|
142
|
+
"keyvaultAuthMode": {
|
143
|
+
"type": "string",
|
144
|
+
"description": "Key Vault authentication mode",
|
145
|
+
"enum": ["default", "client_secret"],
|
146
|
+
"default": "default"
|
147
|
+
},
|
148
|
+
"keyvaultClientId": {
|
149
|
+
"type": "string",
|
150
|
+
"description": "Client ID for Key Vault authentication (client_secret mode)"
|
151
|
+
},
|
152
|
+
"keyvaultClientSecret": {
|
153
|
+
"type": "string",
|
154
|
+
"description": "Client secret for Key Vault authentication (client_secret mode)"
|
155
|
+
},
|
156
|
+
"keyvaultTenantId": {
|
157
|
+
"type": "string",
|
158
|
+
"description": "Tenant ID for Key Vault authentication (client_secret mode)"
|
159
|
+
}
|
160
|
+
},
|
161
|
+
"required": ["sourceType"],
|
162
|
+
"anyOf": [
|
163
|
+
{
|
164
|
+
"properties": {"sourceType": {"const": "environment"}},
|
165
|
+
"additionalProperties": True
|
166
|
+
},
|
167
|
+
{
|
168
|
+
"properties": {"sourceType": {"const": "keyvault"}},
|
169
|
+
"required": ["vaultUrl"],
|
170
|
+
"additionalProperties": True
|
171
|
+
}
|
172
|
+
]
|
173
|
+
},
|
99
174
|
"verifySsl": {
|
100
175
|
"type": "boolean",
|
101
176
|
"description": "Whether to verify SSL certificates",
|
@@ -163,6 +238,81 @@ class ProfileTools:
|
|
163
238
|
"description": "Azure client secret",
|
164
239
|
},
|
165
240
|
"tenantId": {"type": "string", "description": "Azure tenant ID"},
|
241
|
+
"credentialSource": {
|
242
|
+
"type": "object",
|
243
|
+
"description": "Credential source configuration",
|
244
|
+
"properties": {
|
245
|
+
"sourceType": {
|
246
|
+
"type": "string",
|
247
|
+
"description": "Type of credential source",
|
248
|
+
"enum": ["environment", "keyvault"]
|
249
|
+
},
|
250
|
+
"clientIdVar": {
|
251
|
+
"type": "string",
|
252
|
+
"description": "Environment variable name for client ID (environment source)",
|
253
|
+
"default": "D365FO_CLIENT_ID"
|
254
|
+
},
|
255
|
+
"clientSecretVar": {
|
256
|
+
"type": "string",
|
257
|
+
"description": "Environment variable name for client secret (environment source)",
|
258
|
+
"default": "D365FO_CLIENT_SECRET"
|
259
|
+
},
|
260
|
+
"tenantIdVar": {
|
261
|
+
"type": "string",
|
262
|
+
"description": "Environment variable name for tenant ID (environment source)",
|
263
|
+
"default": "D365FO_TENANT_ID"
|
264
|
+
},
|
265
|
+
"vaultUrl": {
|
266
|
+
"type": "string",
|
267
|
+
"description": "Azure Key Vault URL (keyvault source)"
|
268
|
+
},
|
269
|
+
"clientIdSecretName": {
|
270
|
+
"type": "string",
|
271
|
+
"description": "Key Vault secret name for client ID (keyvault source)",
|
272
|
+
"default": "D365FO_CLIENT_ID"
|
273
|
+
},
|
274
|
+
"clientSecretSecretName": {
|
275
|
+
"type": "string",
|
276
|
+
"description": "Key Vault secret name for client secret (keyvault source)",
|
277
|
+
"default": "D365FO_CLIENT_SECRET"
|
278
|
+
},
|
279
|
+
"tenantIdSecretName": {
|
280
|
+
"type": "string",
|
281
|
+
"description": "Key Vault secret name for tenant ID (keyvault source)",
|
282
|
+
"default": "D365FO_TENANT_ID"
|
283
|
+
},
|
284
|
+
"keyvaultAuthMode": {
|
285
|
+
"type": "string",
|
286
|
+
"description": "Key Vault authentication mode",
|
287
|
+
"enum": ["default", "client_secret"],
|
288
|
+
"default": "default"
|
289
|
+
},
|
290
|
+
"keyvaultClientId": {
|
291
|
+
"type": "string",
|
292
|
+
"description": "Client ID for Key Vault authentication (client_secret mode)"
|
293
|
+
},
|
294
|
+
"keyvaultClientSecret": {
|
295
|
+
"type": "string",
|
296
|
+
"description": "Client secret for Key Vault authentication (client_secret mode)"
|
297
|
+
},
|
298
|
+
"keyvaultTenantId": {
|
299
|
+
"type": "string",
|
300
|
+
"description": "Tenant ID for Key Vault authentication (client_secret mode)"
|
301
|
+
}
|
302
|
+
},
|
303
|
+
"required": ["sourceType"],
|
304
|
+
"anyOf": [
|
305
|
+
{
|
306
|
+
"properties": {"sourceType": {"const": "environment"}},
|
307
|
+
"additionalProperties": True
|
308
|
+
},
|
309
|
+
{
|
310
|
+
"properties": {"sourceType": {"const": "keyvault"}},
|
311
|
+
"required": ["vaultUrl"],
|
312
|
+
"additionalProperties": True
|
313
|
+
}
|
314
|
+
]
|
315
|
+
},
|
166
316
|
"verifySsl": {
|
167
317
|
"type": "boolean",
|
168
318
|
"description": "Whether to verify SSL certificates",
|
@@ -299,6 +449,11 @@ class ProfileTools:
|
|
299
449
|
"isDefault": default_profile and default_profile.name == name,
|
300
450
|
"description": profile.description,
|
301
451
|
}
|
452
|
+
|
453
|
+
# Add credential source type if available
|
454
|
+
if profile.credential_source:
|
455
|
+
profile_info["credentialSourceType"] = profile.credential_source.source_type
|
456
|
+
|
302
457
|
profile_list.append(profile_info)
|
303
458
|
|
304
459
|
response = {
|
@@ -356,6 +511,37 @@ class ProfileTools:
|
|
356
511
|
if profile.tenant_id:
|
357
512
|
profile_dict["tenantId"] = profile.tenant_id
|
358
513
|
|
514
|
+
# Add credential source information if available
|
515
|
+
if profile.credential_source:
|
516
|
+
cred_source_dict = profile.credential_source.to_dict()
|
517
|
+
# Convert to camelCase for JSON response
|
518
|
+
credential_source = {
|
519
|
+
"sourceType": cred_source_dict.get("source_type"),
|
520
|
+
}
|
521
|
+
|
522
|
+
if cred_source_dict.get("source_type") == "environment":
|
523
|
+
credential_source.update({
|
524
|
+
"clientIdVar": cred_source_dict.get("client_id_var"),
|
525
|
+
"clientSecretVar": cred_source_dict.get("client_secret_var"),
|
526
|
+
"tenantIdVar": cred_source_dict.get("tenant_id_var"),
|
527
|
+
})
|
528
|
+
elif cred_source_dict.get("source_type") == "keyvault":
|
529
|
+
credential_source.update({
|
530
|
+
"vaultUrl": cred_source_dict.get("vault_url"),
|
531
|
+
"clientIdSecretName": cred_source_dict.get("client_id_secret_name"),
|
532
|
+
"clientSecretSecretName": cred_source_dict.get("client_secret_secret_name"),
|
533
|
+
"tenantIdSecretName": cred_source_dict.get("tenant_id_secret_name"),
|
534
|
+
"keyvaultAuthMode": cred_source_dict.get("keyvault_auth_mode"),
|
535
|
+
})
|
536
|
+
# Only include auth details if using client_secret mode
|
537
|
+
if cred_source_dict.get("keyvault_auth_mode") == "client_secret":
|
538
|
+
credential_source.update({
|
539
|
+
"keyvaultClientId": cred_source_dict.get("keyvault_client_id"),
|
540
|
+
"keyvaultTenantId": cred_source_dict.get("keyvault_tenant_id"),
|
541
|
+
})
|
542
|
+
|
543
|
+
profile_dict["credentialSource"] = credential_source
|
544
|
+
|
359
545
|
return [TextContent(type="text", text=json.dumps(profile_dict, indent=2))]
|
360
546
|
|
361
547
|
except Exception as e:
|
@@ -393,6 +579,42 @@ class ProfileTools:
|
|
393
579
|
description = arguments.get("description")
|
394
580
|
set_as_default = arguments.get("setAsDefault", False)
|
395
581
|
|
582
|
+
# Handle credential source
|
583
|
+
credential_source = None
|
584
|
+
if "credentialSource" in arguments:
|
585
|
+
from ...credential_sources import create_credential_source
|
586
|
+
cred_source_data = arguments["credentialSource"]
|
587
|
+
|
588
|
+
# Convert camelCase to snake_case for the factory function
|
589
|
+
source_type = cred_source_data["sourceType"]
|
590
|
+
kwargs = {}
|
591
|
+
|
592
|
+
if source_type == "environment":
|
593
|
+
if "clientIdVar" in cred_source_data:
|
594
|
+
kwargs["client_id_var"] = cred_source_data["clientIdVar"]
|
595
|
+
if "clientSecretVar" in cred_source_data:
|
596
|
+
kwargs["client_secret_var"] = cred_source_data["clientSecretVar"]
|
597
|
+
if "tenantIdVar" in cred_source_data:
|
598
|
+
kwargs["tenant_id_var"] = cred_source_data["tenantIdVar"]
|
599
|
+
elif source_type == "keyvault":
|
600
|
+
kwargs["vault_url"] = cred_source_data["vaultUrl"]
|
601
|
+
if "clientIdSecretName" in cred_source_data:
|
602
|
+
kwargs["client_id_secret_name"] = cred_source_data["clientIdSecretName"]
|
603
|
+
if "clientSecretSecretName" in cred_source_data:
|
604
|
+
kwargs["client_secret_secret_name"] = cred_source_data["clientSecretSecretName"]
|
605
|
+
if "tenantIdSecretName" in cred_source_data:
|
606
|
+
kwargs["tenant_id_secret_name"] = cred_source_data["tenantIdSecretName"]
|
607
|
+
if "keyvaultAuthMode" in cred_source_data:
|
608
|
+
kwargs["keyvault_auth_mode"] = cred_source_data["keyvaultAuthMode"]
|
609
|
+
if "keyvaultClientId" in cred_source_data:
|
610
|
+
kwargs["keyvault_client_id"] = cred_source_data["keyvaultClientId"]
|
611
|
+
if "keyvaultClientSecret" in cred_source_data:
|
612
|
+
kwargs["keyvault_client_secret"] = cred_source_data["keyvaultClientSecret"]
|
613
|
+
if "keyvaultTenantId" in cred_source_data:
|
614
|
+
kwargs["keyvault_tenant_id"] = cred_source_data["keyvaultTenantId"]
|
615
|
+
|
616
|
+
credential_source = create_credential_source(source_type, **kwargs)
|
617
|
+
|
396
618
|
# Create profile
|
397
619
|
success = self.profile_manager.create_profile(
|
398
620
|
name=name,
|
@@ -408,6 +630,7 @@ class ProfileTools:
|
|
408
630
|
language=language,
|
409
631
|
cache_dir=cache_dir,
|
410
632
|
description=description,
|
633
|
+
credential_source=credential_source,
|
411
634
|
)
|
412
635
|
|
413
636
|
if not success:
|
@@ -477,8 +700,44 @@ class ProfileTools:
|
|
477
700
|
|
478
701
|
mapped_params = {}
|
479
702
|
for key, value in update_params.items():
|
480
|
-
|
481
|
-
|
703
|
+
if key == "credentialSource":
|
704
|
+
# Handle credential source
|
705
|
+
from ...credential_sources import create_credential_source
|
706
|
+
cred_source_data = value
|
707
|
+
|
708
|
+
# Convert camelCase to snake_case for the factory function
|
709
|
+
source_type = cred_source_data["sourceType"]
|
710
|
+
kwargs = {}
|
711
|
+
|
712
|
+
if source_type == "environment":
|
713
|
+
if "clientIdVar" in cred_source_data:
|
714
|
+
kwargs["client_id_var"] = cred_source_data["clientIdVar"]
|
715
|
+
if "clientSecretVar" in cred_source_data:
|
716
|
+
kwargs["client_secret_var"] = cred_source_data["clientSecretVar"]
|
717
|
+
if "tenantIdVar" in cred_source_data:
|
718
|
+
kwargs["tenant_id_var"] = cred_source_data["tenantIdVar"]
|
719
|
+
elif source_type == "keyvault":
|
720
|
+
kwargs["vault_url"] = cred_source_data["vaultUrl"]
|
721
|
+
if "clientIdSecretName" in cred_source_data:
|
722
|
+
kwargs["client_id_secret_name"] = cred_source_data["clientIdSecretName"]
|
723
|
+
if "clientSecretSecretName" in cred_source_data:
|
724
|
+
kwargs["client_secret_secret_name"] = cred_source_data["clientSecretSecretName"]
|
725
|
+
if "tenantIdSecretName" in cred_source_data:
|
726
|
+
kwargs["tenant_id_secret_name"] = cred_source_data["tenantIdSecretName"]
|
727
|
+
if "keyvaultAuthMode" in cred_source_data:
|
728
|
+
kwargs["keyvault_auth_mode"] = cred_source_data["keyvaultAuthMode"]
|
729
|
+
if "keyvaultClientId" in cred_source_data:
|
730
|
+
kwargs["keyvault_client_id"] = cred_source_data["keyvaultClientId"]
|
731
|
+
if "keyvaultClientSecret" in cred_source_data:
|
732
|
+
kwargs["keyvault_client_secret"] = cred_source_data["keyvaultClientSecret"]
|
733
|
+
if "keyvaultTenantId" in cred_source_data:
|
734
|
+
kwargs["keyvault_tenant_id"] = cred_source_data["keyvaultTenantId"]
|
735
|
+
|
736
|
+
credential_source = create_credential_source(source_type, **kwargs)
|
737
|
+
mapped_params["credential_source"] = credential_source
|
738
|
+
else:
|
739
|
+
mapped_key = param_mapping.get(key, key)
|
740
|
+
mapped_params[mapped_key] = value
|
482
741
|
|
483
742
|
success = self.profile_manager.update_profile(name, **mapped_params)
|
484
743
|
|