diff --git a/README.md b/README.md index 5f3bbe9..c231068 100644 --- a/README.md +++ b/README.md @@ -1,54 +1,31 @@ # Dolibarr MCP Server -Dolibarr MCP is a focused Model Context Protocol (MCP) server that exposes the -most useful Dolibarr ERP/CRM operations to AI copilots. The repository mirrors -the clean layout used by [`prestashop-mcp`](https://github.com/latinogino/prestashop-mcp): -a single production-ready server implementation, an async HTTP client, and a -self-contained documentation bundle. +Dolibarr MCP delivers a Model Context Protocol (MCP) interface for the Dolibarr +ERP/CRM. The project mirrors the layout of [`prestashop-mcp`](https://github.com/latinogino/prestashop-mcp): +an async API client, a production-ready STDIO server, and focused +documentation. Claude Desktop and other MCP-aware tools can use the server to +manage customers, products, invoices, orders, and contacts in a Dolibarr +instance. - -This MCP server enables complete management of your Dolibarr ERP/CRM through AI -tools such as Claude Desktop. With specialised tools you can manage customers, -products, invoices, orders, contacts, and system administration tasks from a -single MCP endpoint. - -This MCP server enables complete management of your Dolibarr ERP/CRM through AI -tools such as Claude Desktop. With specialised tools you can manage customers, -products, invoices, orders, contacts, and system administration tasks from a -single MCP endpoint. - -## πŸ“š Documentation - -All user and contributor guides live in [`docs/`](docs/README.md): - -- [Quickstart](docs/quickstart.md) – installation and first run instructions for Linux, macOS, and Windows -- [Configuration](docs/configuration.md) – environment variables and secrets consumed by the server -- [Development](docs/development.md) – test workflow, linting, and Docker helpers -- [API Reference](docs/api-reference.md) – Dolibarr REST resources and corresponding MCP tools +Consult the bundled [documentation index](docs/README.md) for deep dives into +configuration, API coverage, and contributor workflows. ## ✨ Features -- **πŸ’Ό Complete ERP/CRM Management** – Tools for customers, products, invoices, orders, and contacts -- **⚑ Async/Await Architecture** – Modern, high-performance HTTP client and server -- **πŸ›‘οΈ Comprehensive Error Handling** – Robust validation and structured responses -- **🐳 Docker Support** – Optional container workflow for local experimentation and deployment -- **πŸ”§ Production-Ready** – Automated tests and configuration management powered by `pydantic-settings` +- **Full ERP coverage** – CRUD tools for users, customers, products, invoices, + orders, contacts, and raw API access. +- **Async/await HTTP client** – Efficient Dolibarr API wrapper with structured + error handling. +- **Ready for MCP hosts** – STDIO transport compatible with Claude Desktop out + of the box. +- **Shared workflow with prestashop-mcp** – Identical developer ergonomics and + documentation structure across both repositories. -## πŸ›  Available tools +## βœ… Prerequisites -`dolibarr_mcp_server` registers MCP tools that map to common Dolibarr workflows. -See the [API reference](docs/api-reference.md) for full details. - -- **System** – `test_connection`, `get_status`, and `dolibarr_raw_api` -- **Users** – CRUD helpers for Dolibarr users -- **Customers / Third Parties** – CRUD helpers for partners -- **Products** – CRUD helpers for product catalogue entries -- **Invoices** – CRUD helpers for invoices -- **Orders** – CRUD helpers for customer orders -- **Contacts** – CRUD helpers for contact records - -The async implementation in [`dolibarr_client.py`](src/dolibarr_mcp/dolibarr_client.py) -handles authentication, pagination, and error handling for all endpoints. +- Python 3.8 or newer. +- Access to a Dolibarr installation with the REST API enabled and a personal API + token. ## πŸ“¦ Installation @@ -57,33 +34,38 @@ handles authentication, pagination, and error handling for all endpoints. ```bash git clone https://github.com/latinogino/dolibarr-mcp.git cd dolibarr-mcp -python3 -m venv .venv -source .venv/bin/activate +python3 -m venv venv_dolibarr +source venv_dolibarr/bin/activate pip install -e . -``` - -Install development extras (pytest, formatting, type-checking) when needed: - -```bash +# Optional development extras pip install -e '.[dev]' ``` -### Windows (Visual Studio `vsenv`) +While the virtual environment is active record the Python executable path with +`which python`. Claude Desktop must launch the MCP server using this interpreter. -1. Launch the **x64 Native Tools Command Prompt for VS** or **Developer PowerShell for VS** (`vsenv`). -2. Create a virtual environment next to the repository root: `py -3 -m venv .venv`. -3. Activate it: `call .venv\\Scripts\\activate.bat` (Command Prompt) or `.\\.venv\\Scripts\\Activate.ps1` (PowerShell). -4. Install the package: `pip install -e .`. -5. Install development extras when required: `pip install -e .[dev]` (PowerShell requires escaping brackets: ``pip install -e .`[dev`]``). -6. Run `where python` **inside** the activated environment and copy the reported path. Claude Desktop must use this exact `python.exe`; mismatched paths (for example, pointing at a non-existent `venv_dolibarr\Scripts\python.exe`) will produce an immediate `ENOENT` error and the server will show as *disconnected*. +### Windows (PowerShell) -### Docker +```powershell +git clone https://github.com/latinogino/dolibarr-mcp.git +Set-Location dolibarr-mcp +py -3 -m venv venv_dolibarr +./venv_dolibarr/Scripts/Activate.ps1 +pip install -e . +# Optional development extras (escape brackets in PowerShell) +pip install -e .`[dev`] +``` + +Run `Get-Command python` (or `Get-Command python.exe`) while the environment is +activated and note the absolute path. Claude Desktop should use this interpreter +inside the virtual environment, for example +`C:\\path\\to\\dolibarr-mcp\\venv_dolibarr\\Scripts\\python.exe`. + +### Docker (optional) ```bash -# Using Docker Compose (recommended) docker compose up -d - -# Or build and run directly +# or docker build -t dolibarr-mcp . docker run -d \ -e DOLIBARR_URL=https://your-dolibarr.example.com/api/index.php \ @@ -93,64 +75,85 @@ docker run -d \ ## βš™οΈ Configuration -Set the following environment variables (they may live in a `.env` file): +### Environment variables -- `DOLIBARR_URL` – Dolibarr API endpoint, e.g. `https://example.com/api/index.php` -- `DOLIBARR_API_KEY` – personal Dolibarr API token -- `LOG_LEVEL` – optional logging verbosity (defaults to `INFO`) +The server reads configuration from the environment or a `.env` file. Both +`DOLIBARR_URL` and `DOLIBARR_SHOP_URL` are accepted for the base API address. -[`Config`](src/dolibarr_mcp/config.py) is built with `pydantic-settings` and -supports loading from the environment, `.env` files, and CLI overrides. See the -[configuration guide](docs/configuration.md) for a full matrix and troubleshooting tips. +| Variable | Description | +| --- | --- | +| `DOLIBARR_URL` / `DOLIBARR_SHOP_URL` | Base Dolibarr API endpoint, e.g. `https://example.com/api/index.php`. Trailing slashes are handled automatically. | +| `DOLIBARR_API_KEY` | Personal Dolibarr API token. | +| `LOG_LEVEL` | Optional logging verbosity (`INFO`, `DEBUG`, `WARNING`, …). | -## ▢️ Running the server +Example `.env`: -Dolibarr MCP communicates with hosts over STDIO. Once configured, launch the -server with: - -```bash -python -m dolibarr_mcp.cli serve +```env +DOLIBARR_URL=https://your-dolibarr.example.com/api/index.php +DOLIBARR_API_KEY=YOUR_API_KEY +LOG_LEVEL=INFO ``` -You can validate credentials and connectivity using the built-in test command -before wiring it into a host: +### Claude Desktop configuration -```bash -python -m dolibarr_mcp.cli test --url https://example.com/api/index.php --api-key YOUR_TOKEN +Add the following block to `claude_desktop_config.json`, replacing the paths and +credentials with your own values: + +```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_SHOP_URL": "https://your-dolibarr.example.com", + "DOLIBARR_API_KEY": "YOUR_API_KEY" + } + } + } +} ``` -## πŸ€– Using with Claude Desktop +Restart Claude Desktop after saving the configuration. The MCP server reads the +same environment variables when launched from Linux or macOS hosts. -Add an entry to `claude_desktop_config.json` that points to your virtual -environment’s Python executable and the `dolibarr_mcp.cli` module. After -installation, verify the executable path with `which python` (Linux/macOS) or -`where python` (Windows `vsenv`). Restart Claude Desktop so it picks up the new -MCP server. +## ▢️ Usage -## πŸ§ͺ Development workflow +### Start the MCP server -- Run the test-suite with `pytest` (see [development docs](docs/development.md) - for coverage flags and Docker helpers). +The server communicates over STDIO, so run it in the foreground from the virtual +environment: + +```bash +python -m dolibarr_mcp.dolibarr_mcp_server +``` + +Logs are written to stderr to avoid interfering with the MCP protocol. Keep the +process running while Claude Desktop is active. + +### Test the Dolibarr credentials + +Use the standalone connectivity check before wiring the server into an MCP host: + +```bash +python -m dolibarr_mcp.test_connection --url https://your-dolibarr.example.com/api/index.php --api-key YOUR_API_KEY +``` + +When the environment variables are already set, omit the overrides and run +`python -m dolibarr_mcp.test_connection`. + +## πŸ§ͺ Development + +- Run the test-suite with `pytest` (see [`docs/development.md`](docs/development.md) + for coverage options and Docker helpers). - Editable installs rely on the `src/` layout and expose the `dolibarr-mcp` - console entry point. -- Contributions follow the same structure and documentation conventions as - `prestashop-mcp` to keep the twin projects in sync. - -```bash -python -m dolibarr_mcp.cli test --url https://your-dolibarr.example.com/api/index.php --api-key YOUR_KEY -``` - -## Available tools - -- **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` + console entry point for backwards compatibility. +- The repository structure, tooling, and docs intentionally mirror + [`prestashop-mcp`](https://github.com/latinogino/prestashop-mcp) to keep the + companion projects aligned. ## πŸ“„ License -This project is released under the [MIT License](LICENSE). +Released under the [MIT License](LICENSE). + diff --git a/docker/Dockerfile b/docker/Dockerfile index 32bdf66..7aa8bf3 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -59,13 +59,13 @@ USER dolibarr # Health check using test command HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ - CMD python -m dolibarr_mcp.cli test || exit 1 + CMD python -m dolibarr_mcp.test_connection || exit 1 # Expose port (for future HTTP interface) EXPOSE 8080 # Default command runs the MCP server -CMD ["python", "-m", "dolibarr_mcp"] +CMD ["python", "-m", "dolibarr_mcp.dolibarr_mcp_server"] # Labels for metadata LABEL org.opencontainers.image.title="Dolibarr MCP Server" \ diff --git a/docs/configuration.md b/docs/configuration.md index 6a852ca..200c0db 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -4,11 +4,11 @@ The Dolibarr MCP server reads configuration from environment variables. Use a `.env` file during development or configure the variables directly in the MCP host application that will launch the server. -| Variable | Description | -| ------------------- | -------------------------------------------------------------- | -| `DOLIBARR_URL` | Base API URL, e.g. `https://your-dolibarr.example.com/api/index.php` (legacy configs that still export `DOLIBARR_BASE_URL` are also honoured) | -| `DOLIBARR_API_KEY` | Personal Dolibarr API token assigned to your user | -| `LOG_LEVEL` | Optional logging level (`INFO`, `DEBUG`, `WARNING`, …) | +| Variable | Description | +| --- | --- | +| `DOLIBARR_URL` / `DOLIBARR_SHOP_URL` | Base API URL, e.g. `https://your-dolibarr.example.com/api/index.php` (legacy configs that still export `DOLIBARR_BASE_URL` are also honoured). | +| `DOLIBARR_API_KEY` | Personal Dolibarr API token assigned to your user. | +| `LOG_LEVEL` | Optional logging level (`INFO`, `DEBUG`, `WARNING`, …). | ## Example `.env` @@ -25,9 +25,9 @@ are detected. ## Testing credentials -Use the CLI helper to verify that the credentials are accepted by Dolibarr -before wiring the server into your MCP host: +Use the standalone helper to verify that the credentials are accepted by +Dolibarr before wiring the server into your MCP host: ```bash -python -m dolibarr_mcp.cli test --url https://your-dolibarr.example.com/api/index.php --api-key YOUR_KEY +python -m dolibarr_mcp.test_connection --url https://your-dolibarr.example.com/api/index.php --api-key YOUR_KEY ``` diff --git a/docs/quickstart.md b/docs/quickstart.md index ada4a73..cbf2962 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -15,17 +15,15 @@ cd dolibarr-mcp ### Linux / macOS ```bash -python3 -m venv .venv -source .venv/bin/activate +python3 -m venv venv_dolibarr +source venv_dolibarr/bin/activate ``` -### Windows (Visual Studio Developer PowerShell) +### Windows (PowerShell) ```powershell -vsenv -py -3 -m venv .venv -.\.venv\Scripts\Activate.ps1 -where python # copy this path for Claude Desktop +py -3 -m venv venv_dolibarr +./venv_dolibarr/Scripts/Activate.ps1 ``` ## 3. Install the package @@ -48,17 +46,33 @@ pip install -e .`[dev`] ## 4. Configure credentials -Create a `.env` file in the project root (see [`configuration.md`](configuration.md)) -or export the variables within your MCP host application. +Create a `.env` file in the project root (see +[`configuration.md`](configuration.md)) or export the variables within your MCP +host application. ## 5. Run the server ```bash -python -m dolibarr_mcp.cli serve +python -m dolibarr_mcp.dolibarr_mcp_server ``` The command starts the STDIO based MCP server that Claude Desktop and other clients can communicate with. When wiring the server into Claude Desktop, set -`command` to the path returned by `where python` (Windows) or `which python` -(Linux/macOS) while the virtual environment is activated, and use the arguments -`-m dolibarr_mcp.cli serve`. +`command` to the path returned by `Get-Command python` (Windows PowerShell) or +`which python` (Linux/macOS) while the virtual environment is activated, and use +the arguments `-m dolibarr_mcp.dolibarr_mcp_server`. + +## 6. (Optional) Test the connection + +Verify the credentials before integrating the server with Claude Desktop: + +```bash +python -m dolibarr_mcp.test_connection +``` + +Override the URL or API key directly when needed: + +```bash +python -m dolibarr_mcp.test_connection --url https://your-dolibarr.example.com/api/index.php --api-key YOUR_API_KEY +``` + diff --git a/src/dolibarr_mcp/cli.py b/src/dolibarr_mcp/cli.py index d7a8458..dbf8909 100644 --- a/src/dolibarr_mcp/cli.py +++ b/src/dolibarr_mcp/cli.py @@ -6,8 +6,8 @@ from typing import Optional import click -from .config import Config from .dolibarr_mcp_server import main as server_main +from .testing import test_connection as run_test_connection @click.group() @@ -22,57 +22,9 @@ def cli(): @click.option("--api-key", help="Dolibarr API key") def test(url: Optional[str], api_key: Optional[str]): """Test the connection to Dolibarr API.""" - - async def run_test(): - try: - # Import here to avoid circular imports - from .dolibarr_client import DolibarrClient - - # Create config with optional overrides - try: - config = Config() - if url: - config.dolibarr_url = url - if api_key: - config.api_key = api_key - except Exception as e: - click.echo(f"❌ Configuration error: {e}") - sys.exit(1) - - async with DolibarrClient(config) as client: - click.echo("πŸ§ͺ Testing Dolibarr API connection...") - - # Test basic status endpoint - result = await client.get_status() - - if "success" in result or "dolibarr_version" in str(result): - click.echo("βœ… Connection successful!") - if isinstance(result, dict) and "success" in result: - version = result.get("success", {}).get("dolibarr_version", "Unknown") - click.echo(f"Dolibarr Version: {version}") - - # Test a few more endpoints - try: - users = await client.get_users(limit=1) - click.echo(f"Users accessible: {len(users) if isinstance(users, list) else 'Error'}") - except Exception: - click.echo("Users: ⚠️ Access limited or unavailable") - - try: - customers = await client.get_customers(limit=1) - click.echo(f"Customers accessible: {len(customers) if isinstance(customers, list) else 'Error'}") - except Exception: - click.echo("Customers: ⚠️ Access limited or unavailable") - - else: - click.echo(f"❌ Connection failed: {result}") - sys.exit(1) - - except Exception as e: - click.echo(f"❌ Test failed: {e}") - sys.exit(1) - - asyncio.run(run_test()) + exit_code = run_test_connection(url=url, api_key=api_key) + if exit_code != 0: + sys.exit(exit_code) @cli.command() diff --git a/src/dolibarr_mcp/config.py b/src/dolibarr_mcp/config.py index 8de81b9..da138e4 100644 --- a/src/dolibarr_mcp/config.py +++ b/src/dolibarr_mcp/config.py @@ -43,7 +43,12 @@ class Config(BaseSettings): def validate_dolibarr_url(cls, v: str) -> str: """Validate Dolibarr URL.""" if not v: - v = os.getenv("DOLIBARR_URL") or os.getenv("DOLIBARR_BASE_URL", "") + v = ( + os.getenv("DOLIBARR_URL") + or os.getenv("DOLIBARR_BASE_URL") + or os.getenv("DOLIBARR_SHOP_URL") + or "" + ) if not v: # Print warning but don't fail print( @@ -114,7 +119,12 @@ class Config(BaseSettings): """Create configuration from environment variables with validation.""" try: config = cls( - dolibarr_url=os.getenv("DOLIBARR_URL") or os.getenv("DOLIBARR_BASE_URL", ""), + dolibarr_url=( + os.getenv("DOLIBARR_URL") + or os.getenv("DOLIBARR_BASE_URL") + or os.getenv("DOLIBARR_SHOP_URL") + or "" + ), dolibarr_api_key=os.getenv("DOLIBARR_API_KEY", ""), log_level=os.getenv("LOG_LEVEL", "INFO"), ) diff --git a/src/dolibarr_mcp/test_connection.py b/src/dolibarr_mcp/test_connection.py new file mode 100644 index 0000000..24bd26a --- /dev/null +++ b/src/dolibarr_mcp/test_connection.py @@ -0,0 +1,25 @@ +"""Standalone entry point to verify Dolibarr connectivity.""" + +from __future__ import annotations + +import argparse + +from .testing import test_connection + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser(description="Test the Dolibarr MCP configuration") + parser.add_argument("--url", help="Override the Dolibarr API URL", default=None) + parser.add_argument("--api-key", help="Override the Dolibarr API key", default=None) + return parser.parse_args() + + +def main() -> None: + args = parse_args() + exit_code = test_connection(url=args.url, api_key=args.api_key) + raise SystemExit(exit_code) + + +if __name__ == "__main__": # pragma: no cover - manual execution entry + main() + diff --git a/src/dolibarr_mcp/testing.py b/src/dolibarr_mcp/testing.py new file mode 100644 index 0000000..3124018 --- /dev/null +++ b/src/dolibarr_mcp/testing.py @@ -0,0 +1,55 @@ +"""Utility helpers shared by command line entry points.""" + +from __future__ import annotations + +import asyncio +import sys +from typing import Optional + +from .config import Config +from .dolibarr_client import DolibarrClient + + +async def _run_test(url: Optional[str], api_key: Optional[str]) -> int: + """Execute the Dolibarr connectivity test returning an exit code.""" + + try: + config = Config() + if url: + config.dolibarr_url = url + if api_key: + config.api_key = api_key + except Exception as exc: # pragma: no cover - defensive printing + print(f"❌ Configuration error: {exc}", file=sys.stderr) + return 1 + + try: + async with DolibarrClient(config) as client: + print("πŸ§ͺ Testing Dolibarr API connection...") + result = await client.get_status() + + if "success" in result or "dolibarr_version" in str(result): + print("βœ… Connection successful!") + if isinstance(result, dict) and "success" in result: + version = result.get("success", {}).get("dolibarr_version", "Unknown") + print(f"Dolibarr Version: {version}") + return 0 + + print(f"⚠️ API responded unexpectedly: {result}") + print("⚠️ Server will run but API calls may fail") + return 2 + + except Exception as exc: # pragma: no cover - network failure path + print(f"❌ Test failed: {exc}", file=sys.stderr) + return 1 + + +def test_connection(url: Optional[str] = None, api_key: Optional[str] = None) -> int: + """Synchronously test the Dolibarr API connection. + + Parameters mirror the CLI flags and environment variables. + Returns an exit code compatible with `sys.exit`. + """ + + return asyncio.run(_run_test(url=url, api_key=api_key)) +