python-alfresco-mcp-server 1.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.
Files changed (34) hide show
  1. alfresco_mcp_server/__init__.py +27 -0
  2. alfresco_mcp_server/config.py +104 -0
  3. alfresco_mcp_server/fastmcp_server.py +259 -0
  4. alfresco_mcp_server/prompts/__init__.py +7 -0
  5. alfresco_mcp_server/prompts/search_and_analyze.py +67 -0
  6. alfresco_mcp_server/resources/__init__.py +7 -0
  7. alfresco_mcp_server/resources/repository_resources.py +205 -0
  8. alfresco_mcp_server/tools/__init__.py +13 -0
  9. alfresco_mcp_server/tools/core/__init__.py +27 -0
  10. alfresco_mcp_server/tools/core/browse_repository.py +164 -0
  11. alfresco_mcp_server/tools/core/cancel_checkout.py +160 -0
  12. alfresco_mcp_server/tools/core/checkin_document.py +264 -0
  13. alfresco_mcp_server/tools/core/checkout_document.py +258 -0
  14. alfresco_mcp_server/tools/core/create_folder.py +105 -0
  15. alfresco_mcp_server/tools/core/delete_node.py +88 -0
  16. alfresco_mcp_server/tools/core/download_document.py +214 -0
  17. alfresco_mcp_server/tools/core/get_node_properties.py +195 -0
  18. alfresco_mcp_server/tools/core/update_node_properties.py +138 -0
  19. alfresco_mcp_server/tools/core/upload_document.py +244 -0
  20. alfresco_mcp_server/tools/search/__init__.py +15 -0
  21. alfresco_mcp_server/tools/search/advanced_search.py +204 -0
  22. alfresco_mcp_server/tools/search/cmis_search.py +180 -0
  23. alfresco_mcp_server/tools/search/search_by_metadata.py +183 -0
  24. alfresco_mcp_server/tools/search/search_content.py +186 -0
  25. alfresco_mcp_server/utils/__init__.py +34 -0
  26. alfresco_mcp_server/utils/connection.py +114 -0
  27. alfresco_mcp_server/utils/file_type_analysis.py +163 -0
  28. alfresco_mcp_server/utils/json_utils.py +135 -0
  29. python_alfresco_mcp_server-1.1.0.dist-info/METADATA +631 -0
  30. python_alfresco_mcp_server-1.1.0.dist-info/RECORD +34 -0
  31. python_alfresco_mcp_server-1.1.0.dist-info/WHEEL +5 -0
  32. python_alfresco_mcp_server-1.1.0.dist-info/entry_points.txt +2 -0
  33. python_alfresco_mcp_server-1.1.0.dist-info/licenses/LICENSE +201 -0
  34. python_alfresco_mcp_server-1.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,27 @@
1
+ """
2
+ MCP Server for Alfresco
3
+
4
+ Model Context Protocol server for Alfresco Content Services.
5
+ Provides AI-native access to Alfresco content management and search operations.
6
+ """
7
+
8
+ __version__ = "1.1.0"
9
+ __title__ = "MCP Server for Alfresco"
10
+ __description__ = "Model Context Protocol server for Alfresco Content Services"
11
+
12
+ from .config import AlfrescoConfig, load_config
13
+
14
+ # Import subpackages to make them available
15
+ from . import tools
16
+ from . import resources
17
+ from . import prompts
18
+ from . import utils
19
+
20
+ __all__ = [
21
+ "AlfrescoConfig",
22
+ "load_config",
23
+ "tools",
24
+ "resources",
25
+ "prompts",
26
+ "utils",
27
+ ]
@@ -0,0 +1,104 @@
1
+ """
2
+ Configuration management for MCP Server for Alfresco.
3
+ """
4
+
5
+ import os
6
+ from typing import Optional
7
+ from pydantic import BaseModel, Field
8
+
9
+
10
+ class AlfrescoConfig(BaseModel):
11
+ """Configuration for MCP Server for Alfresco."""
12
+
13
+ # Alfresco server connection
14
+ alfresco_url: str = Field(
15
+ default_factory=lambda: os.getenv("ALFRESCO_URL", "http://localhost:8080"),
16
+ description="Alfresco server URL"
17
+ )
18
+
19
+ # Authentication
20
+ username: str = Field(
21
+ default_factory=lambda: os.getenv("ALFRESCO_USERNAME", "admin"),
22
+ description="Alfresco username"
23
+ )
24
+
25
+ password: str = Field(
26
+ default_factory=lambda: os.getenv("ALFRESCO_PASSWORD", "admin"),
27
+ description="Alfresco password"
28
+ )
29
+
30
+ # Connection settings
31
+ verify_ssl: bool = Field(
32
+ default_factory=lambda: os.getenv("ALFRESCO_VERIFY_SSL", "false").lower() == "true",
33
+ description="Verify SSL certificates"
34
+ )
35
+
36
+ timeout: int = Field(
37
+ default_factory=lambda: int(os.getenv("ALFRESCO_TIMEOUT", "30")),
38
+ description="Request timeout in seconds"
39
+ )
40
+
41
+ # MCP Server settings
42
+ server_name: str = Field(
43
+ default="python-alfresco-mcp-server",
44
+ description="MCP server name"
45
+ )
46
+
47
+ server_version: str = Field(
48
+ default="1.0.0",
49
+ description="MCP server version"
50
+ )
51
+
52
+ # FastAPI settings (for HTTP transport)
53
+ fastapi_host: str = Field(
54
+ default_factory=lambda: os.getenv("FASTAPI_HOST", "localhost"),
55
+ description="FastAPI host"
56
+ )
57
+
58
+ fastapi_port: int = Field(
59
+ default_factory=lambda: int(os.getenv("FASTAPI_PORT", "8000")),
60
+ description="FastAPI port"
61
+ )
62
+
63
+ fastapi_prefix: str = Field(
64
+ default_factory=lambda: os.getenv("FASTAPI_PREFIX", "/mcp"),
65
+ description="FastAPI URL prefix"
66
+ )
67
+
68
+ # Logging
69
+ log_level: str = Field(
70
+ default_factory=lambda: os.getenv("LOG_LEVEL", "INFO"),
71
+ description="Logging level"
72
+ )
73
+
74
+ # Content settings
75
+ max_file_size: int = Field(
76
+ default_factory=lambda: int(os.getenv("MAX_FILE_SIZE", "100000000")), # 100MB
77
+ description="Maximum file size for uploads in bytes"
78
+ )
79
+
80
+ allowed_extensions: list[str] = Field(
81
+ default_factory=lambda: [
82
+ ".txt", ".pdf", ".doc", ".docx", ".xls", ".xlsx",
83
+ ".ppt", ".pptx", ".jpg", ".jpeg", ".png", ".gif",
84
+ ".zip", ".xml", ".json", ".csv"
85
+ ],
86
+ description="Allowed file extensions for uploads"
87
+ )
88
+
89
+ class Config:
90
+ env_prefix = "ALFRESCO_"
91
+ case_sensitive = False
92
+
93
+ def model_post_init(self, __context) -> None:
94
+ """Normalize URLs after initialization."""
95
+ if self.alfresco_url.endswith("/"):
96
+ self.alfresco_url = self.alfresco_url.rstrip("/")
97
+
98
+
99
+ def load_config() -> AlfrescoConfig:
100
+ """Load configuration from environment variables and defaults."""
101
+ return AlfrescoConfig()
102
+
103
+ # Global config instance for import
104
+ config = load_config()
@@ -0,0 +1,259 @@
1
+ """
2
+ MCP Server for Alfresco using FastMCP 2.0
3
+ Modular implementation with separated concerns and self-contained tools
4
+ """
5
+ import logging
6
+ from fastmcp import FastMCP, Context
7
+
8
+ # Search tools imports
9
+ from .tools.search.search_content import search_content_impl
10
+ from .tools.search.advanced_search import advanced_search_impl
11
+ from .tools.search.search_by_metadata import search_by_metadata_impl
12
+ from .tools.search.cmis_search import cmis_search_impl
13
+
14
+ # Core tools imports
15
+ from .tools.core.browse_repository import browse_repository_impl
16
+ from .tools.core.upload_document import upload_document_impl
17
+ from .tools.core.download_document import download_document_impl
18
+ from .tools.core.create_folder import create_folder_impl
19
+ from .tools.core.get_node_properties import get_node_properties_impl
20
+ from .tools.core.update_node_properties import update_node_properties_impl
21
+ from .tools.core.delete_node import delete_node_impl
22
+ from .tools.core.checkout_document import checkout_document_impl
23
+ from .tools.core.checkin_document import checkin_document_impl
24
+ from .tools.core.cancel_checkout import cancel_checkout_impl
25
+
26
+ # Resource imports
27
+ from .resources.repository_resources import (
28
+ get_repository_info_impl
29
+ )
30
+
31
+ # Prompt imports
32
+ from .prompts.search_and_analyze import search_and_analyze_impl
33
+
34
+ # Configure logging
35
+ logging.basicConfig(level=logging.INFO)
36
+
37
+ # Reduce verbosity of noisy loggers
38
+ logging.getLogger("httpx").setLevel(logging.WARNING)
39
+ logging.getLogger("httpcore").setLevel(logging.WARNING)
40
+
41
+ logger = logging.getLogger(__name__)
42
+
43
+ # Initialize MCP server
44
+ mcp = FastMCP("MCP Server for Alfresco Content Services")
45
+
46
+ # ================== SEARCH TOOLS ==================
47
+
48
+ @mcp.tool
49
+ async def search_content(
50
+ query: str,
51
+ max_results: int = 25,
52
+ node_type: str = "",
53
+ ctx: Context = None
54
+ ) -> str:
55
+ """Search for content in Alfresco using AFTS query language."""
56
+ return await search_content_impl(query, max_results, node_type, ctx)
57
+
58
+ @mcp.tool
59
+ async def advanced_search(
60
+ query: str,
61
+ sort_field: str = "cm:modified",
62
+ sort_ascending: bool = False,
63
+ max_results: int = 25,
64
+ ctx: Context = None
65
+ ) -> str:
66
+ """Advanced search with sorting and filtering capabilities."""
67
+ return await advanced_search_impl(query, sort_field, sort_ascending, max_results, ctx)
68
+
69
+ @mcp.tool
70
+ async def search_by_metadata(
71
+ term: str = "",
72
+ creator: str = "",
73
+ content_type: str = "",
74
+ max_results: int = 25,
75
+ ctx: Context = None
76
+ ) -> str:
77
+ """Search for content in Alfresco by metadata fields."""
78
+ return await search_by_metadata_impl(term, creator, content_type, max_results, ctx)
79
+
80
+ @mcp.tool
81
+ async def cmis_search(
82
+ cmis_query: str = "SELECT * FROM cmis:document WHERE cmis:contentStreamMimeType = 'application/pdf'",
83
+ max_results: int = 25,
84
+ ctx: Context = None
85
+ ) -> str:
86
+ """Search using CMIS SQL syntax. Default query searches for PDF documents."""
87
+ return await cmis_search_impl(cmis_query, max_results, ctx)
88
+
89
+ # ================== CORE TOOLS ==================
90
+
91
+ @mcp.tool
92
+ async def browse_repository(
93
+ parent_id: str = "-my-",
94
+ max_items: int = 25,
95
+ ctx: Context = None
96
+ ) -> str:
97
+ """Browse the Alfresco repository structure."""
98
+ return await browse_repository_impl(parent_id, max_items, ctx)
99
+
100
+ @mcp.tool
101
+ async def upload_document(
102
+ file_path: str = "",
103
+ base64_content: str = "",
104
+ parent_id: str = "-shared-",
105
+ description: str = "",
106
+ ctx: Context = None
107
+ ) -> str:
108
+ """Upload a document to Alfresco."""
109
+ return await upload_document_impl(file_path, base64_content, parent_id, description, ctx)
110
+
111
+ @mcp.tool
112
+ async def download_document(
113
+ node_id: str,
114
+ save_to_disk: bool = True,
115
+ attachment: bool = True,
116
+ ctx: Context = None
117
+ ) -> str:
118
+ """Download a document from Alfresco repository."""
119
+ return await download_document_impl(node_id, save_to_disk, attachment, ctx)
120
+
121
+ @mcp.tool
122
+ async def create_folder(
123
+ folder_name: str,
124
+ parent_id: str = "-shared-",
125
+ description: str = "",
126
+ ctx: Context = None
127
+ ) -> str:
128
+ """Create a new folder in Alfresco."""
129
+ return await create_folder_impl(folder_name, parent_id, description, ctx)
130
+
131
+ @mcp.tool
132
+ async def get_node_properties(node_id: str, ctx: Context = None) -> str:
133
+ """Get metadata and properties of a document or folder."""
134
+ return await get_node_properties_impl(node_id, ctx)
135
+
136
+ @mcp.tool
137
+ async def update_node_properties(
138
+ node_id: str,
139
+ name: str = "",
140
+ title: str = "",
141
+ description: str = "",
142
+ author: str = "",
143
+ ctx: Context = None
144
+ ) -> str:
145
+ """Update metadata and properties of a document or folder."""
146
+ return await update_node_properties_impl(node_id, name, title, description, author, ctx)
147
+
148
+ @mcp.tool
149
+ async def delete_node(
150
+ node_id: str,
151
+ permanent: bool = False,
152
+ ctx: Context = None
153
+ ) -> str:
154
+ """Delete a document or folder from Alfresco."""
155
+ return await delete_node_impl(node_id, permanent, ctx)
156
+
157
+ # ================== CHECKOUT/CHECKIN TOOLS ==================
158
+
159
+ @mcp.tool
160
+ async def checkout_document(
161
+ node_id: str,
162
+ download_for_editing: bool = True,
163
+ ctx: Context = None
164
+ ) -> str:
165
+ """Check out a document for editing using Alfresco REST API."""
166
+ return await checkout_document_impl(node_id, download_for_editing, ctx)
167
+
168
+ @mcp.tool
169
+ async def checkin_document(
170
+ node_id: str,
171
+ comment: str = "",
172
+ major_version: bool = False,
173
+ file_path: str = "",
174
+ new_name: str = "",
175
+ ctx: Context = None
176
+ ) -> str:
177
+ """Check in a document after editing using Alfresco REST API."""
178
+ return await checkin_document_impl(node_id, comment, major_version, file_path, new_name, ctx)
179
+
180
+ @mcp.tool
181
+ async def cancel_checkout(
182
+ node_id: str,
183
+ ctx: Context = None
184
+ ) -> str:
185
+ """Cancel checkout of a document, discarding any working copy."""
186
+ return await cancel_checkout_impl(node_id, ctx)
187
+
188
+ # ================== RESOURCES ==================
189
+
190
+ @mcp.resource("alfresco://repository/info", description="📊 Live Alfresco repository information including version, edition, and connection status")
191
+ async def repository_info() -> str:
192
+ """Get Alfresco repository information using Discovery Client."""
193
+ return await get_repository_info_impl()
194
+
195
+ @mcp.tool
196
+ async def get_repository_info_tool(ctx: Context = None) -> str:
197
+ """Get Alfresco repository information using Discovery Client (as tool instead of resource)."""
198
+ return await get_repository_info_impl()
199
+
200
+
201
+ # ================== PROMPTS ==================
202
+
203
+ @mcp.prompt(description="🔎 Generate comprehensive search and analysis steps for Alfresco documents with customizable analysis types")
204
+ async def search_and_analyze(query: str, analysis_type: str = "summary") -> str:
205
+ """Generate comprehensive search and analysis prompts for Alfresco documents."""
206
+ return await search_and_analyze_impl(query, analysis_type)
207
+
208
+ # ================== MAIN ENTRY POINT ==================
209
+
210
+ def main():
211
+ """Main entry point for the FastMCP 2.0 Alfresco server."""
212
+ import argparse
213
+
214
+ parser = argparse.ArgumentParser(description="MCP Server for Alfresco 2.0")
215
+ parser.add_argument(
216
+ "--transport",
217
+ choices=["stdio", "http", "sse"],
218
+ default="stdio",
219
+ help="Transport method (default: stdio)"
220
+ )
221
+ parser.add_argument(
222
+ "--host",
223
+ default="localhost",
224
+ help="Host for HTTP/SSE transport (default: localhost)"
225
+ )
226
+ parser.add_argument(
227
+ "--port",
228
+ type=int,
229
+ default=8000,
230
+ help="Port for HTTP/SSE transport (default: 8000)"
231
+ )
232
+ parser.add_argument(
233
+ "--log-level",
234
+ choices=["DEBUG", "INFO", "WARNING", "ERROR"],
235
+ default="INFO",
236
+ help="Logging level (default: INFO)"
237
+ )
238
+
239
+ args = parser.parse_args()
240
+
241
+ # Configure logging
242
+ logging.basicConfig(
243
+ level=getattr(logging, args.log_level),
244
+ format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
245
+ )
246
+
247
+ logger.info(">> Starting MCP Server for Alfresco")
248
+ logger.info(">> Hierarchical structure: tools/{core,search}, resources, prompts, utils")
249
+
250
+ # Run server with specified transport
251
+ if args.transport == "stdio":
252
+ mcp.run(transport="stdio")
253
+ elif args.transport == "http":
254
+ mcp.run(transport="http", host=args.host, port=args.port)
255
+ elif args.transport == "sse":
256
+ mcp.run(transport="sse", host=args.host, port=args.port)
257
+
258
+ if __name__ == "__main__":
259
+ main()
@@ -0,0 +1,7 @@
1
+ # Prompts module
2
+
3
+ from . import search_and_analyze
4
+
5
+ __all__ = [
6
+ "search_and_analyze",
7
+ ]
@@ -0,0 +1,67 @@
1
+ """
2
+ Search and analyze prompt for Alfresco MCP Server.
3
+ Self-contained prompt for generating comprehensive search and analysis workflows.
4
+ """
5
+
6
+
7
+ async def search_and_analyze_impl(query: str, analysis_type: str = "summary") -> str:
8
+ """Generate comprehensive search and analysis prompts for Alfresco documents.
9
+
10
+ Args:
11
+ query: Search query for documents
12
+ analysis_type: Type of analysis (summary, detailed, trends, compliance)
13
+
14
+ Returns:
15
+ Formatted prompt for document analysis workflow
16
+ """
17
+ base_prompt = f"""**Alfresco Document Analysis Request**
18
+
19
+ Please search for documents matching "{query}" and provide a {analysis_type} analysis.
20
+
21
+ **Step 1: Search**
22
+ Use the `search_content` tool to find relevant documents.
23
+
24
+ **Step 2: Analysis**
25
+ Based on the search results, provide:
26
+ """
27
+
28
+ if analysis_type == "summary":
29
+ base_prompt += """
30
+ - Document count and types
31
+ - Key themes and topics
32
+ - Most relevant documents
33
+ - Quick insights
34
+ """
35
+ elif analysis_type == "detailed":
36
+ base_prompt += """
37
+ - Comprehensive document inventory
38
+ - Metadata analysis (dates, authors, sizes)
39
+ - Content categorization
40
+ - Compliance status
41
+ - Recommended actions
42
+ - Related search suggestions
43
+ """
44
+ elif analysis_type == "trends":
45
+ base_prompt += """
46
+ - Temporal patterns (creation/modification dates)
47
+ - Document lifecycle analysis
48
+ - Usage and access patterns
49
+ - Version history insights
50
+ - Storage optimization recommendations
51
+ """
52
+ elif analysis_type == "compliance":
53
+ base_prompt += """
54
+ - Document retention analysis
55
+ - Security classification review
56
+ - Access permissions audit
57
+ - Regulatory compliance status
58
+ - Risk assessment
59
+ - Remediation recommendations
60
+ """
61
+
62
+ base_prompt += f"""
63
+ **Step 3: Recommendations**
64
+ Provide actionable insights and next steps based on the {analysis_type} analysis.
65
+ """
66
+
67
+ return base_prompt
@@ -0,0 +1,7 @@
1
+ # Resources module
2
+
3
+ from . import repository_resources
4
+
5
+ __all__ = [
6
+ "repository_resources",
7
+ ]
@@ -0,0 +1,205 @@
1
+ """
2
+ Repository resources for Alfresco MCP Server.
3
+ Self-contained resources for repository information, health, stats, and configuration.
4
+ Returns data or indicates when unavailable.
5
+ """
6
+ import json
7
+ import logging
8
+ import os
9
+
10
+ from python_alfresco_api.client_factory import ClientFactory
11
+ from ..utils.connection import ensure_connection, get_client_factory
12
+ from ..utils.json_utils import safe_format_output
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+
17
+ async def get_repository_info_impl() -> str:
18
+ """Get Alfresco repository information using Discovery API.
19
+ Returns comprehensive repository details or connection status.
20
+ """
21
+ try:
22
+ await ensure_connection()
23
+ client_factory: ClientFactory = await get_client_factory()
24
+
25
+ logger.info("Getting repository information via Discovery API")
26
+
27
+ # Use the working pattern from test script - high-level API
28
+ discovery_client = client_factory.create_discovery_client()
29
+
30
+ # Check if discovery client has the discovery attribute (working pattern from test)
31
+ if not hasattr(discovery_client, 'discovery'):
32
+ logger.warning("Discovery client does not have discovery attribute")
33
+ return safe_format_output(f"""⚠️ **Repository Information - Discovery Client Unavailable**
34
+
35
+ **Status**: Discovery client initialization failed
36
+
37
+ **Available Information**:
38
+ 🔗 **Server**: {os.getenv('ALFRESCO_URL', 'http://localhost:8080')}
39
+ 👤 **Connected as**: {os.getenv('ALFRESCO_USERNAME', 'admin')}
40
+ ❌ **Discovery API**: Client not available
41
+
42
+ **Note**: This could indicate:
43
+ - Authentication issues
44
+ - Network connectivity problems
45
+ - Server configuration issues
46
+
47
+ **Recommendation**: Check connection settings and server status.""")
48
+
49
+ # Get repository information using high-level Discovery API (working pattern from test)
50
+ repo_info = discovery_client.discovery.get_repository_information()
51
+
52
+ # Handle None response (HTTP 501 - Discovery API disabled)
53
+ if repo_info is None:
54
+ logger.warning("Discovery API is disabled on this Alfresco instance (returned None)")
55
+ return safe_format_output(f"""⚠️ **Repository Information - Discovery API Disabled**
56
+
57
+ **Status**: Discovery API is disabled on this Alfresco instance (HTTP 501)
58
+
59
+ **Available Information**:
60
+ 🔗 **Server**: {os.getenv('ALFRESCO_URL', 'http://localhost:8080')}
61
+ 👤 **Connected as**: {os.getenv('ALFRESCO_USERNAME', 'admin')}
62
+ ✅ **Core API**: Available (connection successful)
63
+ ❌ **Discovery API**: Disabled by administrator
64
+
65
+ **Note**: The Discovery API provides detailed repository information including:
66
+ - Version and edition details
67
+ - License information and entitlements
68
+ - Installed modules and their versions
69
+ - Repository status and capabilities
70
+
71
+ **Administrator Action Required**: To enable Discovery API, the Alfresco administrator needs to:
72
+ 1. Enable the Discovery API in the repository configuration
73
+ 2. Restart the Alfresco service
74
+ 3. Ensure proper permissions are configured
75
+
76
+ **Alternative**: Use Core API tools for basic repository operations.""")
77
+
78
+ if repo_info and hasattr(repo_info, 'entry'):
79
+ entry = repo_info.entry
80
+ repository = getattr(entry, 'repository', {})
81
+
82
+ # Build comprehensive repository information
83
+ result = "🏢 **Alfresco Repository Information**\n\n"
84
+
85
+ # Repository ID and Edition
86
+ repo_id = getattr(repository, 'id', 'Unknown')
87
+ edition = getattr(repository, 'edition', 'Unknown')
88
+ logger.info(f"✅ Retrieved repository info: {edition} edition")
89
+ result += f"🆔 **Repository ID**: {repo_id}\n"
90
+ result += f"🏷️ **Edition**: {edition}\n\n"
91
+
92
+ # Version Information
93
+ version_info = getattr(repository, 'version', {})
94
+ if hasattr(version_info, 'major'):
95
+ result += "📦 **Version Details**:\n"
96
+ result += f" • Major: {getattr(version_info, 'major', 'Unknown')}\n"
97
+ result += f" • Minor: {getattr(version_info, 'minor', 'Unknown')}\n"
98
+ result += f" • Patch: {getattr(version_info, 'patch', 'Unknown')}\n"
99
+ result += f" • Hotfix: {getattr(version_info, 'hotfix', 'Unknown')}\n"
100
+ result += f" • Schema: {getattr(version_info, 'schema', 'Unknown')}\n"
101
+ result += f" • Label: {getattr(version_info, 'label', 'Unknown')}\n"
102
+ result += f" • Display: {getattr(version_info, 'display', 'Unknown')}\n\n"
103
+
104
+ # Repository Status
105
+ status_info = getattr(repository, 'status', {})
106
+ if hasattr(status_info, 'is_read_only'):
107
+ result += "STATUS **Repository Status**:\n"
108
+ result += f" • Read Only: {'Yes' if getattr(status_info, 'is_read_only', False) else 'No'}\n"
109
+ result += f" • Audit Enabled: {'Yes' if getattr(status_info, 'is_audit_enabled', False) else 'No'}\n"
110
+ result += f" • Quick Share Enabled: {'Yes' if getattr(status_info, 'is_quick_share_enabled', False) else 'No'}\n"
111
+ result += f" • Thumbnail Generation: {'Yes' if getattr(status_info, 'is_thumbnail_generation_enabled', False) else 'No'}\n\n"
112
+
113
+ # License Information
114
+ license_info = getattr(repository, 'license', {})
115
+ if hasattr(license_info, 'issued_at'):
116
+ result += "📄 **License Information**:\n"
117
+ result += f" • Issued At: {getattr(license_info, 'issued_at', 'Unknown')}\n"
118
+ result += f" • Expires At: {getattr(license_info, 'expires_at', 'Unknown')}\n"
119
+ result += f" • Remaining Days: {getattr(license_info, 'remaining_days', 'Unknown')}\n"
120
+ result += f" • Holder: {getattr(license_info, 'holder', 'Unknown')}\n"
121
+ result += f" • Mode: {getattr(license_info, 'mode', 'Unknown')}\n"
122
+
123
+ # License Entitlements
124
+ entitlements = getattr(license_info, 'entitlements', {})
125
+ if hasattr(entitlements, 'max_users'):
126
+ result += f" • Max Users: {getattr(entitlements, 'max_users', 'Unknown')}\n"
127
+ result += f" • Max Documents: {getattr(entitlements, 'max_docs', 'Unknown')}\n"
128
+ result += f" • Cluster Enabled: {'Yes' if getattr(entitlements, 'is_cluster_enabled', False) else 'No'}\n"
129
+ result += f" • Cryptodoc Enabled: {'Yes' if getattr(entitlements, 'is_cryptodoc_enabled', False) else 'No'}\n"
130
+ result += "\n"
131
+
132
+ # Modules Information
133
+ modules = getattr(repository, 'modules', [])
134
+ if modules and len(modules) > 0:
135
+ result += f"🧩 **Installed Modules** ({len(modules)} total):\n"
136
+ for i, module in enumerate(modules[:10], 1): # Show first 10 modules
137
+ module_id = getattr(module, 'id', 'Unknown')
138
+ module_title = getattr(module, 'title', 'Unknown')
139
+ module_version = getattr(module, 'version', 'Unknown')
140
+ module_state = getattr(module, 'install_state', 'Unknown')
141
+ result += f" {i}. **{module_title}** (ID: {module_id})\n"
142
+ result += f" • Version: {module_version}\n"
143
+ result += f" • State: {module_state}\n"
144
+
145
+ install_date = getattr(module, 'install_date', None)
146
+ if install_date:
147
+ result += f" • Installed: {install_date}\n"
148
+ result += "\n"
149
+
150
+ if len(modules) > 10:
151
+ result += f" *... and {len(modules) - 10} more modules*\n\n"
152
+
153
+ # Connection Details
154
+ result += "🔗 **Connection Details**:\n"
155
+ result += f" • Server: {os.getenv('ALFRESCO_URL', 'http://localhost:8080')}\n"
156
+ result += f" • Connected as: {os.getenv('ALFRESCO_USERNAME', 'admin')}\n"
157
+ result += f" • Data Source: Discovery API (High-Level)\n"
158
+
159
+ return safe_format_output(result)
160
+
161
+ except Exception as discovery_error:
162
+ error_str = str(discovery_error)
163
+
164
+ # Check if Discovery API is disabled (501 error)
165
+ if "501" in error_str or "Discovery is disabled" in error_str:
166
+ logger.warning("Discovery API is disabled on this Alfresco instance")
167
+ return safe_format_output(f"""WARNING: **Repository Information - Discovery API Disabled**
168
+
169
+ **Status**: Discovery API is disabled on this Alfresco instance (HTTP 501)
170
+
171
+ **Available Information**:
172
+ 🔗 **Server**: {os.getenv('ALFRESCO_URL', 'http://localhost:8080')}
173
+ 👤 **Connected as**: {os.getenv('ALFRESCO_USERNAME', 'admin')}
174
+ ✅ **Core API**: Available (connection successful)
175
+ ❌ **Discovery API**: Disabled by administrator
176
+
177
+ **Note**: The Discovery API provides detailed repository information including:
178
+ - Version and edition details
179
+ - License information and entitlements
180
+ - Installed modules and their versions
181
+ - Repository status and capabilities
182
+
183
+ **Administrator Action Required**: To enable Discovery API, the Alfresco administrator needs to:
184
+ 1. Enable the Discovery API in the repository configuration
185
+ 2. Restart the Alfresco service
186
+ 3. Ensure proper permissions are configured
187
+
188
+ **Alternative**: Use Core API tools for basic repository operations.""")
189
+ else:
190
+ # Other Discovery API errors
191
+ logger.error(f"Discovery API failed: {error_str}")
192
+ return safe_format_output(f"""ERROR: **Repository Information Unavailable**
193
+
194
+ **Error**: Discovery API failed
195
+ **Details**: {error_str}
196
+
197
+ 🔗 **Server**: {os.getenv('ALFRESCO_URL', 'http://localhost:8080')}
198
+ 👤 **Connected as**: {os.getenv('ALFRESCO_USERNAME', 'admin')}
199
+
200
+ **Possible Causes**:
201
+ - Discovery API endpoint not available
202
+ - Insufficient permissions
203
+ - Network connectivity issues
204
+ - Repository service issues
205
+ **Recommendation**: Check server logs and verify Discovery API availability.""")