d365fo-client 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- d365fo_client/__init__.py +305 -0
- d365fo_client/auth.py +93 -0
- d365fo_client/cli.py +700 -0
- d365fo_client/client.py +1454 -0
- d365fo_client/config.py +304 -0
- d365fo_client/crud.py +200 -0
- d365fo_client/exceptions.py +49 -0
- d365fo_client/labels.py +528 -0
- d365fo_client/main.py +502 -0
- d365fo_client/mcp/__init__.py +16 -0
- d365fo_client/mcp/client_manager.py +276 -0
- d365fo_client/mcp/main.py +98 -0
- d365fo_client/mcp/models.py +371 -0
- d365fo_client/mcp/prompts/__init__.py +43 -0
- d365fo_client/mcp/prompts/action_execution.py +480 -0
- d365fo_client/mcp/prompts/sequence_analysis.py +349 -0
- d365fo_client/mcp/resources/__init__.py +15 -0
- d365fo_client/mcp/resources/database_handler.py +555 -0
- d365fo_client/mcp/resources/entity_handler.py +176 -0
- d365fo_client/mcp/resources/environment_handler.py +132 -0
- d365fo_client/mcp/resources/metadata_handler.py +283 -0
- d365fo_client/mcp/resources/query_handler.py +135 -0
- d365fo_client/mcp/server.py +432 -0
- d365fo_client/mcp/tools/__init__.py +17 -0
- d365fo_client/mcp/tools/connection_tools.py +175 -0
- d365fo_client/mcp/tools/crud_tools.py +579 -0
- d365fo_client/mcp/tools/database_tools.py +813 -0
- d365fo_client/mcp/tools/label_tools.py +189 -0
- d365fo_client/mcp/tools/metadata_tools.py +766 -0
- d365fo_client/mcp/tools/profile_tools.py +706 -0
- d365fo_client/metadata_api.py +793 -0
- d365fo_client/metadata_v2/__init__.py +59 -0
- d365fo_client/metadata_v2/cache_v2.py +1372 -0
- d365fo_client/metadata_v2/database_v2.py +585 -0
- d365fo_client/metadata_v2/global_version_manager.py +573 -0
- d365fo_client/metadata_v2/search_engine_v2.py +423 -0
- d365fo_client/metadata_v2/sync_manager_v2.py +819 -0
- d365fo_client/metadata_v2/version_detector.py +439 -0
- d365fo_client/models.py +862 -0
- d365fo_client/output.py +181 -0
- d365fo_client/profile_manager.py +342 -0
- d365fo_client/profiles.py +178 -0
- d365fo_client/query.py +162 -0
- d365fo_client/session.py +60 -0
- d365fo_client/utils.py +196 -0
- d365fo_client-0.1.0.dist-info/METADATA +1084 -0
- d365fo_client-0.1.0.dist-info/RECORD +51 -0
- d365fo_client-0.1.0.dist-info/WHEEL +5 -0
- d365fo_client-0.1.0.dist-info/entry_points.txt +3 -0
- d365fo_client-0.1.0.dist-info/licenses/LICENSE +21 -0
- d365fo_client-0.1.0.dist-info/top_level.txt +1 -0
d365fo_client/main.py
ADDED
@@ -0,0 +1,502 @@
|
|
1
|
+
"""Main module for d365fo-client package with example usage."""
|
2
|
+
|
3
|
+
import argparse
|
4
|
+
import asyncio
|
5
|
+
import sys
|
6
|
+
|
7
|
+
from .client import FOClient, create_client
|
8
|
+
from .models import FOClientConfig, QueryOptions
|
9
|
+
|
10
|
+
|
11
|
+
async def example_usage():
|
12
|
+
"""Example usage of the F&O client with label functionality"""
|
13
|
+
config = FOClientConfig(
|
14
|
+
base_url="https://usnconeboxax1aos.cloud.onebox.dynamics.com",
|
15
|
+
use_default_credentials=True,
|
16
|
+
verify_ssl=False,
|
17
|
+
use_label_cache=True,
|
18
|
+
label_cache_expiry_minutes=60,
|
19
|
+
)
|
20
|
+
|
21
|
+
async with FOClient(config) as client:
|
22
|
+
# Test connections
|
23
|
+
print("š Testing connections...")
|
24
|
+
if await client.test_connection():
|
25
|
+
print("ā
Connected to F&O OData successfully")
|
26
|
+
|
27
|
+
if await client.test_metadata_connection():
|
28
|
+
print("ā
Connected to F&O Metadata API successfully")
|
29
|
+
|
30
|
+
# Download metadata
|
31
|
+
print("\nš„ Downloading metadata...")
|
32
|
+
await client.download_metadata()
|
33
|
+
|
34
|
+
# Search entities
|
35
|
+
print("\nš Searching entities...")
|
36
|
+
customer_entities = await client.search_entities("customer")
|
37
|
+
print(f"Found {len(customer_entities)} customer-related entities")
|
38
|
+
for entity in customer_entities[:5]: # Show first 5
|
39
|
+
print(f" - {entity}")
|
40
|
+
|
41
|
+
# Get entity info with labels
|
42
|
+
print("\nš Getting entity information...")
|
43
|
+
customers_info = await client.get_entity_info_with_labels("Customer")
|
44
|
+
if customers_info:
|
45
|
+
print(f"Customers entity: {customers_info.name}")
|
46
|
+
if customers_info.label_text:
|
47
|
+
print(f"Entity label: '{customers_info.label_text}'")
|
48
|
+
print(f"Has {len(customers_info.properties)} properties")
|
49
|
+
|
50
|
+
# Show properties with labels
|
51
|
+
labeled_props = [p for p in customers_info.properties if p.label_text][:5]
|
52
|
+
if labeled_props:
|
53
|
+
print("Properties with labels:")
|
54
|
+
for prop in labeled_props:
|
55
|
+
print(f" {prop.name}: '{prop.label_text}'")
|
56
|
+
|
57
|
+
# Test label operations
|
58
|
+
print("\nš·ļø Label Operations:")
|
59
|
+
|
60
|
+
# Get specific labels
|
61
|
+
test_labels = ["@SYS78125", "@SYS9490", "@GLS63332"]
|
62
|
+
print("Fetching specific labels:")
|
63
|
+
for label_id in test_labels:
|
64
|
+
text = await client.get_label_text(label_id)
|
65
|
+
print(f" {label_id}: '{text}'")
|
66
|
+
|
67
|
+
# Show cache info
|
68
|
+
cache_info = client.get_label_cache_info()
|
69
|
+
print(f"Label cache: {cache_info}")
|
70
|
+
|
71
|
+
# Get entities with query options
|
72
|
+
print("\nš Querying entities...")
|
73
|
+
query_options = QueryOptions(
|
74
|
+
select=["CustomerAccount", "Name", "SalesCurrencyCode"],
|
75
|
+
top=5,
|
76
|
+
orderby=["Name"],
|
77
|
+
)
|
78
|
+
|
79
|
+
try:
|
80
|
+
customers = await client.get_entities("Customers", query_options)
|
81
|
+
print(f"Retrieved {len(customers.get('value', []))} customers")
|
82
|
+
for customer in customers.get("value", [])[:3]: # Show first 3
|
83
|
+
print(f" - {customer.get('CustomerAccount')}: {customer.get('Name')}")
|
84
|
+
except Exception as e:
|
85
|
+
print(f"Error querying customers: {e}")
|
86
|
+
|
87
|
+
# Search and call actions
|
88
|
+
print("\nā” Searching actions...")
|
89
|
+
calc_actions = await client.search_actions("calculate")
|
90
|
+
print(f"Found {len(calc_actions)} calculation actions")
|
91
|
+
for action in calc_actions[:5]: # Show first 5
|
92
|
+
print(f" - {action}")
|
93
|
+
|
94
|
+
print("\nš§ Calling actions...")
|
95
|
+
|
96
|
+
# Use the new dedicated methods for version information
|
97
|
+
print("Getting version information using dedicated methods...")
|
98
|
+
try:
|
99
|
+
app_version = await client.get_application_version()
|
100
|
+
print(f"Application Version: {app_version}")
|
101
|
+
except Exception as e:
|
102
|
+
print(f"Error getting application version: {e}")
|
103
|
+
|
104
|
+
try:
|
105
|
+
platform_build_version = await client.get_platform_build_version()
|
106
|
+
print(f"Platform Build Version: {platform_build_version}")
|
107
|
+
except Exception as e:
|
108
|
+
print(f"Error getting platform build version: {e}")
|
109
|
+
|
110
|
+
try:
|
111
|
+
app_build_version = await client.get_application_build_version()
|
112
|
+
print(f"Application Build Version: {app_build_version}")
|
113
|
+
except Exception as e:
|
114
|
+
print(f"Error getting application build version: {e}")
|
115
|
+
|
116
|
+
# Call other actions using generic call_action
|
117
|
+
entity_actions = {
|
118
|
+
"DocumentRoutingClientApps": ["GetPlatformVersion"],
|
119
|
+
}
|
120
|
+
|
121
|
+
for entity in entity_actions:
|
122
|
+
for action in entity_actions[entity]:
|
123
|
+
print(f"Calling action '{action}' on entity '{entity}'...")
|
124
|
+
result = await client.call_action(action, entity_name=entity)
|
125
|
+
print(f"Action '{action}' result: {result}")
|
126
|
+
|
127
|
+
# New Metadata APIs demonstration
|
128
|
+
print("\nš New Metadata APIs:")
|
129
|
+
|
130
|
+
# Data Entities API
|
131
|
+
print("\nš Data Entities API:")
|
132
|
+
data_entities = await client.search_data_entities(
|
133
|
+
"customer", entity_category="Master"
|
134
|
+
)
|
135
|
+
print(f"Found {len(data_entities)} customer Master data entities")
|
136
|
+
if data_entities:
|
137
|
+
entity = data_entities[0]
|
138
|
+
print(f" Example: {entity.name} -> {entity.public_collection_name}")
|
139
|
+
print(f" Category: {entity.entity_category}")
|
140
|
+
print(f" Label: {entity.label_text or entity.label_id}")
|
141
|
+
|
142
|
+
# Public Entities API
|
143
|
+
print("\nš Public Entities API:")
|
144
|
+
public_entities = await client.search_public_entities("customer")
|
145
|
+
print(f"Found {len(public_entities)} customer public entities")
|
146
|
+
if public_entities:
|
147
|
+
# Get detailed info for first entity
|
148
|
+
entity_detail = await client.get_public_entity_info(public_entities[0].name)
|
149
|
+
if entity_detail:
|
150
|
+
print(
|
151
|
+
f" {entity_detail.name}: {len(entity_detail.properties)} properties"
|
152
|
+
)
|
153
|
+
key_props = [p.name for p in entity_detail.properties if p.is_key]
|
154
|
+
print(f" Keys: {', '.join(key_props)}")
|
155
|
+
|
156
|
+
# Public Enumerations API
|
157
|
+
print("\nš¢ Public Enumerations API:")
|
158
|
+
enumerations = await client.search_public_enumerations("payment")
|
159
|
+
print(f"Found {len(enumerations)} payment-related enumerations")
|
160
|
+
if enumerations:
|
161
|
+
# Get detailed info for first enumeration
|
162
|
+
enum_detail = await client.get_public_enumeration_info(enumerations[0].name)
|
163
|
+
if enum_detail:
|
164
|
+
print(f" {enum_detail.name}: {len(enum_detail.members)} values")
|
165
|
+
print(f" Label: {enum_detail.label_text or enum_detail.label_id}")
|
166
|
+
if enum_detail.members:
|
167
|
+
print(
|
168
|
+
f" Sample values: {', '.join([f'{m.name}={m.value}' for m in enum_detail.members[:3]])}"
|
169
|
+
)
|
170
|
+
|
171
|
+
|
172
|
+
def create_argument_parser() -> argparse.ArgumentParser:
|
173
|
+
"""Create the enhanced argument parser with all CLI commands."""
|
174
|
+
from . import __author__, __email__, __version__
|
175
|
+
|
176
|
+
parser = argparse.ArgumentParser(
|
177
|
+
description="Microsoft Dynamics 365 Finance & Operations Client",
|
178
|
+
prog="d365fo-client",
|
179
|
+
)
|
180
|
+
|
181
|
+
# Global options (available for all commands)
|
182
|
+
parser.add_argument(
|
183
|
+
"--version",
|
184
|
+
action="version",
|
185
|
+
version=f"d365fo-client {__version__} by {__author__} ({__email__})",
|
186
|
+
)
|
187
|
+
parser.add_argument("--base-url", help="D365 F&O environment URL")
|
188
|
+
parser.add_argument(
|
189
|
+
"--auth-mode",
|
190
|
+
choices=["default", "explicit", "interactive"],
|
191
|
+
default="default",
|
192
|
+
help="Authentication mode (default: default)",
|
193
|
+
)
|
194
|
+
parser.add_argument("--client-id", help="Azure AD client ID")
|
195
|
+
parser.add_argument("--client-secret", help="Azure AD client secret")
|
196
|
+
parser.add_argument("--tenant-id", help="Azure AD tenant ID")
|
197
|
+
parser.add_argument(
|
198
|
+
"--verify-ssl", type=bool, default=True, help="SSL verification (default: true)"
|
199
|
+
)
|
200
|
+
parser.add_argument(
|
201
|
+
"--output",
|
202
|
+
choices=["json", "table", "csv", "yaml"],
|
203
|
+
default="table",
|
204
|
+
help="Output format (default: table)",
|
205
|
+
)
|
206
|
+
parser.add_argument("--verbose", "-v", action="store_true", help="Verbose output")
|
207
|
+
parser.add_argument(
|
208
|
+
"--quiet", "-q", action="store_true", help="Suppress non-essential output"
|
209
|
+
)
|
210
|
+
parser.add_argument("--config", help="Configuration file path")
|
211
|
+
parser.add_argument("--profile", help="Configuration profile to use")
|
212
|
+
parser.add_argument(
|
213
|
+
"--label-cache",
|
214
|
+
type=bool,
|
215
|
+
default=True,
|
216
|
+
help="Enable label caching (default: true)",
|
217
|
+
)
|
218
|
+
parser.add_argument(
|
219
|
+
"--label-expiry",
|
220
|
+
type=int,
|
221
|
+
default=60,
|
222
|
+
help="Label cache expiry in minutes (default: 60)",
|
223
|
+
)
|
224
|
+
|
225
|
+
# Legacy option for backward compatibility
|
226
|
+
parser.add_argument(
|
227
|
+
"--demo", action="store_true", help="Run the demo/example usage"
|
228
|
+
)
|
229
|
+
|
230
|
+
# Subcommands
|
231
|
+
subparsers = parser.add_subparsers(dest="command", help="Available commands")
|
232
|
+
|
233
|
+
# Add command subparsers
|
234
|
+
_add_test_command(subparsers)
|
235
|
+
_add_version_command(subparsers)
|
236
|
+
_add_metadata_commands(subparsers)
|
237
|
+
_add_entity_commands(subparsers)
|
238
|
+
_add_action_commands(subparsers)
|
239
|
+
_add_config_commands(subparsers)
|
240
|
+
|
241
|
+
return parser
|
242
|
+
|
243
|
+
|
244
|
+
def _add_test_command(subparsers) -> None:
|
245
|
+
"""Add test connectivity command."""
|
246
|
+
test_parser = subparsers.add_parser(
|
247
|
+
"test", help="Test connectivity to D365 F&O environment"
|
248
|
+
)
|
249
|
+
test_parser.add_argument(
|
250
|
+
"--odata-only", action="store_true", help="Test only OData API connectivity"
|
251
|
+
)
|
252
|
+
test_parser.add_argument(
|
253
|
+
"--metadata-only",
|
254
|
+
action="store_true",
|
255
|
+
help="Test only Metadata API connectivity",
|
256
|
+
)
|
257
|
+
test_parser.add_argument(
|
258
|
+
"--timeout",
|
259
|
+
type=int,
|
260
|
+
default=30,
|
261
|
+
help="Connection timeout in seconds (default: 30)",
|
262
|
+
)
|
263
|
+
|
264
|
+
|
265
|
+
def _add_version_command(subparsers) -> None:
|
266
|
+
"""Add version information command."""
|
267
|
+
version_parser = subparsers.add_parser(
|
268
|
+
"version", help="Get environment version information"
|
269
|
+
)
|
270
|
+
version_parser.add_argument(
|
271
|
+
"--application", action="store_true", help="Get application version"
|
272
|
+
)
|
273
|
+
version_parser.add_argument(
|
274
|
+
"--platform", action="store_true", help="Get platform build version"
|
275
|
+
)
|
276
|
+
version_parser.add_argument(
|
277
|
+
"--build", action="store_true", help="Get application build version"
|
278
|
+
)
|
279
|
+
version_parser.add_argument(
|
280
|
+
"--all", action="store_true", help="Get all version information"
|
281
|
+
)
|
282
|
+
|
283
|
+
|
284
|
+
def _add_metadata_commands(subparsers) -> None:
|
285
|
+
"""Add metadata operation commands."""
|
286
|
+
metadata_parser = subparsers.add_parser("metadata", help="Metadata operations")
|
287
|
+
metadata_subs = metadata_parser.add_subparsers(
|
288
|
+
dest="metadata_subcommand", help="Metadata subcommands"
|
289
|
+
)
|
290
|
+
|
291
|
+
# sync subcommand
|
292
|
+
sync_parser = metadata_subs.add_parser("sync", help="Sync metadata to cache")
|
293
|
+
sync_parser.add_argument(
|
294
|
+
"--force", action="store_true", help="Force refresh of metadata cache"
|
295
|
+
)
|
296
|
+
|
297
|
+
# search subcommand
|
298
|
+
search_parser = metadata_subs.add_parser(
|
299
|
+
"search", help="Search metadata by pattern"
|
300
|
+
)
|
301
|
+
search_parser.add_argument("pattern", help="Search pattern")
|
302
|
+
search_parser.add_argument(
|
303
|
+
"--type",
|
304
|
+
choices=["entities", "actions", "all"],
|
305
|
+
default="entities",
|
306
|
+
help="Type of metadata to search (default: entities)",
|
307
|
+
)
|
308
|
+
search_parser.add_argument("--limit", type=int, help="Maximum number of results")
|
309
|
+
|
310
|
+
# info subcommand
|
311
|
+
info_parser = metadata_subs.add_parser("info", help="Get entity metadata details")
|
312
|
+
info_parser.add_argument("entity_name", help="Entity name")
|
313
|
+
info_parser.add_argument(
|
314
|
+
"--properties", action="store_true", help="Include property details"
|
315
|
+
)
|
316
|
+
info_parser.add_argument(
|
317
|
+
"--keys", action="store_true", help="Include key information"
|
318
|
+
)
|
319
|
+
info_parser.add_argument(
|
320
|
+
"--labels", action="store_true", help="Include label information"
|
321
|
+
)
|
322
|
+
|
323
|
+
|
324
|
+
def _add_entity_commands(subparsers) -> None:
|
325
|
+
"""Add entity operation commands."""
|
326
|
+
entity_parser = subparsers.add_parser("entity", help="Entity operations")
|
327
|
+
entity_subs = entity_parser.add_subparsers(
|
328
|
+
dest="entity_subcommand", help="Entity subcommands"
|
329
|
+
)
|
330
|
+
|
331
|
+
# get subcommand
|
332
|
+
get_parser = entity_subs.add_parser("get", help="Get entity data")
|
333
|
+
get_parser.add_argument("entity_name", help="Entity name")
|
334
|
+
get_parser.add_argument(
|
335
|
+
"key", nargs="?", help="Entity key (optional, for single record)"
|
336
|
+
)
|
337
|
+
get_parser.add_argument("--select", help="Fields to select (comma-separated)")
|
338
|
+
get_parser.add_argument("--filter", help="OData filter expression")
|
339
|
+
get_parser.add_argument("--top", type=int, help="Maximum number of records")
|
340
|
+
get_parser.add_argument("--orderby", help="Order by fields (comma-separated)")
|
341
|
+
|
342
|
+
# create subcommand
|
343
|
+
create_parser = entity_subs.add_parser("create", help="Create entity record")
|
344
|
+
create_parser.add_argument("entity_name", help="Entity name")
|
345
|
+
create_parser.add_argument("--data", help="Entity data as JSON string")
|
346
|
+
create_parser.add_argument(
|
347
|
+
"--file", help="Path to JSON file containing entity data"
|
348
|
+
)
|
349
|
+
|
350
|
+
# update subcommand
|
351
|
+
update_parser = entity_subs.add_parser("update", help="Update entity record")
|
352
|
+
update_parser.add_argument("entity_name", help="Entity name")
|
353
|
+
update_parser.add_argument("key", help="Entity key")
|
354
|
+
update_parser.add_argument("--data", help="Entity data as JSON string")
|
355
|
+
update_parser.add_argument(
|
356
|
+
"--file", help="Path to JSON file containing entity data"
|
357
|
+
)
|
358
|
+
|
359
|
+
# delete subcommand
|
360
|
+
delete_parser = entity_subs.add_parser("delete", help="Delete entity record")
|
361
|
+
delete_parser.add_argument("entity_name", help="Entity name")
|
362
|
+
delete_parser.add_argument("key", help="Entity key")
|
363
|
+
delete_parser.add_argument(
|
364
|
+
"--confirm", action="store_true", help="Skip confirmation prompt"
|
365
|
+
)
|
366
|
+
|
367
|
+
|
368
|
+
def _add_action_commands(subparsers) -> None:
|
369
|
+
"""Add action operation commands."""
|
370
|
+
action_parser = subparsers.add_parser("action", help="Action operations")
|
371
|
+
action_subs = action_parser.add_subparsers(
|
372
|
+
dest="action_subcommand", help="Action subcommands"
|
373
|
+
)
|
374
|
+
|
375
|
+
# list subcommand
|
376
|
+
list_parser = action_subs.add_parser("list", help="List available actions")
|
377
|
+
list_parser.add_argument(
|
378
|
+
"pattern", nargs="?", default="", help="Search pattern (optional)"
|
379
|
+
)
|
380
|
+
list_parser.add_argument("--entity", help="Filter actions for specific entity")
|
381
|
+
|
382
|
+
# call subcommand
|
383
|
+
call_parser = action_subs.add_parser("call", help="Call OData action")
|
384
|
+
call_parser.add_argument("action_name", help="Action name")
|
385
|
+
call_parser.add_argument(
|
386
|
+
"--entity", help="Entity name (if action is entity-specific)"
|
387
|
+
)
|
388
|
+
call_parser.add_argument("--parameters", help="Action parameters as JSON string")
|
389
|
+
|
390
|
+
|
391
|
+
def _add_config_commands(subparsers) -> None:
|
392
|
+
"""Add configuration management commands."""
|
393
|
+
config_parser = subparsers.add_parser(
|
394
|
+
"config", help="Manage configuration profiles"
|
395
|
+
)
|
396
|
+
config_subs = config_parser.add_subparsers(
|
397
|
+
dest="config_subcommand", help="Configuration subcommands"
|
398
|
+
)
|
399
|
+
|
400
|
+
# list subcommand
|
401
|
+
config_subs.add_parser("list", help="List all configuration profiles")
|
402
|
+
|
403
|
+
# show subcommand
|
404
|
+
show_parser = config_subs.add_parser("show", help="Show profile configuration")
|
405
|
+
show_parser.add_argument("profile_name", help="Profile name")
|
406
|
+
|
407
|
+
# create subcommand
|
408
|
+
create_parser = config_subs.add_parser("create", help="Create new profile")
|
409
|
+
create_parser.add_argument("profile_name", help="Profile name")
|
410
|
+
create_parser.add_argument(
|
411
|
+
"--base-url", required=True, help="D365 F&O environment URL"
|
412
|
+
)
|
413
|
+
create_parser.add_argument(
|
414
|
+
"--auth-mode",
|
415
|
+
choices=["default", "explicit", "interactive"],
|
416
|
+
default="default",
|
417
|
+
help="Authentication mode",
|
418
|
+
)
|
419
|
+
create_parser.add_argument("--client-id", help="Azure AD client ID")
|
420
|
+
create_parser.add_argument("--client-secret", help="Azure AD client secret")
|
421
|
+
create_parser.add_argument("--tenant-id", help="Azure AD tenant ID")
|
422
|
+
create_parser.add_argument(
|
423
|
+
"--verify-ssl", type=bool, default=True, help="SSL verification"
|
424
|
+
)
|
425
|
+
create_parser.add_argument(
|
426
|
+
"--output-format",
|
427
|
+
choices=["json", "table", "csv", "yaml"],
|
428
|
+
default="table",
|
429
|
+
help="Default output format",
|
430
|
+
)
|
431
|
+
create_parser.add_argument(
|
432
|
+
"--label-cache", type=bool, default=True, help="Enable label caching"
|
433
|
+
)
|
434
|
+
create_parser.add_argument(
|
435
|
+
"--label-expiry", type=int, default=60, help="Label cache expiry in minutes"
|
436
|
+
)
|
437
|
+
create_parser.add_argument("--language", default="en-US", help="Language code")
|
438
|
+
|
439
|
+
# update subcommand (placeholder)
|
440
|
+
update_parser = config_subs.add_parser("update", help="Update existing profile")
|
441
|
+
update_parser.add_argument("profile_name", help="Profile name")
|
442
|
+
|
443
|
+
# delete subcommand
|
444
|
+
delete_parser = config_subs.add_parser("delete", help="Delete profile")
|
445
|
+
delete_parser.add_argument("profile_name", help="Profile name")
|
446
|
+
|
447
|
+
# set-default subcommand
|
448
|
+
default_parser = config_subs.add_parser("set-default", help="Set default profile")
|
449
|
+
default_parser.add_argument("profile_name", help="Profile name")
|
450
|
+
|
451
|
+
|
452
|
+
def main() -> None:
|
453
|
+
"""Enhanced main entry point with CLI support."""
|
454
|
+
parser = create_argument_parser()
|
455
|
+
args = parser.parse_args()
|
456
|
+
|
457
|
+
# Handle legacy demo mode or no arguments
|
458
|
+
if (
|
459
|
+
args.demo
|
460
|
+
or (not hasattr(args, "command") or not args.command)
|
461
|
+
and len(sys.argv) == 1
|
462
|
+
):
|
463
|
+
# Run demo if --demo specified or no arguments provided
|
464
|
+
print("Microsoft Dynamics 365 Finance & Operations Client")
|
465
|
+
print("=" * 50)
|
466
|
+
|
467
|
+
try:
|
468
|
+
asyncio.run(example_usage())
|
469
|
+
except KeyboardInterrupt:
|
470
|
+
print("\n\nOperation cancelled by user")
|
471
|
+
except Exception as e:
|
472
|
+
print(f"\n\nError: {e}")
|
473
|
+
return
|
474
|
+
|
475
|
+
# If no command specified but other arguments provided, show help
|
476
|
+
if not hasattr(args, "command") or not args.command:
|
477
|
+
parser.print_help()
|
478
|
+
return
|
479
|
+
|
480
|
+
# Create and run CLI manager
|
481
|
+
from .cli import CLIManager
|
482
|
+
|
483
|
+
cli_manager = CLIManager()
|
484
|
+
|
485
|
+
try:
|
486
|
+
exit_code = asyncio.run(cli_manager.execute_command(args))
|
487
|
+
sys.exit(exit_code)
|
488
|
+
except KeyboardInterrupt:
|
489
|
+
print("\n\nOperation cancelled by user")
|
490
|
+
sys.exit(130)
|
491
|
+
except Exception as e:
|
492
|
+
if getattr(args, "verbose", False):
|
493
|
+
import traceback
|
494
|
+
|
495
|
+
traceback.print_exc()
|
496
|
+
else:
|
497
|
+
print(f"Unexpected error: {e}")
|
498
|
+
sys.exit(1)
|
499
|
+
|
500
|
+
|
501
|
+
if __name__ == "__main__":
|
502
|
+
main()
|
@@ -0,0 +1,16 @@
|
|
1
|
+
"""MCP Server for d365fo-client.
|
2
|
+
|
3
|
+
This module provides a Model Context Protocol (MCP) server that exposes the full
|
4
|
+
capabilities of the d365fo-client to AI assistants and other MCP-compatible tools.
|
5
|
+
|
6
|
+
The server enables sophisticated Microsoft Dynamics 365 Finance & Operations
|
7
|
+
integration workflows through standardized MCP protocol.
|
8
|
+
"""
|
9
|
+
|
10
|
+
from .client_manager import D365FOClientManager
|
11
|
+
from .server import D365FOMCPServer
|
12
|
+
|
13
|
+
__all__ = [
|
14
|
+
"D365FOMCPServer",
|
15
|
+
"D365FOClientManager",
|
16
|
+
]
|