This is page 1 of 3. Use http://codebase.md/stevereiner/python-alfresco-mcp-server?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: -------------------------------------------------------------------------------- ``` # Auto detect text files and perform LF normalization * text=auto ``` -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- ``` # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class # C extensions *.so # Distribution / packaging .Python build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ wheels/ share/python-wheels/ *.egg-info/ .installed.cfg *.egg MANIFEST # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .nox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *.cover *.py,cover .hypothesis/ .pytest_cache/ cover/ # Additional testing framework artifacts .pytest_cache/ pytest.ini.bak test-results/ test-reports/ .testmondata junit.xml report.xml # Translations *.mo *.pot # Django stuff: *.log local_settings.py db.sqlite3 db.sqlite3-journal # Flask stuff: instance/ .webassets-cache # Scrapy stuff: .scrapy # Sphinx documentation docs/_build/ # PyBuilder .pybuilder/ target/ # Jupyter Notebook .ipynb_checkpoints # IPython profile_default/ ipython_config.py # pyenv # For a library or package, you might want to ignore these files since the code is # intended to run in multiple environments; otherwise, check them in: # .python-version # pipenv # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. # However, in case of collaboration, if having platform-specific dependencies or dependencies # having no cross-platform support, pipenv may install dependencies that don't work, or not # install all needed dependencies. #Pipfile.lock # UV # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control. # This is especially recommended for binary packages to ensure reproducibility, and is more # commonly ignored for libraries. #uv.lock # poetry # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. # This is especially recommended for binary packages to ensure reproducibility, and is more # commonly ignored for libraries. # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control #poetry.lock # pdm # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. #pdm.lock # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it # in version control. # https://pdm.fming.dev/latest/usage/project/#working-with-version-control .pdm.toml .pdm-python .pdm-build/ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm __pypackages__/ # Celery stuff celerybeat-schedule celerybeat.pid # SageMath parsed files *.sage.py # Environments .env .venv env/ venv/ ENV/ env.bak/ venv.bak/ # Spyder project settings .spyderproject .spyproject # Rope project settings .ropeproject # mkdocs documentation /site # mypy .mypy_cache/ .dmypy.json dmypy.json # Pyre type checker .pyre/ # pytype static type analyzer .pytype/ # Cython debug symbols cython_debug/ # PyCharm # JetBrains specific template is maintained in a separate JetBrains.gitignore that can # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore # and can be added to the global gitignore or merged into this file. For a more nuclear # option (not recommended) you can uncomment the following to ignore the entire idea folder. #.idea/ # VS Code .vscode/settings.json .vscode/launch.json .vscode/extensions.json .vscode/tasks.json .vscode/.ropeproject # Keep workspace settings but ignore personal settings !.vscode/settings.json.template !.vscode/mcp.json # Ruff stuff: .ruff_cache/ # PyPI configuration file .pypirc # Cursor # Cursor is an AI-powered code editor.`.cursorignore` specifies files/directories to # exclude from AI features like autocomplete and code analysis. Recommended for sensitive data # refer to https://docs.cursor.com/context/ignore-files .cursorignore .cursorindexingignore *.cursormemory .cursormemory memory-bank/ # FastMCP 2.0 and MCP Development # MCP client/server communication logs *.mcp.log mcp_*.log fastmcp_*.log # MCP development artifacts .mcp_cache/ mcp_debug/ .fastmcp/ # Alfresco MCP Server specific alfresco_mcp_server.log* alfresco_*.log connection_test.py debug_*.py # Development and testing artifacts .benchmarks/ performance_results/ integration_test_results/ mock_data/ test_uploads/ test_downloads/ # Configuration files with secrets (keep templates) config.local.yaml config.dev.yaml config.prod.yaml .env.local .env.dev .env.prod # Keep sample config files !sample-*.txt !config.sample.* !.env.sample # Experimental/scratch files o.txt *.tmp test_*.py *_test.py scratch* temp* debug_output.txt output.txt ``` -------------------------------------------------------------------------------- /tests-debug/README.md: -------------------------------------------------------------------------------- ```markdown # Debug Test Files This directory contains one-off debug and development test files that are not part of the main testing suite. These files were moved from the root directory to clean up the project structure: - `test_additional_clients.py` - Testing additional client imports - `test_fixed_auth.py` - Authentication testing - `test_mcp_direct.py` - Direct MCP testing - `test_mcp_tools.py` - MCP tools testing - `test_sse_endpoint.py` - SSE endpoint testing - `test_update_debug.py` - Update debugging - `test_update_direct.py` - Direct update testing These are development/debugging tools and not included in the main test suite in `/tests/`. ``` -------------------------------------------------------------------------------- /tests/README.md: -------------------------------------------------------------------------------- ```markdown # Testing Structure This directory contains tests organized by their purpose. ## MCP Server Tests - `test_*.py` files in the root test the MCP server functionality - `mcp_specific/` contains files specifically for testing MCP server deployment ### Main Test Files (MCP Server Functionality) - `test_unit_tools.py` - Unit tests for MCP tools - `test_integration.py` - Integration tests with Alfresco - `test_fastmcp_2_0.py` - FastMCP 2.0 specific tests - `test_coverage.py` - Test coverage validation - `test_authentication.py` - Authentication tests - `test_search_debug.py` - Search functionality debugging - `test_simple_search.py` - Simple search tests - `test_response_structure.py` - Response structure validation - `test_comprehensive_scenarios.py` - Comprehensive scenario testing ### MCP Specific (Deployment & Inspector) - `mcp_specific/test_with_mcp_client.py` - MCP client testing - `mcp_specific/test_http_server.ps1` - HTTP server testing script - `mcp_specific/mcp_testing_guide.md` - Testing guide - `mcp_specific/test_with_mcp_inspector.md` - MCP Inspector testing - `mcp_specific/test_server_status.py` - Server status check ## Running Tests ### Unit Tests ```bash python -m pytest tests/test_unit_tools.py -v ``` ### Integration Tests (requires live Alfresco) ```bash python -m pytest tests/test_integration.py -v ``` ### MCP Server Status ```bash python tests/mcp_specific/test_server_status.py ``` ## Testing with MCP Inspector 1. Start HTTP server: `fastmcp run alfresco_mcp_server.fastmcp_server --host localhost --port 8003` 2. Use MCP Inspector with URL: `http://localhost:8003` 3. Note: MCP Inspector shows warning about risky auth and includes token in URL ## Testing with Claude Desktop 1. Update `claude-desktop-config.json` with current paths 2. Restart Claude Desktop application 3. Test MCP tools directly in Claude conversation ``` -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- ```markdown # Alfresco MCP Server Documentation 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. ## 📚 Documentation Index ### 🚀 Getting Started - [`quick_start_guide.md`](quick_start_guide.md) - Get up and running in 5 minutes - [`configuration_guide.md`](configuration_guide.md) - Configuration options and setup ### 🔧 Technical Guides - [`api_reference.md`](api_reference.md) - Complete API reference for all 15 tools ### 🏗️ Development & Testing - [`testing_guide.md`](testing_guide.md) - Running tests and validation ### 🆘 Support & Troubleshooting - [`troubleshooting.md`](troubleshooting.md) - Common issues and solutions ## 🎯 Quick Navigation | I want to... | Read this | |--------------|-----------| | **Get started quickly** | [`quick_start_guide.md`](quick_start_guide.md) | | **Configure my setup** | [`configuration_guide.md`](configuration_guide.md) | | **Learn about all tools** | [`api_reference.md`](api_reference.md) | | **Run tests** | [`testing_guide.md`](testing_guide.md) | | **Troubleshoot issues** | [`troubleshooting.md`](troubleshooting.md) | ## 📖 Documentation Standards All documentation in this directory follows these standards: - ✅ **Clear Examples**: Every feature includes working code examples - ✅ **Step-by-Step**: Complex procedures broken into manageable steps - ✅ **Cross-Referenced**: Related topics linked throughout - ✅ **Tested**: All code examples are tested and verified - ✅ **Version Aware**: Marked with applicable version information ## 🔄 Recent Updates - **v1.1.0**: Updated tool count and improved documentation - **v1.1.0**: Corrected tool count to 15 (4 search + 11 core tools) - **v1.1.0**: Removed marketing language for professional tone - **v1.0.0**: Initial production release with FastMCP 2.0 ## 🤝 Contributing to Documentation Found an error or want to improve the docs? Create an issue or pull request on GitHub. ## 📞 Need Help? - 🔍 Browse [`troubleshooting.md`](troubleshooting.md) for solutions - 🐛 Report issues via GitHub Issues --- **📌 Pro Tip**: Bookmark this page and use it as your central hub for all Alfresco MCP Server documentation needs! ``` -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- ```markdown # Alfresco MCP Server Examples 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. ## 📋 Available Examples ### 🚀 Quick Start Examples - [`quick_start.py`](quick_start.py) - Basic server setup and first tool call ### 🔧 Transport Examples - [`transport_examples.py`](transport_examples.py) - All transport protocols (STDIO, HTTP, SSE) ### 🛠️ Tool Usage Examples - [`document_lifecycle.py`](document_lifecycle.py) - Complete document management workflow - [`batch_operations.py`](batch_operations.py) - Bulk document processing ### 📊 Additional Examples - [`error_handling.py`](error_handling.py) - Error handling patterns ### 📖 Documentation Summary - [`examples_summary.md`](examples_summary.md) - Overview of all examples and documentation ## 🎯 Getting Started 1. **Install Dependencies**: Ensure you have the server installed 2. **Set Up Alfresco**: Configure your Alfresco connection 3. **Run Examples**: Each example is self-contained and well-documented ## 🔧 Prerequisites ```bash # Install the package pip install -e . # Set environment variables export ALFRESCO_URL="http://localhost:8080" export ALFRESCO_USERNAME="admin" export ALFRESCO_PASSWORD="admin" ``` ## 📖 Example Structure Each example includes: - ✅ **Clear documentation** of what it demonstrates - ✅ **Step-by-step comments** explaining each operation - ✅ **Error handling** best practices - ✅ **Expected output** descriptions - ✅ **Practical use cases** and scenarios ## 🚀 Running Examples ```bash # Quick start example python examples/quick_start.py # Document lifecycle workflow python examples/document_lifecycle.py # Transport protocols demonstration python examples/transport_examples.py # Batch operations and performance python examples/batch_operations.py # Error handling patterns python examples/error_handling.py ``` ## 💡 Tips - Start with `quick_start.py` for your first experience - Check `error_handling.py` for production-ready patterns - Use `batch_operations.py` for performance optimization insights - Explore `transport_examples.py` for different connection methods - Review `examples_summary.md` for documentation overview ``` -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- ```markdown # Python Alfresco MCP Server v1.1 🚀 [](https://pypi.org/project/python-alfresco-mcp-server/) [](https://pepy.tech/project/python-alfresco-mcp-server) [](https://pypi.org/project/python-alfresco-mcp-server/) [](https://github.com/stevereiner/python-alfresco-mcp-server/blob/main/LICENSE) **Model Context Protocol Server for Alfresco Content Services** 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, 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. Built with [FastMCP 2.0](https://github.com/jlowin/FastMCP). Features complete documentation, examples, and config for various MCP clients (Claude Desktop, MCP Inspector, references to configuring others). ## 🌟 What's New in v1.1 ### **Modular Architecture & Enhanced Testing** - **FastMCP**: v1.0 had FastMCP 2.0 implementation that had all tools implementations in the fastmcp_server.py file - **Code Modularization in v1.1**: Split monolithic single file into organized modular structure with separate files - **Directory Organization**: Organized into `tools/search/`, `tools/core/`, `resources/`, `prompts/`, `utils/` directories - **Enhanced Testing**: Complete test suite transformation - 143 tests with 100% pass rate - **Client Configuration Files**: Added dedicated Claude Desktop and MCP Inspector configuration files - **Live Integration Testing**: 21 Alfresco server validation tests for real-world functionality - **Python-Alfresco-API**: python-alfresco-mcp-server v1.1 requires the v1.1.1 python-alfresco-api package ## 📚 Complete Documentation ### **Documentation & Examples** - **📚 Complete Documentation**: 10 guides covering setup to deployment - **💡 Examples**: 6 practical examples from quick start to implementation patterns - **🔧 Configuration Management**: Environment variables, .env files, and command-line configuration - **🏗️ Setup instruction for use with MCP client ### **Learning Resources** - **🚀 [Quick Start Guide](./docs/quick_start_guide.md)**: 5-minute setup and first operations - **🤖 [Claude Desktop Setup](./docs/claude_desktop_setup.md)**: Complete Claude Desktop configuration for users and developers - **🔧 [Client Configurations](./docs/client_configurations.md)**: Setup guide for Cursor, Claude Code, and other MCP clients - **📖 [Examples Library](./examples/README.md)**: Implementation patterns and examples ### 📖 Guides covering setup, deployment, and usage: - **[📚 Documentation Hub](./docs/README.md)** - Complete navigation and overview - **[🚀 Quick Start Guide](./docs/quick_start_guide.md)** - 5-minute setup and first operations - **[📦 Installation with pip and pipx](./docs/install_with_pip_pipx.md)** - Traditional Python package installation methods - **[🤖 Claude Desktop Setup](./docs/claude_desktop_setup.md)** - Complete Claude Desktop configuration for users and developers - **[🔧 Client Configurations](./docs/client_configurations.md)** - Setup guide for Cursor, Claude Code, and other MCP clients - **[🔍 MCP Inspector Setup](./docs/mcp_inspector_setup.md)** - Development and testing with MCP Inspector - **[🔍 API Reference](./docs/api_reference.md)** - Complete tool and resource documentation - **[⚙️ Configuration Guide](./docs/configuration_guide.md)** - Development to deployment - **[🧪 Testing Guide](./docs/testing_guide.md)** - Quality assurance and test development - **[🛠️ Troubleshooting Guide](./docs/troubleshooting.md)** - Problem diagnosis and resolution ## 🚀 Features ### Content Management and Search Tools - **Search Tools**: - **Full Text Search**: Basic content search with wildcard support (search_content) - **Advanced Search**: AFTS query language with date filters, sorting, and field targeting - **Metadata Search**: Property-based queries with operators (equals, contains, date ranges) - **CMIS Search**: SQL like queries for complex content discovery - **Document Lifecycle**: Upload, download, check-in, checkout, cancel checkout - **Version Management**: Create major/minor versions with comments - **Folder Operations**: Create folders, delete folder nodes - **Property Management**: Get and set document/folder properties and names - **Node Operations**: Delete nodes (documents and folders) (trash or permanent) - **Repository Info**: (Tool and Resource) Returns repository status, version and whether Community or Enterprise, and module configuration ### MCP Architecture - **FastMCP 2.0 Framework**: Modern, high-performance MCP server implementation - **Multiple Transports**: - **STDIO** (direct MCP protocol) - Default and fastest - **HTTP** (RESTful API) - Web services and testing - **SSE** (Server-Sent Events) - Real-time streaming updates - **Enterprise Security**: OAuth 2.1 (optional) - **Type Safety**: Full Pydantic v2 models - **In-Memory Testing**: Client testing with faster execution - **Configuration**: Environment variables, .env files ### Alfresco Integration Works with Alfresco Community (tested) and Enterprise editions ## 📋 Requirements - Python 3.10+ - Alfresco Content Services (Community or Enterprise) > **Note**: The `python-alfresco-api >= 1.1.1` dependency is automatically installed with `python-alfresco-mcp-server` ## 🛠️ Installation ### Install Python You need to have Python 3.10+ installed for the sections below. If not, download the latest 3.13.x version from: [Python.org Downloads](https://www.python.org/downloads/) ### UV/UVX Setup (Recommended) **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. ```bash # Install UV (provides both uv and uvx commands) # Windows powershell -c "irm https://astral.sh/uv/install.ps1 | iex" # macOS/Linux curl -LsSf https://astral.sh/uv/install.sh | sh # Or via pip if you prefer pip install uv # Verify installation (both commands should work) uv --version uvx --version ``` **UV Reference Links:** - **[UV Installation Guide](https://docs.astral.sh/uv/getting-started/installation/)** - Official installation instructions and platform-specific options - **[UV Documentation](https://docs.astral.sh/uv/)** - Complete UV documentation, guides, and advanced usage ### Option A: UVX - Modern Tool Runner (Recommended for Users) **UVX** is UV's tool runner - similar to pipx but faster and more modern. Automatically handles isolation and global availability: ```bash # Install python-alfresco-mcp-server with uvx (after UV/UVX setup above) uvx python-alfresco-mcp-server --help # This tests that installation worked - UVX automatically installs packages on first use! ``` **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. ### Option B: UV - Modern Package Manager (Recommended for Development) **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. ```bash # Install and run from PyPI (fastest for users) uv tool install python-alfresco-mcp-server uv tool run python-alfresco-mcp-server --help # Tests that installation worked # Or install from source (for development) git clone https://github.com/stevereiner/python-alfresco-mcp-server.git cd python-alfresco-mcp-server uv run python-alfresco-mcp-server --help # Tests that installation worked ``` ### Option C: Traditional Methods (pip and pipx) For traditional Python package management approaches, see the **[Installation with pip and pipx](./docs/install_with_pip_pipx.md)**. **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. ### Source Installation (For Development) For development or access to latest features: ```bash # 1. Clone the repository git clone https://github.com/stevereiner/python-alfresco-mcp-server.git cd python-alfresco-mcp-server # 2. UV handles everything automatically - run immediately! uv run python-alfresco-mcp-server --help # Tests that installation worked # Or install dependencies explicitly for development: uv sync # Basic dependencies uv sync --extra dev # With development tools uv sync --extra test # With testing tools uv sync --extra all # Everything ``` ### 4. Configure Alfresco Connection **Option 1: Environment Variables** ```bash # Linux/Mac export ALFRESCO_URL="http://localhost:8080" export ALFRESCO_USERNAME="admin" export ALFRESCO_PASSWORD="admin" export ALFRESCO_VERIFY_SSL="false" # Windows PowerShell $env:ALFRESCO_URL="http://localhost:8080" $env:ALFRESCO_USERNAME="admin" $env:ALFRESCO_PASSWORD="admin" $env:ALFRESCO_VERIFY_SSL="false" # Windows Command Prompt set ALFRESCO_URL=http://localhost:8080 set ALFRESCO_USERNAME=admin set ALFRESCO_PASSWORD=admin set ALFRESCO_VERIFY_SSL=false ``` **Option 2: .env file** (recommended - cross-platform): ```bash # Copy sample-dot-env.txt to .env and customize # Linux/macOS cp sample-dot-env.txt .env # Windows copy sample-dot-env.txt .env # Edit .env file with your settings ALFRESCO_URL=http://localhost:8080 ALFRESCO_USERNAME=admin ALFRESCO_PASSWORD=admin ALFRESCO_VERIFY_SSL=false ``` > **Note**: The `.env` file is not checked into git for security. Use `sample-dot-env.txt` as a template. 📖 **See [Configuration Guide](./docs/configuration_guide.md) for complete setup options** ## Alfresco Installation If you don't have an Alfresco server installed you can get a docker for the Community version from Github ```bash git clone https://github.com/Alfresco/acs-deployment.git ``` **Move to Docker Compose directory** ```bash cd acs-deployment/docker-compose ``` **Edit community-compose.yaml** - Note: you will likely need to comment out activemq ports other than 8161 ```bash ports: - "8161:8161" # Web Console #- "5672:5672" # AMQP #- "61616:61616" # OpenWire #- "61613:61613" # STOMP ``` **Start Alfresco with Docker Compose** ```bash docker-compose -f community-compose.yaml up ``` ## 🚀 Usage ### MCP Server Startup **With UVX (Recommended - Automatic isolation and global availability):** ```bash # Run MCP server with STDIO transport (default) uvx python-alfresco-mcp-server # HTTP transport for web services (matches MCP Inspector) uvx python-alfresco-mcp-server --transport http --host 127.0.0.1 --port 8003 # SSE transport for real-time streaming uvx python-alfresco-mcp-server --transport sse --host 127.0.0.1 --port 8001 ``` **With UV (For development or source installations):** ```bash # Run MCP server with STDIO transport (default) uv run python-alfresco-mcp-server # HTTP transport for web services (matches MCP Inspector) uv run python-alfresco-mcp-server --transport http --host 127.0.0.1 --port 8003 # SSE transport for real-time streaming uv run python-alfresco-mcp-server --transport sse --host 127.0.0.1 --port 8001 ``` **With Traditional Methods (pip/pipx):** See the **[Installation with pip and pipx](./docs/install_with_pip_pipx.md)** for pip and pipx usage instructions. ### MCP Client Setup and Use 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. #### 🤖 **Claude Desktop** for Windows (tested) and MacOS (not tested) 📖 **Complete Setup Guide**: **[Claude Desktop Setup Guide](./docs/claude_desktop_setup.md)** **📥 Download Claude Desktop (Free and Pro versions):** - **[Download Claude Desktop](https://claude.ai/download)** - Official Anthropic download page - Available for **Windows** and **macOS** only (no Linux version) - **Free tier** includes full MCP support and Claude Sonnet 4 access with limits, older Claude models (Claude Opus 4 only in Pro) **🔧 Claude Desktop Configuration by Installation Method:** The Claude Desktop configuration differs based on how you installed the MCP server: **1. UVX (Recommended - Modern tool runner):** ```json { "command": "uvx", "args": ["python-alfresco-mcp-server", "--transport", "stdio"] } ``` - **Sample Config Files:** - Windows: [`claude-desktop-config-uvx-windows.json`](./claude-desktop-config-uvx-windows.json) - macOS: [`claude-desktop-config-uvx-macos.json`](./claude-desktop-config-uvx-macos.json) - UVX automatically handles isolation and global availability - Fastest and most modern approach **2. UV (Development or source installations):** ```json { "command": "uv", "args": ["run", "python-alfresco-mcp-server", "--transport", "stdio"], "cwd": "C:\\path\\to\\python-alfresco-mcp-server" } ``` - **Sample Config Files:** - Windows: [`claude-desktop-config-uv-windows.json`](./claude-desktop-config-uv-windows.json) - macOS: [`claude-desktop-config-uv-macos.json`](./claude-desktop-config-uv-macos.json) - Uses `uv run` with `cwd` pointing to your **project directory** - UV automatically finds and uses the `.venv` from the project directory - Works for both source installations and after `uv tool install` **3. Traditional Methods (pipx/pip):** For traditional installation methods, see the **[Installation with pip and pipx](./docs/install_with_pip_pipx.md)** which covers: - **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) - **pip**: Manual venv path configuration **🔐 Tool-by-Tool Permission System:** 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: - **"Allow once"** - Approve this single tool use only - **"Always allow"** - Approve all future uses of this specific tool automatically (recommended for regular use) This tool-by-tool security feature ensures you maintain granular control over which external tools can be executed. > **🛡️ 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. **Using the Tools:** - **Chat naturally** about what you want to do with documents and search - **Mention "Alfresco"** to ensure the MCP server is used (e.g., "In Alfresco...") - **Use tool-related keywords** - mention something close to the tool name - **Follow-up prompts** will know the document from previous context **Example 1: Document Management** 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'" 2. Update properties: "Set the description property of this document to 'my desc'" 3. Check out the document 4. Cancel checkout 5. Check out again 6. Check in as a major version 7. Download the document 8. Upload a second document from "C:\1 sample files\cmispress.pdf" > **Note**: Claude will figure out to use base64 encoding for the first upload on a second try **Example 2: Search Operations** "With Alfresco please test all 3 search methods and CMIS query:" - Basic search for "txt" documents, return max 10 - Advanced search for documents created after 2024-01-01, return max 25 - Metadata search for documents where cm:title contains "test", limit to 50 - CMIS search to find all txt documents, limit to 50 **More Examples: Create Folder, Browse Folders, Get Repository Info** - "Create a folder called '25 07 25 01 18 am' in shared folder" - "List docs and folders in shared folder" *(will use -shared-)* - "Can you show me what's in my Alfresco home directory?" *(will use browse_repository -my-)* - "Get info on Alfresco" *(will use repository_info tool)* **Chat Box Buttons** - 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 - Click the **+ Button** → "Add from alfresco" for quick access to resources and prompts **Search and Analyze Prompt:** - Provides a form with query field for full-text search - Analysis types: **summary**, **detailed**, **trends**, or **compliance** - **Generates template text** to copy/paste into chat for editing **Repository Info Resource (and Tool):** - Provides status information in text format for viewing or copying **Examples:** - See [`prompts-for-claude.md`](./prompts-for-claude.md) for examples testing the tools #### 🔍 **MCP Inspector** (Development/Testing) > 📖 **Setup Guide**: Complete MCP Inspector setup and connection instructions in [MCP Inspector Setup Guide](./docs/mcp_inspector_setup.md) **📥 Install MCP Inspector:** - **Prerequisites**: Requires **Node.js 18+** - Download from **[nodejs.org](https://nodejs.org/)** - **Install Command**: `npm install -g @modelcontextprotocol/inspector` - **Or run directly**: `npx @modelcontextprotocol/inspector` (no global install needed) - **Purpose**: Web-based tool for testing MCP servers and individual tools with custom parameters **Working Method (Recommended):** **1. Start MCP Server with HTTP transport:** ```bash # With UVX (recommended) uvx python-alfresco-mcp-server --transport http --port 8003 # With UV (development) uv run python-alfresco-mcp-server --transport http --port 8003 # Traditional methods - see Traditional Installation Guide ``` **2. Start MCP Inspector with config:** **UVX Installation (Recommended):** ```bash # Start with stdio transport npx @modelcontextprotocol/inspector --config mcp-inspector-stdio-uvx-config.json --server python-alfresco-mcp-server # Start with http transport npx @modelcontextprotocol/inspector --config mcp-inspector-http-uvx-config.json --server python-alfresco-mcp-server ``` **UV Installation (Development):** ```bash # From project directory where config files exist npx @modelcontextprotocol/inspector --config mcp-inspector-stdio-uv-config.json --server python-alfresco-mcp-server # stdio transport npx @modelcontextprotocol/inspector --config mcp-inspector-http-uv-config.json --server python-alfresco-mcp-server # http transport ``` **Traditional Methods (pipx/pip):** See the **[Installation with pip and pipx](./docs/install_with_pip_pipx.md)** for pipx and pip configuration options. **3. Open browser with pre-filled token:** - Use the URL provided in the output (includes authentication token) - Example: `http://localhost:6274/?MCP_PROXY_AUTH_TOKEN=<token>` - This step applies to **all installation methods** (uv, uvx, pip, pipx) This approach avoids proxy connection errors and provides direct authentication. #### 🔧 **Other MCP Clients** For Cursor, Claude Code, and other MCP clients: 📖 **Complete Setup Guide**: **[Client Configuration Guide](./docs/client_configurations.md)** ## 🛠️ Available Tools (15 Total) ### 🔍 Search Tools (4) | Tool | Description | Parameters | |------|-------------|------------| | `search_content` | Search documents and folders | `query` (str), `max_results` (int), `node_type` (str) | | `advanced_search` | Advanced search with filters | `query` (str), `content_type` (str), `created_after` (str), etc. | | `search_by_metadata` | Search by metadata properties | `property_name` (str), `property_value` (str), `comparison` (str) | | `cmis_search` | CMIS SQL queries | `cmis_query` (str), `preset` (str), `max_results` (int) | ### 🛠️ Core Tools (11) | Tool | Description | Parameters | |------|-------------|------------| | `browse_repository` | Browse repository folders | `node_id` (str) | | `repository_info` | Get repository information | None | | `upload_document` | Upload new document | `filename` (str), `content_base64` (str), `parent_id` (str), `description` (str) | | `download_document` | Download document content | `node_id` (str), `save_to_disk` (bool) | | `create_folder` | Create new folder | `folder_name` (str), `parent_id` (str), `description` (str) | | `get_node_properties` | Get node metadata | `node_id` (str) | | `update_node_properties` | Update node metadata | `node_id` (str), `name` (str), `title` (str), `description` (str), `author` (str) | | `delete_node` | Delete document/folder | `node_id` (str), `permanent` (bool) | | `checkout_document` | Check out for editing | `node_id` (str), `download_for_editing` (bool) | | `checkin_document` | Check in after editing | `node_id` (str), `comment` (str), `major_version` (bool), `file_path` (str) | | `cancel_checkout` | Cancel checkout/unlock | `node_id` (str) | 📖 **See [API Reference](./docs/api_reference.md) for detailed tool documentation** ## 📊 Available Resources ### Repository Information | Resource | Description | Access Method | |----------|-------------|---------------| | `repository_info` | Get comprehensive repository information including version, edition, license details, installed modules, and system status | Available as both MCP resource and tool | The `repository_info` resource provides: - **Repository Details**: ID, edition (Community/Enterprise), version information - **License Information**: Issued/expires dates, remaining days, license holder, entitlements - **System Status**: Read-only mode, audit enabled, quick share, thumbnail generation - **Installed Modules**: Up to 10 modules with ID, title, version, and installation state 📖 **See [API Reference](./docs/api_reference.md) for detailed resource documentation** ## 🎯 Available Prompts ### Search and Analyze Prompt | Prompt | Description | Parameters | |--------|-------------|------------| | `search_and_analyze` | Interactive form for guided content search and analysis | `query` (search terms), `analysis_type` (summary/detailed/trends/compliance) | The Search and Analyze Prompt provides: - **Interactive Form**: User-friendly interface with query input field - **Analysis Options**: Choose from summary, detailed analysis, trends, or compliance reporting - **Template Generation**: Creates copyable template text for chat conversations - **Query Assistance**: Helps users structure effective search queries - **Multiple Search Types**: Integrates with all 4 search tools (content, advanced, metadata, CMIS) 📖 **See [API Reference](./docs/api_reference.md) for detailed prompt documentation** ## 🔧 Configuration Options | Environment Variable | Default | Description | |---------------------|---------|-------------| | `ALFRESCO_URL` | `http://localhost:8080` | Alfresco server URL | | `ALFRESCO_USERNAME` | `admin` | Username for authentication | | `ALFRESCO_PASSWORD` | `admin` | Password for authentication | | `ALFRESCO_VERIFY_SSL` | `false` | Verify SSL certificates | | `ALFRESCO_TIMEOUT` | `30` | Request timeout (seconds) | | `FASTAPI_HOST` | `localhost` | FastAPI host | | `FASTAPI_PORT` | `8000` | FastAPI port | | `LOG_LEVEL` | `INFO` | Logging level | | `MAX_FILE_SIZE` | `100000000` | Max upload size (bytes) | ⚙️ **See [Configuration Guide](./docs/configuration_guide.md) for deployment options** ## 🏗️ Architecture ``` ┌─────────────────────────────────────────────────────┐ │ MCP Clients │ │ Claude Desktop │ MCP Inspector │ Cursor │ Claude │ │ Code │ n8n │ LangFlow │ Custom MCP Client App │ └─────────────────┬───────────────────────────────────┘ │ stdio/HTTP/SSE ┌─────────────────▼───────────────────────────────────┐ │ FastMCP 2.0 MCP Server │ │ ┌─────────────┬─────────────┬─────────────────┐ │ │ │ MCP Tools │ MCP │ HTTP/SSE API │ │ │ │ (15 total) │ Resources │ │ │ │ │ │ MCP Prompts │ │ │ │ └─────────────┴─────────────┴─────────────────┘ │ └─────────────────┬───────────────────────────────────┘ │ python-alfresco-api ┌─────────────────▼───────────────────────────────────┐ │ Alfresco Content Services │ │ (Community/Enterprise Edition) │ └─────────────────────────────────────────────────────┘ ``` ## 🧪 Testing & Quality ### Test Suite Overview - **143 Total Tests**: **100% passed** - Coverage of all functionality - **122 Unit Tests**: **100% passed** - Core functionality validated with mocking (FastMCP 2.0, tools, coverage) - **21 Integration Tests**: **100% passed** - Live server testing (search, upload, download, document lifecycle) - **Integration Tests**: Automated end-to-end testing covering all core document lifecycle scenarios - **Performance Validated**: Search <1s, concurrent operations, resource access ### Coverage Report (Post-Cleanup) - **Overall Coverage**: 51% (1,829 statements tested) - **FastMCP 2.0 Core**: Well tested with comprehensive unit coverage - **Configuration Module**: 93% coverage - Fully tested - **Package Initialization**: 100% coverage (5/5 lines) - Complete - **Overall Project**: 51% coverage of comprehensive codebase ### Run Tests ```bash # Run full test suite pytest # Run with coverage report pytest --cov=alfresco_mcp_server --cov-report=term-missing # Run specific test categories pytest -m "unit" # Unit tests only pytest -m "fastmcp" # FastMCP 2.0 tests pytest -m "integration" # Integration tests (requires Alfresco) ``` 🧪 **See [Testing Guide](./docs/testing_guide.md) for detailed testing strategies** ### 🧪 Test Categories and Execution The project includes **4 levels of testing**: 1. **📋 Unit Tests** (122 tests) - Fast, mocked, isolated component testing 2. **🔗 Integration Tests** (21 tests) - Live Alfresco server testing 3. **📝 Comprehensive Tests** - Automated core document lifecycle scenarios 4. **📊 Coverage Tests** - Edge cases and error path coverage ## 🧪 Development ### Setup Development Environment ```bash git clone <repository> cd python-alfresco-mcp-server # UV handles everything automatically - no manual venv setup needed! uv sync --extra dev # Install with development tools uv sync --extra test # With testing tools uv sync --extra all # Everything # Run immediately to test that installation worked uv run python-alfresco-mcp-server --help # Install python-alfresco-api for local development (if needed) uv add --editable ../python-alfresco-api ``` **Traditional Development Setup:** See the **[Installation with pip and pipx](./docs/install_with_pip_pipx.md)** for pip-based development setup. ## 💡 Examples ### Real-world implementation patterns from beginner to enterprise: - **[💡 Examples Library](./examples/README.md)** - Complete navigation and learning paths - **[🏃 Quick Start](./examples/quick_start.py)** - 5-minute introduction and basic operations - **[📋 Document Lifecycle](./examples/document_lifecycle.py)** - Complete process demonstration - **[🚀 Transport Examples](./examples/transport_examples.py)** - STDIO, HTTP, and SSE protocols - **[⚡ Batch Operations](./examples/batch_operations.py)** - High-performance bulk processing - **[🛡️ Error Handling](./examples/error_handling.py)** - Resilience patterns - **[📊 Examples Summary](./examples/examples_summary.md)** - Overview and statistics ## 🤝 Contributing 1. Fork the repository 2. Create a feature branch (`git checkout -b feature/new-feature`) 3. Commit your changes (`git commit -m 'Add new feature'`) 4. Push to the branch (`git push origin feature/new-feature`) 5. Open a Pull Request ## 📄 License This project is licensed under the Apache 2.0 License - see the [LICENSE](LICENSE) file for details. ## 🔗 Related Projects and References - **[Hyland Alfresco](https://www.hyland.com/en/solutions/products/alfresco-platform)** - Content management platform (Enterprise and Community editions) - **[python-alfresco-api](https://github.com/stevereiner/python-alfresco-api)** - The underlying Alfresco API library - **[FastMCP 2.0](https://github.com/jlowin/FastMCP)** - Modern framework for building MCP servers - **[FastMCP Documentation](https://gofastmcp.com/)** - Complete FastMCP framework documentation and guides - **[Model Context Protocol](https://modelcontextprotocol.io)** - Official MCP specification and documentation - **[Playbooks.com MCP List](https://playbooks.com/mcp/stevereiner-alfresco-content-services)** - Python Alfresco MCP Server listing - **[PulseMCP.com MCP List](https://www.pulsemcp.com/servers/stevereiner-alfresco-content-services)** - Python Alfresco MCP Server listing - **[Glama.ai MCP List](https://glama.ai/mcp/servers?query=alfresco)** - Glama Alfresco list including Python Alfresco MCP Server listing - **[MCPMarket.com MCP List](https://mcpmarket.com/server/alfresco)** - Python Alfresco MCP Server listing ## 🙋♂️ Support - 📚 **Documentation**: Complete guides in [`./docs/`](./docs/README.md) - 💡 **Examples**: Implementation patterns in [`./examples/`](./examples/README.md) - 🧪 **Testing**: Quality assurance in [`./docs/testing_guide.md`](./docs/testing_guide.md) - 🔍 **MCP Inspector**: Development testing in [`./docs/mcp_inspector_setup.md`](./docs/mcp_inspector_setup.md) - 🛠️ **Troubleshooting**: Problem solving in [`./docs/troubleshooting.md`](./docs/troubleshooting.md) - 🐛 **Issues**: [GitHub Issues](https://github.com/stevereiner/python-alfresco-mcp-server/issues) --- **🚀 MCP server built with [python-alfresco-api](https://github.com/stevereiner/python-alfresco-api) and [FastMCP 2.0](https://github.com/paulinephelan/FastMCP)** ``` -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- ```python ``` -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- ```json { "svn.ignoreMissingSvnWarning": true } ``` -------------------------------------------------------------------------------- /alfresco_mcp_server/prompts/__init__.py: -------------------------------------------------------------------------------- ```python # Prompts module from . import search_and_analyze __all__ = [ "search_and_analyze", ] ``` -------------------------------------------------------------------------------- /alfresco_mcp_server/resources/__init__.py: -------------------------------------------------------------------------------- ```python # Resources module from . import repository_resources __all__ = [ "repository_resources", ] ``` -------------------------------------------------------------------------------- /alfresco_mcp_server/tools/__init__.py: -------------------------------------------------------------------------------- ```python """ Tools module for Alfresco MCP Server. Contains core and search tools organized hierarchically. """ # Import subdirectories from . import core from . import search __all__ = [ "core", "search", ] ``` -------------------------------------------------------------------------------- /alfresco_mcp_server/tools/search/__init__.py: -------------------------------------------------------------------------------- ```python # Search tools module from . import ( advanced_search, cmis_search, search_by_metadata, search_content, ) __all__ = [ "advanced_search", "cmis_search", "search_by_metadata", "search_content", ] ``` -------------------------------------------------------------------------------- /mcp-inspector-stdio-pipx-config.json: -------------------------------------------------------------------------------- ```json { "mcpServers": { "python-alfresco-mcp-server": { "command": "python-alfresco-mcp-server", "args": ["--transport", "stdio"], "env": { "ALFRESCO_URL": "http://localhost:8080", "ALFRESCO_USERNAME": "admin", "ALFRESCO_PASSWORD": "admin" } } } } ``` -------------------------------------------------------------------------------- /config.yaml: -------------------------------------------------------------------------------- ```yaml # Alfresco MCP Server Configuration alfresco: base_url: "http://localhost:8080" # Base URL only - API paths added by individual clients username: "admin" password: "admin" verify_ssl: false timeout: 30 # MCP Server Configuration server: name: "python-alfresco-mcp-server" version: "1.1.0" ``` -------------------------------------------------------------------------------- /mcp-inspector-http-pipx-config.json: -------------------------------------------------------------------------------- ```json { "mcpServers": { "python-alfresco-mcp-server": { "command": "python-alfresco-mcp-server", "args": ["--transport", "http", "--port", "8003"], "env": { "ALFRESCO_URL": "http://localhost:8080", "ALFRESCO_USERNAME": "admin", "ALFRESCO_PASSWORD": "admin" } } } } ``` -------------------------------------------------------------------------------- /claude-desktop-config-pipx-macos.json: -------------------------------------------------------------------------------- ```json { "mcpServers": { "python-alfresco-mcp-server": { "command": "python-alfresco-mcp-server", "args": [ "--transport", "stdio" ], "env": { "ALFRESCO_URL": "http://localhost:8080", "ALFRESCO_USERNAME": "admin", "ALFRESCO_PASSWORD": "admin" } } } } ``` -------------------------------------------------------------------------------- /claude-desktop-config-uvx-macos.json: -------------------------------------------------------------------------------- ```json { "mcpServers": { "python-alfresco-mcp-server": { "command": "uvx", "args": [ "python-alfresco-mcp-server", "--transport", "stdio" ], "env": { "ALFRESCO_URL": "http://localhost:8080", "ALFRESCO_USERNAME": "admin", "ALFRESCO_PASSWORD": "admin" } } } } ``` -------------------------------------------------------------------------------- /mcp-inspector-stdio-uvx-config.json: -------------------------------------------------------------------------------- ```json { "mcpServers": { "python-alfresco-mcp-server": { "command": "uvx", "args": [ "python-alfresco-mcp-server", "--transport", "stdio" ], "env": { "ALFRESCO_URL": "http://localhost:8080", "ALFRESCO_USERNAME": "admin", "ALFRESCO_PASSWORD": "admin" } } } } ``` -------------------------------------------------------------------------------- /claude-desktop-config-pipx-windows.json: -------------------------------------------------------------------------------- ```json { "mcpServers": { "python-alfresco-mcp-server": { "command": "python-alfresco-mcp-server", "args": [ "--transport", "stdio" ], "env": { "ALFRESCO_URL": "http://localhost:8080", "ALFRESCO_USERNAME": "admin", "ALFRESCO_PASSWORD": "admin", "PYTHONIOENCODING": "utf-8", "PYTHONLEGACYWINDOWSSTDIO": "1" } } } } ``` -------------------------------------------------------------------------------- /claude-desktop-config-uv-macos.json: -------------------------------------------------------------------------------- ```json { "mcpServers": { "python-alfresco-mcp-server": { "command": "uv", "args": [ "run", "python-alfresco-mcp-server", "--transport", "stdio" ], "cwd": "/path/to/python-alfresco-mcp-server", "env": { "ALFRESCO_URL": "http://localhost:8080", "ALFRESCO_USERNAME": "admin", "ALFRESCO_PASSWORD": "admin" } } } } ``` -------------------------------------------------------------------------------- /mcp-inspector-http-uvx-config.json: -------------------------------------------------------------------------------- ```json { "mcpServers": { "python-alfresco-mcp-server": { "command": "uvx", "args": [ "python-alfresco-mcp-server", "--transport", "http", "--host", "127.0.0.1", "--port", "8003" ], "env": { "ALFRESCO_URL": "http://localhost:8080", "ALFRESCO_USERNAME": "admin", "ALFRESCO_PASSWORD": "admin" } } } } ``` -------------------------------------------------------------------------------- /claude-desktop-config-uvx-windows.json: -------------------------------------------------------------------------------- ```json { "mcpServers": { "python-alfresco-mcp-server": { "command": "uvx", "args": [ "python-alfresco-mcp-server", "--transport", "stdio" ], "env": { "ALFRESCO_URL": "http://localhost:8080", "ALFRESCO_USERNAME": "admin", "ALFRESCO_PASSWORD": "admin", "PYTHONIOENCODING": "utf-8", "PYTHONLEGACYWINDOWSSTDIO": "1" } } } } ``` -------------------------------------------------------------------------------- /.vscode/mcp.json: -------------------------------------------------------------------------------- ```json "servers": { "my-mcp-server": { "type": "http", "command": "python", "args": ["${workspaceFolder}/run_server.py", "--transport", "http", "--port", "8003", "--host", "127.0.0.1"], "dev": { "watch": "${workspaceFolder}/alfresco_mcp_server/**/*.py", "debug": { "type": "python" } }, "env": { "ALFRESCO_URL": "http://localhost:8080", "ALFRESCO_USERNAME": "admin", "ALFRESCO_PASSWORD": "admin" } } } ``` -------------------------------------------------------------------------------- /mcp-inspector-stdio-uv-config.json: -------------------------------------------------------------------------------- ```json { "mcpServers": { "python-alfresco-mcp-server": { "command": "uv", "args": [ "run", "python-alfresco-mcp-server", "--transport", "stdio" ], "cwd": "C:\\newdev3\\python-alfresco-mcp-server", "env": { "ALFRESCO_URL": "http://localhost:8080", "ALFRESCO_USERNAME": "admin", "ALFRESCO_PASSWORD": "admin", "PYTHONIOENCODING": "utf-8", "PYTHONLEGACYWINDOWSSTDIO": "1" } } } } ``` -------------------------------------------------------------------------------- /claude-desktop-config-uv-windows.json: -------------------------------------------------------------------------------- ```json { "mcpServers": { "python-alfresco-mcp-server": { "command": "uv", "args": [ "run", "python-alfresco-mcp-server", "--transport", "stdio" ], "cwd": "C:\\path\\to\\python-alfresco-mcp-server", "env": { "ALFRESCO_URL": "http://localhost:8080", "ALFRESCO_USERNAME": "admin", "ALFRESCO_PASSWORD": "admin", "PYTHONIOENCODING": "utf-8", "PYTHONLEGACYWINDOWSSTDIO": "1" } } } } ``` -------------------------------------------------------------------------------- /alfresco_mcp_server/tools/core/__init__.py: -------------------------------------------------------------------------------- ```python # Core tools module from . import ( browse_repository, cancel_checkout, checkin_document, checkout_document, create_folder, delete_node, download_document, get_node_properties, update_node_properties, upload_document, ) __all__ = [ "browse_repository", "cancel_checkout", "checkin_document", "checkout_document", "create_folder", "delete_node", "download_document", "get_node_properties", "update_node_properties", "upload_document", ] ``` -------------------------------------------------------------------------------- /mcp-inspector-http-uv-config.json: -------------------------------------------------------------------------------- ```json { "mcpServers": { "python-alfresco-mcp-server": { "command": "uv", "args": [ "run", "python-alfresco-mcp-server", "--transport", "http", "--host", "127.0.0.1", "--port", "8003" ], "cwd": "C:\\newdev3\\python-alfresco-mcp-server", "env": { "ALFRESCO_URL": "http://localhost:8080", "ALFRESCO_USERNAME": "admin", "ALFRESCO_PASSWORD": "admin", "PYTHONIOENCODING": "utf-8", "PYTHONLEGACYWINDOWSSTDIO": "1" } } } } ``` -------------------------------------------------------------------------------- /alfresco_mcp_server/__init__.py: -------------------------------------------------------------------------------- ```python """ MCP Server for Alfresco Model Context Protocol server for Alfresco Content Services. Provides AI-native access to Alfresco content management and search operations. """ __version__ = "1.1.0" __title__ = "MCP Server for Alfresco" __description__ = "Model Context Protocol server for Alfresco Content Services" from .config import AlfrescoConfig, load_config # Import subpackages to make them available from . import tools from . import resources from . import prompts from . import utils __all__ = [ "AlfrescoConfig", "load_config", "tools", "resources", "prompts", "utils", ] ``` -------------------------------------------------------------------------------- /alfresco_mcp_server/utils/__init__.py: -------------------------------------------------------------------------------- ```python # Utilities module for Alfresco MCP Server from .connection import ( get_alfresco_config, get_connection, get_search_utils, get_node_utils, ) from .file_type_analysis import ( detect_file_extension_from_content, analyze_content_type, ) from .json_utils import ( make_json_safe, safe_format_output, escape_unicode_for_json, ) __all__ = [ # Connection utilities "get_alfresco_config", "get_connection", "get_search_utils", "get_node_utils", # File type analysis "detect_file_extension_from_content", "analyze_content_type", # JSON utilities "make_json_safe", "safe_format_output", "escape_unicode_for_json", ] ``` -------------------------------------------------------------------------------- /sample-dot-env.txt: -------------------------------------------------------------------------------- ``` # Alfresco MCP Server Configuration # Copy this file to .env and customize for your environment # The .env file will be ignored by git for security # === REQUIRED: Alfresco Connection === ALFRESCO_URL=http://localhost:8080 ALFRESCO_USERNAME=admin ALFRESCO_PASSWORD=admin # === OPTIONAL: Connection Settings === ALFRESCO_VERIFY_SSL=false ALFRESCO_TIMEOUT=30 # === OPTIONAL: Server Settings === LOG_LEVEL=INFO MAX_FILE_SIZE=100000000 # === OPTIONAL: HTTP Transport Settings === FASTAPI_HOST=localhost FASTAPI_PORT=8000 # === NOTES === # - Environment variables take precedence over defaults # - python-alfresco-api may have its own configuration (check its docs) # - For production, use environment variables or secure secret management # - Boolean values: true/false (case insensitive) # - File size in bytes (100000000 = 100MB) ``` -------------------------------------------------------------------------------- /tests/mcp_specific/START_HTTP_SERVER.md: -------------------------------------------------------------------------------- ```markdown # Starting MCP Server for Testing ## For MCP Inspector Testing (HTTP) ### Option 1: Using PowerShell ```powershell cd C:\newdev3\python-alfresco-mcp-server .\venv_clean\Scripts\activate fastmcp run alfresco_mcp_server.fastmcp_server --host localhost --port 8003 ``` ### Option 2: Using Command Prompt ```cmd cd C:\newdev3\python-alfresco-mcp-server venv_clean\Scripts\activate.bat fastmcp run alfresco_mcp_server.fastmcp_server --host localhost --port 8003 ``` ## For Claude Desktop Testing (STDIO) Claude Desktop uses the stdio transport via the config file: `claude-desktop-config.json` ## Testing Server Status After starting the HTTP server, run: ```bash python tests/mcp_specific/test_server_status.py ``` ## MCP Inspector Setup 1. Start HTTP server on port 8003 (see above) 2. Open MCP Inspector in browser 3. Use URL: `http://localhost:8003` 4. **Note**: MCP Inspector will show warning about "risky auth" and include token in URL - this is normal for development testing ## Important Notes - Make sure you're using `venv_clean` which has all the correct dependencies - The FastMCP server will show import success messages when starting correctly - If you see pydantic import errors, make sure you're using the correct virtual environment ``` -------------------------------------------------------------------------------- /run_server.py: -------------------------------------------------------------------------------- ```python #!/usr/bin/env python3 """ Simple wrapper to run the Alfresco MCP Server """ import sys import os # Add the current directory to Python path sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) # Import and run the server from alfresco_mcp_server.fastmcp_server import mcp if __name__ == "__main__": import argparse parser = argparse.ArgumentParser(description="Run Alfresco MCP Server") parser.add_argument("--port", type=int, default=8003, help="Port to run server on") parser.add_argument("--host", type=str, default="127.0.0.1", help="Host to bind to") parser.add_argument("--transport", type=str, default="http", choices=["stdio", "http", "sse"], help="Transport method to use") args = parser.parse_args() if args.transport == "stdio": print(">> Starting Alfresco MCP Server with stdio transport") mcp.run(transport="stdio") elif args.transport == "http": print(f">> Starting Alfresco MCP Server with HTTP transport on {args.host}:{args.port}") mcp.run(transport="http", host=args.host, port=args.port) else: print(f">> Starting Alfresco MCP Server with SSE transport on {args.host}:{args.port}") mcp.run(transport="sse", host=args.host, port=args.port) ``` -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- ``` [tool:pytest] # Pytest configuration for Alfresco MCP Server minversion = 6.0 testpaths = tests python_files = test_*.py python_classes = Test* python_functions = test_* # Add options addopts = --strict-markers --strict-config --verbose --tb=short --cov=alfresco_mcp_server --cov-report=html:htmlcov --cov-report=term-missing --cov-report=xml --cov-branch --cov-fail-under=85 # Test markers markers = unit: marks tests as unit tests (fast, mocked dependencies) integration: marks tests as integration tests (requires live Alfresco) slow: marks tests as slow running performance: marks tests as performance benchmarks # Async test configuration asyncio_mode = auto # Filter warnings filterwarnings = ignore::DeprecationWarning ignore::PendingDeprecationWarning ignore::pytest.PytestUnraisableExceptionWarning # Coverage configuration [tool:coverage:run] source = alfresco_mcp_server omit = tests/* venv/* */site-packages/* */test_* setup.py [tool:coverage:report] exclude_lines = pragma: no cover def __repr__ if self.debug: if settings.DEBUG raise AssertionError raise NotImplementedError if 0: if __name__ == .__main__.: class .*\bProtocol\): @(abc\.)?abstractmethod [tool:coverage:html] directory = htmlcov [tool:coverage:xml] output = coverage.xml ``` -------------------------------------------------------------------------------- /alfresco_mcp_server/prompts/search_and_analyze.py: -------------------------------------------------------------------------------- ```python """ Search and analyze prompt for Alfresco MCP Server. Self-contained prompt for generating comprehensive search and analysis workflows. """ async def search_and_analyze_impl(query: str, analysis_type: str = "summary") -> str: """Generate comprehensive search and analysis prompts for Alfresco documents. Args: query: Search query for documents analysis_type: Type of analysis (summary, detailed, trends, compliance) Returns: Formatted prompt for document analysis workflow """ base_prompt = f"""**Alfresco Document Analysis Request** Please search for documents matching "{query}" and provide a {analysis_type} analysis. **Step 1: Search** Use the `search_content` tool to find relevant documents. **Step 2: Analysis** Based on the search results, provide: """ if analysis_type == "summary": base_prompt += """ - Document count and types - Key themes and topics - Most relevant documents - Quick insights """ elif analysis_type == "detailed": base_prompt += """ - Comprehensive document inventory - Metadata analysis (dates, authors, sizes) - Content categorization - Compliance status - Recommended actions - Related search suggestions """ elif analysis_type == "trends": base_prompt += """ - Temporal patterns (creation/modification dates) - Document lifecycle analysis - Usage and access patterns - Version history insights - Storage optimization recommendations """ elif analysis_type == "compliance": base_prompt += """ - Document retention analysis - Security classification review - Access permissions audit - Regulatory compliance status - Risk assessment - Remediation recommendations """ base_prompt += f""" **Step 3: Recommendations** Provide actionable insights and next steps based on the {analysis_type} analysis. """ return base_prompt ``` -------------------------------------------------------------------------------- /scripts/test.bat: -------------------------------------------------------------------------------- ``` @echo off REM Windows batch script for running Alfresco MCP Server tests echo ================================ echo Alfresco MCP Server Test Suite echo ================================ REM Check if Python is available python --version >nul 2>&1 if %errorlevel% neq 0 ( echo Error: Python is not installed or not in PATH exit /b 1 ) REM Set environment variables for Alfresco set ALFRESCO_URL=http://localhost:8080 set ALFRESCO_USERNAME=admin set ALFRESCO_PASSWORD=admin set ALFRESCO_VERIFY_SSL=false REM Parse command line arguments set MODE=unit set INSTALL_DEPS=false :parse_args if "%1"=="--help" goto :show_help if "%1"=="--unit" set MODE=unit if "%1"=="--integration" set MODE=integration if "%1"=="--performance" set MODE=performance if "%1"=="--coverage" set MODE=coverage if "%1"=="--all" set MODE=all if "%1"=="--lint" set MODE=lint if "%1"=="--install-deps" set INSTALL_DEPS=true shift if not "%1"=="" goto :parse_args REM Install dependencies if requested if "%INSTALL_DEPS%"=="true" ( echo Installing test dependencies... python -m pip install pytest pytest-asyncio pytest-cov pytest-mock coverage httpx if %errorlevel% neq 0 ( echo Error: Failed to install dependencies exit /b 1 ) ) REM Run the test runner echo Running tests in %MODE% mode... python scripts/run_tests.py --mode %MODE% if %errorlevel% neq 0 ( echo Tests failed! exit /b 1 ) echo. echo ================================ echo Tests completed successfully! echo ================================ echo Coverage report: htmlcov/index.html exit /b 0 :show_help echo Usage: test.bat [OPTIONS] echo. echo Options: echo --unit Run unit tests (default) echo --integration Run integration tests (requires Alfresco) echo --performance Run performance tests echo --coverage Run coverage analysis echo --all Run all tests echo --lint Run code linting echo --install-deps Install test dependencies echo --help Show this help exit /b 0 ``` -------------------------------------------------------------------------------- /tests/mcp_specific/MCP_INSPECTOR_CONNECTION.md: -------------------------------------------------------------------------------- ```markdown # Connecting MCP Inspector to Your Running Server ## 🔍 Current Setup - **MCP Inspector**: Running on port 6274 ✅ - **Your MCP Server**: Running on port 8003 ✅ - **URL Pattern**: `http://localhost:6274/?MCP_PROXY_AUTH_TOKEN=...#tools` ## 🔗 How to Connect ### Step 1: MCP Inspector Server Connection In your MCP Inspector interface: 1. **Look for "Add Server" or "Connect Server"** button 2. **Enter your server URL**: `http://localhost:8003` 3. **Transport**: Select "HTTP" 4. **No additional auth needed** - it's local ### Step 2: Expected URL Structure Once connected, your URL will be: ``` http://localhost:6274/?MCP_PROXY_AUTH_TOKEN=<token>&server=http://localhost:8003#tools ``` ### Step 3: What You Should See - ✅ **Server**: "Alfresco Document Management Server" - ✅ **15 Tools Available**: - search_content - search_by_metadata - advanced_search - cmis_search - browse_repository - repository_info - upload_document - download_document - checkout_document - checkin_document - cancel_checkout - delete_node - get_node_properties - update_node_properties - create_folder ## 🚨 If Connection Fails ### Check Server Status ```bash python tests/mcp_specific/test_server_status.py ``` ### Check Both Ports ```bash netstat -an | findstr ":6274\|:8003" ``` ### Restart If Needed ```bash # Stop your MCP server taskkill /PID 57420 /F # Restart with explicit logging fastmcp run alfresco_mcp_server/fastmcp_server.py --transport http --host localhost --port 8003 --log-level DEBUG ``` ## 🎯 Quick Test 1. **Open your MCP Inspector**: `http://localhost:6274/?MCP_PROXY_AUTH_TOKEN=4b4a857ba6eecd2ac7029bf0c9daaf97e311b7f5cee35dec3a25ddf739ffd66b#tools` 2. **Add server**: `http://localhost:8003` 3. **Test a tool**: Try "browse_repository" or "search_content" ## 🔒 The "Risky Auth" Token The `MCP_PROXY_AUTH_TOKEN=4b4a857ba6eecd2ac7029bf0c9daaf97e311b7f5cee35dec3a25ddf739ffd66b` is: - ✅ **Normal**: Generated by MCP Inspector for security - ✅ **Local only**: Not transmitted anywhere - ✅ **Safe**: Standard MCP development practice ``` -------------------------------------------------------------------------------- /tests/mcp_specific/START_MCP_INSPECTOR.md: -------------------------------------------------------------------------------- ```markdown # How to Start MCP Inspector ## 🚀 Quick Start Methods ### Method 1: Using Config File (Recommended - Avoids Proxy Errors) ```bash # 1. Start your MCP server first python -m alfresco_mcp_server.fastmcp_server --transport http --port 8003 # 2. Start MCP Inspector with pre-configured server npx @modelcontextprotocol/inspector --config mcp-inspector-http-config.json --server python-alfresco-mcp-server ``` **Expected Output:** ``` Starting MCP inspector... ⚙️ Proxy server listening on 127.0.0.1:6277 🔑 Session token: d7a62ab6e032eefe5d85e807c50e13b9fffcd12badbf8bbc3377659c0be4fa8d Use this token to authenticate requests or set DANGEROUSLY_OMIT_AUTH=true to disable auth 🔗 Open inspector with token pre-filled: http://localhost:6274/?MCP_PROXY_AUTH_TOKEN=d7a62ab6e032eefe5d85e807c50e13b9fffcd12badbf8bbc3377659c0be4fa8d ``` ### Method 2: Using npx (Basic) ```bash npx @modelcontextprotocol/inspector ``` ### Method 2: Using npm (if globally installed) ```bash npm run dev # or npm start ``` ### Method 3: Direct GitHub (if you have it locally) ```bash # If you have the repo cloned cd path/to/mcp-inspector npm run dev ``` ### Method 4: Using the pre-built package ```bash npx @modelcontextprotocol/inspector@latest ``` ## 📍 Expected Output When MCP Inspector starts, you should see: ``` > Local: http://localhost:6274 > Network: http://192.168.x.x:6274 ``` ## 🔗 After Starting ### 1. Open Browser Navigate to: `http://localhost:6274` ### 2. Connect to Your Server - **Click "Add Server"** or server connection field - **Enter**: `http://localhost:8003` - **Transport**: HTTP - **Connect** ### 3. Expected URL ``` http://localhost:6274/?MCP_PROXY_AUTH_TOKEN=<new-token>&server=http://localhost:8003#tools ``` ## 🛠️ Troubleshooting ### If Port 6274 is Busy ```bash # MCP Inspector will auto-find next available port # Check what port it actually uses in the startup message ``` ### If npx Fails ```bash # Update npm/npx npm install -g npm@latest # Try with explicit version npx @modelcontextprotocol/inspector@latest ``` ### Check if Already Running ```bash netstat -an | findstr :6274 # or check other common ports netstat -an | findstr ":3000\|:5173\|:6274" ``` ## 🎯 Quick Test Command ```bash # This should start MCP Inspector on port 6274 npx @modelcontextprotocol/inspector ``` Then open `http://localhost:6274` and connect to your server at `http://localhost:8003`! ``` -------------------------------------------------------------------------------- /tests/mcp_specific/TESTING_INSTRUCTIONS.md: -------------------------------------------------------------------------------- ```markdown # MCP Server Testing Instructions ## 🟢 Server Status: RUNNING - **Process**: Python PID 57420 ✅ - **Port**: 8003 LISTENING ✅ - **URL**: `http://localhost:8003/mcp/` ✅ ## 🌐 MCP Inspector Testing ### Why Browser Shows Error The error `"Not Acceptable: Client must accept text/event-stream"` is **NORMAL**: - MCP servers use Server-Sent Events (SSE) format - Regular browsers can't handle MCP protocol - You need the MCP Inspector tool ### Setup MCP Inspector 1. **Download**: [MCP Inspector](https://github.com/modelcontextprotocol/inspector) 2. **Install**: Follow their setup instructions 3. **Connect to**: `http://localhost:8003` 4. **No token required** - it's local development ### Expected in MCP Inspector - ✅ Server connection successful - ✅ 10 tools available (search_content, browse_repository, etc.) - ✅ "Risky auth" warning - NORMAL for development - ✅ Can test individual tools ## 💬 Claude Desktop Testing (Easier) ### Setup 1. **Config ready**: `claude-desktop-config.json` ✅ 2. **Restart**: Claude Desktop application 3. **Ready**: MCP tools appear in conversation ### Your Config File ```json { "mcpServers": { "alfresco": { "command": "C:\\newdev3\\python-alfresco-mcp-server\\venv_clean\\Scripts\\python.exe", "args": [ "C:\\newdev3\\python-alfresco-mcp-server\\alfresco_mcp_server\\fastmcp_server.py" ], "env": { "ALFRESCO_URL": "http://localhost:8080", "ALFRESCO_USERNAME": "admin", "ALFRESCO_PASSWORD": "admin" } } } } ``` ### Testing in Claude Ask Claude to: - Search for content: "Search for documents about 'project'" - Browse repository: "Show me the repository structure" - Get node info: "Get details about the root folder" ## 🔒 Security Notes ### LOCAL ONLY - No Central Registration - ✅ Server runs on `localhost:8003` only - ✅ Not accessible from internet - ✅ Not registered in any central directory - ✅ Private development server ### If You're Concerned About Name The server name "Alfresco Document Management Server" is just a display name: - ✅ Not registered anywhere - ✅ Only visible to connected clients - ✅ Can be changed in code if desired ## 🧪 Quick Test Commands ### Test Server Status ```bash python tests/mcp_specific/test_server_status.py ``` ### Test Server Tools ```bash python tests/mcp_specific/test_server_fixed.py ``` ### Stop Server ```bash # Find and kill process 57420 taskkill /PID 57420 /F ``` ## 🎯 Recommended Testing Order 1. **Start with Claude Desktop** (easiest to test) 2. **Then try MCP Inspector** (if you want detailed tool testing) 3. **Both use the same server** - no conflicts ``` -------------------------------------------------------------------------------- /tests/mcp_specific/test_http_server.ps1: -------------------------------------------------------------------------------- ``` # Test script for Python Alfresco MCP Server HTTP transport Write-Host "🌐 Testing Python Alfresco MCP Server - HTTP Transport" -ForegroundColor Green Write-Host "=" * 60 $baseUrl = "http://127.0.0.1:8001" # Wait for server to start Write-Host "⏳ Waiting for server to start..." -ForegroundColor Yellow Start-Sleep -Seconds 3 try { # Test 1: Server health/info Write-Host "`n🏥 Testing Server Health..." -ForegroundColor Cyan try { $response = Invoke-WebRequest -Uri "$baseUrl/" -Method GET -TimeoutSec 10 Write-Host "✅ Server is responding" -ForegroundColor Green Write-Host " Status: $($response.StatusCode)" -ForegroundColor Gray } catch { Write-Host "ℹ️ Root endpoint: $($_.Exception.Message)" -ForegroundColor Yellow } # Test 2: List tools (if available) Write-Host "`n🔧 Testing Tools Endpoint..." -ForegroundColor Cyan try { $response = Invoke-WebRequest -Uri "$baseUrl/tools" -Method GET -TimeoutSec 10 Write-Host "✅ Tools endpoint accessible" -ForegroundColor Green $content = $response.Content | ConvertFrom-Json Write-Host " Tools found: $($content.tools.Count)" -ForegroundColor Gray } catch { Write-Host "ℹ️ Tools endpoint: $($_.Exception.Message)" -ForegroundColor Yellow } # Test 3: List resources (if available) Write-Host "`n📦 Testing Resources Endpoint..." -ForegroundColor Cyan try { $response = Invoke-WebRequest -Uri "$baseUrl/resources" -Method GET -TimeoutSec 10 Write-Host "✅ Resources endpoint accessible" -ForegroundColor Green } catch { Write-Host "ℹ️ Resources endpoint: $($_.Exception.Message)" -ForegroundColor Yellow } # Test 4: Call a tool (if supported) Write-Host "`n🔍 Testing Tool Call..." -ForegroundColor Cyan try { $body = @{ query = "test" max_results = 5 } | ConvertTo-Json $response = Invoke-WebRequest -Uri "$baseUrl/tools/search_content" -Method POST -Body $body -ContentType "application/json" -TimeoutSec 10 Write-Host "✅ Tool call successful" -ForegroundColor Green Write-Host " Response length: $($response.Content.Length) chars" -ForegroundColor Gray } catch { Write-Host "ℹ️ Tool call: $($_.Exception.Message)" -ForegroundColor Yellow } Write-Host "`n🎉 HTTP testing completed!" -ForegroundColor Green } catch { Write-Host "❌ HTTP testing failed: $($_.Exception.Message)" -ForegroundColor Red } Write-Host "`n📖 Next steps:" -ForegroundColor Cyan Write-Host "1. Try MCP Inspector: npx @modelcontextprotocol/inspector" -ForegroundColor Gray Write-Host "2. Test STDIO transport: python test_with_mcp_client.py" -ForegroundColor Gray Write-Host "3. Run existing test suite: python -m pytest tests/" -ForegroundColor Gray ``` -------------------------------------------------------------------------------- /alfresco_mcp_server/tools/core/delete_node.py: -------------------------------------------------------------------------------- ```python """ Delete node tool for Alfresco MCP Server. Self-contained tool for deleting documents or folders from Alfresco repository. """ import logging from typing import Optional from fastmcp import Context from ...utils.connection import ensure_connection from ...utils.json_utils import safe_format_output logger = logging.getLogger(__name__) async def delete_node_impl( node_id: str, permanent: bool = False, ctx: Optional[Context] = None ) -> str: """Delete a document or folder from Alfresco. Args: node_id: Node ID to delete permanent: Whether to permanently delete (bypass trash) ctx: MCP context for progress reporting Returns: Deletion confirmation """ if ctx: delete_type = "permanently delete" if permanent else "move to trash" await ctx.info(f"Preparing to {delete_type}: {node_id}") await ctx.info("Validating deletion request...") await ctx.report_progress(0.1) if not node_id.strip(): return safe_format_output("❌ Error: node_id is required") try: await ensure_connection() from ...utils.connection import get_client_factory # Get client factory and create core client (working pattern from test) client_factory = await get_client_factory() core_client = client_factory.create_core_client() # Clean the node ID (remove any URL encoding or extra characters) clean_node_id = node_id.strip() if clean_node_id.startswith('alfresco://'): # Extract node ID from URI format clean_node_id = clean_node_id.split('/')[-1] logger.info(f"Attempting to delete node: {clean_node_id}") if ctx: await ctx.report_progress(0.7) # Get node information first to validate it exists (working pattern from test) node_response = core_client.nodes.get(clean_node_id) if not hasattr(node_response, 'entry'): return safe_format_output(f"❌ Failed to get node information for: {clean_node_id}") node_info = node_response.entry filename = getattr(node_info, 'name', f"document_{clean_node_id}") # Use the working high-level API pattern from test script core_client.nodes.delete(clean_node_id) status = "permanently deleted" if permanent else "moved to trash" logger.info(f"✅ Node {status}: {filename}") if ctx: await ctx.report_progress(1.0) return safe_format_output(f"""✅ **Deletion Complete** 📄 **Node**: {node_info.name} 🗑️ **Status**: {status.title()} {"⚠️ **WARNING**: This action cannot be undone" if permanent else "ℹ️ **INFO**: Can be restored from trash"} 🆔 **Node ID**: {clean_node_id}""") except Exception as e: error_msg = f"ERROR: Deletion failed: {str(e)}" if ctx: await ctx.error(error_msg) logger.error(f"Deletion failed: {e}") return error_msg ``` -------------------------------------------------------------------------------- /alfresco_mcp_server/config.py: -------------------------------------------------------------------------------- ```python """ Configuration management for MCP Server for Alfresco. """ import os from typing import Optional from pydantic import BaseModel, Field class AlfrescoConfig(BaseModel): """Configuration for MCP Server for Alfresco.""" # Alfresco server connection alfresco_url: str = Field( default_factory=lambda: os.getenv("ALFRESCO_URL", "http://localhost:8080"), description="Alfresco server URL" ) # Authentication username: str = Field( default_factory=lambda: os.getenv("ALFRESCO_USERNAME", "admin"), description="Alfresco username" ) password: str = Field( default_factory=lambda: os.getenv("ALFRESCO_PASSWORD", "admin"), description="Alfresco password" ) # Connection settings verify_ssl: bool = Field( default_factory=lambda: os.getenv("ALFRESCO_VERIFY_SSL", "false").lower() == "true", description="Verify SSL certificates" ) timeout: int = Field( default_factory=lambda: int(os.getenv("ALFRESCO_TIMEOUT", "30")), description="Request timeout in seconds" ) # MCP Server settings server_name: str = Field( default="python-alfresco-mcp-server", description="MCP server name" ) server_version: str = Field( default="1.0.0", description="MCP server version" ) # FastAPI settings (for HTTP transport) fastapi_host: str = Field( default_factory=lambda: os.getenv("FASTAPI_HOST", "localhost"), description="FastAPI host" ) fastapi_port: int = Field( default_factory=lambda: int(os.getenv("FASTAPI_PORT", "8000")), description="FastAPI port" ) fastapi_prefix: str = Field( default_factory=lambda: os.getenv("FASTAPI_PREFIX", "/mcp"), description="FastAPI URL prefix" ) # Logging log_level: str = Field( default_factory=lambda: os.getenv("LOG_LEVEL", "INFO"), description="Logging level" ) # Content settings max_file_size: int = Field( default_factory=lambda: int(os.getenv("MAX_FILE_SIZE", "100000000")), # 100MB description="Maximum file size for uploads in bytes" ) allowed_extensions: list[str] = Field( default_factory=lambda: [ ".txt", ".pdf", ".doc", ".docx", ".xls", ".xlsx", ".ppt", ".pptx", ".jpg", ".jpeg", ".png", ".gif", ".zip", ".xml", ".json", ".csv" ], description="Allowed file extensions for uploads" ) class Config: env_prefix = "ALFRESCO_" case_sensitive = False def model_post_init(self, __context) -> None: """Normalize URLs after initialization.""" if self.alfresco_url.endswith("/"): self.alfresco_url = self.alfresco_url.rstrip("/") def load_config() -> AlfrescoConfig: """Load configuration from environment variables and defaults.""" return AlfrescoConfig() # Global config instance for import config = load_config() ``` -------------------------------------------------------------------------------- /alfresco_mcp_server/tools/core/create_folder.py: -------------------------------------------------------------------------------- ```python """ Create folder tool for Alfresco MCP Server. Self-contained tool for creating folders in Alfresco repository. """ import logging from typing import Optional from fastmcp import Context from ...utils.connection import ensure_connection from ...utils.json_utils import safe_format_output logger = logging.getLogger(__name__) async def create_folder_impl( folder_name: str, parent_id: str = "-shared-", description: str = "", ctx: Optional[Context] = None ) -> str: """Create a new folder in Alfresco. Args: folder_name: Name of the new folder parent_id: Parent folder ID (default: shared folder) description: Folder description ctx: MCP context for progress reporting Returns: Folder creation confirmation with details """ if ctx: await ctx.info(f">> Creating folder '{folder_name}' in {parent_id}") await ctx.info("Validating folder parameters...") await ctx.report_progress(0.0) if not folder_name.strip(): return safe_format_output("❌ Error: folder_name is required") try: # Ensure connection and get client factory (working pattern from test) await ensure_connection() from ...utils.connection import get_client_factory # Get client factory and create core client (working pattern from test) client_factory = await get_client_factory() core_client = client_factory.create_core_client() if ctx: await ctx.info("Creating folder in Alfresco...") await ctx.report_progress(0.5) logger.info(f"Creating folder '{folder_name}' in parent {parent_id}") # Prepare properties properties = {"cm:title": folder_name} if description: properties["cm:description"] = description logger.info(f"Using high-level API: core_client.nodes.create_folder()") # Use the working high-level API pattern from test script folder_response = core_client.nodes.create_folder( name=folder_name, parent_id=parent_id, properties=properties ) if folder_response and hasattr(folder_response, 'entry'): entry = folder_response.entry logger.info("✅ Folder created successfully") # Extract folder details from response folder_id = getattr(entry, 'id', 'Unknown') folder_name_response = getattr(entry, 'name', folder_name) created_at = getattr(entry, 'createdAt', 'Unknown') node_type = getattr(entry, 'nodeType', 'cm:folder') else: raise Exception(f"Failed to create folder - invalid response from core client") if ctx: await ctx.info("Processing folder creation response...") await ctx.report_progress(0.9) if ctx: await ctx.info("Folder created!") await ctx.report_progress(1.0) await ctx.info(f"SUCCESS: Folder '{folder_name_response}' created successfully") # Clean JSON-friendly formatting (no markdown syntax) return safe_format_output(f"""✅ Folder Created Successfully! 📁 Name: {folder_name_response} 🆔 Folder ID: {folder_id} 📍 Parent: {parent_id} 📅 Created: {created_at} 🏷️ Type: {node_type} 📝 Description: {description or 'None'}""") except Exception as e: error_msg = f"❌ Folder creation failed: {str(e)}" if ctx: await ctx.error(error_msg) logger.error(f"Folder creation failed: {e}") return safe_format_output(error_msg) ``` -------------------------------------------------------------------------------- /tests/mcp_specific/test_with_mcp_inspector.md: -------------------------------------------------------------------------------- ```markdown # Testing with MCP Inspector ## Step 1: Start MCP Inspector ```bash # Launch MCP Inspector npx @modelcontextprotocol/inspector ``` This will open a web interface (usually at http://localhost:3000) where you can interactively test your MCP server. ## Step 2: Configure Server Connection In the MCP Inspector interface: 1. **Server Type**: Select "stdio" 2. **Command**: Enter `python` 3. **Arguments**: Add these as separate entries: - `-m` - `alfresco_mcp_server.fastmcp_server` 4. **Environment Variables** (if needed): - `ALFRESCO_URL`: `http://localhost:8080` - `ALFRESCO_USERNAME`: `admin` - `ALFRESCO_PASSWORD`: `admin` - `ALFRESCO_VERIFY_SSL`: `false` ## Step 3: Connect and Explore ### Available Tools (15 total): 1. **search_content** - Search documents and folders 2. **search_by_metadata** - Search by metadata properties 3. **advanced_search** - Advanced search with filters 4. **cmis_search** - CMIS SQL-based search 5. **upload_document** - Upload new documents 6. **download_document** - Download document content 7. **browse_repository** - Browse repository structure 8. **repository_info** - Get repository information and status 9. **checkout_document** - Check out for editing 10. **checkin_document** - Check in after editing 11. **cancel_checkout** - Cancel document checkout 12. **delete_node** - Delete documents/folders 13. **get_node_properties** - Get node metadata 14. **update_node_properties** - Update node metadata 15. **create_folder** - Create new folders ### Available Resources (5 total): 1. **alfresco://repository/info** - Repository information 2. **alfresco://repository/health** - Health status 3. **alfresco://repository/stats** - Usage statistics 4. **alfresco://repository/config** - Configuration details 5. **alfresco://repository/{section}** - Dynamic repository info ### Available Prompts (1 total): 1. **search_and_analyze** - AI-friendly search template ## Step 4: Test Examples ### Quick Tests (No Alfresco Required): - List tools: Should show all 15 tools - List resources: Should show all 5 resources - List prompts: Should show search_and_analyze prompt ### With Live Alfresco Server: 1. **Test Search**: - Tool: `search_content` - Parameters: `{"query": "test", "max_results": 5}` 2. **Test Repository Info**: - Resource: `alfresco://repository/info` 3. **Test Create Folder**: - Tool: `create_folder` - Parameters: `{"folder_name": "MCP Test Folder", "description": "Created via MCP Inspector"}` ## Step 5: Advanced Testing ### Error Handling: - Try invalid parameters - Test without Alfresco connection - Test with wrong credentials ### Performance: - Large search queries - Multiple concurrent operations - File upload/download operations ## Troubleshooting ### Common Issues: 1. **Inspector won't start**: Check Node.js version, try `npm install -g @modelcontextprotocol/inspector` 2. **Server connection fails**: Verify Python path and module installation 3. **Alfresco errors**: Check server status, credentials, and network connectivity 4. **Tool execution fails**: Verify parameters match schema requirements ### Environment Setup: ```bash # Windows PowerShell $env:ALFRESCO_URL="http://localhost:8080" $env:ALFRESCO_USERNAME="admin" $env:ALFRESCO_PASSWORD="admin" $env:ALFRESCO_VERIFY_SSL="false" # Or use .env file (recommended) # Copy sample-dot-env.txt to .env and modify ``` ## Next Steps 1. **Start simple**: Test tool/resource listing first 2. **Add credentials**: Set up environment variables for Alfresco 3. **Test incrementally**: One tool at a time 4. **Explore features**: Try different parameters and combinations 5. **Production testing**: Test with your actual Alfresco deployment ``` -------------------------------------------------------------------------------- /alfresco_mcp_server/utils/connection.py: -------------------------------------------------------------------------------- ```python """ Connection utilities for Alfresco MCP Server. Handles client creation and connection management. """ import logging import os from typing import Optional logger = logging.getLogger(__name__) # Global connection cache _master_client = None _client_factory = None def get_alfresco_config() -> dict: """Get Alfresco configuration from environment variables.""" return { 'alfresco_url': os.getenv('ALFRESCO_URL', 'http://localhost:8080'), 'username': os.getenv('ALFRESCO_USERNAME', 'admin'), 'password': os.getenv('ALFRESCO_PASSWORD', 'admin'), 'verify_ssl': os.getenv('ALFRESCO_VERIFY_SSL', 'false').lower() == 'true', 'timeout': int(os.getenv('ALFRESCO_TIMEOUT', '30')) } async def ensure_connection(): """Ensure we have a working connection to Alfresco using python-alfresco-api.""" global _master_client, _client_factory if _master_client is None: try: # Import here to avoid circular imports from python_alfresco_api import ClientFactory config = get_alfresco_config() logger.info(">> Creating Alfresco clients...") # Use ClientFactory to create authenticated client (original Sunday pattern) factory = ClientFactory( base_url=config['alfresco_url'], username=config['username'], password=config['password'], verify_ssl=config['verify_ssl'], timeout=config['timeout'] ) # Store the factory globally for other functions to use _client_factory = factory _master_client = factory.create_master_client() logger.info("Master client created successfully") # Test connection - use method that initializes and gets try: # Use ensure_httpx_client to initialize, then test simple call _master_client.core.ensure_httpx_client() logger.info("Connection test successful!") except Exception as conn_error: logger.warning(f"Connection test failed: {conn_error}") except Exception as e: logger.error(f"ERROR: Failed to create clients: {str(e)}") raise e return _master_client def get_connection(): """Get the cached connection without async (for sync operations).""" return _master_client async def get_search_client(): """Get the search client for search operations (using master_client for auth compatibility).""" master_client = await ensure_connection() # Return master_client which has simple_search access and working authentication return master_client async def get_core_client(): """Get the core client for core operations (using master_client for auth compatibility).""" master_client = await ensure_connection() # Return the actual core client that has nodes, folders, etc. return master_client.core async def get_client_factory(): """Get the client factory for advanced operations.""" await ensure_connection() if not _client_factory: raise RuntimeError("Connection not initialized. Call ensure_connection() first.") return _client_factory def get_search_utils(): """Get the search_utils module from python-alfresco-api.""" try: from python_alfresco_api.utils import search_utils return search_utils except ImportError as e: logger.error(f"Failed to import search_utils: {e}") raise def get_node_utils(): """Get the node_utils module from python-alfresco-api.""" try: from python_alfresco_api.utils import node_utils return node_utils except ImportError as e: logger.error(f"Failed to import node_utils: {e}") raise ``` -------------------------------------------------------------------------------- /alfresco_mcp_server/utils/json_utils.py: -------------------------------------------------------------------------------- ```python """ JSON utilities for Alfresco MCP Server. Handles proper Unicode emoji encoding for MCP protocol transport. """ import json import logging logger = logging.getLogger(__name__) def make_json_safe(text: str) -> str: """ Make text JSON-safe for MCP protocol transport. Properly encodes Unicode emojis to prevent character map errors. Args: text: Input text that may contain Unicode emojis Returns: JSON-safe text with properly encoded Unicode characters """ if not text: return text try: # Ensure proper Unicode normalization import unicodedata normalized = unicodedata.normalize('NFC', text) # Test if it can be safely JSON serialized json.dumps(normalized) return normalized except (UnicodeError, TypeError) as e: logger.warning(f"Unicode encoding issue, falling back to ASCII: {e}") # Fall back to ASCII-safe version with emoji descriptions return text.encode('ascii', errors='ignore').decode('ascii') def safe_format_output(text: str) -> str: """ Format output text to be safe for MCP JSON transport. Replaces emojis with text equivalents to prevent character map errors. Args: text: Text to format Returns: Safely formatted text with emojis replaced """ if not text: return text try: # Define emoji replacements for common ones used in the tools emoji_replacements = { '🔗': '[LINK]', '🔓': '[UNLOCKED]', '📄': '[DOCUMENT]', '🆔': '[ID]', '📏': '[SIZE]', '💾': '[SAVED]', '🔒': '[LOCKED]', '🕒': '[TIME]', '📥': '[DOWNLOAD]', 'ℹ️': '[INFO]', '⚠️': '[WARNING]', '👤': '[USER]', '✅': '[SUCCESS]', '❌': '[ERROR]', '🏷️': '[TAG]', '🧩': '[MODULE]', '📁': '[FOLDER]', '📍': '[LOCATION]', '📅': '[DATE]', '📝': '[NOTE]', '🔢': '[VERSION]', '📊': '[SIZE]', '🗑️': '[DELETE]', '🔍': '[SEARCH]', '📤': '[UPLOAD]', '🧹': '[CLEANUP]', '🏢': '[REPOSITORY]', '🔧': '[TOOL]', '📦': '[PACKAGE]' } # Replace emojis with text equivalents safe_text = text for emoji, replacement in emoji_replacements.items(): safe_text = safe_text.replace(emoji, replacement) # Test if the result is JSON-safe test_json = json.dumps(safe_text, ensure_ascii=True) json.loads(test_json) return safe_text except Exception as e: logger.warning(f"JSON formatting issue: {e}") try: # Ultimate fallback: remove all non-ASCII characters ascii_text = text.encode('ascii', errors='ignore').decode('ascii') return ascii_text except Exception as fallback_error: logger.error(f"ASCII fallback failed: {fallback_error}") return "Error: Text encoding failed" def escape_unicode_for_json(text: str) -> str: """ Alternative approach: explicitly escape Unicode characters for JSON. Use this if the regular approach doesn't work. Args: text: Input text with Unicode characters Returns: Text with Unicode characters escaped for JSON """ if not text: return text try: # Use json.dumps to properly escape Unicode, then remove the quotes escaped = json.dumps(text, ensure_ascii=False) # Remove the surrounding quotes added by json.dumps if escaped.startswith('"') and escaped.endswith('"'): escaped = escaped[1:-1] return escaped except Exception as e: logger.warning(f"Unicode escaping failed: {e}") return text ``` -------------------------------------------------------------------------------- /docs/install_with_pip_pipx.md: -------------------------------------------------------------------------------- ```markdown # Installation with pip and pipx 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). ## 🛠️ Installation Options ### Option 1: pipx (Recommended for Traditional Methods) pipx automatically creates isolated environments for each tool while making commands globally available - eliminates dependency conflicts while providing system-wide access. ```bash # First install pipx if you don't have it (one-time setup) pip install pipx # Install python-alfresco-mcp-server in isolated environment pipx install python-alfresco-mcp-server # Test that installation worked python-alfresco-mcp-server --help ``` **Why pipx?** pipx automatically creates isolated environments for each tool while making commands globally available - eliminates dependency conflicts while providing system-wide access. ### Option 2: pip (Traditional Package Manager) ```bash # Recommended: Create virtual environment first python -m venv venv source venv/bin/activate # Linux/macOS # venv\Scripts\activate # Windows # Install python-alfresco-mcp-server pip install python-alfresco-mcp-server # Test that installation worked python-alfresco-mcp-server --help ``` ## 🚀 Usage ### MCP Server Startup **With pipx (Global installation - no venv needed):** ```bash # Run MCP server with STDIO transport (default) python-alfresco-mcp-server # HTTP transport for web services (matches MCP Inspector) python-alfresco-mcp-server --transport http --host 127.0.0.1 --port 8003 # SSE transport for real-time streaming python-alfresco-mcp-server --transport sse --host 127.0.0.1 --port 8001 ``` **With pip (Activate venv first if installed in one):** ```bash # Activate virtual environment first (if used during installation) source venv/bin/activate # Linux/macOS # venv\Scripts\activate # Windows # Run MCP server with STDIO transport (default) python-alfresco-mcp-server # HTTP transport for web services (matches MCP Inspector) python-alfresco-mcp-server --transport http --host 127.0.0.1 --port 8003 # SSE transport for real-time streaming python-alfresco-mcp-server --transport sse --host 127.0.0.1 --port 8001 ``` ## 🔧 Claude Desktop Configuration The Claude Desktop configuration differs based on how you installed the MCP server: ### pipx Installation (Global Tool) ```json { "command": "python-alfresco-mcp-server", "args": ["--transport", "stdio"] } ``` - Uses the **global command name** directly (no path needed) - pipx makes tools globally available in your PATH - Simplest configuration **Sample Config Files:** - Windows: [`claude-desktop-config-pipx-windows.json`](../claude-desktop-config-pipx-windows.json) - macOS: [`claude-desktop-config-pipx-macos.json`](../claude-desktop-config-pipx-macos.json) ### pip Installation (Manual venv) ```json { "command": "C:\\path\\to\\venv\\Scripts\\python-alfresco-mcp-server.exe", "args": ["--transport", "stdio"] } ``` - Uses **direct path to executable** in your virtual environment - Path points to `Scripts/` directory in your venv (Windows) or `bin/` (Linux/macOS) - Replace `C:\\path\\to\\venv` with your actual venv location ## 🔍 MCP Inspector Configuration For development and testing with MCP Inspector: ### pipx Installation Use the sample config files: - **stdio transport**: [`mcp-inspector-stdio-pipx-config.json`](../mcp-inspector-stdio-pipx-config.json) - **http transport**: [`mcp-inspector-http-pipx-config.json`](../mcp-inspector-http-pipx-config.json) ```bash # Start with stdio transport npx @modelcontextprotocol/inspector --config mcp-inspector-stdio-pipx-config.json --server python-alfresco-mcp-server # Start with http transport npx @modelcontextprotocol/inspector --config mcp-inspector-http-pipx-config.json --server python-alfresco-mcp-server ``` ### pip Installation Copy one of the pipx sample files above and modify the `"command"` field to point to your venv executable: - Change `"python-alfresco-mcp-server"` to `"C:\\path\\to\\venv\\Scripts\\python-alfresco-mcp-server.exe"` (Windows) - Or `"/path/to/venv/bin/python-alfresco-mcp-server"` (Linux/macOS) ``` -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- ```toml [build-system] requires = ["setuptools>=61.0", "wheel"] build-backend = "setuptools.build_meta" [project] name = "python-alfresco-mcp-server" version = "1.1.0" description = "FastMCP 2.0 server for Alfresco Content Services integration" authors = [{name = "Steve Reiner", email = "[email protected]"}] license = {text = "Apache-2.0"} readme = "README.md" requires-python = ">=3.10" keywords = ["alfresco", "mcp", "content-management", "fastmcp", "ai"] classifiers = [ "Development Status :: 4 - Beta", "Intended Audience :: Developers", "License :: OSI Approved :: Apache Software License", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Topic :: Software Development :: Libraries :: Python Modules", "Topic :: Internet :: WWW/HTTP :: Dynamic Content :: Content Management System", "Topic :: Internet :: WWW/HTTP :: Indexing/Search", "Topic :: Database :: Database Engines/Servers", "Topic :: Scientific/Engineering :: Artificial Intelligence", ] dependencies = [ # FastMCP 2.0 - Modern MCP framework with enhanced features "fastmcp>=2.9.0", # Alfresco integration "python-alfresco-api>=1.1.1", # Configuration and utilities "pydantic>=2.0.0", "pydantic-settings>=2.0.0", "PyYAML>=6.0", # HTTP client "httpx>=0.24.0", # File handling "python-multipart>=0.0.6", ] [project.urls] Homepage = "https://github.com/stevereiner/python-alfresco-mcp-server" Repository = "https://github.com/stevereiner/python-alfresco-mcp-server" Issues = "https://github.com/stevereiner/python-alfresco-mcp-server/issues" Documentation = "https://github.com/stevereiner/python-alfresco-mcp-server#readme" [project.optional-dependencies] dev = [ "black>=23.0.0", "ruff>=0.1.0", "mypy>=1.0.0", ] test = [ "pytest>=7.0.0", "pytest-asyncio>=0.21.0", "pytest-cov>=4.0.0", "pytest-xdist>=3.0.0", "pytest-mock>=3.10.0", "coverage[toml]>=7.0.0", "httpx>=0.24.0", ] all = [ "python-alfresco-mcp-server[dev]", "python-alfresco-mcp-server[test]", ] [project.scripts] python-alfresco-mcp-server = "alfresco_mcp_server.fastmcp_server:main" [tool.setuptools] include-package-data = true [tool.setuptools.packages.find] where = ["."] include = ["alfresco_mcp_server*"] exclude = ["tests*", "tests-debug*", "venv*", "*.egg-info*"] [tool.setuptools.package-data] alfresco_mcp_server = [ "*.py", "*.yaml", "*.yml", "*.json", "*.md", "tools/**/*.py", "resources/**/*.py", "prompts/**/*.py", "utils/**/*.py", ] [tool.pytest.ini_options] minversion = "7.0" addopts = [ "-ra", "-v", "--tb=short", "--strict-markers", "--disable-warnings", ] testpaths = ["tests"] filterwarnings = [ "ignore::DeprecationWarning", "ignore::PendingDeprecationWarning", ] markers = [ "asyncio: marks tests as async", "integration: marks tests as integration tests requiring live Alfresco server", "unit: marks tests as unit tests", "fastmcp: marks tests as FastMCP 2.0 specific tests", ] asyncio_mode = "auto" [tool.coverage.run] source = ["alfresco_mcp_server"] omit = [ "*/tests/*", "*/venv/*", "*/__pycache__/*", ] [tool.coverage.report] exclude_lines = [ "pragma: no cover", "def __repr__", "if self.debug:", "if settings.DEBUG", "raise AssertionError", "raise NotImplementedError", "if 0:", "if __name__ == .__main__.:", "class .*\\bProtocol\\):", "@(abc\\.)?abstractmethod", ] [tool.black] line-length = 88 target-version = ['py310'] include = '\.pyi?$' extend-exclude = ''' /( # directories \.eggs | \.git | \.hg | \.mypy_cache | \.tox | \.venv | venv | build | dist )/ ''' [tool.ruff] target-version = "py310" line-length = 88 select = [ "E", # pycodestyle errors "W", # pycodestyle warnings "F", # pyflakes "I", # isort "B", # flake8-bugbear "C4", # flake8-comprehensions "UP", # pyupgrade ] ignore = [ "E501", # line too long, handled by black "B008", # do not perform function calls in argument defaults "C901", # too complex ] [tool.ruff.per-file-ignores] "__init__.py" = ["F401"] "tests/**/*" = ["B018", "B019"] [tool.mypy] python_version = "3.10" check_untyped_defs = true disallow_any_generics = true disallow_incomplete_defs = true disallow_untyped_defs = true no_implicit_optional = true warn_redundant_casts = true warn_unused_ignores = true warn_return_any = true strict_equality = true [dependency-groups] dev = [ "build>=1.2.2.post1", "twine>=6.1.0", ] ``` -------------------------------------------------------------------------------- /examples/quick_start.py: -------------------------------------------------------------------------------- ```python #!/usr/bin/env python3 """ Quick Start Example for Alfresco MCP Server This example demonstrates: - Basic server connection - Making your first tool call - Handling responses - Simple error handling Prerequisites: - Alfresco MCP Server running - Environment variables set (ALFRESCO_URL, ALFRESCO_USERNAME, ALFRESCO_PASSWORD) """ import asyncio import os from fastmcp import Client from alfresco_mcp_server.fastmcp_server import mcp async def quick_start_example(): """Quick start example showing basic MCP server usage.""" print("🚀 Alfresco MCP Server - Quick Start Example") print("=" * 50) # Check environment setup print("\n📋 Checking Environment Setup...") required_vars = ["ALFRESCO_URL", "ALFRESCO_USERNAME", "ALFRESCO_PASSWORD"] for var in required_vars: value = os.getenv(var) if value: print(f"✅ {var}: {value}") else: print(f"❌ {var}: Not set (using defaults)") try: # Connect to the MCP server print("\n🔌 Connecting to MCP Server...") async with Client(mcp) as client: print("✅ Connected successfully!") # List available tools print("\n🛠️ Available Tools:") tools = await client.list_tools() for i, tool in enumerate(tools, 1): print(f" {i:2d}. {tool.name} - {tool.description}") # List available resources print("\n📚 Available Resources:") resources = await client.list_resources() for i, resource in enumerate(resources, 1): print(f" {i:2d}. {resource.uri}") # List available prompts print("\n💭 Available Prompts:") prompts = await client.list_prompts() for i, prompt in enumerate(prompts, 1): print(f" {i:2d}. {prompt.name} - {prompt.description}") # Example 1: Simple search print("\n🔍 Example 1: Simple Document Search") print("-" * 40) search_result = await client.call_tool("search_content", { "query": "*", # Search for all documents "max_results": 5 }) if search_result: print("Search Result:") print(search_result[0].text) # Example 2: Get repository info print("\n📊 Example 2: Repository Information") print("-" * 40) repo_info = await client.read_resource("alfresco://repository/info") if repo_info: print("Repository Info:") print(repo_info[0].text) # Example 3: Create a test folder print("\n📁 Example 3: Create Test Folder") print("-" * 40) folder_result = await client.call_tool("create_folder", { "folder_name": f"MCP_Test_Folder_{asyncio.current_task().get_name()}", "parent_id": "-root-", "description": "Test folder created by MCP Quick Start example" }) if folder_result: print("Folder Creation Result:") print(folder_result[0].text) # Example 4: Get analysis prompt print("\n💡 Example 4: Analysis Prompt") print("-" * 40) prompt_result = await client.get_prompt("search_and_analyze", { "query": "financial reports", "analysis_type": "summary" }) if prompt_result.messages: print("Generated Prompt:") print(prompt_result.messages[0].content.text[:300] + "...") print("\n✅ Quick Start Complete!") print("Next steps:") print("- Explore other examples in this directory") print("- Check the documentation in ../docs/") print("- Try the document lifecycle example") except Exception as e: print(f"\n❌ Error: {e}") print("\nTroubleshooting:") print("1. Ensure Alfresco server is running") print("2. Check environment variables") print("3. Verify network connectivity") return False return True def main(): """Main function to run the quick start example.""" print("Starting Alfresco MCP Server Quick Start Example...") # Run the async example success = asyncio.run(quick_start_example()) if success: print("\n🎉 Example completed successfully!") else: print("\n💥 Example failed. Please check the error messages above.") if __name__ == "__main__": main() ``` -------------------------------------------------------------------------------- /alfresco_mcp_server/tools/core/update_node_properties.py: -------------------------------------------------------------------------------- ```python """ Update node properties tool for Alfresco MCP Server. Self-contained tool for updating document/folder metadata and properties. """ import logging import os from typing import Optional from fastmcp import Context from ...utils.connection import ensure_connection, get_core_client logger = logging.getLogger(__name__) async def update_node_properties_impl( node_id: str, name: str = "", title: str = "", description: str = "", author: str = "", ctx: Optional[Context] = None ) -> str: """Update metadata and properties of a document or folder. Args: node_id: Node ID to update (required) name: New name for the node (optional) title: Document title (cm:title) (optional) description: Document description (cm:description) (optional) author: Document author (cm:author) (optional) ctx: MCP context for progress reporting Returns: Update confirmation with changes made """ if ctx: await ctx.info(f"Updating properties for: {node_id}") await ctx.report_progress(0.1) if not node_id.strip(): return "ERROR: node_id is required" if not any([name, title, description, author]): return "ERROR: At least one property (name, title, description, or author) must be provided" try: # Ensure connection and get core client await ensure_connection() core_client = await get_core_client() # Clean the node ID (remove any URL encoding or extra characters) clean_node_id = node_id.strip() if clean_node_id.startswith('alfresco://'): # Extract node ID from URI format clean_node_id = clean_node_id.split('/')[-1] logger.info(f"Updating properties for node: {clean_node_id}") if ctx: await ctx.report_progress(0.3) # Get node information first to validate it exists try: node_response = core_client.nodes.get(node_id=clean_node_id) if not hasattr(node_response, 'entry'): return f"ERROR: Failed to get node information for: {clean_node_id}" node_info = node_response.entry current_name = getattr(node_info, 'name', f"document_{clean_node_id}") except Exception as get_error: return f"ERROR: Failed to validate node {clean_node_id}: {str(get_error)}" if ctx: await ctx.report_progress(0.5) # Prepare updates for actual API call properties_updates = {} if title and title.strip(): properties_updates['cm:title'] = title.strip() if description and description.strip(): properties_updates['cm:description'] = description.strip() if author and author.strip(): properties_updates['cm:author'] = author.strip() # Import the UpdateNodeRequest model try: from python_alfresco_api.clients.core.nodes.models import UpdateNodeRequest except ImportError: return "ERROR: Failed to import UpdateNodeRequest model" # Prepare update request update_request = UpdateNodeRequest() if name and name.strip(): update_request.name = name.strip() if properties_updates: update_request.properties = properties_updates if ctx: await ctx.report_progress(0.7) # Use the core client's update method try: updated_node = core_client.nodes.update( node_id=clean_node_id, request=update_request ) logger.info("Node properties updated successfully") except Exception as update_error: logger.error(f"Update failed: {update_error}") return f"ERROR: Update failed: {str(update_error)}" if ctx: await ctx.report_progress(1.0) changes = [] if name: changes.append(f"- Name: {name}") if title: changes.append(f"- Title: {title}") if description: changes.append(f"- Description: {description}") if author: changes.append(f"- Author: {author}") updated_properties = "\n".join(changes) # Clean JSON-friendly formatting (no markdown syntax) return f"Node Updated Successfully\n\nNode ID: {clean_node_id}\nUpdated Properties:\n{updated_properties}\nUpdate completed successfully" except Exception as e: error_msg = f"ERROR: Update failed: {str(e)}" if ctx: await ctx.error(error_msg) logger.error(f"Update failed: {e}") return error_msg ``` -------------------------------------------------------------------------------- /docs/client_configurations.md: -------------------------------------------------------------------------------- ```markdown # MCP Client Configuration Guide 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). ## 🔷 Cursor Configuration Cursor is a VS Code fork with AI capabilities and MCP support. > 📖 **Complete Setup Guide**: Detailed Cursor configuration instructions available at [playbooks.com](https://playbooks.com/mcp/stevereiner-alfresco-content-services#cursor-setup) ### For Users (PyPI Installation) If you installed the package via PyPI with pipx: ```json { "mcpServers": { "python-alfresco-mcp-server": { "command": "python-alfresco-mcp-server", "args": ["--transport", "stdio"], "env": { "ALFRESCO_URL": "http://localhost:8080", "ALFRESCO_USERNAME": "admin", "ALFRESCO_PASSWORD": "admin" } } } } ``` ### For Developers (Source Installation) If you're using the source code with UV: ```json { "mcpServers": { "python-alfresco-mcp-server": { "command": "uv", "args": ["run", "python-alfresco-mcp-server"], "cwd": "/path/to/python-alfresco-mcp-server", "env": { "ALFRESCO_URL": "http://localhost:8080", "ALFRESCO_USERNAME": "admin", "ALFRESCO_PASSWORD": "admin" } } } } ``` ## ⚡ Claude Code Configuration Claude Code is Anthropic's VS Code extension with MCP support. > 📖 **Complete Setup Guide**: Claude Code configuration instructions at [playbooks.com](https://playbooks.com/mcp/stevereiner-alfresco-content-services#claude-code-setup) ### For Users (PyPI Installation) ```bash claude mcp add-json "python-alfresco-mcp-server" '{ "command": "python-alfresco-mcp-server", "args": ["--transport", "stdio"], "env": { "ALFRESCO_URL": "http://localhost:8080", "ALFRESCO_USERNAME": "admin", "ALFRESCO_PASSWORD": "admin" } }' ``` ### For Developers (Source Installation) ```bash claude mcp add-json "python-alfresco-mcp-server" '{ "command": "uv", "args": ["run", "python-alfresco-mcp-server"], "cwd": "/path/to/python-alfresco-mcp-server", "env": { "ALFRESCO_URL": "http://localhost:8080", "ALFRESCO_USERNAME": "admin", "ALFRESCO_PASSWORD": "admin" } }' ``` ## 🔧 Other MCP Clients For any MCP-compatible client, use these connection parameters based on your installation method: ### PyPI Installation (Users) - **Command**: `python-alfresco-mcp-server` (assumes pipx installation) - **Args**: `["--transport", "stdio"]` - **Transport Options**: - STDIO (default) - Direct MCP protocol - HTTP (add `--port 8001`) - RESTful API - SSE (add `--port 8003`) - Server-Sent Events ### Source Installation (Developers) - **Command**: `uv` - **Args**: `["run", "python-alfresco-mcp-server"]` - **Working Directory**: Path to cloned repository - **Transport Options**: Same as above ### Traditional Python Installation If using traditional pip in a virtual environment: - **Command**: `/path/to/venv/bin/python-alfresco-mcp-server` (full path to executable) - **Args**: `["--transport", "stdio"]` - **Transport Options**: Same as above ## 🔧 Environment Variables All clients need these environment variables configured: | Variable | Default | Description | |----------|---------|-------------| | `ALFRESCO_URL` | `http://localhost:8080` | Alfresco server URL | | `ALFRESCO_USERNAME` | `admin` | Username for authentication | | `ALFRESCO_PASSWORD` | `admin` | Password for authentication | | `ALFRESCO_VERIFY_SSL` | `false` | Verify SSL certificates | | `ALFRESCO_TIMEOUT` | `30` | Request timeout (seconds) | ### Windows-Specific Variables (if needed) For Windows systems experiencing character encoding issues: ```json "env": { "ALFRESCO_URL": "http://localhost:8080", "ALFRESCO_USERNAME": "admin", "ALFRESCO_PASSWORD": "admin", "PYTHONIOENCODING": "utf-8", "PYTHONLEGACYWINDOWSSTDIO": "1" } ``` ## 🚀 Transport Options The MCP server supports three transport protocols: ### STDIO (Default) - **Fastest** and most efficient - Direct MCP protocol communication - Recommended for most use cases - Args: `["--transport", "stdio"]` (optional, it's the default) ### HTTP - RESTful API interface - Useful for web services and testing - Args: `["--transport", "http", "--port", "8001"]` ### SSE (Server-Sent Events) - Real-time streaming updates - Good for live monitoring - Args: `["--transport", "sse", "--port", "8003"]` ## 🧪 Testing Your Configuration After setting up your MCP client: 1. **Start Your Client**: Launch your MCP-enabled application 2. **Check Connection**: Look for "python-alfresco-mcp-server" in connected servers 3. **Test Basic Functionality**: - Try the `repository_info` tool to verify connection - Run a simple `search_content` query - Check that all 15 tools are available ## 🛠️ Troubleshooting ### Common Issues 1. **Command Not Found** - Ensure the package is installed correctly - For pipx: Run `pipx list` to verify installation - For source: Ensure UV is installed and working directory is correct 2. **Connection Failures** - Check Alfresco server is running - Verify environment variables are set correctly - Test connection with `curl http://localhost:8080/alfresco` 3. **Permission Errors** - Verify Alfresco username/password - Check that user has appropriate permissions - Try with admin credentials first 4. **Character Encoding (Windows)** - Add Windows-specific environment variables - Ensure UTF-8 encoding is configured ### Getting Help - 📚 **Documentation**: Complete guides in [`../docs/`](./README.md) - 🛠️ **Troubleshooting**: [Troubleshooting Guide](./troubleshooting.md) - 🐛 **Issues**: [GitHub Issues](https://github.com/stevereiner/python-alfresco-mcp-server/issues) ## ⚠️ Security Notes - **Never commit configuration files** with real credentials to version control - **Use environment variables** for production deployments - **Consider using .env files** for local development (they're ignored by git) - **Use strong passwords** for production Alfresco servers ``` -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- ```python """ Pytest configuration and shared fixtures for Alfresco MCP Server tests. Provides configuration for unit tests, integration tests, and code coverage. """ import pytest import pytest_asyncio import asyncio import os import httpx import tempfile import shutil from typing import Generator, AsyncGenerator from unittest.mock import MagicMock, AsyncMock, patch # Test markers pytest_plugins = ["pytest_asyncio"] def pytest_configure(config): """Configure pytest markers.""" config.addinivalue_line( "markers", "unit: marks tests as unit tests (fast, mocked)" ) config.addinivalue_line( "markers", "integration: marks tests as integration tests (requires live Alfresco)" ) config.addinivalue_line( "markers", "slow: marks tests as slow running" ) config.addinivalue_line( "markers", "performance: marks tests as performance benchmarks" ) @pytest.fixture(scope="session") def event_loop(): """Create an instance of the default event loop for the test session.""" loop = asyncio.get_event_loop_policy().new_event_loop() yield loop loop.close() @pytest.fixture def temp_dir() -> Generator[str, None, None]: """Create a temporary directory for test files.""" with tempfile.TemporaryDirectory() as temp_dir: yield temp_dir @pytest.fixture def mock_alfresco_factory(): """Mock Alfresco client factory for unit tests.""" factory = MagicMock() # Mock search client search_client = AsyncMock() search_client.search = AsyncMock() factory.create_search_client.return_value = search_client # Mock core client core_client = AsyncMock() core_client.get_node = AsyncMock() core_client.create_node = AsyncMock() core_client.update_node = AsyncMock() core_client.delete_node = AsyncMock() core_client.get_node_content = AsyncMock() factory.create_core_client.return_value = core_client return factory @pytest.fixture def mock_auth_util(): """Mock authentication utility for unit tests.""" auth_util = AsyncMock() auth_util.ensure_authenticated = AsyncMock() auth_util.get_auth_headers = AsyncMock(return_value={"Authorization": "Bearer mock-token"}) auth_util.is_authenticated.return_value = True return auth_util @pytest_asyncio.fixture async def fastmcp_client(): """FastMCP in-memory client for testing.""" from fastmcp import Client from alfresco_mcp_server.fastmcp_server import mcp async with Client(mcp) as client: yield client @pytest.fixture async def http_client() -> AsyncGenerator[httpx.AsyncClient, None]: """HTTP client for integration tests.""" async with httpx.AsyncClient(timeout=30.0) as client: yield client @pytest.fixture def alfresco_config(): """Alfresco configuration for tests.""" return { "url": os.getenv("ALFRESCO_URL", "http://localhost:8080"), "username": os.getenv("ALFRESCO_USERNAME", "admin"), "password": os.getenv("ALFRESCO_PASSWORD", "admin"), "verify_ssl": os.getenv("ALFRESCO_VERIFY_SSL", "false").lower() == "true" } @pytest.fixture def sample_documents(): """Sample document data for testing.""" import base64 return { "text_doc": { "filename": "test_document.txt", "content": "This is a test document for MCP server testing.", "content_base64": base64.b64encode( "This is a test document for MCP server testing.".encode() ).decode(), "mime_type": "text/plain" }, "json_doc": { "filename": "test_data.json", "content": '{"test": "data", "numbers": [1, 2, 3]}', "content_base64": base64.b64encode( '{"test": "data", "numbers": [1, 2, 3]}'.encode() ).decode(), "mime_type": "application/json" } } @pytest.fixture def mock_search_results(): """Mock search results for testing.""" from types import SimpleNamespace def create_mock_entry(name, node_id, is_folder=False): entry = SimpleNamespace() entry.entry = SimpleNamespace() entry.entry.name = name entry.entry.id = node_id entry.entry.isFolder = is_folder entry.entry.modifiedAt = "2024-01-15T10:30:00Z" entry.entry.createdByUser = {"displayName": "Test User"} entry.entry.content = {"sizeInBytes": 1024} if not is_folder else None entry.entry.path = {"name": "/Shared/Test"} return entry results = SimpleNamespace() results.list = SimpleNamespace() results.list.entries = [ create_mock_entry("Test Document 1.pdf", "doc-123", False), create_mock_entry("Test Folder", "folder-456", True), create_mock_entry("Test Document 2.txt", "doc-789", False) ] return results def pytest_collection_modifyitems(config, items): """Modify test collection to handle markers.""" if config.getoption("--integration"): # Only run integration tests skip_unit = pytest.mark.skip(reason="Integration test run - skipping unit tests") for item in items: if "integration" not in item.keywords: item.add_marker(skip_unit) else: # Skip integration tests by default skip_integration = pytest.mark.skip(reason="Integration tests require --integration flag") for item in items: if "integration" in item.keywords: item.add_marker(skip_integration) def pytest_addoption(parser): """Add custom command line options.""" parser.addoption( "--integration", action="store_true", default=False, help="Run integration tests (requires live Alfresco server)" ) parser.addoption( "--performance", action="store_true", default=False, help="Run performance benchmarks" ) @pytest.fixture def check_alfresco_available(alfresco_config): """Check if Alfresco server is available for integration tests.""" def _check(): try: response = httpx.get( f"{alfresco_config['url']}/alfresco/api/-default-/public/alfresco/versions/1/probes/-ready-", timeout=5.0 ) return response.status_code == 200 except Exception: return False return _check ``` -------------------------------------------------------------------------------- /alfresco_mcp_server/tools/core/cancel_checkout.py: -------------------------------------------------------------------------------- ```python """ Cancel checkout tool implementation for Alfresco MCP Server. Handles canceling document checkout with cleanup and unlock management. """ import logging import pathlib import json from datetime import datetime from fastmcp import Context from ...utils.connection import get_core_client from ...utils.json_utils import safe_format_output logger = logging.getLogger(__name__) async def cancel_checkout_impl( node_id: str, ctx: Context = None ) -> str: """Cancel checkout of a document, discarding any working copy. Args: node_id: Original node ID that was checked out ctx: MCP context for progress reporting Returns: Cancellation confirmation and cleanup status """ if ctx: await ctx.info(f"Cancelling checkout for: {node_id}") await ctx.report_progress(0.1) if not node_id.strip(): return safe_format_output("❌ Error: node_id is required") try: logger.info(f"Starting cancel checkout: node {node_id}") core_client = await get_core_client() # Clean the node ID clean_node_id = node_id.strip() if clean_node_id.startswith('alfresco://'): clean_node_id = clean_node_id.split('/')[-1] if ctx: await ctx.info("Checking node status...") await ctx.report_progress(0.3) # Get node information to validate using high-level core client node_response = core_client.nodes.get(node_id=clean_node_id) if not hasattr(node_response, 'entry'): return f"ERROR: Failed to get node information for: {clean_node_id}" node_info = node_response.entry filename = getattr(node_info, 'name', f"document_{clean_node_id}") if ctx: await ctx.info(">> Performing Alfresco unlock using high-level client...") await ctx.report_progress(0.5) # Use high-level core client unlock method try: logger.info(f"Attempting to unlock document: {clean_node_id}") unlock_response = core_client.versions.cancel_checkout(node_id=clean_node_id) if unlock_response and hasattr(unlock_response, 'entry'): api_status = "✅ Document unlocked in Alfresco" else: api_status = "✅ Document unlocked in Alfresco" logger.info(f"Document unlocked successfully: {clean_node_id}") except Exception as unlock_error: error_str = str(unlock_error) if "404" in error_str: # Document might not be locked api_status = "ℹ️ Document was not locked in Alfresco" logger.info(f"Document was not locked: {clean_node_id}") elif "405" in error_str: # Server doesn't support lock/unlock APIs api_status = "WARNING: Server doesn't support lock/unlock APIs (treating as unlocked)" logger.warning(f"Server doesn't support unlock API for {clean_node_id}") else: api_status = f"WARNING: Alfresco unlock failed: {error_str}" logger.error(f"Failed to unlock document {clean_node_id}: {error_str}") if ctx: await ctx.info("Cleaning up local files...") await ctx.report_progress(0.7) # Clean up local checkout tracking downloads_dir = pathlib.Path.home() / "Downloads" checkout_dir = downloads_dir / "checkout" checkout_manifest_path = checkout_dir / ".checkout_manifest.json" checkout_data = {} cleanup_status = [api_status] if checkout_manifest_path.exists(): try: with open(checkout_manifest_path, 'r') as f: checkout_data = json.load(f) except: checkout_data = {} # Check if this node is tracked in local checkouts if 'checkouts' in checkout_data and clean_node_id in checkout_data['checkouts']: checkout_info = checkout_data['checkouts'][clean_node_id] checkout_filename = checkout_info['local_file'] checkout_file_path = checkout_dir / checkout_filename # Remove local checkout file try: if checkout_file_path.exists(): checkout_file_path.unlink() cleanup_status.append("🗑️ Local checkout file removed") logger.info(f"Removed local checkout file: {checkout_file_path}") else: cleanup_status.append("ℹ️ Local checkout file already removed") except Exception as e: cleanup_status.append(f"WARNING: Could not remove local file: {e}") logger.warning(f"Failed to remove local file {checkout_file_path}: {e}") # Remove from tracking del checkout_data['checkouts'][clean_node_id] # Update manifest try: with open(checkout_manifest_path, 'w') as f: json.dump(checkout_data, f, indent=2) cleanup_status.append(">> Checkout tracking updated") except Exception as e: cleanup_status.append(f"WARNING: Could not update tracking: {e}") else: cleanup_status.append("ℹ️ No local checkout tracking found") if ctx: await ctx.info("Document unlocked!") await ctx.report_progress(1.0) # Clean JSON-friendly formatting (no markdown syntax) result = f"🔓 Document Unlocked\n\n" result += f">> Document: {filename}\n" result += f"ID: Node ID: {clean_node_id}\n" result += f"🕒 Unlocked: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n" result += f"🧹 Cleanup Status:\n" for status in cleanup_status: result += f" {status}\n" result += f"\nINFO: Note: Document is now available for others to edit." result += f"\nWARNING: Important: Any unsaved changes in the local file have been discarded." return safe_format_output(result) except Exception as e: error_msg = f"❌ Cancel checkout failed: {str(e)}" if ctx: await ctx.error(error_msg) logger.error(f"Cancel checkout failed: {e}") return safe_format_output(error_msg) ``` -------------------------------------------------------------------------------- /tests/mcp_specific/mcp_testing_guide.md: -------------------------------------------------------------------------------- ```markdown # MCP Testing Guide - Base64 Examples and Utilities ## Base64 Example Strings ### Simple Examples ``` "SGVsbG8gV29ybGQ=" # "Hello World" "VGVzdCBkb2N1bWVudA==" # "Test document" "U2FtcGxlIGNvbnRlbnQ=" # "Sample content" ``` ### Document Content Examples ```python # Text document text_content = "This is a test document for MCP server testing." text_base64 = "VGhpcyBpcyBhIHRlc3QgZG9jdW1lbnQgZm9yIE1DUCBzZXJ2ZXIgdGVzdGluZy4=" # JSON document json_content = '{"test": "data", "numbers": [1, 2, 3]}' json_base64 = "eyJ0ZXN0IjogImRhdGEiLCAibnVtYmVycyI6IFsxLCAyLCAzXX0=" ``` ## Base64 Generation Functions ### Simple Generator Function ```python import base64 def generate_base64(text_content): """Convert text to base64 string.""" return base64.b64encode(text_content.encode('utf-8')).decode('utf-8') def decode_base64(base64_string): """Decode base64 string to text.""" return base64.b64decode(base64_string).decode('utf-8') # Examples text = "Hello World" encoded = generate_base64(text) # "SGVsbG8gV29ybGQ=" decoded = decode_base64(encoded) # "Hello World" ``` ### Document Generator for MCP Testing ```python import base64 import json import time import uuid def generate_test_document(filename, content_type="text"): """Generate a test document with base64 encoding for MCP upload.""" if content_type == "text": content = f"""Test Document - {filename} Created: {time.strftime('%Y-%m-%d %H:%M:%S')} Document ID: {uuid.uuid4()} Type: MCP Test Document This is a sample document created for testing the Alfresco MCP server. It contains structured content for validation purposes. Sections: 1. Header information 2. Timestamp data 3. Unique identifier 4. Content body 5. Footer notes End of document. """ elif content_type == "json": content = json.dumps({ "document_id": str(uuid.uuid4()), "filename": filename, "created_at": time.strftime('%Y-%m-%d %H:%M:%S'), "content_type": "test_data", "test_data": { "numbers": [1, 2, 3, 4, 5], "strings": ["hello", "world", "test"], "nested": { "level1": { "level2": "deep_value" } } } }, indent=2) content_base64 = base64.b64encode(content.encode('utf-8')).decode('utf-8') return { "filename": filename, "content": content, "content_base64": content_base64, "size": len(content), "encoded_size": len(content_base64) } # Usage examples text_doc = generate_test_document("test_file.txt", "text") json_doc = generate_test_document("test_data.json", "json") ``` ### Batch Document Generator ```python def generate_batch_documents(count=5, prefix="batch_doc"): """Generate multiple test documents for batch operations.""" documents = [] session_id = str(uuid.uuid4())[:8] for i in range(count): content = f"""Batch Document {i+1} Session ID: {session_id} Document Index: {i+1} of {count} Created: {time.strftime('%Y-%m-%d %H:%M:%S')} Unique ID: {uuid.uuid4()} This is batch document number {i+1} created for testing concurrent operations and bulk uploads in the MCP server. Content sections: - Document metadata - Sequential numbering - Timestamp information - Unique identification Processing notes: - Part of batch operation - Sequential creation - Automated generation """ doc = { "filename": f"{prefix}_{session_id}_{i+1:03d}.txt", "content": content, "content_base64": base64.b64encode(content.encode('utf-8')).decode('utf-8'), "description": f"Batch document {i+1} from session {session_id}" } documents.append(doc) return documents # Generate 5 test documents batch_docs = generate_batch_documents(5) ``` ## MCP Upload Examples ### Single Document Upload ```json { "filename": "test_document.txt", "content_base64": "VGhpcyBpcyBhIHRlc3QgZG9jdW1lbnQgZm9yIE1DUCBzZXJ2ZXIgdGVzdGluZy4=", "parent_id": "-root-", "description": "Test upload via MCP Inspector" } ``` ### JSON Document Upload ```json { "filename": "test_data.json", "content_base64": "eyJ0ZXN0IjogImRhdGEiLCAibnVtYmVycyI6IFsxLCAyLCAzXX0=", "parent_id": "-root-", "description": "JSON test data" } ``` ## Validation Functions ### Base64 Validation ```python import re def validate_base64(base64_string): """Validate base64 string format.""" try: # Check format if not re.match(r'^[A-Za-z0-9+/]*={0,2}$', base64_string): return False, "Invalid base64 characters" # Test decode decoded = base64.b64decode(base64_string, validate=True) return True, f"Valid base64 ({len(decoded)} bytes)" except Exception as e: return False, f"Decode error: {str(e)}" # Test validation valid, message = validate_base64("SGVsbG8gV29ybGQ=") print(f"Validation: {valid}, Message: {message}") ``` ### Content Size Calculation ```python def calculate_base64_size(text_content): """Calculate the base64 encoded size before encoding.""" original_bytes = len(text_content.encode('utf-8')) base64_bytes = ((original_bytes + 2) // 3) * 4 return { "original_size": original_bytes, "base64_size": base64_bytes, "size_increase": f"{((base64_bytes / original_bytes - 1) * 100):.1f}%" } # Example size_info = calculate_base64_size("Hello World") print(size_info) # {'original_size': 11, 'base64_size': 16, 'size_increase': '45.5%'} ``` ## Quick Reference ### Common Test Strings ```python TEST_STRINGS = { "hello": "SGVsbG8gV29ybGQ=", "test": "dGVzdA==", "sample": "c2FtcGxl", "document": "ZG9jdW1lbnQ=", "content": "Y29udGVudA==", "data": "ZGF0YQ==", "file": "ZmlsZQ==", "upload": "dXBsb2Fk" } ``` ### Usage in MCP Inspector 1. **Generate content**: Use the generator functions above 2. **Copy base64**: Use the `content_base64` field from generated documents 3. **Test upload**: Paste into MCP Inspector's upload_document tool 4. **Validate**: Check successful upload in Alfresco ### File Size Limits - **Small files**: < 1MB (recommended for testing) - **Medium files**: 1-10MB (stress testing) - **Large files**: > 10MB (may require chunking) Remember: Base64 encoding increases file size by approximately 33%, so plan accordingly for upload limits. ``` -------------------------------------------------------------------------------- /alfresco_mcp_server/utils/file_type_analysis.py: -------------------------------------------------------------------------------- ```python """ File type analysis utility for Alfresco MCP Server. Provides content type analysis and suggestions for different file types. """ import pathlib import mimetypes from typing import Dict, List, Optional def detect_file_extension_from_content(content: bytes) -> Optional[str]: """Detect file type from content and return appropriate extension. Args: content: Raw file content bytes Returns: File extension (e.g., '.pdf', '.txt', '.jpg') or None if unknown """ if not content: return None # Check for common file signatures (magic numbers) if content.startswith(b'%PDF'): return '.pdf' elif content.startswith(b'\xff\xd8\xff'): return '.jpg' elif content.startswith(b'\x89PNG\r\n\x1a\n'): return '.png' elif content.startswith(b'GIF87a') or content.startswith(b'GIF89a'): return '.gif' elif content.startswith(b'PK\x03\x04') or content.startswith(b'PK\x05\x06') or content.startswith(b'PK\x07\x08'): # ZIP-based formats (could be .zip, .docx, .xlsx, .pptx, etc.) # For simplicity, assume .zip unless we detect Office formats if b'word/' in content[:1024] or b'xl/' in content[:1024] or b'ppt/' in content[:1024]: # Likely Office document but hard to determine exact type return '.zip' # Conservative choice return '.zip' elif content.startswith(b'\xd0\xcf\x11\xe0\xa1\xb1\x1a\xe1'): # Microsoft Office old format return '.doc' # Could also be .xls, .ppt but .doc is most common elif content.startswith(b'<?xml') or content.startswith(b'\xef\xbb\xbf<?xml'): return '.xml' elif content.startswith(b'<!DOCTYPE html') or content.startswith(b'<html'): return '.html' elif content.startswith(b'{\n') or content.startswith(b'{"') or content.startswith(b'[\n') or content.startswith(b'[{'): return '.json' else: # Try to detect if it's text content try: # Try to decode as UTF-8 text text_content = content.decode('utf-8') # Check if it contains mostly printable characters printable_chars = sum(1 for c in text_content if c.isprintable() or c.isspace()) if len(text_content) > 0 and printable_chars / len(text_content) > 0.8: return '.txt' except (UnicodeDecodeError, UnicodeError): pass # Unknown binary format return None def analyze_content_type(filename: str, mime_type: str, content: bytes) -> dict: """Analyze file type and provide relevant suggestions. Args: filename: Name of the file mime_type: MIME type of the file content: File content as bytes Returns: Dictionary with category, suggestions, and file_size """ file_size = len(content) # Get case-insensitive filename for macOS/Windows compatibility filename_lower = filename.lower() # Determine file category based on MIME type and extension if mime_type.startswith('image/'): category = 'images' suggestions = [ "Can be used for thumbnails and previews", "Consider image optimization for web use" ] elif mime_type.startswith('video/') or mime_type.startswith('audio/'): category = 'media' suggestions = [ "Large files may need streaming support", "Consider format compatibility for playback" ] elif mime_type in ['application/pdf', 'application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document']: category = 'documents' suggestions = [ "PDF files support full-text search", ">> Consider using text extraction for searchable content", "Can be previewed in most browsers" ] elif mime_type in ['application/vnd.ms-excel', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet']: category = 'documents' suggestions = [ ">> Excel spreadsheet - can be opened with Excel or LibreOffice Calc", ">> May contain tracked changes or comments" ] elif mime_type in ['application/vnd.ms-powerpoint', 'application/vnd.openxmlformats-officedocument.presentationml.presentation']: category = 'documents' suggestions = [ "Presentation files support slide previews", "Consider extracting slide content for search" ] elif mime_type.startswith('text/') or 'javascript' in mime_type or 'json' in mime_type: if filename_lower.endswith(('.py', '.js', '.java', '.cpp', '.c', '.cs', '.php', '.rb')): category = 'code' suggestions = [ "Source code files support syntax highlighting", ">> Check contents before extraction for security", "Can be indexed for code search" ] else: category = 'documents' suggestions = [ ">> Review for security before execution", ">> May require specific runtime environment" ] elif mime_type in ['application/zip', 'application/x-rar-compressed', 'application/gzip']: category = 'archives' suggestions = [ "Archive contents can be extracted and indexed", ">> Check contents before extraction for security", "May contain multiple file types" ] else: category = 'other' suggestions = [ "Unknown file type - review content manually", "Consider file format documentation" ] # Add size-based suggestions if file_size > 100 * 1024 * 1024: # > 100MB suggestions.append("WARNING: Large file - consider network and storage impact") # Add security suggestions for executable files (case-insensitive for cross-platform) executable_extensions = ( '.exe', '.bat', '.sh', '.com', '.scr', # Windows & shell scripts '.app', '.dmg', '.pkg', # macOS '.deb', '.rpm', '.run', '.bin', '.appimage' # Linux ) if filename_lower.endswith(executable_extensions): suggestions = [ "WARNING: Executable file - scan for security before running", "Consider sandboxed execution environment" ] category = 'executable' return { 'category': category, 'suggestions': suggestions, 'file_size': file_size } ``` -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- ```markdown # Changelog All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [1.1.0] - 2025-01-25 ### Alfresco_MCP_Server dir code changes for v1.1 - **Code Split**: Refactored from monolithic single file to modular structure with separate files - **Directory Structure**: Reorganized from flat to hierarchical structure - `tools/search/` - 4 search-related tools - `tools/core/` - 11 core management tools - `resources/` - Repository information - `prompts/` - AI templates - `utils/` - Shared utilities - fix: implement Windows UTF-8 encoding support (emoji character encoding) - get to work with python-alfresco-api 1.1.x - add imports and __all__ to get files included in packaging ### UV Package Management Support - **Rust-based Performance**: UV provides faster dependency resolution and package management - **Automatic Environment Management**: UV handles virtual environment creation and activation - **Simplified Installation**: One-command setup replaces multi-step manual process - **Cross-platform Compatibility**: Consistent behavior across Windows, macOS, and Linux ### PyPI Distribution - **Direct Installation**: Available via `pip install python-alfresco-mcp-server` - **UV Integration**: Compatible with `uv pip install python-alfresco-mcp-server` - **Immediate Usage**: Run server directly after installation without source code ### Update Tests for v1.1 - `test: transform test suite from 76+ failures to 143/143 passing` - `test: add comprehensive live integration testing with 21 Alfresco tests` - `test: implement cross-platform emoji handling for Windows compatibility` - `test: enhance coverage reporting with HTML output` - `test: add automated testing for all manual scenarios` - `test: implement strip_emojis() function for Windows compatibility` - `test: add comprehensive unit test coverage (122 tests)` - `test: fix test import paths for modular architecture` ### MCP Clients: config files added for v1.1 - Added Claude Desktop and MCP Inspector config files - `config: added claude-desktop-config-developer-windows.json with UV support` - `config: added claude-desktop-config-developer-macos.json with UV support` - `config: added claude-desktop-config-user-windows.json` - `config: added claude-desktop-config-user-macos.json` - `config: added Windows UTF-8 encoding variables to windows configs` - `config: added mcp-inspector-http-config.json for HTTP transport using uv` - `config: added mcp-inspector-stdio-config.json for STDIO transport using uv` - 'examples: prompts-for-claude.md has example prompt text to test tools ### Update docs for v1.1 - `docs: create comprehensive CHANGELOG.md for v1.0.0 and v1.1.0` - `docs: update quick start guide for UV approach` - 'docs: api_reference.md added details for all tools, etc - `docs: added claude_desktop_setup.md, mcp_inspector_setup.md, and client_configurations.md ### Documentation Updates for v1.1 - **Installation Instructions**: Added PyPI installation methods - **Configuration Examples**: Updated for UV approach - **Testing Procedures**: Comprehensive test execution guidance - **Troubleshooting**: Enhanced problem resolution guidance ### Update Readme for v1.1 - added install from PyPI - added how to install UV package manager - added claude desktop and mcp inspector setup sections - reword technical sections for accuracy - add v1.1 list of changes, depend on python-alfresco 1.1.1, python 3.10+ - added how to install Alfresco Community from github ### Update project files and misc root dir files for v1.1 - gitignore now ignores cursor memory and memory-bank dir, keep .vscode/mcp.json - config.yaml has changed alfresco_url to have base_url, added timeout, added mcp server name and version confg - MANIFEST.in added for proper package distrubution config - pyproject.toml change to have v1.1.0 release, pypi distrib settings, remove fastMCP version restriction, update to require python 3.10 or greater, add pypi keyword and topic, settings, project urls for pypi, configure setuptools for includes, excludes - run_server.py script added - .vscode/mcp.json uses run_server.py for debugging mcp server - uv.lock add now that use uv, and uv lock --upgrade updated v0.12.5 from v0.12.4 of ruff ### Fixes for pyproject.toml for v1.1 - require python-alfresco-api >= 1.1.1 not 1.0.0 in dependencies - have python 3.10 instead of 3.8 for tool.mypy, and py310 not py38 for tool.ruff and tool.black ### Example Code Updates for v1.1 - `examples: update transport examples for UV approach` - `examples: enhance document lifecycle example` - `examples: add UV-specific installation examples` - `examples: update batch operations for improved performance` - `examples: refactor error handling patterns` ### Requirements - **Python Versions**: 3.10+ (unchanged) - **python-alfresco-api**: >= 1.1.1 (updated requirement) - **FastMCP**: Tested with v2.10.6 ### Tested - **Alfresco Versions**: Community 25.1 (tested), Enterprise (not tested in v1.1) - **Operating Systems**: - Windows (tested) - macOS (needs testing) - Linux (needs testing, note: no Claude Desktop support on Linux) - **MCP Clients**: - Claude Desktop (tested and validated) - MCP Inspector (tested and validated) - Cursor (configuration provided, not tested in v1.1) - Claude Code (configuration provided, not tested in v1.1) ## [1.0.0] - 2024-06-24 ### Added - Initial FastMCP 2.0 server implementation - 15 content management tools across search and core operations - Full text search with wildcard support - Advanced search using AFTS query language - Metadata search with property-based queries - CMIS SQL search capabilities - Complete document lifecycle management (upload, download, checkout, checkin) - Version management with major/minor version support - Folder operations and repository browsing - Property management for documents and folders - Multiple transport protocols (STDIO, HTTP, SSE) - Configuration via environment variables and .env files - Claude Desktop integration with Windows and macOS configs - MCP Inspector support for development testing - Comprehensive documentation and examples - Testing framework with unit and integration tests - Error handling and recovery patterns - Repository discovery and status reporting ### Technical Implementation - python-alfresco-api integration for content services access - Pydantic v2 models for type safety - Async support for concurrent operations - Connection pooling and authentication management - Progress reporting and context logging - Configuration validation and environment setup ``` -------------------------------------------------------------------------------- /alfresco_mcp_server/tools/core/browse_repository.py: -------------------------------------------------------------------------------- ```python """ Browse repository tool for Alfresco MCP Server. Self-contained tool for browsing Alfresco repository structure. """ import logging from typing import Optional from fastmcp import Context from ...utils.connection import ensure_connection logger = logging.getLogger(__name__) async def browse_repository_impl( parent_id: str = "-my-", max_items: int = 25, ctx: Optional[Context] = None ) -> str: """Browse the Alfresco repository structure. Args: parent_id: Parent node ID to browse (default: user's personal space) max_items: Maximum number of items to return (default: 25) ctx: MCP context for progress reporting Returns: Formatted listing of repository contents """ # Parameter validation and extraction try: # Extract parameters with fallback handling if hasattr(parent_id, 'value'): actual_parent_id = str(parent_id.value) else: actual_parent_id = str(parent_id) if hasattr(max_items, 'value'): actual_max_items = int(max_items.value) else: actual_max_items = int(max_items) # Clean and normalize for display (preserve Unicode characters) safe_parent_id_display = str(actual_parent_id) except Exception as e: logger.error(f"Parameter extraction error: {e}") return f"ERROR: Parameter error: {str(e)}" if ctx: await ctx.info(f"Browsing repository node: {safe_parent_id_display}") await ctx.report_progress(0.0) try: # Get all clients that ensure_connection() already created master_client = await ensure_connection() # Access the core client that was already created core_client = master_client.core # Try high-level API first, then use raw client property (NEW: cleaner access) # Check if we can use high-level nodes.get_children() try: # Use high-level API for browsing (preferred approach) children_result = core_client.nodes.get_children(actual_parent_id, max_items=actual_max_items) if children_result and hasattr(children_result, 'list') and hasattr(children_result.list, 'entries'): entries = children_result.list.entries logger.info(f"Browse response via high-level API: {len(entries)} entries found") else: raise Exception("High-level API returned unexpected format") except Exception as high_level_error: logger.info(f"High-level API failed, using raw client: {high_level_error}") # Fallback to raw client (ensure initialization) if not core_client.is_initialized: return safe_format_output("❌ Error: Alfresco server unavailable") # Use httpx_client property directly on AlfrescoCoreClient core_httpx = core_client.httpx_client logger.info(f"Browsing repository node: {safe_parent_id_display}") logger.info(f"Max items: {actual_max_items}") logger.info(f"Using URL: /nodes/{actual_parent_id}/children") if ctx: await ctx.report_progress(0.3) # If high-level API didn't work, use HTTPx fallback if 'entries' not in locals(): if ctx: await ctx.report_progress(0.5) try: # Use HTTPx client as fallback url = f"/nodes/{actual_parent_id}/children" if actual_max_items != 25: url += f"?maxItems={actual_max_items}" response = core_httpx.get(url) if response.status_code == 200: result_data = response.json() entries = result_data.get("list", {}).get("entries", []) logger.info(f"Browse response via HTTPx fallback: {len(entries)} entries found") else: error_text = response.text if hasattr(response, 'text') else str(response) raise Exception(f"Browse failed with status {response.status_code}: {error_text}") except Exception as browse_error: raise Exception(f"Repository browse operation failed: {str(browse_error)}") # Check if we have entries if not entries: return f"Repository Browse Results\n\nNode: {safe_parent_id_display}\n\nNo child items found in this location." if ctx: await ctx.report_progress(1.0) # Process final results if entries: logger.info(f"Found {len(entries)} repository items") # Clean JSON-friendly formatting (no markdown syntax) result_text = f"Repository Browse Results\n\nNode: {safe_parent_id_display}\n\n" result_text += f"Parent Node: {safe_parent_id_display}\n" result_text += f"Found {len(entries)} item(s):\n\n" for i, entry_wrapper in enumerate(entries, 1): # Handle JSON response structure correctly if isinstance(entry_wrapper, dict) and 'entry' in entry_wrapper: entry = entry_wrapper['entry'] else: entry = entry_wrapper # Extract values from dictionary name = str(entry.get('name', 'Unknown')) node_id = str(entry.get('id', 'Unknown')) node_type = str(entry.get('nodeType', 'Unknown')) is_folder = entry.get('isFolder', False) created_at = str(entry.get('createdAt', 'Unknown')) # Choose icon based on type icon = "[FOLDER]" if is_folder else "[FILE]" result_text += f"{i}. {icon} {name}\n" result_text += f" - ID: {node_id}\n" result_text += f" - Type: {node_type}\n" result_text += f" - Created: {created_at}\n\n" result_text += f"Navigation help:\n" result_text += "• Use the node ID to browse deeper: browse_repository(parent_id=\"<node_id>\")\n" result_text += "• Common parent IDs: -root- (repository root), -shared- (shared folder), -my- (my files)\n" return result_text else: return f"Repository Browse Results\n\nNode: {safe_parent_id_display}\n\nNo child items found in this location." except Exception as e: # Preserve Unicode characters in error messages error_msg = f"ERROR: Repository browse failed: {str(e)}" if ctx: await ctx.error(error_msg) return error_msg if ctx: await ctx.info("Repository browse completed!") ``` -------------------------------------------------------------------------------- /scripts/run_tests.py: -------------------------------------------------------------------------------- ```python #!/usr/bin/env python3 """ Comprehensive test runner for Alfresco MCP Server. Provides different test modes: unit, integration, coverage, performance. """ import sys import os import subprocess import argparse from pathlib import Path def run_command(cmd, description=""): """Run a command and handle output.""" print(f"\n{'='*60}") print(f"🚀 {description}" if description else f"Running: {' '.join(cmd)}") print('='*60) try: result = subprocess.run(cmd, check=True, capture_output=True, text=True) print(result.stdout) if result.stderr: print("STDERR:", result.stderr) return True except subprocess.CalledProcessError as e: print(f"❌ Command failed with exit code {e.returncode}") print("STDOUT:", e.stdout) print("STDERR:", e.stderr) return False def install_dependencies(): """Install test dependencies.""" dependencies = [ "pytest>=7.0.0", "pytest-asyncio>=0.21.0", "pytest-cov>=4.0.0", "pytest-xdist>=3.0.0", # For parallel test execution "pytest-mock>=3.10.0", "coverage>=7.0.0", "httpx>=0.24.0", ] print("📦 Installing test dependencies...") for dep in dependencies: cmd = [sys.executable, "-m", "pip", "install", dep] if not run_command(cmd, f"Installing {dep}"): return False return True def run_unit_tests(): """Run unit tests with mocking.""" cmd = [ sys.executable, "-m", "pytest", "tests/test_coverage.py", "tests/test_fastmcp_2_0.py", "tests/test_unit_tools.py", "-v", "--tb=short", "-m", "unit", "--cov=alfresco_mcp_server", "--cov-report=term-missing", "--cov-report=html:htmlcov", "--cov-branch" ] return run_command(cmd, "Running Unit Tests (Fast, Mocked)") def run_integration_tests(): """Run integration tests with live Alfresco.""" cmd = [ sys.executable, "-m", "pytest", "tests/test_integration.py", "-v", "--tb=short", "-m", "integration", "--integration" ] return run_command(cmd, "Running Integration Tests (Requires Live Alfresco)") def run_performance_tests(): """Run performance benchmarks.""" cmd = [ sys.executable, "-m", "pytest", "tests/test_integration.py", "-v", "--tb=short", "-m", "performance", "--integration", "--performance" ] return run_command(cmd, "Running Performance Tests") def run_all_tests(): """Run all tests with comprehensive coverage.""" cmd = [ sys.executable, "-m", "pytest", "tests/", "-v", "--tb=short", "--cov=alfresco_mcp_server", "--cov-report=html:htmlcov", "--cov-report=term-missing", "--cov-report=xml", "--cov-branch", "--cov-fail-under=85", "-x" # Stop on first failure ] return run_command(cmd, "Running All Tests with Coverage") def run_coverage_only(): """Run coverage analysis only.""" # First run tests to generate coverage data cmd = [ sys.executable, "-m", "pytest", "tests/test_coverage.py", "tests/test_fastmcp_2_0.py", "tests/test_unit_tools.py", "--cov=alfresco_mcp_server", "--cov-report=html:htmlcov", "--cov-report=xml", "--cov-branch", "-q" # Quiet mode ] if not run_command(cmd, "Generating Coverage Data"): return False # Generate coverage report cmd = [sys.executable, "-m", "coverage", "report", "--show-missing"] return run_command(cmd, "Coverage Report") def check_alfresco_availability(): """Check if Alfresco server is available.""" try: import httpx response = httpx.get("http://localhost:8080/alfresco/api/-default-/public/alfresco/versions/1/probes/-ready-", timeout=5.0) if response.status_code == 200: print("✅ Alfresco server is available at localhost:8080") return True except Exception as e: print(f"⚠️ Alfresco server not available: {e}") print("Integration tests will be skipped") return False def lint_code(): """Run code linting.""" print("🔍 Running code quality checks...") # Try to install and run linting tools linting_commands = [ ([sys.executable, "-m", "pip", "install", "black", "ruff", "mypy"], "Installing linting tools"), ([sys.executable, "-m", "black", "--check", "alfresco_mcp_server/"], "Black formatting check"), ([sys.executable, "-m", "ruff", "check", "alfresco_mcp_server/"], "Ruff linting"), ] success = True for cmd, desc in linting_commands: if not run_command(cmd, desc): success = False return success def main(): parser = argparse.ArgumentParser(description="Comprehensive test runner for Alfresco MCP Server") parser.add_argument("--mode", choices=["unit", "integration", "performance", "coverage", "all", "lint"], default="unit", help="Test mode to run") parser.add_argument("--install-deps", action="store_true", help="Install test dependencies") parser.add_argument("--check-alfresco", action="store_true", help="Check Alfresco availability") args = parser.parse_args() # Change to project root project_root = Path(__file__).parent.parent os.chdir(project_root) print(f"🧪 Alfresco MCP Server Test Runner") print(f"📁 Working directory: {os.getcwd()}") print(f"🎯 Test mode: {args.mode}") # Install dependencies if requested if args.install_deps: if not install_dependencies(): sys.exit(1) # Check Alfresco availability if requested if args.check_alfresco: check_alfresco_availability() # Run tests based on mode success = True if args.mode == "unit": success = run_unit_tests() elif args.mode == "integration": if not check_alfresco_availability(): print("❌ Alfresco server required for integration tests") sys.exit(1) success = run_integration_tests() elif args.mode == "performance": if not check_alfresco_availability(): print("❌ Alfresco server required for performance tests") sys.exit(1) success = run_performance_tests() elif args.mode == "coverage": success = run_coverage_only() elif args.mode == "all": alfresco_available = check_alfresco_availability() success = run_all_tests() if success and alfresco_available: print("\n🔄 Running integration tests...") success = run_integration_tests() elif args.mode == "lint": success = lint_code() # Summary print(f"\n{'='*60}") if success: print("✅ All tests completed successfully!") print("\n📊 Coverage report available at: htmlcov/index.html") else: print("❌ Some tests failed!") sys.exit(1) print('='*60) if __name__ == "__main__": main() ``` -------------------------------------------------------------------------------- /alfresco_mcp_server/tools/search/search_by_metadata.py: -------------------------------------------------------------------------------- ```python """ Search by metadata tool for Alfresco MCP Server. Each tool is self-contained with its own validation, business logic, and env handling. """ import logging from typing import Optional from fastmcp import Context from ...utils.connection import ensure_connection from ...utils.json_utils import safe_format_output logger = logging.getLogger(__name__) async def search_by_metadata_impl( term: str = "", creator: str = "", content_type: str = "", max_results: int = 25, ctx: Optional[Context] = None ) -> str: """Search for content in Alfresco by metadata fields. Args: term: Search term to include (primary search parameter) creator: Creator username to filter by content_type: Content type to filter by (e.g., "cm:content", "cm:folder") max_results: Maximum number of results to return (default: 25) ctx: MCP context for progress reporting Returns: Formatted search results with metadata """ # Parameter validation and extraction try: # Extract parameters with fallback handling if hasattr(creator, 'value'): actual_creator = str(creator.value) else: actual_creator = str(creator) if hasattr(content_type, 'value'): actual_content_type = str(content_type.value) else: actual_content_type = str(content_type) if hasattr(term, 'value'): actual_term = str(term.value) else: actual_term = str(term) if hasattr(max_results, 'value'): actual_max_results = int(max_results.value) else: actual_max_results = int(max_results) # Clean and normalize for display (preserve Unicode characters) safe_creator_display = str(actual_creator) safe_content_type_display = str(actual_content_type) safe_term_display = str(actual_term) except Exception as e: logger.error(f"Parameter extraction error: {e}") return f"ERROR: Parameter error: {str(e)}" if ctx: await ctx.info(safe_format_output(f"Searching by metadata in Alfresco...")) await ctx.report_progress(0.0) try: # Get all clients that ensure_connection() already created master_client = await ensure_connection() # Import search_utils from python_alfresco_api.utils import search_utils # Access the search client that was already created search_client = master_client.search logger.info(f"Searching Alfresco by metadata - creator: '{safe_creator_display}', type: '{safe_content_type_display}', term: '{safe_term_display}'") if ctx: await ctx.report_progress(0.3) # Build query using search_utils.build_query() utility search_query = search_utils.build_query( term=actual_term if actual_term.strip() else None, content_type=actual_content_type if actual_content_type.strip() else None, creator=actual_creator if actual_creator.strip() else None ) # If no parameters provided, search everything if not search_query or search_query.strip() == "": search_query = "*" # Execute search using existing search client if ctx: await ctx.report_progress(0.5) try: # Use correct working pattern: search_utils.simple_search with existing search_client search_results = search_utils.simple_search(search_client, search_query, max_items=actual_max_results) if not search_results or not hasattr(search_results, 'list_'): return safe_format_output(f"ERROR: Search failed - invalid response from Alfresco") except Exception as e: logger.error(f"Search failed: {e}") return safe_format_output(f"ERROR: Search failed: {str(e)}") # Process results using correct pattern entries = [] if hasattr(search_results, 'list_') and search_results.list_ and hasattr(search_results.list_, 'entries'): entries = search_results.list_.entries if search_results.list_ else [] if ctx: await ctx.report_progress(1.0) # Process final results if entries: logger.info(f"Found {len(entries)} search results") result_text = f"Found {len(entries)} item(s) matching the metadata criteria:\n\n" for i, entry in enumerate(entries, 1): # Debug: Log the entry structure logger.debug(f"Entry {i} type: {type(entry)}, content: {entry}") # Handle different possible entry structures node = None if isinstance(entry, dict): if 'entry' in entry: node = entry['entry'] elif 'name' in entry: # Direct node structure node = entry else: logger.warning(f"Unknown entry structure: {entry}") continue elif hasattr(entry, 'entry'): # ResultSetRowEntry object node = entry.entry else: logger.warning(f"Entry is not a dict or ResultSetRowEntry: {type(entry)}") continue if node: # Handle both dict and ResultNode objects if isinstance(node, dict): name = str(node.get('name', 'Unknown')) node_id = str(node.get('id', 'Unknown')) node_type_actual = str(node.get('nodeType', 'Unknown')) created_at = str(node.get('createdAt', 'Unknown')) else: # ResultNode object - access attributes directly name = str(getattr(node, 'name', 'Unknown')) node_id = str(getattr(node, 'id', 'Unknown')) node_type_actual = str(getattr(node, 'node_type', 'Unknown')) created_at = str(getattr(node, 'created_at', 'Unknown')) # Clean JSON-friendly formatting (no markdown syntax) # Apply safe formatting to individual fields to prevent emoji encoding issues safe_name = safe_format_output(name) safe_node_id = safe_format_output(node_id) safe_node_type = safe_format_output(node_type_actual) safe_created_at = safe_format_output(created_at) result_text += f"{i}. {safe_name}\n" result_text += f" - ID: {safe_node_id}\n" result_text += f" - Type: {safe_node_type}\n" result_text += f" - Created: {safe_created_at}\n\n" return safe_format_output(result_text) else: # Simple "0" for zero results as requested return "0" except Exception as e: # Preserve Unicode characters in error messages error_msg = f"ERROR: Metadata search failed: {str(e)}" if ctx: await ctx.error(safe_format_output(error_msg)) return safe_format_output(error_msg) if ctx: await ctx.info(safe_format_output("Metadata search completed!")) ``` -------------------------------------------------------------------------------- /alfresco_mcp_server/tools/search/cmis_search.py: -------------------------------------------------------------------------------- ```python """ CMIS search tool for Alfresco MCP Server. Each tool is self-contained with its own validation, business logic, and env handling. """ import logging from typing import Optional from fastmcp import Context from ...utils.connection import ensure_connection from ...utils.json_utils import safe_format_output logger = logging.getLogger(__name__) async def cmis_search_impl( cmis_query: str, max_results: int = 25, ctx: Optional[Context] = None ) -> str: """Search using CMIS SQL syntax. Args: cmis_query: CMIS SQL query string max_results: Maximum number of results to return (default: 25) ctx: MCP context for progress reporting Returns: Formatted search results from CMIS query """ # Parameter validation and extraction try: # Extract parameters with fallback handling if hasattr(cmis_query, 'value'): actual_query = str(cmis_query.value) else: actual_query = str(cmis_query) if hasattr(max_results, 'value'): actual_max_results = int(max_results.value) else: actual_max_results = int(max_results) # Clean and normalize for display (preserve Unicode characters) safe_query_display = str(actual_query) except Exception as e: logger.error(f"Parameter extraction error: {e}") return f"ERROR: Parameter error: {str(e)}" if not actual_query.strip(): return """CMIS Search Tool Usage: Provide a CMIS SQL query to search Alfresco repository. Example CMIS queries: - SELECT * FROM cmis:document WHERE cmis:name LIKE 'test%' - SELECT * FROM cmis:folder WHERE CONTAINS('project') - SELECT * FROM cmis:document WHERE cmis:creationDate > '2024-01-01T00:00:00.000Z' - SELECT * FROM cmis:document WHERE cmis:contentStreamMimeType = 'application/pdf' CMIS provides precise SQL queries for exact matching and filtering. """ if ctx: await ctx.info(safe_format_output(f"CMIS search for: '{safe_query_display}'")) await ctx.report_progress(0.0) try: # Get all clients that ensure_connection() already created master_client = await ensure_connection() # Access the search client that was already created (same as other search tools) search_client = master_client.search logger.info(f"CMIS search for: '{safe_query_display}'") if ctx: await ctx.report_progress(0.3) # Use same pattern as other search tools but with CMIS language try: # Import the SearchRequest model for CMIS queries from python_alfresco_api.raw_clients.alfresco_search_client.search_client.models import SearchRequest, RequestQuery, RequestPagination, RequestQueryLanguage from python_alfresco_api.raw_clients.alfresco_search_client.search_client.types import UNSET # Create CMIS search request (same pattern as search_utils.simple_search but with CMIS language) request_query = RequestQuery( query=actual_query, language=RequestQueryLanguage.CMIS # Use CMIS instead of AFTS ) request_pagination = RequestPagination( max_items=actual_max_results, skip_count=0 ) search_request = SearchRequest( query=request_query, paging=request_pagination, include=UNSET ) # Use same pattern as search_utils.simple_search search_results = search_client.search.search(search_request) if search_results and hasattr(search_results, 'list_'): entries_list = search_results.list_.entries if search_results.list_ else [] logger.info(f"Found {len(entries_list)} CMIS search results") if ctx: await ctx.report_progress(1.0) if not entries_list: return "0" result_text = f"Found {len(entries_list)} item(s) matching the CMIS query:\n\n" for i, entry in enumerate(entries_list, 1): # Debug: Log the entry structure logger.debug(f"Entry {i} type: {type(entry)}, content: {entry}") # Handle different possible entry structures node = None if isinstance(entry, dict): if 'entry' in entry: node = entry['entry'] elif 'name' in entry: # Direct node structure node = entry else: logger.warning(f"Unknown entry structure: {entry}") continue elif hasattr(entry, 'entry'): # ResultSetRowEntry object node = entry.entry else: logger.warning(f"Entry is not a dict or ResultSetRowEntry: {type(entry)}") continue if node: # Handle both dict and ResultNode objects if isinstance(node, dict): name = str(node.get('name', 'Unknown')) node_id = str(node.get('id', 'Unknown')) node_type = str(node.get('nodeType', 'Unknown')) created_at = str(node.get('createdAt', 'Unknown')) else: # ResultNode object - access attributes directly name = str(getattr(node, 'name', 'Unknown')) node_id = str(getattr(node, 'id', 'Unknown')) node_type = str(getattr(node, 'node_type', 'Unknown')) created_at = str(getattr(node, 'created_at', 'Unknown')) # Clean JSON-friendly formatting (no markdown syntax) # Apply safe formatting to individual fields to prevent emoji encoding issues safe_name = safe_format_output(name) safe_node_id = safe_format_output(node_id) safe_node_type = safe_format_output(node_type) safe_created_at = safe_format_output(created_at) result_text += f"{i}. {safe_name}\n" result_text += f" - ID: {safe_node_id}\n" result_text += f" - Type: {safe_node_type}\n" result_text += f" - Created: {safe_created_at}\n\n" return safe_format_output(result_text) else: return safe_format_output(f"ERROR: CMIS search failed - invalid response from Alfresco") except Exception as e: logger.error(f"CMIS search failed: {e}") return safe_format_output(f"ERROR: CMIS search failed: {str(e)}") except Exception as e: # Preserve Unicode characters in error messages error_msg = f"ERROR: CMIS search failed: {str(e)}" if ctx: await ctx.error(safe_format_output(error_msg)) return safe_format_output(error_msg) if ctx: await ctx.info(safe_format_output("CMIS search completed!")) ``` -------------------------------------------------------------------------------- /docs/quick_start_guide.md: -------------------------------------------------------------------------------- ```markdown # Quick Start Guide Get up and running with the Alfresco MCP Server in 5 minutes! This guide walks you through installation, configuration, and your first successful connection. ## ⏱️ 5-Minute Setup ### Step 1: Prerequisites (30 seconds) Ensure you have: - ✅ Python 3.10+ installed - ✅ Access to an Alfresco server (local or remote) - ✅ Administrator credentials for Alfresco ```bash # Check Python version python --version # Should be 3.10 or higher ``` ### Step 2: Installation (1 minute) **Option A: UV (Recommended - Automatic dependency management)** ```bash # Install UV if you don't have it powershell -c "irm https://astral.sh/uv/install.ps1 | iex" # Windows # curl -LsSf https://astral.sh/uv/install.sh | sh # macOS/Linux # Clone the repository git clone https://github.com/your-org/python-alfresco-mcp-server.git cd python-alfresco-mcp-server # That's it! UV will handle venv and dependencies automatically # Test with: uv run python-alfresco-mcp-server --help ``` **Option B: Traditional pip (Manual venv management)** ```bash # Clone the repository git clone https://github.com/your-org/python-alfresco-mcp-server.git cd python-alfresco-mcp-server # Create and activate virtual environment python -m venv venv source venv/bin/activate # Linux/macOS # venv\Scripts\activate # Windows # Install the package pip install -e . # Verify installation python -m alfresco_mcp_server.fastmcp_server --help ``` ### Step 3: Configuration (1 minute) Set up your Alfresco connection: ```bash # Option 1: Environment variables (recommended) export ALFRESCO_URL="http://localhost:8080" export ALFRESCO_USERNAME="admin" export ALFRESCO_PASSWORD="admin" # Option 2: Create config.yaml (alternative) cp alfresco_mcp_server/config.yaml.example config.yaml # Edit config.yaml with your settings ``` ### Step 4: Test Connection (30 seconds) ```bash # With UV (recommended) uv run python examples/quick_start.py # With traditional pip python examples/quick_start.py ``` Expected output: ``` 🚀 Alfresco MCP Server - Quick Start Example ================================================== ✅ Connected successfully! 🛠️ Available Tools: 1. search_content - Search for documents and folders 2. upload_document - Upload a new document ... ``` ### Step 5: First MCP Operation (2 minutes) Run your first document search: ```python # Create test_search.py import asyncio from fastmcp import Client from alfresco_mcp_server.fastmcp_server import mcp async def first_search(): async with Client(mcp) as client: # Search for documents result = await client.call_tool("search_content", { "query": "*", "max_results": 5 }) print("Search Results:") print(result[0].text) # Run the search asyncio.run(first_search()) ``` ## 🎉 Success! If you see search results, congratulations! Your Alfresco MCP Server is working. ## 🚀 Next Steps Now that you're connected, try these: ### 1. Upload Your First Document ```python import base64 import asyncio from fastmcp import Client from alfresco_mcp_server.fastmcp_server import mcp async def upload_demo(): async with Client(mcp) as client: # Create sample content content = "Hello from MCP Server!" content_b64 = base64.b64encode(content.encode()).decode() # Upload document result = await client.call_tool("upload_document", { "filename": "hello_mcp.txt", "content_base64": content_b64, "parent_id": "-root-", "description": "My first MCP upload" }) print(result[0].text) asyncio.run(upload_demo()) ``` ### 2. Create a Folder ```python async def create_folder_demo(): async with Client(mcp) as client: result = await client.call_tool("create_folder", { "folder_name": "My_MCP_Folder", "parent_id": "-root-", "description": "Created via MCP Server" }) print(result[0].text) asyncio.run(create_folder_demo()) ``` ### 3. Get Repository Information ```python async def repo_info_demo(): async with Client(mcp) as client: # Get repository info info = await client.read_resource("alfresco://repository/info") print("Repository Info:") print(info[0].text) asyncio.run(repo_info_demo()) ``` ## 🌐 Transport Options The server supports multiple transport protocols: ```bash # STDIO (default) python -m alfresco_mcp_server.fastmcp_server # HTTP python -m alfresco_mcp_server.fastmcp_server --transport http --port 8001 # Server-Sent Events python -m alfresco_mcp_server.fastmcp_server --transport sse --port 8002 ``` ## 🔧 Common Configuration ### Custom Alfresco URL ```bash export ALFRESCO_URL="https://my-alfresco.company.com" ``` ### Authentication Token ```bash export ALFRESCO_TOKEN="your-auth-token" ``` ### Debug Mode ```bash export ALFRESCO_DEBUG="true" ``` ## ⚡ Quick Examples ### Complete Document Workflow ```python import asyncio import base64 from fastmcp import Client from alfresco_mcp_server.fastmcp_server import mcp async def complete_workflow(): async with Client(mcp) as client: # 1. Create folder folder_result = await client.call_tool("create_folder", { "folder_name": "Quick_Start_Demo", "parent_id": "-root-", "description": "Demo folder from quick start" }) print("✅ Folder created") # 2. Upload document content = "This is a demo document created during quick start." content_b64 = base64.b64encode(content.encode()).decode() upload_result = await client.call_tool("upload_document", { "filename": "demo_document.txt", "content_base64": content_b64, "parent_id": "-root-", "description": "Demo document" }) print("✅ Document uploaded") # 3. Search for our content search_result = await client.call_tool("search_content", { "query": "Quick_Start_Demo", "max_results": 10 }) print("✅ Search completed") print("Search Results:", search_result[0].text) # Run the complete workflow asyncio.run(complete_workflow()) ``` ## 🆘 Troubleshooting ### Connection Issues ```bash # Test Alfresco connectivity curl -u admin:admin http://localhost:8080/alfresco/api/-default-/public/alfresco/versions/1/nodes/-root- ``` ### Common Errors **Error: Connection refused** - ✅ Check if Alfresco server is running - ✅ Verify ALFRESCO_URL is correct - ✅ Check firewall settings **Error: Authentication failed** - ✅ Verify username/password - ✅ Check user permissions in Alfresco **Error: Module not found** - ✅ Run `pip install -e .` again - ✅ Check Python virtual environment ### Getting Help - 📖 Read the [troubleshooting guide](troubleshooting.md) - 💬 Check GitHub Issues for common questions - 🐛 Report issues on GitHub ## 📖 What's Next? Explore more advanced features: - 📄 **[Document Lifecycle](../examples/document_lifecycle.py)** - Complete document management - 🔍 **[Search Examples](../examples/search_examples.py)** - Advanced search patterns - ⚡ **[Batch Operations](../examples/batch_operations.py)** - Bulk processing - 🌐 **[Transport Examples](../examples/transport_examples.py)** - Different connection methods ## 🎯 Key Concepts - **MCP Tools**: 15 tools for document management (search, upload, download, checkout/checkin workflow, etc.) - **Transport Protocols**: STDIO, HTTP, SSE for different use cases - **Resources**: Repository information and health status - **Prompts**: AI-powered analysis and insights --- **🚀 Complete!** You've successfully set up the Alfresco MCP Server. You're now ready to build document management integrations! ``` -------------------------------------------------------------------------------- /alfresco_mcp_server/tools/search/search_content.py: -------------------------------------------------------------------------------- ```python """ Content search tool for Alfresco MCP Server. Each tool is self-contained with its own validation, business logic, and env handling. """ import logging from typing import Optional from fastmcp import Context from ...utils.connection import ensure_connection from ...utils.json_utils import safe_format_output logger = logging.getLogger(__name__) async def search_content_impl( search_query: str, max_results: int = 25, node_type: str = "cm:content", ctx: Optional[Context] = None ) -> str: """Search for content in Alfresco repository. Args: search_query: Search query string max_results: Maximum number of results to return (default: 25) node_type: Type of nodes to search for (default: "cm:content" - searches documents) ctx: MCP context for progress reporting Returns: Formatted search results """ # Parameter validation and extraction try: # Extract parameters with fallback handling if hasattr(search_query, 'value'): actual_query = str(search_query.value) else: actual_query = str(search_query) if hasattr(max_results, 'value'): actual_max_results = int(max_results.value) else: actual_max_results = int(max_results) if hasattr(node_type, 'value'): actual_node_type = str(node_type.value) else: actual_node_type = str(node_type) # Default to cm:content if empty if not actual_node_type.strip(): actual_node_type = "cm:content" # Clean and normalize for display (prevent Unicode encoding issues) safe_query_display = safe_format_output(str(actual_query)) safe_node_type_display = safe_format_output(str(actual_node_type)) except Exception as e: logger.error(f"Parameter extraction error: {e}") return safe_format_output(f"ERROR: Parameter error: {str(e)}") if not actual_query.strip(): return """Content Search Tool Usage: Provide a search query to search Alfresco repository content. Example searches: - admin (finds items with 'admin' in name or content) - name:test* (finds items with names starting with 'test') - modified:[2024-01-01 TO 2024-12-31] (finds items modified in 2024) - TYPE:"cm:content" (finds all documents) - TYPE:"cm:folder" (finds all folders) Search uses AFTS (Alfresco Full Text Search) syntax for flexible content discovery. By default, searches for documents (cm:content) unless a different type is specified. """ if ctx: await ctx.info(safe_format_output(f"Content search for: '{safe_query_display}'")) await ctx.report_progress(0.0) try: # Get all clients that ensure_connection() already created master_client = await ensure_connection() # Import search_utils from python_alfresco_api.utils import search_utils # Access the search client that was already created search_client = master_client.search logger.info(f"Content search for: '{safe_query_display}', type: '{safe_node_type_display}'") if ctx: await ctx.report_progress(0.3) # Build search query to include node_type filter final_query = actual_query # Add node_type filter if not already in query has_type_in_query = "TYPE:" in final_query.upper() if not has_type_in_query: if final_query == "*": final_query = f'TYPE:"{actual_node_type}"' else: final_query = f'({final_query}) AND TYPE:"{actual_node_type}"' # Use the correct working pattern: search_utils.simple_search with existing search_client try: search_results = search_utils.simple_search(search_client, final_query, max_items=actual_max_results) if search_results and hasattr(search_results, 'list_'): entries_list = search_results.list_.entries if search_results.list_ else [] logger.info(f"Found {len(entries_list)} content search results") if ctx: await ctx.report_progress(1.0) if not entries_list: return "0" result_text = f"Found {len(entries_list)} item(s) matching the search query:\n\n" for i, entry in enumerate(entries_list, 1): # Debug: Log the entry structure logger.debug(f"Entry {i} type: {type(entry)}, content: {entry}") # Handle different possible entry structures node = None if isinstance(entry, dict): if 'entry' in entry: node = entry['entry'] elif 'name' in entry: # Direct node structure node = entry else: logger.warning(f"Unknown entry structure: {entry}") continue elif hasattr(entry, 'entry'): # ResultSetRowEntry object node = entry.entry else: logger.warning(f"Entry is not a dict or ResultSetRowEntry: {type(entry)}") continue if node: # Handle both dict and ResultNode objects if isinstance(node, dict): name = str(node.get('name', 'Unknown')) node_id = str(node.get('id', 'Unknown')) node_type_actual = str(node.get('nodeType', 'Unknown')) created_at = str(node.get('createdAt', 'Unknown')) else: # ResultNode object - access attributes directly name = str(getattr(node, 'name', 'Unknown')) node_id = str(getattr(node, 'id', 'Unknown')) node_type_actual = str(getattr(node, 'node_type', 'Unknown')) created_at = str(getattr(node, 'created_at', 'Unknown')) # Clean JSON-friendly formatting (no markdown syntax) # Apply safe formatting to individual fields to prevent emoji encoding issues safe_name = safe_format_output(name) safe_node_id = safe_format_output(node_id) safe_node_type = safe_format_output(node_type_actual) safe_created_at = safe_format_output(created_at) result_text += f"{i}. {safe_name}\n" result_text += f" - ID: {safe_node_id}\n" result_text += f" - Type: {safe_node_type}\n" result_text += f" - Created: {safe_created_at}\n\n" return safe_format_output(result_text) else: return safe_format_output(f"ERROR: Content search failed - invalid response from Alfresco") except Exception as e: logger.error(f"Content search failed: {e}") return safe_format_output(f"ERROR: Content search failed: {str(e)}") except Exception as e: # Preserve Unicode characters in error messages error_msg = f"ERROR: Content search failed: {str(e)}" if ctx: await ctx.error(safe_format_output(error_msg)) return safe_format_output(error_msg) if ctx: await ctx.info(safe_format_output("Content search completed!")) ``` -------------------------------------------------------------------------------- /docs/mcp_inspector_setup.md: -------------------------------------------------------------------------------- ```markdown # MCP Inspector Setup Guide MCP Inspector is a debugging and testing tool for Model Context Protocol servers. This guide covers setup and connection for the Python Alfresco MCP Server. ## 🔍 MCP Inspector Overview MCP Inspector provides: - **Tool Testing**: Interactive testing of all 15 MCP tools - **Resource Access**: View repository information and other resources - **Protocol Debugging**: Monitor MCP protocol messages - **Real-time Testing**: Live interaction with Alfresco server ## 🚀 Installation & Setup ### Prerequisites 1. **Node.js**: Required for running MCP Inspector 2. **Running MCP Server**: Your Python Alfresco MCP Server must be running 3. **Alfresco Server**: Live Alfresco instance for testing ### Method 1: Using Config File (Recommended) This method uses the included configuration files and avoids proxy connection errors. #### Step 1: Start MCP Server with HTTP Transport **With UV (Recommended):** ```bash uv run python-alfresco-mcp-server --transport http --port 8003 ``` **Traditional Python:** ```bash python -m alfresco_mcp_server.fastmcp_server --transport http --port 8003 ``` #### Step 2: Start MCP Inspector Use the included configuration file: ```bash npx @modelcontextprotocol/inspector --config mcp-inspector-http-config.json --server python-alfresco-mcp-server ``` #### Step 3: Open Browser MCP Inspector will provide a URL with pre-filled authentication token: ``` 🔗 Open inspector with token pre-filled: http://localhost:6274/?MCP_PROXY_AUTH_TOKEN=d7a62ab6e032eefe5d85e807c50e13b9fffcd12badbf8bbc3377659c0be4fa8d ``` ### Method 2: Manual Connection #### Step 1: Start MCP Inspector ```bash npx @modelcontextprotocol/inspector ``` #### Step 2: Connect to Server 1. Open browser to `http://localhost:6274` 2. Click **"Add Server"** or use server connection field 3. Enter server URL: `http://localhost:8003` 4. Select transport: **HTTP** 5. Click **Connect** ## 📋 Configuration Files The project includes pre-configured files for easy setup: ### HTTP Transport Configuration **File**: `mcp-inspector-http-config.json` ```json { "servers": { "python-alfresco-mcp-server": { "command": "uv", "args": ["run", "python-alfresco-mcp-server", "--transport", "http", "--port", "8003"], "env": { "ALFRESCO_URL": "http://localhost:8080", "ALFRESCO_USERNAME": "admin", "ALFRESCO_PASSWORD": "admin" } } } } ``` ### STDIO Transport Configuration **File**: `mcp-inspector-stdio-config.json` ```json { "servers": { "python-alfresco-mcp-server": { "command": "uv", "args": ["run", "python-alfresco-mcp-server"], "env": { "ALFRESCO_URL": "http://localhost:8080", "ALFRESCO_USERNAME": "admin", "ALFRESCO_PASSWORD": "admin" } } } } ``` ## 🧪 Testing Tools and Features ### Available Tools (15 Total) Once connected, you can test all tools: #### Search Tools (4) - **search_content**: Full text search - **advanced_search**: AFTS query language - **search_by_metadata**: Property-based queries - **cmis_search**: CMIS SQL queries #### Core Tools (11) - **browse_repository**: Browse folders - **repository_info**: Get system information - **upload_document**: Upload files - **download_document**: Download content - **create_folder**: Create directories - **get_node_properties**: View metadata - **update_node_properties**: Modify metadata - **delete_node**: Remove content - **checkout_document**: Lock for editing - **checkin_document**: Save changes - **cancel_checkout**: Cancel editing ### Resources - **repository_info**: Repository status and configuration ### Prompts - **search_and_analyze**: Interactive search form ## 🔧 Usage Examples ### Basic Testing Workflow 1. **Start with Repository Info**: ```json Tool: repository_info Parameters: {} ``` 2. **Search for Content**: ```json Tool: search_content Parameters: { "query": "test", "max_results": 10 } ``` 3. **Browse Repository**: ```json Tool: browse_repository Parameters: { "node_id": "-root-" } ``` 4. **Upload Test Document**: ```json Tool: upload_document Parameters: { "filename": "test.txt", "content_base64": "VGVzdCBjb250ZW50", "parent_id": "-root-", "description": "Test upload" } ``` ### Advanced Testing **CMIS SQL Search**: ```json Tool: cmis_search Parameters: { "cmis_query": "SELECT * FROM cmis:document WHERE cmis:name LIKE '%test%'", "max_results": 5 } ``` **Metadata Search**: ```json Tool: search_by_metadata Parameters: { "property_name": "cm:name", "property_value": "test", "comparison": "contains" } ``` ## 🛠️ Troubleshooting ### Common Issues #### 1. "Cannot connect to server" - **Check**: MCP server is running on correct port - **Verify**: `curl http://localhost:8003/health` returns response - **Solution**: Restart MCP server with HTTP transport #### 2. "Proxy connection failed" - **Use**: Config file method instead of manual connection - **Check**: No other service using port 8003 - **Alternative**: Try different port: `--port 8004` #### 3. "Authentication failed" - **Check**: Environment variables are set correctly - **Verify**: Alfresco server is accessible - **Test**: `curl -u admin:admin http://localhost:8080/alfresco/api/-default-/public/alfresco/versions/1/nodes/-root-` #### 4. "Port 6274 already in use" - **Auto-resolution**: MCP Inspector finds next available port - **Manual**: Check what port is actually used in startup message - **Check**: `netstat -an | findstr :6274` ### Network Issues **Check ports**: ```bash # Windows netstat -an | findstr ":6274\|:8003\|:8080" # Linux/macOS netstat -an | grep ":6274\|:8003\|:8080" ``` **Test connectivity**: ```bash # Test MCP server curl http://localhost:8003 # Test Alfresco server curl http://localhost:8080/alfresco ``` ### Tool-Specific Issues #### Search Returns No Results - **Check**: Alfresco has content indexed - **Verify**: Search service is running - **Test**: Simple query like `*` to return all content #### Upload Fails - **Check**: Base64 encoding is correct - **Verify**: Parent folder exists and is writable - **Test**: Upload to Company Home (-root-) #### Authentication Errors - **Verify**: Username/password in environment variables - **Check**: User has required permissions - **Test**: Basic authentication with curl ## 📊 Expected Behavior ### Successful Connection When properly connected, you should see: - ✅ Green connection status - 📊 List of 15 available tools - 🔍 Repository resource available - 🎯 Search and analyze prompt available ### Typical Response Times - **Search operations**: < 1 second - **Repository browsing**: < 500ms - **Document upload**: 1-3 seconds - **Download operations**: 1-2 seconds ## 🔗 Alternative Testing Methods ### Command Line Testing Use the examples in [`examples/`](../examples/) for programmatic testing: ```bash python examples/quick_start.py python examples/document_lifecycle.py ``` ### Integration Tests Run the automated test suite: ```bash pytest tests/ -m integration ``` ### Manual Testing with Claude Desktop See [`prompts-for-claude.md`](../prompts-for-claude.md) for 14 manual test scenarios. ## 📚 Additional Resources - **[MCP Inspector Documentation](https://github.com/modelcontextprotocol/inspector)**: Official documentation - **[API Reference](./api_reference.md)**: Complete tool documentation - **[Troubleshooting Guide](./troubleshooting.md)**: Problem diagnosis - **[Configuration Guide](./configuration_guide.md)**: Advanced setup options ## 🎯 Quick Start Summary 1. **Start MCP Server**: `uv run python-alfresco-mcp-server --transport http --port 8003` 2. **Start Inspector**: `npx @modelcontextprotocol/inspector --config mcp-inspector-http-config.json --server python-alfresco-mcp-server` 3. **Open URL**: Use the provided URL with pre-filled token 4. **Test Tools**: Start with `repository_info` then explore other tools This provides a comprehensive testing environment for validating all Alfresco MCP server functionality! ```