d365fo-client 0.2.2__tar.gz → 0.2.3__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.
- {d365fo_client-0.2.2/src/d365fo_client.egg-info → d365fo_client-0.2.3}/PKG-INFO +48 -17
- {d365fo_client-0.2.2 → d365fo_client-0.2.3}/README.md +46 -16
- {d365fo_client-0.2.2 → d365fo_client-0.2.3}/pyproject.toml +2 -1
- {d365fo_client-0.2.2 → d365fo_client-0.2.3}/src/d365fo_client/auth.py +48 -9
- {d365fo_client-0.2.2 → d365fo_client-0.2.3}/src/d365fo_client/client.py +40 -20
- d365fo_client-0.2.3/src/d365fo_client/credential_sources.py +431 -0
- {d365fo_client-0.2.2 → d365fo_client-0.2.3}/src/d365fo_client/mcp/client_manager.py +8 -0
- {d365fo_client-0.2.2 → d365fo_client-0.2.3}/src/d365fo_client/mcp/main.py +39 -17
- {d365fo_client-0.2.2 → d365fo_client-0.2.3}/src/d365fo_client/mcp/server.py +69 -22
- {d365fo_client-0.2.2 → d365fo_client-0.2.3}/src/d365fo_client/mcp/tools/__init__.py +2 -0
- {d365fo_client-0.2.2 → d365fo_client-0.2.3}/src/d365fo_client/mcp/tools/profile_tools.py +261 -2
- d365fo_client-0.2.3/src/d365fo_client/mcp/tools/sync_tools.py +503 -0
- {d365fo_client-0.2.2 → d365fo_client-0.2.3}/src/d365fo_client/metadata_api.py +67 -0
- {d365fo_client-0.2.2 → d365fo_client-0.2.3}/src/d365fo_client/metadata_v2/cache_v2.py +11 -9
- {d365fo_client-0.2.2 → d365fo_client-0.2.3}/src/d365fo_client/metadata_v2/global_version_manager.py +2 -4
- {d365fo_client-0.2.2 → d365fo_client-0.2.3}/src/d365fo_client/metadata_v2/sync_manager_v2.py +1 -1
- d365fo_client-0.2.3/src/d365fo_client/metadata_v2/sync_session_manager.py +1043 -0
- {d365fo_client-0.2.2 → d365fo_client-0.2.3}/src/d365fo_client/models.py +22 -3
- {d365fo_client-0.2.2 → d365fo_client-0.2.3}/src/d365fo_client/profile_manager.py +7 -1
- {d365fo_client-0.2.2 → d365fo_client-0.2.3}/src/d365fo_client/profiles.py +28 -1
- d365fo_client-0.2.3/src/d365fo_client/sync_models.py +181 -0
- {d365fo_client-0.2.2 → d365fo_client-0.2.3/src/d365fo_client.egg-info}/PKG-INFO +48 -17
- {d365fo_client-0.2.2 → d365fo_client-0.2.3}/src/d365fo_client.egg-info/SOURCES.txt +4 -0
- {d365fo_client-0.2.2 → d365fo_client-0.2.3}/src/d365fo_client.egg-info/requires.txt +1 -0
- {d365fo_client-0.2.2 → d365fo_client-0.2.3}/LICENSE +0 -0
- {d365fo_client-0.2.2 → d365fo_client-0.2.3}/setup.cfg +0 -0
- {d365fo_client-0.2.2 → d365fo_client-0.2.3}/src/d365fo_client/__init__.py +0 -0
- {d365fo_client-0.2.2 → d365fo_client-0.2.3}/src/d365fo_client/cli.py +0 -0
- {d365fo_client-0.2.2 → d365fo_client-0.2.3}/src/d365fo_client/config.py +0 -0
- {d365fo_client-0.2.2 → d365fo_client-0.2.3}/src/d365fo_client/crud.py +0 -0
- {d365fo_client-0.2.2 → d365fo_client-0.2.3}/src/d365fo_client/exceptions.py +0 -0
- {d365fo_client-0.2.2 → d365fo_client-0.2.3}/src/d365fo_client/labels.py +0 -0
- {d365fo_client-0.2.2 → d365fo_client-0.2.3}/src/d365fo_client/main.py +0 -0
- {d365fo_client-0.2.2 → d365fo_client-0.2.3}/src/d365fo_client/mcp/__init__.py +0 -0
- {d365fo_client-0.2.2 → d365fo_client-0.2.3}/src/d365fo_client/mcp/models.py +0 -0
- {d365fo_client-0.2.2 → d365fo_client-0.2.3}/src/d365fo_client/mcp/prompts/__init__.py +0 -0
- {d365fo_client-0.2.2 → d365fo_client-0.2.3}/src/d365fo_client/mcp/prompts/action_execution.py +0 -0
- {d365fo_client-0.2.2 → d365fo_client-0.2.3}/src/d365fo_client/mcp/prompts/sequence_analysis.py +0 -0
- {d365fo_client-0.2.2 → d365fo_client-0.2.3}/src/d365fo_client/mcp/resources/__init__.py +0 -0
- {d365fo_client-0.2.2 → d365fo_client-0.2.3}/src/d365fo_client/mcp/resources/database_handler.py +0 -0
- {d365fo_client-0.2.2 → d365fo_client-0.2.3}/src/d365fo_client/mcp/resources/entity_handler.py +0 -0
- {d365fo_client-0.2.2 → d365fo_client-0.2.3}/src/d365fo_client/mcp/resources/environment_handler.py +0 -0
- {d365fo_client-0.2.2 → d365fo_client-0.2.3}/src/d365fo_client/mcp/resources/metadata_handler.py +0 -0
- {d365fo_client-0.2.2 → d365fo_client-0.2.3}/src/d365fo_client/mcp/resources/query_handler.py +0 -0
- {d365fo_client-0.2.2 → d365fo_client-0.2.3}/src/d365fo_client/mcp/tools/connection_tools.py +0 -0
- {d365fo_client-0.2.2 → d365fo_client-0.2.3}/src/d365fo_client/mcp/tools/crud_tools.py +0 -0
- {d365fo_client-0.2.2 → d365fo_client-0.2.3}/src/d365fo_client/mcp/tools/database_tools.py +0 -0
- {d365fo_client-0.2.2 → d365fo_client-0.2.3}/src/d365fo_client/mcp/tools/label_tools.py +0 -0
- {d365fo_client-0.2.2 → d365fo_client-0.2.3}/src/d365fo_client/mcp/tools/metadata_tools.py +0 -0
- {d365fo_client-0.2.2 → d365fo_client-0.2.3}/src/d365fo_client/metadata_v2/__init__.py +0 -0
- {d365fo_client-0.2.2 → d365fo_client-0.2.3}/src/d365fo_client/metadata_v2/database_v2.py +0 -0
- {d365fo_client-0.2.2 → d365fo_client-0.2.3}/src/d365fo_client/metadata_v2/label_utils.py +0 -0
- {d365fo_client-0.2.2 → d365fo_client-0.2.3}/src/d365fo_client/metadata_v2/search_engine_v2.py +0 -0
- {d365fo_client-0.2.2 → d365fo_client-0.2.3}/src/d365fo_client/metadata_v2/version_detector.py +0 -0
- {d365fo_client-0.2.2 → d365fo_client-0.2.3}/src/d365fo_client/output.py +0 -0
- {d365fo_client-0.2.2 → d365fo_client-0.2.3}/src/d365fo_client/query.py +0 -0
- {d365fo_client-0.2.2 → d365fo_client-0.2.3}/src/d365fo_client/session.py +0 -0
- {d365fo_client-0.2.2 → d365fo_client-0.2.3}/src/d365fo_client/utils.py +0 -0
- {d365fo_client-0.2.2 → d365fo_client-0.2.3}/src/d365fo_client.egg-info/dependency_links.txt +0 -0
- {d365fo_client-0.2.2 → d365fo_client-0.2.3}/src/d365fo_client.egg-info/entry_points.txt +0 -0
- {d365fo_client-0.2.2 → d365fo_client-0.2.3}/src/d365fo_client.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: d365fo-client
|
3
|
-
Version: 0.2.
|
3
|
+
Version: 0.2.3
|
4
4
|
Summary: Microsoft Dynamics 365 Finance & Operations client
|
5
5
|
Author-email: Muhammad Afzaal <mo@thedataguy.pro>
|
6
6
|
License-Expression: MIT
|
@@ -22,6 +22,7 @@ License-File: LICENSE
|
|
22
22
|
Requires-Dist: aiohttp>=3.10.0
|
23
23
|
Requires-Dist: aiofiles>=24.1.0
|
24
24
|
Requires-Dist: azure-identity>=1.19.0
|
25
|
+
Requires-Dist: azure-keyvault-secrets>=4.8.0
|
25
26
|
Requires-Dist: requests>=2.32.0
|
26
27
|
Requires-Dist: aiosqlite>=0.19.0
|
27
28
|
Requires-Dist: cachetools>=6.1.0
|
@@ -50,7 +51,7 @@ A comprehensive Python client library and MCP server for Microsoft Dynamics 365
|
|
50
51
|
- 🏷️ **Label Operations V2**: Multilingual label caching with performance improvements and async support
|
51
52
|
- 🔍 **Advanced Querying**: Support for all OData query parameters ($select, $filter, $expand, etc.)
|
52
53
|
- ⚡ **Action Execution**: Execute bound and unbound OData actions with comprehensive parameter handling
|
53
|
-
- 🔒 **Authentication**: Azure AD integration with default credentials
|
54
|
+
- 🔒 **Authentication**: Azure AD integration with default credentials, service principal, and Azure Key Vault support
|
54
55
|
- 💾 **Intelligent Caching**: Cross-environment cache sharing with module-based version detection
|
55
56
|
- 🌐 **Async/Await**: Modern async/await patterns with optimized session management
|
56
57
|
- 📝 **Type Hints**: Full type annotation support with enhanced data models
|
@@ -58,6 +59,8 @@ A comprehensive Python client library and MCP server for Microsoft Dynamics 365
|
|
58
59
|
- 🖥️ **Comprehensive CLI**: Hierarchical command-line interface for all D365 F&O operations
|
59
60
|
- 🧪 **Multi-tier Testing**: Mock, sandbox, and live integration testing framework (17/17 tests passing)
|
60
61
|
- 📋 **Metadata Scripts**: PowerShell and Python utilities for entity, enumeration, and action discovery
|
62
|
+
- 🔐 **Enhanced Credential Management**: Support for Azure Key Vault and multiple credential sources
|
63
|
+
- 📊 **Advanced Sync Management**: Session-based synchronization with detailed progress tracking
|
61
64
|
|
62
65
|
## Installation
|
63
66
|
|
@@ -73,6 +76,13 @@ uv sync # Installs with exact dependencies from uv.lock
|
|
73
76
|
|
74
77
|
**Note**: The package includes MCP (Model Context Protocol) dependencies by default, enabling AI assistant integration. Both `d365fo-client` CLI and `d365fo-mcp-server` commands will be available after installation.
|
75
78
|
|
79
|
+
**Breaking Change in v0.2.3**: Environment variable names have been updated for consistency:
|
80
|
+
- `AZURE_CLIENT_ID` → `D365FO_CLIENT_ID`
|
81
|
+
- `AZURE_CLIENT_SECRET` → `D365FO_CLIENT_SECRET`
|
82
|
+
- `AZURE_TENANT_ID` → `D365FO_TENANT_ID`
|
83
|
+
|
84
|
+
Please update your environment variables accordingly when upgrading.
|
85
|
+
|
76
86
|
## Quick Start
|
77
87
|
|
78
88
|
## Command Line Interface (CLI)
|
@@ -158,9 +168,9 @@ profiles:
|
|
158
168
|
|
159
169
|
development:
|
160
170
|
base_url: "https://dev.dynamics.com"
|
161
|
-
client_id: "${
|
162
|
-
client_secret: "${
|
163
|
-
tenant_id: "${
|
171
|
+
client_id: "${D365FO_CLIENT_ID}"
|
172
|
+
client_secret: "${D365FO_CLIENT_SECRET}"
|
173
|
+
tenant_id: "${D365FO_TENANT_ID}"
|
164
174
|
use_cache_first: true
|
165
175
|
|
166
176
|
default_profile: "development"
|
@@ -262,7 +272,14 @@ config = FOClientConfig(
|
|
262
272
|
use_default_credentials=False
|
263
273
|
)
|
264
274
|
|
265
|
-
# Option 3:
|
275
|
+
# Option 3: Azure Key Vault integration (New in v0.2.3)
|
276
|
+
config = FOClientConfig(
|
277
|
+
base_url="https://your-fo-environment.dynamics.com",
|
278
|
+
credential_source="keyvault", # Use Azure Key Vault for credentials
|
279
|
+
keyvault_url="https://your-keyvault.vault.azure.net/"
|
280
|
+
)
|
281
|
+
|
282
|
+
# Option 4: With custom settings
|
266
283
|
config = FOClientConfig(
|
267
284
|
base_url="https://your-fo-environment.dynamics.com",
|
268
285
|
use_default_credentials=True,
|
@@ -462,7 +479,9 @@ d365fo-client/
|
|
462
479
|
│ ├── metadata.py # Legacy metadata operations
|
463
480
|
│ ├── metadata_api.py # Metadata API client
|
464
481
|
│ ├── metadata_cache.py # Metadata caching layer V2
|
465
|
-
│ ├── metadata_sync.py # Metadata synchronization V2
|
482
|
+
│ ├── metadata_sync.py # Metadata synchronization V2 with session management
|
483
|
+
│ ├── sync_session.py # Enhanced sync session management (New in v0.2.3)
|
484
|
+
│ ├── credential_manager.py # Credential source management (New in v0.2.3)
|
466
485
|
│ ├── labels.py # Label operations V2
|
467
486
|
│ ├── profiles.py # Profile data models
|
468
487
|
│ ├── profile_manager.py # Profile management
|
@@ -519,6 +538,8 @@ d365fo-client/
|
|
519
538
|
| `client_secret` | str | None | Azure AD client secret |
|
520
539
|
| `tenant_id` | str | None | Azure AD tenant ID |
|
521
540
|
| `use_default_credentials` | bool | True | Use Azure Default Credential |
|
541
|
+
| `credential_source` | str | "environment" | Credential source: "environment", "keyvault" |
|
542
|
+
| `keyvault_url` | str | None | Azure Key Vault URL for credential storage |
|
522
543
|
| `verify_ssl` | bool | False | Verify SSL certificates |
|
523
544
|
| `timeout` | int | 30 | Request timeout in seconds |
|
524
545
|
| `metadata_cache_dir` | str | Platform-specific user cache | Metadata cache directory |
|
@@ -619,9 +640,9 @@ cp tests/integration/.env.template tests/integration/.env
|
|
619
640
|
# Edit .env file with your settings:
|
620
641
|
INTEGRATION_TEST_LEVEL=sandbox
|
621
642
|
D365FO_SANDBOX_BASE_URL=https://your-test.dynamics.com
|
622
|
-
|
623
|
-
|
624
|
-
|
643
|
+
D365FO_CLIENT_ID=your-client-id
|
644
|
+
D365FO_CLIENT_SECRET=your-client-secret
|
645
|
+
D365FO_TENANT_ID=your-tenant-id
|
625
646
|
```
|
626
647
|
|
627
648
|
#### Available Commands
|
@@ -707,9 +728,9 @@ pip install d365fo-client
|
|
707
728
|
|
708
729
|
# Set up environment variables
|
709
730
|
export D365FO_BASE_URL="https://your-environment.dynamics.com"
|
710
|
-
export
|
711
|
-
export
|
712
|
-
export
|
731
|
+
export D365FO_CLIENT_ID="your-client-id" # Optional with default credentials
|
732
|
+
export D365FO_CLIENT_SECRET="your-client-secret" # Optional with default credentials
|
733
|
+
export D365FO_TENANT_ID="your-tenant-id" # Optional with default credentials
|
713
734
|
|
714
735
|
# Start the MCP server
|
715
736
|
d365fo-mcp-server
|
@@ -857,9 +878,19 @@ For service principal authentication:
|
|
857
878
|
|
858
879
|
```bash
|
859
880
|
export D365FO_BASE_URL="https://your-environment.dynamics.com"
|
860
|
-
export
|
861
|
-
export
|
862
|
-
export
|
881
|
+
export D365FO_CLIENT_ID="your-client-id"
|
882
|
+
export D365FO_CLIENT_SECRET="your-client-secret"
|
883
|
+
export D365FO_TENANT_ID="your-tenant-id"
|
884
|
+
d365fo-mcp-server
|
885
|
+
```
|
886
|
+
|
887
|
+
#### Azure Key Vault Integration (New in v0.2.3)
|
888
|
+
For secure credential storage using Azure Key Vault:
|
889
|
+
|
890
|
+
```bash
|
891
|
+
export D365FO_BASE_URL="https://your-environment.dynamics.com"
|
892
|
+
export D365FO_CREDENTIAL_SOURCE="keyvault"
|
893
|
+
export D365FO_KEYVAULT_URL="https://your-keyvault.vault.azure.net/"
|
863
894
|
d365fo-mcp-server
|
864
895
|
```
|
865
896
|
|
@@ -1032,7 +1063,7 @@ tail -f ~/.d365fo-mcp/logs/mcp-server.log
|
|
1032
1063
|
az account show
|
1033
1064
|
|
1034
1065
|
# Test with explicit credentials
|
1035
|
-
export
|
1066
|
+
export D365FO_CLIENT_ID="your-client-id"
|
1036
1067
|
# ... set other variables
|
1037
1068
|
d365fo-mcp-server
|
1038
1069
|
```
|
@@ -9,7 +9,7 @@ A comprehensive Python client library and MCP server for Microsoft Dynamics 365
|
|
9
9
|
- 🏷️ **Label Operations V2**: Multilingual label caching with performance improvements and async support
|
10
10
|
- 🔍 **Advanced Querying**: Support for all OData query parameters ($select, $filter, $expand, etc.)
|
11
11
|
- ⚡ **Action Execution**: Execute bound and unbound OData actions with comprehensive parameter handling
|
12
|
-
- 🔒 **Authentication**: Azure AD integration with default credentials
|
12
|
+
- 🔒 **Authentication**: Azure AD integration with default credentials, service principal, and Azure Key Vault support
|
13
13
|
- 💾 **Intelligent Caching**: Cross-environment cache sharing with module-based version detection
|
14
14
|
- 🌐 **Async/Await**: Modern async/await patterns with optimized session management
|
15
15
|
- 📝 **Type Hints**: Full type annotation support with enhanced data models
|
@@ -17,6 +17,8 @@ A comprehensive Python client library and MCP server for Microsoft Dynamics 365
|
|
17
17
|
- 🖥️ **Comprehensive CLI**: Hierarchical command-line interface for all D365 F&O operations
|
18
18
|
- 🧪 **Multi-tier Testing**: Mock, sandbox, and live integration testing framework (17/17 tests passing)
|
19
19
|
- 📋 **Metadata Scripts**: PowerShell and Python utilities for entity, enumeration, and action discovery
|
20
|
+
- 🔐 **Enhanced Credential Management**: Support for Azure Key Vault and multiple credential sources
|
21
|
+
- 📊 **Advanced Sync Management**: Session-based synchronization with detailed progress tracking
|
20
22
|
|
21
23
|
## Installation
|
22
24
|
|
@@ -32,6 +34,13 @@ uv sync # Installs with exact dependencies from uv.lock
|
|
32
34
|
|
33
35
|
**Note**: The package includes MCP (Model Context Protocol) dependencies by default, enabling AI assistant integration. Both `d365fo-client` CLI and `d365fo-mcp-server` commands will be available after installation.
|
34
36
|
|
37
|
+
**Breaking Change in v0.2.3**: Environment variable names have been updated for consistency:
|
38
|
+
- `AZURE_CLIENT_ID` → `D365FO_CLIENT_ID`
|
39
|
+
- `AZURE_CLIENT_SECRET` → `D365FO_CLIENT_SECRET`
|
40
|
+
- `AZURE_TENANT_ID` → `D365FO_TENANT_ID`
|
41
|
+
|
42
|
+
Please update your environment variables accordingly when upgrading.
|
43
|
+
|
35
44
|
## Quick Start
|
36
45
|
|
37
46
|
## Command Line Interface (CLI)
|
@@ -117,9 +126,9 @@ profiles:
|
|
117
126
|
|
118
127
|
development:
|
119
128
|
base_url: "https://dev.dynamics.com"
|
120
|
-
client_id: "${
|
121
|
-
client_secret: "${
|
122
|
-
tenant_id: "${
|
129
|
+
client_id: "${D365FO_CLIENT_ID}"
|
130
|
+
client_secret: "${D365FO_CLIENT_SECRET}"
|
131
|
+
tenant_id: "${D365FO_TENANT_ID}"
|
123
132
|
use_cache_first: true
|
124
133
|
|
125
134
|
default_profile: "development"
|
@@ -221,7 +230,14 @@ config = FOClientConfig(
|
|
221
230
|
use_default_credentials=False
|
222
231
|
)
|
223
232
|
|
224
|
-
# Option 3:
|
233
|
+
# Option 3: Azure Key Vault integration (New in v0.2.3)
|
234
|
+
config = FOClientConfig(
|
235
|
+
base_url="https://your-fo-environment.dynamics.com",
|
236
|
+
credential_source="keyvault", # Use Azure Key Vault for credentials
|
237
|
+
keyvault_url="https://your-keyvault.vault.azure.net/"
|
238
|
+
)
|
239
|
+
|
240
|
+
# Option 4: With custom settings
|
225
241
|
config = FOClientConfig(
|
226
242
|
base_url="https://your-fo-environment.dynamics.com",
|
227
243
|
use_default_credentials=True,
|
@@ -421,7 +437,9 @@ d365fo-client/
|
|
421
437
|
│ ├── metadata.py # Legacy metadata operations
|
422
438
|
│ ├── metadata_api.py # Metadata API client
|
423
439
|
│ ├── metadata_cache.py # Metadata caching layer V2
|
424
|
-
│ ├── metadata_sync.py # Metadata synchronization V2
|
440
|
+
│ ├── metadata_sync.py # Metadata synchronization V2 with session management
|
441
|
+
│ ├── sync_session.py # Enhanced sync session management (New in v0.2.3)
|
442
|
+
│ ├── credential_manager.py # Credential source management (New in v0.2.3)
|
425
443
|
│ ├── labels.py # Label operations V2
|
426
444
|
│ ├── profiles.py # Profile data models
|
427
445
|
│ ├── profile_manager.py # Profile management
|
@@ -478,6 +496,8 @@ d365fo-client/
|
|
478
496
|
| `client_secret` | str | None | Azure AD client secret |
|
479
497
|
| `tenant_id` | str | None | Azure AD tenant ID |
|
480
498
|
| `use_default_credentials` | bool | True | Use Azure Default Credential |
|
499
|
+
| `credential_source` | str | "environment" | Credential source: "environment", "keyvault" |
|
500
|
+
| `keyvault_url` | str | None | Azure Key Vault URL for credential storage |
|
481
501
|
| `verify_ssl` | bool | False | Verify SSL certificates |
|
482
502
|
| `timeout` | int | 30 | Request timeout in seconds |
|
483
503
|
| `metadata_cache_dir` | str | Platform-specific user cache | Metadata cache directory |
|
@@ -578,9 +598,9 @@ cp tests/integration/.env.template tests/integration/.env
|
|
578
598
|
# Edit .env file with your settings:
|
579
599
|
INTEGRATION_TEST_LEVEL=sandbox
|
580
600
|
D365FO_SANDBOX_BASE_URL=https://your-test.dynamics.com
|
581
|
-
|
582
|
-
|
583
|
-
|
601
|
+
D365FO_CLIENT_ID=your-client-id
|
602
|
+
D365FO_CLIENT_SECRET=your-client-secret
|
603
|
+
D365FO_TENANT_ID=your-tenant-id
|
584
604
|
```
|
585
605
|
|
586
606
|
#### Available Commands
|
@@ -666,9 +686,9 @@ pip install d365fo-client
|
|
666
686
|
|
667
687
|
# Set up environment variables
|
668
688
|
export D365FO_BASE_URL="https://your-environment.dynamics.com"
|
669
|
-
export
|
670
|
-
export
|
671
|
-
export
|
689
|
+
export D365FO_CLIENT_ID="your-client-id" # Optional with default credentials
|
690
|
+
export D365FO_CLIENT_SECRET="your-client-secret" # Optional with default credentials
|
691
|
+
export D365FO_TENANT_ID="your-tenant-id" # Optional with default credentials
|
672
692
|
|
673
693
|
# Start the MCP server
|
674
694
|
d365fo-mcp-server
|
@@ -816,9 +836,19 @@ For service principal authentication:
|
|
816
836
|
|
817
837
|
```bash
|
818
838
|
export D365FO_BASE_URL="https://your-environment.dynamics.com"
|
819
|
-
export
|
820
|
-
export
|
821
|
-
export
|
839
|
+
export D365FO_CLIENT_ID="your-client-id"
|
840
|
+
export D365FO_CLIENT_SECRET="your-client-secret"
|
841
|
+
export D365FO_TENANT_ID="your-tenant-id"
|
842
|
+
d365fo-mcp-server
|
843
|
+
```
|
844
|
+
|
845
|
+
#### Azure Key Vault Integration (New in v0.2.3)
|
846
|
+
For secure credential storage using Azure Key Vault:
|
847
|
+
|
848
|
+
```bash
|
849
|
+
export D365FO_BASE_URL="https://your-environment.dynamics.com"
|
850
|
+
export D365FO_CREDENTIAL_SOURCE="keyvault"
|
851
|
+
export D365FO_KEYVAULT_URL="https://your-keyvault.vault.azure.net/"
|
822
852
|
d365fo-mcp-server
|
823
853
|
```
|
824
854
|
|
@@ -991,7 +1021,7 @@ tail -f ~/.d365fo-mcp/logs/mcp-server.log
|
|
991
1021
|
az account show
|
992
1022
|
|
993
1023
|
# Test with explicit credentials
|
994
|
-
export
|
1024
|
+
export D365FO_CLIENT_ID="your-client-id"
|
995
1025
|
# ... set other variables
|
996
1026
|
d365fo-mcp-server
|
997
1027
|
```
|
@@ -1,6 +1,6 @@
|
|
1
1
|
[project]
|
2
2
|
name = "d365fo-client"
|
3
|
-
version = "0.2.
|
3
|
+
version = "0.2.3"
|
4
4
|
description = "Microsoft Dynamics 365 Finance & Operations client"
|
5
5
|
readme = "README.md"
|
6
6
|
license = "MIT"
|
@@ -10,6 +10,7 @@ dependencies = [
|
|
10
10
|
"aiohttp>=3.10.0",
|
11
11
|
"aiofiles>=24.1.0",
|
12
12
|
"azure-identity>=1.19.0",
|
13
|
+
"azure-keyvault-secrets>=4.8.0",
|
13
14
|
"requests>=2.32.0",
|
14
15
|
"aiosqlite>=0.19.0",
|
15
16
|
"cachetools>=6.1.0",
|
@@ -5,6 +5,7 @@ from typing import Optional
|
|
5
5
|
|
6
6
|
from azure.identity import ClientSecretCredential, DefaultAzureCredential
|
7
7
|
|
8
|
+
from .credential_sources import CredentialManager, CredentialSource
|
8
9
|
from .models import FOClientConfig
|
9
10
|
|
10
11
|
|
@@ -20,25 +21,46 @@ class AuthenticationManager:
|
|
20
21
|
self.config = config
|
21
22
|
self._token = None
|
22
23
|
self._token_expires = None
|
23
|
-
self.
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
24
|
+
self._credential_manager = CredentialManager()
|
25
|
+
self.credential = None # Will be set by _setup_credentials
|
26
|
+
|
27
|
+
async def _setup_credentials(self):
|
28
|
+
"""Setup authentication credentials with support for credential sources"""
|
29
|
+
|
30
|
+
# Check if credential source is specified in config
|
31
|
+
credential_source = getattr(self.config, 'credential_source', None)
|
32
|
+
|
33
|
+
if credential_source is not None:
|
34
|
+
# Use credential source to get credentials
|
35
|
+
try:
|
36
|
+
client_id, client_secret, tenant_id = await self._credential_manager.get_credentials(credential_source)
|
37
|
+
self.credential = ClientSecretCredential(
|
38
|
+
tenant_id=tenant_id,
|
39
|
+
client_id=client_id,
|
40
|
+
client_secret=client_secret,
|
41
|
+
)
|
42
|
+
return
|
43
|
+
except Exception as e:
|
44
|
+
raise ValueError(f"Failed to setup credentials from source: {e}")
|
45
|
+
|
46
|
+
|
47
|
+
# Fallback to existing logic for backward compatibility
|
48
|
+
|
49
|
+
if (
|
30
50
|
self.config.client_id
|
31
51
|
and self.config.client_secret
|
32
52
|
and self.config.tenant_id
|
33
53
|
):
|
34
|
-
|
54
|
+
self.credential = ClientSecretCredential(
|
35
55
|
tenant_id=self.config.tenant_id,
|
36
56
|
client_id=self.config.client_id,
|
37
57
|
client_secret=self.config.client_secret,
|
38
58
|
)
|
59
|
+
elif self.config.use_default_credentials:
|
60
|
+
self.credential = DefaultAzureCredential()
|
39
61
|
else:
|
40
62
|
raise ValueError(
|
41
|
-
"Must provide either use_default_credentials=True
|
63
|
+
"Must provide either use_default_credentials=True, client credentials, or credential_source"
|
42
64
|
)
|
43
65
|
|
44
66
|
async def get_token(self) -> str:
|
@@ -51,6 +73,10 @@ class AuthenticationManager:
|
|
51
73
|
if self._is_localhost():
|
52
74
|
return "mock-token-for-localhost"
|
53
75
|
|
76
|
+
# Initialize credentials if not already set
|
77
|
+
if self.credential is None:
|
78
|
+
await self._setup_credentials()
|
79
|
+
|
54
80
|
if (
|
55
81
|
self._token
|
56
82
|
and self._token_expires
|
@@ -91,3 +117,16 @@ class AuthenticationManager:
|
|
91
117
|
"""Invalidate cached token to force refresh"""
|
92
118
|
self._token = None
|
93
119
|
self._token_expires = None
|
120
|
+
|
121
|
+
async def invalidate_credentials(self):
|
122
|
+
"""Invalidate cached credentials and token to force full refresh"""
|
123
|
+
self.invalidate_token()
|
124
|
+
self.credential = None
|
125
|
+
if hasattr(self, '_credential_manager'):
|
126
|
+
self._credential_manager.clear_cache()
|
127
|
+
|
128
|
+
def get_credential_cache_stats(self) -> dict:
|
129
|
+
"""Get credential cache statistics for debugging"""
|
130
|
+
if hasattr(self, '_credential_manager'):
|
131
|
+
return self._credential_manager.get_cache_stats()
|
132
|
+
return {"total_cached": 0, "expired": 0, "active": 0}
|
@@ -11,6 +11,7 @@ from .exceptions import FOClientError
|
|
11
11
|
from .labels import LabelOperations, resolve_labels_generic
|
12
12
|
from .metadata_api import MetadataAPIOperations
|
13
13
|
from .metadata_v2 import MetadataCacheV2, SmartSyncManagerV2
|
14
|
+
from .metadata_v2.sync_session_manager import SyncSessionManager
|
14
15
|
from .models import (
|
15
16
|
ActionInfo,
|
16
17
|
DataEntityInfo,
|
@@ -58,6 +59,7 @@ class FOClient:
|
|
58
59
|
# Initialize new metadata cache and sync components
|
59
60
|
self.metadata_cache = None
|
60
61
|
self.sync_manager = None
|
62
|
+
self._sync_session_manager = None
|
61
63
|
self._metadata_initialized = False
|
62
64
|
self._background_sync_task = None
|
63
65
|
|
@@ -83,6 +85,10 @@ class FOClient:
|
|
83
85
|
|
84
86
|
await self.session_manager.close()
|
85
87
|
|
88
|
+
|
89
|
+
async def initialize_metadata(self):
|
90
|
+
await self._ensure_metadata_initialized()
|
91
|
+
|
86
92
|
async def _ensure_metadata_initialized(self):
|
87
93
|
"""Ensure metadata cache and sync manager are initialized"""
|
88
94
|
if not self._metadata_initialized and self.config.enable_metadata_cache:
|
@@ -104,6 +110,9 @@ class FOClient:
|
|
104
110
|
self.metadata_cache, self.metadata_api_ops
|
105
111
|
)
|
106
112
|
|
113
|
+
# Initialize sync message with session
|
114
|
+
self._sync_session_manager = SyncSessionManager(self.metadata_cache, self.metadata_api_ops)
|
115
|
+
|
107
116
|
self._metadata_initialized = True
|
108
117
|
self.logger.debug("Metadata cache v2 with label caching initialized")
|
109
118
|
|
@@ -144,18 +153,9 @@ class FOClient:
|
|
144
153
|
f"Starting background metadata sync for version {global_version_id}"
|
145
154
|
)
|
146
155
|
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
if result.success:
|
151
|
-
self.logger.info(
|
152
|
-
f"Background sync completed: "
|
153
|
-
f"{result.entity_count} entities, "
|
154
|
-
f"{result.enumeration_count} enumerations, "
|
155
|
-
f"{result.duration_ms:.2f}ms"
|
156
|
-
)
|
157
|
-
else:
|
158
|
-
self.logger.warning(f"Background sync failed: {result.error}")
|
156
|
+
|
157
|
+
self.sync_session_manager.start_sync_session(global_version_id=global_version_id,initiated_by="background_task")
|
158
|
+
|
159
159
|
|
160
160
|
except Exception as e:
|
161
161
|
self.logger.error(f"Background sync error: {e}")
|
@@ -172,6 +172,27 @@ class FOClient:
|
|
172
172
|
and not self._background_sync_task.done()
|
173
173
|
)
|
174
174
|
|
175
|
+
@property
|
176
|
+
def sync_session_manager(self) -> SyncSessionManager:
|
177
|
+
"""Get sync session manager (lazy initialization).
|
178
|
+
|
179
|
+
Returns:
|
180
|
+
SyncSessionManager instance for enhanced sync progress tracking
|
181
|
+
|
182
|
+
Raises:
|
183
|
+
RuntimeError: If metadata cache is not initialized
|
184
|
+
"""
|
185
|
+
if self._sync_session_manager is None:
|
186
|
+
if not self.metadata_cache:
|
187
|
+
raise RuntimeError("Metadata cache must be initialized before accessing sync session manager")
|
188
|
+
|
189
|
+
self._sync_session_manager = SyncSessionManager(
|
190
|
+
cache=self.metadata_cache,
|
191
|
+
metadata_api=self.metadata_api_ops
|
192
|
+
)
|
193
|
+
|
194
|
+
return self._sync_session_manager
|
195
|
+
|
175
196
|
async def _get_from_cache_first(
|
176
197
|
self,
|
177
198
|
cache_method,
|
@@ -687,17 +708,16 @@ class FOClient:
|
|
687
708
|
|
688
709
|
async def get_data_entities(
|
689
710
|
self, options: Optional[QueryOptions] = None
|
690
|
-
) ->
|
691
|
-
"""Get data entities
|
711
|
+
) -> Dict[str, Any]:
|
712
|
+
"""Get data entities from DataEntities metadata endpoint
|
692
713
|
|
693
714
|
Args:
|
694
|
-
options: OData query options
|
715
|
+
options: OData query options
|
695
716
|
|
696
717
|
Returns:
|
697
|
-
|
718
|
+
Response containing data entities
|
698
719
|
"""
|
699
|
-
|
700
|
-
return await self.metadata_api_ops.search_data_entities("") # Get all entities
|
720
|
+
return await self.metadata_api_ops.get_data_entities(options)
|
701
721
|
|
702
722
|
async def get_data_entities_raw(
|
703
723
|
self, options: Optional[QueryOptions] = None
|
@@ -934,7 +954,7 @@ class FOClient:
|
|
934
954
|
Returns:
|
935
955
|
Response containing public enumerations
|
936
956
|
"""
|
937
|
-
self._ensure_metadata_initialized()
|
957
|
+
await self._ensure_metadata_initialized()
|
938
958
|
|
939
959
|
return await self.metadata_api_ops.get_public_enumerations(options)
|
940
960
|
|
@@ -1188,7 +1208,7 @@ class FOClient:
|
|
1188
1208
|
"advanced_cache_enabled": True,
|
1189
1209
|
"cache_v2_enabled": True,
|
1190
1210
|
"cache_initialized": self._metadata_initialized,
|
1191
|
-
"sync_manager_available": self.sync_manager is not None,
|
1211
|
+
"sync_manager_available": self.sync_manager is not None or self._sync_session_manager is not None,
|
1192
1212
|
"background_sync_running": self._is_background_sync_running(),
|
1193
1213
|
"statistics": stats,
|
1194
1214
|
}
|