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.
- {d365fo_client-0.2.1/src/d365fo_client.egg-info → d365fo_client-0.2.3}/PKG-INFO +48 -17
- {d365fo_client-0.2.1 → d365fo_client-0.2.3}/README.md +46 -16
- {d365fo_client-0.2.1 → d365fo_client-0.2.3}/pyproject.toml +2 -1
- {d365fo_client-0.2.1 → d365fo_client-0.2.3}/src/d365fo_client/__init__.py +4 -48
- {d365fo_client-0.2.1 → d365fo_client-0.2.3}/src/d365fo_client/auth.py +48 -9
- {d365fo_client-0.2.1 → d365fo_client-0.2.3}/src/d365fo_client/client.py +84 -44
- d365fo_client-0.2.3/src/d365fo_client/credential_sources.py +431 -0
- {d365fo_client-0.2.1 → d365fo_client-0.2.3}/src/d365fo_client/mcp/client_manager.py +8 -0
- {d365fo_client-0.2.1 → d365fo_client-0.2.3}/src/d365fo_client/mcp/main.py +39 -17
- {d365fo_client-0.2.1 → d365fo_client-0.2.3}/src/d365fo_client/mcp/models.py +2 -2
- {d365fo_client-0.2.1 → d365fo_client-0.2.3}/src/d365fo_client/mcp/server.py +69 -22
- {d365fo_client-0.2.1 → d365fo_client-0.2.3}/src/d365fo_client/mcp/tools/__init__.py +2 -0
- {d365fo_client-0.2.1 → d365fo_client-0.2.3}/src/d365fo_client/mcp/tools/connection_tools.py +7 -0
- {d365fo_client-0.2.1 → 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.1 → d365fo_client-0.2.3}/src/d365fo_client/metadata_api.py +68 -1
- {d365fo_client-0.2.1 → d365fo_client-0.2.3}/src/d365fo_client/metadata_v2/cache_v2.py +26 -19
- {d365fo_client-0.2.1 → d365fo_client-0.2.3}/src/d365fo_client/metadata_v2/database_v2.py +93 -0
- {d365fo_client-0.2.1 → d365fo_client-0.2.3}/src/d365fo_client/metadata_v2/global_version_manager.py +62 -4
- {d365fo_client-0.2.1 → 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.1 → d365fo_client-0.2.3}/src/d365fo_client/models.py +41 -13
- {d365fo_client-0.2.1 → d365fo_client-0.2.3}/src/d365fo_client/profile_manager.py +7 -1
- {d365fo_client-0.2.1 → 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.1 → d365fo_client-0.2.3/src/d365fo_client.egg-info}/PKG-INFO +48 -17
- {d365fo_client-0.2.1 → d365fo_client-0.2.3}/src/d365fo_client.egg-info/SOURCES.txt +4 -0
- {d365fo_client-0.2.1 → d365fo_client-0.2.3}/src/d365fo_client.egg-info/requires.txt +1 -0
- {d365fo_client-0.2.1 → d365fo_client-0.2.3}/LICENSE +0 -0
- {d365fo_client-0.2.1 → d365fo_client-0.2.3}/setup.cfg +0 -0
- {d365fo_client-0.2.1 → d365fo_client-0.2.3}/src/d365fo_client/cli.py +0 -0
- {d365fo_client-0.2.1 → d365fo_client-0.2.3}/src/d365fo_client/config.py +0 -0
- {d365fo_client-0.2.1 → d365fo_client-0.2.3}/src/d365fo_client/crud.py +0 -0
- {d365fo_client-0.2.1 → d365fo_client-0.2.3}/src/d365fo_client/exceptions.py +0 -0
- {d365fo_client-0.2.1 → d365fo_client-0.2.3}/src/d365fo_client/labels.py +0 -0
- {d365fo_client-0.2.1 → d365fo_client-0.2.3}/src/d365fo_client/main.py +0 -0
- {d365fo_client-0.2.1 → d365fo_client-0.2.3}/src/d365fo_client/mcp/__init__.py +0 -0
- {d365fo_client-0.2.1 → d365fo_client-0.2.3}/src/d365fo_client/mcp/prompts/__init__.py +0 -0
- {d365fo_client-0.2.1 → d365fo_client-0.2.3}/src/d365fo_client/mcp/prompts/action_execution.py +0 -0
- {d365fo_client-0.2.1 → d365fo_client-0.2.3}/src/d365fo_client/mcp/prompts/sequence_analysis.py +0 -0
- {d365fo_client-0.2.1 → d365fo_client-0.2.3}/src/d365fo_client/mcp/resources/__init__.py +0 -0
- {d365fo_client-0.2.1 → d365fo_client-0.2.3}/src/d365fo_client/mcp/resources/database_handler.py +0 -0
- {d365fo_client-0.2.1 → d365fo_client-0.2.3}/src/d365fo_client/mcp/resources/entity_handler.py +0 -0
- {d365fo_client-0.2.1 → d365fo_client-0.2.3}/src/d365fo_client/mcp/resources/environment_handler.py +0 -0
- {d365fo_client-0.2.1 → d365fo_client-0.2.3}/src/d365fo_client/mcp/resources/metadata_handler.py +0 -0
- {d365fo_client-0.2.1 → d365fo_client-0.2.3}/src/d365fo_client/mcp/resources/query_handler.py +0 -0
- {d365fo_client-0.2.1 → d365fo_client-0.2.3}/src/d365fo_client/mcp/tools/crud_tools.py +0 -0
- {d365fo_client-0.2.1 → d365fo_client-0.2.3}/src/d365fo_client/mcp/tools/database_tools.py +0 -0
- {d365fo_client-0.2.1 → d365fo_client-0.2.3}/src/d365fo_client/mcp/tools/label_tools.py +0 -0
- {d365fo_client-0.2.1 → d365fo_client-0.2.3}/src/d365fo_client/mcp/tools/metadata_tools.py +0 -0
- {d365fo_client-0.2.1 → d365fo_client-0.2.3}/src/d365fo_client/metadata_v2/__init__.py +0 -0
- {d365fo_client-0.2.1 → d365fo_client-0.2.3}/src/d365fo_client/metadata_v2/label_utils.py +0 -0
- {d365fo_client-0.2.1 → d365fo_client-0.2.3}/src/d365fo_client/metadata_v2/search_engine_v2.py +0 -0
- {d365fo_client-0.2.1 → d365fo_client-0.2.3}/src/d365fo_client/metadata_v2/version_detector.py +0 -0
- {d365fo_client-0.2.1 → d365fo_client-0.2.3}/src/d365fo_client/output.py +0 -0
- {d365fo_client-0.2.1 → d365fo_client-0.2.3}/src/d365fo_client/query.py +0 -0
- {d365fo_client-0.2.1 → d365fo_client-0.2.3}/src/d365fo_client/session.py +0 -0
- {d365fo_client-0.2.1 → d365fo_client-0.2.3}/src/d365fo_client/utils.py +0 -0
- {d365fo_client-0.2.1 → d365fo_client-0.2.3}/src/d365fo_client.egg-info/dependency_links.txt +0 -0
- {d365fo_client-0.2.1 → d365fo_client-0.2.3}/src/d365fo_client.egg-info/entry_points.txt +0 -0
- {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.
|
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",
|
@@ -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
|
-
|
186
|
-
|
187
|
-
|
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
|
-
|
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.
|
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}
|