d365fo-client 0.2.1__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.
Files changed (61) hide show
  1. {d365fo_client-0.2.1/src/d365fo_client.egg-info → d365fo_client-0.2.3}/PKG-INFO +48 -17
  2. {d365fo_client-0.2.1 → d365fo_client-0.2.3}/README.md +46 -16
  3. {d365fo_client-0.2.1 → d365fo_client-0.2.3}/pyproject.toml +2 -1
  4. {d365fo_client-0.2.1 → d365fo_client-0.2.3}/src/d365fo_client/__init__.py +4 -48
  5. {d365fo_client-0.2.1 → d365fo_client-0.2.3}/src/d365fo_client/auth.py +48 -9
  6. {d365fo_client-0.2.1 → d365fo_client-0.2.3}/src/d365fo_client/client.py +84 -44
  7. d365fo_client-0.2.3/src/d365fo_client/credential_sources.py +431 -0
  8. {d365fo_client-0.2.1 → d365fo_client-0.2.3}/src/d365fo_client/mcp/client_manager.py +8 -0
  9. {d365fo_client-0.2.1 → d365fo_client-0.2.3}/src/d365fo_client/mcp/main.py +39 -17
  10. {d365fo_client-0.2.1 → d365fo_client-0.2.3}/src/d365fo_client/mcp/models.py +2 -2
  11. {d365fo_client-0.2.1 → d365fo_client-0.2.3}/src/d365fo_client/mcp/server.py +69 -22
  12. {d365fo_client-0.2.1 → d365fo_client-0.2.3}/src/d365fo_client/mcp/tools/__init__.py +2 -0
  13. {d365fo_client-0.2.1 → d365fo_client-0.2.3}/src/d365fo_client/mcp/tools/connection_tools.py +7 -0
  14. {d365fo_client-0.2.1 → d365fo_client-0.2.3}/src/d365fo_client/mcp/tools/profile_tools.py +261 -2
  15. d365fo_client-0.2.3/src/d365fo_client/mcp/tools/sync_tools.py +503 -0
  16. {d365fo_client-0.2.1 → d365fo_client-0.2.3}/src/d365fo_client/metadata_api.py +68 -1
  17. {d365fo_client-0.2.1 → d365fo_client-0.2.3}/src/d365fo_client/metadata_v2/cache_v2.py +26 -19
  18. {d365fo_client-0.2.1 → d365fo_client-0.2.3}/src/d365fo_client/metadata_v2/database_v2.py +93 -0
  19. {d365fo_client-0.2.1 → d365fo_client-0.2.3}/src/d365fo_client/metadata_v2/global_version_manager.py +62 -4
  20. {d365fo_client-0.2.1 → d365fo_client-0.2.3}/src/d365fo_client/metadata_v2/sync_manager_v2.py +1 -1
  21. d365fo_client-0.2.3/src/d365fo_client/metadata_v2/sync_session_manager.py +1043 -0
  22. {d365fo_client-0.2.1 → d365fo_client-0.2.3}/src/d365fo_client/models.py +41 -13
  23. {d365fo_client-0.2.1 → d365fo_client-0.2.3}/src/d365fo_client/profile_manager.py +7 -1
  24. {d365fo_client-0.2.1 → d365fo_client-0.2.3}/src/d365fo_client/profiles.py +28 -1
  25. d365fo_client-0.2.3/src/d365fo_client/sync_models.py +181 -0
  26. {d365fo_client-0.2.1 → d365fo_client-0.2.3/src/d365fo_client.egg-info}/PKG-INFO +48 -17
  27. {d365fo_client-0.2.1 → d365fo_client-0.2.3}/src/d365fo_client.egg-info/SOURCES.txt +4 -0
  28. {d365fo_client-0.2.1 → d365fo_client-0.2.3}/src/d365fo_client.egg-info/requires.txt +1 -0
  29. {d365fo_client-0.2.1 → d365fo_client-0.2.3}/LICENSE +0 -0
  30. {d365fo_client-0.2.1 → d365fo_client-0.2.3}/setup.cfg +0 -0
  31. {d365fo_client-0.2.1 → d365fo_client-0.2.3}/src/d365fo_client/cli.py +0 -0
  32. {d365fo_client-0.2.1 → d365fo_client-0.2.3}/src/d365fo_client/config.py +0 -0
  33. {d365fo_client-0.2.1 → d365fo_client-0.2.3}/src/d365fo_client/crud.py +0 -0
  34. {d365fo_client-0.2.1 → d365fo_client-0.2.3}/src/d365fo_client/exceptions.py +0 -0
  35. {d365fo_client-0.2.1 → d365fo_client-0.2.3}/src/d365fo_client/labels.py +0 -0
  36. {d365fo_client-0.2.1 → d365fo_client-0.2.3}/src/d365fo_client/main.py +0 -0
  37. {d365fo_client-0.2.1 → d365fo_client-0.2.3}/src/d365fo_client/mcp/__init__.py +0 -0
  38. {d365fo_client-0.2.1 → d365fo_client-0.2.3}/src/d365fo_client/mcp/prompts/__init__.py +0 -0
  39. {d365fo_client-0.2.1 → d365fo_client-0.2.3}/src/d365fo_client/mcp/prompts/action_execution.py +0 -0
  40. {d365fo_client-0.2.1 → d365fo_client-0.2.3}/src/d365fo_client/mcp/prompts/sequence_analysis.py +0 -0
  41. {d365fo_client-0.2.1 → d365fo_client-0.2.3}/src/d365fo_client/mcp/resources/__init__.py +0 -0
  42. {d365fo_client-0.2.1 → d365fo_client-0.2.3}/src/d365fo_client/mcp/resources/database_handler.py +0 -0
  43. {d365fo_client-0.2.1 → d365fo_client-0.2.3}/src/d365fo_client/mcp/resources/entity_handler.py +0 -0
  44. {d365fo_client-0.2.1 → d365fo_client-0.2.3}/src/d365fo_client/mcp/resources/environment_handler.py +0 -0
  45. {d365fo_client-0.2.1 → d365fo_client-0.2.3}/src/d365fo_client/mcp/resources/metadata_handler.py +0 -0
  46. {d365fo_client-0.2.1 → d365fo_client-0.2.3}/src/d365fo_client/mcp/resources/query_handler.py +0 -0
  47. {d365fo_client-0.2.1 → d365fo_client-0.2.3}/src/d365fo_client/mcp/tools/crud_tools.py +0 -0
  48. {d365fo_client-0.2.1 → d365fo_client-0.2.3}/src/d365fo_client/mcp/tools/database_tools.py +0 -0
  49. {d365fo_client-0.2.1 → d365fo_client-0.2.3}/src/d365fo_client/mcp/tools/label_tools.py +0 -0
  50. {d365fo_client-0.2.1 → d365fo_client-0.2.3}/src/d365fo_client/mcp/tools/metadata_tools.py +0 -0
  51. {d365fo_client-0.2.1 → d365fo_client-0.2.3}/src/d365fo_client/metadata_v2/__init__.py +0 -0
  52. {d365fo_client-0.2.1 → d365fo_client-0.2.3}/src/d365fo_client/metadata_v2/label_utils.py +0 -0
  53. {d365fo_client-0.2.1 → d365fo_client-0.2.3}/src/d365fo_client/metadata_v2/search_engine_v2.py +0 -0
  54. {d365fo_client-0.2.1 → d365fo_client-0.2.3}/src/d365fo_client/metadata_v2/version_detector.py +0 -0
  55. {d365fo_client-0.2.1 → d365fo_client-0.2.3}/src/d365fo_client/output.py +0 -0
  56. {d365fo_client-0.2.1 → d365fo_client-0.2.3}/src/d365fo_client/query.py +0 -0
  57. {d365fo_client-0.2.1 → d365fo_client-0.2.3}/src/d365fo_client/session.py +0 -0
  58. {d365fo_client-0.2.1 → d365fo_client-0.2.3}/src/d365fo_client/utils.py +0 -0
  59. {d365fo_client-0.2.1 → d365fo_client-0.2.3}/src/d365fo_client.egg-info/dependency_links.txt +0 -0
  60. {d365fo_client-0.2.1 → d365fo_client-0.2.3}/src/d365fo_client.egg-info/entry_points.txt +0 -0
  61. {d365fo_client-0.2.1 → 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.1
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 and service principal support
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: "${AZURE_CLIENT_ID}"
162
- client_secret: "${AZURE_CLIENT_SECRET}"
163
- tenant_id: "${AZURE_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: With custom settings
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
- AZURE_CLIENT_ID=your-client-id
623
- AZURE_CLIENT_SECRET=your-client-secret
624
- AZURE_TENANT_ID=your-tenant-id
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 AZURE_CLIENT_ID="your-client-id" # Optional with default credentials
711
- export AZURE_CLIENT_SECRET="your-client-secret" # Optional with default credentials
712
- export AZURE_TENANT_ID="your-tenant-id" # Optional with default credentials
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 AZURE_CLIENT_ID="your-client-id"
861
- export AZURE_CLIENT_SECRET="your-client-secret"
862
- export AZURE_TENANT_ID="your-tenant-id"
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 AZURE_CLIENT_ID="your-client-id"
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 and service principal support
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: "${AZURE_CLIENT_ID}"
121
- client_secret: "${AZURE_CLIENT_SECRET}"
122
- tenant_id: "${AZURE_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: With custom settings
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
- AZURE_CLIENT_ID=your-client-id
582
- AZURE_CLIENT_SECRET=your-client-secret
583
- AZURE_TENANT_ID=your-tenant-id
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 AZURE_CLIENT_ID="your-client-id" # Optional with default credentials
670
- export AZURE_CLIENT_SECRET="your-client-secret" # Optional with default credentials
671
- export AZURE_TENANT_ID="your-tenant-id" # Optional with default credentials
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 AZURE_CLIENT_ID="your-client-id"
820
- export AZURE_CLIENT_SECRET="your-client-secret"
821
- export AZURE_TENANT_ID="your-tenant-id"
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 AZURE_CLIENT_ID="your-client-id"
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.1"
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",
@@ -174,55 +174,13 @@ from .main import main
174
174
  # MCP Server
175
175
  from .mcp import D365FOClientManager, D365FOMCPServer
176
176
 
177
- # Legacy Metadata Cache (deprecated - use metadata_v2)
178
- # REMOVED: Legacy classes have been replaced with V2 implementations
179
- # from .metadata_cache import MetadataCache, MetadataSearchEngine
180
-
181
177
  # V2 Metadata Cache (recommended - now the only implementation)
182
178
  from .metadata_v2 import MetadataCacheV2, VersionAwareSearchEngine
183
179
 
184
180
  # Provide backward compatibility with immediate import errors
185
- import warnings
186
-
187
- class _DeprecatedMetadataCache:
188
- """Deprecated placeholder for MetadataCache - raises error on any access"""
189
- def __init__(self, *args, **kwargs):
190
- warnings.warn(
191
- "MetadataCache is deprecated and has been removed. "
192
- "Use MetadataCacheV2 from d365fo_client.metadata_v2 instead.",
193
- DeprecationWarning,
194
- stacklevel=2
195
- )
196
- raise ImportError(
197
- "MetadataCache has been removed. Use MetadataCacheV2 from d365fo_client.metadata_v2 instead."
198
- )
199
-
200
- def __getattr__(self, name):
201
- raise ImportError(
202
- "MetadataCache has been removed. Use MetadataCacheV2 from d365fo_client.metadata_v2 instead."
203
- )
204
-
205
- class _DeprecatedMetadataSearchEngine:
206
- """Deprecated placeholder for MetadataSearchEngine - raises error on any access"""
207
- def __init__(self, *args, **kwargs):
208
- warnings.warn(
209
- "MetadataSearchEngine is deprecated and has been removed. "
210
- "Use VersionAwareSearchEngine from d365fo_client.metadata_v2 instead.",
211
- DeprecationWarning,
212
- stacklevel=2
213
- )
214
- raise ImportError(
215
- "MetadataSearchEngine has been removed. Use VersionAwareSearchEngine from d365fo_client.metadata_v2 instead."
216
- )
217
-
218
- def __getattr__(self, name):
219
- raise ImportError(
220
- "MetadataSearchEngine has been removed. Use VersionAwareSearchEngine from d365fo_client.metadata_v2 instead."
221
- )
222
-
223
- # Create deprecated placeholder classes
224
- MetadataCache = _DeprecatedMetadataCache
225
- MetadataSearchEngine = _DeprecatedMetadataSearchEngine
181
+
182
+
183
+
226
184
  from .models import (
227
185
  ActionInfo,
228
186
  DataEntityInfo,
@@ -255,9 +213,7 @@ __all__ = [
255
213
  # Main client
256
214
  "FOClient",
257
215
  "create_client",
258
- # Legacy caching (deprecated placeholders - raise errors when used)
259
- "MetadataCache",
260
- "MetadataSearchEngine",
216
+
261
217
  # V2 caching (now the primary implementation)
262
218
  "MetadataCacheV2",
263
219
  "VersionAwareSearchEngine",
@@ -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.credential = self._setup_credentials()
24
-
25
- def _setup_credentials(self):
26
- """Setup authentication credentials"""
27
- if self.config.use_default_credentials:
28
- return DefaultAzureCredential()
29
- elif (
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
- return ClientSecretCredential(
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 or client credentials"
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}