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
@@ -0,0 +1,176 @@
|
|
1
|
+
"""Entity resource handler for MCP server."""
|
2
|
+
|
3
|
+
import json
|
4
|
+
import logging
|
5
|
+
from datetime import datetime
|
6
|
+
from time import timezone
|
7
|
+
from typing import List
|
8
|
+
|
9
|
+
from mcp.types import Resource
|
10
|
+
|
11
|
+
from ..client_manager import D365FOClientManager
|
12
|
+
|
13
|
+
logger = logging.getLogger(__name__)
|
14
|
+
|
15
|
+
|
16
|
+
class EntityResourceHandler:
|
17
|
+
"""Handles entity resources for the MCP server."""
|
18
|
+
|
19
|
+
def __init__(self, client_manager: D365FOClientManager):
|
20
|
+
"""Initialize the entity resource handler.
|
21
|
+
|
22
|
+
Args:
|
23
|
+
client_manager: D365FO client manager instance
|
24
|
+
"""
|
25
|
+
self.client_manager = client_manager
|
26
|
+
|
27
|
+
async def list_resources(self) -> List[Resource]:
|
28
|
+
"""List available entity resources.
|
29
|
+
|
30
|
+
Returns:
|
31
|
+
List of entity resources
|
32
|
+
"""
|
33
|
+
try:
|
34
|
+
client = await self.client_manager.get_client()
|
35
|
+
entities = await client.search_entities("")
|
36
|
+
|
37
|
+
resources = []
|
38
|
+
for entity_name in entities[:100]: # Limit to first 100 for performance
|
39
|
+
resources.append(
|
40
|
+
Resource(
|
41
|
+
uri=f"d365fo://entities/{entity_name}",
|
42
|
+
name=f"Entity: {entity_name}",
|
43
|
+
description=f"D365FO entity {entity_name} with metadata and sample data",
|
44
|
+
mimeType="application/json",
|
45
|
+
)
|
46
|
+
)
|
47
|
+
|
48
|
+
logger.info(f"Listed {len(resources)} entity resources")
|
49
|
+
return resources
|
50
|
+
except Exception as e:
|
51
|
+
logger.error(f"Failed to list entity resources: {e}")
|
52
|
+
raise
|
53
|
+
|
54
|
+
async def read_resource(self, uri: str) -> str:
|
55
|
+
"""Read specific entity resource.
|
56
|
+
|
57
|
+
Args:
|
58
|
+
uri: Resource URI (e.g., "d365fo://entities/Customers")
|
59
|
+
|
60
|
+
Returns:
|
61
|
+
JSON string with entity resource content
|
62
|
+
"""
|
63
|
+
entity_name = self._extract_entity_name(uri)
|
64
|
+
client = await self.client_manager.get_client()
|
65
|
+
|
66
|
+
try:
|
67
|
+
# Get entity metadata
|
68
|
+
entity_info = await client.get_entity_info(entity_name)
|
69
|
+
|
70
|
+
# Get sample data (limited to 5 records)
|
71
|
+
from ...models import QueryOptions
|
72
|
+
|
73
|
+
sample_data = await client.get_entities(
|
74
|
+
entity_name, options=QueryOptions(top=5)
|
75
|
+
)
|
76
|
+
|
77
|
+
# Build resource content
|
78
|
+
metadata = None
|
79
|
+
if entity_info:
|
80
|
+
# Handle both object and dictionary return types
|
81
|
+
if isinstance(entity_info, dict):
|
82
|
+
metadata = {
|
83
|
+
"name": entity_info.get("name", entity_name),
|
84
|
+
"entitySetName": entity_info.get(
|
85
|
+
"entity_set_name", entity_name
|
86
|
+
),
|
87
|
+
"keys": entity_info.get("keys", []),
|
88
|
+
"properties": [
|
89
|
+
{
|
90
|
+
"name": (
|
91
|
+
prop.get("name", "")
|
92
|
+
if isinstance(prop, dict)
|
93
|
+
else prop.name
|
94
|
+
),
|
95
|
+
"type": (
|
96
|
+
prop.get("type", "")
|
97
|
+
if isinstance(prop, dict)
|
98
|
+
else getattr(prop, "type", "")
|
99
|
+
),
|
100
|
+
"isKey": (
|
101
|
+
prop.get("name", "")
|
102
|
+
if isinstance(prop, dict)
|
103
|
+
else prop.name
|
104
|
+
)
|
105
|
+
in entity_info.get("keys", []),
|
106
|
+
"maxLength": (
|
107
|
+
prop.get("max_length")
|
108
|
+
if isinstance(prop, dict)
|
109
|
+
else getattr(prop, "max_length", None)
|
110
|
+
),
|
111
|
+
"label": (
|
112
|
+
prop.get("label_text")
|
113
|
+
if isinstance(prop, dict)
|
114
|
+
else getattr(prop, "label_text", None)
|
115
|
+
),
|
116
|
+
}
|
117
|
+
for prop in entity_info.get("properties", [])
|
118
|
+
],
|
119
|
+
"isReadOnly": entity_info.get("is_read_only", False),
|
120
|
+
"labelText": entity_info.get("label_text", None),
|
121
|
+
}
|
122
|
+
else:
|
123
|
+
metadata = {
|
124
|
+
"name": getattr(entity_info, "name", entity_name),
|
125
|
+
"entitySetName": getattr(
|
126
|
+
entity_info, "entity_set_name", entity_name
|
127
|
+
),
|
128
|
+
"keys": getattr(entity_info, "keys", []),
|
129
|
+
"properties": [
|
130
|
+
{
|
131
|
+
"name": getattr(prop, "name", ""),
|
132
|
+
"type": getattr(prop, "type", "")
|
133
|
+
or getattr(prop, "type_name", ""),
|
134
|
+
"isKey": getattr(prop, "name", "")
|
135
|
+
in getattr(entity_info, "keys", []),
|
136
|
+
"maxLength": getattr(prop, "max_length", None),
|
137
|
+
"label": getattr(prop, "label_text", None),
|
138
|
+
}
|
139
|
+
for prop in getattr(entity_info, "properties", [])
|
140
|
+
],
|
141
|
+
"isReadOnly": getattr(entity_info, "is_read_only", False),
|
142
|
+
"labelText": getattr(entity_info, "label_text", None),
|
143
|
+
}
|
144
|
+
|
145
|
+
resource_content = {
|
146
|
+
"metadata": metadata,
|
147
|
+
"sampleData": sample_data.get("value", []) if sample_data else [],
|
148
|
+
"recordCount": sample_data.get("@odata.count") if sample_data else None,
|
149
|
+
"lastUpdated": datetime.now(timezone.utc).isoformat(),
|
150
|
+
}
|
151
|
+
|
152
|
+
logger.info(f"Retrieved entity resource: {entity_name}")
|
153
|
+
return json.dumps(resource_content, indent=2)
|
154
|
+
|
155
|
+
except Exception as e:
|
156
|
+
logger.error(f"Failed to read entity resource {entity_name}: {e}")
|
157
|
+
# Return error in resource format
|
158
|
+
error_content = {
|
159
|
+
"error": str(e),
|
160
|
+
"entityName": entity_name,
|
161
|
+
"timestamp": datetime.utcnow().isoformat(),
|
162
|
+
}
|
163
|
+
return json.dumps(error_content, indent=2)
|
164
|
+
|
165
|
+
def _extract_entity_name(self, uri: str) -> str:
|
166
|
+
"""Extract entity name from resource URI.
|
167
|
+
|
168
|
+
Args:
|
169
|
+
uri: Resource URI
|
170
|
+
|
171
|
+
Returns:
|
172
|
+
Entity name
|
173
|
+
"""
|
174
|
+
if uri.startswith("d365fo://entities/"):
|
175
|
+
return uri[len("d365fo://entities/") :]
|
176
|
+
raise ValueError(f"Invalid entity resource URI: {uri}")
|
@@ -0,0 +1,132 @@
|
|
1
|
+
"""Environment resource handler for MCP server."""
|
2
|
+
|
3
|
+
import json
|
4
|
+
import logging
|
5
|
+
from datetime import datetime
|
6
|
+
from time import timezone
|
7
|
+
from typing import List
|
8
|
+
|
9
|
+
from mcp.types import Resource
|
10
|
+
|
11
|
+
from ..client_manager import D365FOClientManager
|
12
|
+
|
13
|
+
logger = logging.getLogger(__name__)
|
14
|
+
|
15
|
+
|
16
|
+
class EnvironmentResourceHandler:
|
17
|
+
"""Handles environment resources for the MCP server."""
|
18
|
+
|
19
|
+
def __init__(self, client_manager: D365FOClientManager):
|
20
|
+
"""Initialize the environment resource handler.
|
21
|
+
|
22
|
+
Args:
|
23
|
+
client_manager: D365FO client manager instance
|
24
|
+
"""
|
25
|
+
self.client_manager = client_manager
|
26
|
+
|
27
|
+
async def list_resources(self) -> List[Resource]:
|
28
|
+
"""List available environment resources.
|
29
|
+
|
30
|
+
Returns:
|
31
|
+
List of environment resources
|
32
|
+
"""
|
33
|
+
resources = [
|
34
|
+
Resource(
|
35
|
+
uri="d365fo://environment/status",
|
36
|
+
name="Environment Status",
|
37
|
+
description="Environment health and connectivity status",
|
38
|
+
mimeType="application/json",
|
39
|
+
),
|
40
|
+
Resource(
|
41
|
+
uri="d365fo://environment/version",
|
42
|
+
name="Version Information",
|
43
|
+
description="D365FO application and platform version information",
|
44
|
+
mimeType="application/json",
|
45
|
+
),
|
46
|
+
Resource(
|
47
|
+
uri="d365fo://environment/cache",
|
48
|
+
name="Cache Status",
|
49
|
+
description="Cache status and performance statistics",
|
50
|
+
mimeType="application/json",
|
51
|
+
),
|
52
|
+
]
|
53
|
+
|
54
|
+
logger.info(f"Listed {len(resources)} environment resources")
|
55
|
+
return resources
|
56
|
+
|
57
|
+
async def read_resource(self, uri: str) -> str:
|
58
|
+
"""Read specific environment resource.
|
59
|
+
|
60
|
+
Args:
|
61
|
+
uri: Resource URI
|
62
|
+
|
63
|
+
Returns:
|
64
|
+
JSON string with environment resource content
|
65
|
+
"""
|
66
|
+
try:
|
67
|
+
if uri == "d365fo://environment/status":
|
68
|
+
return await self._get_status_resource()
|
69
|
+
elif uri == "d365fo://environment/version":
|
70
|
+
return await self._get_version_resource()
|
71
|
+
elif uri == "d365fo://environment/cache":
|
72
|
+
return await self._get_cache_resource()
|
73
|
+
else:
|
74
|
+
raise ValueError(f"Unknown environment resource URI: {uri}")
|
75
|
+
except Exception as e:
|
76
|
+
logger.error(f"Failed to read environment resource {uri}: {e}")
|
77
|
+
error_content = {
|
78
|
+
"error": str(e),
|
79
|
+
"uri": uri,
|
80
|
+
"timestamp": datetime.now(timezone.utc),
|
81
|
+
}
|
82
|
+
return json.dumps(error_content, indent=2)
|
83
|
+
|
84
|
+
async def _get_status_resource(self) -> str:
|
85
|
+
"""Get environment status resource."""
|
86
|
+
try:
|
87
|
+
env_info = await self.client_manager.get_environment_info()
|
88
|
+
health_check = await self.client_manager.health_check()
|
89
|
+
|
90
|
+
status_content = {
|
91
|
+
"baseUrl": env_info["base_url"],
|
92
|
+
"connectivity": env_info["connectivity"],
|
93
|
+
"healthCheck": health_check,
|
94
|
+
"lastUpdated": datetime.now(timezone.utc),
|
95
|
+
}
|
96
|
+
|
97
|
+
return json.dumps(status_content, indent=2)
|
98
|
+
except Exception as e:
|
99
|
+
logger.error(f"Failed to get status resource: {e}")
|
100
|
+
raise
|
101
|
+
|
102
|
+
async def _get_version_resource(self) -> str:
|
103
|
+
"""Get version information resource."""
|
104
|
+
try:
|
105
|
+
env_info = await self.client_manager.get_environment_info()
|
106
|
+
|
107
|
+
version_content = {
|
108
|
+
"baseUrl": env_info["base_url"],
|
109
|
+
"versions": env_info["versions"],
|
110
|
+
"lastUpdated": datetime.now(timezone.utc),
|
111
|
+
}
|
112
|
+
|
113
|
+
return json.dumps(version_content, indent=2)
|
114
|
+
except Exception as e:
|
115
|
+
logger.error(f"Failed to get version resource: {e}")
|
116
|
+
raise
|
117
|
+
|
118
|
+
async def _get_cache_resource(self) -> str:
|
119
|
+
"""Get cache status resource."""
|
120
|
+
try:
|
121
|
+
env_info = await self.client_manager.get_environment_info()
|
122
|
+
|
123
|
+
cache_content = {
|
124
|
+
"baseUrl": env_info["base_url"],
|
125
|
+
"cacheStatus": env_info["cache_status"],
|
126
|
+
"lastUpdated": datetime.now(timezone.utc),
|
127
|
+
}
|
128
|
+
|
129
|
+
return json.dumps(cache_content, indent=2)
|
130
|
+
except Exception as e:
|
131
|
+
logger.error(f"Failed to get cache resource: {e}")
|
132
|
+
raise
|
@@ -0,0 +1,283 @@
|
|
1
|
+
"""Metadata resource handler for MCP server."""
|
2
|
+
|
3
|
+
import json
|
4
|
+
import logging
|
5
|
+
from datetime import datetime
|
6
|
+
from typing import List
|
7
|
+
|
8
|
+
from mcp.types import Resource
|
9
|
+
|
10
|
+
from ..client_manager import D365FOClientManager
|
11
|
+
|
12
|
+
logger = logging.getLogger(__name__)
|
13
|
+
|
14
|
+
|
15
|
+
class MetadataResourceHandler:
|
16
|
+
"""Handles metadata resources for the MCP server."""
|
17
|
+
|
18
|
+
def __init__(self, client_manager: D365FOClientManager):
|
19
|
+
"""Initialize the metadata resource handler.
|
20
|
+
|
21
|
+
Args:
|
22
|
+
client_manager: D365FO client manager instance
|
23
|
+
"""
|
24
|
+
self.client_manager = client_manager
|
25
|
+
|
26
|
+
async def list_resources(self) -> List[Resource]:
|
27
|
+
"""List available metadata resources.
|
28
|
+
|
29
|
+
Returns:
|
30
|
+
List of metadata resources
|
31
|
+
"""
|
32
|
+
resources = [
|
33
|
+
Resource(
|
34
|
+
uri="d365fo://metadata/entities",
|
35
|
+
name="Data Entities Metadata",
|
36
|
+
description="All data entities metadata and schemas",
|
37
|
+
mimeType="application/json",
|
38
|
+
),
|
39
|
+
Resource(
|
40
|
+
uri="d365fo://metadata/actions",
|
41
|
+
name="OData Actions",
|
42
|
+
description="Available OData actions and functions",
|
43
|
+
mimeType="application/json",
|
44
|
+
),
|
45
|
+
Resource(
|
46
|
+
uri="d365fo://metadata/enumerations",
|
47
|
+
name="System Enumerations",
|
48
|
+
description="System enumerations and their values",
|
49
|
+
mimeType="application/json",
|
50
|
+
),
|
51
|
+
Resource(
|
52
|
+
uri="d365fo://metadata/labels",
|
53
|
+
name="System Labels",
|
54
|
+
description="System labels and translations",
|
55
|
+
mimeType="application/json",
|
56
|
+
),
|
57
|
+
]
|
58
|
+
|
59
|
+
logger.info(f"Listed {len(resources)} metadata resources")
|
60
|
+
return resources
|
61
|
+
|
62
|
+
async def read_resource(self, uri: str) -> str:
|
63
|
+
"""Read specific metadata resource.
|
64
|
+
|
65
|
+
Args:
|
66
|
+
uri: Resource URI
|
67
|
+
|
68
|
+
Returns:
|
69
|
+
JSON string with metadata resource content
|
70
|
+
"""
|
71
|
+
try:
|
72
|
+
if uri == "d365fo://metadata/entities":
|
73
|
+
return await self._get_entities_metadata()
|
74
|
+
elif uri == "d365fo://metadata/actions":
|
75
|
+
return await self._get_actions_metadata()
|
76
|
+
elif uri == "d365fo://metadata/enumerations":
|
77
|
+
return await self._get_enumerations_metadata()
|
78
|
+
elif uri == "d365fo://metadata/labels":
|
79
|
+
return await self._get_labels_metadata()
|
80
|
+
else:
|
81
|
+
raise ValueError(f"Unknown metadata resource URI: {uri}")
|
82
|
+
except Exception as e:
|
83
|
+
logger.error(f"Failed to read metadata resource {uri}: {e}")
|
84
|
+
error_content = {
|
85
|
+
"error": str(e),
|
86
|
+
"uri": uri,
|
87
|
+
"timestamp": datetime.utcnow().isoformat(),
|
88
|
+
}
|
89
|
+
return json.dumps(error_content, indent=2)
|
90
|
+
|
91
|
+
async def _get_entities_metadata(self) -> str:
|
92
|
+
"""Get entities metadata resource."""
|
93
|
+
try:
|
94
|
+
client = await self.client_manager.get_client()
|
95
|
+
|
96
|
+
# Get all entities
|
97
|
+
entities = await client.search_entities("")
|
98
|
+
|
99
|
+
# Get detailed info for first 50 entities (performance limit)
|
100
|
+
detailed_entities = []
|
101
|
+
for entity_name in entities[:50]:
|
102
|
+
entity_info = await client.get_entity_info(entity_name)
|
103
|
+
if entity_info:
|
104
|
+
# Handle both object and dictionary return types
|
105
|
+
if isinstance(entity_info, dict):
|
106
|
+
detailed_entities.append(
|
107
|
+
{
|
108
|
+
"name": entity_info.get("name", entity_name),
|
109
|
+
"entitySetName": entity_info.get("entity_set_name", ""),
|
110
|
+
"keys": entity_info.get("keys", []),
|
111
|
+
"propertyCount": len(entity_info.get("properties", [])),
|
112
|
+
"isReadOnly": entity_info.get("is_read_only", False),
|
113
|
+
"labelText": entity_info.get("label_text", ""),
|
114
|
+
}
|
115
|
+
)
|
116
|
+
else:
|
117
|
+
detailed_entities.append(
|
118
|
+
{
|
119
|
+
"name": getattr(entity_info, "name", entity_name),
|
120
|
+
"entitySetName": getattr(
|
121
|
+
entity_info, "entity_set_name", ""
|
122
|
+
),
|
123
|
+
"keys": getattr(entity_info, "keys", []),
|
124
|
+
"propertyCount": len(
|
125
|
+
getattr(entity_info, "properties", [])
|
126
|
+
),
|
127
|
+
"isReadOnly": getattr(
|
128
|
+
entity_info, "is_read_only", False
|
129
|
+
),
|
130
|
+
"labelText": getattr(entity_info, "label_text", ""),
|
131
|
+
}
|
132
|
+
)
|
133
|
+
|
134
|
+
metadata_content = {
|
135
|
+
"type": "entities",
|
136
|
+
"count": len(entities),
|
137
|
+
"detailedItems": detailed_entities,
|
138
|
+
"totalEntities": len(entities),
|
139
|
+
"lastUpdated": datetime.utcnow().isoformat(),
|
140
|
+
"statistics": {
|
141
|
+
"readOnlyCount": sum(
|
142
|
+
1 for e in detailed_entities if e["isReadOnly"]
|
143
|
+
),
|
144
|
+
"writableCount": sum(
|
145
|
+
1 for e in detailed_entities if not e["isReadOnly"]
|
146
|
+
),
|
147
|
+
},
|
148
|
+
}
|
149
|
+
|
150
|
+
return json.dumps(metadata_content, indent=2)
|
151
|
+
except Exception as e:
|
152
|
+
logger.error(f"Failed to get entities metadata: {e}")
|
153
|
+
raise
|
154
|
+
|
155
|
+
async def _get_actions_metadata(self) -> str:
|
156
|
+
"""Get actions metadata resource."""
|
157
|
+
try:
|
158
|
+
client = await self.client_manager.get_client()
|
159
|
+
|
160
|
+
# Get all actions
|
161
|
+
actions = await client.search_actions("")
|
162
|
+
|
163
|
+
# Get detailed info for first 50 actions (performance limit)
|
164
|
+
detailed_actions = []
|
165
|
+
for action_name in actions[:50]:
|
166
|
+
action_info = await client.get_action_info(action_name)
|
167
|
+
if action_info:
|
168
|
+
# Handle both object and dictionary return types
|
169
|
+
if isinstance(action_info, dict):
|
170
|
+
detailed_actions.append(
|
171
|
+
{
|
172
|
+
"name": action_info.get("name", action_name),
|
173
|
+
"isFunction": action_info.get("is_function", False),
|
174
|
+
"isBound": action_info.get("is_bound", False),
|
175
|
+
"parameterCount": len(
|
176
|
+
action_info.get("parameters", [])
|
177
|
+
),
|
178
|
+
"returnType": action_info.get("return_type", "void"),
|
179
|
+
}
|
180
|
+
)
|
181
|
+
else:
|
182
|
+
detailed_actions.append(
|
183
|
+
{
|
184
|
+
"name": getattr(action_info, "name", action_name),
|
185
|
+
"isFunction": getattr(
|
186
|
+
action_info, "is_function", False
|
187
|
+
),
|
188
|
+
"isBound": getattr(action_info, "is_bound", False),
|
189
|
+
"parameterCount": len(
|
190
|
+
getattr(action_info, "parameters", [])
|
191
|
+
),
|
192
|
+
"returnType": getattr(
|
193
|
+
action_info, "return_type", "void"
|
194
|
+
),
|
195
|
+
}
|
196
|
+
)
|
197
|
+
|
198
|
+
metadata_content = {
|
199
|
+
"type": "actions",
|
200
|
+
"count": len(actions),
|
201
|
+
"detailedItems": detailed_actions,
|
202
|
+
"totalActions": len(actions),
|
203
|
+
"lastUpdated": datetime.utcnow().isoformat(),
|
204
|
+
"statistics": {
|
205
|
+
"functionsCount": sum(
|
206
|
+
1 for a in detailed_actions if a["isFunction"]
|
207
|
+
),
|
208
|
+
"actionsCount": sum(
|
209
|
+
1 for a in detailed_actions if not a["isFunction"]
|
210
|
+
),
|
211
|
+
"boundCount": sum(1 for a in detailed_actions if a["isBound"]),
|
212
|
+
},
|
213
|
+
}
|
214
|
+
|
215
|
+
return json.dumps(metadata_content, indent=2)
|
216
|
+
except Exception as e:
|
217
|
+
logger.error(f"Failed to get actions metadata: {e}")
|
218
|
+
raise
|
219
|
+
|
220
|
+
async def _get_enumerations_metadata(self) -> str:
|
221
|
+
"""Get enumerations metadata resource."""
|
222
|
+
try:
|
223
|
+
client = await self.client_manager.get_client()
|
224
|
+
|
225
|
+
# Try to get public enumerations
|
226
|
+
try:
|
227
|
+
enumerations = await client.get_public_enumerations()
|
228
|
+
detailed_enums = []
|
229
|
+
|
230
|
+
for enum_info in enumerations[:50]: # Limit for performance
|
231
|
+
# Handle both object and dictionary return types
|
232
|
+
if isinstance(enum_info, dict):
|
233
|
+
detailed_enums.append(
|
234
|
+
{
|
235
|
+
"name": enum_info.get("name", "Unknown"),
|
236
|
+
"valueCount": len(enum_info.get("members", [])),
|
237
|
+
"description": enum_info.get("description", ""),
|
238
|
+
}
|
239
|
+
)
|
240
|
+
else:
|
241
|
+
detailed_enums.append(
|
242
|
+
{
|
243
|
+
"name": getattr(enum_info, "name", "Unknown"),
|
244
|
+
"valueCount": len(getattr(enum_info, "members", [])),
|
245
|
+
"description": getattr(enum_info, "description", ""),
|
246
|
+
}
|
247
|
+
)
|
248
|
+
except Exception:
|
249
|
+
# Fallback if public enumerations not available
|
250
|
+
detailed_enums = []
|
251
|
+
|
252
|
+
metadata_content = {
|
253
|
+
"type": "enumerations",
|
254
|
+
"count": len(detailed_enums),
|
255
|
+
"detailedItems": detailed_enums,
|
256
|
+
"totalEnumerations": len(detailed_enums),
|
257
|
+
"lastUpdated": datetime.utcnow().isoformat(),
|
258
|
+
}
|
259
|
+
|
260
|
+
return json.dumps(metadata_content, indent=2)
|
261
|
+
except Exception as e:
|
262
|
+
logger.error(f"Failed to get enumerations metadata: {e}")
|
263
|
+
raise
|
264
|
+
|
265
|
+
async def _get_labels_metadata(self) -> str:
|
266
|
+
"""Get labels metadata resource."""
|
267
|
+
try:
|
268
|
+
# For now, return basic label information
|
269
|
+
# In a full implementation, we'd query the label cache
|
270
|
+
metadata_content = {
|
271
|
+
"type": "labels",
|
272
|
+
"count": 0, # TODO: Get actual label count
|
273
|
+
"detailedItems": [],
|
274
|
+
"totalLabels": 0,
|
275
|
+
"lastUpdated": datetime.utcnow().isoformat(),
|
276
|
+
"supportedLanguages": ["en-US", "en-GB", "de-DE", "fr-FR", "es-ES"],
|
277
|
+
"note": "Label metadata requires metadata sync to be populated",
|
278
|
+
}
|
279
|
+
|
280
|
+
return json.dumps(metadata_content, indent=2)
|
281
|
+
except Exception as e:
|
282
|
+
logger.error(f"Failed to get labels metadata: {e}")
|
283
|
+
raise
|