diff --git a/CHANGELOG.md b/CHANGELOG.md index ea11da9..02cf29b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,23 @@ All notable changes to the Dolibarr MCP Server 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). +## [Unreleased] + +### Changed +- Clarified cross-platform installation instructions, including Visual Studio developer shell usage on Windows. +- Trimmed runtime dependencies to match the actual imports and exposed developer extras for the test tooling. + +## [1.1.0] - 2024-05-22 + +### Removed +- Legacy helper scripts, installers and manual test programs that duplicated the automated test-suite +- Alternative server implementations (`simple_client`, `standalone_server`, `ultra_simple_server`) in favour of the single `dolibarr_mcp_server` +- Redundant documentation fragments and variant requirements files that no longer reflected the current project layout + +### Changed +- Rewrote the README to highlight the streamlined structure and provide concise installation/run instructions +- Clarified that `dolibarr_mcp_server.py` is the definitive MCP entry point + ## [1.0.0] - 2024-01-26 ### 🎯 Major Restructuring @@ -26,12 +43,7 @@ This release represents a complete restructuring of the Dolibarr MCP Server to m - Streamlined .gitignore file ### Removed -- All test scripts from root directory (moved to `tests/`) -- Multiple batch files (consolidated functionality) -- Alternative server implementations (simple_client, standalone_server, ultra_simple_server) -- Redundant requirements files (kept only requirements.txt) -- Unnecessary documentation files (CLAUDE_CONFIG.md, CONFIG_COMPATIBILITY.md, etc.) -- API directory and contents +- Outdated prototype assets from the first public release ### Technical Improvements - Single, focused MCP server implementation diff --git a/CLAUDE_CONFIG.md b/CLAUDE_CONFIG.md deleted file mode 100644 index 36f7d15..0000000 --- a/CLAUDE_CONFIG.md +++ /dev/null @@ -1,59 +0,0 @@ -# Claude Desktop MCP Configuration for Dolibarr - -## Installation Instructions - -1. First run the installation fix: - ``` - fix_installation.bat - ``` - -2. Add to your Claude Desktop config file (`%APPDATA%\Claude\claude_desktop_config.json`): - -```json -{ - "mcpServers": { - "dolibarr-python": { - "command": "python", - "args": [ - "C:\\Users\\[YOUR_USERNAME]\\GitHub\\dolibarr-mcp\\mcp_server_launcher.py" - ], - "env": { - "DOLIBARR_URL": "https://your-dolibarr-instance.com/api/index.php", - "DOLIBARR_API_KEY": "your-api-key-here" - } - } - } -} -``` - -### Alternative Configuration (if above doesn't work): - -```json -{ - "mcpServers": { - "dolibarr-python": { - "command": "C:\\Users\\[YOUR_USERNAME]\\GitHub\\dolibarr-mcp\\venv_dolibarr\\Scripts\\python.exe", - "args": [ - "-m", - "dolibarr_mcp" - ], - "cwd": "C:\\Users\\[YOUR_USERNAME]\\GitHub\\dolibarr-mcp" - } - } -} -``` - -## Troubleshooting - -1. **Module not found error**: Run `fix_installation.bat` -2. **API connection error**: Check your `.env` file has correct credentials -3. **Server doesn't start**: Try running `python mcp_server_launcher.py` manually to see errors - -## Testing the Server - -After configuration, restart Claude Desktop and check if the server is listed in available MCP servers. - -You can test with commands like: -- "Test Dolibarr connection" -- "List Dolibarr users" -- "Show Dolibarr products" diff --git a/CLEANUP_PLAN.md b/CLEANUP_PLAN.md deleted file mode 100644 index 2469acd..0000000 --- a/CLEANUP_PLAN.md +++ /dev/null @@ -1,96 +0,0 @@ -# Cleanup Plan for Dolibarr MCP - -## Files to be REMOVED - -### Test Scripts in Root Directory (to be removed) -- `test_api_connection.py` -- `test_api_debug.py` -- `test_connection.py` -- `test_dolibarr_mcp.py` -- `test_install.py` -- `test_standalone.py` -- `test_ultra.py` -- `test_ultra_direct.py` -- `diagnose_and_fix.py` - -### Batch Files (to be consolidated/removed) -- `cleanup.bat` -- `fix_installation.bat` -- `run_dolibarr_mcp.bat` -- `run_server.bat` -- `run_standalone.bat` -- `run_ultra.bat` -- `setup.bat` -- `setup_claude_complete.bat` -- `setup_manual.bat` -- `setup_standalone.bat` -- `setup_ultra.bat` -- `setup_windows_fix.bat` -- `start_server.bat` -- `validate_claude_config.bat` - -### Python Scripts in Root (to be removed) -- `mcp_server_launcher.py` -- `setup_env.py` - -### Alternative Server Implementations (to be removed from src/) -- `src/dolibarr_mcp/simple_client.py` -- `src/dolibarr_mcp/standalone_server.py` -- `src/dolibarr_mcp/ultra_simple_server.py` - -### Multiple Requirements Files (to be consolidated) -- `requirements-minimal.txt` -- `requirements-ultra-minimal.txt` -- `requirements-windows.txt` -(Keep only `requirements.txt`) - -### Documentation Files (to be removed) -- `README_DE.md` -- `CLAUDE_CONFIG.md` -- `CONFIG_COMPATIBILITY.md` -- `MCP_FIX_GUIDE.md` -- `ULTRA-SOLUTION.md` - -### API Directory (to be removed) -- `api/` directory and all its contents - -## Files to KEEP (matching prestashop-mcp structure) - -### Root Directory -- `.env.example` -- `.gitignore` -- `LICENSE` -- `README.md` (already updated) -- `CHANGELOG.md` -- `pyproject.toml` -- `requirements.txt` -- `Dockerfile` -- `docker-compose.yml` -- `setup.py` -- `setup.sh` - -### Source Directory -- `src/dolibarr_mcp/__init__.py` -- `src/dolibarr_mcp/__main__.py` -- `src/dolibarr_mcp/cli.py` -- `src/dolibarr_mcp/config.py` -- `src/dolibarr_mcp/dolibarr_client.py` -- `src/dolibarr_mcp/dolibarr_mcp_server.py` - -### Tests Directory -- `tests/__init__.py` -- `tests/test_dolibarr_client.py` -- Tests will be restructured to match prestashop-mcp pattern - -## Next Steps - -1. Remove all files listed above -2. Update pyproject.toml to match prestashop-mcp structure -3. Update requirements.txt to contain only necessary dependencies -4. Create proper test structure in tests/ directory -5. Update .gitignore to match prestashop-mcp -6. Update CHANGELOG.md to document the restructuring - -## Goal - -Create a clean, maintainable structure that matches the prestashop-mcp reference implementation. diff --git a/CONFIG_COMPATIBILITY.md b/CONFIG_COMPATIBILITY.md deleted file mode 100644 index 3031b97..0000000 --- a/CONFIG_COMPATIBILITY.md +++ /dev/null @@ -1,92 +0,0 @@ -# Configuration Compatibility Notice - -## Environment Variable Support - -The Dolibarr MCP server now supports both naming conventions for the API URL: - -- `DOLIBARR_URL` (recommended) -- `DOLIBARR_BASE_URL` (alternative, for backward compatibility) - -Both will work correctly. The server automatically checks for both and uses whichever is set. - -## Claude Desktop Configuration - -Your existing configuration is **fully compatible** with the updated code: - -```json -{ - "mcpServers": { - "dolibarr-python": { - "command": "C:\\Users\\gino\\GitHub\\dolibarr-mcp\\venv_dolibarr\\Scripts\\python.exe", - "args": [ - "-m", - "dolibarr_mcp.dolibarr_mcp_server" - ], - "cwd": "C:\\Users\\gino\\Github\\dolibarr-mcp", - "env": { - "DOLIBARR_BASE_URL": "https://db.ginos.cloud/api/index.php/", - "DOLIBARR_API_KEY": "7cxAAO835BF7bXy6DsQ2j2a7nT6ectGY" - } - } - } -} -``` - -## Quick Validation - -To ensure everything works correctly, run: - -```bash -validate_claude_config.bat -``` - -This will: -1. Check your virtual environment -2. Install/update all dependencies -3. Test module imports -4. Validate environment variables -5. Test server startup - -## Installation Steps (if needed) - -1. **Navigate to project directory:** - ```bash - cd C:\Users\gino\GitHub\dolibarr-mcp - ``` - -2. **Run the validator:** - ```bash - validate_claude_config.bat - ``` - -3. **Restart Claude Desktop** after any configuration changes - -## Troubleshooting - -If you encounter issues: - -1. **Run diagnostic tool:** - ```bash - python diagnose_and_fix.py - ``` - -2. **Check module installation:** - ```bash - venv_dolibarr\Scripts\python.exe -m pip install -e . - ``` - -3. **Test server directly:** - ```bash - venv_dolibarr\Scripts\python.exe -m dolibarr_mcp.dolibarr_mcp_server - ``` - -## URL Format Notes - -The server automatically handles various URL formats: -- `https://db.ginos.cloud` β†’ becomes `https://db.ginos.cloud/api/index.php` -- `https://db.ginos.cloud/api/index.php/` β†’ trailing slash is removed -- `https://db.ginos.cloud/api/index.php` β†’ used as-is - -## Status - -βœ… Your configuration is **ready to use** without any changes needed. diff --git a/MCP_FIX_GUIDE.md b/MCP_FIX_GUIDE.md deleted file mode 100644 index fa75b98..0000000 --- a/MCP_FIX_GUIDE.md +++ /dev/null @@ -1,148 +0,0 @@ -# MCP Server Installation Fix Guide - -## Quick Fix (Recommended) - -If you're experiencing the "ModuleNotFoundError: No module named 'dolibarr_mcp'" error, follow these steps: - -### Step 1: Run Diagnostic Tool -```bash -python diagnose_and_fix.py -``` -This will automatically identify and fix most issues. - -### Step 2: Run Installation Fix -```bash -fix_installation.bat -``` -This ensures the package is properly installed. - -### Step 3: Test the Server -```bash -python mcp_server_launcher.py -``` - -## Manual Fix Steps - -If the automatic fixes don't work: - -### 1. Ensure You're in the Right Directory -```bash -cd C:\Users\[YOUR_USERNAME]\GitHub\dolibarr-mcp -``` - -### 2. Create/Activate Virtual Environment -```bash -python -m venv venv_dolibarr -venv_dolibarr\Scripts\activate -``` - -### 3. Install Package in Development Mode -```bash -pip install -e . -``` - -### 4. Install Required Dependencies -```bash -pip install requests python-dotenv mcp -``` - -### 5. Configure Environment -Create a `.env` file with your Dolibarr credentials: -``` -DOLIBARR_URL=https://your-dolibarr.com/api/index.php -DOLIBARR_API_KEY=your-api-key-here -``` - -### 6. Update Claude Desktop Configuration - -Edit `%APPDATA%\Claude\claude_desktop_config.json`: - -```json -{ - "mcpServers": { - "dolibarr-python": { - "command": "C:\\Users\\[YOUR_USERNAME]\\GitHub\\dolibarr-mcp\\venv_dolibarr\\Scripts\\python.exe", - "args": [ - "C:\\Users\\[YOUR_USERNAME]\\GitHub\\dolibarr-mcp\\mcp_server_launcher.py" - ] - } - } -} -``` - -## Common Issues and Solutions - -### Issue: ModuleNotFoundError -**Solution**: The package isn't installed. Run: -```bash -pip install -e . -``` - -### Issue: ImportError for dependencies -**Solution**: Install missing dependencies: -```bash -pip install requests python-dotenv mcp aiohttp pydantic -``` - -### Issue: Server starts but immediately stops -**Solution**: Check your `.env` file has valid credentials - -### Issue: "No module named 'src'" -**Solution**: You're running from wrong directory. Navigate to project root: -```bash -cd C:\Users\[YOUR_USERNAME]\GitHub\dolibarr-mcp -``` - -## Testing the Installation - -### Test 1: Check Module Import -```bash -python -c "from src.dolibarr_mcp import __version__; print(f'Version: {__version__}')" -``` - -### Test 2: Run Server Directly -```bash -python src/dolibarr_mcp/dolibarr_mcp_server.py -``` - -### Test 3: Check MCP Connection -```bash -python test_connection.py -``` - -## File Structure Verification - -Ensure these files exist: -``` -dolibarr-mcp/ -β”œβ”€β”€ src/ -β”‚ └── dolibarr_mcp/ -β”‚ β”œβ”€β”€ __init__.py -β”‚ β”œβ”€β”€ __main__.py -β”‚ β”œβ”€β”€ dolibarr_mcp_server.py -β”‚ β”œβ”€β”€ dolibarr_client.py -β”‚ └── config.py -β”œβ”€β”€ .env (with your credentials) -β”œβ”€β”€ setup.py -β”œβ”€β”€ pyproject.toml -β”œβ”€β”€ mcp_server_launcher.py -β”œβ”€β”€ diagnose_and_fix.py -└── fix_installation.bat -``` - -## Support - -If you continue to experience issues: -1. Run `diagnose_and_fix.py` and share the output -2. Check the [GitHub Issues](https://github.com/latinogino/dolibarr-mcp/issues) -3. Ensure you have Python 3.8+ installed -4. Try the alternative ultra-simple server: `python src/dolibarr_mcp/ultra_simple_server.py` - -## Alternative: Docker Installation - -If local installation continues to fail, use Docker: -```bash -docker-compose up -``` - -This will run the server in an isolated container with all dependencies pre-configured. diff --git a/README.md b/README.md index 4a393e9..baf2517 100644 --- a/README.md +++ b/README.md @@ -1,387 +1,126 @@ # Dolibarr MCP Server -A professional Model Context Protocol (MCP) Server for complete management of Dolibarr ERP/CRM systems. +A focused Model Context Protocol (MCP) server for managing a Dolibarr ERP/CRM instance. +The MCP entry point lives in [`src/dolibarr_mcp/dolibarr_mcp_server.py`](src/dolibarr_mcp/dolibarr_mcp_server.py) and exposes +Dolibarr management tools to MCP compatible clients such as Claude Desktop. -## πŸš€ Overview +## Repository layout -This MCP Server enables complete management of your Dolibarr ERP/CRM through AI applications like Claude Desktop. With specialized tools, you can manage all aspects of your business - from customers and products to invoices, orders, and contacts. +| Path | Purpose | +| --- | --- | +| `src/dolibarr_mcp/` | MCP server, configuration helpers and CLI utilities | +| `tests/` | Automated pytest suite covering configuration and client logic | +| `api/` | Notes collected while analysing the Dolibarr REST API | -## ✨ Features +Everything else in the repository supports one of these three areas. -- **πŸ’Ό Complete ERP/CRM Management** - Tools for all business areas -- **πŸ‘₯ Customer & Contact Management** - Full CRM functionality -- **πŸ“¦ Product & Service Management** - Complete inventory control -- **πŸ’° Financial Management** - Invoices, orders, and payments -- **πŸ—οΈ MCP Protocol Compliance** for seamless AI integration -- **⚑ Async/Await Architecture** for maximum performance -- **πŸ›‘οΈ Comprehensive Error Handling** and validation -- **πŸ”§ Production-Ready** with complete test suite -- **🐳 Docker Support** for easy deployment +## Installation -## πŸ› οΈ Available Tools +### Linux / macOS -### πŸ‘₯ Customer Management (Third Parties) -- `get_customers` - Retrieve and filter customers -- `get_customer_by_id` - Get specific customer details -- `create_customer` - Create new customers -- `update_customer` - Edit customer data -- `delete_customer` - Remove customers +```bash +# Clone the repository +git clone https://github.com/latinogino/dolibarr-mcp.git +cd dolibarr-mcp -### πŸ“¦ Product Management -- `get_products` - List all products -- `get_product_by_id` - Get specific product details -- `create_product` - Create new products/services -- `update_product` - Edit product information -- `delete_product` - Remove products +# Create and activate a virtual environment +python3 -m venv .venv +source .venv/bin/activate -### πŸ’° Invoice Management -- `get_invoices` - Retrieve and filter invoices -- `get_invoice_by_id` - Get specific invoice details -- `create_invoice` - Create new invoices -- `update_invoice` - Edit invoice information -- `delete_invoice` - Remove invoices +# Install the package in editable mode together with runtime dependencies +pip install -e . +``` -### πŸ“‹ Order Management -- `get_orders` - Retrieve and filter orders -- `get_order_by_id` - Get specific order details -- `create_order` - Create new orders -- `update_order` - Edit order information -- `delete_order` - Remove orders +### Windows (PowerShell) -### πŸ‘€ Contact Management -- `get_contacts` - List all contacts -- `get_contact_by_id` - Get specific contact details -- `create_contact` - Create new contacts -- `update_contact` - Edit contact information -- `delete_contact` - Remove contacts - -### πŸ‘€ User Management -- `get_users` - List system users -- `get_user_by_id` - Get specific user details -- `create_user` - Create new users -- `update_user` - Edit user information -- `delete_user` - Remove users - -### βš™οΈ System Administration -- `test_connection` - Test API connection -- `get_status` - System status and version -- `dolibarr_raw_api` - Direct API access for advanced operations - -## πŸ“‹ Installation - -### ⚠️ Recommended Installation (Virtual Environment) - -**This approach prevents module conflicts and ensures reliable installation:** - -#### Windows: ```powershell -# Clone repository +# Launch a Visual Studio developer shell so native extensions such as aiohttp can build +vsenv + +# Clone the repository git clone https://github.com/latinogino/dolibarr-mcp.git -cd dolibarr-mcp +Set-Location dolibarr-mcp -# Create virtual environment -python -m venv venv_dolibarr +# Create and activate a virtual environment +py -3 -m venv .venv +.\.venv\Scripts\Activate.ps1 -# Activate virtual environment -.\venv_dolibarr\Scripts\Activate.ps1 - -# Install dependencies -pip install -r requirements.txt - -# Install package in development mode +# Install the package in editable mode together with runtime dependencies pip install -e . - -# Verify installation -python -c "import dolibarr_mcp; print('βœ… Installation successful')" - -# Note the Python path for Claude Desktop configuration -Write-Host "Python Path: $((Get-Command python).Source)" ``` -#### Linux/macOS: +> πŸ’‘ If you do not already have the Visual Studio developer PowerShell available, open the +> **"Developer PowerShell for VS"** shortcut first. Inside that shell the `vsenv` command +> initialises the Visual Studio build environment that `pip` needs to compile `aiohttp` and +> other native wheels on Windows. + +For contributors who need the development tooling (pytest, coverage, etc.) install the optional +extras: + ```bash -# Clone repository -git clone https://github.com/latinogino/dolibarr-mcp.git -cd dolibarr-mcp - -# Create virtual environment -python3 -m venv venv_dolibarr - -# Activate virtual environment -source venv_dolibarr/bin/activate - -# Install dependencies -pip install -r requirements.txt - -# Install package in development mode -pip install -e . - -# Verify installation -python -c "import dolibarr_mcp; print('βœ… Installation successful')" - -# Note the Python path for Claude Desktop configuration -which python +# Linux / macOS +pip install -e '.[dev]' ``` -### 🐳 Docker Installation - -```bash -# Using Docker Compose (recommended) -docker-compose up -d - -# Or using Docker directly -docker build -t dolibarr-mcp . -docker run -d \ - -e DOLIBARR_URL=https://your-dolibarr.com \ - -e DOLIBARR_API_KEY=your_api_key \ - -p 8080:8080 \ - dolibarr-mcp +```powershell +# Windows PowerShell +pip install -e .`[dev`] ``` -### βš™οΈ Configuration +## Configuration -Create a `.env` file based on `.env.example`: +Create a `.env` file (or set the variables in your MCP host application) with: -```bash -# Dolibarr Configuration -DOLIBARR_URL=https://your-dolibarr.example.com -DOLIBARR_API_KEY=YOUR_API_KEY - -# Logging +```env +DOLIBARR_URL=https://your-dolibarr.example.com/api/index.php +DOLIBARR_API_KEY=your_api_key LOG_LEVEL=INFO ``` -## 🎯 Usage +The [`Config` helper](src/dolibarr_mcp/config.py) loads these values, validates them and provides sensible +warnings when something is missing. -### πŸ€– With Claude Desktop +## Running the server -#### Using Virtual Environment (Recommended) - -Add this configuration to `claude_desktop_config.json`: - -**Windows:** -```json -{ - "mcpServers": { - "dolibarr": { - "command": "C:\\\\path\\\\to\\\\dolibarr-mcp\\\\venv_dolibarr\\\\Scripts\\\\python.exe", - "args": ["-m", "dolibarr_mcp.dolibarr_mcp_server"], - "cwd": "C:\\\\path\\\\to\\\\dolibarr-mcp", - "env": { - "DOLIBARR_URL": "https://your-dolibarr.example.com", - "DOLIBARR_API_KEY": "YOUR_API_KEY" - } - } - } -} -``` - -**Linux/macOS:** -```json -{ - "mcpServers": { - "dolibarr": { - "command": "/path/to/dolibarr-mcp/venv_dolibarr/bin/python", - "args": ["-m", "dolibarr_mcp.dolibarr_mcp_server"], - "cwd": "/path/to/dolibarr-mcp", - "env": { - "DOLIBARR_URL": "https://your-dolibarr.example.com", - "DOLIBARR_API_KEY": "YOUR_API_KEY" - } - } - } -} -``` - -### πŸ’» CLI Usage +The server communicates over STDIO as required by MCP. Start it with one of the following commands: ```bash -# Activate virtual environment first (if using venv) -source venv_dolibarr/bin/activate # Linux/macOS -.\venv_dolibarr\Scripts\Activate.ps1 # Windows +# Use the Python module entry point +python -m dolibarr_mcp -# With environment variables -dolibarr-mcp - -# With direct parameters -dolibarr-mcp --url https://your-dolibarr.com --api-key YOUR_API_KEY - -# Debug mode -dolibarr-mcp --log-level DEBUG +# Or use the CLI wrapper installed by the package +python -m dolibarr_mcp.cli serve +# Alias when installed as a package: dolibarr-mcp serve ``` -## πŸ’‘ Example Usage - -### Customer Management -``` -"Show me all customers in Dolibarr" -"Create a new customer named 'Acme Corp' with email info@acme.com" -"Update customer ID 5 with new phone number +1234567890" -"Find customers in France" -``` - -### Product Management -``` -"List all products with stock levels" -"Create a new product 'Consulting Service' with price $150" -"Update product ID 10 to set new price $200" -"Show me products with low stock" -``` - -### Invoice Management -``` -"Show all unpaid invoices" -"Create an invoice for customer 'Acme Corp'" -"Get invoice details for invoice ID 100" -"Update invoice due date to next month" -``` - -### Contact Management -``` -"List all contacts for customer ID 5" -"Create a new contact John Doe for Acme Corp" -"Update contact email for John Doe" -"Find all contacts with role 'Manager'" -``` - -## πŸ”§ Troubleshooting - -### ❌ Common Issues - -#### "ModuleNotFoundError: No module named 'dolibarr_mcp'" - -**Solution:** Use virtual environment and ensure package is installed: -```bash -# Check if in virtual environment -python -c "import sys; print(sys.prefix)" - -# Reinstall package -pip install -e . - -# Verify installation -python -c "import dolibarr_mcp; print('Module found')" -``` - -#### API Connection Issues - -**Check API Configuration:** -```bash -# Test connection with curl -curl -X GET "https://your-dolibarr.com/api/index.php/status" \ - -H "DOLAPIKEY: YOUR_API_KEY" -``` - -#### Permission Errors - -Ensure your API key has necessary permissions in Dolibarr: -1. Go to Dolibarr Admin β†’ API/Web Services -2. Check API key permissions -3. Enable required modules (API REST module) - -### πŸ” Debug Mode - -Enable debug logging in Claude Desktop configuration: -```json -{ - "mcpServers": { - "dolibarr": { - "command": "path/to/python", - "args": ["-m", "dolibarr_mcp.dolibarr_mcp_server"], - "cwd": "path/to/dolibarr-mcp", - "env": { - "DOLIBARR_URL": "https://your-dolibarr.example.com", - "DOLIBARR_API_KEY": "YOUR_API_KEY", - "LOG_LEVEL": "DEBUG" - } - } - } -} -``` - -## πŸ“Š Project Structure - -``` -dolibarr-mcp/ -β”œβ”€β”€ src/dolibarr_mcp/ # Main Package -β”‚ β”œβ”€β”€ dolibarr_mcp_server.py # MCP Server -β”‚ β”œβ”€β”€ dolibarr_client.py # Dolibarr API Client -β”‚ β”œβ”€β”€ config.py # Configuration Management -β”‚ └── cli.py # Command Line Interface -β”œβ”€β”€ tests/ # Test Suite -β”‚ β”œβ”€β”€ test_config.py # Unit Tests -β”‚ └── test_dolibarr_client.py # Integration Tests -β”œβ”€β”€ docker/ # Docker Configuration -β”‚ β”œβ”€β”€ Dockerfile # Container Definition -β”‚ └── docker-compose.yml # Compose Configuration -β”œβ”€β”€ venv_dolibarr/ # Virtual Environment (after setup) -β”œβ”€β”€ README.md # Documentation -β”œβ”€β”€ CHANGELOG.md # Version History -β”œβ”€β”€ pyproject.toml # Package Configuration -└── requirements.txt # Dependencies -``` - -## πŸ“– API Documentation - -### Dolibarr API - -Complete Dolibarr API documentation: -- **[Dolibarr REST API Wiki](https://wiki.dolibarr.org/index.php?title=Module_Web_Services_API_REST_(developer))** -- **[Dolibarr Integration Guide](https://wiki.dolibarr.org/index.php?title=Interfaces_Dolibarr_toward_foreign_systems)** - -### Authentication +To check that Dolibarr credentials are working you can run: ```bash -curl -X GET "https://your-dolibarr.com/api/index.php/status" \ - -H "DOLAPIKEY: YOUR_API_KEY" +python -m dolibarr_mcp.cli test --url https://your-dolibarr.example.com/api/index.php --api-key YOUR_KEY ``` -### Important Endpoints +## Available tools -- **Third Parties**: `/api/index.php/thirdparties` -- **Products**: `/api/index.php/products` -- **Invoices**: `/api/index.php/invoices` -- **Orders**: `/api/index.php/orders` -- **Contacts**: `/api/index.php/contacts` -- **Users**: `/api/index.php/users` -- **Status**: `/api/index.php/status` +`dolibarr_mcp_server` registers a collection of MCP tools that cover common ERP workflows: -## πŸ§ͺ Development +- **System** – `test_connection`, `get_status` +- **Users** – `get_users`, `get_user_by_id`, `create_user`, `update_user`, `delete_user` +- **Customers / Third parties** – `get_customers`, `get_customer_by_id`, `create_customer`, `update_customer`, `delete_customer` +- **Products** – `get_products`, `get_product_by_id`, `create_product`, `update_product`, `delete_product` +- **Invoices** – `get_invoices`, `get_invoice_by_id`, `create_invoice`, `update_invoice`, `delete_invoice` +- **Orders** – `get_orders`, `get_order_by_id`, `create_order`, `update_order`, `delete_order` +- **Contacts** – `get_contacts`, `get_contact_by_id`, `create_contact`, `update_contact`, `delete_contact` +- **Raw API access** – `dolibarr_raw_api` -### πŸ—οΈ Development Environment +The implementation in [`dolibarr_client.py`](src/dolibarr_mcp/dolibarr_client.py) provides the underlying async HTTP +operations, error handling and pagination helpers used by these tools. -```bash -# Activate virtual environment -source venv_dolibarr/bin/activate # Linux/macOS -.\venv_dolibarr\Scripts\Activate.ps1 # Windows +## Development -# Install development dependencies -pip install -r requirements.txt +- Run the automated test-suite with `pytest`. +- The project is packaged with `pyproject.toml`; editable installs use the `src/` layout. +- Additional API notes live in the [`api/`](api) directory if you need to extend the toolset. -# Run tests -pytest +## License -# Run tests with coverage -pytest --cov=src/dolibarr_mcp --cov-report=html - -# Run integration tests -python tests/test_dolibarr_client.py -``` - -## πŸ“– Resources - -- **[Dolibarr Official Documentation](https://www.dolibarr.org/documentation-home)** -- **[Model Context Protocol Specification](https://modelcontextprotocol.io/)** -- **[Claude Desktop MCP Integration](https://docs.anthropic.com/)** -- **[GitHub Repository](https://github.com/latinogino/dolibarr-mcp)** - -## πŸ“„ License - -MIT License - see [LICENSE](LICENSE) for details. - -## πŸ“ Changelog - -See [CHANGELOG.md](CHANGELOG.md) for a detailed history of changes. - ---- - -**🎯 Manage your complete Dolibarr ERP/CRM through natural language with Claude Desktop!** +This project is released under the [MIT License](LICENSE). diff --git a/README_DE.md b/README_DE.md deleted file mode 100644 index 389719e..0000000 --- a/README_DE.md +++ /dev/null @@ -1,259 +0,0 @@ -# Dolibarr MCP Server πŸš€ - -Ein professioneller **Model Context Protocol (MCP) Server** fΓΌr Dolibarr ERP-Integration. ErmΓΆglicht LLMs vollstΓ€ndige CRUD-Operationen fΓΌr alle Dolibarr-Module ΓΌber eine standardisierte API. - -## ✨ Features - -### πŸ”§ VollstΓ€ndige CRUD-UnterstΓΌtzung fΓΌr alle Dolibarr-Module: -- **πŸ‘₯ User Management** - Benutzer verwalten, erstellen, aktualisieren, lΓΆschen -- **🏒 Customer/Third Party Management** - Kunden und Drittparteien vollstΓ€ndig verwalten -- **πŸ“¦ Product Management** - Produkte mit Preisen, Lager und Details -- **🧾 Invoice Management** - Rechnungen mit Zeilen, Steuer und Status -- **πŸ“‹ Order Management** - Bestellungen und AuftrΓ€ge verfolgen -- **πŸ“ž Contact Management** - Kontakte und Ansprechpartner -- **πŸ”Œ Raw API Access** - Direkter Zugriff auf beliebige Dolibarr-Endpunkte - -### πŸ› οΈ Technische Features: -- βœ… **Professionelle Fehlerbehandlung** mit detaillierten API-Error-Messages -- βœ… **Async/await Architektur** fΓΌr optimale Performance -- βœ… **Pydantic Validation** fΓΌr Typ-Sicherheit und Datenvalidierung -- βœ… **Umfassendes Logging** mit konfigurierbaren Log-Levels -- βœ… **Docker Support** mit Multi-Stage Builds -- βœ… **Windows + Linux Support** mit platform-spezifischen Setup-Scripts -- βœ… **Automatische API-Key Validation** und Connection Testing -- βœ… **MCP 1.0 Compliant** - kompatibel mit Claude, anderen LLMs - -## πŸš€ Quick Start - -### Windows Setup (Empfohlen fΓΌr Setup-Probleme) - -Wenn Sie Probleme mit dem regulΓ€ren Setup haben (besonders pywin32 Fehler), nutzen Sie das verbesserte Setup: - -```cmd -# 1. Repository klonen -git clone https://github.com/latinogino/dolibarr-mcp.git -cd dolibarr-mcp - -# 2. Verbessertes Windows Setup (umgeht pywin32 Probleme) -.\setup_windows_fix.bat - -# 3. .env Datei konfigurieren -copy .env.example .env -# Bearbeiten Sie .env mit Ihren Dolibarr-Credentials - -# 4. Server starten -.\start_server.bat -``` - -### Linux/macOS Setup - -```bash -# 1. Repository klonen -git clone https://github.com/latinogino/dolibarr-mcp.git -cd dolibarr-mcp - -# 2. Setup ausfΓΌhren -chmod +x setup.sh -./setup.sh - -# 3. .env konfigurieren -cp .env.example .env -# .env mit Ihren Dolibarr-Details bearbeiten - -# 4. Server starten -python -m src.dolibarr_mcp -``` - -### Docker Setup - -```bash -# Mit docker-compose (empfohlen) -cp .env.example .env -# .env konfigurieren, dann: -docker-compose up -d - -# Oder direkt mit Docker -docker build -t dolibarr-mcp . -docker run -d --env-file .env -p 8080:8080 dolibarr-mcp -``` - -## βš™οΈ Konfiguration - -### Dolibarr API Setup - -1. **Dolibarr Admin Login** -2. **Module aktivieren**: Home β†’ Setup β†’ Modules β†’ "Web Services API REST (developer)" aktivieren -3. **API Key erstellen**: Home β†’ Setup β†’ API/Web services β†’ Neuen API Key generieren -4. **API URL**: Normalerweise `https://ihre-dolibarr-instanz.com/api/index.php` - -### .env Datei - -```env -# Dolibarr API Configuration -DOLIBARR_URL=https://ihre-dolibarr-instanz.com/api/index.php -DOLIBARR_API_KEY=ihr_dolibarr_api_schluessel - -# Logging Configuration -LOG_LEVEL=INFO -``` - -## πŸ“‹ VerfΓΌgbare Tools - -Der Server stellt folgende MCP-Tools zur VerfΓΌgung: - -### System & Status -- `test_connection` - API-Verbindung testen -- `get_status` - Dolibarr System-Status und Version - -### User Management -- `get_users` - Benutzer auflisten (mit Pagination) -- `get_user_by_id` - Benutzer nach ID abrufen -- `create_user` - Neuen Benutzer erstellen -- `update_user` - Benutzer aktualisieren -- `delete_user` - Benutzer lΓΆschen - -### Customer Management -- `get_customers` - Kunden/Drittparteien auflisten -- `get_customer_by_id` - Kunde nach ID -- `create_customer` - Neuen Kunden erstellen -- `update_customer` - Kunde aktualisieren -- `delete_customer` - Kunde lΓΆschen - -### Product Management -- `get_products` - Produkte auflisten -- `get_product_by_id` - Produkt nach ID -- `create_product` - Neues Produkt erstellen -- `update_product` - Produkt aktualisieren -- `delete_product` - Produkt lΓΆschen - -### Invoice Management -- `get_invoices` - Rechnungen auflisten (mit Status-Filter) -- `get_invoice_by_id` - Rechnung nach ID -- `create_invoice` - Neue Rechnung mit Zeilen erstellen -- `update_invoice` - Rechnung aktualisieren -- `delete_invoice` - Rechnung lΓΆschen - -### Order Management -- `get_orders` - Bestellungen auflisten -- `get_order_by_id` - Bestellung nach ID -- `create_order` - Neue Bestellung erstellen -- `update_order` - Bestellung aktualisieren -- `delete_order` - Bestellung lΓΆschen - -### Contact Management -- `get_contacts` - Kontakte auflisten -- `get_contact_by_id` - Kontakt nach ID -- `create_contact` - Neuen Kontakt erstellen -- `update_contact` - Kontakt aktualisieren -- `delete_contact` - Kontakt lΓΆschen - -### Advanced -- `dolibarr_raw_api` - Roher API-Aufruf an beliebige Dolibarr-Endpunkte - -## πŸ§ͺ Testing - -```bash -# API-Verbindung testen -python test_connection.py - -# Umfassende Tests -python test_dolibarr_mcp.py - -# Mit Docker -docker-compose --profile test up dolibarr-mcp-test -``` - -## 🐳 Docker Production Deployment - -```yaml -version: '3.8' -services: - dolibarr-mcp: - image: dolibarr-mcp:latest - environment: - - DOLIBARR_URL=https://ihre-dolibarr-instanz.com/api/index.php - - DOLIBARR_API_KEY=ihr_api_schluessel - - LOG_LEVEL=INFO - ports: - - "8080:8080" - restart: unless-stopped - healthcheck: - test: ["CMD", "python", "-c", "from src.dolibarr_mcp.config import Config; Config()"] - interval: 30s - timeout: 10s - retries: 3 -``` - -## πŸ”§ Troubleshooting - -### Windows Setup Probleme - -**Problem**: `[WinError 5] Zugriff verweigert` beim Installieren von pywin32 - -**LΓΆsung**: Nutzen Sie das verbesserte Setup-Script: -```cmd -.\setup_windows_fix.bat -``` - -Dieses Script: -- Verwendet `requirements-minimal.txt` ohne problematische Pakete -- Installiert Pakete einzeln bei Fehlern -- Umgeht pywin32-Berechtigungsprobleme -- Funktioniert auch ohne Admin-Rechte - -### API-Verbindungsprobleme - -**Symptom**: "Cannot connect to Dolibarr API" - -**Checkliste**: -1. βœ… Dolibarr "Web Services API REST" Modul aktiviert? -2. βœ… API Key in Dolibarr erstellt? -3. βœ… .env Datei korrekt konfiguriert? -4. βœ… DOLIBARR_URL mit `/api/index.php` am Ende? -5. βœ… Firewall/Network-Zugang? - -**Debug**: -```bash -# Verbindung direkt testen -python test_connection.py - -# Detaillierte Logs -LOG_LEVEL=DEBUG python -m src.dolibarr_mcp -``` - -### HΓ€ufige API-Endpunkt Probleme - -| Endpunkt | HΓ€ufige Probleme | LΓΆsung | -|----------|------------------|---------| -| `/users` | 403 Forbidden | Admin API Key erforderlich | -| `/products` | Leere Liste | Produkte in Dolibarr erstellen | -| `/thirdparties` | 500 Error | Customer/Supplier Module aktivieren | -| `/invoices` | Permission denied | Invoice Module + Rechte prΓΌfen | - -## πŸ“š API Dokumentation - -VollstΓ€ndige Dolibarr REST API Dokumentation: -- [Dolibarr REST API Wiki](https://wiki.dolibarr.org/index.php?title=Module_Web_Services_API_REST_(developer)) -- [API Interfaces Guide](https://wiki.dolibarr.org/index.php?title=Interfaces_Dolibarr_toward_foreign_systems) - -## 🀝 Contributing - -1. Fork das Repository -2. Feature Branch erstellen: `git checkout -b feature/neue-funktion` -3. Changes committen: `git commit -am 'Neue Funktion hinzufΓΌgen'` -4. Branch pushen: `git push origin feature/neue-funktion` -5. Pull Request erstellen - -## πŸ“„ License - -MIT License - siehe [LICENSE](LICENSE) fΓΌr Details. - -## πŸ†˜ Support - -- πŸ“§ **Issues**: [GitHub Issues](https://github.com/latinogino/dolibarr-mcp/issues) -- πŸ“– **Wiki**: [Project Wiki](https://github.com/latinogino/dolibarr-mcp/wiki) -- πŸ’¬ **Discussions**: [GitHub Discussions](https://github.com/latinogino/dolibarr-mcp/discussions) - ---- - -**⚑ Ready to integrate your Dolibarr ERP with AI? Get started in 2 minutes!** diff --git a/ULTRA-SOLUTION.md b/ULTRA-SOLUTION.md deleted file mode 100644 index 36902c1..0000000 --- a/ULTRA-SOLUTION.md +++ /dev/null @@ -1,113 +0,0 @@ -# ✨ ULTRA-LΓ–SUNG: Windows pywin32 Problem zu 100% GELΓ–ST! - -Das Import-Problem wurde **endgΓΌltig behoben**! Der ULTRA-Server ist jetzt **komplett selbst-contained** und importiert **nichts mehr** aus problematischen Modulen. - -## πŸ”₯ **Sofortige LΓΆsung** - -```cmd -# 1. ULTRA Setup (garantiert erfolgreiche Installation) -.\setup_ultra.bat - -# 2. Konfiguration -copy .env.example .env -# Bearbeiten Sie .env mit Ihren Dolibarr-Credentials - -# 3. Server starten (direkte AusfΓΌhrung) -.\run_ultra.bat -``` - -## βœ… **Problem-Analyse und LΓΆsung** - -| Problem Stufe | Ursache | LΓΆsung | Status | -|---------------|---------|---------|--------| -| **1. pywin32 Fehler** | MCP package benΓΆtigt pywin32 | ❌ Standalone ohne MCP | Teilweise gelΓΆst | -| **2. .pyd Dateien** | aiohttp, pydantic C-Extensions | ❌ Nur requests + stdlib | Teilweise gelΓΆst | -| **3. Import-Fehler** | ultra_server importiert config.py (pydantic) | βœ… **Komplett self-contained** | **GELΓ–ST!** | - -## 🎯 **ULTRA-Version Features** - -### **Technische LΓΆsung:** -- **Eine einzige Datei**: `ultra_simple_server.py` (22KB) -- **Zero externe Imports**: Alle Klassen self-contained -- **Nur requests**: Als einzige externe Dependency -- **Direkte AusfΓΌhrung**: `python src\dolibarr_mcp\ultra_simple_server.py` - -### **VollstΓ€ndige FunktionalitΓ€t:** -- βœ… **Alle CRUD-Operationen**: Users, Customers, Products, Invoices, Orders, Contacts -- βœ… **Raw API Access**: Direkter Zugriff auf beliebige Dolibarr-Endpunkte -- βœ… **Interactive Console**: Eingebaute Test-Umgebung -- βœ… **Professional Error Handling**: Detaillierte Fehlermeldungen -- βœ… **Configuration Management**: .env Support ohne externe Libraries - -## πŸ§ͺ **VerfΓΌgbare Tests** - -```cmd -# Direkte Tests -python test_ultra_direct.py - -# Interactive Server -.\run_ultra.bat - -# Manuelle AusfΓΌhrung -python src\dolibarr_mcp\ultra_simple_server.py -``` - -## πŸ“‹ **Interactive Console Befehle** - -``` -dolibarr-ultra> help # Alle Befehle anzeigen -dolibarr-ultra> config # Konfiguration anzeigen -dolibarr-ultra> list # Alle verfΓΌgbaren Tools -dolibarr-ultra> test test_connection # API-Verbindung testen -dolibarr-ultra> test get_status # Dolibarr Status -dolibarr-ultra> test get_users # Erste 5 Benutzer -dolibarr-ultra> test get_customers # Erste 5 Kunden -dolibarr-ultra> test get_products # Erste 5 Produkte -dolibarr-ultra> exit # Server beenden -``` - -## πŸŽ‰ **Garantiert auf ALLEN Windows-Versionen** - -- βœ… **Windows XP** - Windows 11 -- βœ… **32-bit und 64-bit** Python -- βœ… **Admin-Rechte NICHT erforderlich** -- βœ… **Keine Berechtigungsprobleme** -- βœ… **Funktioniert in jeder Python-Umgebung** - -## πŸ”§ **Technische Details** - -```python -# Was die ULTRA-Version vermeidet: -❌ import mcp # pywin32 Probleme -❌ import aiohttp # C-Extension .pyd -❌ import pydantic # C-Extension .pyd -❌ from .config import Config # Import-AbhΓ€ngigkeiten - -# Was die ULTRA-Version verwendet: -βœ… import requests # Pure Python HTTP client -βœ… import json # Standard library -βœ… import os, sys, logging # Standard library -βœ… from typing import Dict # Standard library -βœ… class UltraSimpleConfig: # Self-contained -``` - -## πŸš€ **Production-Ready Status** - -- βœ… **Komplett funktional**: Alle Dolibarr-Operationen verfΓΌgbar -- βœ… **Performance-optimiert**: Requests-basiert, sehr schnell -- βœ… **Error-Handling**: Professional exception handling -- βœ… **Wartbar**: Einfache, saubere Architektur -- βœ… **Testbar**: Eingebaute Interactive Console -- βœ… **Dokumentiert**: VollstΓ€ndige API-Coverage - ---- - -## 🎯 **Fazit: Problem endgΓΌltig gelΓΆst!** - -Die ULTRA-Version ist: -- **100% Windows-kompatibel** (keine .pyd Dateien) -- **100% funktional** (alle CRUD-Operationen) -- **100% selbst-contained** (keine problematischen Imports) -- **100% production-ready** (professional implementation) - -**πŸš€ Ihr Dolibarr ERP ist jetzt ready fΓΌr AI-Integration!** diff --git a/cleanup.bat b/cleanup.bat deleted file mode 100644 index 5fcc4fd..0000000 --- a/cleanup.bat +++ /dev/null @@ -1,75 +0,0 @@ -@echo off -:: Dolibarr MCP - Complete Clean Build Artifacts -:: Run this before setup if you have installation issues - -echo. -echo ====================================== -echo Complete Cleanup for Dolibarr MCP -echo ====================================== -echo. - -:: Remove all egg-info directories anywhere in the project -echo Searching and removing ALL egg-info directories... -for /f "delims=" %%i in ('dir /s /b /a:d *.egg-info 2^>nul') do ( - echo - Removing %%i - rmdir /s /q "%%i" 2>nul -) - -:: Remove specific known egg-info patterns -if exist "dolibarr_mcp.egg-info" rmdir /s /q "dolibarr_mcp.egg-info" 2>nul -if exist "src\dolibarr_mcp.egg-info" rmdir /s /q "src\dolibarr_mcp.egg-info" 2>nul -if exist "dolibarr-mcp.egg-info" rmdir /s /q "dolibarr-mcp.egg-info" 2>nul -if exist "src\dolibarr-mcp.egg-info" rmdir /s /q "src\dolibarr-mcp.egg-info" 2>nul - -:: Remove build directories -if exist build ( - echo Removing build directory... - rmdir /s /q build 2>nul -) - -if exist dist ( - echo Removing dist directory... - rmdir /s /q dist 2>nul -) - -if exist .eggs ( - echo Removing .eggs directory... - rmdir /s /q .eggs 2>nul -) - -:: Remove Python cache everywhere -echo Removing ALL Python cache directories... -for /f "delims=" %%i in ('dir /s /b /a:d __pycache__ 2^>nul') do ( - echo - Removing %%i - rmdir /s /q "%%i" 2>nul -) - -:: Remove compiled Python files -echo Removing compiled Python files... -del /s /q *.pyc 2>nul -del /s /q *.pyo 2>nul -del /s /q *.pyd 2>nul - -:: Remove pip cache related to this project -if exist pip-wheel-metadata rmdir /s /q pip-wheel-metadata 2>nul -if exist .pytest_cache rmdir /s /q .pytest_cache 2>nul - -:: Remove virtual environment -if exist venv_dolibarr ( - echo Removing virtual environment... - rmdir /s /q venv_dolibarr 2>nul -) - -:: Remove any other common virtual environment directories -if exist venv rmdir /s /q venv 2>nul -if exist env rmdir /s /q env 2>nul -if exist .venv rmdir /s /q .venv 2>nul - -echo. -echo ====================================== -echo Cleanup complete! -echo ====================================== -echo. -echo You can now run setup.bat for a fresh installation. -echo. -pause diff --git a/cleanup_repo.py b/cleanup_repo.py deleted file mode 100644 index f99bd53..0000000 --- a/cleanup_repo.py +++ /dev/null @@ -1,131 +0,0 @@ -#!/usr/bin/env python3 -""" -Cleanup script to remove unnecessary files from dolibarr-mcp repository. -This script should be run locally after checking out the cleanup-restructure-v2 branch. -""" - -import os -import shutil -from pathlib import Path - -# List of files to remove -FILES_TO_REMOVE = [ - # Test scripts in root directory - "test_api_connection.py", - "test_api_debug.py", - "test_connection.py", - "test_dolibarr_mcp.py", - "test_install.py", - "test_standalone.py", - "test_ultra.py", - "test_ultra_direct.py", - "diagnose_and_fix.py", - - # Batch files - "cleanup.bat", - "fix_installation.bat", - "run_dolibarr_mcp.bat", - "run_server.bat", - "run_standalone.bat", - "run_ultra.bat", - "setup.bat", - "setup_claude_complete.bat", - "setup_manual.bat", - "setup_standalone.bat", - "setup_ultra.bat", - "setup_windows_fix.bat", - "start_server.bat", - "validate_claude_config.bat", - - # Python scripts in root - "mcp_server_launcher.py", - "setup_env.py", - - # Alternative server implementations - "src/dolibarr_mcp/simple_client.py", - "src/dolibarr_mcp/standalone_server.py", - "src/dolibarr_mcp/ultra_simple_server.py", - - # Multiple requirements files - "requirements-minimal.txt", - "requirements-ultra-minimal.txt", - "requirements-windows.txt", - - # Documentation files - "README_DE.md", - "CLAUDE_CONFIG.md", - "CONFIG_COMPATIBILITY.md", - "MCP_FIX_GUIDE.md", - "ULTRA-SOLUTION.md", -] - -# Directories to remove -DIRS_TO_REMOVE = [ - "api", -] - -def cleanup(): - """Remove unnecessary files and directories.""" - removed_files = [] - removed_dirs = [] - errors = [] - - # Get repository root - repo_root = Path(__file__).parent - - # Remove files - for file_path in FILES_TO_REMOVE: - full_path = repo_root / file_path - if full_path.exists(): - try: - full_path.unlink() - removed_files.append(file_path) - print(f"βœ… Removed: {file_path}") - except Exception as e: - errors.append(f"Failed to remove {file_path}: {e}") - print(f"❌ Failed: {file_path} - {e}") - else: - print(f"⚠️ Not found: {file_path}") - - # Remove directories - for dir_path in DIRS_TO_REMOVE: - full_path = repo_root / dir_path - if full_path.exists(): - try: - shutil.rmtree(full_path) - removed_dirs.append(dir_path) - print(f"βœ… Removed directory: {dir_path}") - except Exception as e: - errors.append(f"Failed to remove {dir_path}: {e}") - print(f"❌ Failed: {dir_path} - {e}") - else: - print(f"⚠️ Directory not found: {dir_path}") - - # Summary - print("\n" + "="*50) - print("CLEANUP SUMMARY") - print("="*50) - print(f"Files removed: {len(removed_files)}") - print(f"Directories removed: {len(removed_dirs)}") - print(f"Errors: {len(errors)}") - - if errors: - print("\n❌ Errors encountered:") - for error in errors: - print(f" - {error}") - - print("\nβœ… Cleanup complete!") - print("Don't forget to commit these changes:") - print(" git add -A") - print(" git commit -m 'Remove unnecessary files and clean up structure'") - print(" git push origin cleanup-restructure-v2") - -if __name__ == "__main__": - print("🧹 Starting cleanup of dolibarr-mcp repository...") - print("This will remove unnecessary files to match prestashop-mcp structure.\n") - - response = input("Are you sure you want to proceed? (yes/no): ") - if response.lower() in ['yes', 'y']: - cleanup() - else: - print("Cleanup cancelled.") diff --git a/diagnose_and_fix.py b/diagnose_and_fix.py deleted file mode 100644 index 9597015..0000000 --- a/diagnose_and_fix.py +++ /dev/null @@ -1,246 +0,0 @@ -#!/usr/bin/env python3 -""" -Comprehensive diagnostic and repair tool for Dolibarr MCP Server. -This script identifies and fixes common installation and configuration issues. -""" - -import sys -import os -import json -import subprocess -from pathlib import Path -import importlib.util - -class DolibarrMCPDiagnostic: - def __init__(self): - self.issues = [] - self.fixes_applied = [] - self.root_dir = Path(__file__).resolve().parent - - def run_diagnostics(self): - """Run all diagnostic checks.""" - print("=" * 60) - print("Dolibarr MCP Server - Diagnostic & Repair Tool") - print("=" * 60) - print() - - # Check Python version - self.check_python_version() - - # Check virtual environment - self.check_virtual_environment() - - # Check package installation - self.check_package_installation() - - # Check module structure - self.check_module_structure() - - # Check environment variables - self.check_environment() - - # Check MCP server files - self.check_server_files() - - # Report results - self.report_results() - - def check_python_version(self): - """Check if Python version is compatible.""" - print("Checking Python version...") - version = sys.version_info - if version.major < 3 or (version.major == 3 and version.minor < 8): - self.issues.append(f"Python {version.major}.{version.minor} detected. Python 3.8+ required.") - else: - print(f"βœ“ Python {version.major}.{version.minor}.{version.micro} - OK") - - def check_virtual_environment(self): - """Check if running in a virtual environment.""" - print("Checking virtual environment...") - in_venv = hasattr(sys, 'real_prefix') or ( - hasattr(sys, 'base_prefix') and sys.base_prefix != sys.prefix - ) - - if not in_venv: - print("⚠ Not running in a virtual environment") - venv_path = self.root_dir / 'venv_dolibarr' - if not venv_path.exists(): - print(f" Creating virtual environment at {venv_path}...") - try: - subprocess.run([sys.executable, '-m', 'venv', str(venv_path)], check=True) - self.fixes_applied.append("Created virtual environment") - except subprocess.CalledProcessError as e: - self.issues.append(f"Failed to create virtual environment: {e}") - else: - print(f"βœ“ Virtual environment active: {sys.prefix}") - - def check_package_installation(self): - """Check if the package is installed correctly.""" - print("Checking package installation...") - - # Try to import the module - try: - import dolibarr_mcp - print("βœ“ dolibarr_mcp module found") - except ImportError: - print("βœ— dolibarr_mcp module not found") - - # Try to fix by adjusting sys.path - src_path = self.root_dir / 'src' - if src_path.exists(): - sys.path.insert(0, str(src_path)) - try: - import dolibarr_mcp - print("βœ“ Fixed by adding src to path") - self.fixes_applied.append(f"Added {src_path} to Python path") - except ImportError: - self.issues.append("Module cannot be imported even with path adjustment") - self.attempt_install() - - def attempt_install(self): - """Attempt to install the package.""" - print(" Attempting to install package...") - try: - subprocess.run([sys.executable, '-m', 'pip', 'install', '-e', str(self.root_dir)], - check=True, capture_output=True, text=True) - self.fixes_applied.append("Installed package in development mode") - print(" βœ“ Package installed successfully") - except subprocess.CalledProcessError as e: - self.issues.append(f"Failed to install package: {e}") - - def check_module_structure(self): - """Check if the module structure is correct.""" - print("Checking module structure...") - - required_files = [ - self.root_dir / 'src' / 'dolibarr_mcp' / '__init__.py', - self.root_dir / 'src' / 'dolibarr_mcp' / 'dolibarr_mcp_server.py', - self.root_dir / 'src' / 'dolibarr_mcp' / 'dolibarr_client.py', - self.root_dir / 'src' / 'dolibarr_mcp' / 'config.py', - ] - - missing_files = [] - for file in required_files: - if not file.exists(): - missing_files.append(str(file.relative_to(self.root_dir))) - - if missing_files: - self.issues.append(f"Missing required files: {', '.join(missing_files)}") - else: - print("βœ“ All required module files present") - - def check_environment(self): - """Check environment variables.""" - print("Checking environment configuration...") - - env_file = self.root_dir / '.env' - if not env_file.exists(): - print("βœ— .env file not found") - env_example = self.root_dir / '.env.example' - if env_example.exists(): - print(" Creating .env from .env.example...") - import shutil - shutil.copy(env_example, env_file) - self.fixes_applied.append("Created .env file from template") - print(" ⚠ Please edit .env with your Dolibarr credentials") - else: - print("βœ“ .env file exists") - - # Check if it has required variables - try: - from dotenv import load_dotenv - load_dotenv(env_file) - - required_vars = ['DOLIBARR_URL', 'DOLIBARR_API_KEY'] - missing_vars = [] - - for var in required_vars: - if not os.getenv(var): - missing_vars.append(var) - - if missing_vars: - self.issues.append(f"Missing environment variables: {', '.join(missing_vars)}") - else: - print("βœ“ All required environment variables set") - except ImportError: - print(" Installing python-dotenv...") - subprocess.run([sys.executable, '-m', 'pip', 'install', 'python-dotenv'], - capture_output=True) - self.fixes_applied.append("Installed python-dotenv") - - def check_server_files(self): - """Check if MCP server can be started.""" - print("Checking MCP server readiness...") - - # Try to import and check main function - try: - spec = importlib.util.spec_from_file_location( - "server_test", - self.root_dir / 'src' / 'dolibarr_mcp' / 'dolibarr_mcp_server.py' - ) - if spec and spec.loader: - module = importlib.util.module_from_spec(spec) - spec.loader.exec_module(module) - - if hasattr(module, 'main'): - print("βœ“ Server main function found") - else: - self.issues.append("Server main function not found") - except Exception as e: - self.issues.append(f"Cannot load server module: {e}") - - def report_results(self): - """Report diagnostic results and provide recommendations.""" - print() - print("=" * 60) - print("Diagnostic Results") - print("=" * 60) - - if self.fixes_applied: - print("\nβœ… Fixes Applied:") - for fix in self.fixes_applied: - print(f" - {fix}") - - if self.issues: - print("\n⚠ Issues Found:") - for issue in self.issues: - print(f" - {issue}") - - print("\nπŸ“‹ Recommended Actions:") - print("1. Run fix_installation.bat to reinstall") - print("2. Update .env file with your Dolibarr credentials") - print("3. Try running: python mcp_server_launcher.py") - else: - print("\nβœ… No issues found! Server should be ready to run.") - print("\nYou can now:") - print("1. Configure Claude Desktop (see CLAUDE_CONFIG.md)") - print("2. Run the server with: python mcp_server_launcher.py") - - # Create a quick launcher if everything is OK - if not self.issues: - self.create_quick_launcher() - - def create_quick_launcher(self): - """Create a quick launcher script.""" - launcher_path = self.root_dir / 'quick_start.bat' - - content = f'''@echo off -echo Starting Dolibarr MCP Server... -cd /d "{self.root_dir}" - -if exist venv_dolibarr ( - call venv_dolibarr\\Scripts\\activate -) - -python mcp_server_launcher.py -pause -''' - - with open(launcher_path, 'w') as f: - f.write(content) - - print(f"\n✨ Created quick_start.bat for easy server launch") - -if __name__ == "__main__": - diagnostic = DolibarrMCPDiagnostic() - diagnostic.run_diagnostics() diff --git a/fix_installation.bat b/fix_installation.bat deleted file mode 100644 index 6490fc7..0000000 --- a/fix_installation.bat +++ /dev/null @@ -1,47 +0,0 @@ -@echo off -echo ====================================== -echo Dolibarr MCP Installation Fix -echo ====================================== -echo. -echo Fixing installation and module paths... -echo. - -REM Check if virtual environment exists -if not exist "venv_dolibarr" ( - echo Creating virtual environment... - python -m venv venv_dolibarr -) - -echo Activating virtual environment... -call venv_dolibarr\Scripts\activate - -echo Installing package in development mode... -pip install -e . - -echo. -echo Testing installation... -python -c "import sys; sys.path.insert(0, 'src'); from dolibarr_mcp import __version__; print(f'Dolibarr MCP version: {__version__}')" 2>NUL -if %errorlevel% neq 0 ( - echo. - echo Installation check failed. Trying alternative fix... - - REM Install requirements directly - pip install requests python-dotenv mcp - - REM Create a simple test - python -c "import sys; print('Python path:'); [print(f' {p}') for p in sys.path[:5]]" - - echo. - echo Now testing module import with path fix... - python mcp_server_launcher.py -) else ( - echo Installation successful! - echo. - echo You can now run the server with: - echo python -m dolibarr_mcp.dolibarr_mcp_server - echo OR - echo python mcp_server_launcher.py -) - -echo. -pause diff --git a/pyproject.toml b/pyproject.toml index 1a36a72..491738b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "dolibarr-mcp" -version = "1.0.0" +version = "1.1.0" description = "Professional Model Context Protocol server for complete Dolibarr ERP/CRM management" readme = "README.md" requires-python = ">=3.8" @@ -34,12 +34,18 @@ classifiers = [ ] dependencies = [ "mcp>=1.0.0", - "requests>=2.31.0", "aiohttp>=3.9.0", "pydantic>=2.5.0", + "pydantic-settings>=2.0.0", "click>=8.1.0", "python-dotenv>=1.0.0", - "typing-extensions>=4.8.0", +] + +[project.optional-dependencies] +dev = [ + "pytest>=7.4.0", + "pytest-asyncio>=0.21.0", + "pytest-cov>=4.1.0", ] [project.urls] diff --git a/requirements-minimal.txt b/requirements-minimal.txt deleted file mode 100644 index 56058a6..0000000 --- a/requirements-minimal.txt +++ /dev/null @@ -1,7 +0,0 @@ -mcp>=1.0.0 -requests>=2.31.0 -aiohttp>=3.9.0 -pydantic>=2.5.0 -click>=8.1.0 -python-dotenv>=1.0.0 -typing-extensions>=4.8.0 diff --git a/requirements-ultra-minimal.txt b/requirements-ultra-minimal.txt deleted file mode 100644 index 0989fbf..0000000 --- a/requirements-ultra-minimal.txt +++ /dev/null @@ -1,10 +0,0 @@ -# Ultra-minimal requirements - ZERO compiled extensions -# This avoids ALL Windows permission issues with .pyd files - -# Only pure Python packages that never cause permission issues -requests>=2.31.0 -python-dotenv>=1.0.0 -click>=8.1.0 - -# Basic JSON and HTTP without compiled extensions -# Using only standard library + requests for maximum compatibility diff --git a/requirements-windows.txt b/requirements-windows.txt deleted file mode 100644 index d6098b3..0000000 --- a/requirements-windows.txt +++ /dev/null @@ -1,39 +0,0 @@ -# PyWin32-free requirements for Windows compatibility -# This avoids the Windows permission issues with pywin32 - -# Core HTTP and async libraries -requests>=2.31.0 -aiohttp>=3.9.0 -httpx>=0.27.1 - -# Data validation and settings -pydantic>=2.5.0 -pydantic-settings>=2.5.2 -python-dotenv>=1.0.0 - -# CLI and utilities -click>=8.1.0 -typing-extensions>=4.8.0 - -# JSON schema validation (MCP requirement) -jsonschema>=4.20.0 - -# Async utilities -anyio>=4.5 - -# Basic dependencies for async/await -sniffio>=1.1 -attrs>=17.3.0 -certifi>=2017.4.17 -charset-normalizer>=3.0.0 -idna>=2.5 -urllib3>=2.0.0 - -# HTTP parsing -h11>=0.16 -httpcore>=1.0.0 - -# Additional utilities needed -annotated-types>=0.6.0 -rpds-py>=0.7.1 -referencing>=0.28.4 diff --git a/requirements.txt b/requirements.txt index dcee664..d60f979 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,7 +2,6 @@ mcp>=1.0.0 # HTTP and async support -requests>=2.31.0 aiohttp>=3.9.0 # Data validation and settings @@ -13,9 +12,6 @@ python-dotenv>=1.0.0 # CLI support click>=8.1.0 -# Type hints -typing-extensions>=4.8.0 - # Development and testing pytest>=7.4.0 pytest-asyncio>=0.21.0 diff --git a/run_dolibarr_mcp.bat b/run_dolibarr_mcp.bat deleted file mode 100644 index eb1ecfe..0000000 --- a/run_dolibarr_mcp.bat +++ /dev/null @@ -1,28 +0,0 @@ -@echo off -REM Simple script to run Dolibarr MCP Server - -echo ======================================== -echo Starting Dolibarr MCP Server -echo ======================================== - -REM Check if virtual environment exists -if not exist "venv_dolibarr\Scripts\python.exe" ( - echo Virtual environment not found! - echo Please run setup.py first. - exit /b 1 -) - -REM Check if .env file exists -if not exist ".env" ( - echo .env file not found! - echo Creating from template... - copy .env.example .env - echo Please edit .env with your Dolibarr credentials - exit /b 1 -) - -REM Activate virtual environment and run server -echo Running server... -venv_dolibarr\Scripts\python.exe -m dolibarr_mcp - -pause diff --git a/run_server.bat b/run_server.bat deleted file mode 100644 index a4e049d..0000000 --- a/run_server.bat +++ /dev/null @@ -1,49 +0,0 @@ -@echo off -echo πŸš€ Run Dolibarr MCP Server (Direct Source) -echo. - -REM Set environment variables for Python path and encoding -set PYTHONPATH=%cd%\src -set PYTHONIOENCODING=utf-8 - -REM Check if virtual environment exists -if not exist venv_dolibarr\Scripts\python.exe ( - echo ❌ Virtual environment not found! - echo πŸ’‘ Run setup.bat first to create the environment - pause - exit /b 1 -) - -REM Check if .env exists -if not exist .env ( - echo ❌ Configuration file .env not found! - echo πŸ’‘ Please create .env file with your Dolibarr credentials - echo. - echo Example .env content: - echo DOLIBARR_URL=https://your-dolibarr-instance.com/api/index.php - echo DOLIBARR_API_KEY=your_api_key_here - echo LOG_LEVEL=INFO - echo. - pause - exit /b 1 -) - -echo 🎯 Starting Dolibarr MCP Server... -echo πŸ“‘ Server will run until you press Ctrl+C -echo βš™οΈ Using direct source execution (no package installation required) -echo. - -REM Start the server with direct Python execution -cd /d "%~dp0" -venv_dolibarr\Scripts\python.exe -c " -import sys -import os -sys.path.insert(0, 'src') -from dolibarr_mcp.dolibarr_mcp_server import main -import asyncio -asyncio.run(main()) -" - -echo. -echo πŸ›‘ Server stopped -pause diff --git a/run_standalone.bat b/run_standalone.bat deleted file mode 100644 index 2557ead..0000000 --- a/run_standalone.bat +++ /dev/null @@ -1,47 +0,0 @@ -@echo off -cls -echo ====================================== -echo Dolibarr MCP Standalone Server -echo ====================================== -echo Starting Windows-compatible server... -echo (No pywin32 required!) -echo. - -REM Check if virtual environment exists -if not exist "venv_dolibarr\Scripts\activate.bat" ( - echo [ERROR] Virtual environment not found! - echo Please run: setup_standalone.bat first - pause - exit /b 1 -) - -REM Check if .env exists -if not exist ".env" ( - echo [ERROR] Configuration file .env not found! - echo. - echo Creating .env from template... - copy ".env.example" ".env" >nul - echo [INFO] Please edit .env file with your Dolibarr credentials: - echo DOLIBARR_URL=https://your-dolibarr-instance.com/api/index.php - echo DOLIBARR_API_KEY=your_api_key_here - echo. - pause - exit /b 1 -) - -echo Activating virtual environment... -call venv_dolibarr\Scripts\activate.bat - -echo. -echo Starting Standalone Dolibarr MCP Server... -echo βœ… Using Python libraries without MCP package -echo βœ… No Windows permission issues -echo βœ… All CRUD operations available -echo. - -REM Start the standalone server -python -m src.dolibarr_mcp.standalone_server - -echo. -echo Server stopped. -pause diff --git a/run_ultra.bat b/run_ultra.bat deleted file mode 100644 index 0894885..0000000 --- a/run_ultra.bat +++ /dev/null @@ -1,56 +0,0 @@ -@echo off -cls -echo ====================================== -echo Dolibarr MCP ULTRA Server -echo ====================================== -echo Maximum Windows Compatibility Mode -echo ZERO compiled extensions (.pyd files) -echo. - -REM Check if ultra virtual environment exists -if not exist "venv_ultra\Scripts\activate.bat" ( - echo [ERROR] Ultra virtual environment not found! - echo Please run: setup_ultra.bat first - echo. - pause - exit /b 1 -) - -REM Check if .env exists -if not exist ".env" ( - echo [ERROR] Configuration file .env not found! - echo. - echo Creating .env from template... - copy ".env.example" ".env" >nul - echo [INFO] Please edit .env file with your Dolibarr credentials: - echo DOLIBARR_URL=https://your-dolibarr-instance.com/api/index.php - echo DOLIBARR_API_KEY=your_api_key_here - echo. - pause - exit /b 1 -) - -echo Activating ultra virtual environment... -call venv_ultra\Scripts\activate.bat - -echo. -echo πŸš€ Starting ULTRA-COMPATIBLE Dolibarr MCP Server... -echo βœ… Pure Python implementation -echo βœ… ZERO compiled extensions -echo βœ… Standard library + requests only -echo βœ… Works on ANY Windows version -echo. -echo Available features: -echo β€’ All CRUD operations for Dolibarr -echo β€’ Interactive testing console -echo β€’ Professional error handling -echo β€’ Zero permission issues -echo. - -REM Start the ultra-simple server directly (avoid import issues) -python src\dolibarr_mcp\ultra_simple_server.py - -echo. -echo Server stopped. -echo. -pause diff --git a/setup.bat b/setup.bat deleted file mode 100644 index 667590b..0000000 --- a/setup.bat +++ /dev/null @@ -1,53 +0,0 @@ -@echo off -:: Dolibarr MCP Setup Script - Fixed Version 2.0 -:: Uses dedicated environment setup to avoid conflicts - -echo. -echo ====================================== -echo Dolibarr MCP Development Setup v2.0 -echo ====================================== -echo. - -:: First, clean up any old build artifacts -echo Cleaning up old build artifacts... - -:: Remove ALL egg-info directories recursively -for /f "delims=" %%i in ('dir /s /b /a:d *.egg-info 2^>nul') do ( - rmdir /s /q "%%i" 2>nul -) - -:: Clean other build directories -if exist build rmdir /s /q build 2>nul -if exist dist rmdir /s /q dist 2>nul -if exist .eggs rmdir /s /q .eggs 2>nul - -:: Remove old virtual environment if exists -if exist venv_dolibarr ( - echo Removing old virtual environment... - rmdir /s /q venv_dolibarr -) - -echo. -echo Running environment setup... -echo. - -:: Run the Python setup script -python setup_env.py - -if errorlevel 1 ( - echo. - echo ====================================== - echo Setup failed! - echo ====================================== - echo. - echo Troubleshooting tips: - echo 1. Make sure Python 3.8+ is installed - echo 2. Try running: python --version - echo 3. If issues persist, run cleanup.bat first - echo. - pause - exit /b 1 -) - -echo. -pause diff --git a/setup.py b/setup.py deleted file mode 100644 index 65c0fd7..0000000 --- a/setup.py +++ /dev/null @@ -1,49 +0,0 @@ -#!/usr/bin/env python3 -"""Setup script for Dolibarr MCP package.""" - -from setuptools import setup, find_packages -import os - -# Read the README file -here = os.path.abspath(os.path.dirname(__file__)) -with open(os.path.join(here, 'README.md'), encoding='utf-8') as f: - long_description = f.read() - -setup( - name='dolibarr-mcp', - version='1.0.0', - description='Professional Model Context Protocol server for complete Dolibarr ERP management', - long_description=long_description, - long_description_content_type='text/markdown', - url='https://github.com/latinogino/dolibarr-mcp', - author='Dolibarr MCP Team', - license='MIT', - classifiers=[ - 'Development Status :: 5 - Production/Stable', - 'Intended Audience :: Developers', - 'License :: OSI Approved :: MIT License', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.8', - 'Programming Language :: Python :: 3.9', - 'Programming Language :: Python :: 3.10', - 'Programming Language :: Python :: 3.11', - ], - keywords='dolibarr mcp erp api integration', - package_dir={'': 'src'}, - packages=find_packages(where='src'), - python_requires='>=3.8', - install_requires=[ - 'mcp>=1.0.0', - 'requests>=2.31.0', - 'aiohttp>=3.9.0', - 'pydantic>=2.5.0', - 'click>=8.1.0', - 'python-dotenv>=1.0.0', - 'typing-extensions>=4.8.0', - ], - entry_points={ - 'console_scripts': [ - 'dolibarr-mcp=dolibarr_mcp.cli:main', - ], - }, -) diff --git a/setup.sh b/setup.sh deleted file mode 100644 index b912117..0000000 --- a/setup.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/bin/bash -echo "πŸš€ Setting up Dolibarr MCP Development Environment..." -echo - -# Run the setup script -python3 setup.py - -echo -echo "πŸ“‹ Quick Start Commands:" -echo -echo " Activate virtual environment:" -echo " \$ source venv_dolibarr/bin/activate" -echo -echo " Test the server:" -echo " \$ venv_dolibarr/bin/python -m dolibarr_mcp.dolibarr_mcp_server" -echo -echo " Run with Docker:" -echo " \$ docker-compose up" -echo diff --git a/setup_claude_complete.bat b/setup_claude_complete.bat deleted file mode 100644 index e165ce9..0000000 --- a/setup_claude_complete.bat +++ /dev/null @@ -1,109 +0,0 @@ -@echo off -echo ====================================== -echo DOLIBARR MCP - COMPLETE SETUP -echo ====================================== -echo. -echo This script will ensure your Claude Desktop -echo configuration works correctly. -echo. - -cd /d "C:\Users\gino\GitHub\dolibarr-mcp" - -echo Step 1: Creating/Checking Virtual Environment... -if not exist "venv_dolibarr" ( - python -m venv venv_dolibarr - echo Created new virtual environment -) else ( - echo Virtual environment exists -) - -echo. -echo Step 2: Activating Virtual Environment... -call venv_dolibarr\Scripts\activate - -echo. -echo Step 3: Upgrading pip... -python -m pip install --upgrade pip >nul 2>&1 - -echo. -echo Step 4: Installing package in development mode... -pip install -e . >nul 2>&1 -if %errorlevel% equ 0 ( - echo βœ“ Package installed successfully -) else ( - echo ⚠ Installation warning - continuing... -) - -echo. -echo Step 5: Installing all dependencies... -pip install requests python-dotenv >nul 2>&1 -pip install mcp aiohttp pydantic click typing-extensions >nul 2>&1 -echo βœ“ Dependencies installed - -echo. -echo Step 6: Testing Configuration... -echo ---------------------------------------- - -REM Set environment variables for testing -set DOLIBARR_BASE_URL=https://db.ginos.cloud/api/index.php/ -set DOLIBARR_API_KEY=7cxAAO835BF7bXy6DsQ2j2a7nT6ectGY - -REM Test import -python -c "from src.dolibarr_mcp import __version__; print(f' Module version: {__version__}')" 2>nul -if %errorlevel% equ 0 ( - echo βœ“ Module imports correctly -) else ( - echo βœ— Module import failed -) - -REM Test config loading -python -c "from src.dolibarr_mcp.config import Config; c=Config(); print(f' Config URL: {c.dolibarr_url[:40]}...')" 2>nul -if %errorlevel% equ 0 ( - echo βœ“ Configuration loads correctly -) else ( - echo βœ— Configuration failed -) - -echo. -echo Step 7: Creating test launcher... -( -echo @echo off -echo cd /d "C:\Users\gino\GitHub\dolibarr-mcp" -echo set DOLIBARR_BASE_URL=https://db.ginos.cloud/api/index.php/ -echo set DOLIBARR_API_KEY=7cxAAO835BF7bXy6DsQ2j2a7nT6ectGY -echo venv_dolibarr\Scripts\python.exe -m dolibarr_mcp.dolibarr_mcp_server -echo pause -) > test_mcp_server.bat -echo βœ“ Created test_mcp_server.bat - -echo. -echo ======================================== -echo SETUP COMPLETE! -echo ======================================== -echo. -echo Your Claude Desktop configuration is: -echo. -echo { -echo "mcpServers": { -echo "dolibarr-python": { -echo "command": "C:\\Users\\gino\\GitHub\\dolibarr-mcp\\venv_dolibarr\\Scripts\\python.exe", -echo "args": ["-m", "dolibarr_mcp.dolibarr_mcp_server"], -echo "cwd": "C:\\Users\\gino\\GitHub\\dolibarr-mcp", -echo "env": { -echo "DOLIBARR_BASE_URL": "https://db.ginos.cloud/api/index.php/", -echo "DOLIBARR_API_KEY": "7cxAAO835BF7bXy6DsQ2j2a7nT6ectGY" -echo } -echo } -echo } -echo } -echo. -echo βœ“ This configuration is CONFIRMED WORKING -echo. -echo Next steps: -echo 1. Copy the above configuration to %%APPDATA%%\Claude\claude_desktop_config.json -echo 2. Restart Claude Desktop -echo 3. Test with: "Test Dolibarr connection" -echo. -echo To test the server manually, run: test_mcp_server.bat -echo. -pause diff --git a/setup_env.py b/setup_env.py deleted file mode 100644 index 4883b67..0000000 --- a/setup_env.py +++ /dev/null @@ -1,109 +0,0 @@ -#!/usr/bin/env python3 -"""Environment setup script for Dolibarr MCP development.""" - -import os -import subprocess -import sys -import platform -import shutil - -def run_command(command, shell=False): - """Run a command and handle errors.""" - try: - result = subprocess.run(command, shell=shell, check=True, capture_output=True, text=True) - command_str = ' '.join(command) if isinstance(command, list) else command - print(f"[OK] {command_str}") - return True - except subprocess.CalledProcessError as e: - command_str = ' '.join(command) if isinstance(command, list) else command - print(f"[ERROR] {command_str}") - if e.stderr: - print(f" Error: {e.stderr}") - return False - -def setup_environment(): - """Set up the development environment.""" - print("\n" + "="*50) - print("Dolibarr MCP Development Environment Setup") - print("="*50 + "\n") - - # Determine platform-specific paths - if platform.system() == "Windows": - venv_name = "venv_dolibarr" - python_exe = os.path.join(venv_name, "Scripts", "python.exe") - pip_exe = os.path.join(venv_name, "Scripts", "pip.exe") - activate_script = os.path.join(venv_name, "Scripts", "activate.bat") - else: - venv_name = "venv_dolibarr" - python_exe = os.path.join(venv_name, "bin", "python") - pip_exe = os.path.join(venv_name, "bin", "pip") - activate_script = f"source {os.path.join(venv_name, 'bin', 'activate')}" - - # Create virtual environment - print("1. Creating virtual environment...") - if not run_command([sys.executable, "-m", "venv", venv_name]): - print("Failed to create virtual environment!") - return False - - # Ensure pip is installed - print("\n2. Ensuring pip is installed...") - run_command([python_exe, "-m", "ensurepip", "--upgrade"]) - - # Upgrade pip - print("\n3. Upgrading pip, setuptools, and wheel...") - if not run_command([python_exe, "-m", "pip", "install", "--upgrade", "pip", "setuptools", "wheel"]): - print("Failed to upgrade pip!") - return False - - # Install requirements - print("\n4. Installing requirements...") - if os.path.exists("requirements.txt"): - if not run_command([pip_exe, "install", "-r", "requirements.txt"]): - print("Failed to install requirements!") - return False - - # Install package in editable mode - print("\n5. Installing dolibarr-mcp package...") - if not run_command([pip_exe, "install", "-e", "."]): - print("Failed to install package!") - return False - - # Create .env file if it doesn't exist - print("\n6. Setting up configuration...") - if not os.path.exists(".env"): - if os.path.exists(".env.example"): - shutil.copy(".env.example", ".env") - print("[OK] Created .env from .env.example") - else: - with open(".env", "w") as f: - f.write("# Dolibarr MCP Configuration\n") - f.write("DOLIBARR_URL=https://your-dolibarr-instance.com/api/index.php\n") - f.write("DOLIBARR_API_KEY=your_api_key_here\n") - f.write("LOG_LEVEL=INFO\n") - print("[OK] Created default .env file") - print(" [!] Please edit .env with your Dolibarr credentials") - else: - print("[OK] .env file already exists") - - # Test import - use ASCII characters to avoid encoding issues - print("\n7. Testing installation...") - test_command = [python_exe, "-c", "from dolibarr_mcp.config import Config; print('[OK] Import test passed')"] - if run_command(test_command): - print("\n" + "="*50) - print("[SUCCESS] Setup Complete!") - print("="*50) - print(f"\nVirtual environment: {os.path.abspath(venv_name)}") - print(f"\nTo activate the environment:") - print(f" {activate_script}") - print(f"\nTo run the server:") - print(f" python -m dolibarr_mcp") - return True - else: - print("\n[ERROR] Installation test failed!") - return False - -if __name__ == "__main__": - if setup_environment(): - sys.exit(0) - else: - sys.exit(1) diff --git a/setup_manual.bat b/setup_manual.bat deleted file mode 100644 index 57b56d3..0000000 --- a/setup_manual.bat +++ /dev/null @@ -1,72 +0,0 @@ -@echo off -echo πŸ”§ Manual Setup for Dolibarr MCP (Windows Fix) -echo. - -REM Activate virtual environment first -echo 🐍 Activating virtual environment... -call venv_dolibarr\Scripts\activate.bat - -REM Verify Python installation -echo βœ… Checking Python version... -venv_dolibarr\Scripts\python.exe --version - -REM Install requirements again with explicit encoding -echo πŸ“¦ Installing requirements with UTF-8 encoding... -set PYTHONIOENCODING=utf-8 -venv_dolibarr\Scripts\python.exe -m pip install --upgrade pip -venv_dolibarr\Scripts\python.exe -m pip install aiohttp>=3.9.0 -venv_dolibarr\Scripts\python.exe -m pip install pydantic>=2.5.0 -venv_dolibarr\Scripts\python.exe -m pip install python-dotenv>=1.0.0 -venv_dolibarr\Scripts\python.exe -m pip install click>=8.1.0 -venv_dolibarr\Scripts\python.exe -m pip install typing-extensions>=4.8.0 - -REM Install MCP manually - try different sources -echo πŸ”— Installing MCP framework... -venv_dolibarr\Scripts\python.exe -m pip install mcp==1.0.0 -if errorlevel 1 ( - echo πŸ”„ Trying alternative MCP installation... - venv_dolibarr\Scripts\python.exe -m pip install git+https://github.com/modelcontextprotocol/python-sdk.git -) - -REM Create .env file if it doesn't exist -if not exist .env ( - echo πŸ“ Creating .env file... - copy .env.example .env 2>nul || ( - echo # Dolibarr MCP Configuration > .env - echo DOLIBARR_URL=https://your-dolibarr-instance.com/api/index.php >> .env - echo DOLIBARR_API_KEY=your_api_key_here >> .env - echo LOG_LEVEL=INFO >> .env - ) -) - -REM Test Python module import manually -echo πŸ§ͺ Testing module import... -venv_dolibarr\Scripts\python.exe -c "import sys; sys.path.insert(0, 'src'); from dolibarr_mcp.config import Config; print('βœ… Module import successful')" 2>nul -if errorlevel 1 ( - echo ⚠️ Direct import failed, but this is expected without development installation -) else ( - echo βœ… Module import working! -) - -REM Test basic connection -echo πŸ”Œ Testing Dolibarr connection (if configured)... -venv_dolibarr\Scripts\python.exe test_connection.py 2>nul -if errorlevel 1 ( - echo ⚠️ Connection test failed - please check your .env configuration -) else ( - echo βœ… Connection test passed! -) - -echo. -echo 🎯 Manual Setup Complete! -echo. -echo πŸ“‹ Next Steps: -echo 1. Edit .env file: notepad .env -echo 2. Configure your Dolibarr URL and API key -echo 3. Test: venv_dolibarr\Scripts\python.exe test_connection.py -echo 4. Run MCP: venv_dolibarr\Scripts\python.exe -m dolibarr_mcp.dolibarr_mcp_server -echo. -echo πŸ’‘ Alternative: Use Docker instead: docker-compose up -echo. - -pause diff --git a/setup_standalone.bat b/setup_standalone.bat deleted file mode 100644 index f3e0541..0000000 --- a/setup_standalone.bat +++ /dev/null @@ -1,78 +0,0 @@ -@echo off -cls -echo ====================================== -echo Dolibarr MCP Standalone Setup v3.0 -echo ====================================== -echo SOLVES: Windows pywin32 permission issues -echo METHOD: Standalone implementation without MCP package -echo. - -echo [1/5] Cleanup old environment... -if exist "venv_dolibarr" ( - echo Removing old virtual environment... - rmdir /s /q "venv_dolibarr" 2>nul - timeout /t 1 /nobreak >nul -) - -echo [2/5] Creating fresh Python environment... -python -m venv venv_dolibarr -if %ERRORLEVEL% neq 0 ( - echo [ERROR] Virtual environment creation failed - echo Check: python --version - pause - exit /b 1 -) - -echo [3/5] Activating environment... -call venv_dolibarr\Scripts\activate.bat - -echo [4/5] Installing Windows-compatible dependencies... -echo Installing core packages without pywin32... - -REM Install packages one by one to handle failures gracefully -pip install --no-warn-script-location requests>=2.31.0 -pip install --no-warn-script-location aiohttp>=3.9.0 -pip install --no-warn-script-location pydantic>=2.5.0 -pip install --no-warn-script-location python-dotenv>=1.0.0 -pip install --no-warn-script-location click>=8.1.0 -pip install --no-warn-script-location typing-extensions>=4.8.0 -pip install --no-warn-script-location jsonschema>=4.20.0 -pip install --no-warn-script-location httpx>=0.27.1 - -echo. -echo [5/5] Testing installation... -python -c "import aiohttp, pydantic, requests, json; print('βœ… Core libraries working')" 2>nul -if %ERRORLEVEL% neq 0 ( - echo [WARNING] Some import test failed, but continuing... -) - -echo. -echo Checking configuration... -if not exist ".env" ( - echo Creating .env from template... - copy ".env.example" ".env" >nul 2>&1 - echo [INFO] Created .env file - please configure your Dolibarr credentials -) - -echo. -echo ====================================== -echo βœ… STANDALONE SETUP COMPLETE! -echo ====================================== -echo. -echo πŸ”§ What was installed: -echo β€’ Python virtual environment (venv_dolibarr) -echo β€’ Core HTTP libraries (aiohttp, requests, httpx) -echo β€’ Data validation (pydantic) -echo β€’ Configuration (.env support) -echo β€’ JSON-RPC support (no MCP package needed) -echo. -echo πŸ“ Next Steps: -echo 1. Edit .env file with your Dolibarr credentials -echo 2. Run: run_standalone.bat -echo. -echo πŸš€ Test the server: -echo python -m src.dolibarr_mcp.standalone_server -echo. -echo πŸ’‘ This version works WITHOUT the problematic MCP package! -echo. -pause diff --git a/setup_ultra.bat b/setup_ultra.bat deleted file mode 100644 index 0df0f55..0000000 --- a/setup_ultra.bat +++ /dev/null @@ -1,87 +0,0 @@ -@echo off -cls -echo ====================================== -echo Dolibarr MCP ULTRA Setup v4.0 -echo ====================================== -echo GUARANTEED Windows compatibility! -echo METHOD: ZERO compiled extensions (.pyd files) -echo RESULT: Works on ANY Windows system! -echo. - -echo [1/4] Cleanup... -if exist "venv_ultra" ( - echo Removing old virtual environment... - rmdir /s /q "venv_ultra" 2>nul - timeout /t 1 /nobreak >nul -) - -echo [2/4] Creating Python environment... -python -m venv venv_ultra -if %ERRORLEVEL% neq 0 ( - echo [ERROR] Virtual environment creation failed - echo Please check: python --version - pause - exit /b 1 -) - -echo [3/4] Activating environment... -call venv_ultra\Scripts\activate.bat - -echo Installing ULTRA-MINIMAL dependencies (no compiled extensions)... -echo This will work on ANY Windows system! - -REM Install only pure Python packages that never cause permission issues -pip install --no-warn-script-location requests>=2.31.0 -echo βœ… requests installed - -pip install --no-warn-script-location python-dotenv>=1.0.0 -echo βœ… python-dotenv installed - -pip install --no-warn-script-location click>=8.1.0 -echo βœ… click installed - -echo. -echo [4/4] Testing ultra-minimal installation... -python -c "import requests, json, sys; print('βœ… Ultra-minimal setup successful!'); print(f'Python: {sys.version}')" - -if %ERRORLEVEL% neq 0 ( - echo [WARNING] Basic test failed, but this is likely OK for ultra setup -) - -echo. -echo Checking configuration... -if not exist ".env" ( - echo Creating .env from template... - copy ".env.example" ".env" >nul 2>&1 - echo [INFO] Created .env file - please configure your Dolibarr credentials -) - -echo. -echo ====================================== -echo βœ… ULTRA SETUP COMPLETE! -echo ====================================== -echo. -echo 🎯 What makes this different: -echo β€’ ZERO compiled Python extensions (.pyd files) -echo β€’ Only pure Python packages (requests, dotenv, click) -echo β€’ Uses Python standard library for everything else -echo β€’ NO aiohttp, pydantic, pywin32, or other compiled packages -echo β€’ Works on Windows XP through Windows 11! -echo. -echo πŸ› οΈ What's installed: -echo β€’ Python virtual environment (venv_ultra) -echo β€’ requests library (HTTP client) -echo β€’ python-dotenv (configuration) -echo β€’ click (command line interface) -echo β€’ Standard library JSON, logging, etc. -echo. -echo πŸ“ Next Steps: -echo 1. Edit .env file with your Dolibarr credentials -echo 2. Run: run_ultra.bat -echo. -echo πŸš€ Test the ultra server: -echo python -m src.dolibarr_mcp.ultra_simple_server -echo. -echo πŸŽ‰ GUARANTEED to work - no permission issues possible! -echo. -pause diff --git a/setup_windows_fix.bat b/setup_windows_fix.bat deleted file mode 100644 index bf4638a..0000000 --- a/setup_windows_fix.bat +++ /dev/null @@ -1,92 +0,0 @@ -@echo off -cls -echo ====================================== -echo Dolibarr MCP Windows Setup Fix v2.1 -echo ====================================== - -echo Cleaning up old artifacts... - -REM Force cleanup of problematic files -if exist "venv_dolibarr" ( - echo Removing old virtual environment... - rmdir /s /q "venv_dolibarr" 2>nul - timeout /t 2 /nobreak >nul -) - -echo Creating fresh virtual environment... -python -m venv venv_dolibarr -if %ERRORLEVEL% neq 0 ( - echo [ERROR] Failed to create virtual environment - echo Check if Python 3.8+ is installed: python --version - pause - exit /b 1 -) - -echo Activating virtual environment... -call venv_dolibarr\Scripts\activate.bat -if %ERRORLEVEL% neq 0 ( - echo [ERROR] Failed to activate virtual environment - pause - exit /b 1 -) - -echo Upgrading pip... -python -m pip install --upgrade pip -if %ERRORLEVEL% neq 0 ( - echo [WARNING] Pip upgrade failed but continuing... -) - -echo Installing dependencies (minimal set to avoid Windows issues)... -pip install -r requirements-minimal.txt -if %ERRORLEVEL% neq 0 ( - echo [ERROR] Failed to install dependencies - echo Trying alternative installation method... - - echo Installing packages individually... - pip install mcp>=1.0.0 - pip install requests>=2.31.0 - pip install aiohttp>=3.9.0 - pip install pydantic>=2.5.0 - pip install click>=8.1.0 - pip install python-dotenv>=1.0.0 - pip install typing-extensions>=4.8.0 - - if %ERRORLEVEL% neq 0 ( - echo [ERROR] Individual package installation also failed - echo Trying with --no-deps flag for problematic packages... - pip install --no-deps aiohttp - pip install --no-deps pydantic - ) -) - -echo. -echo Testing installation... -python -c "import mcp; import requests; import aiohttp; import pydantic; import click; import dotenv; print('βœ… All core packages imported successfully')" -if %ERRORLEVEL% neq 0 ( - echo [WARNING] Some packages may not have installed correctly - echo But attempting to continue... -) - -echo. -echo Checking if .env file exists... -if not exist ".env" ( - echo Creating .env file from template... - copy ".env.example" ".env" >nul 2>&1 - echo [INFO] Please edit .env file with your Dolibarr credentials -) - -echo. -echo ====================================== -echo Setup Complete! -echo ====================================== -echo. -echo βœ… Virtual environment: venv_dolibarr -echo βœ… Dependencies: Installed (minimal set) -echo πŸ“ Next steps: -echo 1. Edit .env file with your Dolibarr credentials -echo 2. Run: start_server.bat -echo. -echo To test the server: -echo python -m src.dolibarr_mcp -echo. -pause diff --git a/src/dolibarr_mcp/__init__.py b/src/dolibarr_mcp/__init__.py index 6b26774..e1baab1 100644 --- a/src/dolibarr_mcp/__init__.py +++ b/src/dolibarr_mcp/__init__.py @@ -4,7 +4,7 @@ Dolibarr MCP Server Package Professional Model Context Protocol server for complete Dolibarr ERP/CRM management. """ -__version__ = "1.0.0" +__version__ = "1.1.0" __author__ = "Dolibarr MCP Team" from .dolibarr_client import DolibarrClient diff --git a/src/dolibarr_mcp/cli.py b/src/dolibarr_mcp/cli.py index 9b50d0b..d7a8458 100644 --- a/src/dolibarr_mcp/cli.py +++ b/src/dolibarr_mcp/cli.py @@ -11,7 +11,7 @@ from .dolibarr_mcp_server import main as server_main @click.group() -@click.version_option(version="1.0.1", prog_name="dolibarr-mcp") +@click.version_option(version="1.1.0", prog_name="dolibarr-mcp") def cli(): """Dolibarr MCP Server - Professional ERP integration via Model Context Protocol.""" pass @@ -91,7 +91,7 @@ def serve(host: str, port: int): @cli.command() def version(): """Show version information.""" - click.echo("Dolibarr MCP Server v1.0.1") + click.echo("Dolibarr MCP Server v1.1.0") click.echo("Professional ERP integration via Model Context Protocol") diff --git a/src/dolibarr_mcp/config.py b/src/dolibarr_mcp/config.py index 2356b1a..8de81b9 100644 --- a/src/dolibarr_mcp/config.py +++ b/src/dolibarr_mcp/config.py @@ -2,10 +2,9 @@ import os import sys -from typing import Optional -from pydantic import Field, field_validator -from pydantic_settings import BaseSettings +from pydantic import AliasChoices, Field, field_validator +from pydantic_settings import BaseSettings, SettingsConfigDict from dotenv import load_dotenv # Load environment variables from .env file @@ -14,23 +13,32 @@ load_dotenv() class Config(BaseSettings): """Configuration for Dolibarr MCP Server.""" - + + model_config = SettingsConfigDict( + env_file=".env", + env_file_encoding="utf-8", + case_sensitive=False, + validate_assignment=True, + extra="forbid", + ) + dolibarr_url: str = Field( description="Dolibarr API URL", - default="" + default="", ) - + dolibarr_api_key: str = Field( description="Dolibarr API key", - default="" + default="", + validation_alias=AliasChoices("dolibarr_api_key", "api_key"), ) - + log_level: str = Field( description="Logging level", - default="INFO" + default="INFO", ) - - @field_validator('dolibarr_url') + + @field_validator("dolibarr_url") @classmethod def validate_dolibarr_url(cls, v: str) -> str: """Validate Dolibarr URL.""" @@ -38,62 +46,69 @@ class Config(BaseSettings): v = os.getenv("DOLIBARR_URL") or os.getenv("DOLIBARR_BASE_URL", "") if not v: # Print warning but don't fail - print("⚠️ DOLIBARR_URL/DOLIBARR_BASE_URL not configured - API calls will fail", file=sys.stderr) + print( + "⚠️ DOLIBARR_URL/DOLIBARR_BASE_URL not configured - API calls will fail", + file=sys.stderr, + ) return "https://your-dolibarr-instance.com/api/index.php" - - if not v.startswith(('http://', 'https://')): + + if not v.startswith(("http://", "https://")): raise ValueError("DOLIBARR_URL must start with http:// or https://") - + # Remove trailing slash if present - v = v.rstrip('/') - + v = v.rstrip("/") + # Ensure it ends with the proper API path - if not v.endswith('/api/index.php'): - # Check if it already has /api somewhere - if '/api' in v: - # Just ensure it ends properly - if not v.endswith('/index.php'): - # Check if it ends with /api/index.php/ - if v.endswith('/index.php/'): - v = v[:-1] # Remove trailing slash - elif not v.endswith('/index.php'): - v = v + '/index.php' + if not v.endswith("/api/index.php"): + if "/api" in v: + if not v.endswith("/index.php"): + if v.endswith("/index.php/"): + v = v[:-1] + elif not v.endswith("/index.php"): + v = v + "/index.php" else: - # Add the full API path - v = v + '/api/index.php' - + v = v + "/api/index.php" + return v - - @field_validator('dolibarr_api_key') + + @field_validator("dolibarr_api_key") @classmethod def validate_api_key(cls, v: str) -> str: """Validate API key.""" if not v: v = os.getenv("DOLIBARR_API_KEY", "") if not v: - # Print warning but don't fail - print("⚠️ DOLIBARR_API_KEY not configured - API authentication will fail", file=sys.stderr) - print("πŸ“ Please set DOLIBARR_API_KEY in your .env file or Claude configuration", file=sys.stderr) + print( + "⚠️ DOLIBARR_API_KEY not configured - API authentication will fail", + file=sys.stderr, + ) + print( + "πŸ“ Please set DOLIBARR_API_KEY in your .env file or Claude configuration", + file=sys.stderr, + ) return "placeholder_api_key" - + if v == "your_dolibarr_api_key_here": - print("⚠️ Using placeholder API key - please configure a real API key", file=sys.stderr) - + print( + "⚠️ Using placeholder API key - please configure a real API key", + file=sys.stderr, + ) + return v - - @field_validator('log_level') + + @field_validator("log_level") @classmethod def validate_log_level(cls, v: str) -> str: """Validate log level.""" if not v: v = os.getenv("LOG_LEVEL", "INFO") - - valid_levels = {'DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'} + + valid_levels = {"DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"} if v.upper() not in valid_levels: print(f"⚠️ Invalid LOG_LEVEL '{v}', using INFO", file=sys.stderr) - return 'INFO' + return "INFO" return v.upper() - + @classmethod def from_env(cls) -> "Config": """Create configuration from environment variables with validation.""" @@ -101,13 +116,15 @@ class Config(BaseSettings): config = cls( dolibarr_url=os.getenv("DOLIBARR_URL") or os.getenv("DOLIBARR_BASE_URL", ""), dolibarr_api_key=os.getenv("DOLIBARR_API_KEY", ""), - log_level=os.getenv("LOG_LEVEL", "INFO") + log_level=os.getenv("LOG_LEVEL", "INFO"), ) - # Debug output for troubleshooting if os.getenv("DEBUG_CONFIG"): print(f"βœ… Config loaded:", file=sys.stderr) print(f" URL: {config.dolibarr_url}", file=sys.stderr) - print(f" API Key: {'*' * 10 if config.dolibarr_api_key else 'NOT SET'}", file=sys.stderr) + print( + f" API Key: {'*' * 10 if config.dolibarr_api_key else 'NOT SET'}", + file=sys.stderr, + ) return config except Exception as e: print(f"❌ Configuration Error: {e}", file=sys.stderr) @@ -115,8 +132,14 @@ class Config(BaseSettings): print("πŸ’‘ Quick Setup Guide:", file=sys.stderr) print("1. Copy .env.example to .env", file=sys.stderr) print("2. Edit .env with your Dolibarr details:", file=sys.stderr) - print(" DOLIBARR_URL=https://your-dolibarr-instance.com", file=sys.stderr) - print(" (or DOLIBARR_BASE_URL=https://your-dolibarr-instance.com/api/index.php/)", file=sys.stderr) + print( + " DOLIBARR_URL=https://your-dolibarr-instance.com", + file=sys.stderr, + ) + print( + " (or DOLIBARR_BASE_URL=https://your-dolibarr-instance.com/api/index.php/)", + file=sys.stderr, + ) print(" DOLIBARR_API_KEY=your_api_key_here", file=sys.stderr) print(file=sys.stderr) print("πŸ”§ Dolibarr API Key Setup:", file=sys.stderr) @@ -127,30 +150,22 @@ class Config(BaseSettings): print(" 5. Create a new API key", file=sys.stderr) print(file=sys.stderr) raise - - # Alias for backward compatibility + + def validate_config(self) -> None: + """Validate current configuration values.""" + self.dolibarr_url = type(self).validate_dolibarr_url(self.dolibarr_url) + self.dolibarr_api_key = type(self).validate_api_key(self.dolibarr_api_key) + self.log_level = type(self).validate_log_level(self.log_level) + + if self.dolibarr_url.endswith('your-dolibarr-instance.com/api/index.php') or self.dolibarr_api_key in {'', 'placeholder_api_key', 'your_dolibarr_api_key_here'}: + raise ValueError('Dolibarr configuration is incomplete') + @property def api_key(self) -> str: """Backward compatibility for api_key property.""" return self.dolibarr_api_key - - class Config: - """Pydantic configuration.""" - env_file = '.env' - env_file_encoding = 'utf-8' - case_sensitive = False - # Load from environment - env_prefix = "" - - @classmethod - def customise_sources( - cls, - init_settings, - env_settings, - file_secret_settings - ): - return ( - init_settings, - env_settings, - file_secret_settings, - ) + + @api_key.setter + def api_key(self, value: str) -> None: + """Allow updating the API key via legacy attribute.""" + self.dolibarr_api_key = value diff --git a/src/dolibarr_mcp/dolibarr_client.py b/src/dolibarr_mcp/dolibarr_client.py index 9eb8575..6ffd02b 100644 --- a/src/dolibarr_mcp/dolibarr_client.py +++ b/src/dolibarr_mcp/dolibarr_client.py @@ -2,8 +2,7 @@ import json import logging -from typing import Any, Dict, List, Optional, Union -from urllib.parse import urljoin, quote +from typing import Any, Dict, List, Optional import aiohttp from aiohttp import ClientSession, ClientTimeout @@ -61,22 +60,50 @@ class DolibarrClient: if self.session: await self.session.close() self.session = None + + @staticmethod + def _extract_identifier(response: Any) -> Any: + """Return the identifier from Dolibarr responses when available.""" + if isinstance(response, dict): + if "id" in response: + return response["id"] + success = response.get("success") + if isinstance(success, dict) and "id" in success: + return success["id"] + return response + + @staticmethod + def _merge_payload(data: Optional[Dict[str, Any]] = None, **kwargs) -> Dict[str, Any]: + """Merge an optional dictionary with keyword overrides.""" + payload: Dict[str, Any] = {} + if data: + payload.update(data) + if kwargs: + payload.update(kwargs) + return payload + + async def request( + self, + method: str, + endpoint: str, + params: Optional[Dict] = None, + data: Optional[Dict] = None + ) -> Dict[str, Any]: + """Public helper retained for compatibility with legacy integrations and tests.""" + return await self._make_request(method, endpoint, params=params, data=data) + def _build_url(self, endpoint: str) -> str: """Build full API URL.""" - # Remove leading slash from endpoint endpoint = endpoint.lstrip('/') - - # Special handling for status endpoint + base = self.base_url.rstrip('/') + if endpoint == "status": - # Try different possible locations for status endpoint - # Some Dolibarr versions have it at /api/status instead of /api/index.php/status - base = self.base_url.replace('/index.php', '') - return f"{base}/status" - - # For all other endpoints, use the standard format - return f"{self.base_url}/{endpoint}" - + base_without_index = base.replace('/index.php', '') + return f"{base_without_index}/status" + + return f"{base}/{endpoint}" + async def _make_request( self, method: str, @@ -165,15 +192,19 @@ class DolibarrClient: # SYSTEM ENDPOINTS # ============================================================================ + async def test_connection(self) -> Dict[str, Any]: + """Compatibility helper that proxies to get_status.""" + return await self.get_status() + async def get_status(self) -> Dict[str, Any]: """Get API status and version information.""" try: # First try the standard status endpoint - return await self._make_request("GET", "status") + return await self.request("GET", "status") except DolibarrAPIError: # If status fails, try to get module list as a connectivity test try: - result = await self._make_request("GET", "setup/modules") + result = await self.request("GET", "setup/modules") if result: return { "success": 1, @@ -186,7 +217,7 @@ class DolibarrClient: # If all else fails, try a simple user list try: - result = await self._make_request("GET", "users?limit=1") + result = await self.request("GET", "users?limit=1") if result is not None: return { "success": 1, @@ -206,24 +237,36 @@ class DolibarrClient: if page > 1: params["page"] = page - result = await self._make_request("GET", "users", params=params) + result = await self.request("GET", "users", params=params) return result if isinstance(result, list) else [] async def get_user_by_id(self, user_id: int) -> Dict[str, Any]: """Get specific user by ID.""" - return await self._make_request("GET", f"users/{user_id}") + return await self.request("GET", f"users/{user_id}") - async def create_user(self, **kwargs) -> Dict[str, Any]: + async def create_user( + self, + data: Optional[Dict[str, Any]] = None, + **kwargs, + ) -> Dict[str, Any]: """Create a new user.""" - return await self._make_request("POST", "users", data=kwargs) - - async def update_user(self, user_id: int, **kwargs) -> Dict[str, Any]: + payload = self._merge_payload(data, **kwargs) + result = await self.request("POST", "users", data=payload) + return self._extract_identifier(result) + + async def update_user( + self, + user_id: int, + data: Optional[Dict[str, Any]] = None, + **kwargs, + ) -> Dict[str, Any]: """Update an existing user.""" - return await self._make_request("PUT", f"users/{user_id}", data=kwargs) - + payload = self._merge_payload(data, **kwargs) + return await self.request("PUT", f"users/{user_id}", data=payload) + async def delete_user(self, user_id: int) -> Dict[str, Any]: """Delete a user.""" - return await self._make_request("DELETE", f"users/{user_id}") + return await self.request("DELETE", f"users/{user_id}") # ============================================================================ # CUSTOMER/THIRD PARTY MANAGEMENT @@ -235,56 +278,53 @@ class DolibarrClient: if page > 1: params["page"] = page - result = await self._make_request("GET", "thirdparties", params=params) + result = await self.request("GET", "thirdparties", params=params) return result if isinstance(result, list) else [] async def get_customer_by_id(self, customer_id: int) -> Dict[str, Any]: """Get specific customer by ID.""" - return await self._make_request("GET", f"thirdparties/{customer_id}") + return await self.request("GET", f"thirdparties/{customer_id}") async def create_customer( self, - name: str, - email: Optional[str] = None, - phone: Optional[str] = None, - address: Optional[str] = None, - town: Optional[str] = None, - zip: Optional[str] = None, - country_id: int = 1, - type: int = 1, # 1=Customer, 2=Supplier, 3=Both - status: int = 1, - **kwargs + data: Optional[Dict[str, Any]] = None, + **kwargs, ) -> Dict[str, Any]: """Create a new customer/third party.""" - data = { - "name": name, - "status": status, - "client": type if type in [1, 3] else 0, - "fournisseur": 1 if type in [2, 3] else 0, - "country_id": country_id, - **kwargs - } - - if email: - data["email"] = email - if phone: - data["phone"] = phone - if address: - data["address"] = address - if town: - data["town"] = town - if zip: - data["zip"] = zip - - return await self._make_request("POST", "thirdparties", data=data) - - async def update_customer(self, customer_id: int, **kwargs) -> Dict[str, Any]: + payload = self._merge_payload(data, **kwargs) + + type_value = payload.pop("type", None) + if type_value is not None: + payload.setdefault("client", 1 if type_value in (1, 3) else 0) + payload.setdefault("fournisseur", 1 if type_value in (2, 3) else 0) + else: + payload.setdefault("client", 1) + + payload.setdefault("status", payload.get("status", 1)) + payload.setdefault("country_id", payload.get("country_id", 1)) + + result = await self.request("POST", "thirdparties", data=payload) + return self._extract_identifier(result) + + async def update_customer( + self, + customer_id: int, + data: Optional[Dict[str, Any]] = None, + **kwargs, + ) -> Dict[str, Any]: """Update an existing customer.""" - return await self._make_request("PUT", f"thirdparties/{customer_id}", data=kwargs) - + payload = self._merge_payload(data, **kwargs) + + type_value = payload.pop("type", None) + if type_value is not None: + payload["client"] = 1 if type_value in (1, 3) else 0 + payload["fournisseur"] = 1 if type_value in (2, 3) else 0 + + return await self.request("PUT", f"thirdparties/{customer_id}", data=payload) + async def delete_customer(self, customer_id: int) -> Dict[str, Any]: """Delete a customer.""" - return await self._make_request("DELETE", f"thirdparties/{customer_id}") + return await self.request("DELETE", f"thirdparties/{customer_id}") # ============================================================================ # PRODUCT MANAGEMENT @@ -293,60 +333,36 @@ class DolibarrClient: async def get_products(self, limit: int = 100) -> List[Dict[str, Any]]: """Get list of products.""" params = {"limit": limit} - result = await self._make_request("GET", "products", params=params) + result = await self.request("GET", "products", params=params) return result if isinstance(result, list) else [] async def get_product_by_id(self, product_id: int) -> Dict[str, Any]: """Get specific product by ID.""" - return await self._make_request("GET", f"products/{product_id}") + return await self.request("GET", f"products/{product_id}") async def create_product( self, - label: str, - price: float, - ref: Optional[str] = None, # Product reference/SKU - description: Optional[str] = None, - stock: Optional[int] = None, - **kwargs + data: Optional[Dict[str, Any]] = None, + **kwargs, + ) -> Dict[str, Any]: + """Create a new product or service.""" + payload = self._merge_payload(data, **kwargs) + result = await self.request("POST", "products", data=payload) + return self._extract_identifier(result) + + async def update_product( + self, + product_id: int, + data: Optional[Dict[str, Any]] = None, + **kwargs, ) -> Dict[str, Any]: - """Create a new product. - - Args: - label: Product name/label - price: Product price - ref: Product reference/SKU (required by Dolibarr, auto-generated if not provided) - description: Product description - stock: Initial stock quantity - **kwargs: Additional product fields - """ - import time - - # Generate ref if not provided (required field in Dolibarr) - if ref is None: - ref = f"PROD-{int(time.time())}" - - data = { - "ref": ref, # Required field - "label": label, - "price": price, - "price_ttc": price, # Price including tax (using same as price for simplicity) - **kwargs - } - - if description: - data["description"] = description - if stock is not None: - data["stock"] = stock - - return await self._make_request("POST", "products", data=data) - - async def update_product(self, product_id: int, **kwargs) -> Dict[str, Any]: """Update an existing product.""" - return await self._make_request("PUT", f"products/{product_id}", data=kwargs) - + payload = self._merge_payload(data, **kwargs) + return await self.request("PUT", f"products/{product_id}", data=payload) + async def delete_product(self, product_id: int) -> Dict[str, Any]: """Delete a product.""" - return await self._make_request("DELETE", f"products/{product_id}") + return await self.request("DELETE", f"products/{product_id}") # ============================================================================ # INVOICE MANAGEMENT @@ -358,42 +374,36 @@ class DolibarrClient: if status: params["status"] = status - result = await self._make_request("GET", "invoices", params=params) + result = await self.request("GET", "invoices", params=params) return result if isinstance(result, list) else [] async def get_invoice_by_id(self, invoice_id: int) -> Dict[str, Any]: """Get specific invoice by ID.""" - return await self._make_request("GET", f"invoices/{invoice_id}") + return await self.request("GET", f"invoices/{invoice_id}") async def create_invoice( self, - customer_id: int, - lines: List[Dict[str, Any]], - date: Optional[str] = None, - due_date: Optional[str] = None, - **kwargs + data: Optional[Dict[str, Any]] = None, + **kwargs, ) -> Dict[str, Any]: """Create a new invoice.""" - data = { - "socid": customer_id, - "lines": lines, - **kwargs - } - - if date: - data["date"] = date - if due_date: - data["due_date"] = due_date - - return await self._make_request("POST", "invoices", data=data) - - async def update_invoice(self, invoice_id: int, **kwargs) -> Dict[str, Any]: + payload = self._merge_payload(data, **kwargs) + result = await self.request("POST", "invoices", data=payload) + return self._extract_identifier(result) + + async def update_invoice( + self, + invoice_id: int, + data: Optional[Dict[str, Any]] = None, + **kwargs, + ) -> Dict[str, Any]: """Update an existing invoice.""" - return await self._make_request("PUT", f"invoices/{invoice_id}", data=kwargs) - + payload = self._merge_payload(data, **kwargs) + return await self.request("PUT", f"invoices/{invoice_id}", data=payload) + async def delete_invoice(self, invoice_id: int) -> Dict[str, Any]: """Delete an invoice.""" - return await self._make_request("DELETE", f"invoices/{invoice_id}") + return await self.request("DELETE", f"invoices/{invoice_id}") # ============================================================================ # ORDER MANAGEMENT @@ -405,25 +415,36 @@ class DolibarrClient: if status: params["status"] = status - result = await self._make_request("GET", "orders", params=params) + result = await self.request("GET", "orders", params=params) return result if isinstance(result, list) else [] async def get_order_by_id(self, order_id: int) -> Dict[str, Any]: """Get specific order by ID.""" - return await self._make_request("GET", f"orders/{order_id}") + return await self.request("GET", f"orders/{order_id}") - async def create_order(self, customer_id: int, **kwargs) -> Dict[str, Any]: + async def create_order( + self, + data: Optional[Dict[str, Any]] = None, + **kwargs, + ) -> Dict[str, Any]: """Create a new order.""" - data = {"socid": customer_id, **kwargs} - return await self._make_request("POST", "orders", data=data) - - async def update_order(self, order_id: int, **kwargs) -> Dict[str, Any]: + payload = self._merge_payload(data, **kwargs) + result = await self.request("POST", "orders", data=payload) + return self._extract_identifier(result) + + async def update_order( + self, + order_id: int, + data: Optional[Dict[str, Any]] = None, + **kwargs, + ) -> Dict[str, Any]: """Update an existing order.""" - return await self._make_request("PUT", f"orders/{order_id}", data=kwargs) - + payload = self._merge_payload(data, **kwargs) + return await self.request("PUT", f"orders/{order_id}", data=payload) + async def delete_order(self, order_id: int) -> Dict[str, Any]: """Delete an order.""" - return await self._make_request("DELETE", f"orders/{order_id}") + return await self.request("DELETE", f"orders/{order_id}") # ============================================================================ # CONTACT MANAGEMENT @@ -432,24 +453,36 @@ class DolibarrClient: async def get_contacts(self, limit: int = 100) -> List[Dict[str, Any]]: """Get list of contacts.""" params = {"limit": limit} - result = await self._make_request("GET", "contacts", params=params) + result = await self.request("GET", "contacts", params=params) return result if isinstance(result, list) else [] async def get_contact_by_id(self, contact_id: int) -> Dict[str, Any]: """Get specific contact by ID.""" - return await self._make_request("GET", f"contacts/{contact_id}") + return await self.request("GET", f"contacts/{contact_id}") - async def create_contact(self, **kwargs) -> Dict[str, Any]: + async def create_contact( + self, + data: Optional[Dict[str, Any]] = None, + **kwargs, + ) -> Dict[str, Any]: """Create a new contact.""" - return await self._make_request("POST", "contacts", data=kwargs) - - async def update_contact(self, contact_id: int, **kwargs) -> Dict[str, Any]: + payload = self._merge_payload(data, **kwargs) + result = await self.request("POST", "contacts", data=payload) + return self._extract_identifier(result) + + async def update_contact( + self, + contact_id: int, + data: Optional[Dict[str, Any]] = None, + **kwargs, + ) -> Dict[str, Any]: """Update an existing contact.""" - return await self._make_request("PUT", f"contacts/{contact_id}", data=kwargs) - + payload = self._merge_payload(data, **kwargs) + return await self.request("PUT", f"contacts/{contact_id}", data=payload) + async def delete_contact(self, contact_id: int) -> Dict[str, Any]: """Delete a contact.""" - return await self._make_request("DELETE", f"contacts/{contact_id}") + return await self.request("DELETE", f"contacts/{contact_id}") # ============================================================================ # RAW API CALL @@ -463,4 +496,4 @@ class DolibarrClient: data: Optional[Dict] = None ) -> Dict[str, Any]: """Make raw API call to any Dolibarr endpoint.""" - return await self._make_request(method, endpoint, params=params, data=data) + return await self.request(method, endpoint, params=params, data=data) diff --git a/src/dolibarr_mcp/simple_client.py b/src/dolibarr_mcp/simple_client.py deleted file mode 100644 index 80fb13c..0000000 --- a/src/dolibarr_mcp/simple_client.py +++ /dev/null @@ -1,318 +0,0 @@ -"""Ultra-simple Dolibarr API client - Windows compatible, zero compiled extensions.""" - -import json -import logging -import os -import sys -from typing import Any, Dict, List, Optional, Union - -# Only use standard library + requests (pure Python) -import requests -from requests.adapters import HTTPAdapter -from urllib3.util.retry import Retry - -# Minimal config handling without pydantic -class SimpleConfig: - """Simple configuration without pydantic - no compiled extensions.""" - - def __init__(self): - # Load .env manually - self.load_env() - - self.dolibarr_url = os.getenv("DOLIBARR_URL", "") - self.api_key = os.getenv("DOLIBARR_API_KEY", "") - self.log_level = os.getenv("LOG_LEVEL", "INFO") - - # Validate and fix URL - if not self.dolibarr_url or "your-dolibarr-instance" in self.dolibarr_url: - print("⚠️ DOLIBARR_URL not configured in .env file", file=sys.stderr) - self.dolibarr_url = "https://your-dolibarr-instance.com/api/index.php" - - if not self.api_key or "your_dolibarr_api_key" in self.api_key: - print("⚠️ DOLIBARR_API_KEY not configured in .env file", file=sys.stderr) - self.api_key = "placeholder_api_key" - - # Ensure URL format - if self.dolibarr_url and not self.dolibarr_url.endswith('/api/index.php'): - if '/api' not in self.dolibarr_url: - self.dolibarr_url = self.dolibarr_url.rstrip('/') + '/api/index.php' - elif not self.dolibarr_url.endswith('/index.php'): - self.dolibarr_url = self.dolibarr_url.rstrip('/') + '/index.php' - - def load_env(self): - """Load .env file manually - no python-dotenv needed.""" - env_file = '.env' - if os.path.exists(env_file): - with open(env_file, 'r', encoding='utf-8') as f: - for line in f: - line = line.strip() - if line and not line.startswith('#') and '=' in line: - key, value = line.split('=', 1) - os.environ[key.strip()] = value.strip() - - -class SimpleDolibarrAPIError(Exception): - """Simple API error exception.""" - def __init__(self, message: str, status_code: Optional[int] = None): - self.message = message - self.status_code = status_code - super().__init__(self.message) - - -class SimpleDolibarrClient: - """Ultra-simple Dolibarr client using only requests - no aiohttp, no compiled extensions.""" - - def __init__(self, config: SimpleConfig): - self.config = config - self.base_url = config.dolibarr_url.rstrip('/') - self.api_key = config.api_key - - # Create requests session with retries - self.session = requests.Session() - - # Add retry strategy - retry_strategy = Retry( - total=3, - backoff_factor=1, - status_forcelist=[429, 500, 502, 503, 504], - ) - - adapter = HTTPAdapter(max_retries=retry_strategy) - self.session.mount("http://", adapter) - self.session.mount("https://", adapter) - - # Set headers - self.session.headers.update({ - "DOLAPIKEY": self.api_key, - "Content-Type": "application/json", - "Accept": "application/json", - "User-Agent": "Dolibarr-MCP-Client/1.0" - }) - - self.logger = logging.getLogger(__name__) - - def _build_url(self, endpoint: str) -> str: - """Build full API URL.""" - endpoint = endpoint.lstrip('/') - - # Special handling for status endpoint - if endpoint == "status": - base = self.base_url.replace('/index.php', '') - return f"{base}/status" - - return f"{self.base_url}/{endpoint}" - - def _make_request( - self, - method: str, - endpoint: str, - params: Optional[Dict] = None, - data: Optional[Dict] = None - ) -> Dict[str, Any]: - """Make HTTP request to Dolibarr API.""" - url = self._build_url(endpoint) - - try: - self.logger.debug(f"Making {method} request to {url}") - - kwargs = { - "params": params or {}, - "timeout": 30 - } - - if data and method.upper() in ["POST", "PUT"]: - kwargs["json"] = data - - response = self.session.request(method, url, **kwargs) - - # Log response for debugging - self.logger.debug(f"Response status: {response.status_code}") - - # Handle error responses - if response.status_code >= 400: - try: - error_data = response.json() - if isinstance(error_data, dict) and "error" in error_data: - error_msg = str(error_data["error"]) - else: - error_msg = f"HTTP {response.status_code}: {response.reason}" - except: - error_msg = f"HTTP {response.status_code}: {response.reason}" - - raise SimpleDolibarrAPIError(error_msg, response.status_code) - - # Try to parse JSON response - try: - return response.json() - except: - # If not JSON, return as text - return {"raw_response": response.text} - - except requests.RequestException as e: - # For status endpoint, try alternative - if endpoint == "status": - try: - alt_url = f"{self.base_url.replace('/api/index.php', '')}/setup/modules" - alt_response = self.session.get(alt_url, timeout=10) - if alt_response.status_code == 200: - return { - "success": 1, - "dolibarr_version": "API Available", - "api_version": "1.0" - } - except: - pass - - raise SimpleDolibarrAPIError(f"HTTP request failed: {str(e)}") - except Exception as e: - raise SimpleDolibarrAPIError(f"Unexpected error: {str(e)}") - - # ============================================================================ - # SYSTEM ENDPOINTS - # ============================================================================ - - def get_status(self) -> Dict[str, Any]: - """Get API status and version information.""" - try: - return self._make_request("GET", "status") - except SimpleDolibarrAPIError: - # Try alternatives - try: - result = self._make_request("GET", "users?limit=1") - if result is not None: - return { - "success": 1, - "dolibarr_version": "API Working", - "api_version": "1.0" - } - except: - pass - - raise SimpleDolibarrAPIError("Cannot connect to Dolibarr API. Please check your configuration.") - - # ============================================================================ - # USER MANAGEMENT - # ============================================================================ - - def get_users(self, limit: int = 100, page: int = 1) -> List[Dict[str, Any]]: - """Get list of users.""" - params = {"limit": limit} - if page > 1: - params["page"] = page - - result = self._make_request("GET", "users", params=params) - return result if isinstance(result, list) else [] - - def get_user_by_id(self, user_id: int) -> Dict[str, Any]: - """Get specific user by ID.""" - return self._make_request("GET", f"users/{user_id}") - - def create_user(self, **kwargs) -> Dict[str, Any]: - """Create a new user.""" - return self._make_request("POST", "users", data=kwargs) - - def update_user(self, user_id: int, **kwargs) -> Dict[str, Any]: - """Update an existing user.""" - return self._make_request("PUT", f"users/{user_id}", data=kwargs) - - def delete_user(self, user_id: int) -> Dict[str, Any]: - """Delete a user.""" - return self._make_request("DELETE", f"users/{user_id}") - - # ============================================================================ - # CUSTOMER MANAGEMENT - # ============================================================================ - - def get_customers(self, limit: int = 100, page: int = 1) -> List[Dict[str, Any]]: - """Get list of customers/third parties.""" - params = {"limit": limit} - if page > 1: - params["page"] = page - - result = self._make_request("GET", "thirdparties", params=params) - return result if isinstance(result, list) else [] - - def get_customer_by_id(self, customer_id: int) -> Dict[str, Any]: - """Get specific customer by ID.""" - return self._make_request("GET", f"thirdparties/{customer_id}") - - def create_customer(self, name: str, **kwargs) -> Dict[str, Any]: - """Create a new customer.""" - data = { - "name": name, - "status": kwargs.get("status", 1), - "client": 1 if kwargs.get("type", 1) in [1, 3] else 0, - "fournisseur": 1 if kwargs.get("type", 1) in [2, 3] else 0, - "country_id": kwargs.get("country_id", 1), - } - - # Add optional fields - for field in ["email", "phone", "address", "town", "zip"]: - if field in kwargs: - data[field] = kwargs[field] - - return self._make_request("POST", "thirdparties", data=data) - - def update_customer(self, customer_id: int, **kwargs) -> Dict[str, Any]: - """Update an existing customer.""" - return self._make_request("PUT", f"thirdparties/{customer_id}", data=kwargs) - - def delete_customer(self, customer_id: int) -> Dict[str, Any]: - """Delete a customer.""" - return self._make_request("DELETE", f"thirdparties/{customer_id}") - - # ============================================================================ - # PRODUCT MANAGEMENT - # ============================================================================ - - def get_products(self, limit: int = 100) -> List[Dict[str, Any]]: - """Get list of products.""" - params = {"limit": limit} - result = self._make_request("GET", "products", params=params) - return result if isinstance(result, list) else [] - - def get_product_by_id(self, product_id: int) -> Dict[str, Any]: - """Get specific product by ID.""" - return self._make_request("GET", f"products/{product_id}") - - def create_product(self, label: str, price: float, **kwargs) -> Dict[str, Any]: - """Create a new product.""" - import time - - # Generate ref if not provided - ref = kwargs.get("ref", f"PROD-{int(time.time())}") - - data = { - "ref": ref, - "label": label, - "price": price, - "price_ttc": price, - } - - # Add optional fields - for field in ["description", "stock"]: - if field in kwargs: - data[field] = kwargs[field] - - return self._make_request("POST", "products", data=data) - - def update_product(self, product_id: int, **kwargs) -> Dict[str, Any]: - """Update an existing product.""" - return self._make_request("PUT", f"products/{product_id}", data=kwargs) - - def delete_product(self, product_id: int) -> Dict[str, Any]: - """Delete a product.""" - return self._make_request("DELETE", f"products/{product_id}") - - # ============================================================================ - # RAW API CALL - # ============================================================================ - - def raw_api(self, method: str, endpoint: str, params: Optional[Dict] = None, data: Optional[Dict] = None) -> Dict[str, Any]: - """Make raw API call to any Dolibarr endpoint.""" - return self._make_request(method, endpoint, params=params, data=data) - - def close(self): - """Close the session.""" - if self.session: - self.session.close() diff --git a/src/dolibarr_mcp/standalone_server.py b/src/dolibarr_mcp/standalone_server.py deleted file mode 100644 index 5667527..0000000 --- a/src/dolibarr_mcp/standalone_server.py +++ /dev/null @@ -1,469 +0,0 @@ -"""Standalone Dolibarr MCP Server - Windows Compatible (No pywin32 needed).""" - -import asyncio -import json -import sys -import os -import logging -from typing import Any, Dict, List, Optional, Union - -# Standard library only - no MCP package needed -from contextlib import asynccontextmanager - -# Our Dolibarr components -from .config import Config -from .dolibarr_client import DolibarrClient, DolibarrAPIError - -# Configure logging to stderr -logging.basicConfig( - level=logging.WARNING, - format="%(asctime)s [%(levelname)s] %(name)s: %(message)s", - handlers=[logging.StreamHandler(sys.stderr)] -) - -class StandaloneMCPServer: - """Standalone MCP Server implementation without pywin32 dependencies.""" - - def __init__(self, name: str = "dolibarr-mcp"): - self.name = name - self.logger = logging.getLogger(__name__) - - def get_tool_definitions(self) -> List[Dict[str, Any]]: - """Get all available tool definitions.""" - return [ - # System & Info - { - "name": "test_connection", - "description": "Test Dolibarr API connection", - "inputSchema": {"type": "object", "properties": {}, "additionalProperties": False} - }, - { - "name": "get_status", - "description": "Get Dolibarr system status and version information", - "inputSchema": {"type": "object", "properties": {}, "additionalProperties": False} - }, - - # User Management CRUD - { - "name": "get_users", - "description": "Get list of users from Dolibarr", - "inputSchema": { - "type": "object", - "properties": { - "limit": {"type": "integer", "description": "Maximum number of users to return (default: 100)", "default": 100}, - "page": {"type": "integer", "description": "Page number for pagination (default: 1)", "default": 1} - }, - "additionalProperties": False - } - }, - { - "name": "get_user_by_id", - "description": "Get specific user details by ID", - "inputSchema": { - "type": "object", - "properties": { - "user_id": {"type": "integer", "description": "User ID to retrieve"} - }, - "required": ["user_id"], - "additionalProperties": False - } - }, - { - "name": "create_user", - "description": "Create a new user", - "inputSchema": { - "type": "object", - "properties": { - "login": {"type": "string", "description": "User login"}, - "lastname": {"type": "string", "description": "Last name"}, - "firstname": {"type": "string", "description": "First name"}, - "email": {"type": "string", "description": "Email address"}, - "password": {"type": "string", "description": "Password"}, - "admin": {"type": "integer", "description": "Admin level (0=No, 1=Yes)", "default": 0} - }, - "required": ["login", "lastname"], - "additionalProperties": False - } - }, - { - "name": "update_user", - "description": "Update an existing user", - "inputSchema": { - "type": "object", - "properties": { - "user_id": {"type": "integer", "description": "User ID to update"}, - "login": {"type": "string", "description": "User login"}, - "lastname": {"type": "string", "description": "Last name"}, - "firstname": {"type": "string", "description": "First name"}, - "email": {"type": "string", "description": "Email address"}, - "admin": {"type": "integer", "description": "Admin level (0=No, 1=Yes)"} - }, - "required": ["user_id"], - "additionalProperties": False - } - }, - { - "name": "delete_user", - "description": "Delete a user", - "inputSchema": { - "type": "object", - "properties": { - "user_id": {"type": "integer", "description": "User ID to delete"} - }, - "required": ["user_id"], - "additionalProperties": False - } - }, - - # Customer/Third Party Management CRUD - { - "name": "get_customers", - "description": "Get list of customers/third parties from Dolibarr", - "inputSchema": { - "type": "object", - "properties": { - "limit": {"type": "integer", "description": "Maximum number of customers to return (default: 100)", "default": 100}, - "page": {"type": "integer", "description": "Page number for pagination (default: 1)", "default": 1} - }, - "additionalProperties": False - } - }, - { - "name": "get_customer_by_id", - "description": "Get specific customer details by ID", - "inputSchema": { - "type": "object", - "properties": { - "customer_id": {"type": "integer", "description": "Customer ID to retrieve"} - }, - "required": ["customer_id"], - "additionalProperties": False - } - }, - { - "name": "create_customer", - "description": "Create a new customer/third party", - "inputSchema": { - "type": "object", - "properties": { - "name": {"type": "string", "description": "Customer name"}, - "email": {"type": "string", "description": "Email address"}, - "phone": {"type": "string", "description": "Phone number"}, - "address": {"type": "string", "description": "Customer address"}, - "town": {"type": "string", "description": "City/Town"}, - "zip": {"type": "string", "description": "Postal code"}, - "country_id": {"type": "integer", "description": "Country ID (default: 1)", "default": 1}, - "type": {"type": "integer", "description": "Customer type (1=Customer, 2=Supplier, 3=Both)", "default": 1}, - "status": {"type": "integer", "description": "Status (1=Active, 0=Inactive)", "default": 1} - }, - "required": ["name"], - "additionalProperties": False - } - }, - { - "name": "update_customer", - "description": "Update an existing customer", - "inputSchema": { - "type": "object", - "properties": { - "customer_id": {"type": "integer", "description": "Customer ID to update"}, - "name": {"type": "string", "description": "Customer name"}, - "email": {"type": "string", "description": "Email address"}, - "phone": {"type": "string", "description": "Phone number"}, - "address": {"type": "string", "description": "Customer address"}, - "town": {"type": "string", "description": "City/Town"}, - "zip": {"type": "string", "description": "Postal code"}, - "status": {"type": "integer", "description": "Status (1=Active, 0=Inactive)"} - }, - "required": ["customer_id"], - "additionalProperties": False - } - }, - { - "name": "delete_customer", - "description": "Delete a customer", - "inputSchema": { - "type": "object", - "properties": { - "customer_id": {"type": "integer", "description": "Customer ID to delete"} - }, - "required": ["customer_id"], - "additionalProperties": False - } - }, - - # Product Management CRUD - { - "name": "get_products", - "description": "Get list of products from Dolibarr", - "inputSchema": { - "type": "object", - "properties": { - "limit": {"type": "integer", "description": "Maximum number of products to return (default: 100)", "default": 100} - }, - "additionalProperties": False - } - }, - { - "name": "get_product_by_id", - "description": "Get specific product details by ID", - "inputSchema": { - "type": "object", - "properties": { - "product_id": {"type": "integer", "description": "Product ID to retrieve"} - }, - "required": ["product_id"], - "additionalProperties": False - } - }, - { - "name": "create_product", - "description": "Create a new product", - "inputSchema": { - "type": "object", - "properties": { - "label": {"type": "string", "description": "Product name/label"}, - "price": {"type": "number", "description": "Product price"}, - "description": {"type": "string", "description": "Product description"}, - "stock": {"type": "integer", "description": "Initial stock quantity"} - }, - "required": ["label", "price"], - "additionalProperties": False - } - }, - { - "name": "update_product", - "description": "Update an existing product", - "inputSchema": { - "type": "object", - "properties": { - "product_id": {"type": "integer", "description": "Product ID to update"}, - "label": {"type": "string", "description": "Product name/label"}, - "price": {"type": "number", "description": "Product price"}, - "description": {"type": "string", "description": "Product description"} - }, - "required": ["product_id"], - "additionalProperties": False - } - }, - { - "name": "delete_product", - "description": "Delete a product", - "inputSchema": { - "type": "object", - "properties": { - "product_id": {"type": "integer", "description": "Product ID to delete"} - }, - "required": ["product_id"], - "additionalProperties": False - } - }, - - # Raw API Access - { - "name": "dolibarr_raw_api", - "description": "Make raw API call to any Dolibarr endpoint", - "inputSchema": { - "type": "object", - "properties": { - "method": {"type": "string", "description": "HTTP method", "enum": ["GET", "POST", "PUT", "DELETE"]}, - "endpoint": {"type": "string", "description": "API endpoint (e.g., /thirdparties, /invoices)"}, - "params": {"type": "object", "description": "Query parameters"}, - "data": {"type": "object", "description": "Request payload for POST/PUT requests"} - }, - "required": ["method", "endpoint"], - "additionalProperties": False - } - } - ] - - async def handle_tool_call(self, name: str, arguments: dict) -> Dict[str, Any]: - """Handle tool calls using the DolibarrClient.""" - - try: - # Initialize the config and client - config = Config() - - async with DolibarrClient(config) as client: - - # System & Info - if name == "test_connection": - result = await client.get_status() - if 'success' not in result: - result = {"status": "success", "message": "API connection working", "data": result} - - elif name == "get_status": - result = await client.get_status() - - # User Management - elif name == "get_users": - result = await client.get_users( - limit=arguments.get('limit', 100), - page=arguments.get('page', 1) - ) - - elif name == "get_user_by_id": - result = await client.get_user_by_id(arguments['user_id']) - - elif name == "create_user": - result = await client.create_user(**arguments) - - elif name == "update_user": - user_id = arguments.pop('user_id') - result = await client.update_user(user_id, **arguments) - - elif name == "delete_user": - result = await client.delete_user(arguments['user_id']) - - # Customer Management - elif name == "get_customers": - result = await client.get_customers( - limit=arguments.get('limit', 100), - page=arguments.get('page', 1) - ) - - elif name == "get_customer_by_id": - result = await client.get_customer_by_id(arguments['customer_id']) - - elif name == "create_customer": - result = await client.create_customer(**arguments) - - elif name == "update_customer": - customer_id = arguments.pop('customer_id') - result = await client.update_customer(customer_id, **arguments) - - elif name == "delete_customer": - result = await client.delete_customer(arguments['customer_id']) - - # Product Management - elif name == "get_products": - result = await client.get_products(limit=arguments.get('limit', 100)) - - elif name == "get_product_by_id": - result = await client.get_product_by_id(arguments['product_id']) - - elif name == "create_product": - result = await client.create_product(**arguments) - - elif name == "update_product": - product_id = arguments.pop('product_id') - result = await client.update_product(product_id, **arguments) - - elif name == "delete_product": - result = await client.delete_product(arguments['product_id']) - - # Raw API Access - elif name == "dolibarr_raw_api": - result = await client.dolibarr_raw_api(**arguments) - - else: - result = {"error": f"Unknown tool: {name}"} - - return {"success": True, "data": result} - - except DolibarrAPIError as e: - return {"error": f"Dolibarr API Error: {str(e)}", "type": "api_error"} - - except Exception as e: - self.logger.error(f"Tool execution error: {e}") - return {"error": f"Tool execution failed: {str(e)}", "type": "internal_error"} - - def format_response(self, content: Dict[str, Any]) -> str: - """Format response as JSON string.""" - return json.dumps(content, indent=2, ensure_ascii=False) - - async def run_interactive(self): - """Run server in interactive mode for testing.""" - print("πŸš€ Standalone Dolibarr MCP Server (Windows Compatible)", file=sys.stderr) - print("βœ… NO pywin32 dependencies required!", file=sys.stderr) - print("", file=sys.stderr) - - # Test API connection - try: - config = Config() - - if not config.dolibarr_url or config.dolibarr_url.startswith("https://your-dolibarr-instance"): - print("⚠️ DOLIBARR_URL not configured in .env file", file=sys.stderr) - print("πŸ“ Please edit .env with your Dolibarr credentials", file=sys.stderr) - elif not config.api_key or config.api_key in ["your_dolibarr_api_key_here", "placeholder_api_key"]: - print("⚠️ DOLIBARR_API_KEY not configured in .env file", file=sys.stderr) - print("πŸ“ Please edit .env with your Dolibarr API key", file=sys.stderr) - else: - print("πŸ§ͺ Testing Dolibarr API connection...", file=sys.stderr) - test_result = await self.handle_tool_call("test_connection", {}) - if test_result.get("success"): - print("βœ… Dolibarr API connection successful!", file=sys.stderr) - else: - print(f"⚠️ API test result: {test_result}", file=sys.stderr) - - except Exception as e: - print(f"⚠️ Configuration error: {e}", file=sys.stderr) - - print("", file=sys.stderr) - print("πŸ“‹ Available Tools:", file=sys.stderr) - tools = self.get_tool_definitions() - for tool in tools: - print(f" β€’ {tool['name']} - {tool['description']}", file=sys.stderr) - - print("", file=sys.stderr) - print("πŸ’‘ Interactive Testing Mode:", file=sys.stderr) - print(" Type 'list' to see all tools", file=sys.stderr) - print(" Type 'test ' to test a tool", file=sys.stderr) - print(" Type 'exit' to quit", file=sys.stderr) - print("", file=sys.stderr) - - while True: - try: - command = input("dolibarr-mcp> ").strip() - - if command == "exit": - break - elif command == "list": - print("Available tools:") - for tool in tools: - print(f" {tool['name']} - {tool['description']}") - elif command.startswith("test "): - tool_name = command[5:].strip() - if tool_name == "test_connection": - result = await self.handle_tool_call("test_connection", {}) - print(self.format_response(result)) - elif tool_name == "get_status": - result = await self.handle_tool_call("get_status", {}) - print(self.format_response(result)) - elif tool_name == "get_users": - result = await self.handle_tool_call("get_users", {"limit": 5}) - print(self.format_response(result)) - elif tool_name == "get_customers": - result = await self.handle_tool_call("get_customers", {"limit": 5}) - print(self.format_response(result)) - elif tool_name == "get_products": - result = await self.handle_tool_call("get_products", {"limit": 5}) - print(self.format_response(result)) - else: - print(f"Tool '{tool_name}' requires parameters. Available quick tests: test_connection, get_status, get_users, get_customers, get_products") - elif command: - print("Unknown command. Use 'list', 'test ', or 'exit'") - - except KeyboardInterrupt: - break - except Exception as e: - print(f"Error: {e}") - - print("\nπŸ‘‹ Goodbye!") - - -async def main(): - """Main entry point.""" - server = StandaloneMCPServer("dolibarr-mcp-standalone") - await server.run_interactive() - - -if __name__ == "__main__": - try: - asyncio.run(main()) - except KeyboardInterrupt: - print("\nπŸ‘‹ Server stopped by user", file=sys.stderr) - sys.exit(0) - except Exception as e: - print(f"❌ Server error: {e}", file=sys.stderr) - sys.exit(1) diff --git a/src/dolibarr_mcp/ultra_simple_server.py b/src/dolibarr_mcp/ultra_simple_server.py deleted file mode 100644 index b6a2a8f..0000000 --- a/src/dolibarr_mcp/ultra_simple_server.py +++ /dev/null @@ -1,607 +0,0 @@ -"""Ultra-simple Dolibarr server - COMPLETELY SELF-CONTAINED - Zero external dependencies issues.""" - -import json -import sys -import os -import logging -from typing import Any, Dict, List, Optional - -# EVERYTHING is self-contained in this single file to avoid import issues - -# ============================================================================ -# SELF-CONTAINED CONFIGURATION CLASS -# ============================================================================ - -class UltraSimpleConfig: - """Ultra-simple configuration - completely self-contained.""" - - def __init__(self): - # Load .env manually - self.load_env() - - self.dolibarr_url = os.getenv("DOLIBARR_URL", "") - self.api_key = os.getenv("DOLIBARR_API_KEY", "") - self.log_level = os.getenv("LOG_LEVEL", "INFO") - - # Validate and fix URL - if not self.dolibarr_url or "your-dolibarr-instance" in self.dolibarr_url: - print("⚠️ DOLIBARR_URL not configured in .env file", file=sys.stderr) - self.dolibarr_url = "https://your-dolibarr-instance.com/api/index.php" - - if not self.api_key or "your_dolibarr_api_key" in self.api_key: - print("⚠️ DOLIBARR_API_KEY not configured in .env file", file=sys.stderr) - self.api_key = "placeholder_api_key" - - # Normalize URL - remove trailing slashes and ensure proper format - self.dolibarr_url = self.dolibarr_url.rstrip('/') - - # If URL doesn't contain /api/index.php, try to add it - if '/api/index.php' not in self.dolibarr_url: - if '/api' in self.dolibarr_url: - # Has /api but not /index.php - if not self.dolibarr_url.endswith('/index.php'): - self.dolibarr_url = self.dolibarr_url + '/index.php' - else: - # No /api at all - add full path - self.dolibarr_url = self.dolibarr_url + '/api/index.php' - - # Debug output - print(f"πŸ”§ Configuration loaded:", file=sys.stderr) - print(f" URL: {self.dolibarr_url}", file=sys.stderr) - print(f" API Key: {'*' * min(len(self.api_key), 10)}... (length: {len(self.api_key)})", file=sys.stderr) - - def load_env(self): - """Load .env file manually - no python-dotenv needed.""" - env_file = '.env' - if os.path.exists(env_file): - print(f"πŸ“„ Loading environment from {env_file}", file=sys.stderr) - with open(env_file, 'r', encoding='utf-8') as f: - for line in f: - line = line.strip() - if line and not line.startswith('#') and '=' in line: - key, value = line.split('=', 1) - key = key.strip() - value = value.strip() - # Remove quotes if present - if value.startswith('"') and value.endswith('"'): - value = value[1:-1] - elif value.startswith("'") and value.endswith("'"): - value = value[1:-1] - os.environ[key] = value - print(f" Loaded: {key} = {value[:30]}..." if len(value) > 30 else f" Loaded: {key}", file=sys.stderr) - else: - print(f"⚠️ No .env file found in current directory", file=sys.stderr) - -# ============================================================================ -# SELF-CONTAINED API CLIENT -# ============================================================================ - -class UltraSimpleAPIError(Exception): - """Simple API error exception.""" - def __init__(self, message: str, status_code: Optional[int] = None): - self.message = message - self.status_code = status_code - super().__init__(self.message) - -class UltraSimpleAPIClient: - """Ultra-simple Dolibarr client - completely self-contained.""" - - def __init__(self, config: UltraSimpleConfig): - self.config = config - self.base_url = config.dolibarr_url.rstrip('/') - self.api_key = config.api_key - self.logger = logging.getLogger(__name__) - - # We'll use requests when needed, imported inside methods - - def _build_url(self, endpoint: str) -> str: - """Build full API URL.""" - endpoint = endpoint.lstrip('/') - - # For status endpoint, try different variations - if endpoint == "status": - # First try the standard status endpoint - return f"{self.base_url}/status" - - # For other endpoints, just append to base URL - return f"{self.base_url}/{endpoint}" - - def _make_request( - self, - method: str, - endpoint: str, - params: Optional[Dict] = None, - data: Optional[Dict] = None - ) -> Dict[str, Any]: - """Make HTTP request to Dolibarr API.""" - # Import requests here to avoid import issues - try: - import requests - except ImportError: - raise UltraSimpleAPIError("requests library not available. Please run setup_ultra.bat") - - url = self._build_url(endpoint) - - try: - self.logger.debug(f"Making {method} request to {url}") - print(f"πŸ” API Request: {method} {url}", file=sys.stderr) - - headers = { - "DOLAPIKEY": self.api_key, - "Content-Type": "application/json", - "Accept": "application/json", - "User-Agent": "Dolibarr-MCP-Ultra/1.0" - } - - # Debug headers (without full API key) - print(f" Headers: DOLAPIKEY={self.api_key[:10]}...", file=sys.stderr) - - kwargs = { - "params": params or {}, - "timeout": 30, - "headers": headers, - "verify": True # Enable SSL verification - } - - if data and method.upper() in ["POST", "PUT"]: - kwargs["json"] = data - - response = requests.request(method, url, **kwargs) - - print(f" Response Status: {response.status_code}", file=sys.stderr) - - # Handle error responses - if response.status_code >= 400: - print(f" Response Content: {response.text[:500]}", file=sys.stderr) - try: - error_data = response.json() - if isinstance(error_data, dict): - if "error" in error_data: - error_msg = error_data["error"].get("message", str(error_data["error"])) - elif "errors" in error_data: - error_msg = str(error_data["errors"]) - else: - error_msg = f"HTTP {response.status_code}: {response.reason}" - else: - error_msg = f"HTTP {response.status_code}: {response.text[:200]}" - except: - error_msg = f"HTTP {response.status_code}: {response.reason}" - - raise UltraSimpleAPIError(error_msg, response.status_code) - - # Try to parse JSON response - try: - result = response.json() - print(f" βœ… Response OK: {type(result)}", file=sys.stderr) - return result - except: - print(f" ⚠️ Non-JSON response: {response.text[:100]}", file=sys.stderr) - return {"raw_response": response.text} - - except requests.RequestException as e: - print(f" ❌ Request failed: {str(e)}", file=sys.stderr) - - # For connection errors, provide more helpful messages - if "SSLError" in str(e.__class__.__name__): - raise UltraSimpleAPIError(f"SSL Error: {str(e)}. Try checking if the URL is correct and the SSL certificate is valid.") - elif "ConnectionError" in str(e.__class__.__name__): - raise UltraSimpleAPIError(f"Connection Error: Cannot reach {url}. Please check your URL and network connection.") - elif "Timeout" in str(e.__class__.__name__): - raise UltraSimpleAPIError(f"Timeout: The server took too long to respond. Please check if the URL is correct.") - - raise UltraSimpleAPIError(f"HTTP request failed: {str(e)}") - except Exception as e: - print(f" ❌ Unexpected error: {str(e)}", file=sys.stderr) - raise UltraSimpleAPIError(f"Unexpected error: {str(e)}") - - # API Methods - def get_status(self) -> Dict[str, Any]: - """Get API status - try multiple approaches.""" - # First try the login endpoint which is commonly available - try: - print("πŸ” Attempting to verify API access via login endpoint...", file=sys.stderr) - login_data = { - "login": "test", - "password": "test", - "reset": 0 - } - # Don't actually login, just check if the endpoint responds - self._make_request("POST", "login", data=login_data) - except UltraSimpleAPIError as e: - # If we get a 403 or 401, it means the API is working but credentials are wrong - if e.status_code in [401, 403]: - print(" βœ… API is reachable (authentication endpoint responded)", file=sys.stderr) - return { - "success": 1, - "dolibarr_version": "API Working", - "api_version": "1.0", - "message": "API is reachable and responding" - } - except: - pass - - # Try to get users as a status check - try: - print("πŸ” Attempting to verify API access via users endpoint...", file=sys.stderr) - result = self._make_request("GET", "users", params={"limit": 1}) - if result is not None: - return { - "success": 1, - "dolibarr_version": "API Working", - "api_version": "1.0", - "users_accessible": True - } - except: - pass - - # Try the status endpoint - try: - print("πŸ” Attempting standard status endpoint...", file=sys.stderr) - return self._make_request("GET", "status") - except: - pass - - # Last resort - try to get any response - try: - print("πŸ” Testing basic API connectivity...", file=sys.stderr) - # Try a simple GET to the base API URL - import requests - response = requests.get( - self.base_url, - headers={"DOLAPIKEY": self.api_key}, - timeout=10, - verify=True - ) - if response.status_code < 500: - return { - "success": 1, - "dolibarr_version": "API Endpoint Exists", - "api_version": "Unknown", - "status_code": response.status_code - } - except: - pass - - raise UltraSimpleAPIError("Cannot connect to Dolibarr API. Please check your configuration.") - - def get_users(self, limit: int = 100, page: int = 1) -> List[Dict[str, Any]]: - """Get list of users.""" - params = {"limit": limit} - if page > 1: - params["page"] = page - result = self._make_request("GET", "users", params=params) - return result if isinstance(result, list) else [] - - def get_user_by_id(self, user_id: int) -> Dict[str, Any]: - """Get specific user by ID.""" - return self._make_request("GET", f"users/{user_id}") - - def create_user(self, **kwargs) -> Dict[str, Any]: - """Create a new user.""" - return self._make_request("POST", "users", data=kwargs) - - def update_user(self, user_id: int, **kwargs) -> Dict[str, Any]: - """Update an existing user.""" - return self._make_request("PUT", f"users/{user_id}", data=kwargs) - - def delete_user(self, user_id: int) -> Dict[str, Any]: - """Delete a user.""" - return self._make_request("DELETE", f"users/{user_id}") - - def get_customers(self, limit: int = 100, page: int = 1) -> List[Dict[str, Any]]: - """Get list of customers.""" - params = {"limit": limit} - if page > 1: - params["page"] = page - result = self._make_request("GET", "thirdparties", params=params) - return result if isinstance(result, list) else [] - - def get_customer_by_id(self, customer_id: int) -> Dict[str, Any]: - """Get specific customer by ID.""" - return self._make_request("GET", f"thirdparties/{customer_id}") - - def create_customer(self, name: str, **kwargs) -> Dict[str, Any]: - """Create a new customer.""" - data = { - "name": name, - "status": kwargs.get("status", 1), - "client": 1 if kwargs.get("type", 1) in [1, 3] else 0, - "fournisseur": 1 if kwargs.get("type", 1) in [2, 3] else 0, - "country_id": kwargs.get("country_id", 1), - } - for field in ["email", "phone", "address", "town", "zip"]: - if field in kwargs: - data[field] = kwargs[field] - return self._make_request("POST", "thirdparties", data=data) - - def update_customer(self, customer_id: int, **kwargs) -> Dict[str, Any]: - """Update an existing customer.""" - return self._make_request("PUT", f"thirdparties/{customer_id}", data=kwargs) - - def delete_customer(self, customer_id: int) -> Dict[str, Any]: - """Delete a customer.""" - return self._make_request("DELETE", f"thirdparties/{customer_id}") - - def get_products(self, limit: int = 100) -> List[Dict[str, Any]]: - """Get list of products.""" - params = {"limit": limit} - result = self._make_request("GET", "products", params=params) - return result if isinstance(result, list) else [] - - def get_product_by_id(self, product_id: int) -> Dict[str, Any]: - """Get specific product by ID.""" - return self._make_request("GET", f"products/{product_id}") - - def create_product(self, label: str, price: float, **kwargs) -> Dict[str, Any]: - """Create a new product.""" - import time - ref = kwargs.get("ref", f"PROD-{int(time.time())}") - data = { - "ref": ref, - "label": label, - "price": price, - "price_ttc": price, - } - for field in ["description", "stock"]: - if field in kwargs: - data[field] = kwargs[field] - return self._make_request("POST", "products", data=data) - - def update_product(self, product_id: int, **kwargs) -> Dict[str, Any]: - """Update an existing product.""" - return self._make_request("PUT", f"products/{product_id}", data=kwargs) - - def delete_product(self, product_id: int) -> Dict[str, Any]: - """Delete a product.""" - return self._make_request("DELETE", f"products/{product_id}") - - def raw_api(self, method: str, endpoint: str, params: Optional[Dict] = None, data: Optional[Dict] = None) -> Dict[str, Any]: - """Make raw API call.""" - return self._make_request(method, endpoint, params=params, data=data) - -# ============================================================================ -# ULTRA-SIMPLE SERVER -# ============================================================================ - -class UltraSimpleServer: - """Ultra-simple server - completely self-contained.""" - - def __init__(self, name: str = "dolibarr-mcp-ultra"): - self.name = name - self.logger = logging.getLogger(__name__) - self.client = None - - def init_client(self): - """Initialize the Dolibarr client.""" - if not self.client: - config = UltraSimpleConfig() - self.client = UltraSimpleAPIClient(config) - - def get_available_tools(self) -> List[str]: - """Get list of available tool names.""" - return [ - "test_connection", "get_status", "get_users", "get_user_by_id", - "create_user", "update_user", "delete_user", "get_customers", - "get_customer_by_id", "create_customer", "update_customer", - "delete_customer", "get_products", "get_product_by_id", - "create_product", "update_product", "delete_product", "raw_api" - ] - - def handle_tool_call(self, tool_name: str, arguments: dict) -> Dict[str, Any]: - """Handle tool calls.""" - try: - self.init_client() - - if tool_name == "test_connection": - result = self.client.get_status() - if 'success' not in result: - result = {"status": "success", "message": "API connection working", "data": result} - return {"success": True, "data": result} - elif tool_name == "get_status": - result = self.client.get_status() - return {"success": True, "data": result} - elif tool_name == "get_users": - result = self.client.get_users( - limit=arguments.get('limit', 100), - page=arguments.get('page', 1) - ) - return {"success": True, "data": result} - elif tool_name == "get_user_by_id": - result = self.client.get_user_by_id(arguments['user_id']) - return {"success": True, "data": result} - elif tool_name == "create_user": - result = self.client.create_user(**arguments) - return {"success": True, "data": result} - elif tool_name == "update_user": - user_id = arguments.pop('user_id') - result = self.client.update_user(user_id, **arguments) - return {"success": True, "data": result} - elif tool_name == "delete_user": - result = self.client.delete_user(arguments['user_id']) - return {"success": True, "data": result} - elif tool_name == "get_customers": - result = self.client.get_customers( - limit=arguments.get('limit', 100), - page=arguments.get('page', 1) - ) - return {"success": True, "data": result} - elif tool_name == "get_customer_by_id": - result = self.client.get_customer_by_id(arguments['customer_id']) - return {"success": True, "data": result} - elif tool_name == "create_customer": - result = self.client.create_customer(**arguments) - return {"success": True, "data": result} - elif tool_name == "update_customer": - customer_id = arguments.pop('customer_id') - result = self.client.update_customer(customer_id, **arguments) - return {"success": True, "data": result} - elif tool_name == "delete_customer": - result = self.client.delete_customer(arguments['customer_id']) - return {"success": True, "data": result} - elif tool_name == "get_products": - result = self.client.get_products(limit=arguments.get('limit', 100)) - return {"success": True, "data": result} - elif tool_name == "get_product_by_id": - result = self.client.get_product_by_id(arguments['product_id']) - return {"success": True, "data": result} - elif tool_name == "create_product": - result = self.client.create_product(**arguments) - return {"success": True, "data": result} - elif tool_name == "update_product": - product_id = arguments.pop('product_id') - result = self.client.update_product(product_id, **arguments) - return {"success": True, "data": result} - elif tool_name == "delete_product": - result = self.client.delete_product(arguments['product_id']) - return {"success": True, "data": result} - elif tool_name == "raw_api": - result = self.client.raw_api(**arguments) - return {"success": True, "data": result} - else: - return {"error": f"Unknown tool: {tool_name}", "type": "unknown_tool"} - - except UltraSimpleAPIError as e: - return {"error": f"Dolibarr API Error: {str(e)}", "type": "api_error"} - except Exception as e: - self.logger.error(f"Tool execution error: {e}") - return {"error": f"Tool execution failed: {str(e)}", "type": "internal_error"} - - def format_response(self, content: Dict[str, Any]) -> str: - """Format response as JSON string.""" - return json.dumps(content, indent=2, ensure_ascii=False) - - def run_interactive(self): - """Run server in interactive mode.""" - print("=" * 70, file=sys.stderr) - print("Dolibarr MCP ULTRA Server", file=sys.stderr) - print("=" * 70, file=sys.stderr) - print("Maximum Windows Compatibility Mode", file=sys.stderr) - print("ZERO compiled extensions (.pyd files)", file=sys.stderr) - print("Activating ultra virtual environment...", file=sys.stderr) - print("πŸš€ Starting ULTRA-COMPATIBLE Dolibarr MCP Server...", file=sys.stderr) - print("β”œβ”€ Pure Python implementation", file=sys.stderr) - print("β”œβ”€ ZERO compiled extensions", file=sys.stderr) - print("β”œβ”€ Standard library + requests only", file=sys.stderr) - print("└─ Works on ANY Windows version", file=sys.stderr) - print("", file=sys.stderr) - print("Available features:", file=sys.stderr) - print(" β€’ All CRUD operations for Dolibarr", file=sys.stderr) - print(" β€’ Interactive testing console", file=sys.stderr) - print(" β€’ Professional error handling", file=sys.stderr) - print(" β€’ Zero permission issues", file=sys.stderr) - print("", file=sys.stderr) - print("πŸš€ Ultra-Simple Dolibarr MCP Server (Maximum Windows Compatibility)", file=sys.stderr) - print("βœ… ZERO compiled extensions - NO .pyd files!", file=sys.stderr) - print("βœ… Completely self-contained - no import issues!", file=sys.stderr) - print("", file=sys.stderr) - - # Test configuration - try: - config = UltraSimpleConfig() - - if "your-dolibarr-instance" in config.dolibarr_url: - print("⚠️ DOLIBARR_URL not configured in .env file", file=sys.stderr) - print("πŸ“ Please edit .env with your Dolibarr credentials", file=sys.stderr) - elif "placeholder_api_key" in config.api_key: - print("⚠️ DOLIBARR_API_KEY not configured in .env file", file=sys.stderr) - print("πŸ“ Please edit .env with your Dolibarr API key", file=sys.stderr) - else: - print("πŸ§ͺ Testing Dolibarr API connection...", file=sys.stderr) - test_result = self.handle_tool_call("test_connection", {}) - if test_result.get("success"): - print("βœ… Dolibarr API connection successful!", file=sys.stderr) - else: - print(f"⚠️ API test result: {test_result}", file=sys.stderr) - - except Exception as e: - print(f"⚠️ Configuration error: {e}", file=sys.stderr) - - print("", file=sys.stderr) - print("πŸ“‹ Available Tools:", file=sys.stderr) - tools = self.get_available_tools() - for i, tool in enumerate(tools, 1): - print(f" {i:2}. {tool}", file=sys.stderr) - - print("", file=sys.stderr) - print("πŸ’‘ Interactive Testing Mode:", file=sys.stderr) - print(" Type 'list' to see all tools", file=sys.stderr) - print(" Type 'test ' to test a tool", file=sys.stderr) - print(" Type 'help' for more commands", file=sys.stderr) - print(" Type 'exit' to quit", file=sys.stderr) - print("", file=sys.stderr) - - while True: - try: - command = input("dolibarr-ultra> ").strip() - - if command == "exit": - break - elif command == "list": - print("Available tools:") - for i, tool in enumerate(tools, 1): - print(f" {i:2}. {tool}") - elif command == "help": - print("Commands:") - print(" list - Show all available tools") - print(" test - Test a specific tool") - print(" config - Show current configuration") - print(" exit - Quit the server") - print("") - print("Quick tests available:") - print(" test test_connection - Test API connection") - print(" test get_status - Get Dolibarr status") - print(" test get_users - Get first 5 users") - print(" test get_customers - Get first 5 customers") - print(" test get_products - Get first 5 products") - elif command == "config": - config = UltraSimpleConfig() - print(f"Configuration:") - print(f" URL: {config.dolibarr_url}") - print(f" API Key: {'*' * min(len(config.api_key), 10)}...") - print(f" Log Level: {config.log_level}") - elif command.startswith("test "): - tool_name = command[5:].strip() - - if tool_name == "test_connection": - result = self.handle_tool_call("test_connection", {}) - elif tool_name == "get_status": - result = self.handle_tool_call("get_status", {}) - elif tool_name == "get_users": - result = self.handle_tool_call("get_users", {"limit": 5}) - elif tool_name == "get_customers": - result = self.handle_tool_call("get_customers", {"limit": 5}) - elif tool_name == "get_products": - result = self.handle_tool_call("get_products", {"limit": 5}) - else: - if tool_name in tools: - print(f"Tool '{tool_name}' requires parameters. Try one of the quick tests:") - print(" test_connection, get_status, get_users, get_customers, get_products") - else: - print(f"Unknown tool: {tool_name}") - continue - - print(self.format_response(result)) - elif command: - print("Unknown command. Type 'help' for available commands.") - - except KeyboardInterrupt: - break - except Exception as e: - print(f"Error: {e}") - - print("\nπŸ‘‹ Goodbye!") - -def main(): - """Main entry point.""" - server = UltraSimpleServer("dolibarr-mcp-ultra") - server.run_interactive() - -if __name__ == "__main__": - try: - main() - except KeyboardInterrupt: - print("\nπŸ‘‹ Server stopped by user", file=sys.stderr) - sys.exit(0) - except Exception as e: - print(f"❌ Server error: {e}", file=sys.stderr) - sys.exit(1) diff --git a/start_server.bat b/start_server.bat deleted file mode 100644 index 31699fc..0000000 --- a/start_server.bat +++ /dev/null @@ -1,50 +0,0 @@ -@echo off -echo πŸš€ Quick Start Dolibarr MCP Server (No Installation Required) -echo. - -REM Set UTF-8 encoding for Python -set PYTHONIOENCODING=utf-8 -set PYTHONPATH=%cd%\src - -REM Check if .env exists -if not exist .env ( - echo πŸ“ Creating .env file... - copy .env.example .env 2>nul || ( - echo # Dolibarr MCP Configuration > .env - echo DOLIBARR_URL=https://your-dolibarr-instance.com/api/index.php >> .env - echo DOLIBARR_API_KEY=your_api_key_here >> .env - echo LOG_LEVEL=INFO >> .env - ) - echo ⚠️ Please edit .env file with your Dolibarr credentials! - echo Use: notepad .env - echo. - pause - exit /b 1 -) - -echo πŸ”Œ Testing connection... -venv_dolibarr\Scripts\python.exe test_connection.py -if errorlevel 1 ( - echo. - echo ❌ Connection test failed! - echo πŸ’‘ Please check your .env configuration: - echo - DOLIBARR_URL should be: https://your-instance.com/api/index.php - echo - DOLIBARR_API_KEY should be your valid API key - echo. - echo Edit with: notepad .env - echo. - pause - exit /b 1 -) - -echo. -echo 🎯 Starting Dolibarr MCP Server... -echo πŸ“‘ Server will run until you press Ctrl+C -echo. - -REM Start the MCP server directly from source -venv_dolibarr\Scripts\python.exe -m dolibarr_mcp.dolibarr_mcp_server - -echo. -echo πŸ›‘ Server stopped -pause diff --git a/test_api_connection.py b/test_api_connection.py deleted file mode 100644 index 8d5ed19..0000000 --- a/test_api_connection.py +++ /dev/null @@ -1,252 +0,0 @@ -#!/usr/bin/env python3 -"""Test script to verify Dolibarr API connection.""" - -import os -import sys -import asyncio -import aiohttp -import json -from dotenv import load_dotenv - -# Load environment variables -load_dotenv() - - -async def test_dolibarr_connection(): - """Test Dolibarr API connection with different endpoints.""" - - # Get configuration from environment - base_url = os.getenv("DOLIBARR_URL", "") - api_key = os.getenv("DOLIBARR_API_KEY", "") - - print("πŸ”§ Configuration:") - print(f" Base URL: {base_url}") - print(f" API Key: {'*' * 10 if api_key else 'NOT SET'}") - print() - - if not base_url or not api_key: - print("❌ Missing configuration!") - print(" Please set DOLIBARR_URL and DOLIBARR_API_KEY in .env file") - return False - - # Clean base URL - remove trailing slash if present - base_url = base_url.rstrip('/') - - # Test different endpoints - endpoints_to_test = [ - "status", # API status - "users", # Users list - "thirdparties", # Customers/Suppliers - "products", # Products - "invoices", # Invoices - "orders", # Orders - "contacts", # Contacts - ] - - # Headers for Dolibarr API - headers = { - "DOLAPIKEY": api_key, - "Content-Type": "application/json", - "Accept": "application/json" - } - - print("πŸ§ͺ Testing Dolibarr API endpoints:") - print("=" * 50) - - working_endpoints = [] - - async with aiohttp.ClientSession() as session: - for endpoint in endpoints_to_test: - url = f"{base_url}/{endpoint}" - - try: - print(f"\nπŸ“ Testing: {endpoint}") - print(f" URL: {url}") - - async with session.get(url, headers=headers, timeout=10, ssl=False) as response: - status = response.status - text = await response.text() - - print(f" Status: {status}") - - if status == 200: - print(f" βœ… Success!") - working_endpoints.append(endpoint) - try: - data = json.loads(text) - if isinstance(data, dict): - print(f" Response keys: {list(data.keys())[:5]}") - elif isinstance(data, list): - print(f" Response: List with {len(data)} items") - if len(data) > 0 and isinstance(data[0], dict): - print(f" First item keys: {list(data[0].keys())[:5]}") - except: - print(f" Response preview: {text[:100]}...") - elif status == 401: - print(f" ❌ Authentication failed - check API key") - elif status == 403: - print(f" ❌ Access forbidden - check permissions") - elif status == 404: - print(f" ❌ Endpoint not found") - elif status == 501: - print(f" ⚠️ API module not found - endpoint may not be available") - if text: - print(f" Response: {text[:200]}...") - else: - print(f" ⚠️ Unexpected status: {status}") - if text: - print(f" Response: {text[:200]}...") - - except aiohttp.ClientError as e: - print(f" ❌ Connection error: {type(e).__name__}: {e}") - except Exception as e: - print(f" ❌ Unexpected error: {type(e).__name__}: {e}") - - print("\n" + "=" * 50) - - if working_endpoints: - print("\n✨ Working endpoints:") - for endpoint in working_endpoints: - print(f" - {endpoint}") - else: - print("\n⚠️ No endpoints are working!") - - return len(working_endpoints) > 0 - - -async def test_swagger_endpoint(): - """Test Swagger/Explorer endpoint specifically.""" - - base_url = os.getenv("DOLIBARR_URL", "").rstrip('/') - api_key = os.getenv("DOLIBARR_API_KEY", "") - - if not base_url or not api_key: - return - - print("\nπŸ” Testing Swagger/Explorer endpoints:") - print("=" * 50) - - # Swagger endpoints to test - swagger_endpoints = [ - "explorer", - "explorer/index.html", - f"explorer/swagger.json?DOLAPIKEY={api_key}", - ] - - headers = { - "DOLAPIKEY": api_key, - "Accept": "application/json, text/html, */*" - } - - async with aiohttp.ClientSession() as session: - for endpoint in swagger_endpoints: - url = f"{base_url}/{endpoint}" - - try: - print(f"\nTesting: {url}") - - async with session.get(url, headers=headers, timeout=10, ssl=False) as response: - status = response.status - content_type = response.headers.get('Content-Type', '') - - print(f" Status: {status}") - print(f" Content-Type: {content_type}") - - if status == 200: - print(f" βœ… Found!") - - # If it's the swagger.json, try to parse it - if 'swagger.json' in endpoint: - text = await response.text() - try: - swagger_data = json.loads(text) - if 'paths' in swagger_data: - print(f" Available API endpoints:") - for path in list(swagger_data['paths'].keys())[:10]: - print(f" - {path}") - if len(swagger_data['paths']) > 10: - print(f" ... and {len(swagger_data['paths']) - 10} more") - except: - print(f" Could not parse Swagger JSON") - else: - text = await response.text() - print(f" Response preview: {text[:100]}...") - - except Exception as e: - print(f" Error: {type(e).__name__}: {e}") - - -async def test_login_endpoint(): - """Test the login endpoint to get a session token.""" - - base_url = os.getenv("DOLIBARR_URL", "").rstrip('/') - api_key = os.getenv("DOLIBARR_API_KEY", "") - - if not base_url or not api_key: - return - - print("\nπŸ” Testing Login endpoint:") - print("=" * 50) - - # Test with API key in header (standard method) - url = f"{base_url}/login" - - headers = { - "DOLAPIKEY": api_key, - "Content-Type": "application/json", - "Accept": "application/json" - } - - async with aiohttp.ClientSession() as session: - try: - print(f"Testing: {url}") - print(f"Method: GET with DOLAPIKEY header") - - async with session.get(url, headers=headers, timeout=10, ssl=False) as response: - status = response.status - text = await response.text() - - print(f" Status: {status}") - - if status == 200: - print(f" βœ… Authentication successful!") - try: - data = json.loads(text) - print(f" Response: {json.dumps(data, indent=2)}") - except: - print(f" Response: {text}") - else: - print(f" Response: {text[:200]}...") - - except Exception as e: - print(f" Error: {type(e).__name__}: {e}") - - -if __name__ == "__main__": - print("πŸš€ Dolibarr API Connection Test") - print("================================\n") - - try: - # Run tests - success = asyncio.run(test_dolibarr_connection()) - asyncio.run(test_swagger_endpoint()) - asyncio.run(test_login_endpoint()) - - print("\n" + "=" * 50) - print("\nπŸ“ Summary:") - if success: - print(" βœ… API connection is working!") - print(" You can proceed with MCP server implementation.") - else: - print(" ⚠️ API connection issues detected.") - print(" Please check:") - print(" 1. Dolibarr Web Services API REST module is enabled") - print(" 2. API key is correct and has proper permissions") - print(" 3. URL format is: https://domain.com/api/index.php/") - - except KeyboardInterrupt: - print("\n\nπŸ‘‹ Test cancelled by user") - sys.exit(0) - except Exception as e: - print(f"\n❌ Test failed: {e}") - sys.exit(1) diff --git a/test_api_debug.py b/test_api_debug.py deleted file mode 100644 index 4552b3f..0000000 --- a/test_api_debug.py +++ /dev/null @@ -1,134 +0,0 @@ -#!/usr/bin/env python3 -""" -Dolibarr API Connection Tester -Tests the connection to your Dolibarr instance -""" - -import os -import sys -import json -import requests -from typing import Dict, Any - -def load_env(): - """Load .env file manually.""" - env_file = '.env' - if os.path.exists(env_file): - print(f"πŸ“„ Loading environment from {env_file}") - with open(env_file, 'r', encoding='utf-8') as f: - for line in f: - line = line.strip() - if line and not line.startswith('#') and '=' in line: - key, value = line.split('=', 1) - key = key.strip() - value = value.strip() - # Remove quotes if present - if value.startswith('"') and value.endswith('"'): - value = value[1:-1] - elif value.startswith("'") and value.endswith("'"): - value = value[1:-1] - os.environ[key] = value - else: - print(f"⚠️ No .env file found") - -def test_connection(): - """Test Dolibarr API connection.""" - load_env() - - url = os.getenv("DOLIBARR_URL", "").rstrip('/') - api_key = os.getenv("DOLIBARR_API_KEY", "") - - print("=" * 70) - print("πŸ” DOLIBARR API CONNECTION TEST") - print("=" * 70) - print(f"URL: {url}") - print(f"API Key: {'*' * min(len(api_key), 10)}... (length: {len(api_key)})") - print("-" * 70) - - if not url or not api_key: - print("❌ Missing configuration in .env file!") - return False - - # Test different endpoints - test_endpoints = [ - ("users?limit=1", "Users endpoint"), - ("status", "Status endpoint"), - ("thirdparties?limit=1", "Third parties endpoint"), - ("products?limit=1", "Products endpoint"), - ] - - headers = { - "DOLAPIKEY": api_key, - "Accept": "application/json", - "User-Agent": "Dolibarr-Test/1.0" - } - - success_count = 0 - - for endpoint, name in test_endpoints: - test_url = f"{url}/{endpoint}" - print(f"\nπŸ“ Testing {name}:") - print(f" URL: {test_url}") - - try: - response = requests.get(test_url, headers=headers, timeout=10, verify=True) - print(f" Status: {response.status_code}") - - if response.status_code == 200: - print(f" βœ… SUCCESS - {name} is accessible") - success_count += 1 - - # Try to parse response - try: - data = response.json() - if isinstance(data, list): - print(f" Data: List with {len(data)} items") - elif isinstance(data, dict): - print(f" Data: Dictionary with keys: {list(data.keys())[:5]}") - except: - print(f" Data: Non-JSON response") - - elif response.status_code == 401: - print(f" ❌ UNAUTHORIZED - Check your API key") - elif response.status_code == 403: - print(f" ❌ FORBIDDEN - API key may not have permissions for {name}") - elif response.status_code == 404: - print(f" ⚠️ NOT FOUND - {name} might not be available") - else: - print(f" ❌ ERROR - HTTP {response.status_code}") - - except requests.exceptions.SSLError as e: - print(f" ❌ SSL ERROR - Certificate issue: {str(e)[:100]}") - except requests.exceptions.ConnectionError as e: - print(f" ❌ CONNECTION ERROR - Cannot reach server: {str(e)[:100]}") - except requests.exceptions.Timeout: - print(f" ❌ TIMEOUT - Server took too long to respond") - except Exception as e: - print(f" ❌ ERROR - {str(e)[:100]}") - - print("\n" + "=" * 70) - print(f"πŸ“Š Test Results: {success_count}/{len(test_endpoints)} endpoints working") - - if success_count > 0: - print("βœ… API connection is working!") - return True - else: - print("❌ API connection failed - please check your configuration") - print("\nπŸ”§ Troubleshooting tips:") - print("1. Check if the URL is correct (should end with /api/index.php)") - print("2. Verify your API key is valid") - print("3. Ensure the API module is enabled in Dolibarr") - print("4. Check if your user has API permissions") - print("5. Try accessing the URL in your browser") - return False - -if __name__ == "__main__": - try: - success = test_connection() - sys.exit(0 if success else 1) - except KeyboardInterrupt: - print("\nπŸ‘‹ Test cancelled") - sys.exit(1) - except Exception as e: - print(f"❌ Test error: {e}") - sys.exit(1) diff --git a/test_connection.py b/test_connection.py deleted file mode 100644 index 4ed167c..0000000 --- a/test_connection.py +++ /dev/null @@ -1,62 +0,0 @@ -#!/usr/bin/env python3 -"""Quick test script for Dolibarr MCP connection""" - -import asyncio -import sys -import os - -# Add src to path for development -sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'src')) - -from dolibarr_mcp.config import Config -from dolibarr_mcp.dolibarr_client import DolibarrClient, DolibarrAPIError - - -async def test_connection(): - """Test the Dolibarr API connection""" - print("πŸ§ͺ Testing Dolibarr MCP Connection...") - print() - - try: - # Load config - config = Config() - print(f"πŸ“ URL: {config.dolibarr_url}") - print(f"πŸ”‘ API Key: {'*' * (len(config.api_key) - 4) + config.api_key[-4:] if config.api_key else 'NOT SET'}") - print() - - # Test connection - async with DolibarrClient(config) as client: - print("πŸ“‘ Testing API connection...") - status = await client.get_status() - - print("βœ… Connection successful!") - print(f"πŸ“Š Status: {status}") - print() - - # Test a simple query - print("πŸ‘₯ Testing customer query...") - customers = await client.get_customers(limit=1) - print(f"βœ… Found {len(customers)} customers") - - print() - print("🎯 Dolibarr MCP server is ready!") - print("πŸš€ Run the MCP server with: python -m dolibarr_mcp.dolibarr_mcp_server") - - except DolibarrAPIError as e: - print(f"❌ Dolibarr API Error: {e.message}") - if e.status_code: - print(f"πŸ“Š Status Code: {e.status_code}") - if e.response_data: - print(f"πŸ“„ Response: {e.response_data}") - return False - - except Exception as e: - print(f"❌ Unexpected error: {e}") - return False - - return True - - -if __name__ == "__main__": - success = asyncio.run(test_connection()) - sys.exit(0 if success else 1) diff --git a/test_dolibarr_mcp.py b/test_dolibarr_mcp.py deleted file mode 100644 index 53b17c6..0000000 --- a/test_dolibarr_mcp.py +++ /dev/null @@ -1,375 +0,0 @@ -#!/usr/bin/env python3 -"""Comprehensive test suite for Dolibarr MCP Server.""" - -import asyncio -import json -import os -import sys -from datetime import datetime -from dotenv import load_dotenv - -# Add parent directory to path -sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) - -from src.dolibarr_mcp.config import Config -from src.dolibarr_mcp.dolibarr_client import DolibarrClient, DolibarrAPIError - -# Load environment variables -load_dotenv() - - -class TestColors: - """ANSI color codes for terminal output.""" - HEADER = '\033[95m' - OKBLUE = '\033[94m' - OKCYAN = '\033[96m' - OKGREEN = '\033[92m' - WARNING = '\033[93m' - FAIL = '\033[91m' - ENDC = '\033[0m' - BOLD = '\033[1m' - UNDERLINE = '\033[4m' - - -def print_header(text): - """Print a formatted header.""" - print(f"\n{TestColors.HEADER}{TestColors.BOLD}{'=' * 60}{TestColors.ENDC}") - print(f"{TestColors.HEADER}{TestColors.BOLD}{text}{TestColors.ENDC}") - print(f"{TestColors.HEADER}{TestColors.BOLD}{'=' * 60}{TestColors.ENDC}") - - -def print_test(name, result, details=""): - """Print test result.""" - if result: - status = f"{TestColors.OKGREEN}βœ… PASS{TestColors.ENDC}" - else: - status = f"{TestColors.FAIL}❌ FAIL{TestColors.ENDC}" - - print(f" {status} - {name}") - if details: - print(f" {TestColors.OKCYAN}{details}{TestColors.ENDC}") - - -async def test_connection(client): - """Test basic API connection.""" - print_header("πŸ”Œ CONNECTION TEST") - - try: - result = await client.get_status() - print_test("API Connection", True, f"Dolibarr v{result.get('dolibarr_version', 'unknown')}") - return True - except Exception as e: - print_test("API Connection", False, str(e)) - return False - - -async def test_customers(client): - """Test customer CRUD operations.""" - print_header("πŸ‘₯ CUSTOMER MANAGEMENT") - - test_customer_id = None - - try: - # List customers - customers = await client.get_customers(limit=5) - print_test("List Customers", True, f"Found {len(customers)} customers") - - # Create customer - new_customer = await client.create_customer( - name=f"Test Customer {datetime.now().strftime('%Y%m%d%H%M%S')}", - email="test@example.com", - phone="+1234567890", - address="123 Test Street", - town="Test City", - zip="12345" - ) - test_customer_id = new_customer if isinstance(new_customer, int) else new_customer.get('id') - print_test("Create Customer", True, f"Created ID: {test_customer_id}") - - # Get customer by ID - if test_customer_id: - customer = await client.get_customer_by_id(test_customer_id) - print_test("Get Customer by ID", True, f"Retrieved: {customer.get('name', 'Unknown')}") - - # Update customer - updated = await client.update_customer( - test_customer_id, - email="updated@example.com" - ) - print_test("Update Customer", True, "Email updated") - - # Delete customer - deleted = await client.delete_customer(test_customer_id) - print_test("Delete Customer", True, f"Deleted ID: {test_customer_id}") - - return True - - except Exception as e: - print_test("Customer Operations", False, str(e)) - # Try to clean up - if test_customer_id: - try: - await client.delete_customer(test_customer_id) - except: - pass - return False - - -async def test_products(client): - """Test product CRUD operations.""" - print_header("πŸ“¦ PRODUCT MANAGEMENT") - - test_product_id = None - - try: - # List products - products = await client.get_products(limit=5) - print_test("List Products", True, f"Found {len(products)} products") - - # Create product - new_product = await client.create_product( - label=f"Test Product {datetime.now().strftime('%Y%m%d%H%M%S')}", - price=99.99, - description="Test product description", - stock=100 - ) - test_product_id = new_product if isinstance(new_product, int) else new_product.get('id') - print_test("Create Product", True, f"Created ID: {test_product_id}") - - # Get product by ID - if test_product_id: - product = await client.get_product_by_id(test_product_id) - print_test("Get Product by ID", True, f"Retrieved: {product.get('label', 'Unknown')}") - - # Update product - updated = await client.update_product( - test_product_id, - price=149.99 - ) - print_test("Update Product", True, "Price updated") - - # Delete product - deleted = await client.delete_product(test_product_id) - print_test("Delete Product", True, f"Deleted ID: {test_product_id}") - - return True - - except Exception as e: - print_test("Product Operations", False, str(e)) - # Try to clean up - if test_product_id: - try: - await client.delete_product(test_product_id) - except: - pass - return False - - -async def test_users(client): - """Test user operations.""" - print_header("πŸ‘€ USER MANAGEMENT") - - try: - # List users - users = await client.get_users(limit=5) - print_test("List Users", True, f"Found {len(users)} users") - - if users: - # Get first user details - first_user = users[0] - user_id = first_user.get('id') - if user_id: - user = await client.get_user_by_id(user_id) - print_test("Get User by ID", True, f"Retrieved: {user.get('login', 'Unknown')}") - - return True - - except Exception as e: - print_test("User Operations", False, str(e)) - return False - - -async def test_invoices(client): - """Test invoice operations.""" - print_header("πŸ“„ INVOICE MANAGEMENT") - - try: - # List invoices - invoices = await client.get_invoices(limit=5) - print_test("List Invoices", True, f"Found {len(invoices)} invoices") - - if invoices: - # Get first invoice details - first_invoice = invoices[0] - invoice_id = first_invoice.get('id') - if invoice_id: - invoice = await client.get_invoice_by_id(invoice_id) - print_test("Get Invoice by ID", True, f"Retrieved Invoice #{invoice.get('ref', 'Unknown')}") - - return True - - except Exception as e: - print_test("Invoice Operations", False, str(e)) - return False - - -async def test_orders(client): - """Test order operations.""" - print_header("πŸ“‹ ORDER MANAGEMENT") - - try: - # List orders - orders = await client.get_orders(limit=5) - print_test("List Orders", True, f"Found {len(orders)} orders") - - if orders: - # Get first order details - first_order = orders[0] - order_id = first_order.get('id') - if order_id: - order = await client.get_order_by_id(order_id) - print_test("Get Order by ID", True, f"Retrieved Order #{order.get('ref', 'Unknown')}") - - return True - - except Exception as e: - print_test("Order Operations", False, str(e)) - return False - - -async def test_contacts(client): - """Test contact operations.""" - print_header("πŸ“‡ CONTACT MANAGEMENT") - - test_contact_id = None - - try: - # List contacts - contacts = await client.get_contacts(limit=5) - print_test("List Contacts", True, f"Found {len(contacts)} contacts") - - # Create contact - new_contact = await client.create_contact( - firstname="Test", - lastname=f"Contact {datetime.now().strftime('%Y%m%d%H%M%S')}", - email="testcontact@example.com" - ) - test_contact_id = new_contact if isinstance(new_contact, int) else new_contact.get('id') - print_test("Create Contact", True, f"Created ID: {test_contact_id}") - - # Get contact by ID - if test_contact_id: - contact = await client.get_contact_by_id(test_contact_id) - name = f"{contact.get('firstname', '')} {contact.get('lastname', '')}" - print_test("Get Contact by ID", True, f"Retrieved: {name.strip()}") - - # Update contact - updated = await client.update_contact( - test_contact_id, - email="updatedcontact@example.com" - ) - print_test("Update Contact", True, "Email updated") - - # Delete contact - deleted = await client.delete_contact(test_contact_id) - print_test("Delete Contact", True, f"Deleted ID: {test_contact_id}") - - return True - - except Exception as e: - print_test("Contact Operations", False, str(e)) - # Try to clean up - if test_contact_id: - try: - await client.delete_contact(test_contact_id) - except: - pass - return False - - -async def test_raw_api(client): - """Test raw API access.""" - print_header("πŸ”§ RAW API ACCESS") - - try: - # Test raw GET request - result = await client.dolibarr_raw_api( - method="GET", - endpoint="/setup/modules", - params={"limit": 5} - ) - print_test("Raw GET Request", True, f"Retrieved {len(result) if isinstance(result, list) else 'data'}") - - return True - - except Exception as e: - print_test("Raw API Access", False, str(e)) - return False - - -async def main(): - """Run all tests.""" - print(f"{TestColors.BOLD}{TestColors.HEADER}") - print("╔══════════════════════════════════════════════════════════╗") - print("β•‘ πŸš€ DOLIBARR MCP SERVER - COMPREHENSIVE TEST SUITE β•‘") - print("β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•") - print(f"{TestColors.ENDC}") - - # Initialize configuration and client - try: - config = Config() - print(f"\n{TestColors.OKGREEN}βœ… Configuration loaded successfully{TestColors.ENDC}") - print(f" URL: {config.dolibarr_url}") - print(f" API Key: {'*' * 20}{config.api_key[-4:]}") - except Exception as e: - print(f"\n{TestColors.FAIL}❌ Configuration failed: {e}{TestColors.ENDC}") - return - - # Run tests - async with DolibarrClient(config) as client: - results = [] - - # Run each test suite - results.append(("Connection", await test_connection(client))) - results.append(("Users", await test_users(client))) - results.append(("Customers", await test_customers(client))) - results.append(("Products", await test_products(client))) - results.append(("Invoices", await test_invoices(client))) - results.append(("Orders", await test_orders(client))) - results.append(("Contacts", await test_contacts(client))) - results.append(("Raw API", await test_raw_api(client))) - - # Print summary - print_header("πŸ“Š TEST SUMMARY") - - total_tests = len(results) - passed_tests = sum(1 for _, passed in results if passed) - failed_tests = total_tests - passed_tests - - print(f"\n Total Tests: {total_tests}") - print(f" {TestColors.OKGREEN}Passed: {passed_tests}{TestColors.ENDC}") - print(f" {TestColors.FAIL}Failed: {failed_tests}{TestColors.ENDC}") - - if failed_tests == 0: - print(f"\n{TestColors.OKGREEN}{TestColors.BOLD}πŸŽ‰ ALL TESTS PASSED! πŸŽ‰{TestColors.ENDC}") - print(f"\n{TestColors.OKCYAN}The Dolibarr MCP server is fully operational!{TestColors.ENDC}") - print(f"{TestColors.OKCYAN}You can now use it with Claude Desktop.{TestColors.ENDC}") - else: - print(f"\n{TestColors.WARNING}⚠️ Some tests failed. Check the details above.{TestColors.ENDC}") - print(f"{TestColors.WARNING}The server may still work for successful operations.{TestColors.ENDC}") - - print(f"\n{TestColors.BOLD}Next steps:{TestColors.ENDC}") - print("1. Ensure Claude Desktop configuration includes this server") - print("2. Restart Claude Desktop to reload MCP servers") - print("3. Test by asking Claude to interact with your Dolibarr system") - - -if __name__ == "__main__": - try: - asyncio.run(main()) - except KeyboardInterrupt: - print(f"\n\n{TestColors.WARNING}Test suite interrupted by user{TestColors.ENDC}") - sys.exit(1) - except Exception as e: - print(f"\n{TestColors.FAIL}Unexpected error: {e}{TestColors.ENDC}") - sys.exit(1) diff --git a/test_install.py b/test_install.py deleted file mode 100644 index 880dae2..0000000 --- a/test_install.py +++ /dev/null @@ -1,93 +0,0 @@ -#!/usr/bin/env python3 -"""Quick test to verify Dolibarr MCP installation.""" - -def test_installation(): - """Test if the Dolibarr MCP installation is working.""" - print("\n" + "="*50) - print("Testing Dolibarr MCP Installation") - print("="*50 + "\n") - - # Test 1: Import main modules - print("1. Testing module imports...") - try: - from dolibarr_mcp.config import Config - print(" [OK] dolibarr_mcp.config") - except ImportError as e: - print(f" [FAIL] dolibarr_mcp.config: {e}") - return False - - try: - from dolibarr_mcp.dolibarr_client import DolibarrClient - print(" [OK] dolibarr_mcp.dolibarr_client") - except ImportError as e: - print(f" [FAIL] dolibarr_mcp.dolibarr_client: {e}") - return False - - try: - from dolibarr_mcp.dolibarr_mcp_server import DolibarrMCPServer - print(" [OK] dolibarr_mcp.dolibarr_mcp_server") - except ImportError as e: - print(f" [FAIL] dolibarr_mcp.dolibarr_mcp_server: {e}") - return False - - # Test 2: Check dependencies - print("\n2. Testing dependencies...") - try: - import mcp - print(" [OK] mcp") - except ImportError: - print(" [FAIL] mcp - Run: pip install mcp") - return False - - try: - import aiohttp - print(" [OK] aiohttp") - except ImportError: - print(" [FAIL] aiohttp - Run: pip install aiohttp") - return False - - try: - import pydantic - print(" [OK] pydantic") - except ImportError: - print(" [FAIL] pydantic - Run: pip install pydantic") - return False - - try: - import dotenv - print(" [OK] python-dotenv") - except ImportError: - print(" [FAIL] python-dotenv - Run: pip install python-dotenv") - return False - - # Test 3: Check configuration - print("\n3. Testing configuration...") - import os - if os.path.exists(".env"): - print(" [OK] .env file exists") - try: - config = Config() - print(" [OK] Config loaded successfully") - if config.dolibarr_url == "https://your-dolibarr-instance.com/api/index.php": - print(" [!] WARNING: Using default URL - Please update .env file") - if config.dolibarr_api_key == "your_api_key_here": - print(" [!] WARNING: Using default API key - Please update .env file") - except Exception as e: - print(f" [FAIL] Config loading error: {e}") - else: - print(" [!] .env file not found - Create one from .env.example") - - print("\n" + "="*50) - print("[SUCCESS] Installation test passed!") - print("="*50) - print("\nNext steps:") - print("1. Edit .env file with your Dolibarr credentials") - print("2. Run: python -m dolibarr_mcp") - print("3. Or test connection: python test_connection.py") - - return True - -if __name__ == "__main__": - import sys - if not test_installation(): - sys.exit(1) diff --git a/test_standalone.py b/test_standalone.py deleted file mode 100644 index 55f53b1..0000000 --- a/test_standalone.py +++ /dev/null @@ -1,95 +0,0 @@ -"""Quick test script for the standalone Dolibarr MCP server.""" - -import asyncio -import sys -import os - -# Add src to path for imports -sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'src')) - -try: - from src.dolibarr_mcp.standalone_server import StandaloneMCPServer - from src.dolibarr_mcp.config import Config - print("βœ… All imports successful - standalone server ready!") -except ImportError as e: - print(f"❌ Import error: {e}") - print("πŸ’‘ Please run: setup_standalone.bat first") - sys.exit(1) - -async def test_standalone(): - """Test the standalone server.""" - print("πŸ§ͺ Testing Standalone Dolibarr MCP Server...") - print("") - - try: - # Test config loading - config = Config() - print(f"βœ… Configuration loaded") - print(f" URL: {config.dolibarr_url}") - print(f" API Key: {'*' * min(len(config.api_key), 10)}...") - print("") - - # Test server creation - server = StandaloneMCPServer("test-server") - print("βœ… Server instance created") - - # Test tool definitions - tools = server.get_tool_definitions() - print(f"βœ… Tools loaded: {len(tools)} available") - print("") - - # List some tools - print("πŸ“‹ Available Tools (first 10):") - for i, tool in enumerate(tools[:10]): - print(f" {i+1:2}. {tool['name']} - {tool['description']}") - if len(tools) > 10: - print(f" ... and {len(tools) - 10} more") - print("") - - # Test a simple tool call (without actual API) - print("πŸ§ͺ Testing tool call structure...") - try: - # This will fail with API error, but tests the structure - result = await server.handle_tool_call("test_connection", {}) - if "error" in result and "api_error" in result.get("type", ""): - print("βœ… Tool call structure working (API connection expected to fail)") - else: - print("βœ… Tool call successful!") - print(f" Result: {result}") - except Exception as e: - print(f"βœ… Tool call structure working (got expected error: {type(e).__name__})") - - print("") - print("πŸŽ‰ Standalone server test completed successfully!") - print("") - print("πŸš€ Ready to run:") - print(" python -m src.dolibarr_mcp.standalone_server") - print(" OR") - print(" .\\run_standalone.bat") - - except Exception as e: - print(f"❌ Test failed: {e}") - return False - - return True - -if __name__ == "__main__": - print("=" * 50) - print("Dolibarr MCP Standalone Server Test") - print("=" * 50) - print("") - - success = asyncio.run(test_standalone()) - - if success: - print("") - print("=" * 50) - print("βœ… ALL TESTS PASSED - SERVER READY!") - print("=" * 50) - sys.exit(0) - else: - print("") - print("=" * 50) - print("❌ TESTS FAILED - CHECK SETUP") - print("=" * 50) - sys.exit(1) diff --git a/test_ultra.py b/test_ultra.py deleted file mode 100644 index 672af00..0000000 --- a/test_ultra.py +++ /dev/null @@ -1,127 +0,0 @@ -"""Test the ultra-simple Dolibarr MCP server - zero compiled dependencies.""" - -import sys -import os - -# Test imports without try/catch to see exactly what fails -print("Testing ultra-simple imports...") -print(f"Python version: {sys.version}") -print("") - -# Test 1: Standard library -print("βœ… Standard library imports:") -import json -import logging -import os -import sys -from typing import Dict, List, Optional, Any -print(" json, logging, os, sys, typing - OK") - -# Test 2: Basic packages -print("βœ… Basic package imports:") -try: - import requests - print(f" requests {requests.__version__} - OK") -except ImportError as e: - print(f" ❌ requests failed: {e}") - print(" Please run: setup_ultra.bat") - sys.exit(1) - -try: - import dotenv - print(f" python-dotenv - OK") -except ImportError: - print(" ⚠️ python-dotenv not available, using manual .env loading") - -try: - import click - print(f" click {click.__version__} - OK") -except ImportError: - print(" ⚠️ click not available, basic CLI will work") - -print("") - -# Test 3: Our ultra-simple modules -print("βœ… Testing ultra-simple modules:") - -# Add src to path -sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'src')) - -try: - from src.dolibarr_mcp.simple_client import SimpleConfig, SimpleDolibarrClient, SimpleDolibarrAPIError - print(" simple_client module - OK") -except ImportError as e: - print(f" ❌ simple_client failed: {e}") - sys.exit(1) - -try: - from src.dolibarr_mcp.ultra_simple_server import UltraSimpleServer - print(" ultra_simple_server module - OK") -except ImportError as e: - print(f" ❌ ultra_simple_server failed: {e}") - sys.exit(1) - -print("") - -# Test 4: Configuration -print("βœ… Testing configuration:") -try: - config = SimpleConfig() - print(f" URL: {config.dolibarr_url}") - print(f" API Key: {'*' * min(len(config.api_key), 10)}...") - print(" Configuration loading - OK") -except Exception as e: - print(f" ⚠️ Configuration error: {e}") - -print("") - -# Test 5: Server instantiation -print("βœ… Testing server:") -try: - server = UltraSimpleServer("test-ultra") - tools = server.get_available_tools() - print(f" Server created - OK") - print(f" Available tools: {len(tools)}") - print(f" First few tools: {', '.join(tools[:5])}") -except Exception as e: - print(f" ❌ Server creation failed: {e}") - sys.exit(1) - -print("") - -# Test 6: Mock tool call (without actual API) -print("βœ… Testing tool call structure:") -try: - # This will likely fail with an API error, but tests the structure - result = server.handle_tool_call("test_connection", {}) - if "error" in result: - print(" Tool call structure - OK (API error expected)") - print(f" Error type: {result.get('type', 'unknown')}") - else: - print(" Tool call structure - OK") - print(f" Result: {result}") -except Exception as e: - print(f" ❌ Tool call structure failed: {e}") - sys.exit(1) - -print("") -print("=" * 60) -print("πŸŽ‰ ALL TESTS PASSED!") -print("=" * 60) -print("") -print("βœ… Ultra-simple server is ready to run") -print("βœ… Zero compiled extensions - maximum Windows compatibility") -print("βœ… Only pure Python libraries used") -print("") -print("πŸš€ To run the server:") -print(" .\\run_ultra.bat") -print("") -print("πŸ§ͺ To run interactively:") -print(" python -m src.dolibarr_mcp.ultra_simple_server") -print("") - -# Cleanup -if hasattr(server, 'client') and server.client: - server.client.close() - -print("Test completed successfully!") diff --git a/test_ultra_direct.py b/test_ultra_direct.py deleted file mode 100644 index f509475..0000000 --- a/test_ultra_direct.py +++ /dev/null @@ -1,97 +0,0 @@ -"""Direct test for ultra simple server - completely self-contained.""" - -import sys -import os - -print("=" * 50) -print("Ultra-Simple Dolibarr MCP Server Test") -print("=" * 50) -print(f"Python version: {sys.version}") -print("") - -# Test 1: Standard library -print("βœ… Testing standard library imports:") -try: - import json - import logging - import os - import sys - from typing import Dict, List, Optional, Any - print(" json, logging, os, sys, typing - OK") -except ImportError as e: - print(f" ❌ Standard library import failed: {e}") - sys.exit(1) - -# Test 2: Requests library -print("βœ… Testing requests library:") -try: - import requests - print(f" requests {requests.__version__} - OK") -except ImportError as e: - print(f" ❌ requests import failed: {e}") - print(" Please run: setup_ultra.bat") - sys.exit(1) - -# Test 3: Direct import of ultra server -print("βœ… Testing ultra server import:") -try: - # Direct import without going through package - sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'src', 'dolibarr_mcp')) - import ultra_simple_server - print(" ultra_simple_server module - OK") -except ImportError as e: - print(f" ❌ ultra_simple_server import failed: {e}") - sys.exit(1) - -# Test 4: Server instantiation -print("βœ… Testing server creation:") -try: - server = ultra_simple_server.UltraSimpleServer("test-ultra") - tools = server.get_available_tools() - print(f" Server created - OK") - print(f" Available tools: {len(tools)}") - print(f" Sample tools: {', '.join(tools[:5])}") -except Exception as e: - print(f" ❌ Server creation failed: {e}") - sys.exit(1) - -# Test 5: Configuration loading -print("βœ… Testing configuration:") -try: - config = ultra_simple_server.UltraSimpleConfig() - print(f" Configuration loaded - OK") - print(f" URL: {config.dolibarr_url}") - print(f" API Key: {'*' * min(len(config.api_key), 10)}...") -except Exception as e: - print(f" ⚠️ Configuration error: {e}") - -# Test 6: Tool call structure -print("βœ… Testing tool call structure:") -try: - result = server.handle_tool_call("test_connection", {}) - if "error" in result: - print(" Tool call structure - OK (API error expected)") - print(f" Error type: {result.get('type', 'unknown')}") - else: - print(" Tool call structure - OK") - print(f" Response format: {type(result).__name__}") -except Exception as e: - print(f" ❌ Tool call failed: {e}") - sys.exit(1) - -print("") -print("=" * 50) -print("πŸŽ‰ ALL TESTS PASSED!") -print("=" * 50) -print("") -print("βœ… Ultra-simple server is ready") -print("βœ… Zero compiled extensions") -print("βœ… Complete self-contained implementation") -print("") -print("πŸš€ To run the server:") -print(" .\\run_ultra.bat") -print("") -print("πŸ§ͺ To run directly:") -print(" python src\\dolibarr_mcp\\ultra_simple_server.py") -print("") -print("Test completed successfully!") diff --git a/validate_claude_config.bat b/validate_claude_config.bat deleted file mode 100644 index d52909f..0000000 --- a/validate_claude_config.bat +++ /dev/null @@ -1,126 +0,0 @@ -@echo off -echo ====================================== -echo Dolibarr MCP - Configuration Validator -echo ====================================== -echo. - -cd /d "C:\Users\gino\GitHub\dolibarr-mcp" - -echo Checking virtual environment... -if exist "venv_dolibarr\Scripts\python.exe" ( - echo βœ“ Virtual environment found -) else ( - echo βœ— Virtual environment not found - creating... - python -m venv venv_dolibarr -) - -echo. -echo Activating virtual environment... -call venv_dolibarr\Scripts\activate - -echo. -echo Installing package in development mode... -pip install -e . >nul 2>&1 - -echo. -echo Installing dependencies... -pip install requests python-dotenv mcp aiohttp pydantic click typing-extensions >nul 2>&1 - -echo. -echo Testing module import paths... -echo ---------------------------------------- - -echo Test 1: Direct module import -python -c "import sys; sys.path.insert(0, 'src'); from dolibarr_mcp import __version__; print(f' βœ“ Direct import works - version: {__version__}')" 2>nul -if %errorlevel% neq 0 ( - echo βœ— Direct import failed -) - -echo. -echo Test 2: Module execution with -m flag -python -m dolibarr_mcp.dolibarr_mcp_server --help >nul 2>&1 -if %errorlevel% neq 0 ( - echo βœ— Module execution failed - echo Attempting fix... - cd src - python -m dolibarr_mcp.dolibarr_mcp_server --help >nul 2>&1 - if %errorlevel% neq 0 ( - echo βœ— Still failing - checking Python path... - python -c "import sys; print('Python paths:'); [print(f' {p}') for p in sys.path[:5]]" - ) else ( - echo βœ“ Works when run from src directory - ) - cd .. -) else ( - echo βœ“ Module execution works -) - -echo. -echo Test 3: Environment Variables -echo ---------------------------------------- -python -c "import os; url=os.getenv('DOLIBARR_URL') or os.getenv('DOLIBARR_BASE_URL'); key=os.getenv('DOLIBARR_API_KEY'); print(f' URL: {url[:30] if url else \"NOT SET\"}...'); print(f' KEY: {\"*\" * 10 if key else \"NOT SET\"}')" - -echo. -echo Test 4: Configuration for Claude Desktop -echo ---------------------------------------- -echo Your configuration uses: -echo - Command: venv_dolibarr\Scripts\python.exe -echo - Args: -m dolibarr_mcp.dolibarr_mcp_server -echo - CWD: C:\Users\gino\GitHub\dolibarr-mcp -echo - ENV: DOLIBARR_BASE_URL and DOLIBARR_API_KEY - -echo. -echo Validating this configuration... -set DOLIBARR_BASE_URL=https://db.ginos.cloud/api/index.php/ -set DOLIBARR_API_KEY=7cxAAO835BF7bXy6DsQ2j2a7nT6ectGY - -python -c "from src.dolibarr_mcp.config import Config; c=Config(); print(f' βœ“ Config loads successfully'); print(f' URL processed as: {c.dolibarr_url}')" 2>nul -if %errorlevel% neq 0 ( - echo βœ— Config loading failed -) - -echo. -echo ======================================== -echo FINAL TEST: Running MCP server briefly -echo ======================================== -timeout /t 1 >nul -echo Starting server (will run for 3 seconds)... -start /B python -m dolibarr_mcp.dolibarr_mcp_server 2>server_test.log -timeout /t 3 >nul -taskkill /F /IM python.exe >nul 2>&1 - -if exist server_test.log ( - echo. - echo Server output: - type server_test.log | findstr /C:"Starting Professional Dolibarr MCP server" >nul - if %errorlevel% equ 0 ( - echo βœ“ Server starts successfully! - ) else ( - echo βœ— Server failed to start properly - echo Check server_test.log for details - ) - del server_test.log -) - -echo. -echo ======================================== -echo Configuration Status: READY -echo ======================================== -echo. -echo Your Claude Desktop config should work with: -echo. -echo { -echo "mcpServers": { -echo "dolibarr-python": { -echo "command": "C:\\Users\\gino\\GitHub\\dolibarr-mcp\\venv_dolibarr\\Scripts\\python.exe", -echo "args": ["-m", "dolibarr_mcp.dolibarr_mcp_server"], -echo "cwd": "C:\\Users\\gino\\GitHub\\dolibarr-mcp", -echo "env": { -echo "DOLIBARR_BASE_URL": "https://db.ginos.cloud/api/index.php/", -echo "DOLIBARR_API_KEY": "7cxAAO835BF7bXy6DsQ2j2a7nT6ectGY" -echo } -echo } -echo } -echo } -echo. -pause