This is page 1 of 4. Use http://codebase.md/stevereiner/python-alfresco-mcp-server?lines=true&page={x} to view the full context. # Directory Structure ``` ├── .gitattributes ├── .gitignore ├── .vscode │ ├── mcp.json │ └── settings.json ├── alfresco_mcp_server │ ├── __init__.py │ ├── config.py │ ├── fastmcp_server.py │ ├── prompts │ │ ├── __init__.py │ │ └── search_and_analyze.py │ ├── resources │ │ ├── __init__.py │ │ └── repository_resources.py │ ├── tools │ │ ├── __init__.py │ │ ├── core │ │ │ ├── __init__.py │ │ │ ├── browse_repository.py │ │ │ ├── cancel_checkout.py │ │ │ ├── checkin_document.py │ │ │ ├── checkout_document.py │ │ │ ├── create_folder.py │ │ │ ├── delete_node.py │ │ │ ├── download_document.py │ │ │ ├── get_node_properties.py │ │ │ ├── update_node_properties.py │ │ │ └── upload_document.py │ │ └── search │ │ ├── __init__.py │ │ ├── advanced_search.py │ │ ├── cmis_search.py │ │ ├── search_by_metadata.py │ │ └── search_content.py │ └── utils │ ├── __init__.py │ ├── connection.py │ ├── file_type_analysis.py │ └── json_utils.py ├── CHANGELOG.md ├── claude-desktop-config-pipx-macos.json ├── claude-desktop-config-pipx-windows.json ├── claude-desktop-config-uv-macos.json ├── claude-desktop-config-uv-windows.json ├── claude-desktop-config-uvx-macos.json ├── claude-desktop-config-uvx-windows.json ├── config.yaml ├── docs │ ├── api_reference.md │ ├── claude_desktop_setup.md │ ├── client_configurations.md │ ├── configuration_guide.md │ ├── install_with_pip_pipx.md │ ├── mcp_inspector_setup.md │ ├── quick_start_guide.md │ ├── README.md │ ├── testing_guide.md │ └── troubleshooting.md ├── examples │ ├── batch_operations.py │ ├── document_lifecycle.py │ ├── error_handling.py │ ├── examples_summary.md │ ├── quick_start.py │ ├── README.md │ └── transport_examples.py ├── LICENSE ├── MANIFEST.in ├── mcp-inspector-http-pipx-config.json ├── mcp-inspector-http-uv-config.json ├── mcp-inspector-http-uvx-config.json ├── mcp-inspector-stdio-pipx-config.json ├── mcp-inspector-stdio-uv-config.json ├── mcp-inspector-stdio-uvx-config.json ├── prompts-for-claude.md ├── pyproject.toml ├── pytest.ini ├── README.md ├── run_server.py ├── sample-dot-env.txt ├── scripts │ ├── run_tests.py │ └── test.bat ├── tests │ ├── __init__.py │ ├── conftest.py │ ├── mcp_specific │ │ ├── MCP_INSPECTOR_CONNECTION.md │ │ ├── mcp_testing_guide.md │ │ ├── START_HTTP_SERVER.md │ │ ├── START_MCP_INSPECTOR.md │ │ ├── test_http_server.ps1 │ │ ├── test_with_mcp_inspector.md │ │ └── TESTING_INSTRUCTIONS.md │ ├── README.md │ ├── test_coverage.py │ ├── test_fastmcp_2_0.py │ ├── test_integration.py │ └── test_unit_tools.py ├── tests-debug │ └── README.md └── uv.lock ``` # Files -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- ``` 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | ``` -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- ``` 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Additional testing framework artifacts 55 | .pytest_cache/ 56 | pytest.ini.bak 57 | test-results/ 58 | test-reports/ 59 | .testmondata 60 | junit.xml 61 | report.xml 62 | 63 | # Translations 64 | *.mo 65 | *.pot 66 | 67 | # Django stuff: 68 | *.log 69 | local_settings.py 70 | db.sqlite3 71 | db.sqlite3-journal 72 | 73 | # Flask stuff: 74 | instance/ 75 | .webassets-cache 76 | 77 | # Scrapy stuff: 78 | .scrapy 79 | 80 | # Sphinx documentation 81 | docs/_build/ 82 | 83 | # PyBuilder 84 | .pybuilder/ 85 | target/ 86 | 87 | # Jupyter Notebook 88 | .ipynb_checkpoints 89 | 90 | # IPython 91 | profile_default/ 92 | ipython_config.py 93 | 94 | # pyenv 95 | # For a library or package, you might want to ignore these files since the code is 96 | # intended to run in multiple environments; otherwise, check them in: 97 | # .python-version 98 | 99 | # pipenv 100 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 101 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 102 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 103 | # install all needed dependencies. 104 | #Pipfile.lock 105 | 106 | # UV 107 | # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control. 108 | # This is especially recommended for binary packages to ensure reproducibility, and is more 109 | # commonly ignored for libraries. 110 | #uv.lock 111 | 112 | # poetry 113 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 114 | # This is especially recommended for binary packages to ensure reproducibility, and is more 115 | # commonly ignored for libraries. 116 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 117 | #poetry.lock 118 | 119 | # pdm 120 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 121 | #pdm.lock 122 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 123 | # in version control. 124 | # https://pdm.fming.dev/latest/usage/project/#working-with-version-control 125 | .pdm.toml 126 | .pdm-python 127 | .pdm-build/ 128 | 129 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 130 | __pypackages__/ 131 | 132 | # Celery stuff 133 | celerybeat-schedule 134 | celerybeat.pid 135 | 136 | # SageMath parsed files 137 | *.sage.py 138 | 139 | # Environments 140 | .env 141 | .venv 142 | env/ 143 | venv/ 144 | ENV/ 145 | env.bak/ 146 | venv.bak/ 147 | 148 | # Spyder project settings 149 | .spyderproject 150 | .spyproject 151 | 152 | # Rope project settings 153 | .ropeproject 154 | 155 | # mkdocs documentation 156 | /site 157 | 158 | # mypy 159 | .mypy_cache/ 160 | .dmypy.json 161 | dmypy.json 162 | 163 | # Pyre type checker 164 | .pyre/ 165 | 166 | # pytype static type analyzer 167 | .pytype/ 168 | 169 | # Cython debug symbols 170 | cython_debug/ 171 | 172 | # PyCharm 173 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 174 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 175 | # and can be added to the global gitignore or merged into this file. For a more nuclear 176 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 177 | #.idea/ 178 | 179 | # VS Code 180 | .vscode/settings.json 181 | .vscode/launch.json 182 | .vscode/extensions.json 183 | .vscode/tasks.json 184 | .vscode/.ropeproject 185 | # Keep workspace settings but ignore personal settings 186 | !.vscode/settings.json.template 187 | !.vscode/mcp.json 188 | 189 | # Ruff stuff: 190 | .ruff_cache/ 191 | 192 | # PyPI configuration file 193 | .pypirc 194 | 195 | # Cursor 196 | # Cursor is an AI-powered code editor.`.cursorignore` specifies files/directories to 197 | # exclude from AI features like autocomplete and code analysis. Recommended for sensitive data 198 | # refer to https://docs.cursor.com/context/ignore-files 199 | .cursorignore 200 | .cursorindexingignore 201 | *.cursormemory 202 | .cursormemory 203 | memory-bank/ 204 | 205 | # FastMCP 2.0 and MCP Development 206 | # MCP client/server communication logs 207 | *.mcp.log 208 | mcp_*.log 209 | fastmcp_*.log 210 | 211 | # MCP development artifacts 212 | .mcp_cache/ 213 | mcp_debug/ 214 | .fastmcp/ 215 | 216 | # Alfresco MCP Server specific 217 | alfresco_mcp_server.log* 218 | alfresco_*.log 219 | connection_test.py 220 | debug_*.py 221 | 222 | # Development and testing artifacts 223 | .benchmarks/ 224 | performance_results/ 225 | integration_test_results/ 226 | mock_data/ 227 | test_uploads/ 228 | test_downloads/ 229 | 230 | # Configuration files with secrets (keep templates) 231 | config.local.yaml 232 | config.dev.yaml 233 | config.prod.yaml 234 | .env.local 235 | .env.dev 236 | .env.prod 237 | # Keep sample config files 238 | !sample-*.txt 239 | !config.sample.* 240 | !.env.sample 241 | 242 | # Experimental/scratch files 243 | o.txt 244 | *.tmp 245 | test_*.py 246 | *_test.py 247 | scratch* 248 | temp* 249 | debug_output.txt 250 | output.txt ``` -------------------------------------------------------------------------------- /tests-debug/README.md: -------------------------------------------------------------------------------- ```markdown 1 | # Debug Test Files 2 | 3 | This directory contains one-off debug and development test files that are not part of the main testing suite. 4 | 5 | These files were moved from the root directory to clean up the project structure: 6 | 7 | - `test_additional_clients.py` - Testing additional client imports 8 | - `test_fixed_auth.py` - Authentication testing 9 | - `test_mcp_direct.py` - Direct MCP testing 10 | - `test_mcp_tools.py` - MCP tools testing 11 | - `test_sse_endpoint.py` - SSE endpoint testing 12 | - `test_update_debug.py` - Update debugging 13 | - `test_update_direct.py` - Direct update testing 14 | 15 | These are development/debugging tools and not included in the main test suite in `/tests/`. ``` -------------------------------------------------------------------------------- /tests/README.md: -------------------------------------------------------------------------------- ```markdown 1 | # Testing Structure 2 | 3 | This directory contains tests organized by their purpose. 4 | 5 | ## MCP Server Tests 6 | - `test_*.py` files in the root test the MCP server functionality 7 | - `mcp_specific/` contains files specifically for testing MCP server deployment 8 | 9 | ### Main Test Files (MCP Server Functionality) 10 | - `test_unit_tools.py` - Unit tests for MCP tools 11 | - `test_integration.py` - Integration tests with Alfresco 12 | - `test_fastmcp_2_0.py` - FastMCP 2.0 specific tests 13 | - `test_coverage.py` - Test coverage validation 14 | - `test_authentication.py` - Authentication tests 15 | - `test_search_debug.py` - Search functionality debugging 16 | - `test_simple_search.py` - Simple search tests 17 | - `test_response_structure.py` - Response structure validation 18 | - `test_comprehensive_scenarios.py` - Comprehensive scenario testing 19 | 20 | ### MCP Specific (Deployment & Inspector) 21 | - `mcp_specific/test_with_mcp_client.py` - MCP client testing 22 | - `mcp_specific/test_http_server.ps1` - HTTP server testing script 23 | - `mcp_specific/mcp_testing_guide.md` - Testing guide 24 | - `mcp_specific/test_with_mcp_inspector.md` - MCP Inspector testing 25 | - `mcp_specific/test_server_status.py` - Server status check 26 | 27 | ## Running Tests 28 | 29 | ### Unit Tests 30 | ```bash 31 | python -m pytest tests/test_unit_tools.py -v 32 | ``` 33 | 34 | ### Integration Tests (requires live Alfresco) 35 | ```bash 36 | python -m pytest tests/test_integration.py -v 37 | ``` 38 | 39 | ### MCP Server Status 40 | ```bash 41 | python tests/mcp_specific/test_server_status.py 42 | ``` 43 | 44 | ## Testing with MCP Inspector 45 | 1. Start HTTP server: `fastmcp run alfresco_mcp_server.fastmcp_server --host localhost --port 8003` 46 | 2. Use MCP Inspector with URL: `http://localhost:8003` 47 | 3. Note: MCP Inspector shows warning about risky auth and includes token in URL 48 | 49 | ## Testing with Claude Desktop 50 | 1. Update `claude-desktop-config.json` with current paths 51 | 2. Restart Claude Desktop application 52 | 3. Test MCP tools directly in Claude conversation ``` -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- ```markdown 1 | # Alfresco MCP Server Documentation 2 | 3 | Welcome to the documentation for the Alfresco MCP Server! This directory contains guides, tutorials, and reference materials to help you get the most out of your Alfresco integration. 4 | 5 | ## 📚 Documentation Index 6 | 7 | ### 🚀 Getting Started 8 | - [`quick_start_guide.md`](quick_start_guide.md) - Get up and running in 5 minutes 9 | - [`configuration_guide.md`](configuration_guide.md) - Configuration options and setup 10 | 11 | ### 🔧 Technical Guides 12 | - [`api_reference.md`](api_reference.md) - Complete API reference for all 15 tools 13 | 14 | ### 🏗️ Development & Testing 15 | - [`testing_guide.md`](testing_guide.md) - Running tests and validation 16 | 17 | ### 🆘 Support & Troubleshooting 18 | - [`troubleshooting.md`](troubleshooting.md) - Common issues and solutions 19 | 20 | ## 🎯 Quick Navigation 21 | 22 | | I want to... | Read this | 23 | |--------------|-----------| 24 | | **Get started quickly** | [`quick_start_guide.md`](quick_start_guide.md) | 25 | | **Configure my setup** | [`configuration_guide.md`](configuration_guide.md) | 26 | | **Learn about all tools** | [`api_reference.md`](api_reference.md) | 27 | | **Run tests** | [`testing_guide.md`](testing_guide.md) | 28 | | **Troubleshoot issues** | [`troubleshooting.md`](troubleshooting.md) | 29 | 30 | ## 📖 Documentation Standards 31 | 32 | All documentation in this directory follows these standards: 33 | 34 | - ✅ **Clear Examples**: Every feature includes working code examples 35 | - ✅ **Step-by-Step**: Complex procedures broken into manageable steps 36 | - ✅ **Cross-Referenced**: Related topics linked throughout 37 | - ✅ **Tested**: All code examples are tested and verified 38 | - ✅ **Version Aware**: Marked with applicable version information 39 | 40 | ## 🔄 Recent Updates 41 | 42 | - **v1.1.0**: Updated tool count and improved documentation 43 | - **v1.1.0**: Corrected tool count to 15 (4 search + 11 core tools) 44 | - **v1.1.0**: Removed marketing language for professional tone 45 | - **v1.0.0**: Initial production release with FastMCP 2.0 46 | 47 | ## 🤝 Contributing to Documentation 48 | 49 | Found an error or want to improve the docs? Create an issue or pull request on GitHub. 50 | 51 | ## 📞 Need Help? 52 | 53 | - 🔍 Browse [`troubleshooting.md`](troubleshooting.md) for solutions 54 | - 🐛 Report issues via GitHub Issues 55 | 56 | --- 57 | 58 | **📌 Pro Tip**: Bookmark this page and use it as your central hub for all Alfresco MCP Server documentation needs! ``` -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- ```markdown 1 | # Alfresco MCP Server Examples 2 | 3 | This directory contains practical examples demonstrating how to use the Alfresco MCP Server's **15 tools** across search, core operations, and workflow management in different scenarios. 4 | 5 | ## 📋 Available Examples 6 | 7 | ### 🚀 Quick Start Examples 8 | - [`quick_start.py`](quick_start.py) - Basic server setup and first tool call 9 | 10 | ### 🔧 Transport Examples 11 | - [`transport_examples.py`](transport_examples.py) - All transport protocols (STDIO, HTTP, SSE) 12 | 13 | ### 🛠️ Tool Usage Examples 14 | - [`document_lifecycle.py`](document_lifecycle.py) - Complete document management workflow 15 | - [`batch_operations.py`](batch_operations.py) - Bulk document processing 16 | 17 | ### 📊 Additional Examples 18 | - [`error_handling.py`](error_handling.py) - Error handling patterns 19 | 20 | ### 📖 Documentation Summary 21 | - [`examples_summary.md`](examples_summary.md) - Overview of all examples and documentation 22 | 23 | ## 🎯 Getting Started 24 | 25 | 1. **Install Dependencies**: Ensure you have the server installed 26 | 2. **Set Up Alfresco**: Configure your Alfresco connection 27 | 3. **Run Examples**: Each example is self-contained and well-documented 28 | 29 | ## 🔧 Prerequisites 30 | 31 | ```bash 32 | # Install the package 33 | pip install -e . 34 | 35 | # Set environment variables 36 | export ALFRESCO_URL="http://localhost:8080" 37 | export ALFRESCO_USERNAME="admin" 38 | export ALFRESCO_PASSWORD="admin" 39 | ``` 40 | 41 | ## 📖 Example Structure 42 | 43 | Each example includes: 44 | - ✅ **Clear documentation** of what it demonstrates 45 | - ✅ **Step-by-step comments** explaining each operation 46 | - ✅ **Error handling** best practices 47 | - ✅ **Expected output** descriptions 48 | - ✅ **Practical use cases** and scenarios 49 | 50 | ## 🚀 Running Examples 51 | 52 | ```bash 53 | # Quick start example 54 | python examples/quick_start.py 55 | 56 | # Document lifecycle workflow 57 | python examples/document_lifecycle.py 58 | 59 | # Transport protocols demonstration 60 | python examples/transport_examples.py 61 | 62 | # Batch operations and performance 63 | python examples/batch_operations.py 64 | 65 | # Error handling patterns 66 | python examples/error_handling.py 67 | ``` 68 | 69 | ## 💡 Tips 70 | 71 | - Start with `quick_start.py` for your first experience 72 | - Check `error_handling.py` for production-ready patterns 73 | - Use `batch_operations.py` for performance optimization insights 74 | - Explore `transport_examples.py` for different connection methods 75 | - Review `examples_summary.md` for documentation overview ``` -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- ```markdown 1 | # Python Alfresco MCP Server v1.1 🚀 2 | 3 | [](https://pypi.org/project/python-alfresco-mcp-server/) 4 | [](https://pepy.tech/project/python-alfresco-mcp-server) 5 | [](https://pypi.org/project/python-alfresco-mcp-server/) 6 | [](https://github.com/stevereiner/python-alfresco-mcp-server/blob/main/LICENSE) 7 | 8 | **Model Context Protocol Server for Alfresco Content Services** 9 | 10 | A full featured MCP server for Alfresco in search and content management areas. It provides the following tools: full text search (content and properties), advanced search, metadata search, CMIS SQL like search, upload, download, 11 | checkin, checkout, cancel checkout, create folder, folder browse, delete node, and get/set properties. Also has a tool for getting repository status/config (also a resource). Has one prompt example. 12 | Built with [FastMCP 2.0](https://github.com/jlowin/FastMCP). 13 | Features complete documentation, examples, and 14 | config for various MCP clients (Claude Desktop, MCP Inspector, references to configuring others). 15 | 16 | ## 🌟 What's New in v1.1 17 | 18 | ### **Modular Architecture & Enhanced Testing** 19 | - **FastMCP**: v1.0 had FastMCP 2.0 implementation that had all tools implementations in the fastmcp_server.py file 20 | - **Code Modularization in v1.1**: Split monolithic single file into organized modular structure with separate files 21 | - **Directory Organization**: Organized into `tools/search/`, `tools/core/`, `resources/`, `prompts/`, `utils/` directories 22 | - **Enhanced Testing**: Complete test suite transformation - 143 tests with 100% pass rate 23 | - **Client Configuration Files**: Added dedicated Claude Desktop and MCP Inspector configuration files 24 | - **Live Integration Testing**: 21 Alfresco server validation tests for real-world functionality 25 | - **Python-Alfresco-API**: python-alfresco-mcp-server v1.1 requires the v1.1.1 python-alfresco-api package 26 | 27 | ## 📚 Complete Documentation 28 | 29 | ### **Documentation & Examples** 30 | - **📚 Complete Documentation**: 10 guides covering setup to deployment 31 | - **💡 Examples**: 6 practical examples from quick start to implementation patterns 32 | - **🔧 Configuration Management**: Environment variables, .env files, and command-line configuration 33 | - **🏗️ Setup instruction for use with MCP client 34 | 35 | ### **Learning Resources** 36 | - **🚀 [Quick Start Guide](./docs/quick_start_guide.md)**: 5-minute setup and first operations 37 | - **🤖 [Claude Desktop Setup](./docs/claude_desktop_setup.md)**: Complete Claude Desktop configuration for users and developers 38 | - **🔧 [Client Configurations](./docs/client_configurations.md)**: Setup guide for Cursor, Claude Code, and other MCP clients 39 | - **📖 [Examples Library](./examples/README.md)**: Implementation patterns and examples 40 | 41 | ### 📖 Guides covering setup, deployment, and usage: 42 | 43 | - **[📚 Documentation Hub](./docs/README.md)** - Complete navigation and overview 44 | - **[🚀 Quick Start Guide](./docs/quick_start_guide.md)** - 5-minute setup and first operations 45 | - **[📦 Installation with pip and pipx](./docs/install_with_pip_pipx.md)** - Traditional Python package installation methods 46 | - **[🤖 Claude Desktop Setup](./docs/claude_desktop_setup.md)** - Complete Claude Desktop configuration for users and developers 47 | - **[🔧 Client Configurations](./docs/client_configurations.md)** - Setup guide for Cursor, Claude Code, and other MCP clients 48 | - **[🔍 MCP Inspector Setup](./docs/mcp_inspector_setup.md)** - Development and testing with MCP Inspector 49 | - **[🔍 API Reference](./docs/api_reference.md)** - Complete tool and resource documentation 50 | - **[⚙️ Configuration Guide](./docs/configuration_guide.md)** - Development to deployment 51 | - **[🧪 Testing Guide](./docs/testing_guide.md)** - Quality assurance and test development 52 | - **[🛠️ Troubleshooting Guide](./docs/troubleshooting.md)** - Problem diagnosis and resolution 53 | 54 | ## 🚀 Features 55 | 56 | ### Content Management and Search Tools 57 | - **Search Tools**: 58 | - **Full Text Search**: Basic content search with wildcard support (search_content) 59 | - **Advanced Search**: AFTS query language with date filters, sorting, and field targeting 60 | - **Metadata Search**: Property-based queries with operators (equals, contains, date ranges) 61 | - **CMIS Search**: SQL like queries for complex content discovery 62 | - **Document Lifecycle**: Upload, download, check-in, checkout, cancel checkout 63 | - **Version Management**: Create major/minor versions with comments 64 | - **Folder Operations**: Create folders, delete folder nodes 65 | - **Property Management**: Get and set document/folder properties and names 66 | - **Node Operations**: Delete nodes (documents and folders) (trash or permanent) 67 | - **Repository Info**: (Tool and Resource) Returns repository status, version and whether Community or Enterprise, and module configuration 68 | 69 | ### MCP Architecture 70 | - **FastMCP 2.0 Framework**: Modern, high-performance MCP server implementation 71 | - **Multiple Transports**: 72 | - **STDIO** (direct MCP protocol) - Default and fastest 73 | - **HTTP** (RESTful API) - Web services and testing 74 | - **SSE** (Server-Sent Events) - Real-time streaming updates 75 | - **Enterprise Security**: OAuth 2.1 (optional) 76 | - **Type Safety**: Full Pydantic v2 models 77 | - **In-Memory Testing**: Client testing with faster execution 78 | - **Configuration**: Environment variables, .env files 79 | 80 | ### Alfresco Integration 81 | Works with Alfresco Community (tested) and Enterprise editions 82 | 83 | 84 | ## 📋 Requirements 85 | 86 | - Python 3.10+ 87 | - Alfresco Content Services (Community or Enterprise) 88 | 89 | > **Note**: The `python-alfresco-api >= 1.1.1` dependency is automatically installed with `python-alfresco-mcp-server` 90 | 91 | ## 🛠️ Installation 92 | 93 | ### Install Python 94 | 95 | You need to have Python 3.10+ installed for the sections below. If not, download the latest 3.13.x version from: 96 | 97 | [Python.org Downloads](https://www.python.org/downloads/) 98 | 99 | ### UV/UVX Setup (Recommended) 100 | 101 | **UV** is a modern Python package manager written in **Rust** that provides both `uv` (package manager) and `uvx` (tool runner). **Much faster than pip** due to its compiled nature and optimized dependency resolution. 102 | 103 | ```bash 104 | # Install UV (provides both uv and uvx commands) 105 | # Windows 106 | powershell -c "irm https://astral.sh/uv/install.ps1 | iex" 107 | 108 | # macOS/Linux 109 | curl -LsSf https://astral.sh/uv/install.sh | sh 110 | 111 | # Or via pip if you prefer 112 | pip install uv 113 | 114 | # Verify installation (both commands should work) 115 | uv --version 116 | uvx --version 117 | ``` 118 | 119 | **UV Reference Links:** 120 | - **[UV Installation Guide](https://docs.astral.sh/uv/getting-started/installation/)** - Official installation instructions and platform-specific options 121 | - **[UV Documentation](https://docs.astral.sh/uv/)** - Complete UV documentation, guides, and advanced usage 122 | 123 | ### Option A: UVX - Modern Tool Runner (Recommended for Users) 124 | 125 | **UVX** is UV's tool runner - similar to pipx but faster and more modern. Automatically handles isolation and global availability: 126 | 127 | ```bash 128 | # Install python-alfresco-mcp-server with uvx (after UV/UVX setup above) 129 | uvx python-alfresco-mcp-server --help 130 | 131 | # This tests that installation worked - UVX automatically installs packages on first use! 132 | ``` 133 | 134 | **Why UVX?** UVX combines the benefits of pipx (isolated environments + global availability) with UV's Rust-based speed and modern dependency resolution. It automatically installs packages on first use. 135 | 136 | ### Option B: UV - Modern Package Manager (Recommended for Development) 137 | 138 | **UV** is a modern Python package manager written in **Rust** that handles everything automatically. **Much faster than pip** due to its compiled nature and optimized dependency resolution. 139 | 140 | ```bash 141 | # Install and run from PyPI (fastest for users) 142 | uv tool install python-alfresco-mcp-server 143 | uv tool run python-alfresco-mcp-server --help # Tests that installation worked 144 | 145 | # Or install from source (for development) 146 | git clone https://github.com/stevereiner/python-alfresco-mcp-server.git 147 | cd python-alfresco-mcp-server 148 | uv run python-alfresco-mcp-server --help # Tests that installation worked 149 | ``` 150 | 151 | ### Option C: Traditional Methods (pip and pipx) 152 | 153 | For traditional Python package management approaches, see the **[Installation with pip and pipx](./docs/install_with_pip_pipx.md)**. 154 | 155 | **Note**: You still need to configure your MCP client (Claude Desktop, MCP Inspector, etc.) with the appropriate configuration. See the [MCP Client Setup and Use](#mcp-client-setup-and-use) section below for client configuration details. 156 | 157 | ### Source Installation (For Development) 158 | 159 | For development or access to latest features: 160 | 161 | ```bash 162 | # 1. Clone the repository 163 | git clone https://github.com/stevereiner/python-alfresco-mcp-server.git 164 | cd python-alfresco-mcp-server 165 | 166 | # 2. UV handles everything automatically - run immediately! 167 | uv run python-alfresco-mcp-server --help # Tests that installation worked 168 | 169 | # Or install dependencies explicitly for development: 170 | uv sync # Basic dependencies 171 | uv sync --extra dev # With development tools 172 | uv sync --extra test # With testing tools 173 | uv sync --extra all # Everything 174 | ``` 175 | 176 | ### 4. Configure Alfresco Connection 177 | 178 | **Option 1: Environment Variables** 179 | ```bash 180 | # Linux/Mac 181 | export ALFRESCO_URL="http://localhost:8080" 182 | export ALFRESCO_USERNAME="admin" 183 | export ALFRESCO_PASSWORD="admin" 184 | export ALFRESCO_VERIFY_SSL="false" 185 | 186 | # Windows PowerShell 187 | $env:ALFRESCO_URL="http://localhost:8080" 188 | $env:ALFRESCO_USERNAME="admin" 189 | $env:ALFRESCO_PASSWORD="admin" 190 | $env:ALFRESCO_VERIFY_SSL="false" 191 | 192 | # Windows Command Prompt 193 | set ALFRESCO_URL=http://localhost:8080 194 | set ALFRESCO_USERNAME=admin 195 | set ALFRESCO_PASSWORD=admin 196 | set ALFRESCO_VERIFY_SSL=false 197 | ``` 198 | 199 | **Option 2: .env file** (recommended - cross-platform): 200 | ```bash 201 | # Copy sample-dot-env.txt to .env and customize 202 | # Linux/macOS 203 | cp sample-dot-env.txt .env 204 | 205 | # Windows 206 | copy sample-dot-env.txt .env 207 | 208 | # Edit .env file with your settings 209 | ALFRESCO_URL=http://localhost:8080 210 | ALFRESCO_USERNAME=admin 211 | ALFRESCO_PASSWORD=admin 212 | ALFRESCO_VERIFY_SSL=false 213 | ``` 214 | > **Note**: The `.env` file is not checked into git for security. Use `sample-dot-env.txt` as a template. 215 | 216 | 📖 **See [Configuration Guide](./docs/configuration_guide.md) for complete setup options** 217 | 218 | ## Alfresco Installation 219 | 220 | If you don't have an Alfresco server installed you can get a docker for the 221 | Community version from Github 222 | 223 | ```bash 224 | git clone https://github.com/Alfresco/acs-deployment.git 225 | ``` 226 | 227 | **Move to Docker Compose directory** 228 | 229 | ```bash 230 | cd acs-deployment/docker-compose 231 | ``` 232 | 233 | **Edit community-compose.yaml** 234 | - Note: you will likely need to comment out activemq ports other than 8161 235 | 236 | ```bash 237 | ports: 238 | - "8161:8161" # Web Console 239 | #- "5672:5672" # AMQP 240 | #- "61616:61616" # OpenWire 241 | #- "61613:61613" # STOMP 242 | ``` 243 | 244 | **Start Alfresco with Docker Compose** 245 | 246 | ```bash 247 | docker-compose -f community-compose.yaml up 248 | ``` 249 | 250 | ## 🚀 Usage 251 | 252 | ### MCP Server Startup 253 | 254 | **With UVX (Recommended - Automatic isolation and global availability):** 255 | 256 | ```bash 257 | # Run MCP server with STDIO transport (default) 258 | uvx python-alfresco-mcp-server 259 | 260 | # HTTP transport for web services (matches MCP Inspector) 261 | uvx python-alfresco-mcp-server --transport http --host 127.0.0.1 --port 8003 262 | 263 | # SSE transport for real-time streaming 264 | uvx python-alfresco-mcp-server --transport sse --host 127.0.0.1 --port 8001 265 | ``` 266 | 267 | **With UV (For development or source installations):** 268 | 269 | ```bash 270 | # Run MCP server with STDIO transport (default) 271 | uv run python-alfresco-mcp-server 272 | 273 | # HTTP transport for web services (matches MCP Inspector) 274 | uv run python-alfresco-mcp-server --transport http --host 127.0.0.1 --port 8003 275 | 276 | # SSE transport for real-time streaming 277 | uv run python-alfresco-mcp-server --transport sse --host 127.0.0.1 --port 8001 278 | ``` 279 | 280 | **With Traditional Methods (pip/pipx):** 281 | 282 | See the **[Installation with pip and pipx](./docs/install_with_pip_pipx.md)** for pip and pipx usage instructions. 283 | 284 | ### MCP Client Setup and Use 285 | 286 | Python-Alfresco-MCP-Server was tested with Claude Desktop which is recommended as an end user MCP client. Python-Alfresco-MCP-Server was also tested with MCP Inspector which is recommended for developers to test tools with argument values. 287 | 288 | #### 🤖 **Claude Desktop** for Windows (tested) and MacOS (not tested) 289 | 290 | 📖 **Complete Setup Guide**: **[Claude Desktop Setup Guide](./docs/claude_desktop_setup.md)** 291 | 292 | **📥 Download Claude Desktop (Free and Pro versions):** 293 | - **[Download Claude Desktop](https://claude.ai/download)** - Official Anthropic download page 294 | - Available for **Windows** and **macOS** only (no Linux version) 295 | - **Free tier** includes full MCP support and Claude Sonnet 4 access with limits, older Claude models 296 | (Claude Opus 4 only in Pro) 297 | 298 | 299 | **🔧 Claude Desktop Configuration by Installation Method:** 300 | 301 | The Claude Desktop configuration differs based on how you installed the MCP server: 302 | 303 | **1. UVX (Recommended - Modern tool runner):** 304 | ```json 305 | { 306 | "command": "uvx", 307 | "args": ["python-alfresco-mcp-server", "--transport", "stdio"] 308 | } 309 | ``` 310 | - **Sample Config Files:** 311 | - Windows: [`claude-desktop-config-uvx-windows.json`](./claude-desktop-config-uvx-windows.json) 312 | - macOS: [`claude-desktop-config-uvx-macos.json`](./claude-desktop-config-uvx-macos.json) 313 | - UVX automatically handles isolation and global availability 314 | - Fastest and most modern approach 315 | 316 | **2. UV (Development or source installations):** 317 | ```json 318 | { 319 | "command": "uv", 320 | "args": ["run", "python-alfresco-mcp-server", "--transport", "stdio"], 321 | "cwd": "C:\\path\\to\\python-alfresco-mcp-server" 322 | } 323 | ``` 324 | - **Sample Config Files:** 325 | - Windows: [`claude-desktop-config-uv-windows.json`](./claude-desktop-config-uv-windows.json) 326 | - macOS: [`claude-desktop-config-uv-macos.json`](./claude-desktop-config-uv-macos.json) 327 | - Uses `uv run` with `cwd` pointing to your **project directory** 328 | - UV automatically finds and uses the `.venv` from the project directory 329 | - Works for both source installations and after `uv tool install` 330 | 331 | **3. Traditional Methods (pipx/pip):** 332 | 333 | For traditional installation methods, see the **[Installation with pip and pipx](./docs/install_with_pip_pipx.md)** which covers: 334 | - **pipx**: [`claude-desktop-config-pipx-windows.json`](./claude-desktop-config-pipx-windows.json) / [`claude-desktop-config-pipx-macos.json`](./claude-desktop-config-pipx-macos.json) 335 | - **pip**: Manual venv path configuration 336 | 337 | **🔐 Tool-by-Tool Permission System:** 338 | Claude Desktop will prompt you **individually for each tool** on first use. Since this MCP server has 15 tools, you may see up to 15 permission prompts if you use all features. For each tool, you can choose: 339 | - **"Allow once"** - Approve this single tool use only 340 | - **"Always allow"** - Approve all future uses of this specific tool automatically (recommended for regular use) 341 | 342 | This tool-by-tool security feature ensures you maintain granular control over which external tools can be executed. 343 | 344 | > **🛡️ Virus Scanner Note**: If you have virus checkers like Norton 360, don't worry if you get a "checking" message once for pip, pipx, uv, uvx, or python-alfresco-mcp-server.exe - this is normal security scanning behavior. 345 | 346 | **Using the Tools:** 347 | 348 | - **Chat naturally** about what you want to do with documents and search 349 | - **Mention "Alfresco"** to ensure the MCP server is used (e.g., "In Alfresco...") 350 | - **Use tool-related keywords** - mention something close to the tool name 351 | - **Follow-up prompts** will know the document from previous context 352 | 353 | **Example 1: Document Management** 354 | 355 | 1. Upload a simple text document: "Please create a file called 'claude_test_doc-25 07 25 101 0 AM.txt' in the repository shared folder with this content: 'This is a test document created by Claude via MCP.' description 'Test document uploaded via Claude MCP'" 356 | 2. Update properties: "Set the description property of this document to 'my desc'" 357 | 3. Check out the document 358 | 4. Cancel checkout 359 | 5. Check out again 360 | 6. Check in as a major version 361 | 7. Download the document 362 | 8. Upload a second document from "C:\1 sample files\cmispress.pdf" 363 | 364 | > **Note**: Claude will figure out to use base64 encoding for the first upload on a second try 365 | 366 | **Example 2: Search Operations** 367 | 368 | "With Alfresco please test all 3 search methods and CMIS query:" 369 | - Basic search for "txt" documents, return max 10 370 | - Advanced search for documents created after 2024-01-01, return max 25 371 | - Metadata search for documents where cm:title contains "test", limit to 50 372 | - CMIS search to find all txt documents, limit to 50 373 | 374 | **More Examples: Create Folder, Browse Folders, Get Repository Info** 375 | 376 | - "Create a folder called '25 07 25 01 18 am' in shared folder" 377 | - "List docs and folders in shared folder" *(will use -shared-)* 378 | - "Can you show me what's in my Alfresco home directory?" *(will use browse_repository -my-)* 379 | - "Get info on Alfresco" *(will use repository_info tool)* 380 | 381 | **Chat Box Buttons** 382 | 383 | - Use **Search and tools button** (two horizontal lines with circles icon) in the chat box and choose "python-alfresco-mcp-server" - this allows you to enable/disable all tools or individual tools 384 | 385 | - Click the **+ Button** → "Add from alfresco" for quick access to resources and prompts 386 | 387 | **Search and Analyze Prompt:** 388 | - Provides a form with query field for full-text search 389 | - Analysis types: **summary**, **detailed**, **trends**, or **compliance** 390 | - **Generates template text** to copy/paste into chat for editing 391 | 392 | **Repository Info Resource (and Tool):** 393 | - Provides status information in text format for viewing or copying 394 | 395 | **Examples:** 396 | - See [`prompts-for-claude.md`](./prompts-for-claude.md) for examples testing the tools 397 | 398 | 399 | #### 🔍 **MCP Inspector** (Development/Testing) 400 | 401 | > 📖 **Setup Guide**: Complete MCP Inspector setup and connection instructions in [MCP Inspector Setup Guide](./docs/mcp_inspector_setup.md) 402 | 403 | **📥 Install MCP Inspector:** 404 | - **Prerequisites**: Requires **Node.js 18+** - Download from **[nodejs.org](https://nodejs.org/)** 405 | - **Install Command**: `npm install -g @modelcontextprotocol/inspector` 406 | - **Or run directly**: `npx @modelcontextprotocol/inspector` (no global install needed) 407 | - **Purpose**: Web-based tool for testing MCP servers and individual tools with custom parameters 408 | 409 | **Working Method (Recommended):** 410 | 411 | **1. Start MCP Server with HTTP transport:** 412 | 413 | ```bash 414 | # With UVX (recommended) 415 | uvx python-alfresco-mcp-server --transport http --port 8003 416 | 417 | # With UV (development) 418 | uv run python-alfresco-mcp-server --transport http --port 8003 419 | 420 | # Traditional methods - see Traditional Installation Guide 421 | ``` 422 | 423 | **2. Start MCP Inspector with config:** 424 | 425 | **UVX Installation (Recommended):** 426 | ```bash 427 | # Start with stdio transport 428 | npx @modelcontextprotocol/inspector --config mcp-inspector-stdio-uvx-config.json --server python-alfresco-mcp-server 429 | 430 | # Start with http transport 431 | npx @modelcontextprotocol/inspector --config mcp-inspector-http-uvx-config.json --server python-alfresco-mcp-server 432 | ``` 433 | 434 | **UV Installation (Development):** 435 | ```bash 436 | # From project directory where config files exist 437 | npx @modelcontextprotocol/inspector --config mcp-inspector-stdio-uv-config.json --server python-alfresco-mcp-server # stdio transport 438 | npx @modelcontextprotocol/inspector --config mcp-inspector-http-uv-config.json --server python-alfresco-mcp-server # http transport 439 | ``` 440 | 441 | **Traditional Methods (pipx/pip):** 442 | 443 | See the **[Installation with pip and pipx](./docs/install_with_pip_pipx.md)** for pipx and pip configuration options. 444 | 445 | **3. Open browser with pre-filled token:** 446 | 447 | - Use the URL provided in the output (includes authentication token) 448 | - Example: `http://localhost:6274/?MCP_PROXY_AUTH_TOKEN=<token>` 449 | - This step applies to **all installation methods** (uv, uvx, pip, pipx) 450 | 451 | This approach avoids proxy connection errors and provides direct authentication. 452 | 453 | 454 | #### 🔧 **Other MCP Clients** 455 | 456 | For Cursor, Claude Code, and other MCP clients: 457 | 458 | 📖 **Complete Setup Guide**: **[Client Configuration Guide](./docs/client_configurations.md)** 459 | 460 | 461 | ## 🛠️ Available Tools (15 Total) 462 | 463 | ### 🔍 Search Tools (4) 464 | | Tool | Description | Parameters | 465 | |------|-------------|------------| 466 | | `search_content` | Search documents and folders | `query` (str), `max_results` (int), `node_type` (str) | 467 | | `advanced_search` | Advanced search with filters | `query` (str), `content_type` (str), `created_after` (str), etc. | 468 | | `search_by_metadata` | Search by metadata properties | `property_name` (str), `property_value` (str), `comparison` (str) | 469 | | `cmis_search` | CMIS SQL queries | `cmis_query` (str), `preset` (str), `max_results` (int) | 470 | 471 | ### 🛠️ Core Tools (11) 472 | | Tool | Description | Parameters | 473 | |------|-------------|------------| 474 | | `browse_repository` | Browse repository folders | `node_id` (str) | 475 | | `repository_info` | Get repository information | None | 476 | | `upload_document` | Upload new document | `filename` (str), `content_base64` (str), `parent_id` (str), `description` (str) | 477 | | `download_document` | Download document content | `node_id` (str), `save_to_disk` (bool) | 478 | | `create_folder` | Create new folder | `folder_name` (str), `parent_id` (str), `description` (str) | 479 | | `get_node_properties` | Get node metadata | `node_id` (str) | 480 | | `update_node_properties` | Update node metadata | `node_id` (str), `name` (str), `title` (str), `description` (str), `author` (str) | 481 | | `delete_node` | Delete document/folder | `node_id` (str), `permanent` (bool) | 482 | | `checkout_document` | Check out for editing | `node_id` (str), `download_for_editing` (bool) | 483 | | `checkin_document` | Check in after editing | `node_id` (str), `comment` (str), `major_version` (bool), `file_path` (str) | 484 | | `cancel_checkout` | Cancel checkout/unlock | `node_id` (str) | 485 | 486 | 📖 **See [API Reference](./docs/api_reference.md) for detailed tool documentation** 487 | 488 | ## 📊 Available Resources 489 | 490 | ### Repository Information 491 | | Resource | Description | Access Method | 492 | |----------|-------------|---------------| 493 | | `repository_info` | Get comprehensive repository information including version, edition, license details, installed modules, and system status | Available as both MCP resource and tool | 494 | 495 | The `repository_info` resource provides: 496 | - **Repository Details**: ID, edition (Community/Enterprise), version information 497 | - **License Information**: Issued/expires dates, remaining days, license holder, entitlements 498 | - **System Status**: Read-only mode, audit enabled, quick share, thumbnail generation 499 | - **Installed Modules**: Up to 10 modules with ID, title, version, and installation state 500 | 501 | 📖 **See [API Reference](./docs/api_reference.md) for detailed resource documentation** 502 | 503 | ## 🎯 Available Prompts 504 | 505 | ### Search and Analyze Prompt 506 | | Prompt | Description | Parameters | 507 | |--------|-------------|------------| 508 | | `search_and_analyze` | Interactive form for guided content search and analysis | `query` (search terms), `analysis_type` (summary/detailed/trends/compliance) | 509 | 510 | The Search and Analyze Prompt provides: 511 | - **Interactive Form**: User-friendly interface with query input field 512 | - **Analysis Options**: Choose from summary, detailed analysis, trends, or compliance reporting 513 | - **Template Generation**: Creates copyable template text for chat conversations 514 | - **Query Assistance**: Helps users structure effective search queries 515 | - **Multiple Search Types**: Integrates with all 4 search tools (content, advanced, metadata, CMIS) 516 | 517 | 📖 **See [API Reference](./docs/api_reference.md) for detailed prompt documentation** 518 | 519 | ## 🔧 Configuration Options 520 | 521 | | Environment Variable | Default | Description | 522 | |---------------------|---------|-------------| 523 | | `ALFRESCO_URL` | `http://localhost:8080` | Alfresco server URL | 524 | | `ALFRESCO_USERNAME` | `admin` | Username for authentication | 525 | | `ALFRESCO_PASSWORD` | `admin` | Password for authentication | 526 | | `ALFRESCO_VERIFY_SSL` | `false` | Verify SSL certificates | 527 | | `ALFRESCO_TIMEOUT` | `30` | Request timeout (seconds) | 528 | | `FASTAPI_HOST` | `localhost` | FastAPI host | 529 | | `FASTAPI_PORT` | `8000` | FastAPI port | 530 | | `LOG_LEVEL` | `INFO` | Logging level | 531 | | `MAX_FILE_SIZE` | `100000000` | Max upload size (bytes) | 532 | 533 | ⚙️ **See [Configuration Guide](./docs/configuration_guide.md) for deployment options** 534 | 535 | ## 🏗️ Architecture 536 | 537 | ``` 538 | ┌─────────────────────────────────────────────────────┐ 539 | │ MCP Clients │ 540 | │ Claude Desktop │ MCP Inspector │ Cursor │ Claude │ 541 | │ Code │ n8n │ LangFlow │ Custom MCP Client App │ 542 | └─────────────────┬───────────────────────────────────┘ 543 | │ stdio/HTTP/SSE 544 | ┌─────────────────▼───────────────────────────────────┐ 545 | │ FastMCP 2.0 MCP Server │ 546 | │ ┌─────────────┬─────────────┬─────────────────┐ │ 547 | │ │ MCP Tools │ MCP │ HTTP/SSE API │ │ 548 | │ │ (15 total) │ Resources │ │ │ 549 | │ │ │ MCP Prompts │ │ │ 550 | │ └─────────────┴─────────────┴─────────────────┘ │ 551 | └─────────────────┬───────────────────────────────────┘ 552 | │ python-alfresco-api 553 | ┌─────────────────▼───────────────────────────────────┐ 554 | │ Alfresco Content Services │ 555 | │ (Community/Enterprise Edition) │ 556 | └─────────────────────────────────────────────────────┘ 557 | ``` 558 | 559 | ## 🧪 Testing & Quality 560 | 561 | ### Test Suite Overview 562 | - **143 Total Tests**: **100% passed** - Coverage of all functionality 563 | - **122 Unit Tests**: **100% passed** - Core functionality validated with mocking (FastMCP 2.0, tools, coverage) 564 | - **21 Integration Tests**: **100% passed** - Live server testing (search, upload, download, document lifecycle) 565 | - **Integration Tests**: Automated end-to-end testing covering all core document lifecycle scenarios 566 | - **Performance Validated**: Search <1s, concurrent operations, resource access 567 | 568 | ### Coverage Report (Post-Cleanup) 569 | - **Overall Coverage**: 51% (1,829 statements tested) 570 | - **FastMCP 2.0 Core**: Well tested with comprehensive unit coverage 571 | - **Configuration Module**: 93% coverage - Fully tested 572 | - **Package Initialization**: 100% coverage (5/5 lines) - Complete 573 | - **Overall Project**: 51% coverage of comprehensive codebase 574 | 575 | ### Run Tests 576 | 577 | ```bash 578 | # Run full test suite 579 | pytest 580 | 581 | # Run with coverage report 582 | pytest --cov=alfresco_mcp_server --cov-report=term-missing 583 | 584 | # Run specific test categories 585 | pytest -m "unit" # Unit tests only 586 | pytest -m "fastmcp" # FastMCP 2.0 tests 587 | pytest -m "integration" # Integration tests (requires Alfresco) 588 | ``` 589 | 590 | 🧪 **See [Testing Guide](./docs/testing_guide.md) for detailed testing strategies** 591 | 592 | ### 🧪 Test Categories and Execution 593 | 594 | The project includes **4 levels of testing**: 595 | 596 | 1. **📋 Unit Tests** (122 tests) - Fast, mocked, isolated component testing 597 | 2. **🔗 Integration Tests** (21 tests) - Live Alfresco server testing 598 | 3. **📝 Comprehensive Tests** - Automated core document lifecycle scenarios 599 | 4. **📊 Coverage Tests** - Edge cases and error path coverage 600 | 601 | 602 | 603 | ## 🧪 Development 604 | 605 | ### Setup Development Environment 606 | 607 | ```bash 608 | git clone <repository> 609 | cd python-alfresco-mcp-server 610 | 611 | # UV handles everything automatically - no manual venv setup needed! 612 | uv sync --extra dev # Install with development tools 613 | uv sync --extra test # With testing tools 614 | uv sync --extra all # Everything 615 | 616 | # Run immediately to test that installation worked 617 | uv run python-alfresco-mcp-server --help 618 | 619 | # Install python-alfresco-api for local development (if needed) 620 | uv add --editable ../python-alfresco-api 621 | ``` 622 | 623 | **Traditional Development Setup:** 624 | 625 | See the **[Installation with pip and pipx](./docs/install_with_pip_pipx.md)** for pip-based development setup. 626 | 627 | ## 💡 Examples 628 | 629 | ### Real-world implementation patterns from beginner to enterprise: 630 | 631 | - **[💡 Examples Library](./examples/README.md)** - Complete navigation and learning paths 632 | - **[🏃 Quick Start](./examples/quick_start.py)** - 5-minute introduction and basic operations 633 | - **[📋 Document Lifecycle](./examples/document_lifecycle.py)** - Complete process demonstration 634 | - **[🚀 Transport Examples](./examples/transport_examples.py)** - STDIO, HTTP, and SSE protocols 635 | - **[⚡ Batch Operations](./examples/batch_operations.py)** - High-performance bulk processing 636 | - **[🛡️ Error Handling](./examples/error_handling.py)** - Resilience patterns 637 | - **[📊 Examples Summary](./examples/examples_summary.md)** - Overview and statistics 638 | 639 | ## 🤝 Contributing 640 | 641 | 1. Fork the repository 642 | 2. Create a feature branch (`git checkout -b feature/new-feature`) 643 | 3. Commit your changes (`git commit -m 'Add new feature'`) 644 | 4. Push to the branch (`git push origin feature/new-feature`) 645 | 5. Open a Pull Request 646 | 647 | ## 📄 License 648 | 649 | This project is licensed under the Apache 2.0 License - see the [LICENSE](LICENSE) file for details. 650 | 651 | ## 🔗 Related Projects and References 652 | 653 | - **[Hyland Alfresco](https://www.hyland.com/en/solutions/products/alfresco-platform)** - Content management platform (Enterprise and Community editions) 654 | - **[python-alfresco-api](https://github.com/stevereiner/python-alfresco-api)** - The underlying Alfresco API library 655 | - **[FastMCP 2.0](https://github.com/jlowin/FastMCP)** - Modern framework for building MCP servers 656 | - **[FastMCP Documentation](https://gofastmcp.com/)** - Complete FastMCP framework documentation and guides 657 | - **[Model Context Protocol](https://modelcontextprotocol.io)** - Official MCP specification and documentation 658 | - **[Playbooks.com MCP List](https://playbooks.com/mcp/stevereiner-alfresco-content-services)** - Python Alfresco MCP Server listing 659 | - **[PulseMCP.com MCP List](https://www.pulsemcp.com/servers/stevereiner-alfresco-content-services)** - Python Alfresco MCP Server listing 660 | - **[Glama.ai MCP List](https://glama.ai/mcp/servers?query=alfresco)** - Glama Alfresco list including Python Alfresco MCP Server listing 661 | - **[MCPMarket.com MCP List](https://mcpmarket.com/server/alfresco)** - Python Alfresco MCP Server listing 662 | 663 | ## 🙋♂️ Support 664 | 665 | - 📚 **Documentation**: Complete guides in [`./docs/`](./docs/README.md) 666 | - 💡 **Examples**: Implementation patterns in [`./examples/`](./examples/README.md) 667 | - 🧪 **Testing**: Quality assurance in [`./docs/testing_guide.md`](./docs/testing_guide.md) 668 | - 🔍 **MCP Inspector**: Development testing in [`./docs/mcp_inspector_setup.md`](./docs/mcp_inspector_setup.md) 669 | - 🛠️ **Troubleshooting**: Problem solving in [`./docs/troubleshooting.md`](./docs/troubleshooting.md) 670 | - 🐛 **Issues**: [GitHub Issues](https://github.com/stevereiner/python-alfresco-mcp-server/issues) 671 | 672 | --- 673 | 674 | **🚀 MCP server built with [python-alfresco-api](https://github.com/stevereiner/python-alfresco-api) and [FastMCP 2.0](https://github.com/paulinephelan/FastMCP)** 675 | ``` -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- ```python 1 | ``` -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "svn.ignoreMissingSvnWarning": true 3 | } ``` -------------------------------------------------------------------------------- /alfresco_mcp_server/prompts/__init__.py: -------------------------------------------------------------------------------- ```python 1 | # Prompts module 2 | 3 | from . import search_and_analyze 4 | 5 | __all__ = [ 6 | "search_and_analyze", 7 | ] ``` -------------------------------------------------------------------------------- /alfresco_mcp_server/resources/__init__.py: -------------------------------------------------------------------------------- ```python 1 | # Resources module 2 | 3 | from . import repository_resources 4 | 5 | __all__ = [ 6 | "repository_resources", 7 | ] ``` -------------------------------------------------------------------------------- /alfresco_mcp_server/tools/__init__.py: -------------------------------------------------------------------------------- ```python 1 | """ 2 | Tools module for Alfresco MCP Server. 3 | Contains core and search tools organized hierarchically. 4 | """ 5 | 6 | # Import subdirectories 7 | from . import core 8 | from . import search 9 | 10 | __all__ = [ 11 | "core", 12 | "search", 13 | ] ``` -------------------------------------------------------------------------------- /alfresco_mcp_server/tools/search/__init__.py: -------------------------------------------------------------------------------- ```python 1 | # Search tools module 2 | 3 | from . import ( 4 | advanced_search, 5 | cmis_search, 6 | search_by_metadata, 7 | search_content, 8 | ) 9 | 10 | __all__ = [ 11 | "advanced_search", 12 | "cmis_search", 13 | "search_by_metadata", 14 | "search_content", 15 | ] ``` -------------------------------------------------------------------------------- /mcp-inspector-stdio-pipx-config.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "mcpServers": { 3 | "python-alfresco-mcp-server": { 4 | "command": "python-alfresco-mcp-server", 5 | "args": ["--transport", "stdio"], 6 | "env": { 7 | "ALFRESCO_URL": "http://localhost:8080", 8 | "ALFRESCO_USERNAME": "admin", 9 | "ALFRESCO_PASSWORD": "admin" 10 | } 11 | } 12 | } 13 | } ``` -------------------------------------------------------------------------------- /config.yaml: -------------------------------------------------------------------------------- ```yaml 1 | # Alfresco MCP Server Configuration 2 | alfresco: 3 | base_url: "http://localhost:8080" # Base URL only - API paths added by individual clients 4 | username: "admin" 5 | password: "admin" 6 | verify_ssl: false 7 | timeout: 30 8 | 9 | # MCP Server Configuration 10 | server: 11 | name: "python-alfresco-mcp-server" 12 | version: "1.1.0" ``` -------------------------------------------------------------------------------- /mcp-inspector-http-pipx-config.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "mcpServers": { 3 | "python-alfresco-mcp-server": { 4 | "command": "python-alfresco-mcp-server", 5 | "args": ["--transport", "http", "--port", "8003"], 6 | "env": { 7 | "ALFRESCO_URL": "http://localhost:8080", 8 | "ALFRESCO_USERNAME": "admin", 9 | "ALFRESCO_PASSWORD": "admin" 10 | } 11 | } 12 | } 13 | } ``` -------------------------------------------------------------------------------- /claude-desktop-config-pipx-macos.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "mcpServers": { 3 | "python-alfresco-mcp-server": { 4 | "command": "python-alfresco-mcp-server", 5 | "args": [ 6 | "--transport", 7 | "stdio" 8 | ], 9 | "env": { 10 | "ALFRESCO_URL": "http://localhost:8080", 11 | "ALFRESCO_USERNAME": "admin", 12 | "ALFRESCO_PASSWORD": "admin" 13 | } 14 | } 15 | } 16 | } ``` -------------------------------------------------------------------------------- /claude-desktop-config-uvx-macos.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "mcpServers": { 3 | "python-alfresco-mcp-server": { 4 | "command": "uvx", 5 | "args": [ 6 | "python-alfresco-mcp-server", 7 | "--transport", 8 | "stdio" 9 | ], 10 | "env": { 11 | "ALFRESCO_URL": "http://localhost:8080", 12 | "ALFRESCO_USERNAME": "admin", 13 | "ALFRESCO_PASSWORD": "admin" 14 | } 15 | } 16 | } 17 | } ``` -------------------------------------------------------------------------------- /mcp-inspector-stdio-uvx-config.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "mcpServers": { 3 | "python-alfresco-mcp-server": { 4 | "command": "uvx", 5 | "args": [ 6 | "python-alfresco-mcp-server", 7 | "--transport", 8 | "stdio" 9 | ], 10 | "env": { 11 | "ALFRESCO_URL": "http://localhost:8080", 12 | "ALFRESCO_USERNAME": "admin", 13 | "ALFRESCO_PASSWORD": "admin" 14 | } 15 | } 16 | } 17 | } ``` -------------------------------------------------------------------------------- /claude-desktop-config-pipx-windows.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "mcpServers": { 3 | "python-alfresco-mcp-server": { 4 | "command": "python-alfresco-mcp-server", 5 | "args": [ 6 | "--transport", 7 | "stdio" 8 | ], 9 | "env": { 10 | "ALFRESCO_URL": "http://localhost:8080", 11 | "ALFRESCO_USERNAME": "admin", 12 | "ALFRESCO_PASSWORD": "admin", 13 | "PYTHONIOENCODING": "utf-8", 14 | "PYTHONLEGACYWINDOWSSTDIO": "1" 15 | } 16 | } 17 | } 18 | } ``` -------------------------------------------------------------------------------- /claude-desktop-config-uv-macos.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "mcpServers": { 3 | "python-alfresco-mcp-server": { 4 | "command": "uv", 5 | "args": [ 6 | "run", 7 | "python-alfresco-mcp-server", 8 | "--transport", 9 | "stdio" 10 | ], 11 | "cwd": "/path/to/python-alfresco-mcp-server", 12 | "env": { 13 | "ALFRESCO_URL": "http://localhost:8080", 14 | "ALFRESCO_USERNAME": "admin", 15 | "ALFRESCO_PASSWORD": "admin" 16 | } 17 | } 18 | } 19 | } ``` -------------------------------------------------------------------------------- /mcp-inspector-http-uvx-config.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "mcpServers": { 3 | "python-alfresco-mcp-server": { 4 | "command": "uvx", 5 | "args": [ 6 | "python-alfresco-mcp-server", 7 | "--transport", 8 | "http", 9 | "--host", 10 | "127.0.0.1", 11 | "--port", 12 | "8003" 13 | ], 14 | "env": { 15 | "ALFRESCO_URL": "http://localhost:8080", 16 | "ALFRESCO_USERNAME": "admin", 17 | "ALFRESCO_PASSWORD": "admin" 18 | } 19 | } 20 | } 21 | } ``` -------------------------------------------------------------------------------- /claude-desktop-config-uvx-windows.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "mcpServers": { 3 | "python-alfresco-mcp-server": { 4 | "command": "uvx", 5 | "args": [ 6 | "python-alfresco-mcp-server", 7 | "--transport", 8 | "stdio" 9 | ], 10 | "env": { 11 | "ALFRESCO_URL": "http://localhost:8080", 12 | "ALFRESCO_USERNAME": "admin", 13 | "ALFRESCO_PASSWORD": "admin", 14 | "PYTHONIOENCODING": "utf-8", 15 | "PYTHONLEGACYWINDOWSSTDIO": "1" 16 | } 17 | } 18 | } 19 | } ``` -------------------------------------------------------------------------------- /.vscode/mcp.json: -------------------------------------------------------------------------------- ```json 1 | "servers": { 2 | "my-mcp-server": { 3 | "type": "http", 4 | "command": "python", 5 | "args": ["${workspaceFolder}/run_server.py", "--transport", "http", "--port", "8003", "--host", "127.0.0.1"], 6 | "dev": { 7 | "watch": "${workspaceFolder}/alfresco_mcp_server/**/*.py", 8 | "debug": { "type": "python" } 9 | }, 10 | "env": { 11 | "ALFRESCO_URL": "http://localhost:8080", 12 | "ALFRESCO_USERNAME": "admin", 13 | "ALFRESCO_PASSWORD": "admin" 14 | } 15 | } 16 | } 17 | ``` -------------------------------------------------------------------------------- /mcp-inspector-stdio-uv-config.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "mcpServers": { 3 | "python-alfresco-mcp-server": { 4 | "command": "uv", 5 | "args": [ 6 | "run", 7 | "python-alfresco-mcp-server", 8 | "--transport", 9 | "stdio" 10 | ], 11 | "cwd": "C:\\newdev3\\python-alfresco-mcp-server", 12 | "env": { 13 | "ALFRESCO_URL": "http://localhost:8080", 14 | "ALFRESCO_USERNAME": "admin", 15 | "ALFRESCO_PASSWORD": "admin", 16 | "PYTHONIOENCODING": "utf-8", 17 | "PYTHONLEGACYWINDOWSSTDIO": "1" 18 | } 19 | } 20 | } 21 | } ``` -------------------------------------------------------------------------------- /claude-desktop-config-uv-windows.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "mcpServers": { 3 | "python-alfresco-mcp-server": { 4 | "command": "uv", 5 | "args": [ 6 | "run", 7 | "python-alfresco-mcp-server", 8 | "--transport", 9 | "stdio" 10 | ], 11 | "cwd": "C:\\path\\to\\python-alfresco-mcp-server", 12 | "env": { 13 | "ALFRESCO_URL": "http://localhost:8080", 14 | "ALFRESCO_USERNAME": "admin", 15 | "ALFRESCO_PASSWORD": "admin", 16 | "PYTHONIOENCODING": "utf-8", 17 | "PYTHONLEGACYWINDOWSSTDIO": "1" 18 | } 19 | } 20 | } 21 | } ``` -------------------------------------------------------------------------------- /alfresco_mcp_server/tools/core/__init__.py: -------------------------------------------------------------------------------- ```python 1 | # Core tools module 2 | 3 | from . import ( 4 | browse_repository, 5 | cancel_checkout, 6 | checkin_document, 7 | checkout_document, 8 | create_folder, 9 | delete_node, 10 | download_document, 11 | get_node_properties, 12 | update_node_properties, 13 | upload_document, 14 | ) 15 | 16 | __all__ = [ 17 | "browse_repository", 18 | "cancel_checkout", 19 | "checkin_document", 20 | "checkout_document", 21 | "create_folder", 22 | "delete_node", 23 | "download_document", 24 | "get_node_properties", 25 | "update_node_properties", 26 | "upload_document", 27 | ] ``` -------------------------------------------------------------------------------- /mcp-inspector-http-uv-config.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "mcpServers": { 3 | "python-alfresco-mcp-server": { 4 | "command": "uv", 5 | "args": [ 6 | "run", 7 | "python-alfresco-mcp-server", 8 | "--transport", 9 | "http", 10 | "--host", 11 | "127.0.0.1", 12 | "--port", 13 | "8003" 14 | ], 15 | "cwd": "C:\\newdev3\\python-alfresco-mcp-server", 16 | "env": { 17 | "ALFRESCO_URL": "http://localhost:8080", 18 | "ALFRESCO_USERNAME": "admin", 19 | "ALFRESCO_PASSWORD": "admin", 20 | "PYTHONIOENCODING": "utf-8", 21 | "PYTHONLEGACYWINDOWSSTDIO": "1" 22 | } 23 | } 24 | } 25 | } ``` -------------------------------------------------------------------------------- /alfresco_mcp_server/__init__.py: -------------------------------------------------------------------------------- ```python 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 | ] ``` -------------------------------------------------------------------------------- /alfresco_mcp_server/utils/__init__.py: -------------------------------------------------------------------------------- ```python 1 | # Utilities module for Alfresco MCP Server 2 | 3 | from .connection import ( 4 | get_alfresco_config, 5 | get_connection, 6 | get_search_utils, 7 | get_node_utils, 8 | ) 9 | 10 | from .file_type_analysis import ( 11 | detect_file_extension_from_content, 12 | analyze_content_type, 13 | ) 14 | 15 | from .json_utils import ( 16 | make_json_safe, 17 | safe_format_output, 18 | escape_unicode_for_json, 19 | ) 20 | 21 | __all__ = [ 22 | # Connection utilities 23 | "get_alfresco_config", 24 | "get_connection", 25 | "get_search_utils", 26 | "get_node_utils", 27 | # File type analysis 28 | "detect_file_extension_from_content", 29 | "analyze_content_type", 30 | # JSON utilities 31 | "make_json_safe", 32 | "safe_format_output", 33 | "escape_unicode_for_json", 34 | ] ``` -------------------------------------------------------------------------------- /sample-dot-env.txt: -------------------------------------------------------------------------------- ``` 1 | # Alfresco MCP Server Configuration 2 | # Copy this file to .env and customize for your environment 3 | # The .env file will be ignored by git for security 4 | 5 | # === REQUIRED: Alfresco Connection === 6 | ALFRESCO_URL=http://localhost:8080 7 | ALFRESCO_USERNAME=admin 8 | ALFRESCO_PASSWORD=admin 9 | 10 | # === OPTIONAL: Connection Settings === 11 | ALFRESCO_VERIFY_SSL=false 12 | ALFRESCO_TIMEOUT=30 13 | 14 | # === OPTIONAL: Server Settings === 15 | LOG_LEVEL=INFO 16 | MAX_FILE_SIZE=100000000 17 | 18 | # === OPTIONAL: HTTP Transport Settings === 19 | FASTAPI_HOST=localhost 20 | FASTAPI_PORT=8000 21 | 22 | # === NOTES === 23 | # - Environment variables take precedence over defaults 24 | # - python-alfresco-api may have its own configuration (check its docs) 25 | # - For production, use environment variables or secure secret management 26 | # - Boolean values: true/false (case insensitive) 27 | # - File size in bytes (100000000 = 100MB) ``` -------------------------------------------------------------------------------- /tests/mcp_specific/START_HTTP_SERVER.md: -------------------------------------------------------------------------------- ```markdown 1 | # Starting MCP Server for Testing 2 | 3 | ## For MCP Inspector Testing (HTTP) 4 | 5 | ### Option 1: Using PowerShell 6 | ```powershell 7 | cd C:\newdev3\python-alfresco-mcp-server 8 | .\venv_clean\Scripts\activate 9 | fastmcp run alfresco_mcp_server.fastmcp_server --host localhost --port 8003 10 | ``` 11 | 12 | ### Option 2: Using Command Prompt 13 | ```cmd 14 | cd C:\newdev3\python-alfresco-mcp-server 15 | venv_clean\Scripts\activate.bat 16 | fastmcp run alfresco_mcp_server.fastmcp_server --host localhost --port 8003 17 | ``` 18 | 19 | ## For Claude Desktop Testing (STDIO) 20 | Claude Desktop uses the stdio transport via the config file: 21 | `claude-desktop-config.json` 22 | 23 | ## Testing Server Status 24 | After starting the HTTP server, run: 25 | ```bash 26 | python tests/mcp_specific/test_server_status.py 27 | ``` 28 | 29 | ## MCP Inspector Setup 30 | 1. Start HTTP server on port 8003 (see above) 31 | 2. Open MCP Inspector in browser 32 | 3. Use URL: `http://localhost:8003` 33 | 4. **Note**: MCP Inspector will show warning about "risky auth" and include token in URL - this is normal for development testing 34 | 35 | ## Important Notes 36 | - Make sure you're using `venv_clean` which has all the correct dependencies 37 | - The FastMCP server will show import success messages when starting correctly 38 | - If you see pydantic import errors, make sure you're using the correct virtual environment ``` -------------------------------------------------------------------------------- /run_server.py: -------------------------------------------------------------------------------- ```python 1 | #!/usr/bin/env python3 2 | """ 3 | Simple wrapper to run the Alfresco MCP Server 4 | """ 5 | import sys 6 | import os 7 | 8 | # Add the current directory to Python path 9 | sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) 10 | 11 | # Import and run the server 12 | from alfresco_mcp_server.fastmcp_server import mcp 13 | 14 | if __name__ == "__main__": 15 | import argparse 16 | 17 | parser = argparse.ArgumentParser(description="Run Alfresco MCP Server") 18 | parser.add_argument("--port", type=int, default=8003, help="Port to run server on") 19 | parser.add_argument("--host", type=str, default="127.0.0.1", help="Host to bind to") 20 | parser.add_argument("--transport", type=str, default="http", choices=["stdio", "http", "sse"], help="Transport method to use") 21 | 22 | args = parser.parse_args() 23 | 24 | if args.transport == "stdio": 25 | print(">> Starting Alfresco MCP Server with stdio transport") 26 | mcp.run(transport="stdio") 27 | elif args.transport == "http": 28 | print(f">> Starting Alfresco MCP Server with HTTP transport on {args.host}:{args.port}") 29 | mcp.run(transport="http", host=args.host, port=args.port) 30 | else: 31 | print(f">> Starting Alfresco MCP Server with SSE transport on {args.host}:{args.port}") 32 | mcp.run(transport="sse", host=args.host, port=args.port) ``` -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- ``` 1 | [tool:pytest] 2 | # Pytest configuration for Alfresco MCP Server 3 | minversion = 6.0 4 | testpaths = tests 5 | python_files = test_*.py 6 | python_classes = Test* 7 | python_functions = test_* 8 | 9 | # Add options 10 | addopts = 11 | --strict-markers 12 | --strict-config 13 | --verbose 14 | --tb=short 15 | --cov=alfresco_mcp_server 16 | --cov-report=html:htmlcov 17 | --cov-report=term-missing 18 | --cov-report=xml 19 | --cov-branch 20 | --cov-fail-under=85 21 | 22 | # Test markers 23 | markers = 24 | unit: marks tests as unit tests (fast, mocked dependencies) 25 | integration: marks tests as integration tests (requires live Alfresco) 26 | slow: marks tests as slow running 27 | performance: marks tests as performance benchmarks 28 | 29 | # Async test configuration 30 | asyncio_mode = auto 31 | 32 | # Filter warnings 33 | filterwarnings = 34 | ignore::DeprecationWarning 35 | ignore::PendingDeprecationWarning 36 | ignore::pytest.PytestUnraisableExceptionWarning 37 | 38 | # Coverage configuration 39 | [tool:coverage:run] 40 | source = alfresco_mcp_server 41 | omit = 42 | tests/* 43 | venv/* 44 | */site-packages/* 45 | */test_* 46 | setup.py 47 | 48 | [tool:coverage:report] 49 | exclude_lines = 50 | pragma: no cover 51 | def __repr__ 52 | if self.debug: 53 | if settings.DEBUG 54 | raise AssertionError 55 | raise NotImplementedError 56 | if 0: 57 | if __name__ == .__main__.: 58 | class .*\bProtocol\): 59 | @(abc\.)?abstractmethod 60 | 61 | [tool:coverage:html] 62 | directory = htmlcov 63 | 64 | [tool:coverage:xml] 65 | output = coverage.xml ``` -------------------------------------------------------------------------------- /alfresco_mcp_server/prompts/search_and_analyze.py: -------------------------------------------------------------------------------- ```python 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 ``` -------------------------------------------------------------------------------- /scripts/test.bat: -------------------------------------------------------------------------------- ``` 1 | @echo off 2 | REM Windows batch script for running Alfresco MCP Server tests 3 | 4 | echo ================================ 5 | echo Alfresco MCP Server Test Suite 6 | echo ================================ 7 | 8 | REM Check if Python is available 9 | python --version >nul 2>&1 10 | if %errorlevel% neq 0 ( 11 | echo Error: Python is not installed or not in PATH 12 | exit /b 1 13 | ) 14 | 15 | REM Set environment variables for Alfresco 16 | set ALFRESCO_URL=http://localhost:8080 17 | set ALFRESCO_USERNAME=admin 18 | set ALFRESCO_PASSWORD=admin 19 | set ALFRESCO_VERIFY_SSL=false 20 | 21 | REM Parse command line arguments 22 | set MODE=unit 23 | set INSTALL_DEPS=false 24 | 25 | :parse_args 26 | if "%1"=="--help" goto :show_help 27 | if "%1"=="--unit" set MODE=unit 28 | if "%1"=="--integration" set MODE=integration 29 | if "%1"=="--performance" set MODE=performance 30 | if "%1"=="--coverage" set MODE=coverage 31 | if "%1"=="--all" set MODE=all 32 | if "%1"=="--lint" set MODE=lint 33 | if "%1"=="--install-deps" set INSTALL_DEPS=true 34 | shift 35 | if not "%1"=="" goto :parse_args 36 | 37 | REM Install dependencies if requested 38 | if "%INSTALL_DEPS%"=="true" ( 39 | echo Installing test dependencies... 40 | python -m pip install pytest pytest-asyncio pytest-cov pytest-mock coverage httpx 41 | if %errorlevel% neq 0 ( 42 | echo Error: Failed to install dependencies 43 | exit /b 1 44 | ) 45 | ) 46 | 47 | REM Run the test runner 48 | echo Running tests in %MODE% mode... 49 | python scripts/run_tests.py --mode %MODE% 50 | 51 | if %errorlevel% neq 0 ( 52 | echo Tests failed! 53 | exit /b 1 54 | ) 55 | 56 | echo. 57 | echo ================================ 58 | echo Tests completed successfully! 59 | echo ================================ 60 | echo Coverage report: htmlcov/index.html 61 | exit /b 0 62 | 63 | :show_help 64 | echo Usage: test.bat [OPTIONS] 65 | echo. 66 | echo Options: 67 | echo --unit Run unit tests (default) 68 | echo --integration Run integration tests (requires Alfresco) 69 | echo --performance Run performance tests 70 | echo --coverage Run coverage analysis 71 | echo --all Run all tests 72 | echo --lint Run code linting 73 | echo --install-deps Install test dependencies 74 | echo --help Show this help 75 | exit /b 0 ``` -------------------------------------------------------------------------------- /tests/mcp_specific/MCP_INSPECTOR_CONNECTION.md: -------------------------------------------------------------------------------- ```markdown 1 | # Connecting MCP Inspector to Your Running Server 2 | 3 | ## 🔍 Current Setup 4 | - **MCP Inspector**: Running on port 6274 ✅ 5 | - **Your MCP Server**: Running on port 8003 ✅ 6 | - **URL Pattern**: `http://localhost:6274/?MCP_PROXY_AUTH_TOKEN=...#tools` 7 | 8 | ## 🔗 How to Connect 9 | 10 | ### Step 1: MCP Inspector Server Connection 11 | In your MCP Inspector interface: 12 | 13 | 1. **Look for "Add Server" or "Connect Server"** button 14 | 2. **Enter your server URL**: `http://localhost:8003` 15 | 3. **Transport**: Select "HTTP" 16 | 4. **No additional auth needed** - it's local 17 | 18 | ### Step 2: Expected URL Structure 19 | Once connected, your URL will be: 20 | ``` 21 | http://localhost:6274/?MCP_PROXY_AUTH_TOKEN=<token>&server=http://localhost:8003#tools 22 | ``` 23 | 24 | ### Step 3: What You Should See 25 | - ✅ **Server**: "Alfresco Document Management Server" 26 | - ✅ **15 Tools Available**: 27 | - search_content 28 | - search_by_metadata 29 | - advanced_search 30 | - cmis_search 31 | - browse_repository 32 | - repository_info 33 | - upload_document 34 | - download_document 35 | - checkout_document 36 | - checkin_document 37 | - cancel_checkout 38 | - delete_node 39 | - get_node_properties 40 | - update_node_properties 41 | - create_folder 42 | 43 | ## 🚨 If Connection Fails 44 | 45 | ### Check Server Status 46 | ```bash 47 | python tests/mcp_specific/test_server_status.py 48 | ``` 49 | 50 | ### Check Both Ports 51 | ```bash 52 | netstat -an | findstr ":6274\|:8003" 53 | ``` 54 | 55 | ### Restart If Needed 56 | ```bash 57 | # Stop your MCP server 58 | taskkill /PID 57420 /F 59 | 60 | # Restart with explicit logging 61 | fastmcp run alfresco_mcp_server/fastmcp_server.py --transport http --host localhost --port 8003 --log-level DEBUG 62 | ``` 63 | 64 | ## 🎯 Quick Test 65 | 1. **Open your MCP Inspector**: `http://localhost:6274/?MCP_PROXY_AUTH_TOKEN=4b4a857ba6eecd2ac7029bf0c9daaf97e311b7f5cee35dec3a25ddf739ffd66b#tools` 66 | 2. **Add server**: `http://localhost:8003` 67 | 3. **Test a tool**: Try "browse_repository" or "search_content" 68 | 69 | ## 🔒 The "Risky Auth" Token 70 | The `MCP_PROXY_AUTH_TOKEN=4b4a857ba6eecd2ac7029bf0c9daaf97e311b7f5cee35dec3a25ddf739ffd66b` is: 71 | - ✅ **Normal**: Generated by MCP Inspector for security 72 | - ✅ **Local only**: Not transmitted anywhere 73 | - ✅ **Safe**: Standard MCP development practice ``` -------------------------------------------------------------------------------- /tests/mcp_specific/START_MCP_INSPECTOR.md: -------------------------------------------------------------------------------- ```markdown 1 | # How to Start MCP Inspector 2 | 3 | ## 🚀 Quick Start Methods 4 | 5 | ### Method 1: Using Config File (Recommended - Avoids Proxy Errors) 6 | ```bash 7 | # 1. Start your MCP server first 8 | python -m alfresco_mcp_server.fastmcp_server --transport http --port 8003 9 | 10 | # 2. Start MCP Inspector with pre-configured server 11 | npx @modelcontextprotocol/inspector --config mcp-inspector-http-config.json --server python-alfresco-mcp-server 12 | ``` 13 | 14 | **Expected Output:** 15 | ``` 16 | Starting MCP inspector... 17 | ⚙️ Proxy server listening on 127.0.0.1:6277 18 | 🔑 Session token: d7a62ab6e032eefe5d85e807c50e13b9fffcd12badbf8bbc3377659c0be4fa8d 19 | Use this token to authenticate requests or set DANGEROUSLY_OMIT_AUTH=true to disable auth 20 | 21 | 🔗 Open inspector with token pre-filled: 22 | http://localhost:6274/?MCP_PROXY_AUTH_TOKEN=d7a62ab6e032eefe5d85e807c50e13b9fffcd12badbf8bbc3377659c0be4fa8d 23 | ``` 24 | 25 | ### Method 2: Using npx (Basic) 26 | ```bash 27 | npx @modelcontextprotocol/inspector 28 | ``` 29 | 30 | ### Method 2: Using npm (if globally installed) 31 | ```bash 32 | npm run dev 33 | # or 34 | npm start 35 | ``` 36 | 37 | ### Method 3: Direct GitHub (if you have it locally) 38 | ```bash 39 | # If you have the repo cloned 40 | cd path/to/mcp-inspector 41 | npm run dev 42 | ``` 43 | 44 | ### Method 4: Using the pre-built package 45 | ```bash 46 | npx @modelcontextprotocol/inspector@latest 47 | ``` 48 | 49 | ## 📍 Expected Output 50 | When MCP Inspector starts, you should see: 51 | ``` 52 | > Local: http://localhost:6274 53 | > Network: http://192.168.x.x:6274 54 | ``` 55 | 56 | ## 🔗 After Starting 57 | 58 | ### 1. Open Browser 59 | Navigate to: `http://localhost:6274` 60 | 61 | ### 2. Connect to Your Server 62 | - **Click "Add Server"** or server connection field 63 | - **Enter**: `http://localhost:8003` 64 | - **Transport**: HTTP 65 | - **Connect** 66 | 67 | ### 3. Expected URL 68 | ``` 69 | http://localhost:6274/?MCP_PROXY_AUTH_TOKEN=<new-token>&server=http://localhost:8003#tools 70 | ``` 71 | 72 | ## 🛠️ Troubleshooting 73 | 74 | ### If Port 6274 is Busy 75 | ```bash 76 | # MCP Inspector will auto-find next available port 77 | # Check what port it actually uses in the startup message 78 | ``` 79 | 80 | ### If npx Fails 81 | ```bash 82 | # Update npm/npx 83 | npm install -g npm@latest 84 | 85 | # Try with explicit version 86 | npx @modelcontextprotocol/inspector@latest 87 | ``` 88 | 89 | ### Check if Already Running 90 | ```bash 91 | netstat -an | findstr :6274 92 | # or check other common ports 93 | netstat -an | findstr ":3000\|:5173\|:6274" 94 | ``` 95 | 96 | ## 🎯 Quick Test Command 97 | ```bash 98 | # This should start MCP Inspector on port 6274 99 | npx @modelcontextprotocol/inspector 100 | ``` 101 | 102 | Then open `http://localhost:6274` and connect to your server at `http://localhost:8003`! ``` -------------------------------------------------------------------------------- /tests/mcp_specific/TESTING_INSTRUCTIONS.md: -------------------------------------------------------------------------------- ```markdown 1 | # MCP Server Testing Instructions 2 | 3 | ## 🟢 Server Status: RUNNING 4 | - **Process**: Python PID 57420 ✅ 5 | - **Port**: 8003 LISTENING ✅ 6 | - **URL**: `http://localhost:8003/mcp/` ✅ 7 | 8 | ## 🌐 MCP Inspector Testing 9 | 10 | ### Why Browser Shows Error 11 | The error `"Not Acceptable: Client must accept text/event-stream"` is **NORMAL**: 12 | - MCP servers use Server-Sent Events (SSE) format 13 | - Regular browsers can't handle MCP protocol 14 | - You need the MCP Inspector tool 15 | 16 | ### Setup MCP Inspector 17 | 1. **Download**: [MCP Inspector](https://github.com/modelcontextprotocol/inspector) 18 | 2. **Install**: Follow their setup instructions 19 | 3. **Connect to**: `http://localhost:8003` 20 | 4. **No token required** - it's local development 21 | 22 | ### Expected in MCP Inspector 23 | - ✅ Server connection successful 24 | - ✅ 10 tools available (search_content, browse_repository, etc.) 25 | - ✅ "Risky auth" warning - NORMAL for development 26 | - ✅ Can test individual tools 27 | 28 | ## 💬 Claude Desktop Testing (Easier) 29 | 30 | ### Setup 31 | 1. **Config ready**: `claude-desktop-config.json` ✅ 32 | 2. **Restart**: Claude Desktop application 33 | 3. **Ready**: MCP tools appear in conversation 34 | 35 | ### Your Config File 36 | ```json 37 | { 38 | "mcpServers": { 39 | "alfresco": { 40 | "command": "C:\\newdev3\\python-alfresco-mcp-server\\venv_clean\\Scripts\\python.exe", 41 | "args": [ 42 | "C:\\newdev3\\python-alfresco-mcp-server\\alfresco_mcp_server\\fastmcp_server.py" 43 | ], 44 | "env": { 45 | "ALFRESCO_URL": "http://localhost:8080", 46 | "ALFRESCO_USERNAME": "admin", 47 | "ALFRESCO_PASSWORD": "admin" 48 | } 49 | } 50 | } 51 | } 52 | ``` 53 | 54 | ### Testing in Claude 55 | Ask Claude to: 56 | - Search for content: "Search for documents about 'project'" 57 | - Browse repository: "Show me the repository structure" 58 | - Get node info: "Get details about the root folder" 59 | 60 | ## 🔒 Security Notes 61 | 62 | ### LOCAL ONLY - No Central Registration 63 | - ✅ Server runs on `localhost:8003` only 64 | - ✅ Not accessible from internet 65 | - ✅ Not registered in any central directory 66 | - ✅ Private development server 67 | 68 | ### If You're Concerned About Name 69 | The server name "Alfresco Document Management Server" is just a display name: 70 | - ✅ Not registered anywhere 71 | - ✅ Only visible to connected clients 72 | - ✅ Can be changed in code if desired 73 | 74 | ## 🧪 Quick Test Commands 75 | 76 | ### Test Server Status 77 | ```bash 78 | python tests/mcp_specific/test_server_status.py 79 | ``` 80 | 81 | ### Test Server Tools 82 | ```bash 83 | python tests/mcp_specific/test_server_fixed.py 84 | ``` 85 | 86 | ### Stop Server 87 | ```bash 88 | # Find and kill process 57420 89 | taskkill /PID 57420 /F 90 | ``` 91 | 92 | ## 🎯 Recommended Testing Order 93 | 1. **Start with Claude Desktop** (easiest to test) 94 | 2. **Then try MCP Inspector** (if you want detailed tool testing) 95 | 3. **Both use the same server** - no conflicts ``` -------------------------------------------------------------------------------- /tests/mcp_specific/test_http_server.ps1: -------------------------------------------------------------------------------- ``` 1 | # Test script for Python Alfresco MCP Server HTTP transport 2 | Write-Host "🌐 Testing Python Alfresco MCP Server - HTTP Transport" -ForegroundColor Green 3 | Write-Host "=" * 60 4 | 5 | $baseUrl = "http://127.0.0.1:8001" 6 | 7 | # Wait for server to start 8 | Write-Host "⏳ Waiting for server to start..." -ForegroundColor Yellow 9 | Start-Sleep -Seconds 3 10 | 11 | try { 12 | # Test 1: Server health/info 13 | Write-Host "`n🏥 Testing Server Health..." -ForegroundColor Cyan 14 | try { 15 | $response = Invoke-WebRequest -Uri "$baseUrl/" -Method GET -TimeoutSec 10 16 | Write-Host "✅ Server is responding" -ForegroundColor Green 17 | Write-Host " Status: $($response.StatusCode)" -ForegroundColor Gray 18 | } 19 | catch { 20 | Write-Host "ℹ️ Root endpoint: $($_.Exception.Message)" -ForegroundColor Yellow 21 | } 22 | 23 | # Test 2: List tools (if available) 24 | Write-Host "`n🔧 Testing Tools Endpoint..." -ForegroundColor Cyan 25 | try { 26 | $response = Invoke-WebRequest -Uri "$baseUrl/tools" -Method GET -TimeoutSec 10 27 | Write-Host "✅ Tools endpoint accessible" -ForegroundColor Green 28 | $content = $response.Content | ConvertFrom-Json 29 | Write-Host " Tools found: $($content.tools.Count)" -ForegroundColor Gray 30 | } 31 | catch { 32 | Write-Host "ℹ️ Tools endpoint: $($_.Exception.Message)" -ForegroundColor Yellow 33 | } 34 | 35 | # Test 3: List resources (if available) 36 | Write-Host "`n📦 Testing Resources Endpoint..." -ForegroundColor Cyan 37 | try { 38 | $response = Invoke-WebRequest -Uri "$baseUrl/resources" -Method GET -TimeoutSec 10 39 | Write-Host "✅ Resources endpoint accessible" -ForegroundColor Green 40 | } 41 | catch { 42 | Write-Host "ℹ️ Resources endpoint: $($_.Exception.Message)" -ForegroundColor Yellow 43 | } 44 | 45 | # Test 4: Call a tool (if supported) 46 | Write-Host "`n🔍 Testing Tool Call..." -ForegroundColor Cyan 47 | try { 48 | $body = @{ 49 | query = "test" 50 | max_results = 5 51 | } | ConvertTo-Json 52 | 53 | $response = Invoke-WebRequest -Uri "$baseUrl/tools/search_content" -Method POST -Body $body -ContentType "application/json" -TimeoutSec 10 54 | Write-Host "✅ Tool call successful" -ForegroundColor Green 55 | Write-Host " Response length: $($response.Content.Length) chars" -ForegroundColor Gray 56 | } 57 | catch { 58 | Write-Host "ℹ️ Tool call: $($_.Exception.Message)" -ForegroundColor Yellow 59 | } 60 | 61 | Write-Host "`n🎉 HTTP testing completed!" -ForegroundColor Green 62 | 63 | } 64 | catch { 65 | Write-Host "❌ HTTP testing failed: $($_.Exception.Message)" -ForegroundColor Red 66 | } 67 | 68 | Write-Host "`n📖 Next steps:" -ForegroundColor Cyan 69 | Write-Host "1. Try MCP Inspector: npx @modelcontextprotocol/inspector" -ForegroundColor Gray 70 | Write-Host "2. Test STDIO transport: python test_with_mcp_client.py" -ForegroundColor Gray 71 | Write-Host "3. Run existing test suite: python -m pytest tests/" -ForegroundColor Gray ``` -------------------------------------------------------------------------------- /alfresco_mcp_server/tools/core/delete_node.py: -------------------------------------------------------------------------------- ```python 1 | """ 2 | Delete node tool for Alfresco MCP Server. 3 | Self-contained tool for deleting documents or folders from Alfresco repository. 4 | """ 5 | import logging 6 | from typing import Optional 7 | from fastmcp import Context 8 | 9 | from ...utils.connection import ensure_connection 10 | from ...utils.json_utils import safe_format_output 11 | 12 | logger = logging.getLogger(__name__) 13 | 14 | 15 | async def delete_node_impl( 16 | node_id: str, 17 | permanent: bool = False, 18 | ctx: Optional[Context] = None 19 | ) -> str: 20 | """Delete a document or folder from Alfresco. 21 | 22 | Args: 23 | node_id: Node ID to delete 24 | permanent: Whether to permanently delete (bypass trash) 25 | ctx: MCP context for progress reporting 26 | 27 | Returns: 28 | Deletion confirmation 29 | """ 30 | if ctx: 31 | delete_type = "permanently delete" if permanent else "move to trash" 32 | await ctx.info(f"Preparing to {delete_type}: {node_id}") 33 | await ctx.info("Validating deletion request...") 34 | await ctx.report_progress(0.1) 35 | 36 | if not node_id.strip(): 37 | return safe_format_output("❌ Error: node_id is required") 38 | 39 | try: 40 | await ensure_connection() 41 | from ...utils.connection import get_client_factory 42 | 43 | # Get client factory and create core client (working pattern from test) 44 | client_factory = await get_client_factory() 45 | core_client = client_factory.create_core_client() 46 | 47 | # Clean the node ID (remove any URL encoding or extra characters) 48 | clean_node_id = node_id.strip() 49 | if clean_node_id.startswith('alfresco://'): 50 | # Extract node ID from URI format 51 | clean_node_id = clean_node_id.split('/')[-1] 52 | 53 | logger.info(f"Attempting to delete node: {clean_node_id}") 54 | 55 | if ctx: 56 | await ctx.report_progress(0.7) 57 | 58 | # Get node information first to validate it exists (working pattern from test) 59 | node_response = core_client.nodes.get(clean_node_id) 60 | 61 | if not hasattr(node_response, 'entry'): 62 | return safe_format_output(f"❌ Failed to get node information for: {clean_node_id}") 63 | 64 | node_info = node_response.entry 65 | filename = getattr(node_info, 'name', f"document_{clean_node_id}") 66 | 67 | # Use the working high-level API pattern from test script 68 | core_client.nodes.delete(clean_node_id) 69 | 70 | status = "permanently deleted" if permanent else "moved to trash" 71 | logger.info(f"✅ Node {status}: {filename}") 72 | 73 | if ctx: 74 | await ctx.report_progress(1.0) 75 | return safe_format_output(f"""✅ **Deletion Complete** 76 | 77 | 📄 **Node**: {node_info.name} 78 | 🗑️ **Status**: {status.title()} 79 | {"⚠️ **WARNING**: This action cannot be undone" if permanent else "ℹ️ **INFO**: Can be restored from trash"} 80 | 81 | 🆔 **Node ID**: {clean_node_id}""") 82 | 83 | except Exception as e: 84 | error_msg = f"ERROR: Deletion failed: {str(e)}" 85 | if ctx: 86 | await ctx.error(error_msg) 87 | logger.error(f"Deletion failed: {e}") 88 | return error_msg ``` -------------------------------------------------------------------------------- /alfresco_mcp_server/config.py: -------------------------------------------------------------------------------- ```python 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() ``` -------------------------------------------------------------------------------- /alfresco_mcp_server/tools/core/create_folder.py: -------------------------------------------------------------------------------- ```python 1 | """ 2 | Create folder tool for Alfresco MCP Server. 3 | Self-contained tool for creating folders in Alfresco repository. 4 | """ 5 | import logging 6 | from typing import Optional 7 | from fastmcp import Context 8 | 9 | from ...utils.connection import ensure_connection 10 | from ...utils.json_utils import safe_format_output 11 | 12 | logger = logging.getLogger(__name__) 13 | 14 | 15 | async def create_folder_impl( 16 | folder_name: str, 17 | parent_id: str = "-shared-", 18 | description: str = "", 19 | ctx: Optional[Context] = None 20 | ) -> str: 21 | """Create a new folder in Alfresco. 22 | 23 | Args: 24 | folder_name: Name of the new folder 25 | parent_id: Parent folder ID (default: shared folder) 26 | description: Folder description 27 | ctx: MCP context for progress reporting 28 | 29 | Returns: 30 | Folder creation confirmation with details 31 | """ 32 | if ctx: 33 | await ctx.info(f">> Creating folder '{folder_name}' in {parent_id}") 34 | await ctx.info("Validating folder parameters...") 35 | await ctx.report_progress(0.0) 36 | 37 | if not folder_name.strip(): 38 | return safe_format_output("❌ Error: folder_name is required") 39 | 40 | try: 41 | # Ensure connection and get client factory (working pattern from test) 42 | await ensure_connection() 43 | from ...utils.connection import get_client_factory 44 | 45 | # Get client factory and create core client (working pattern from test) 46 | client_factory = await get_client_factory() 47 | core_client = client_factory.create_core_client() 48 | 49 | if ctx: 50 | await ctx.info("Creating folder in Alfresco...") 51 | await ctx.report_progress(0.5) 52 | 53 | logger.info(f"Creating folder '{folder_name}' in parent {parent_id}") 54 | 55 | # Prepare properties 56 | properties = {"cm:title": folder_name} 57 | if description: 58 | properties["cm:description"] = description 59 | 60 | logger.info(f"Using high-level API: core_client.nodes.create_folder()") 61 | 62 | # Use the working high-level API pattern from test script 63 | folder_response = core_client.nodes.create_folder( 64 | name=folder_name, 65 | parent_id=parent_id, 66 | properties=properties 67 | ) 68 | 69 | if folder_response and hasattr(folder_response, 'entry'): 70 | entry = folder_response.entry 71 | logger.info("✅ Folder created successfully") 72 | 73 | # Extract folder details from response 74 | folder_id = getattr(entry, 'id', 'Unknown') 75 | folder_name_response = getattr(entry, 'name', folder_name) 76 | created_at = getattr(entry, 'createdAt', 'Unknown') 77 | node_type = getattr(entry, 'nodeType', 'cm:folder') 78 | else: 79 | raise Exception(f"Failed to create folder - invalid response from core client") 80 | 81 | if ctx: 82 | await ctx.info("Processing folder creation response...") 83 | await ctx.report_progress(0.9) 84 | 85 | if ctx: 86 | await ctx.info("Folder created!") 87 | await ctx.report_progress(1.0) 88 | await ctx.info(f"SUCCESS: Folder '{folder_name_response}' created successfully") 89 | 90 | # Clean JSON-friendly formatting (no markdown syntax) 91 | return safe_format_output(f"""✅ Folder Created Successfully! 92 | 93 | 📁 Name: {folder_name_response} 94 | 🆔 Folder ID: {folder_id} 95 | 📍 Parent: {parent_id} 96 | 📅 Created: {created_at} 97 | 🏷️ Type: {node_type} 98 | 📝 Description: {description or 'None'}""") 99 | 100 | except Exception as e: 101 | error_msg = f"❌ Folder creation failed: {str(e)}" 102 | if ctx: 103 | await ctx.error(error_msg) 104 | logger.error(f"Folder creation failed: {e}") 105 | return safe_format_output(error_msg) ``` -------------------------------------------------------------------------------- /tests/mcp_specific/test_with_mcp_inspector.md: -------------------------------------------------------------------------------- ```markdown 1 | # Testing with MCP Inspector 2 | 3 | ## Step 1: Start MCP Inspector 4 | 5 | ```bash 6 | # Launch MCP Inspector 7 | npx @modelcontextprotocol/inspector 8 | ``` 9 | 10 | This will open a web interface (usually at http://localhost:3000) where you can interactively test your MCP server. 11 | 12 | ## Step 2: Configure Server Connection 13 | 14 | In the MCP Inspector interface: 15 | 16 | 1. **Server Type**: Select "stdio" 17 | 2. **Command**: Enter `python` 18 | 3. **Arguments**: Add these as separate entries: 19 | - `-m` 20 | - `alfresco_mcp_server.fastmcp_server` 21 | 4. **Environment Variables** (if needed): 22 | - `ALFRESCO_URL`: `http://localhost:8080` 23 | - `ALFRESCO_USERNAME`: `admin` 24 | - `ALFRESCO_PASSWORD`: `admin` 25 | - `ALFRESCO_VERIFY_SSL`: `false` 26 | 27 | ## Step 3: Connect and Explore 28 | 29 | ### Available Tools (15 total): 30 | 1. **search_content** - Search documents and folders 31 | 2. **search_by_metadata** - Search by metadata properties 32 | 3. **advanced_search** - Advanced search with filters 33 | 4. **cmis_search** - CMIS SQL-based search 34 | 5. **upload_document** - Upload new documents 35 | 6. **download_document** - Download document content 36 | 7. **browse_repository** - Browse repository structure 37 | 8. **repository_info** - Get repository information and status 38 | 9. **checkout_document** - Check out for editing 39 | 10. **checkin_document** - Check in after editing 40 | 11. **cancel_checkout** - Cancel document checkout 41 | 12. **delete_node** - Delete documents/folders 42 | 13. **get_node_properties** - Get node metadata 43 | 14. **update_node_properties** - Update node metadata 44 | 15. **create_folder** - Create new folders 45 | 46 | ### Available Resources (5 total): 47 | 1. **alfresco://repository/info** - Repository information 48 | 2. **alfresco://repository/health** - Health status 49 | 3. **alfresco://repository/stats** - Usage statistics 50 | 4. **alfresco://repository/config** - Configuration details 51 | 5. **alfresco://repository/{section}** - Dynamic repository info 52 | 53 | ### Available Prompts (1 total): 54 | 1. **search_and_analyze** - AI-friendly search template 55 | 56 | ## Step 4: Test Examples 57 | 58 | ### Quick Tests (No Alfresco Required): 59 | - List tools: Should show all 15 tools 60 | - List resources: Should show all 5 resources 61 | - List prompts: Should show search_and_analyze prompt 62 | 63 | ### With Live Alfresco Server: 64 | 1. **Test Search**: 65 | - Tool: `search_content` 66 | - Parameters: `{"query": "test", "max_results": 5}` 67 | 68 | 2. **Test Repository Info**: 69 | - Resource: `alfresco://repository/info` 70 | 71 | 3. **Test Create Folder**: 72 | - Tool: `create_folder` 73 | - Parameters: `{"folder_name": "MCP Test Folder", "description": "Created via MCP Inspector"}` 74 | 75 | ## Step 5: Advanced Testing 76 | 77 | ### Error Handling: 78 | - Try invalid parameters 79 | - Test without Alfresco connection 80 | - Test with wrong credentials 81 | 82 | ### Performance: 83 | - Large search queries 84 | - Multiple concurrent operations 85 | - File upload/download operations 86 | 87 | ## Troubleshooting 88 | 89 | ### Common Issues: 90 | 1. **Inspector won't start**: Check Node.js version, try `npm install -g @modelcontextprotocol/inspector` 91 | 2. **Server connection fails**: Verify Python path and module installation 92 | 3. **Alfresco errors**: Check server status, credentials, and network connectivity 93 | 4. **Tool execution fails**: Verify parameters match schema requirements 94 | 95 | ### Environment Setup: 96 | ```bash 97 | # Windows PowerShell 98 | $env:ALFRESCO_URL="http://localhost:8080" 99 | $env:ALFRESCO_USERNAME="admin" 100 | $env:ALFRESCO_PASSWORD="admin" 101 | $env:ALFRESCO_VERIFY_SSL="false" 102 | 103 | # Or use .env file (recommended) 104 | # Copy sample-dot-env.txt to .env and modify 105 | ``` 106 | 107 | ## Next Steps 108 | 109 | 1. **Start simple**: Test tool/resource listing first 110 | 2. **Add credentials**: Set up environment variables for Alfresco 111 | 3. **Test incrementally**: One tool at a time 112 | 4. **Explore features**: Try different parameters and combinations 113 | 5. **Production testing**: Test with your actual Alfresco deployment ``` -------------------------------------------------------------------------------- /alfresco_mcp_server/utils/connection.py: -------------------------------------------------------------------------------- ```python 1 | """ 2 | Connection utilities for Alfresco MCP Server. 3 | Handles client creation and connection management. 4 | """ 5 | import logging 6 | import os 7 | from typing import Optional 8 | 9 | 10 | logger = logging.getLogger(__name__) 11 | 12 | # Global connection cache 13 | _master_client = None 14 | _client_factory = None 15 | 16 | def get_alfresco_config() -> dict: 17 | """Get Alfresco configuration from environment variables.""" 18 | return { 19 | 'alfresco_url': os.getenv('ALFRESCO_URL', 'http://localhost:8080'), 20 | 'username': os.getenv('ALFRESCO_USERNAME', 'admin'), 21 | 'password': os.getenv('ALFRESCO_PASSWORD', 'admin'), 22 | 'verify_ssl': os.getenv('ALFRESCO_VERIFY_SSL', 'false').lower() == 'true', 23 | 'timeout': int(os.getenv('ALFRESCO_TIMEOUT', '30')) 24 | } 25 | 26 | 27 | async def ensure_connection(): 28 | """Ensure we have a working connection to Alfresco using python-alfresco-api.""" 29 | global _master_client, _client_factory 30 | 31 | if _master_client is None: 32 | try: 33 | # Import here to avoid circular imports 34 | from python_alfresco_api import ClientFactory 35 | 36 | config = get_alfresco_config() 37 | 38 | logger.info(">> Creating Alfresco clients...") 39 | 40 | # Use ClientFactory to create authenticated client (original Sunday pattern) 41 | factory = ClientFactory( 42 | base_url=config['alfresco_url'], 43 | username=config['username'], 44 | password=config['password'], 45 | verify_ssl=config['verify_ssl'], 46 | timeout=config['timeout'] 47 | ) 48 | 49 | # Store the factory globally for other functions to use 50 | _client_factory = factory 51 | 52 | _master_client = factory.create_master_client() 53 | logger.info("Master client created successfully") 54 | 55 | # Test connection - use method that initializes and gets 56 | try: 57 | # Use ensure_httpx_client to initialize, then test simple call 58 | _master_client.core.ensure_httpx_client() 59 | logger.info("Connection test successful!") 60 | except Exception as conn_error: 61 | logger.warning(f"Connection test failed: {conn_error}") 62 | 63 | except Exception as e: 64 | logger.error(f"ERROR: Failed to create clients: {str(e)}") 65 | raise e 66 | 67 | return _master_client 68 | 69 | 70 | def get_connection(): 71 | """Get the cached connection without async (for sync operations).""" 72 | return _master_client 73 | 74 | 75 | async def get_search_client(): 76 | """Get the search client for search operations (using master_client for auth compatibility).""" 77 | master_client = await ensure_connection() 78 | # Return master_client which has simple_search access and working authentication 79 | return master_client 80 | 81 | 82 | async def get_core_client(): 83 | """Get the core client for core operations (using master_client for auth compatibility).""" 84 | master_client = await ensure_connection() 85 | # Return the actual core client that has nodes, folders, etc. 86 | return master_client.core 87 | 88 | 89 | async def get_client_factory(): 90 | """Get the client factory for advanced operations.""" 91 | await ensure_connection() 92 | if not _client_factory: 93 | raise RuntimeError("Connection not initialized. Call ensure_connection() first.") 94 | return _client_factory 95 | 96 | 97 | def get_search_utils(): 98 | """Get the search_utils module from python-alfresco-api.""" 99 | try: 100 | from python_alfresco_api.utils import search_utils 101 | return search_utils 102 | except ImportError as e: 103 | logger.error(f"Failed to import search_utils: {e}") 104 | raise 105 | 106 | 107 | def get_node_utils(): 108 | """Get the node_utils module from python-alfresco-api.""" 109 | try: 110 | from python_alfresco_api.utils import node_utils 111 | return node_utils 112 | except ImportError as e: 113 | logger.error(f"Failed to import node_utils: {e}") 114 | raise ``` -------------------------------------------------------------------------------- /alfresco_mcp_server/utils/json_utils.py: -------------------------------------------------------------------------------- ```python 1 | """ 2 | JSON utilities for Alfresco MCP Server. 3 | Handles proper Unicode emoji encoding for MCP protocol transport. 4 | """ 5 | import json 6 | import logging 7 | 8 | 9 | logger = logging.getLogger(__name__) 10 | 11 | 12 | def make_json_safe(text: str) -> str: 13 | """ 14 | Make text JSON-safe for MCP protocol transport. 15 | Properly encodes Unicode emojis to prevent character map errors. 16 | 17 | Args: 18 | text: Input text that may contain Unicode emojis 19 | 20 | Returns: 21 | JSON-safe text with properly encoded Unicode characters 22 | """ 23 | if not text: 24 | return text 25 | 26 | try: 27 | # Ensure proper Unicode normalization 28 | import unicodedata 29 | normalized = unicodedata.normalize('NFC', text) 30 | 31 | # Test if it can be safely JSON serialized 32 | json.dumps(normalized) 33 | return normalized 34 | 35 | except (UnicodeError, TypeError) as e: 36 | logger.warning(f"Unicode encoding issue, falling back to ASCII: {e}") 37 | # Fall back to ASCII-safe version with emoji descriptions 38 | return text.encode('ascii', errors='ignore').decode('ascii') 39 | 40 | 41 | def safe_format_output(text: str) -> str: 42 | """ 43 | Format output text to be safe for MCP JSON transport. 44 | Replaces emojis with text equivalents to prevent character map errors. 45 | 46 | Args: 47 | text: Text to format 48 | 49 | Returns: 50 | Safely formatted text with emojis replaced 51 | """ 52 | if not text: 53 | return text 54 | 55 | try: 56 | # Define emoji replacements for common ones used in the tools 57 | emoji_replacements = { 58 | '🔗': '[LINK]', 59 | '🔓': '[UNLOCKED]', 60 | '📄': '[DOCUMENT]', 61 | '🆔': '[ID]', 62 | '📏': '[SIZE]', 63 | '💾': '[SAVED]', 64 | '🔒': '[LOCKED]', 65 | '🕒': '[TIME]', 66 | '📥': '[DOWNLOAD]', 67 | 'ℹ️': '[INFO]', 68 | '⚠️': '[WARNING]', 69 | '👤': '[USER]', 70 | '✅': '[SUCCESS]', 71 | '❌': '[ERROR]', 72 | '🏷️': '[TAG]', 73 | '🧩': '[MODULE]', 74 | '📁': '[FOLDER]', 75 | '📍': '[LOCATION]', 76 | '📅': '[DATE]', 77 | '📝': '[NOTE]', 78 | '🔢': '[VERSION]', 79 | '📊': '[SIZE]', 80 | '🗑️': '[DELETE]', 81 | '🔍': '[SEARCH]', 82 | '📤': '[UPLOAD]', 83 | '🧹': '[CLEANUP]', 84 | '🏢': '[REPOSITORY]', 85 | '🔧': '[TOOL]', 86 | '📦': '[PACKAGE]' 87 | } 88 | 89 | # Replace emojis with text equivalents 90 | safe_text = text 91 | for emoji, replacement in emoji_replacements.items(): 92 | safe_text = safe_text.replace(emoji, replacement) 93 | 94 | # Test if the result is JSON-safe 95 | test_json = json.dumps(safe_text, ensure_ascii=True) 96 | json.loads(test_json) 97 | 98 | return safe_text 99 | 100 | except Exception as e: 101 | logger.warning(f"JSON formatting issue: {e}") 102 | try: 103 | # Ultimate fallback: remove all non-ASCII characters 104 | ascii_text = text.encode('ascii', errors='ignore').decode('ascii') 105 | return ascii_text 106 | except Exception as fallback_error: 107 | logger.error(f"ASCII fallback failed: {fallback_error}") 108 | return "Error: Text encoding failed" 109 | 110 | 111 | def escape_unicode_for_json(text: str) -> str: 112 | """ 113 | Alternative approach: explicitly escape Unicode characters for JSON. 114 | Use this if the regular approach doesn't work. 115 | 116 | Args: 117 | text: Input text with Unicode characters 118 | 119 | Returns: 120 | Text with Unicode characters escaped for JSON 121 | """ 122 | if not text: 123 | return text 124 | 125 | try: 126 | # Use json.dumps to properly escape Unicode, then remove the quotes 127 | escaped = json.dumps(text, ensure_ascii=False) 128 | # Remove the surrounding quotes added by json.dumps 129 | if escaped.startswith('"') and escaped.endswith('"'): 130 | escaped = escaped[1:-1] 131 | return escaped 132 | 133 | except Exception as e: 134 | logger.warning(f"Unicode escaping failed: {e}") 135 | return text ``` -------------------------------------------------------------------------------- /docs/install_with_pip_pipx.md: -------------------------------------------------------------------------------- ```markdown 1 | # Installation with pip and pipx 2 | 3 | This guide covers traditional Python package installation methods (pip and pipx) for Python Alfresco MCP Server. For modern UV/UVX installation (recommended), see the [main README](../README.md). 4 | 5 | ## 🛠️ Installation Options 6 | 7 | ### Option 1: pipx (Recommended for Traditional Methods) 8 | 9 | pipx automatically creates isolated environments for each tool while making commands globally available - eliminates dependency conflicts while providing system-wide access. 10 | 11 | ```bash 12 | # First install pipx if you don't have it (one-time setup) 13 | pip install pipx 14 | 15 | # Install python-alfresco-mcp-server in isolated environment 16 | pipx install python-alfresco-mcp-server 17 | 18 | # Test that installation worked 19 | python-alfresco-mcp-server --help 20 | ``` 21 | 22 | **Why pipx?** pipx automatically creates isolated environments for each tool while making commands globally available - eliminates dependency conflicts while providing system-wide access. 23 | 24 | ### Option 2: pip (Traditional Package Manager) 25 | 26 | ```bash 27 | # Recommended: Create virtual environment first 28 | python -m venv venv 29 | source venv/bin/activate # Linux/macOS 30 | # venv\Scripts\activate # Windows 31 | 32 | # Install python-alfresco-mcp-server 33 | pip install python-alfresco-mcp-server 34 | 35 | # Test that installation worked 36 | python-alfresco-mcp-server --help 37 | ``` 38 | 39 | ## 🚀 Usage 40 | 41 | ### MCP Server Startup 42 | 43 | **With pipx (Global installation - no venv needed):** 44 | 45 | ```bash 46 | # Run MCP server with STDIO transport (default) 47 | python-alfresco-mcp-server 48 | 49 | # HTTP transport for web services (matches MCP Inspector) 50 | python-alfresco-mcp-server --transport http --host 127.0.0.1 --port 8003 51 | 52 | # SSE transport for real-time streaming 53 | python-alfresco-mcp-server --transport sse --host 127.0.0.1 --port 8001 54 | ``` 55 | 56 | **With pip (Activate venv first if installed in one):** 57 | 58 | ```bash 59 | # Activate virtual environment first (if used during installation) 60 | source venv/bin/activate # Linux/macOS 61 | # venv\Scripts\activate # Windows 62 | 63 | # Run MCP server with STDIO transport (default) 64 | python-alfresco-mcp-server 65 | 66 | # HTTP transport for web services (matches MCP Inspector) 67 | python-alfresco-mcp-server --transport http --host 127.0.0.1 --port 8003 68 | 69 | # SSE transport for real-time streaming 70 | python-alfresco-mcp-server --transport sse --host 127.0.0.1 --port 8001 71 | ``` 72 | 73 | ## 🔧 Claude Desktop Configuration 74 | 75 | The Claude Desktop configuration differs based on how you installed the MCP server: 76 | 77 | ### pipx Installation (Global Tool) 78 | 79 | ```json 80 | { 81 | "command": "python-alfresco-mcp-server", 82 | "args": ["--transport", "stdio"] 83 | } 84 | ``` 85 | 86 | - Uses the **global command name** directly (no path needed) 87 | - pipx makes tools globally available in your PATH 88 | - Simplest configuration 89 | 90 | **Sample Config Files:** 91 | - Windows: [`claude-desktop-config-pipx-windows.json`](../claude-desktop-config-pipx-windows.json) 92 | - macOS: [`claude-desktop-config-pipx-macos.json`](../claude-desktop-config-pipx-macos.json) 93 | 94 | ### pip Installation (Manual venv) 95 | 96 | ```json 97 | { 98 | "command": "C:\\path\\to\\venv\\Scripts\\python-alfresco-mcp-server.exe", 99 | "args": ["--transport", "stdio"] 100 | } 101 | ``` 102 | 103 | - Uses **direct path to executable** in your virtual environment 104 | - Path points to `Scripts/` directory in your venv (Windows) or `bin/` (Linux/macOS) 105 | - Replace `C:\\path\\to\\venv` with your actual venv location 106 | 107 | ## 🔍 MCP Inspector Configuration 108 | 109 | For development and testing with MCP Inspector: 110 | 111 | ### pipx Installation 112 | 113 | Use the sample config files: 114 | - **stdio transport**: [`mcp-inspector-stdio-pipx-config.json`](../mcp-inspector-stdio-pipx-config.json) 115 | - **http transport**: [`mcp-inspector-http-pipx-config.json`](../mcp-inspector-http-pipx-config.json) 116 | 117 | ```bash 118 | # Start with stdio transport 119 | npx @modelcontextprotocol/inspector --config mcp-inspector-stdio-pipx-config.json --server python-alfresco-mcp-server 120 | 121 | # Start with http transport 122 | npx @modelcontextprotocol/inspector --config mcp-inspector-http-pipx-config.json --server python-alfresco-mcp-server 123 | ``` 124 | 125 | ### pip Installation 126 | 127 | Copy one of the pipx sample files above and modify the `"command"` field to point to your venv executable: 128 | - Change `"python-alfresco-mcp-server"` to `"C:\\path\\to\\venv\\Scripts\\python-alfresco-mcp-server.exe"` (Windows) 129 | - Or `"/path/to/venv/bin/python-alfresco-mcp-server"` (Linux/macOS) ``` -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- ```toml 1 | [build-system] 2 | requires = ["setuptools>=61.0", "wheel"] 3 | build-backend = "setuptools.build_meta" 4 | 5 | [project] 6 | name = "python-alfresco-mcp-server" 7 | version = "1.1.0" 8 | description = "FastMCP 2.0 server for Alfresco Content Services integration" 9 | authors = [{name = "Steve Reiner", email = "[email protected]"}] 10 | license = {text = "Apache-2.0"} 11 | readme = "README.md" 12 | requires-python = ">=3.10" 13 | keywords = ["alfresco", "mcp", "content-management", "fastmcp", "ai"] 14 | classifiers = [ 15 | "Development Status :: 4 - Beta", 16 | "Intended Audience :: Developers", 17 | "License :: OSI Approved :: Apache Software License", 18 | "Programming Language :: Python :: 3", 19 | "Programming Language :: Python :: 3.10", 20 | "Programming Language :: Python :: 3.11", 21 | "Programming Language :: Python :: 3.12", 22 | "Programming Language :: Python :: 3.13", 23 | "Topic :: Software Development :: Libraries :: Python Modules", 24 | "Topic :: Internet :: WWW/HTTP :: Dynamic Content :: Content Management System", 25 | "Topic :: Internet :: WWW/HTTP :: Indexing/Search", 26 | "Topic :: Database :: Database Engines/Servers", 27 | "Topic :: Scientific/Engineering :: Artificial Intelligence", 28 | ] 29 | 30 | dependencies = [ 31 | # FastMCP 2.0 - Modern MCP framework with enhanced features 32 | "fastmcp>=2.9.0", 33 | 34 | # Alfresco integration 35 | "python-alfresco-api>=1.1.1", 36 | 37 | # Configuration and utilities 38 | "pydantic>=2.0.0", 39 | "pydantic-settings>=2.0.0", 40 | "PyYAML>=6.0", 41 | 42 | # HTTP client 43 | "httpx>=0.24.0", 44 | 45 | # File handling 46 | "python-multipart>=0.0.6", 47 | ] 48 | 49 | [project.urls] 50 | Homepage = "https://github.com/stevereiner/python-alfresco-mcp-server" 51 | Repository = "https://github.com/stevereiner/python-alfresco-mcp-server" 52 | Issues = "https://github.com/stevereiner/python-alfresco-mcp-server/issues" 53 | Documentation = "https://github.com/stevereiner/python-alfresco-mcp-server#readme" 54 | 55 | [project.optional-dependencies] 56 | dev = [ 57 | "black>=23.0.0", 58 | "ruff>=0.1.0", 59 | "mypy>=1.0.0", 60 | ] 61 | test = [ 62 | "pytest>=7.0.0", 63 | "pytest-asyncio>=0.21.0", 64 | "pytest-cov>=4.0.0", 65 | "pytest-xdist>=3.0.0", 66 | "pytest-mock>=3.10.0", 67 | "coverage[toml]>=7.0.0", 68 | "httpx>=0.24.0", 69 | ] 70 | all = [ 71 | "python-alfresco-mcp-server[dev]", 72 | "python-alfresco-mcp-server[test]", 73 | ] 74 | 75 | [project.scripts] 76 | python-alfresco-mcp-server = "alfresco_mcp_server.fastmcp_server:main" 77 | 78 | [tool.setuptools] 79 | include-package-data = true 80 | 81 | [tool.setuptools.packages.find] 82 | where = ["."] 83 | include = ["alfresco_mcp_server*"] 84 | exclude = ["tests*", "tests-debug*", "venv*", "*.egg-info*"] 85 | 86 | [tool.setuptools.package-data] 87 | alfresco_mcp_server = [ 88 | "*.py", 89 | "*.yaml", 90 | "*.yml", 91 | "*.json", 92 | "*.md", 93 | "tools/**/*.py", 94 | "resources/**/*.py", 95 | "prompts/**/*.py", 96 | "utils/**/*.py", 97 | ] 98 | 99 | [tool.pytest.ini_options] 100 | minversion = "7.0" 101 | addopts = [ 102 | "-ra", 103 | "-v", 104 | "--tb=short", 105 | "--strict-markers", 106 | "--disable-warnings", 107 | ] 108 | testpaths = ["tests"] 109 | filterwarnings = [ 110 | "ignore::DeprecationWarning", 111 | "ignore::PendingDeprecationWarning", 112 | ] 113 | markers = [ 114 | "asyncio: marks tests as async", 115 | "integration: marks tests as integration tests requiring live Alfresco server", 116 | "unit: marks tests as unit tests", 117 | "fastmcp: marks tests as FastMCP 2.0 specific tests", 118 | ] 119 | asyncio_mode = "auto" 120 | 121 | [tool.coverage.run] 122 | source = ["alfresco_mcp_server"] 123 | omit = [ 124 | "*/tests/*", 125 | "*/venv/*", 126 | "*/__pycache__/*", 127 | ] 128 | 129 | [tool.coverage.report] 130 | exclude_lines = [ 131 | "pragma: no cover", 132 | "def __repr__", 133 | "if self.debug:", 134 | "if settings.DEBUG", 135 | "raise AssertionError", 136 | "raise NotImplementedError", 137 | "if 0:", 138 | "if __name__ == .__main__.:", 139 | "class .*\\bProtocol\\):", 140 | "@(abc\\.)?abstractmethod", 141 | ] 142 | 143 | [tool.black] 144 | line-length = 88 145 | target-version = ['py310'] 146 | include = '\.pyi?$' 147 | extend-exclude = ''' 148 | /( 149 | # directories 150 | \.eggs 151 | | \.git 152 | | \.hg 153 | | \.mypy_cache 154 | | \.tox 155 | | \.venv 156 | | venv 157 | | build 158 | | dist 159 | )/ 160 | ''' 161 | 162 | [tool.ruff] 163 | target-version = "py310" 164 | line-length = 88 165 | select = [ 166 | "E", # pycodestyle errors 167 | "W", # pycodestyle warnings 168 | "F", # pyflakes 169 | "I", # isort 170 | "B", # flake8-bugbear 171 | "C4", # flake8-comprehensions 172 | "UP", # pyupgrade 173 | ] 174 | ignore = [ 175 | "E501", # line too long, handled by black 176 | "B008", # do not perform function calls in argument defaults 177 | "C901", # too complex 178 | ] 179 | 180 | [tool.ruff.per-file-ignores] 181 | "__init__.py" = ["F401"] 182 | "tests/**/*" = ["B018", "B019"] 183 | 184 | [tool.mypy] 185 | python_version = "3.10" 186 | check_untyped_defs = true 187 | disallow_any_generics = true 188 | disallow_incomplete_defs = true 189 | disallow_untyped_defs = true 190 | no_implicit_optional = true 191 | warn_redundant_casts = true 192 | warn_unused_ignores = true 193 | warn_return_any = true 194 | strict_equality = true 195 | 196 | [dependency-groups] 197 | dev = [ 198 | "build>=1.2.2.post1", 199 | "twine>=6.1.0", 200 | ] 201 | ``` -------------------------------------------------------------------------------- /examples/quick_start.py: -------------------------------------------------------------------------------- ```python 1 | #!/usr/bin/env python3 2 | """ 3 | Quick Start Example for Alfresco MCP Server 4 | 5 | This example demonstrates: 6 | - Basic server connection 7 | - Making your first tool call 8 | - Handling responses 9 | - Simple error handling 10 | 11 | Prerequisites: 12 | - Alfresco MCP Server running 13 | - Environment variables set (ALFRESCO_URL, ALFRESCO_USERNAME, ALFRESCO_PASSWORD) 14 | """ 15 | 16 | import asyncio 17 | import os 18 | from fastmcp import Client 19 | from alfresco_mcp_server.fastmcp_server import mcp 20 | 21 | 22 | async def quick_start_example(): 23 | """Quick start example showing basic MCP server usage.""" 24 | 25 | print("🚀 Alfresco MCP Server - Quick Start Example") 26 | print("=" * 50) 27 | 28 | # Check environment setup 29 | print("\n📋 Checking Environment Setup...") 30 | required_vars = ["ALFRESCO_URL", "ALFRESCO_USERNAME", "ALFRESCO_PASSWORD"] 31 | for var in required_vars: 32 | value = os.getenv(var) 33 | if value: 34 | print(f"✅ {var}: {value}") 35 | else: 36 | print(f"❌ {var}: Not set (using defaults)") 37 | 38 | try: 39 | # Connect to the MCP server 40 | print("\n🔌 Connecting to MCP Server...") 41 | async with Client(mcp) as client: 42 | print("✅ Connected successfully!") 43 | 44 | # List available tools 45 | print("\n🛠️ Available Tools:") 46 | tools = await client.list_tools() 47 | for i, tool in enumerate(tools, 1): 48 | print(f" {i:2d}. {tool.name} - {tool.description}") 49 | 50 | # List available resources 51 | print("\n📚 Available Resources:") 52 | resources = await client.list_resources() 53 | for i, resource in enumerate(resources, 1): 54 | print(f" {i:2d}. {resource.uri}") 55 | 56 | # List available prompts 57 | print("\n💭 Available Prompts:") 58 | prompts = await client.list_prompts() 59 | for i, prompt in enumerate(prompts, 1): 60 | print(f" {i:2d}. {prompt.name} - {prompt.description}") 61 | 62 | # Example 1: Simple search 63 | print("\n🔍 Example 1: Simple Document Search") 64 | print("-" * 40) 65 | search_result = await client.call_tool("search_content", { 66 | "query": "*", # Search for all documents 67 | "max_results": 5 68 | }) 69 | 70 | if search_result: 71 | print("Search Result:") 72 | print(search_result[0].text) 73 | 74 | # Example 2: Get repository info 75 | print("\n📊 Example 2: Repository Information") 76 | print("-" * 40) 77 | repo_info = await client.read_resource("alfresco://repository/info") 78 | if repo_info: 79 | print("Repository Info:") 80 | print(repo_info[0].text) 81 | 82 | # Example 3: Create a test folder 83 | print("\n📁 Example 3: Create Test Folder") 84 | print("-" * 40) 85 | folder_result = await client.call_tool("create_folder", { 86 | "folder_name": f"MCP_Test_Folder_{asyncio.current_task().get_name()}", 87 | "parent_id": "-root-", 88 | "description": "Test folder created by MCP Quick Start example" 89 | }) 90 | 91 | if folder_result: 92 | print("Folder Creation Result:") 93 | print(folder_result[0].text) 94 | 95 | # Example 4: Get analysis prompt 96 | print("\n💡 Example 4: Analysis Prompt") 97 | print("-" * 40) 98 | prompt_result = await client.get_prompt("search_and_analyze", { 99 | "query": "financial reports", 100 | "analysis_type": "summary" 101 | }) 102 | 103 | if prompt_result.messages: 104 | print("Generated Prompt:") 105 | print(prompt_result.messages[0].content.text[:300] + "...") 106 | 107 | print("\n✅ Quick Start Complete!") 108 | print("Next steps:") 109 | print("- Explore other examples in this directory") 110 | print("- Check the documentation in ../docs/") 111 | print("- Try the document lifecycle example") 112 | 113 | except Exception as e: 114 | print(f"\n❌ Error: {e}") 115 | print("\nTroubleshooting:") 116 | print("1. Ensure Alfresco server is running") 117 | print("2. Check environment variables") 118 | print("3. Verify network connectivity") 119 | return False 120 | 121 | return True 122 | 123 | 124 | def main(): 125 | """Main function to run the quick start example.""" 126 | print("Starting Alfresco MCP Server Quick Start Example...") 127 | 128 | # Run the async example 129 | success = asyncio.run(quick_start_example()) 130 | 131 | if success: 132 | print("\n🎉 Example completed successfully!") 133 | else: 134 | print("\n💥 Example failed. Please check the error messages above.") 135 | 136 | 137 | if __name__ == "__main__": 138 | main() ``` -------------------------------------------------------------------------------- /alfresco_mcp_server/tools/core/update_node_properties.py: -------------------------------------------------------------------------------- ```python 1 | """ 2 | Update node properties tool for Alfresco MCP Server. 3 | Self-contained tool for updating document/folder metadata and properties. 4 | """ 5 | import logging 6 | import os 7 | from typing import Optional 8 | from fastmcp import Context 9 | 10 | from ...utils.connection import ensure_connection, get_core_client 11 | 12 | logger = logging.getLogger(__name__) 13 | 14 | 15 | async def update_node_properties_impl( 16 | node_id: str, 17 | name: str = "", 18 | title: str = "", 19 | description: str = "", 20 | author: str = "", 21 | ctx: Optional[Context] = None 22 | ) -> str: 23 | """Update metadata and properties of a document or folder. 24 | 25 | Args: 26 | node_id: Node ID to update (required) 27 | name: New name for the node (optional) 28 | title: Document title (cm:title) (optional) 29 | description: Document description (cm:description) (optional) 30 | author: Document author (cm:author) (optional) 31 | ctx: MCP context for progress reporting 32 | 33 | Returns: 34 | Update confirmation with changes made 35 | """ 36 | if ctx: 37 | await ctx.info(f"Updating properties for: {node_id}") 38 | await ctx.report_progress(0.1) 39 | 40 | if not node_id.strip(): 41 | return "ERROR: node_id is required" 42 | 43 | if not any([name, title, description, author]): 44 | return "ERROR: At least one property (name, title, description, or author) must be provided" 45 | 46 | try: 47 | # Ensure connection and get core client 48 | await ensure_connection() 49 | core_client = await get_core_client() 50 | 51 | # Clean the node ID (remove any URL encoding or extra characters) 52 | clean_node_id = node_id.strip() 53 | if clean_node_id.startswith('alfresco://'): 54 | # Extract node ID from URI format 55 | clean_node_id = clean_node_id.split('/')[-1] 56 | 57 | logger.info(f"Updating properties for node: {clean_node_id}") 58 | 59 | if ctx: 60 | await ctx.report_progress(0.3) 61 | 62 | # Get node information first to validate it exists 63 | try: 64 | node_response = core_client.nodes.get(node_id=clean_node_id) 65 | if not hasattr(node_response, 'entry'): 66 | return f"ERROR: Failed to get node information for: {clean_node_id}" 67 | 68 | node_info = node_response.entry 69 | current_name = getattr(node_info, 'name', f"document_{clean_node_id}") 70 | 71 | except Exception as get_error: 72 | return f"ERROR: Failed to validate node {clean_node_id}: {str(get_error)}" 73 | 74 | if ctx: 75 | await ctx.report_progress(0.5) 76 | 77 | # Prepare updates for actual API call 78 | properties_updates = {} 79 | if title and title.strip(): 80 | properties_updates['cm:title'] = title.strip() 81 | if description and description.strip(): 82 | properties_updates['cm:description'] = description.strip() 83 | if author and author.strip(): 84 | properties_updates['cm:author'] = author.strip() 85 | 86 | # Import the UpdateNodeRequest model 87 | try: 88 | from python_alfresco_api.clients.core.nodes.models import UpdateNodeRequest 89 | except ImportError: 90 | return "ERROR: Failed to import UpdateNodeRequest model" 91 | 92 | # Prepare update request 93 | update_request = UpdateNodeRequest() 94 | 95 | if name and name.strip(): 96 | update_request.name = name.strip() 97 | if properties_updates: 98 | update_request.properties = properties_updates 99 | 100 | if ctx: 101 | await ctx.report_progress(0.7) 102 | 103 | # Use the core client's update method 104 | try: 105 | updated_node = core_client.nodes.update( 106 | node_id=clean_node_id, 107 | request=update_request 108 | ) 109 | logger.info("Node properties updated successfully") 110 | 111 | except Exception as update_error: 112 | logger.error(f"Update failed: {update_error}") 113 | return f"ERROR: Update failed: {str(update_error)}" 114 | 115 | if ctx: 116 | await ctx.report_progress(1.0) 117 | 118 | changes = [] 119 | if name: 120 | changes.append(f"- Name: {name}") 121 | if title: 122 | changes.append(f"- Title: {title}") 123 | if description: 124 | changes.append(f"- Description: {description}") 125 | if author: 126 | changes.append(f"- Author: {author}") 127 | 128 | updated_properties = "\n".join(changes) 129 | 130 | # Clean JSON-friendly formatting (no markdown syntax) 131 | return f"Node Updated Successfully\n\nNode ID: {clean_node_id}\nUpdated Properties:\n{updated_properties}\nUpdate completed successfully" 132 | 133 | except Exception as e: 134 | error_msg = f"ERROR: Update failed: {str(e)}" 135 | if ctx: 136 | await ctx.error(error_msg) 137 | logger.error(f"Update failed: {e}") 138 | return error_msg ``` -------------------------------------------------------------------------------- /docs/client_configurations.md: -------------------------------------------------------------------------------- ```markdown 1 | # MCP Client Configuration Guide 2 | 3 | This guide covers configuration for various MCP clients beyond Claude Desktop. For Claude Desktop configuration, see the [Claude Desktop Setup Guide](./claude_desktop_setup.md). 4 | 5 | ## 🔷 Cursor Configuration 6 | 7 | Cursor is a VS Code fork with AI capabilities and MCP support. 8 | 9 | > 📖 **Complete Setup Guide**: Detailed Cursor configuration instructions available at [playbooks.com](https://playbooks.com/mcp/stevereiner-alfresco-content-services#cursor-setup) 10 | 11 | ### For Users (PyPI Installation) 12 | 13 | If you installed the package via PyPI with pipx: 14 | 15 | ```json 16 | { 17 | "mcpServers": { 18 | "python-alfresco-mcp-server": { 19 | "command": "python-alfresco-mcp-server", 20 | "args": ["--transport", "stdio"], 21 | "env": { 22 | "ALFRESCO_URL": "http://localhost:8080", 23 | "ALFRESCO_USERNAME": "admin", 24 | "ALFRESCO_PASSWORD": "admin" 25 | } 26 | } 27 | } 28 | } 29 | ``` 30 | 31 | ### For Developers (Source Installation) 32 | 33 | If you're using the source code with UV: 34 | 35 | ```json 36 | { 37 | "mcpServers": { 38 | "python-alfresco-mcp-server": { 39 | "command": "uv", 40 | "args": ["run", "python-alfresco-mcp-server"], 41 | "cwd": "/path/to/python-alfresco-mcp-server", 42 | "env": { 43 | "ALFRESCO_URL": "http://localhost:8080", 44 | "ALFRESCO_USERNAME": "admin", 45 | "ALFRESCO_PASSWORD": "admin" 46 | } 47 | } 48 | } 49 | } 50 | ``` 51 | 52 | ## ⚡ Claude Code Configuration 53 | 54 | Claude Code is Anthropic's VS Code extension with MCP support. 55 | 56 | > 📖 **Complete Setup Guide**: Claude Code configuration instructions at [playbooks.com](https://playbooks.com/mcp/stevereiner-alfresco-content-services#claude-code-setup) 57 | 58 | ### For Users (PyPI Installation) 59 | 60 | ```bash 61 | claude mcp add-json "python-alfresco-mcp-server" '{ 62 | "command": "python-alfresco-mcp-server", 63 | "args": ["--transport", "stdio"], 64 | "env": { 65 | "ALFRESCO_URL": "http://localhost:8080", 66 | "ALFRESCO_USERNAME": "admin", 67 | "ALFRESCO_PASSWORD": "admin" 68 | } 69 | }' 70 | ``` 71 | 72 | ### For Developers (Source Installation) 73 | 74 | ```bash 75 | claude mcp add-json "python-alfresco-mcp-server" '{ 76 | "command": "uv", 77 | "args": ["run", "python-alfresco-mcp-server"], 78 | "cwd": "/path/to/python-alfresco-mcp-server", 79 | "env": { 80 | "ALFRESCO_URL": "http://localhost:8080", 81 | "ALFRESCO_USERNAME": "admin", 82 | "ALFRESCO_PASSWORD": "admin" 83 | } 84 | }' 85 | ``` 86 | 87 | ## 🔧 Other MCP Clients 88 | 89 | For any MCP-compatible client, use these connection parameters based on your installation method: 90 | 91 | ### PyPI Installation (Users) 92 | 93 | - **Command**: `python-alfresco-mcp-server` (assumes pipx installation) 94 | - **Args**: `["--transport", "stdio"]` 95 | - **Transport Options**: 96 | - STDIO (default) - Direct MCP protocol 97 | - HTTP (add `--port 8001`) - RESTful API 98 | - SSE (add `--port 8003`) - Server-Sent Events 99 | 100 | ### Source Installation (Developers) 101 | 102 | - **Command**: `uv` 103 | - **Args**: `["run", "python-alfresco-mcp-server"]` 104 | - **Working Directory**: Path to cloned repository 105 | - **Transport Options**: Same as above 106 | 107 | ### Traditional Python Installation 108 | 109 | If using traditional pip in a virtual environment: 110 | 111 | - **Command**: `/path/to/venv/bin/python-alfresco-mcp-server` (full path to executable) 112 | - **Args**: `["--transport", "stdio"]` 113 | - **Transport Options**: Same as above 114 | 115 | ## 🔧 Environment Variables 116 | 117 | All clients need these environment variables configured: 118 | 119 | | Variable | Default | Description | 120 | |----------|---------|-------------| 121 | | `ALFRESCO_URL` | `http://localhost:8080` | Alfresco server URL | 122 | | `ALFRESCO_USERNAME` | `admin` | Username for authentication | 123 | | `ALFRESCO_PASSWORD` | `admin` | Password for authentication | 124 | | `ALFRESCO_VERIFY_SSL` | `false` | Verify SSL certificates | 125 | | `ALFRESCO_TIMEOUT` | `30` | Request timeout (seconds) | 126 | 127 | ### Windows-Specific Variables (if needed) 128 | 129 | For Windows systems experiencing character encoding issues: 130 | 131 | ```json 132 | "env": { 133 | "ALFRESCO_URL": "http://localhost:8080", 134 | "ALFRESCO_USERNAME": "admin", 135 | "ALFRESCO_PASSWORD": "admin", 136 | "PYTHONIOENCODING": "utf-8", 137 | "PYTHONLEGACYWINDOWSSTDIO": "1" 138 | } 139 | ``` 140 | 141 | ## 🚀 Transport Options 142 | 143 | The MCP server supports three transport protocols: 144 | 145 | ### STDIO (Default) 146 | - **Fastest** and most efficient 147 | - Direct MCP protocol communication 148 | - Recommended for most use cases 149 | - Args: `["--transport", "stdio"]` (optional, it's the default) 150 | 151 | ### HTTP 152 | - RESTful API interface 153 | - Useful for web services and testing 154 | - Args: `["--transport", "http", "--port", "8001"]` 155 | 156 | ### SSE (Server-Sent Events) 157 | - Real-time streaming updates 158 | - Good for live monitoring 159 | - Args: `["--transport", "sse", "--port", "8003"]` 160 | 161 | ## 🧪 Testing Your Configuration 162 | 163 | After setting up your MCP client: 164 | 165 | 1. **Start Your Client**: Launch your MCP-enabled application 166 | 2. **Check Connection**: Look for "python-alfresco-mcp-server" in connected servers 167 | 3. **Test Basic Functionality**: 168 | - Try the `repository_info` tool to verify connection 169 | - Run a simple `search_content` query 170 | - Check that all 15 tools are available 171 | 172 | ## 🛠️ Troubleshooting 173 | 174 | ### Common Issues 175 | 176 | 1. **Command Not Found** 177 | - Ensure the package is installed correctly 178 | - For pipx: Run `pipx list` to verify installation 179 | - For source: Ensure UV is installed and working directory is correct 180 | 181 | 2. **Connection Failures** 182 | - Check Alfresco server is running 183 | - Verify environment variables are set correctly 184 | - Test connection with `curl http://localhost:8080/alfresco` 185 | 186 | 3. **Permission Errors** 187 | - Verify Alfresco username/password 188 | - Check that user has appropriate permissions 189 | - Try with admin credentials first 190 | 191 | 4. **Character Encoding (Windows)** 192 | - Add Windows-specific environment variables 193 | - Ensure UTF-8 encoding is configured 194 | 195 | ### Getting Help 196 | 197 | - 📚 **Documentation**: Complete guides in [`../docs/`](./README.md) 198 | - 🛠️ **Troubleshooting**: [Troubleshooting Guide](./troubleshooting.md) 199 | - 🐛 **Issues**: [GitHub Issues](https://github.com/stevereiner/python-alfresco-mcp-server/issues) 200 | 201 | ## ⚠️ Security Notes 202 | 203 | - **Never commit configuration files** with real credentials to version control 204 | - **Use environment variables** for production deployments 205 | - **Consider using .env files** for local development (they're ignored by git) 206 | - **Use strong passwords** for production Alfresco servers ``` -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- ```python 1 | """ 2 | Pytest configuration and shared fixtures for Alfresco MCP Server tests. 3 | Provides configuration for unit tests, integration tests, and code coverage. 4 | """ 5 | import pytest 6 | import pytest_asyncio 7 | import asyncio 8 | import os 9 | import httpx 10 | import tempfile 11 | import shutil 12 | from typing import Generator, AsyncGenerator 13 | from unittest.mock import MagicMock, AsyncMock, patch 14 | 15 | # Test markers 16 | pytest_plugins = ["pytest_asyncio"] 17 | 18 | def pytest_configure(config): 19 | """Configure pytest markers.""" 20 | config.addinivalue_line( 21 | "markers", "unit: marks tests as unit tests (fast, mocked)" 22 | ) 23 | config.addinivalue_line( 24 | "markers", "integration: marks tests as integration tests (requires live Alfresco)" 25 | ) 26 | config.addinivalue_line( 27 | "markers", "slow: marks tests as slow running" 28 | ) 29 | config.addinivalue_line( 30 | "markers", "performance: marks tests as performance benchmarks" 31 | ) 32 | 33 | 34 | @pytest.fixture(scope="session") 35 | def event_loop(): 36 | """Create an instance of the default event loop for the test session.""" 37 | loop = asyncio.get_event_loop_policy().new_event_loop() 38 | yield loop 39 | loop.close() 40 | 41 | 42 | @pytest.fixture 43 | def temp_dir() -> Generator[str, None, None]: 44 | """Create a temporary directory for test files.""" 45 | with tempfile.TemporaryDirectory() as temp_dir: 46 | yield temp_dir 47 | 48 | 49 | @pytest.fixture 50 | def mock_alfresco_factory(): 51 | """Mock Alfresco client factory for unit tests.""" 52 | factory = MagicMock() 53 | 54 | # Mock search client 55 | search_client = AsyncMock() 56 | search_client.search = AsyncMock() 57 | factory.create_search_client.return_value = search_client 58 | 59 | # Mock core client 60 | core_client = AsyncMock() 61 | core_client.get_node = AsyncMock() 62 | core_client.create_node = AsyncMock() 63 | core_client.update_node = AsyncMock() 64 | core_client.delete_node = AsyncMock() 65 | core_client.get_node_content = AsyncMock() 66 | factory.create_core_client.return_value = core_client 67 | 68 | return factory 69 | 70 | 71 | @pytest.fixture 72 | def mock_auth_util(): 73 | """Mock authentication utility for unit tests.""" 74 | auth_util = AsyncMock() 75 | auth_util.ensure_authenticated = AsyncMock() 76 | auth_util.get_auth_headers = AsyncMock(return_value={"Authorization": "Bearer mock-token"}) 77 | auth_util.is_authenticated.return_value = True 78 | return auth_util 79 | 80 | 81 | @pytest_asyncio.fixture 82 | async def fastmcp_client(): 83 | """FastMCP in-memory client for testing.""" 84 | from fastmcp import Client 85 | from alfresco_mcp_server.fastmcp_server import mcp 86 | 87 | async with Client(mcp) as client: 88 | yield client 89 | 90 | 91 | @pytest.fixture 92 | async def http_client() -> AsyncGenerator[httpx.AsyncClient, None]: 93 | """HTTP client for integration tests.""" 94 | async with httpx.AsyncClient(timeout=30.0) as client: 95 | yield client 96 | 97 | 98 | @pytest.fixture 99 | def alfresco_config(): 100 | """Alfresco configuration for tests.""" 101 | return { 102 | "url": os.getenv("ALFRESCO_URL", "http://localhost:8080"), 103 | "username": os.getenv("ALFRESCO_USERNAME", "admin"), 104 | "password": os.getenv("ALFRESCO_PASSWORD", "admin"), 105 | "verify_ssl": os.getenv("ALFRESCO_VERIFY_SSL", "false").lower() == "true" 106 | } 107 | 108 | 109 | @pytest.fixture 110 | def sample_documents(): 111 | """Sample document data for testing.""" 112 | import base64 113 | 114 | return { 115 | "text_doc": { 116 | "filename": "test_document.txt", 117 | "content": "This is a test document for MCP server testing.", 118 | "content_base64": base64.b64encode( 119 | "This is a test document for MCP server testing.".encode() 120 | ).decode(), 121 | "mime_type": "text/plain" 122 | }, 123 | "json_doc": { 124 | "filename": "test_data.json", 125 | "content": '{"test": "data", "numbers": [1, 2, 3]}', 126 | "content_base64": base64.b64encode( 127 | '{"test": "data", "numbers": [1, 2, 3]}'.encode() 128 | ).decode(), 129 | "mime_type": "application/json" 130 | } 131 | } 132 | 133 | 134 | @pytest.fixture 135 | def mock_search_results(): 136 | """Mock search results for testing.""" 137 | from types import SimpleNamespace 138 | 139 | def create_mock_entry(name, node_id, is_folder=False): 140 | entry = SimpleNamespace() 141 | entry.entry = SimpleNamespace() 142 | entry.entry.name = name 143 | entry.entry.id = node_id 144 | entry.entry.isFolder = is_folder 145 | entry.entry.modifiedAt = "2024-01-15T10:30:00Z" 146 | entry.entry.createdByUser = {"displayName": "Test User"} 147 | entry.entry.content = {"sizeInBytes": 1024} if not is_folder else None 148 | entry.entry.path = {"name": "/Shared/Test"} 149 | return entry 150 | 151 | results = SimpleNamespace() 152 | results.list = SimpleNamespace() 153 | results.list.entries = [ 154 | create_mock_entry("Test Document 1.pdf", "doc-123", False), 155 | create_mock_entry("Test Folder", "folder-456", True), 156 | create_mock_entry("Test Document 2.txt", "doc-789", False) 157 | ] 158 | 159 | return results 160 | 161 | 162 | def pytest_collection_modifyitems(config, items): 163 | """Modify test collection to handle markers.""" 164 | if config.getoption("--integration"): 165 | # Only run integration tests 166 | skip_unit = pytest.mark.skip(reason="Integration test run - skipping unit tests") 167 | for item in items: 168 | if "integration" not in item.keywords: 169 | item.add_marker(skip_unit) 170 | else: 171 | # Skip integration tests by default 172 | skip_integration = pytest.mark.skip(reason="Integration tests require --integration flag") 173 | for item in items: 174 | if "integration" in item.keywords: 175 | item.add_marker(skip_integration) 176 | 177 | 178 | def pytest_addoption(parser): 179 | """Add custom command line options.""" 180 | parser.addoption( 181 | "--integration", 182 | action="store_true", 183 | default=False, 184 | help="Run integration tests (requires live Alfresco server)" 185 | ) 186 | parser.addoption( 187 | "--performance", 188 | action="store_true", 189 | default=False, 190 | help="Run performance benchmarks" 191 | ) 192 | 193 | 194 | @pytest.fixture 195 | def check_alfresco_available(alfresco_config): 196 | """Check if Alfresco server is available for integration tests.""" 197 | def _check(): 198 | try: 199 | response = httpx.get( 200 | f"{alfresco_config['url']}/alfresco/api/-default-/public/alfresco/versions/1/probes/-ready-", 201 | timeout=5.0 202 | ) 203 | return response.status_code == 200 204 | except Exception: 205 | return False 206 | 207 | return _check ``` -------------------------------------------------------------------------------- /alfresco_mcp_server/tools/core/cancel_checkout.py: -------------------------------------------------------------------------------- ```python 1 | """ 2 | Cancel checkout tool implementation for Alfresco MCP Server. 3 | Handles canceling document checkout with cleanup and unlock management. 4 | """ 5 | import logging 6 | import pathlib 7 | import json 8 | from datetime import datetime 9 | from fastmcp import Context 10 | 11 | from ...utils.connection import get_core_client 12 | from ...utils.json_utils import safe_format_output 13 | 14 | logger = logging.getLogger(__name__) 15 | 16 | 17 | async def cancel_checkout_impl( 18 | node_id: str, 19 | ctx: Context = None 20 | ) -> str: 21 | """Cancel checkout of a document, discarding any working copy. 22 | 23 | Args: 24 | node_id: Original node ID that was checked out 25 | ctx: MCP context for progress reporting 26 | 27 | Returns: 28 | Cancellation confirmation and cleanup status 29 | """ 30 | if ctx: 31 | await ctx.info(f"Cancelling checkout for: {node_id}") 32 | await ctx.report_progress(0.1) 33 | 34 | if not node_id.strip(): 35 | return safe_format_output("❌ Error: node_id is required") 36 | 37 | try: 38 | logger.info(f"Starting cancel checkout: node {node_id}") 39 | core_client = await get_core_client() 40 | 41 | # Clean the node ID 42 | clean_node_id = node_id.strip() 43 | if clean_node_id.startswith('alfresco://'): 44 | clean_node_id = clean_node_id.split('/')[-1] 45 | 46 | if ctx: 47 | await ctx.info("Checking node status...") 48 | await ctx.report_progress(0.3) 49 | 50 | # Get node information to validate using high-level core client 51 | node_response = core_client.nodes.get(node_id=clean_node_id) 52 | 53 | if not hasattr(node_response, 'entry'): 54 | return f"ERROR: Failed to get node information for: {clean_node_id}" 55 | 56 | node_info = node_response.entry 57 | filename = getattr(node_info, 'name', f"document_{clean_node_id}") 58 | 59 | if ctx: 60 | await ctx.info(">> Performing Alfresco unlock using high-level client...") 61 | await ctx.report_progress(0.5) 62 | 63 | # Use high-level core client unlock method 64 | try: 65 | logger.info(f"Attempting to unlock document: {clean_node_id}") 66 | unlock_response = core_client.versions.cancel_checkout(node_id=clean_node_id) 67 | if unlock_response and hasattr(unlock_response, 'entry'): 68 | api_status = "✅ Document unlocked in Alfresco" 69 | else: 70 | api_status = "✅ Document unlocked in Alfresco" 71 | logger.info(f"Document unlocked successfully: {clean_node_id}") 72 | except Exception as unlock_error: 73 | error_str = str(unlock_error) 74 | if "404" in error_str: 75 | # Document might not be locked 76 | api_status = "ℹ️ Document was not locked in Alfresco" 77 | logger.info(f"Document was not locked: {clean_node_id}") 78 | elif "405" in error_str: 79 | # Server doesn't support lock/unlock APIs 80 | api_status = "WARNING: Server doesn't support lock/unlock APIs (treating as unlocked)" 81 | logger.warning(f"Server doesn't support unlock API for {clean_node_id}") 82 | else: 83 | api_status = f"WARNING: Alfresco unlock failed: {error_str}" 84 | logger.error(f"Failed to unlock document {clean_node_id}: {error_str}") 85 | 86 | if ctx: 87 | await ctx.info("Cleaning up local files...") 88 | await ctx.report_progress(0.7) 89 | 90 | # Clean up local checkout tracking 91 | downloads_dir = pathlib.Path.home() / "Downloads" 92 | checkout_dir = downloads_dir / "checkout" 93 | checkout_manifest_path = checkout_dir / ".checkout_manifest.json" 94 | 95 | checkout_data = {} 96 | cleanup_status = [api_status] 97 | 98 | if checkout_manifest_path.exists(): 99 | try: 100 | with open(checkout_manifest_path, 'r') as f: 101 | checkout_data = json.load(f) 102 | except: 103 | checkout_data = {} 104 | 105 | # Check if this node is tracked in local checkouts 106 | if 'checkouts' in checkout_data and clean_node_id in checkout_data['checkouts']: 107 | checkout_info = checkout_data['checkouts'][clean_node_id] 108 | checkout_filename = checkout_info['local_file'] 109 | checkout_file_path = checkout_dir / checkout_filename 110 | 111 | # Remove local checkout file 112 | try: 113 | if checkout_file_path.exists(): 114 | checkout_file_path.unlink() 115 | cleanup_status.append("🗑️ Local checkout file removed") 116 | logger.info(f"Removed local checkout file: {checkout_file_path}") 117 | else: 118 | cleanup_status.append("ℹ️ Local checkout file already removed") 119 | except Exception as e: 120 | cleanup_status.append(f"WARNING: Could not remove local file: {e}") 121 | logger.warning(f"Failed to remove local file {checkout_file_path}: {e}") 122 | 123 | # Remove from tracking 124 | del checkout_data['checkouts'][clean_node_id] 125 | 126 | # Update manifest 127 | try: 128 | with open(checkout_manifest_path, 'w') as f: 129 | json.dump(checkout_data, f, indent=2) 130 | cleanup_status.append(">> Checkout tracking updated") 131 | except Exception as e: 132 | cleanup_status.append(f"WARNING: Could not update tracking: {e}") 133 | else: 134 | cleanup_status.append("ℹ️ No local checkout tracking found") 135 | 136 | if ctx: 137 | await ctx.info("Document unlocked!") 138 | await ctx.report_progress(1.0) 139 | 140 | # Clean JSON-friendly formatting (no markdown syntax) 141 | result = f"🔓 Document Unlocked\n\n" 142 | result += f">> Document: {filename}\n" 143 | result += f"ID: Node ID: {clean_node_id}\n" 144 | result += f"🕒 Unlocked: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n" 145 | result += f"🧹 Cleanup Status:\n" 146 | 147 | for status in cleanup_status: 148 | result += f" {status}\n" 149 | 150 | result += f"\nINFO: Note: Document is now available for others to edit." 151 | result += f"\nWARNING: Important: Any unsaved changes in the local file have been discarded." 152 | 153 | return safe_format_output(result) 154 | 155 | except Exception as e: 156 | error_msg = f"❌ Cancel checkout failed: {str(e)}" 157 | if ctx: 158 | await ctx.error(error_msg) 159 | logger.error(f"Cancel checkout failed: {e}") 160 | return safe_format_output(error_msg) ``` -------------------------------------------------------------------------------- /tests/mcp_specific/mcp_testing_guide.md: -------------------------------------------------------------------------------- ```markdown 1 | # MCP Testing Guide - Base64 Examples and Utilities 2 | 3 | ## Base64 Example Strings 4 | 5 | ### Simple Examples 6 | ``` 7 | "SGVsbG8gV29ybGQ=" # "Hello World" 8 | "VGVzdCBkb2N1bWVudA==" # "Test document" 9 | "U2FtcGxlIGNvbnRlbnQ=" # "Sample content" 10 | ``` 11 | 12 | ### Document Content Examples 13 | ```python 14 | # Text document 15 | text_content = "This is a test document for MCP server testing." 16 | text_base64 = "VGhpcyBpcyBhIHRlc3QgZG9jdW1lbnQgZm9yIE1DUCBzZXJ2ZXIgdGVzdGluZy4=" 17 | 18 | # JSON document 19 | json_content = '{"test": "data", "numbers": [1, 2, 3]}' 20 | json_base64 = "eyJ0ZXN0IjogImRhdGEiLCAibnVtYmVycyI6IFsxLCAyLCAzXX0=" 21 | ``` 22 | 23 | ## Base64 Generation Functions 24 | 25 | ### Simple Generator Function 26 | ```python 27 | import base64 28 | 29 | def generate_base64(text_content): 30 | """Convert text to base64 string.""" 31 | return base64.b64encode(text_content.encode('utf-8')).decode('utf-8') 32 | 33 | def decode_base64(base64_string): 34 | """Decode base64 string to text.""" 35 | return base64.b64decode(base64_string).decode('utf-8') 36 | 37 | # Examples 38 | text = "Hello World" 39 | encoded = generate_base64(text) # "SGVsbG8gV29ybGQ=" 40 | decoded = decode_base64(encoded) # "Hello World" 41 | ``` 42 | 43 | ### Document Generator for MCP Testing 44 | ```python 45 | import base64 46 | import json 47 | import time 48 | import uuid 49 | 50 | def generate_test_document(filename, content_type="text"): 51 | """Generate a test document with base64 encoding for MCP upload.""" 52 | 53 | if content_type == "text": 54 | content = f"""Test Document - {filename} 55 | 56 | Created: {time.strftime('%Y-%m-%d %H:%M:%S')} 57 | Document ID: {uuid.uuid4()} 58 | Type: MCP Test Document 59 | 60 | This is a sample document created for testing the Alfresco MCP server. 61 | It contains structured content for validation purposes. 62 | 63 | Sections: 64 | 1. Header information 65 | 2. Timestamp data 66 | 3. Unique identifier 67 | 4. Content body 68 | 5. Footer notes 69 | 70 | End of document. 71 | """ 72 | elif content_type == "json": 73 | content = json.dumps({ 74 | "document_id": str(uuid.uuid4()), 75 | "filename": filename, 76 | "created_at": time.strftime('%Y-%m-%d %H:%M:%S'), 77 | "content_type": "test_data", 78 | "test_data": { 79 | "numbers": [1, 2, 3, 4, 5], 80 | "strings": ["hello", "world", "test"], 81 | "nested": { 82 | "level1": { 83 | "level2": "deep_value" 84 | } 85 | } 86 | } 87 | }, indent=2) 88 | 89 | content_base64 = base64.b64encode(content.encode('utf-8')).decode('utf-8') 90 | 91 | return { 92 | "filename": filename, 93 | "content": content, 94 | "content_base64": content_base64, 95 | "size": len(content), 96 | "encoded_size": len(content_base64) 97 | } 98 | 99 | # Usage examples 100 | text_doc = generate_test_document("test_file.txt", "text") 101 | json_doc = generate_test_document("test_data.json", "json") 102 | ``` 103 | 104 | ### Batch Document Generator 105 | ```python 106 | def generate_batch_documents(count=5, prefix="batch_doc"): 107 | """Generate multiple test documents for batch operations.""" 108 | 109 | documents = [] 110 | session_id = str(uuid.uuid4())[:8] 111 | 112 | for i in range(count): 113 | content = f"""Batch Document {i+1} 114 | 115 | Session ID: {session_id} 116 | Document Index: {i+1} of {count} 117 | Created: {time.strftime('%Y-%m-%d %H:%M:%S')} 118 | Unique ID: {uuid.uuid4()} 119 | 120 | This is batch document number {i+1} created for testing 121 | concurrent operations and bulk uploads in the MCP server. 122 | 123 | Content sections: 124 | - Document metadata 125 | - Sequential numbering 126 | - Timestamp information 127 | - Unique identification 128 | 129 | Processing notes: 130 | - Part of batch operation 131 | - Sequential creation 132 | - Automated generation 133 | """ 134 | 135 | doc = { 136 | "filename": f"{prefix}_{session_id}_{i+1:03d}.txt", 137 | "content": content, 138 | "content_base64": base64.b64encode(content.encode('utf-8')).decode('utf-8'), 139 | "description": f"Batch document {i+1} from session {session_id}" 140 | } 141 | documents.append(doc) 142 | 143 | return documents 144 | 145 | # Generate 5 test documents 146 | batch_docs = generate_batch_documents(5) 147 | ``` 148 | 149 | ## MCP Upload Examples 150 | 151 | ### Single Document Upload 152 | ```json 153 | { 154 | "filename": "test_document.txt", 155 | "content_base64": "VGhpcyBpcyBhIHRlc3QgZG9jdW1lbnQgZm9yIE1DUCBzZXJ2ZXIgdGVzdGluZy4=", 156 | "parent_id": "-root-", 157 | "description": "Test upload via MCP Inspector" 158 | } 159 | ``` 160 | 161 | ### JSON Document Upload 162 | ```json 163 | { 164 | "filename": "test_data.json", 165 | "content_base64": "eyJ0ZXN0IjogImRhdGEiLCAibnVtYmVycyI6IFsxLCAyLCAzXX0=", 166 | "parent_id": "-root-", 167 | "description": "JSON test data" 168 | } 169 | ``` 170 | 171 | ## Validation Functions 172 | 173 | ### Base64 Validation 174 | ```python 175 | import re 176 | 177 | def validate_base64(base64_string): 178 | """Validate base64 string format.""" 179 | try: 180 | # Check format 181 | if not re.match(r'^[A-Za-z0-9+/]*={0,2}$', base64_string): 182 | return False, "Invalid base64 characters" 183 | 184 | # Test decode 185 | decoded = base64.b64decode(base64_string, validate=True) 186 | return True, f"Valid base64 ({len(decoded)} bytes)" 187 | 188 | except Exception as e: 189 | return False, f"Decode error: {str(e)}" 190 | 191 | # Test validation 192 | valid, message = validate_base64("SGVsbG8gV29ybGQ=") 193 | print(f"Validation: {valid}, Message: {message}") 194 | ``` 195 | 196 | ### Content Size Calculation 197 | ```python 198 | def calculate_base64_size(text_content): 199 | """Calculate the base64 encoded size before encoding.""" 200 | original_bytes = len(text_content.encode('utf-8')) 201 | base64_bytes = ((original_bytes + 2) // 3) * 4 202 | return { 203 | "original_size": original_bytes, 204 | "base64_size": base64_bytes, 205 | "size_increase": f"{((base64_bytes / original_bytes - 1) * 100):.1f}%" 206 | } 207 | 208 | # Example 209 | size_info = calculate_base64_size("Hello World") 210 | print(size_info) 211 | # {'original_size': 11, 'base64_size': 16, 'size_increase': '45.5%'} 212 | ``` 213 | 214 | ## Quick Reference 215 | 216 | ### Common Test Strings 217 | ```python 218 | TEST_STRINGS = { 219 | "hello": "SGVsbG8gV29ybGQ=", 220 | "test": "dGVzdA==", 221 | "sample": "c2FtcGxl", 222 | "document": "ZG9jdW1lbnQ=", 223 | "content": "Y29udGVudA==", 224 | "data": "ZGF0YQ==", 225 | "file": "ZmlsZQ==", 226 | "upload": "dXBsb2Fk" 227 | } 228 | ``` 229 | 230 | ### Usage in MCP Inspector 231 | 1. **Generate content**: Use the generator functions above 232 | 2. **Copy base64**: Use the `content_base64` field from generated documents 233 | 3. **Test upload**: Paste into MCP Inspector's upload_document tool 234 | 4. **Validate**: Check successful upload in Alfresco 235 | 236 | ### File Size Limits 237 | - **Small files**: < 1MB (recommended for testing) 238 | - **Medium files**: 1-10MB (stress testing) 239 | - **Large files**: > 10MB (may require chunking) 240 | 241 | Remember: Base64 encoding increases file size by approximately 33%, so plan accordingly for upload limits. ``` -------------------------------------------------------------------------------- /alfresco_mcp_server/utils/file_type_analysis.py: -------------------------------------------------------------------------------- ```python 1 | """ 2 | File type analysis utility for Alfresco MCP Server. 3 | Provides content type analysis and suggestions for different file types. 4 | """ 5 | import pathlib 6 | import mimetypes 7 | from typing import Dict, List, Optional 8 | 9 | 10 | def detect_file_extension_from_content(content: bytes) -> Optional[str]: 11 | """Detect file type from content and return appropriate extension. 12 | 13 | Args: 14 | content: Raw file content bytes 15 | 16 | Returns: 17 | File extension (e.g., '.pdf', '.txt', '.jpg') or None if unknown 18 | """ 19 | if not content: 20 | return None 21 | 22 | # Check for common file signatures (magic numbers) 23 | if content.startswith(b'%PDF'): 24 | return '.pdf' 25 | elif content.startswith(b'\xff\xd8\xff'): 26 | return '.jpg' 27 | elif content.startswith(b'\x89PNG\r\n\x1a\n'): 28 | return '.png' 29 | elif content.startswith(b'GIF87a') or content.startswith(b'GIF89a'): 30 | return '.gif' 31 | elif content.startswith(b'PK\x03\x04') or content.startswith(b'PK\x05\x06') or content.startswith(b'PK\x07\x08'): 32 | # ZIP-based formats (could be .zip, .docx, .xlsx, .pptx, etc.) 33 | # For simplicity, assume .zip unless we detect Office formats 34 | if b'word/' in content[:1024] or b'xl/' in content[:1024] or b'ppt/' in content[:1024]: 35 | # Likely Office document but hard to determine exact type 36 | return '.zip' # Conservative choice 37 | return '.zip' 38 | elif content.startswith(b'\xd0\xcf\x11\xe0\xa1\xb1\x1a\xe1'): 39 | # Microsoft Office old format 40 | return '.doc' # Could also be .xls, .ppt but .doc is most common 41 | elif content.startswith(b'<?xml') or content.startswith(b'\xef\xbb\xbf<?xml'): 42 | return '.xml' 43 | elif content.startswith(b'<!DOCTYPE html') or content.startswith(b'<html'): 44 | return '.html' 45 | elif content.startswith(b'{\n') or content.startswith(b'{"') or content.startswith(b'[\n') or content.startswith(b'[{'): 46 | return '.json' 47 | else: 48 | # Try to detect if it's text content 49 | try: 50 | # Try to decode as UTF-8 text 51 | text_content = content.decode('utf-8') 52 | # Check if it contains mostly printable characters 53 | printable_chars = sum(1 for c in text_content if c.isprintable() or c.isspace()) 54 | if len(text_content) > 0 and printable_chars / len(text_content) > 0.8: 55 | return '.txt' 56 | except (UnicodeDecodeError, UnicodeError): 57 | pass 58 | 59 | # Unknown binary format 60 | return None 61 | 62 | 63 | def analyze_content_type(filename: str, mime_type: str, content: bytes) -> dict: 64 | """Analyze file type and provide relevant suggestions. 65 | 66 | Args: 67 | filename: Name of the file 68 | mime_type: MIME type of the file 69 | content: File content as bytes 70 | 71 | Returns: 72 | Dictionary with category, suggestions, and file_size 73 | """ 74 | file_size = len(content) 75 | 76 | # Get case-insensitive filename for macOS/Windows compatibility 77 | filename_lower = filename.lower() 78 | 79 | # Determine file category based on MIME type and extension 80 | if mime_type.startswith('image/'): 81 | category = 'images' 82 | suggestions = [ 83 | "Can be used for thumbnails and previews", 84 | "Consider image optimization for web use" 85 | ] 86 | elif mime_type.startswith('video/') or mime_type.startswith('audio/'): 87 | category = 'media' 88 | suggestions = [ 89 | "Large files may need streaming support", 90 | "Consider format compatibility for playback" 91 | ] 92 | elif mime_type in ['application/pdf', 'application/msword', 93 | 'application/vnd.openxmlformats-officedocument.wordprocessingml.document']: 94 | category = 'documents' 95 | suggestions = [ 96 | "PDF files support full-text search", 97 | ">> Consider using text extraction for searchable content", 98 | "Can be previewed in most browsers" 99 | ] 100 | elif mime_type in ['application/vnd.ms-excel', 101 | 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet']: 102 | category = 'documents' 103 | suggestions = [ 104 | ">> Excel spreadsheet - can be opened with Excel or LibreOffice Calc", 105 | ">> May contain tracked changes or comments" 106 | ] 107 | elif mime_type in ['application/vnd.ms-powerpoint', 108 | 'application/vnd.openxmlformats-officedocument.presentationml.presentation']: 109 | category = 'documents' 110 | suggestions = [ 111 | "Presentation files support slide previews", 112 | "Consider extracting slide content for search" 113 | ] 114 | elif mime_type.startswith('text/') or 'javascript' in mime_type or 'json' in mime_type: 115 | if filename_lower.endswith(('.py', '.js', '.java', '.cpp', '.c', '.cs', '.php', '.rb')): 116 | category = 'code' 117 | suggestions = [ 118 | "Source code files support syntax highlighting", 119 | ">> Check contents before extraction for security", 120 | "Can be indexed for code search" 121 | ] 122 | else: 123 | category = 'documents' 124 | suggestions = [ 125 | ">> Review for security before execution", 126 | ">> May require specific runtime environment" 127 | ] 128 | elif mime_type in ['application/zip', 'application/x-rar-compressed', 'application/gzip']: 129 | category = 'archives' 130 | suggestions = [ 131 | "Archive contents can be extracted and indexed", 132 | ">> Check contents before extraction for security", 133 | "May contain multiple file types" 134 | ] 135 | else: 136 | category = 'other' 137 | suggestions = [ 138 | "Unknown file type - review content manually", 139 | "Consider file format documentation" 140 | ] 141 | 142 | # Add size-based suggestions 143 | if file_size > 100 * 1024 * 1024: # > 100MB 144 | suggestions.append("WARNING: Large file - consider network and storage impact") 145 | 146 | # Add security suggestions for executable files (case-insensitive for cross-platform) 147 | executable_extensions = ( 148 | '.exe', '.bat', '.sh', '.com', '.scr', # Windows & shell scripts 149 | '.app', '.dmg', '.pkg', # macOS 150 | '.deb', '.rpm', '.run', '.bin', '.appimage' # Linux 151 | ) 152 | if filename_lower.endswith(executable_extensions): 153 | suggestions = [ 154 | "WARNING: Executable file - scan for security before running", 155 | "Consider sandboxed execution environment" 156 | ] 157 | category = 'executable' 158 | 159 | return { 160 | 'category': category, 161 | 'suggestions': suggestions, 162 | 'file_size': file_size 163 | } ```