fa5cb994c9
Changelog Validation / Validate CHANGELOG.md (push) Has been cancelled
Build & Release / Build & Release Pipeline (push) Has been cancelled
Deploy to Demo Server (SFTP) / Verify Deployment Permission (push) Has been cancelled
Standards Compliance / Secret Scanning (push) Has been cancelled
Standards Compliance / License Header Validation (push) Has been cancelled
Standards Compliance / Repository Structure Validation (push) Has been cancelled
Standards Compliance / Coding Standards Check (push) Has been cancelled
Standards Compliance / Workflow Configuration Check (push) Has been cancelled
Standards Compliance / Documentation Quality Check (push) Has been cancelled
Standards Compliance / README Completeness Check (push) Has been cancelled
Standards Compliance / Git Repository Hygiene (push) Has been cancelled
Standards Compliance / Script Integrity Validation (push) Has been cancelled
Standards Compliance / Line Length Check (push) Has been cancelled
Standards Compliance / File Naming Standards (push) Has been cancelled
Standards Compliance / Insecure Code Pattern Detection (push) Has been cancelled
CodeQL Security Scanning / Analyze (actions) (push) Has been cancelled
CodeQL Security Scanning / Analyze (javascript) (push) Has been cancelled
Standards Compliance / Version Consistency Check (push) Has been cancelled
Standards Compliance / Dead Code Detection (push) Has been cancelled
Standards Compliance / File Size Limits (push) Has been cancelled
Standards Compliance / Binary File Detection (push) Has been cancelled
Standards Compliance / TODO/FIXME Tracking (push) Has been cancelled
Standards Compliance / Code Duplication Detection (push) Has been cancelled
Standards Compliance / Broken Link Detection (push) Has been cancelled
Standards Compliance / Code Complexity Analysis (push) Has been cancelled
Standards Compliance / API Documentation Coverage (push) Has been cancelled
Standards Compliance / Accessibility Check (push) Has been cancelled
Standards Compliance / Performance Metrics (push) Has been cancelled
Standards Compliance / Dependency Vulnerability Scanning (push) Has been cancelled
Standards Compliance / Unused Dependencies Check (push) Has been cancelled
Standards Compliance / Terraform Configuration Validation (push) Has been cancelled
Deploy to Demo Server (SFTP) / SFTP Deploy → Demo (push) Has been cancelled
CodeQL Security Scanning / Security Scan Summary (push) Has been cancelled
Standards Compliance / Enterprise Readiness Check (push) Has been cancelled
Standards Compliance / Repository Health Check (push) Has been cancelled
Sync Version from README / Propagate README version (push) Has been cancelled
Standards Compliance / Compliance Summary (push) Has been cancelled
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1781 lines
66 KiB
TypeScript
1781 lines
66 KiB
TypeScript
#!/usr/bin/env node
|
|
/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
|
*
|
|
* This file is part of a Moko Consulting project.
|
|
*
|
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
*
|
|
* FILE INFORMATION
|
|
* DEFGROUP: dolibarr-api-mcp.Server
|
|
* INGROUP: dolibarr-api-mcp
|
|
* REPO: https://git.mokoconsulting.tech/MokoConsulting/dolibarr-api-mcp
|
|
* PATH: /src/index.ts
|
|
* VERSION: 01.00.00
|
|
* BRIEF: MCP server entry point — registers all Dolibarr API tools
|
|
*/
|
|
|
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
import { z } from 'zod';
|
|
import { loadConfig, getConnection } from './config.js';
|
|
import { DolibarrClient } from './client.js';
|
|
import type { DolibarrConfig, ApiResponse } from './types.js';
|
|
|
|
let config: DolibarrConfig;
|
|
|
|
function clientFor(connection?: string): DolibarrClient {
|
|
return new DolibarrClient(getConnection(config, connection));
|
|
}
|
|
|
|
function formatResponse(res: ApiResponse): { content: Array<{ type: 'text'; text: string }> } {
|
|
if (res.status >= 400) {
|
|
const error_data = res.data;
|
|
let msg: string;
|
|
if (typeof error_data === 'object' && error_data !== null && 'error' in error_data) {
|
|
const err = error_data as { error: { code: number; message: string } };
|
|
msg = `${err.error.code}: ${err.error.message}`;
|
|
} else {
|
|
msg = `HTTP ${res.status}: ${JSON.stringify(res.data, null, 2)}`;
|
|
}
|
|
return { content: [{ type: 'text' as const, text: `Error: ${msg}` }] };
|
|
}
|
|
return {
|
|
content: [{ type: 'text' as const, text: JSON.stringify(res.data, null, 2) }],
|
|
};
|
|
}
|
|
|
|
const ConnectionParam = {
|
|
connection: z.string().optional().describe('Named connection from config (uses default if omitted)'),
|
|
};
|
|
|
|
const PaginationParams = {
|
|
limit: z.number().optional().describe('Max results (default 100)'),
|
|
page: z.number().optional().describe('Page number (0-based)'),
|
|
sortfield: z.string().optional().describe('Field to sort by'),
|
|
sortorder: z.enum(['ASC', 'DESC']).optional().describe('Sort direction'),
|
|
};
|
|
|
|
function paginationQuery(params: { limit?: number; page?: number; sortfield?: string; sortorder?: string }): Record<string, string> {
|
|
const q: Record<string, string> = {};
|
|
if (params.limit !== undefined) q['limit'] = String(params.limit);
|
|
if (params.page !== undefined) q['page'] = String(params.page);
|
|
if (params.sortfield) q['sortfield'] = params.sortfield;
|
|
if (params.sortorder) q['sortorder'] = params.sortorder;
|
|
return q;
|
|
}
|
|
|
|
type SqlOp = 'like' | '=' | '!=' | '<' | '>' | '<=' | '>=' | 'is' | 'isnot';
|
|
|
|
interface SqlFilterClause {
|
|
field: string;
|
|
op: SqlOp;
|
|
value: string | number | null;
|
|
}
|
|
|
|
function buildSqlFilter(clauses: SqlFilterClause[], join: 'AND' | 'OR' = 'AND'): string {
|
|
const parts = clauses.map(({ field, op, value }) => {
|
|
if (value === null) return `(${field}:${op}:null)`;
|
|
const v = typeof value === 'string' ? `'${value.replace(/'/g, "''")}'` : String(value);
|
|
return `(${field}:${op}:${v})`;
|
|
});
|
|
if (parts.length === 0) return '';
|
|
if (parts.length === 1) return parts[0];
|
|
return parts.join(` ${join} `);
|
|
}
|
|
|
|
function searchFilter(field: string, term: string): string {
|
|
return buildSqlFilter([{ field, op: 'like', value: `%${term}%` }]);
|
|
}
|
|
|
|
const server = new McpServer({
|
|
name: 'dolibarr-api-mcp',
|
|
version: '1.0.0',
|
|
});
|
|
|
|
// ── Third Parties (Customers/Suppliers) ─────────────────────────────────
|
|
|
|
server.tool(
|
|
'dolibarr_thirdparties_list',
|
|
'List third parties (customers, suppliers, prospects)',
|
|
{
|
|
mode: z.enum(['1', '2', '3', '4']).optional().describe('1=customer, 2=prospect, 3=supplier, 4=customer+supplier'),
|
|
search: z.string().optional().describe('Search in name'),
|
|
category: z.number().optional().describe('Filter by category ID'),
|
|
...PaginationParams,
|
|
...ConnectionParam,
|
|
},
|
|
async ({ mode, search, category, limit, page, sortfield, sortorder, connection }) => {
|
|
const client = clientFor(connection);
|
|
const params: Record<string, string> = { ...paginationQuery({ limit, page, sortfield, sortorder }) };
|
|
if (mode) params['mode'] = mode;
|
|
if (search) params['sqlfilters'] = searchFilter('t.nom', search);
|
|
if (category !== undefined) params['category'] = String(category);
|
|
return formatResponse(await client.get('/thirdparties', params));
|
|
},
|
|
);
|
|
|
|
server.tool(
|
|
'dolibarr_thirdparty_get',
|
|
'Get a single third party by ID',
|
|
{
|
|
id: z.number().describe('Third party ID'),
|
|
...ConnectionParam,
|
|
},
|
|
async ({ id, connection }) => {
|
|
const client = clientFor(connection);
|
|
return formatResponse(await client.get(`/thirdparties/${id}`));
|
|
},
|
|
);
|
|
|
|
server.tool(
|
|
'dolibarr_thirdparty_create',
|
|
'Create a new third party',
|
|
{
|
|
name: z.string().describe('Company or individual name'),
|
|
client: z.enum(['0', '1', '2', '3']).optional().describe('0=neither, 1=customer, 2=prospect, 3=customer+prospect'),
|
|
fournisseur: z.enum(['0', '1']).optional().describe('0=not supplier, 1=supplier'),
|
|
email: z.string().optional().describe('Email address'),
|
|
phone: z.string().optional().describe('Phone number'),
|
|
address: z.string().optional().describe('Street address'),
|
|
zip: z.string().optional().describe('Postal code'),
|
|
town: z.string().optional().describe('City'),
|
|
country_id: z.number().optional().describe('Country ID'),
|
|
...ConnectionParam,
|
|
},
|
|
async ({ name, client: clientType, fournisseur, email, phone, address, zip, town, country_id, connection }) => {
|
|
const api = clientFor(connection);
|
|
const body: Record<string, unknown> = { name };
|
|
if (clientType !== undefined) body.client = clientType;
|
|
if (fournisseur !== undefined) body.fournisseur = fournisseur;
|
|
if (email) body.email = email;
|
|
if (phone) body.phone = phone;
|
|
if (address) body.address = address;
|
|
if (zip) body.zip = zip;
|
|
if (town) body.town = town;
|
|
if (country_id !== undefined) body.country_id = country_id;
|
|
return formatResponse(await api.post('/thirdparties', body));
|
|
},
|
|
);
|
|
|
|
server.tool(
|
|
'dolibarr_thirdparty_update',
|
|
'Update an existing third party',
|
|
{
|
|
id: z.number().describe('Third party ID'),
|
|
name: z.string().optional().describe('Company or individual name'),
|
|
email: z.string().optional().describe('Email address'),
|
|
phone: z.string().optional().describe('Phone number'),
|
|
address: z.string().optional().describe('Street address'),
|
|
zip: z.string().optional().describe('Postal code'),
|
|
town: z.string().optional().describe('City'),
|
|
country_id: z.number().optional().describe('Country ID'),
|
|
...ConnectionParam,
|
|
},
|
|
async ({ id, name, email, phone, address, zip, town, country_id, connection }) => {
|
|
const client = clientFor(connection);
|
|
const body: Record<string, unknown> = {};
|
|
if (name !== undefined) body.name = name;
|
|
if (email !== undefined) body.email = email;
|
|
if (phone !== undefined) body.phone = phone;
|
|
if (address !== undefined) body.address = address;
|
|
if (zip !== undefined) body.zip = zip;
|
|
if (town !== undefined) body.town = town;
|
|
if (country_id !== undefined) body.country_id = country_id;
|
|
return formatResponse(await client.put(`/thirdparties/${id}`, body));
|
|
},
|
|
);
|
|
|
|
server.tool(
|
|
'dolibarr_thirdparty_delete',
|
|
'Delete a third party',
|
|
{
|
|
id: z.number().describe('Third party ID'),
|
|
...ConnectionParam,
|
|
},
|
|
async ({ id, connection }) => {
|
|
const client = clientFor(connection);
|
|
return formatResponse(await client.delete(`/thirdparties/${id}`));
|
|
},
|
|
);
|
|
|
|
// ── Invoices ────────────────────────────────────────────────────────────
|
|
|
|
server.tool(
|
|
'dolibarr_invoices_list',
|
|
'List invoices',
|
|
{
|
|
status: z.enum(['draft', 'unpaid', 'paid', 'cancelled']).optional().describe('Filter by invoice status'),
|
|
thirdparty_ids: z.string().optional().describe('Comma-separated third party IDs'),
|
|
search: z.string().optional().describe('Search in ref or ref_client'),
|
|
...PaginationParams,
|
|
...ConnectionParam,
|
|
},
|
|
async ({ status, thirdparty_ids, search, limit, page, sortfield, sortorder, connection }) => {
|
|
const client = clientFor(connection);
|
|
const params: Record<string, string> = { ...paginationQuery({ limit, page, sortfield, sortorder }) };
|
|
if (status) params['status'] = status;
|
|
if (thirdparty_ids) params['thirdparty_ids'] = thirdparty_ids;
|
|
if (search) params['sqlfilters'] = searchFilter('t.ref', search);
|
|
return formatResponse(await client.get('/invoices', params));
|
|
},
|
|
);
|
|
|
|
server.tool(
|
|
'dolibarr_invoice_get',
|
|
'Get a single invoice by ID',
|
|
{
|
|
id: z.number().describe('Invoice ID'),
|
|
...ConnectionParam,
|
|
},
|
|
async ({ id, connection }) => {
|
|
const client = clientFor(connection);
|
|
return formatResponse(await client.get(`/invoices/${id}`));
|
|
},
|
|
);
|
|
|
|
server.tool(
|
|
'dolibarr_invoice_create',
|
|
'Create a new invoice',
|
|
{
|
|
socid: z.number().describe('Third party (customer) ID'),
|
|
type: z.enum(['0', '1', '2', '3']).optional().describe('0=standard, 1=replacement, 2=credit note, 3=deposit'),
|
|
date: z.string().optional().describe('Invoice date (YYYY-MM-DD or Unix timestamp)'),
|
|
note_public: z.string().optional().describe('Public note'),
|
|
note_private: z.string().optional().describe('Private note'),
|
|
...ConnectionParam,
|
|
},
|
|
async ({ socid, type, date, note_public, note_private, connection }) => {
|
|
const client = clientFor(connection);
|
|
const body: Record<string, unknown> = { socid };
|
|
if (type !== undefined) body.type = type;
|
|
if (date) body.date = date;
|
|
if (note_public) body.note_public = note_public;
|
|
if (note_private) body.note_private = note_private;
|
|
return formatResponse(await client.post('/invoices', body));
|
|
},
|
|
);
|
|
|
|
server.tool(
|
|
'dolibarr_invoice_add_line',
|
|
'Add a line to an invoice',
|
|
{
|
|
id: z.number().describe('Invoice ID'),
|
|
desc: z.string().describe('Line description'),
|
|
subprice: z.number().describe('Unit price (HT)'),
|
|
qty: z.number().describe('Quantity'),
|
|
tva_tx: z.number().optional().describe('VAT rate (e.g. 20.0)'),
|
|
product_id: z.number().optional().describe('Product/service ID'),
|
|
...ConnectionParam,
|
|
},
|
|
async ({ id, desc, subprice, qty, tva_tx, product_id, connection }) => {
|
|
const client = clientFor(connection);
|
|
const body: Record<string, unknown> = { desc, subprice, qty };
|
|
if (tva_tx !== undefined) body.tva_tx = tva_tx;
|
|
if (product_id !== undefined) body.fk_product = product_id;
|
|
return formatResponse(await client.post(`/invoices/${id}/lines`, body));
|
|
},
|
|
);
|
|
|
|
server.tool(
|
|
'dolibarr_invoice_validate',
|
|
'Validate (finalize) a draft invoice',
|
|
{
|
|
id: z.number().describe('Invoice ID'),
|
|
...ConnectionParam,
|
|
},
|
|
async ({ id, connection }) => {
|
|
const client = clientFor(connection);
|
|
return formatResponse(await client.post(`/invoices/${id}/validate`, {}));
|
|
},
|
|
);
|
|
|
|
server.tool(
|
|
'dolibarr_invoice_set_paid',
|
|
'Mark an invoice as paid',
|
|
{
|
|
id: z.number().describe('Invoice ID'),
|
|
close_code: z.string().optional().describe('Close code (e.g. "bankorder", "cash")'),
|
|
close_note: z.string().optional().describe('Close note'),
|
|
...ConnectionParam,
|
|
},
|
|
async ({ id, close_code, close_note, connection }) => {
|
|
const client = clientFor(connection);
|
|
const body: Record<string, unknown> = {};
|
|
if (close_code) body.close_code = close_code;
|
|
if (close_note) body.close_note = close_note;
|
|
return formatResponse(await client.post(`/invoices/${id}/settopaid`, body));
|
|
},
|
|
);
|
|
|
|
// ── Proposals (Quotes) ─────────────────────────────────────────────────
|
|
|
|
server.tool(
|
|
'dolibarr_proposals_list',
|
|
'List commercial proposals (quotes)',
|
|
{
|
|
status: z.enum(['0', '1', '2', '3', '4']).optional().describe('0=draft, 1=validated, 2=signed, 3=not-signed, 4=billed'),
|
|
thirdparty_ids: z.string().optional().describe('Comma-separated third party IDs'),
|
|
...PaginationParams,
|
|
...ConnectionParam,
|
|
},
|
|
async ({ status, thirdparty_ids, limit, page, sortfield, sortorder, connection }) => {
|
|
const client = clientFor(connection);
|
|
const params: Record<string, string> = { ...paginationQuery({ limit, page, sortfield, sortorder }) };
|
|
if (status) params['status'] = status;
|
|
if (thirdparty_ids) params['thirdparty_ids'] = thirdparty_ids;
|
|
return formatResponse(await client.get('/proposals', params));
|
|
},
|
|
);
|
|
|
|
server.tool(
|
|
'dolibarr_proposal_get',
|
|
'Get a single proposal by ID',
|
|
{
|
|
id: z.number().describe('Proposal ID'),
|
|
...ConnectionParam,
|
|
},
|
|
async ({ id, connection }) => {
|
|
const client = clientFor(connection);
|
|
return formatResponse(await client.get(`/proposals/${id}`));
|
|
},
|
|
);
|
|
|
|
server.tool(
|
|
'dolibarr_proposal_create',
|
|
'Create a new commercial proposal',
|
|
{
|
|
socid: z.number().describe('Third party (customer) ID'),
|
|
date: z.string().optional().describe('Proposal date (YYYY-MM-DD or Unix timestamp)'),
|
|
duree_validite: z.number().optional().describe('Validity duration in days'),
|
|
note_public: z.string().optional().describe('Public note'),
|
|
note_private: z.string().optional().describe('Private note'),
|
|
...ConnectionParam,
|
|
},
|
|
async ({ socid, date, duree_validite, note_public, note_private, connection }) => {
|
|
const client = clientFor(connection);
|
|
const body: Record<string, unknown> = { socid };
|
|
if (date) body.date = date;
|
|
if (duree_validite !== undefined) body.duree_validite = duree_validite;
|
|
if (note_public) body.note_public = note_public;
|
|
if (note_private) body.note_private = note_private;
|
|
return formatResponse(await client.post('/proposals', body));
|
|
},
|
|
);
|
|
|
|
server.tool(
|
|
'dolibarr_proposal_add_line',
|
|
'Add a line to a proposal',
|
|
{
|
|
id: z.number().describe('Proposal ID'),
|
|
desc: z.string().describe('Line description'),
|
|
subprice: z.number().describe('Unit price (HT)'),
|
|
qty: z.number().describe('Quantity'),
|
|
tva_tx: z.number().optional().describe('VAT rate (e.g. 20.0)'),
|
|
product_id: z.number().optional().describe('Product/service ID'),
|
|
...ConnectionParam,
|
|
},
|
|
async ({ id, desc, subprice, qty, tva_tx, product_id, connection }) => {
|
|
const client = clientFor(connection);
|
|
const body: Record<string, unknown> = { desc, subprice, qty };
|
|
if (tva_tx !== undefined) body.tva_tx = tva_tx;
|
|
if (product_id !== undefined) body.fk_product = product_id;
|
|
return formatResponse(await client.post(`/proposals/${id}/lines`, body));
|
|
},
|
|
);
|
|
|
|
server.tool(
|
|
'dolibarr_proposal_validate',
|
|
'Validate a draft proposal',
|
|
{
|
|
id: z.number().describe('Proposal ID'),
|
|
...ConnectionParam,
|
|
},
|
|
async ({ id, connection }) => {
|
|
const client = clientFor(connection);
|
|
return formatResponse(await client.post(`/proposals/${id}/validate`, {}));
|
|
},
|
|
);
|
|
|
|
server.tool(
|
|
'dolibarr_proposal_close',
|
|
'Close a proposal (sign or refuse)',
|
|
{
|
|
id: z.number().describe('Proposal ID'),
|
|
status: z.enum(['2', '3']).describe('2=signed, 3=not signed (refused)'),
|
|
note: z.string().optional().describe('Close note'),
|
|
...ConnectionParam,
|
|
},
|
|
async ({ id, status, note, connection }) => {
|
|
const client = clientFor(connection);
|
|
const body: Record<string, unknown> = { status: Number(status) };
|
|
if (note) body.note_private = note;
|
|
return formatResponse(await client.post(`/proposals/${id}/close`, body));
|
|
},
|
|
);
|
|
|
|
// ── Orders ──────────────────────────────────────────────────────────────
|
|
|
|
server.tool(
|
|
'dolibarr_orders_list',
|
|
'List customer orders',
|
|
{
|
|
status: z.enum(['0', '1', '2', '3', '-1']).optional().describe('0=draft, 1=validated, 2=processing, 3=delivered, -1=cancelled'),
|
|
thirdparty_ids: z.string().optional().describe('Comma-separated third party IDs'),
|
|
...PaginationParams,
|
|
...ConnectionParam,
|
|
},
|
|
async ({ status, thirdparty_ids, limit, page, sortfield, sortorder, connection }) => {
|
|
const client = clientFor(connection);
|
|
const params: Record<string, string> = { ...paginationQuery({ limit, page, sortfield, sortorder }) };
|
|
if (status) params['status'] = status;
|
|
if (thirdparty_ids) params['thirdparty_ids'] = thirdparty_ids;
|
|
return formatResponse(await client.get('/orders', params));
|
|
},
|
|
);
|
|
|
|
server.tool(
|
|
'dolibarr_order_get',
|
|
'Get a single order by ID',
|
|
{
|
|
id: z.number().describe('Order ID'),
|
|
...ConnectionParam,
|
|
},
|
|
async ({ id, connection }) => {
|
|
const client = clientFor(connection);
|
|
return formatResponse(await client.get(`/orders/${id}`));
|
|
},
|
|
);
|
|
|
|
server.tool(
|
|
'dolibarr_order_create',
|
|
'Create a new customer order',
|
|
{
|
|
socid: z.number().describe('Third party (customer) ID'),
|
|
date: z.string().optional().describe('Order date (YYYY-MM-DD or Unix timestamp)'),
|
|
note_public: z.string().optional().describe('Public note'),
|
|
note_private: z.string().optional().describe('Private note'),
|
|
...ConnectionParam,
|
|
},
|
|
async ({ socid, date, note_public, note_private, connection }) => {
|
|
const client = clientFor(connection);
|
|
const body: Record<string, unknown> = { socid };
|
|
if (date) body.date = date;
|
|
if (note_public) body.note_public = note_public;
|
|
if (note_private) body.note_private = note_private;
|
|
return formatResponse(await client.post('/orders', body));
|
|
},
|
|
);
|
|
|
|
server.tool(
|
|
'dolibarr_order_validate',
|
|
'Validate a draft order',
|
|
{
|
|
id: z.number().describe('Order ID'),
|
|
...ConnectionParam,
|
|
},
|
|
async ({ id, connection }) => {
|
|
const client = clientFor(connection);
|
|
return formatResponse(await client.post(`/orders/${id}/validate`, {}));
|
|
},
|
|
);
|
|
|
|
// ── Products / Services ─────────────────────────────────────────────────
|
|
|
|
server.tool(
|
|
'dolibarr_products_list',
|
|
'List products and/or services',
|
|
{
|
|
type: z.enum(['0', '1']).optional().describe('0=product, 1=service'),
|
|
search: z.string().optional().describe('Search in label or ref'),
|
|
category: z.number().optional().describe('Filter by category ID'),
|
|
to_sell: z.enum(['0', '1']).optional().describe('1=for sale only'),
|
|
to_buy: z.enum(['0', '1']).optional().describe('1=for purchase only'),
|
|
...PaginationParams,
|
|
...ConnectionParam,
|
|
},
|
|
async ({ type, search, category, to_sell, to_buy, limit, page, sortfield, sortorder, connection }) => {
|
|
const client = clientFor(connection);
|
|
const params: Record<string, string> = { ...paginationQuery({ limit, page, sortfield, sortorder }) };
|
|
if (type) params['type'] = type;
|
|
if (search) params['sqlfilters'] = searchFilter('t.label', search);
|
|
if (category !== undefined) params['category'] = String(category);
|
|
if (to_sell) params['to_sell'] = to_sell;
|
|
if (to_buy) params['to_buy'] = to_buy;
|
|
return formatResponse(await client.get('/products', params));
|
|
},
|
|
);
|
|
|
|
server.tool(
|
|
'dolibarr_product_get',
|
|
'Get a single product/service by ID',
|
|
{
|
|
id: z.number().describe('Product ID'),
|
|
...ConnectionParam,
|
|
},
|
|
async ({ id, connection }) => {
|
|
const client = clientFor(connection);
|
|
return formatResponse(await client.get(`/products/${id}`));
|
|
},
|
|
);
|
|
|
|
server.tool(
|
|
'dolibarr_product_create',
|
|
'Create a new product or service',
|
|
{
|
|
ref: z.string().describe('Product reference code'),
|
|
label: z.string().describe('Product label/name'),
|
|
type: z.enum(['0', '1']).optional().describe('0=product, 1=service (default 0)'),
|
|
price: z.number().optional().describe('Selling price (HT)'),
|
|
price_ttc: z.number().optional().describe('Selling price (TTC)'),
|
|
tva_tx: z.number().optional().describe('Default VAT rate'),
|
|
description: z.string().optional().describe('Description'),
|
|
status: z.enum(['0', '1']).optional().describe('1=on sale, 0=not on sale'),
|
|
status_buy: z.enum(['0', '1']).optional().describe('1=on purchase, 0=not on purchase'),
|
|
...ConnectionParam,
|
|
},
|
|
async ({ ref, label, type, price, price_ttc, tva_tx, description, status, status_buy, connection }) => {
|
|
const client = clientFor(connection);
|
|
const body: Record<string, unknown> = { ref, label };
|
|
if (type !== undefined) body.type = type;
|
|
if (price !== undefined) body.price = price;
|
|
if (price_ttc !== undefined) body.price_ttc = price_ttc;
|
|
if (tva_tx !== undefined) body.tva_tx = tva_tx;
|
|
if (description) body.description = description;
|
|
if (status !== undefined) body.status = status;
|
|
if (status_buy !== undefined) body.status_buy = status_buy;
|
|
return formatResponse(await client.post('/products', body));
|
|
},
|
|
);
|
|
|
|
server.tool(
|
|
'dolibarr_product_update',
|
|
'Update a product or service',
|
|
{
|
|
id: z.number().describe('Product ID'),
|
|
ref: z.string().optional().describe('Product reference code'),
|
|
label: z.string().optional().describe('Product label/name'),
|
|
price: z.number().optional().describe('Selling price (HT)'),
|
|
description: z.string().optional().describe('Description'),
|
|
status: z.enum(['0', '1']).optional().describe('1=on sale, 0=not on sale'),
|
|
status_buy: z.enum(['0', '1']).optional().describe('1=on purchase, 0=not on purchase'),
|
|
...ConnectionParam,
|
|
},
|
|
async ({ id, ref, label, price, description, status, status_buy, connection }) => {
|
|
const client = clientFor(connection);
|
|
const body: Record<string, unknown> = {};
|
|
if (ref !== undefined) body.ref = ref;
|
|
if (label !== undefined) body.label = label;
|
|
if (price !== undefined) body.price = price;
|
|
if (description !== undefined) body.description = description;
|
|
if (status !== undefined) body.status = status;
|
|
if (status_buy !== undefined) body.status_buy = status_buy;
|
|
return formatResponse(await client.put(`/products/${id}`, body));
|
|
},
|
|
);
|
|
|
|
// ── Contacts / Addresses ────────────────────────────────────────────────
|
|
|
|
server.tool(
|
|
'dolibarr_contacts_list',
|
|
'List contacts/addresses',
|
|
{
|
|
search: z.string().optional().describe('Search in name'),
|
|
thirdparty_id: z.number().optional().describe('Filter by third party ID'),
|
|
...PaginationParams,
|
|
...ConnectionParam,
|
|
},
|
|
async ({ search, thirdparty_id, limit, page, sortfield, sortorder, connection }) => {
|
|
const client = clientFor(connection);
|
|
const params: Record<string, string> = { ...paginationQuery({ limit, page, sortfield, sortorder }) };
|
|
if (search) params['sqlfilters'] = searchFilter('t.lastname', search);
|
|
if (thirdparty_id !== undefined) params['thirdparty_ids'] = String(thirdparty_id);
|
|
return formatResponse(await client.get('/contacts', params));
|
|
},
|
|
);
|
|
|
|
server.tool(
|
|
'dolibarr_contact_get',
|
|
'Get a single contact by ID',
|
|
{
|
|
id: z.number().describe('Contact ID'),
|
|
...ConnectionParam,
|
|
},
|
|
async ({ id, connection }) => {
|
|
const client = clientFor(connection);
|
|
return formatResponse(await client.get(`/contacts/${id}`));
|
|
},
|
|
);
|
|
|
|
// ── Projects ────────────────────────────────────────────────────────────
|
|
|
|
server.tool(
|
|
'dolibarr_projects_list',
|
|
'List projects',
|
|
{
|
|
status: z.enum(['0', '1', '2']).optional().describe('0=draft, 1=open, 2=closed'),
|
|
search: z.string().optional().describe('Search in title or ref'),
|
|
...PaginationParams,
|
|
...ConnectionParam,
|
|
},
|
|
async ({ status, search, limit, page, sortfield, sortorder, connection }) => {
|
|
const client = clientFor(connection);
|
|
const params: Record<string, string> = { ...paginationQuery({ limit, page, sortfield, sortorder }) };
|
|
const projectClauses: SqlFilterClause[] = [];
|
|
if (status) projectClauses.push({ field: 't.fk_statut', op: '=', value: Number(status) });
|
|
if (search) projectClauses.push({ field: 't.title', op: 'like', value: `%${search}%` });
|
|
if (projectClauses.length) params['sqlfilters'] = buildSqlFilter(projectClauses);
|
|
return formatResponse(await client.get('/projects', params));
|
|
},
|
|
);
|
|
|
|
server.tool(
|
|
'dolibarr_project_get',
|
|
'Get a single project by ID',
|
|
{
|
|
id: z.number().describe('Project ID'),
|
|
...ConnectionParam,
|
|
},
|
|
async ({ id, connection }) => {
|
|
const client = clientFor(connection);
|
|
return formatResponse(await client.get(`/projects/${id}`));
|
|
},
|
|
);
|
|
|
|
server.tool(
|
|
'dolibarr_project_create',
|
|
'Create a new project',
|
|
{
|
|
ref: z.string().describe('Project reference'),
|
|
title: z.string().describe('Project title'),
|
|
socid: z.number().optional().describe('Third party ID'),
|
|
description: z.string().optional().describe('Project description'),
|
|
date_start: z.string().optional().describe('Start date (YYYY-MM-DD or Unix timestamp)'),
|
|
date_end: z.string().optional().describe('End date (YYYY-MM-DD or Unix timestamp)'),
|
|
...ConnectionParam,
|
|
},
|
|
async ({ ref, title, socid, description, date_start, date_end, connection }) => {
|
|
const client = clientFor(connection);
|
|
const body: Record<string, unknown> = { ref, title };
|
|
if (socid !== undefined) body.socid = socid;
|
|
if (description) body.description = description;
|
|
if (date_start) body.date_start = date_start;
|
|
if (date_end) body.date_end = date_end;
|
|
return formatResponse(await client.post('/projects', body));
|
|
},
|
|
);
|
|
|
|
// ── Tasks ───────────────────────────────────────────────────────────────
|
|
|
|
server.tool(
|
|
'dolibarr_tasks_list',
|
|
'List project tasks',
|
|
{
|
|
project_id: z.number().optional().describe('Filter by project ID'),
|
|
...PaginationParams,
|
|
...ConnectionParam,
|
|
},
|
|
async ({ project_id, limit, page, sortfield, sortorder, connection }) => {
|
|
const client = clientFor(connection);
|
|
const params: Record<string, string> = { ...paginationQuery({ limit, page, sortfield, sortorder }) };
|
|
if (project_id !== undefined) params['sqlfilters'] = buildSqlFilter([{ field: 't.fk_projet', op: '=', value: project_id }]);
|
|
return formatResponse(await client.get('/tasks', params));
|
|
},
|
|
);
|
|
|
|
server.tool(
|
|
'dolibarr_task_get',
|
|
'Get a single task by ID',
|
|
{
|
|
id: z.number().describe('Task ID'),
|
|
...ConnectionParam,
|
|
},
|
|
async ({ id, connection }) => {
|
|
const client = clientFor(connection);
|
|
return formatResponse(await client.get(`/tasks/${id}`));
|
|
},
|
|
);
|
|
|
|
// ── Users ───────────────────────────────────────────────────────────────
|
|
|
|
server.tool(
|
|
'dolibarr_users_list',
|
|
'List Dolibarr users',
|
|
{
|
|
search: z.string().optional().describe('Search in name/login'),
|
|
...PaginationParams,
|
|
...ConnectionParam,
|
|
},
|
|
async ({ search, limit, page, sortfield, sortorder, connection }) => {
|
|
const client = clientFor(connection);
|
|
const params: Record<string, string> = { ...paginationQuery({ limit, page, sortfield, sortorder }) };
|
|
if (search) params['sqlfilters'] = searchFilter('t.login', search);
|
|
return formatResponse(await client.get('/users', params));
|
|
},
|
|
);
|
|
|
|
server.tool(
|
|
'dolibarr_user_get',
|
|
'Get a single user by ID',
|
|
{
|
|
id: z.number().describe('User ID'),
|
|
...ConnectionParam,
|
|
},
|
|
async ({ id, connection }) => {
|
|
const client = clientFor(connection);
|
|
return formatResponse(await client.get(`/users/${id}`));
|
|
},
|
|
);
|
|
|
|
// ── Categories ──────────────────────────────────────────────────────────
|
|
|
|
server.tool(
|
|
'dolibarr_categories_list',
|
|
'List categories',
|
|
{
|
|
type: z.enum(['product', 'supplier', 'customer', 'member', 'contact', 'project']).optional().describe('Category type'),
|
|
search: z.string().optional().describe('Search in label'),
|
|
...PaginationParams,
|
|
...ConnectionParam,
|
|
},
|
|
async ({ type, search, limit, page, sortfield, sortorder, connection }) => {
|
|
const client = clientFor(connection);
|
|
const params: Record<string, string> = { ...paginationQuery({ limit, page, sortfield, sortorder }) };
|
|
if (type) params['type'] = type;
|
|
if (search) params['sqlfilters'] = searchFilter('t.label', search);
|
|
return formatResponse(await client.get('/categories', params));
|
|
},
|
|
);
|
|
|
|
// ── Bank Accounts ───────────────────────────────────────────────────────
|
|
|
|
server.tool(
|
|
'dolibarr_bankaccounts_list',
|
|
'List bank accounts',
|
|
{
|
|
...PaginationParams,
|
|
...ConnectionParam,
|
|
},
|
|
async ({ limit, page, sortfield, sortorder, connection }) => {
|
|
const client = clientFor(connection);
|
|
const params = paginationQuery({ limit, page, sortfield, sortorder });
|
|
return formatResponse(await client.get('/bankaccounts', params));
|
|
},
|
|
);
|
|
|
|
// ── Supplier Invoices ───────────────────────────────────────────────────
|
|
|
|
server.tool(
|
|
'dolibarr_supplier_invoices_list',
|
|
'List supplier invoices',
|
|
{
|
|
status: z.enum(['draft', 'unpaid', 'paid', 'cancelled']).optional().describe('Filter by status'),
|
|
thirdparty_ids: z.string().optional().describe('Comma-separated supplier IDs'),
|
|
...PaginationParams,
|
|
...ConnectionParam,
|
|
},
|
|
async ({ status, thirdparty_ids, limit, page, sortfield, sortorder, connection }) => {
|
|
const client = clientFor(connection);
|
|
const params: Record<string, string> = { ...paginationQuery({ limit, page, sortfield, sortorder }) };
|
|
if (status) params['status'] = status;
|
|
if (thirdparty_ids) params['thirdparty_ids'] = thirdparty_ids;
|
|
return formatResponse(await client.get('/supplierinvoices', params));
|
|
},
|
|
);
|
|
|
|
// ── Supplier Orders ─────────────────────────────────────────────────────
|
|
|
|
server.tool(
|
|
'dolibarr_supplier_orders_list',
|
|
'List supplier orders (purchase orders)',
|
|
{
|
|
status: z.enum(['0', '1', '2', '3', '4', '5', '9']).optional().describe('0=draft, 1=validated, 2=approved, 3=ordered, 4=partially received, 5=received, 9=cancelled'),
|
|
thirdparty_ids: z.string().optional().describe('Comma-separated supplier IDs'),
|
|
...PaginationParams,
|
|
...ConnectionParam,
|
|
},
|
|
async ({ status, thirdparty_ids, limit, page, sortfield, sortorder, connection }) => {
|
|
const client = clientFor(connection);
|
|
const params: Record<string, string> = { ...paginationQuery({ limit, page, sortfield, sortorder }) };
|
|
if (status) params['status'] = status;
|
|
if (thirdparty_ids) params['thirdparty_ids'] = thirdparty_ids;
|
|
return formatResponse(await client.get('/supplierorders', params));
|
|
},
|
|
);
|
|
|
|
// ── Warehouses / Stock ──────────────────────────────────────────────────
|
|
|
|
server.tool(
|
|
'dolibarr_warehouses_list',
|
|
'List warehouses',
|
|
{
|
|
...PaginationParams,
|
|
...ConnectionParam,
|
|
},
|
|
async ({ limit, page, sortfield, sortorder, connection }) => {
|
|
const client = clientFor(connection);
|
|
const params = paginationQuery({ limit, page, sortfield, sortorder });
|
|
return formatResponse(await client.get('/warehouses', params));
|
|
},
|
|
);
|
|
|
|
server.tool(
|
|
'dolibarr_product_stock',
|
|
'Get stock levels for a product across warehouses',
|
|
{
|
|
id: z.number().describe('Product ID'),
|
|
...ConnectionParam,
|
|
},
|
|
async ({ id, connection }) => {
|
|
const client = clientFor(connection);
|
|
return formatResponse(await client.get(`/products/${id}/stock`));
|
|
},
|
|
);
|
|
|
|
// ── Shipments ───────────────────────────────────────────────────────────
|
|
|
|
server.tool(
|
|
'dolibarr_shipments_list',
|
|
'List shipments (expeditions)',
|
|
{
|
|
status: z.enum(['0', '1', '2']).optional().describe('0=draft, 1=validated, 2=closed'),
|
|
thirdparty_ids: z.string().optional().describe('Comma-separated third party IDs'),
|
|
...PaginationParams,
|
|
...ConnectionParam,
|
|
},
|
|
async ({ status, thirdparty_ids, limit, page, sortfield, sortorder, connection }) => {
|
|
const client = clientFor(connection);
|
|
const params: Record<string, string> = { ...paginationQuery({ limit, page, sortfield, sortorder }) };
|
|
if (status) params['status'] = status;
|
|
if (thirdparty_ids) params['thirdparty_ids'] = thirdparty_ids;
|
|
return formatResponse(await client.get('/shipments', params));
|
|
},
|
|
);
|
|
|
|
server.tool(
|
|
'dolibarr_shipment_get',
|
|
'Get a single shipment by ID',
|
|
{
|
|
id: z.number().describe('Shipment ID'),
|
|
...ConnectionParam,
|
|
},
|
|
async ({ id, connection }) => {
|
|
const client = clientFor(connection);
|
|
return formatResponse(await client.get(`/shipments/${id}`));
|
|
},
|
|
);
|
|
|
|
server.tool(
|
|
'dolibarr_shipment_create',
|
|
'Create a new shipment',
|
|
{
|
|
socid: z.number().describe('Third party (customer) ID'),
|
|
origin_id: z.number().optional().describe('Source order ID'),
|
|
origin_type: z.string().optional().describe('Source type (e.g. "commande")'),
|
|
date_delivery: z.string().optional().describe('Delivery date (YYYY-MM-DD or Unix timestamp)'),
|
|
note_public: z.string().optional().describe('Public note'),
|
|
note_private: z.string().optional().describe('Private note'),
|
|
...ConnectionParam,
|
|
},
|
|
async ({ socid, origin_id, origin_type, date_delivery, note_public, note_private, connection }) => {
|
|
const client = clientFor(connection);
|
|
const body: Record<string, unknown> = { socid };
|
|
if (origin_id !== undefined) body.origin_id = origin_id;
|
|
if (origin_type) body.origin_type = origin_type;
|
|
if (date_delivery) body.date_delivery = date_delivery;
|
|
if (note_public) body.note_public = note_public;
|
|
if (note_private) body.note_private = note_private;
|
|
return formatResponse(await client.post('/shipments', body));
|
|
},
|
|
);
|
|
|
|
server.tool(
|
|
'dolibarr_shipment_validate',
|
|
'Validate a draft shipment',
|
|
{
|
|
id: z.number().describe('Shipment ID'),
|
|
...ConnectionParam,
|
|
},
|
|
async ({ id, connection }) => {
|
|
const client = clientFor(connection);
|
|
return formatResponse(await client.post(`/shipments/${id}/validate`, {}));
|
|
},
|
|
);
|
|
|
|
server.tool(
|
|
'dolibarr_shipment_close',
|
|
'Close a shipment (mark as delivered)',
|
|
{
|
|
id: z.number().describe('Shipment ID'),
|
|
...ConnectionParam,
|
|
},
|
|
async ({ id, connection }) => {
|
|
const client = clientFor(connection);
|
|
return formatResponse(await client.post(`/shipments/${id}/close`, {}));
|
|
},
|
|
);
|
|
|
|
// ── Contracts / Subscriptions ───────────────────────────────────────────
|
|
|
|
server.tool(
|
|
'dolibarr_contracts_list',
|
|
'List contracts/subscriptions',
|
|
{
|
|
status: z.enum(['0', '1', '4', '5']).optional().describe('0=draft, 1=validated, 4=closed, 5=running'),
|
|
thirdparty_ids: z.string().optional().describe('Comma-separated third party IDs'),
|
|
...PaginationParams,
|
|
...ConnectionParam,
|
|
},
|
|
async ({ status, thirdparty_ids, limit, page, sortfield, sortorder, connection }) => {
|
|
const client = clientFor(connection);
|
|
const params: Record<string, string> = { ...paginationQuery({ limit, page, sortfield, sortorder }) };
|
|
if (status) params['status'] = status;
|
|
if (thirdparty_ids) params['thirdparty_ids'] = thirdparty_ids;
|
|
return formatResponse(await client.get('/contracts', params));
|
|
},
|
|
);
|
|
|
|
server.tool(
|
|
'dolibarr_contract_get',
|
|
'Get a single contract by ID',
|
|
{
|
|
id: z.number().describe('Contract ID'),
|
|
...ConnectionParam,
|
|
},
|
|
async ({ id, connection }) => {
|
|
const client = clientFor(connection);
|
|
return formatResponse(await client.get(`/contracts/${id}`));
|
|
},
|
|
);
|
|
|
|
server.tool(
|
|
'dolibarr_contract_create',
|
|
'Create a new contract',
|
|
{
|
|
socid: z.number().describe('Third party ID'),
|
|
ref: z.string().optional().describe('Contract reference'),
|
|
date_contrat: z.string().optional().describe('Contract date (YYYY-MM-DD or Unix timestamp)'),
|
|
note_public: z.string().optional().describe('Public note'),
|
|
note_private: z.string().optional().describe('Private note'),
|
|
...ConnectionParam,
|
|
},
|
|
async ({ socid, ref, date_contrat, note_public, note_private, connection }) => {
|
|
const client = clientFor(connection);
|
|
const body: Record<string, unknown> = { socid };
|
|
if (ref) body.ref = ref;
|
|
if (date_contrat) body.date_contrat = date_contrat;
|
|
if (note_public) body.note_public = note_public;
|
|
if (note_private) body.note_private = note_private;
|
|
return formatResponse(await client.post('/contracts', body));
|
|
},
|
|
);
|
|
|
|
server.tool(
|
|
'dolibarr_contract_validate',
|
|
'Validate a draft contract',
|
|
{
|
|
id: z.number().describe('Contract ID'),
|
|
...ConnectionParam,
|
|
},
|
|
async ({ id, connection }) => {
|
|
const client = clientFor(connection);
|
|
return formatResponse(await client.post(`/contracts/${id}/validate`, {}));
|
|
},
|
|
);
|
|
|
|
// ── Interventions ───────────────────────────────────────────────────────
|
|
|
|
server.tool(
|
|
'dolibarr_interventions_list',
|
|
'List interventions (field service)',
|
|
{
|
|
status: z.enum(['0', '1', '2']).optional().describe('0=draft, 1=validated, 2=billed'),
|
|
thirdparty_ids: z.string().optional().describe('Comma-separated third party IDs'),
|
|
...PaginationParams,
|
|
...ConnectionParam,
|
|
},
|
|
async ({ status, thirdparty_ids, limit, page, sortfield, sortorder, connection }) => {
|
|
const client = clientFor(connection);
|
|
const params: Record<string, string> = { ...paginationQuery({ limit, page, sortfield, sortorder }) };
|
|
if (status) params['status'] = status;
|
|
if (thirdparty_ids) params['thirdparty_ids'] = thirdparty_ids;
|
|
return formatResponse(await client.get('/interventions', params));
|
|
},
|
|
);
|
|
|
|
server.tool(
|
|
'dolibarr_intervention_get',
|
|
'Get a single intervention by ID',
|
|
{
|
|
id: z.number().describe('Intervention ID'),
|
|
...ConnectionParam,
|
|
},
|
|
async ({ id, connection }) => {
|
|
const client = clientFor(connection);
|
|
return formatResponse(await client.get(`/interventions/${id}`));
|
|
},
|
|
);
|
|
|
|
// ── Expense Reports ─────────────────────────────────────────────────────
|
|
|
|
server.tool(
|
|
'dolibarr_expensereports_list',
|
|
'List expense reports',
|
|
{
|
|
status: z.enum(['0', '2', '4', '5', '6', '99']).optional().describe('0=draft, 2=validated, 4=cancelled, 5=approved, 6=paid, 99=refused'),
|
|
...PaginationParams,
|
|
...ConnectionParam,
|
|
},
|
|
async ({ status, limit, page, sortfield, sortorder, connection }) => {
|
|
const client = clientFor(connection);
|
|
const params: Record<string, string> = { ...paginationQuery({ limit, page, sortfield, sortorder }) };
|
|
if (status) params['status'] = status;
|
|
return formatResponse(await client.get('/expensereports', params));
|
|
},
|
|
);
|
|
|
|
server.tool(
|
|
'dolibarr_expensereport_get',
|
|
'Get a single expense report by ID',
|
|
{
|
|
id: z.number().describe('Expense report ID'),
|
|
...ConnectionParam,
|
|
},
|
|
async ({ id, connection }) => {
|
|
const client = clientFor(connection);
|
|
return formatResponse(await client.get(`/expensereports/${id}`));
|
|
},
|
|
);
|
|
|
|
server.tool(
|
|
'dolibarr_expensereport_create',
|
|
'Create a new expense report',
|
|
{
|
|
fk_user_author: z.number().describe('User ID of the author'),
|
|
date_debut: z.string().describe('Start date (YYYY-MM-DD or Unix timestamp)'),
|
|
date_fin: z.string().describe('End date (YYYY-MM-DD or Unix timestamp)'),
|
|
note_public: z.string().optional().describe('Public note'),
|
|
note_private: z.string().optional().describe('Private note'),
|
|
...ConnectionParam,
|
|
},
|
|
async ({ fk_user_author, date_debut, date_fin, note_public, note_private, connection }) => {
|
|
const client = clientFor(connection);
|
|
const body: Record<string, unknown> = { fk_user_author, date_debut, date_fin };
|
|
if (note_public) body.note_public = note_public;
|
|
if (note_private) body.note_private = note_private;
|
|
return formatResponse(await client.post('/expensereports', body));
|
|
},
|
|
);
|
|
|
|
// ── Tickets (Helpdesk) ─────────────────────────────────────────────────
|
|
|
|
server.tool(
|
|
'dolibarr_tickets_list',
|
|
'List helpdesk tickets',
|
|
{
|
|
status: z.string().optional().describe('Filter by status'),
|
|
search: z.string().optional().describe('Search in subject'),
|
|
...PaginationParams,
|
|
...ConnectionParam,
|
|
},
|
|
async ({ status, search, limit, page, sortfield, sortorder, connection }) => {
|
|
const client = clientFor(connection);
|
|
const params: Record<string, string> = { ...paginationQuery({ limit, page, sortfield, sortorder }) };
|
|
if (status) params['status'] = status;
|
|
if (search) params['sqlfilters'] = searchFilter('t.subject', search);
|
|
return formatResponse(await client.get('/tickets', params));
|
|
},
|
|
);
|
|
|
|
server.tool(
|
|
'dolibarr_ticket_get',
|
|
'Get a single ticket by ID',
|
|
{
|
|
id: z.number().describe('Ticket ID'),
|
|
...ConnectionParam,
|
|
},
|
|
async ({ id, connection }) => {
|
|
const client = clientFor(connection);
|
|
return formatResponse(await client.get(`/tickets/${id}`));
|
|
},
|
|
);
|
|
|
|
server.tool(
|
|
'dolibarr_ticket_create',
|
|
'Create a new helpdesk ticket',
|
|
{
|
|
subject: z.string().describe('Ticket subject'),
|
|
message: z.string().describe('Ticket message/description'),
|
|
type_code: z.string().optional().describe('Ticket type code'),
|
|
category_code: z.string().optional().describe('Ticket category code'),
|
|
severity_code: z.string().optional().describe('Ticket severity code'),
|
|
socid: z.number().optional().describe('Third party ID'),
|
|
notify_tiers_at_create: z.number().optional().describe('1=notify third party on creation'),
|
|
...ConnectionParam,
|
|
},
|
|
async ({ subject, message, type_code, category_code, severity_code, socid, notify_tiers_at_create, connection }) => {
|
|
const client = clientFor(connection);
|
|
const body: Record<string, unknown> = { subject, message };
|
|
if (type_code) body.type_code = type_code;
|
|
if (category_code) body.category_code = category_code;
|
|
if (severity_code) body.severity_code = severity_code;
|
|
if (socid !== undefined) body.socid = socid;
|
|
if (notify_tiers_at_create !== undefined) body.notify_tiers_at_create = notify_tiers_at_create;
|
|
return formatResponse(await client.post('/tickets', body));
|
|
},
|
|
);
|
|
|
|
// ── Agenda / Events ─────────────────────────────────────────────────────
|
|
|
|
server.tool(
|
|
'dolibarr_agendaevents_list',
|
|
'List agenda events',
|
|
{
|
|
type: z.string().optional().describe('Event type code'),
|
|
status: z.enum(['-1', '0', '50', '100']).optional().describe('-1=cancelled, 0=draft, 50=in progress, 100=done'),
|
|
userassigned: z.number().optional().describe('Filter by assigned user ID'),
|
|
...PaginationParams,
|
|
...ConnectionParam,
|
|
},
|
|
async ({ type, status, userassigned, limit, page, sortfield, sortorder, connection }) => {
|
|
const client = clientFor(connection);
|
|
const params: Record<string, string> = { ...paginationQuery({ limit, page, sortfield, sortorder }) };
|
|
if (type) params['type'] = type;
|
|
if (status) params['status'] = status;
|
|
if (userassigned !== undefined) params['userassigned'] = String(userassigned);
|
|
return formatResponse(await client.get('/agendaevents', params));
|
|
},
|
|
);
|
|
|
|
server.tool(
|
|
'dolibarr_agendaevent_get',
|
|
'Get a single agenda event by ID',
|
|
{
|
|
id: z.number().describe('Event ID'),
|
|
...ConnectionParam,
|
|
},
|
|
async ({ id, connection }) => {
|
|
const client = clientFor(connection);
|
|
return formatResponse(await client.get(`/agendaevents/${id}`));
|
|
},
|
|
);
|
|
|
|
server.tool(
|
|
'dolibarr_agendaevent_create',
|
|
'Create a new agenda event',
|
|
{
|
|
label: z.string().describe('Event label/title'),
|
|
type_code: z.string().describe('Event type code (e.g. "AC_RDV", "AC_TEL", "AC_OTH")'),
|
|
datep: z.string().describe('Start date (YYYY-MM-DD HH:MM:SS or Unix timestamp)'),
|
|
datef: z.string().optional().describe('End date'),
|
|
socid: z.number().optional().describe('Third party ID'),
|
|
contactid: z.number().optional().describe('Contact ID'),
|
|
fk_project: z.number().optional().describe('Project ID'),
|
|
userownerid: z.number().optional().describe('Owner user ID'),
|
|
note: z.string().optional().describe('Event note'),
|
|
percentage: z.enum(['-1', '0', '50', '100']).optional().describe('-1=N/A, 0=todo, 50=in progress, 100=done'),
|
|
...ConnectionParam,
|
|
},
|
|
async ({ label, type_code, datep, datef, socid, contactid, fk_project, userownerid, note, percentage, connection }) => {
|
|
const client = clientFor(connection);
|
|
const body: Record<string, unknown> = { label, type_code, datep };
|
|
if (datef) body.datef = datef;
|
|
if (socid !== undefined) body.socid = socid;
|
|
if (contactid !== undefined) body.contactid = contactid;
|
|
if (fk_project !== undefined) body.fk_project = fk_project;
|
|
if (userownerid !== undefined) body.userownerid = userownerid;
|
|
if (note) body.note = note;
|
|
if (percentage) body.percentage = percentage;
|
|
return formatResponse(await client.post('/agendaevents', body));
|
|
},
|
|
);
|
|
|
|
server.tool(
|
|
'dolibarr_agendaevent_update',
|
|
'Update an agenda event',
|
|
{
|
|
id: z.number().describe('Event ID'),
|
|
label: z.string().optional().describe('Event label'),
|
|
datep: z.string().optional().describe('Start date'),
|
|
datef: z.string().optional().describe('End date'),
|
|
percentage: z.enum(['-1', '0', '50', '100']).optional().describe('Completion: -1=N/A, 0=todo, 50=in progress, 100=done'),
|
|
note: z.string().optional().describe('Event note'),
|
|
...ConnectionParam,
|
|
},
|
|
async ({ id, label, datep, datef, percentage, note, connection }) => {
|
|
const client = clientFor(connection);
|
|
const body: Record<string, unknown> = {};
|
|
if (label !== undefined) body.label = label;
|
|
if (datep !== undefined) body.datep = datep;
|
|
if (datef !== undefined) body.datef = datef;
|
|
if (percentage !== undefined) body.percentage = percentage;
|
|
if (note !== undefined) body.note = note;
|
|
return formatResponse(await client.put(`/agendaevents/${id}`, body));
|
|
},
|
|
);
|
|
|
|
// ── Payments ────────────────────────────────────────────────────────────
|
|
|
|
server.tool(
|
|
'dolibarr_invoice_payments',
|
|
'List payments for an invoice',
|
|
{
|
|
id: z.number().describe('Invoice ID'),
|
|
...ConnectionParam,
|
|
},
|
|
async ({ id, connection }) => {
|
|
const client = clientFor(connection);
|
|
return formatResponse(await client.get(`/invoices/${id}/payments`));
|
|
},
|
|
);
|
|
|
|
server.tool(
|
|
'dolibarr_invoice_add_payment',
|
|
'Add a payment to an invoice',
|
|
{
|
|
id: z.number().describe('Invoice ID'),
|
|
datepaye: z.string().describe('Payment date (YYYY-MM-DD or Unix timestamp)'),
|
|
paymentid: z.number().describe('Payment type ID (e.g. 4=bank transfer, 6=credit card)'),
|
|
closepaidinvoices: z.string().optional().describe('"yes" to auto-close fully paid invoices'),
|
|
accountid: z.number().optional().describe('Bank account ID'),
|
|
num_payment: z.string().optional().describe('Payment number/reference'),
|
|
comment: z.string().optional().describe('Payment comment'),
|
|
...ConnectionParam,
|
|
},
|
|
async ({ id, datepaye, paymentid, closepaidinvoices, accountid, num_payment, comment, connection }) => {
|
|
const client = clientFor(connection);
|
|
const body: Record<string, unknown> = { datepaye, paymentid };
|
|
if (closepaidinvoices) body.closepaidinvoices = closepaidinvoices;
|
|
if (accountid !== undefined) body.accountid = accountid;
|
|
if (num_payment) body.num_payment = num_payment;
|
|
if (comment) body.comment = comment;
|
|
return formatResponse(await client.post(`/invoices/${id}/payments`, body));
|
|
},
|
|
);
|
|
|
|
// ── Documents ───────────────────────────────────────────────────────────
|
|
|
|
server.tool(
|
|
'dolibarr_documents_list',
|
|
'List documents attached to an object',
|
|
{
|
|
modulepart: z.string().describe('Module name (e.g. "facture", "propal", "commande", "societe", "project")'),
|
|
id: z.number().optional().describe('Object ID'),
|
|
ref: z.string().optional().describe('Object ref (alternative to ID)'),
|
|
...ConnectionParam,
|
|
},
|
|
async ({ modulepart, id, ref, connection }) => {
|
|
const client = clientFor(connection);
|
|
const params: Record<string, string> = { modulepart };
|
|
if (id !== undefined) params['id'] = String(id);
|
|
if (ref) params['ref'] = ref;
|
|
return formatResponse(await client.get('/documents', params));
|
|
},
|
|
);
|
|
|
|
server.tool(
|
|
'dolibarr_document_download',
|
|
'Download/get content of a document',
|
|
{
|
|
modulepart: z.string().describe('Module name (e.g. "facture", "propal", "commande")'),
|
|
original_file: z.string().describe('Relative file path (e.g. "FA2301-0001/FA2301-0001.pdf")'),
|
|
...ConnectionParam,
|
|
},
|
|
async ({ modulepart, original_file, connection }) => {
|
|
const client = clientFor(connection);
|
|
return formatResponse(await client.get('/documents/download', { modulepart, original_file }));
|
|
},
|
|
);
|
|
|
|
server.tool(
|
|
'dolibarr_document_builddoc',
|
|
'Generate/build a document (e.g. PDF invoice)',
|
|
{
|
|
modulepart: z.string().describe('Module name (e.g. "facture", "propal", "commande")'),
|
|
original_file: z.string().describe('Expected output filename'),
|
|
doctemplate: z.string().optional().describe('Document template name (e.g. "crabe", "sponge")'),
|
|
langcode: z.string().optional().describe('Language code (e.g. "en_US", "fr_FR")'),
|
|
...ConnectionParam,
|
|
},
|
|
async ({ modulepart, original_file, doctemplate, langcode, connection }) => {
|
|
const client = clientFor(connection);
|
|
const body: Record<string, unknown> = { modulepart, original_file };
|
|
if (doctemplate) body.doctemplate = doctemplate;
|
|
if (langcode) body.langcode = langcode;
|
|
return formatResponse(await client.put('/documents/builddoc', body));
|
|
},
|
|
);
|
|
|
|
// ── Members (Associations) ──────────────────────────────────────────────
|
|
|
|
server.tool(
|
|
'dolibarr_members_list',
|
|
'List association members',
|
|
{
|
|
typeid: z.number().optional().describe('Filter by member type ID'),
|
|
status: z.enum(['0', '1', '-1', '-2']).optional().describe('1=validated, 0=draft, -1=resigned, -2=excluded'),
|
|
search: z.string().optional().describe('Search in name'),
|
|
...PaginationParams,
|
|
...ConnectionParam,
|
|
},
|
|
async ({ typeid, status, search, limit, page, sortfield, sortorder, connection }) => {
|
|
const client = clientFor(connection);
|
|
const params: Record<string, string> = { ...paginationQuery({ limit, page, sortfield, sortorder }) };
|
|
if (typeid !== undefined) params['typeid'] = String(typeid);
|
|
if (status) params['status'] = status;
|
|
if (search) params['sqlfilters'] = searchFilter('t.lastname', search);
|
|
return formatResponse(await client.get('/members', params));
|
|
},
|
|
);
|
|
|
|
server.tool(
|
|
'dolibarr_member_get',
|
|
'Get a single member by ID',
|
|
{
|
|
id: z.number().describe('Member ID'),
|
|
...ConnectionParam,
|
|
},
|
|
async ({ id, connection }) => {
|
|
const client = clientFor(connection);
|
|
return formatResponse(await client.get(`/members/${id}`));
|
|
},
|
|
);
|
|
|
|
// ── Stock Movements ─────────────────────────────────────────────────────
|
|
|
|
server.tool(
|
|
'dolibarr_stockmovements_list',
|
|
'List stock movements',
|
|
{
|
|
product_id: z.number().optional().describe('Filter by product ID'),
|
|
warehouse_id: z.number().optional().describe('Filter by warehouse ID'),
|
|
...PaginationParams,
|
|
...ConnectionParam,
|
|
},
|
|
async ({ product_id, warehouse_id, limit, page, sortfield, sortorder, connection }) => {
|
|
const client = clientFor(connection);
|
|
const params: Record<string, string> = { ...paginationQuery({ limit, page, sortfield, sortorder }) };
|
|
if (product_id !== undefined) params['productidselected'] = String(product_id);
|
|
if (warehouse_id !== undefined) params['warehouseidselected'] = String(warehouse_id);
|
|
return formatResponse(await client.get('/stockmovements', params));
|
|
},
|
|
);
|
|
|
|
server.tool(
|
|
'dolibarr_stockmovement_create',
|
|
'Create a stock movement (add/remove stock)',
|
|
{
|
|
product_id: z.number().describe('Product ID'),
|
|
warehouse_id: z.number().describe('Warehouse ID'),
|
|
qty: z.number().describe('Quantity (positive=add, negative=remove)'),
|
|
type: z.enum(['0', '1', '2', '3']).optional().describe('0=increase, 1=decrease, 2=transfer increase, 3=transfer decrease'),
|
|
label: z.string().optional().describe('Movement label/reason'),
|
|
inventorycode: z.string().optional().describe('Inventory code'),
|
|
...ConnectionParam,
|
|
},
|
|
async ({ product_id, warehouse_id, qty, type, label, inventorycode, connection }) => {
|
|
const client = clientFor(connection);
|
|
const body: Record<string, unknown> = { product_id, warehouse_id, qty };
|
|
if (type !== undefined) body.type = type;
|
|
if (label) body.label = label;
|
|
if (inventorycode) body.inventorycode = inventorycode;
|
|
return formatResponse(await client.post('/stockmovements', body));
|
|
},
|
|
);
|
|
|
|
// ── Contact CRUD ────────────────────────────────────────────────────────
|
|
|
|
server.tool(
|
|
'dolibarr_contact_create',
|
|
'Create a new contact/address',
|
|
{
|
|
lastname: z.string().describe('Last name'),
|
|
firstname: z.string().optional().describe('First name'),
|
|
socid: z.number().optional().describe('Third party ID'),
|
|
poste: z.string().optional().describe('Job title/position'),
|
|
email: z.string().optional().describe('Email address'),
|
|
phone_pro: z.string().optional().describe('Professional phone'),
|
|
phone_mobile: z.string().optional().describe('Mobile phone'),
|
|
address: z.string().optional().describe('Street address'),
|
|
zip: z.string().optional().describe('Postal code'),
|
|
town: z.string().optional().describe('City'),
|
|
country_id: z.number().optional().describe('Country ID'),
|
|
...ConnectionParam,
|
|
},
|
|
async ({ lastname, firstname, socid, poste, email, phone_pro, phone_mobile, address, zip, town, country_id, connection }) => {
|
|
const client = clientFor(connection);
|
|
const body: Record<string, unknown> = { lastname };
|
|
if (firstname) body.firstname = firstname;
|
|
if (socid !== undefined) body.socid = socid;
|
|
if (poste) body.poste = poste;
|
|
if (email) body.email = email;
|
|
if (phone_pro) body.phone_pro = phone_pro;
|
|
if (phone_mobile) body.phone_mobile = phone_mobile;
|
|
if (address) body.address = address;
|
|
if (zip) body.zip = zip;
|
|
if (town) body.town = town;
|
|
if (country_id !== undefined) body.country_id = country_id;
|
|
return formatResponse(await client.post('/contacts', body));
|
|
},
|
|
);
|
|
|
|
server.tool(
|
|
'dolibarr_contact_update',
|
|
'Update a contact/address',
|
|
{
|
|
id: z.number().describe('Contact ID'),
|
|
lastname: z.string().optional().describe('Last name'),
|
|
firstname: z.string().optional().describe('First name'),
|
|
email: z.string().optional().describe('Email address'),
|
|
phone_pro: z.string().optional().describe('Professional phone'),
|
|
phone_mobile: z.string().optional().describe('Mobile phone'),
|
|
poste: z.string().optional().describe('Job title/position'),
|
|
...ConnectionParam,
|
|
},
|
|
async ({ id, lastname, firstname, email, phone_pro, phone_mobile, poste, connection }) => {
|
|
const client = clientFor(connection);
|
|
const body: Record<string, unknown> = {};
|
|
if (lastname !== undefined) body.lastname = lastname;
|
|
if (firstname !== undefined) body.firstname = firstname;
|
|
if (email !== undefined) body.email = email;
|
|
if (phone_pro !== undefined) body.phone_pro = phone_pro;
|
|
if (phone_mobile !== undefined) body.phone_mobile = phone_mobile;
|
|
if (poste !== undefined) body.poste = poste;
|
|
return formatResponse(await client.put(`/contacts/${id}`, body));
|
|
},
|
|
);
|
|
|
|
server.tool(
|
|
'dolibarr_contact_delete',
|
|
'Delete a contact/address',
|
|
{
|
|
id: z.number().describe('Contact ID'),
|
|
...ConnectionParam,
|
|
},
|
|
async ({ id, connection }) => {
|
|
const client = clientFor(connection);
|
|
return formatResponse(await client.delete(`/contacts/${id}`));
|
|
},
|
|
);
|
|
|
|
// ── Order Lines ─────────────────────────────────────────────────────────
|
|
|
|
server.tool(
|
|
'dolibarr_order_add_line',
|
|
'Add a line to a customer order',
|
|
{
|
|
id: z.number().describe('Order ID'),
|
|
desc: z.string().describe('Line description'),
|
|
subprice: z.number().describe('Unit price (HT)'),
|
|
qty: z.number().describe('Quantity'),
|
|
tva_tx: z.number().optional().describe('VAT rate (e.g. 20.0)'),
|
|
product_id: z.number().optional().describe('Product/service ID'),
|
|
...ConnectionParam,
|
|
},
|
|
async ({ id, desc, subprice, qty, tva_tx, product_id, connection }) => {
|
|
const client = clientFor(connection);
|
|
const body: Record<string, unknown> = { desc, subprice, qty };
|
|
if (tva_tx !== undefined) body.tva_tx = tva_tx;
|
|
if (product_id !== undefined) body.fk_product = product_id;
|
|
return formatResponse(await client.post(`/orders/${id}/lines`, body));
|
|
},
|
|
);
|
|
|
|
// ── Project Tasks CRUD ──────────────────────────────────────────────────
|
|
|
|
server.tool(
|
|
'dolibarr_task_create',
|
|
'Create a new project task',
|
|
{
|
|
fk_project: z.number().describe('Project ID'),
|
|
label: z.string().describe('Task label'),
|
|
description: z.string().optional().describe('Task description'),
|
|
date_start: z.string().optional().describe('Start date (YYYY-MM-DD or Unix timestamp)'),
|
|
date_end: z.string().optional().describe('End date'),
|
|
planned_workload: z.number().optional().describe('Planned workload in seconds'),
|
|
progress: z.number().optional().describe('Progress percentage (0-100)'),
|
|
...ConnectionParam,
|
|
},
|
|
async ({ fk_project, label, description, date_start, date_end, planned_workload, progress, connection }) => {
|
|
const client = clientFor(connection);
|
|
const body: Record<string, unknown> = { fk_project, label };
|
|
if (description) body.description = description;
|
|
if (date_start) body.date_start = date_start;
|
|
if (date_end) body.date_end = date_end;
|
|
if (planned_workload !== undefined) body.planned_workload = planned_workload;
|
|
if (progress !== undefined) body.progress = progress;
|
|
return formatResponse(await client.post('/tasks', body));
|
|
},
|
|
);
|
|
|
|
server.tool(
|
|
'dolibarr_task_update',
|
|
'Update a project task',
|
|
{
|
|
id: z.number().describe('Task ID'),
|
|
label: z.string().optional().describe('Task label'),
|
|
description: z.string().optional().describe('Task description'),
|
|
date_start: z.string().optional().describe('Start date'),
|
|
date_end: z.string().optional().describe('End date'),
|
|
progress: z.number().optional().describe('Progress percentage (0-100)'),
|
|
...ConnectionParam,
|
|
},
|
|
async ({ id, label, description, date_start, date_end, progress, connection }) => {
|
|
const client = clientFor(connection);
|
|
const body: Record<string, unknown> = {};
|
|
if (label !== undefined) body.label = label;
|
|
if (description !== undefined) body.description = description;
|
|
if (date_start !== undefined) body.date_start = date_start;
|
|
if (date_end !== undefined) body.date_end = date_end;
|
|
if (progress !== undefined) body.progress = progress;
|
|
return formatResponse(await client.put(`/tasks/${id}`, body));
|
|
},
|
|
);
|
|
|
|
server.tool(
|
|
'dolibarr_task_timespent_list',
|
|
'List time spent entries for a task',
|
|
{
|
|
id: z.number().describe('Task ID'),
|
|
...ConnectionParam,
|
|
},
|
|
async ({ id, connection }) => {
|
|
const client = clientFor(connection);
|
|
return formatResponse(await client.get(`/tasks/${id}/timespent`));
|
|
},
|
|
);
|
|
|
|
server.tool(
|
|
'dolibarr_task_timespent_add',
|
|
'Add time spent on a task',
|
|
{
|
|
id: z.number().describe('Task ID'),
|
|
date: z.string().describe('Date of work (YYYY-MM-DD or Unix timestamp)'),
|
|
duration: z.number().describe('Duration in seconds'),
|
|
fk_user: z.number().describe('User ID who did the work'),
|
|
note: z.string().optional().describe('Note about the work done'),
|
|
...ConnectionParam,
|
|
},
|
|
async ({ id, date, duration, fk_user, note, connection }) => {
|
|
const client = clientFor(connection);
|
|
const body: Record<string, unknown> = { date, duration, fk_user };
|
|
if (note) body.note = note;
|
|
return formatResponse(await client.post(`/tasks/${id}/timespent`, body));
|
|
},
|
|
);
|
|
|
|
// ── Project Update ──────────────────────────────────────────────────────
|
|
|
|
server.tool(
|
|
'dolibarr_project_update',
|
|
'Update a project',
|
|
{
|
|
id: z.number().describe('Project ID'),
|
|
title: z.string().optional().describe('Project title'),
|
|
description: z.string().optional().describe('Description'),
|
|
date_start: z.string().optional().describe('Start date'),
|
|
date_end: z.string().optional().describe('End date'),
|
|
status: z.enum(['0', '1', '2']).optional().describe('0=draft, 1=open, 2=closed'),
|
|
...ConnectionParam,
|
|
},
|
|
async ({ id, title, description, date_start, date_end, status, connection }) => {
|
|
const client = clientFor(connection);
|
|
const body: Record<string, unknown> = {};
|
|
if (title !== undefined) body.title = title;
|
|
if (description !== undefined) body.description = description;
|
|
if (date_start !== undefined) body.date_start = date_start;
|
|
if (date_end !== undefined) body.date_end = date_end;
|
|
if (status !== undefined) body.status = status;
|
|
return formatResponse(await client.put(`/projects/${id}`, body));
|
|
},
|
|
);
|
|
|
|
// ── Dictionary / Setup ──────────────────────────────────────────────────
|
|
|
|
server.tool(
|
|
'dolibarr_setup_dictionary',
|
|
'Get dictionary entries (countries, currencies, payment types, etc.)',
|
|
{
|
|
type: z.enum([
|
|
'countries', 'regions', 'states', 'currencies', 'civilities',
|
|
'payment_types', 'shipping_methods', 'availability',
|
|
'order_methods', 'event_types', 'expense_report_types',
|
|
'ticket_types', 'ticket_severities', 'ticket_categories',
|
|
'units', 'legal_form', 'staff_range', 'typent',
|
|
]).describe('Dictionary type to retrieve'),
|
|
...PaginationParams,
|
|
...ConnectionParam,
|
|
},
|
|
async ({ type, limit, page, sortfield, sortorder, connection }) => {
|
|
const client = clientFor(connection);
|
|
const params = paginationQuery({ limit, page, sortfield, sortorder });
|
|
return formatResponse(await client.get(`/setup/dictionary/${type}`, params));
|
|
},
|
|
);
|
|
|
|
server.tool(
|
|
'dolibarr_setup_modules',
|
|
'List enabled Dolibarr modules',
|
|
{ ...ConnectionParam },
|
|
async ({ connection }) => {
|
|
const client = clientFor(connection);
|
|
return formatResponse(await client.get('/setup/modules'));
|
|
},
|
|
);
|
|
|
|
// ── Bank Account Transactions ───────────────────────────────────────────
|
|
|
|
server.tool(
|
|
'dolibarr_bankaccount_lines',
|
|
'List transactions/lines for a bank account',
|
|
{
|
|
id: z.number().describe('Bank account ID'),
|
|
...PaginationParams,
|
|
...ConnectionParam,
|
|
},
|
|
async ({ id, limit, page, sortfield, sortorder, connection }) => {
|
|
const client = clientFor(connection);
|
|
const params = paginationQuery({ limit, page, sortfield, sortorder });
|
|
return formatResponse(await client.get(`/bankaccounts/${id}/lines`, params));
|
|
},
|
|
);
|
|
|
|
// ── Category CRUD ───────────────────────────────────────────────────────
|
|
|
|
server.tool(
|
|
'dolibarr_category_get',
|
|
'Get a single category by ID',
|
|
{
|
|
id: z.number().describe('Category ID'),
|
|
...ConnectionParam,
|
|
},
|
|
async ({ id, connection }) => {
|
|
const client = clientFor(connection);
|
|
return formatResponse(await client.get(`/categories/${id}`));
|
|
},
|
|
);
|
|
|
|
server.tool(
|
|
'dolibarr_category_create',
|
|
'Create a new category',
|
|
{
|
|
label: z.string().describe('Category label'),
|
|
type: z.enum(['product', 'supplier', 'customer', 'member', 'contact', 'project']).describe('Category type'),
|
|
fk_parent: z.number().optional().describe('Parent category ID (0 = root)'),
|
|
description: z.string().optional().describe('Category description'),
|
|
color: z.string().optional().describe('Category color (hex, e.g. "ff0000")'),
|
|
...ConnectionParam,
|
|
},
|
|
async ({ label, type, fk_parent, description, color, connection }) => {
|
|
const client = clientFor(connection);
|
|
const body: Record<string, unknown> = { label, type };
|
|
if (fk_parent !== undefined) body.fk_parent = fk_parent;
|
|
if (description) body.description = description;
|
|
if (color) body.color = color;
|
|
return formatResponse(await client.post('/categories', body));
|
|
},
|
|
);
|
|
|
|
// ── User CRUD ───────────────────────────────────────────────────────────
|
|
|
|
server.tool(
|
|
'dolibarr_user_create',
|
|
'Create a new Dolibarr user',
|
|
{
|
|
login: z.string().describe('Login/username'),
|
|
lastname: z.string().describe('Last name'),
|
|
firstname: z.string().optional().describe('First name'),
|
|
email: z.string().optional().describe('Email address'),
|
|
admin: z.enum(['0', '1']).optional().describe('0=user, 1=admin'),
|
|
employee: z.enum(['0', '1']).optional().describe('1=is employee'),
|
|
...ConnectionParam,
|
|
},
|
|
async ({ login, lastname, firstname, email, admin, employee, connection }) => {
|
|
const client = clientFor(connection);
|
|
const body: Record<string, unknown> = { login, lastname };
|
|
if (firstname) body.firstname = firstname;
|
|
if (email) body.email = email;
|
|
if (admin) body.admin = admin;
|
|
if (employee) body.employee = employee;
|
|
return formatResponse(await client.post('/users', body));
|
|
},
|
|
);
|
|
|
|
// ── Setup / Dictionary ──────────────────────────────────────────────────
|
|
|
|
server.tool(
|
|
'dolibarr_setup_company',
|
|
'Get company/entity information',
|
|
{ ...ConnectionParam },
|
|
async ({ connection }) => {
|
|
const client = clientFor(connection);
|
|
return formatResponse(await client.get('/setup/company'));
|
|
},
|
|
);
|
|
|
|
server.tool(
|
|
'dolibarr_status',
|
|
'Get Dolibarr instance status and version',
|
|
{ ...ConnectionParam },
|
|
async ({ connection }) => {
|
|
const client = clientFor(connection);
|
|
return formatResponse(await client.get('/status'));
|
|
},
|
|
);
|
|
|
|
// ── Generic API Call ────────────────────────────────────────────────────
|
|
|
|
server.tool(
|
|
'dolibarr_api_request',
|
|
'Make a raw API request to any Dolibarr REST endpoint',
|
|
{
|
|
method: z.enum(['GET', 'POST', 'PUT', 'DELETE']).describe('HTTP method'),
|
|
endpoint: z.string().describe('API endpoint path (e.g. "/thirdparties")'),
|
|
body: z.record(z.string(), z.unknown()).optional().describe('Request body for POST/PUT'),
|
|
params: z.record(z.string(), z.string()).optional().describe('Query parameters'),
|
|
...ConnectionParam,
|
|
},
|
|
async ({ method, endpoint, body, params, connection }) => {
|
|
const client = clientFor(connection);
|
|
switch (method) {
|
|
case 'GET':
|
|
return formatResponse(await client.get(endpoint, params));
|
|
case 'POST':
|
|
return formatResponse(await client.post(endpoint, body));
|
|
case 'PUT':
|
|
return formatResponse(await client.put(endpoint, body));
|
|
case 'DELETE':
|
|
return formatResponse(await client.delete(endpoint));
|
|
}
|
|
},
|
|
);
|
|
|
|
// ── Connections Management ──────────────────────────────────────────────
|
|
|
|
server.tool(
|
|
'dolibarr_list_connections',
|
|
'List configured Dolibarr API connections',
|
|
{},
|
|
async () => {
|
|
const lines = Object.entries(config.connections).map(([name, conn]) => {
|
|
const is_default = name === config.defaultConnection ? ' (default)' : '';
|
|
return ` ${name}${is_default}: ${conn.baseUrl}`;
|
|
});
|
|
return {
|
|
content: [{ type: 'text' as const, text: `Configured connections:\n${lines.join('\n')}` }],
|
|
};
|
|
},
|
|
);
|
|
|
|
// ── Start Server ────────────────────────────────────────────────────────
|
|
|
|
async function main(): Promise<void> {
|
|
config = await loadConfig();
|
|
const transport = new StdioServerTransport();
|
|
await server.connect(transport);
|
|
}
|
|
|
|
main().catch((err) => {
|
|
process.stderr.write(`Fatal: ${err}\n`);
|
|
process.exit(1);
|
|
});
|