
MCP Holded
io.github.t4dhg由 GitHub 账号 t4dhg 发布v3.0.0更新于 Nov 5, 2026
MCP server for Holded (Spanish ERP): invoicing, accounting, treasury, CRM, projects, payroll.
概览
面向 Holded(西班牙 ERP)v2 API 的 MCP 服务器,让助手读取和管理联系人、发票、采购、付款、资金、会计与商品目录数据。
- 可以做什么
- 联系人:列出、搜索、获取、创建、更新、删除、批量归档或删除,管理附件与分组
- 销售与采购单据:列出、获取、按编号查找、下载 PDF、创建、更新、审批、取消、发送、登记付款、转换
- 付款与资金:列出和管理付款与付款方式、银行账户、流水、对账、汇款与预测
- 会计:会计科目表、分录、税项、费用科目、销售渠道、编号序列与标签
- 商品目录:产品、服务、仓库、库存、价格表与生产订单
- 另提供七个资源(API 行为指南与账户参考)和六个只读工作流提示,如应收账款账龄、增值税汇总与月末结账
- 适用场景
- 适合需要操作西班牙公司 Holded 账户的场景:催收未付发票、核对某月银行流水、准备季度增值税数据、月末结账,或起草和管理发票、联系人与库存。
- 运行要求
- 通过 npx 在本地运行,需要 Node.js 20 或更高版本。需要 Holded v2 个人访问令牌,放在 HOLDED_API_KEY 中,在设置、开发者、API 下创建;v1 密钥会在启动时被拒绝。可选 HOLDED_BASE_URL、HOLDED_TIMEOUT_MS、HOLDED_MAX_RETRIES 和 DEBUG。需要访问 Holded API 的网络连接。
安装
在 SourceWeft 中
- 打开 控制台中的 MCP Holded,将其添加到工作区。
- 为需要使用其工具的对话启用该服务。
Desktop only,通过 STDIO。 STDIO 服务会启动本地进程,因此需要 SourceWeft 桌面宿主。
其他 MCP 客户端
参照 仓库 中的启动说明。
README
MCP Holded
Model Context Protocol server for Holded, on the v2 API
[License: MIT] [CI] [codecov] [TypeScript] [Node.js] [npm version] [npm downloads] [MCP Compatible]
A community project. Not affiliated with, endorsed by, or supported by Holded. "Holded" and the Holded logo are trademarks of their owner, used here to identify the API this server speaks to.
An MCP server for the Holded API v2: contacts, sales and purchase documents, payments, treasury, accounting, catalog, team, CRM, projects, calendar, inbox and webhooks. Thirteen hierarchical tools with an action parameter, Zod validation of every request and response, retries on reads, and a confirmation gate on every destructive operation.
Holded deprecated its classic API (/api/invoicing/v1, key header). New keys use the format pat_<id>_<secret> and only authenticate against v2 (https://api.holded.com/api/v2, Authorization: Bearer). This server speaks v2 only.
Why this one
- v2 only, which is the API that still works. Holded deprecated the classic API (
/api/invoicing/v1,keyheader). New tokens arepat_<id>_<secret>and authenticate only against v2. This server speaks v2 and nothing else. - Thirteen hierarchical tools with an
actionparameter, not one tool per endpoint, so the tool list stays small in a model's context while covering contacts, sales, purchases, payments, treasury, accounting, catalog, team, CRM, projects, calendar, inbox and webhooks. - Seven MCP resources and six MCP prompts. The resources carry the account's own tax keys, numbering series, payment methods and chart of accounts, plus the API behaviour a schema cannot express. The prompts run the real workflows: aged receivables, VAT summary, bank reconciliation, statements, month-end close.
- A confirmation gate on every destructive operation, with a structural test that keeps the documented list equal to the code. Nothing that deletes, overwrites, approves or emails goes through without
confirm: true. - Zod validation of every request and every response. A shape the server has not seen surfaces as a clear schema error rather than a silent success.
- Over 200 tests against anonymised real v2 responses, npm provenance via trusted publishing, and Node 20 or later.
How it compares
Four MCP servers for Holded exist on npm. This compares the published code of each, not their descriptions, as at 2026-09-08: @t4dhg/mcp-holded 2.1.1, @nubiia/mcp-holded 2.0.2, @energio/holded-mcp 1.5.1 and holded-mcp 1.0.0. It is a snapshot and will age; check the current versions yourself before relying on it.
Where this differs most: the v1 servers are on an API Holded has deprecated, and new pat_ tokens do not authenticate against it at all, so they cannot work with a newly issued key. Against @nubiia/mcp-holded, which is current, the differences are breadth (this server covers calendar and bookings, inbox, webhooks, and CRM leads and funnels, none of which appear in nubiia's tool list), the resource and prompt surface, the hierarchical tool shape, and a gated raw-request escape hatch. Nubiia's own Node floor is 22.14, genuinely higher than the 18 this server and holded-mcp both support.
Quick start
Install nothing. Every client below runs the server through npx. Create the token in Holded under Settings, Developers, API and use the full pat_..._... value.
Claude Desktop
Edit ~/Library/Application Support/Claude/claude_desktop_config.json on macOS, or %APPDATA%\Claude\claude_desktop_config.json on Windows:
Claude Code
Cursor
Edit ~/.cursor/mcp.json for every project, or .cursor/mcp.json for one:
VS Code
Edit .vscode/mcp.json. Note the key is servers, not mcpServers:
Requires Node.js 20 or later. Development uses Node 24 (nvm use).
Typed request collections accept at most 1,000 items, including nested collections. Existing lower API limits still apply (bank movement batches accept at most 100). A tool call may contain at most 10,000 combined array elements and object members, including arbitrary JSON payloads. Split larger operations into smaller calls.
Environment variables
What people use it for
Chasing unpaid invoices. Run the holded_aged_receivables prompt. You get every outstanding invoice bucketed by how late it is and grouped by customer, worst first, counting only the unpaid remainder of part-paid invoices. Then ask for a statement for the worst offender with holded_contact_statement, in a form you can send them.
Reconciling a bank month. Run holded_bank_reconciliation for the period. It matches bank movements against recorded payments on amount and date, then shows you the two lists that matter: movements with no payment recorded, and payments the bank has not shown. Reconciling stays behind a confirmation, so nothing is matched in your books until you approve it.
Preparing the quarterly VAT figures. Run holded_vat_summary. It groups sales and purchases by tax key for the quarter and gives base and quota for each, which is the shape Modelo 303 wants. It is a working aid, not a filing, and it says so.
Closing a month. Run holded_month_end_close. It tells you what is still in draft, what is past due, and what is sitting in the inbox, each with the exact call that clears it.
Tools
Every tool takes an action. List actions accept limit (1 to 200) and cursor; the response says how to continue. Create and update actions take the payload in data, following the Holded v2 request contract, and return the record re-read from the API. Gated actions take confirm: true.
Example
Resources
Seven MCP resources give a model the context it needs before it can make a correct call. Attach them in your client, or read them by URI.
The three guides are static. The four references read the account's own configuration and are cached for fifteen minutes, because clients re-read resources at the start of every conversation. A reference whose read fails returns an explanation and the tool to fall back to; it never throws.
Prompts
Six MCP prompts run the workflows this server exists for. Each one pre-reads the facts and hands back a report plus the exact tool calls to act on it. None of them writes. Where a change is needed, the prompt emits the gated call for you to approve.
Safety
Operations that require confirmation
These actions delete, overwrite, lock or email something. Without confirm: true the tool sends nothing and explains what would happen; the same call with confirm: true proceeds. A structural test keeps this list equal to the gated actions in the code.
holded_contacts({ action: 'update' }),holded_contacts({ action: 'delete' }),holded_contacts({ action: 'bulk_archive' }),holded_contacts({ action: 'bulk_delete' }),holded_contacts({ action: 'update_group' }),holded_contacts({ action: 'delete_group' })holded_sales({ action: 'update' }),holded_sales({ action: 'delete' }),holded_sales({ action: 'approve' })(assigns the legal number; irreversible under Verifactu),holded_sales({ action: 'cancel' }),holded_sales({ action: 'send' })(emails the customer),holded_sales({ action: 'set_pipeline' }),holded_sales({ action: 'skip' }),holded_sales({ action: 'bulk_approve' }),holded_sales({ action: 'bulk_cancel' }),holded_sales({ action: 'bulk_delete' })holded_purchases({ action: 'update' }),holded_purchases({ action: 'delete' }),holded_purchases({ action: 'approve' }),holded_purchases({ action: 'send' }),holded_purchases({ action: 'set_pipeline' })holded_payments({ action: 'update' }),holded_payments({ action: 'delete' })holded_treasury({ action: 'update_account' }),holded_treasury({ action: 'archive_account' }),holded_treasury({ action: 'delete_account' }),holded_treasury({ action: 'reconcile' }),holded_treasury({ action: 'update_forecast' }),holded_treasury({ action: 'delete_forecast' })holded_accounting({ action: 'update_expenses_account' }),holded_accounting({ action: 'delete_expenses_account' }),holded_accounting({ action: 'update_sales_channel' }),holded_accounting({ action: 'delete_sales_channel' }),holded_accounting({ action: 'update_numbering_series' }),holded_accounting({ action: 'delete_numbering_series' }),holded_accounting({ action: 'delete_tag' })holded_catalog({ action: 'update_product' }),holded_catalog({ action: 'update_stock' }),holded_catalog({ action: 'delete_product' }),holded_catalog({ action: 'update_service' }),holded_catalog({ action: 'delete_service' }),holded_catalog({ action: 'update_warehouse' }),holded_catalog({ action: 'delete_warehouse' }),holded_catalog({ action: 'update_price_list' }),holded_catalog({ action: 'delete_price_list' }),holded_catalog({ action: 'update_production_order' }),holded_catalog({ action: 'delete_production_order' })holded_team({ action: 'delete_payslip_payment' })holded_crm({ action: 'update_funnel' }),holded_crm({ action: 'delete_funnel' }),holded_crm({ action: 'update_lead' }),holded_crm({ action: 'delete_lead' }),holded_crm({ action: 'move_stage' }),holded_crm({ action: 'update_dates' }),holded_crm({ action: 'update_note' }),holded_crm({ action: 'update_task' }),holded_crm({ action: 'delete_task' })holded_projects({ action: 'update_project' }),holded_projects({ action: 'delete_project' }),holded_projects({ action: 'update_task' }),holded_projects({ action: 'delete_task' }),holded_projects({ action: 'update_time' }),holded_projects({ action: 'delete_time' })holded_calendar({ action: 'update_event' }),holded_calendar({ action: 'delete_event' }),holded_calendar({ action: 'update_booking' }),holded_calendar({ action: 'cancel_booking' })holded_inbox({ action: 'update' }),holded_inbox({ action: 'attach' }),holded_inbox({ action: 'delete' })holded_webhooks({ action: 'update' }),holded_webhooks({ action: 'disable' }),holded_webhooks({ action: 'delete' })
Creates are not gated, including record_payment, create_movements and create_ledger_entry: they add records that can be deleted afterwards. The gate reduces accidents; it is not an authorization boundary, and the token grants whatever Holded grants it.
The raw request tool
holded_request reaches every v2 endpoint with correct Bearer auth and returns the response unvalidated. DELETE, PUT and PATCH require confirm: true, and so does a POST to any path containing bulk, cancel, archive, approve, send, reconcile, ship, skip, clock-in or clock-out, because Holded hides destructive operations behind those. The refused call returns the exact method, URL and body it would have sent.
How the API really behaves
Everything below was verified against live v2 responses on 2026-09-06 and is what the schemas are built from.
- Pagination is cursor based. Lists return
{ items, cursor, has_more };cursoris the last item id and goes back as?cursor=.limitdefaults to 50 and is capped at 200;pageandoffsetare ignored. Configuration lists (taxes, tags, warehouses, accounting accounts, expenses accounts, sales channels, numbering series, price lists, booking locations) return{ items }with no cursor. - Amounts are strings, with two decimal conventions. Sales and purchase documents, their lines and salary records use a comma ("63500,00"). Payments, treasury accounts, bank movements, accounting accounts and ledger entries use a dot ("8.3", "0.00"). The server passes them through unchanged and never converts, so nothing is rounded.
- Ledger dates are
DD/MM/YYYY. Everything else is ISO 8601. - Errors are RFC 7807 (
{ type, title, status, detail }) for 400, 403 and 404. A bad or missing key gives 403 "Access denied", not 401. Unknown routes give 404{ message: "No route found..." }. Some malformed paths return Holded's HTML app shell with a 200 or 405; the server turns that into an error instead of returning HTML. - Undocumented query parameters are silently ignored.
query,qandsearchon/contactsfilter nothing; usesearch(which calls/contacts/search?name=) or the exact-match filters. - Get-by-id returns more than list for documents:
accounting_date,approved_at,notes,language,payments_detail,payments_refunds,shipping,design_id,pipeline_idand more. - Not every verb exists for every document type. Purchase refunds are listed at
/purchase-refundsbut read and created under/purchases/refund, and offer nothing else (no update, delete, pdf, attachments or approve). Purchases have no pdf or send route. Recurring invoices have update, delete, skip and schedule only. Estimates, proformas, orders and waybills have no payments. The tools refuse an unsupported combination with a message listing what is available, before sending anything. - Numbering series types are invoice, purchase, estimate, creditnote, salesorder, waybill, salesreceipt and purchaserefund.
- Rate limit is 100 requests per minute per key. GETs retry on 429 (honouring
Retry-After), 5xx, timeouts and network errors with exponential backoff and jitter. Writes are sent exactly once because Holded has no idempotency key. - Creates return
{ id }with a 201. The server re-reads the record so the tool returns the validated object. - Schemas for CRM leads, projects, tasks, events, bookings, webhooks, payslips, price lists, production orders, sales orders, sales receipts, receipt notes, purchase shipments and remittances come from the OpenAPI document rather than live records, because the account used for verification has none. They are loose objects, so extra fields pass through, and any mismatch surfaces as a clear schema error rather than a silent success.
Troubleshooting
The server exits immediately with a message about the key
HOLDED_API_KEY must be a v2 Personal Access Token, in the form pat_<id>_<secret>. The server checks this at startup and refuses to run on anything else, rather than failing later on every call. A classic v1 key is 32 hex characters and does not authenticate against v2 at all.
Everything returns 403 "Access denied"
That is what a bad, revoked or wrong-scope token looks like on v2. Holded returns 403, not 401, so it reads like a permissions problem when it is usually an authentication one. Regenerate the token in Holded under Settings, Developers, API, and check you copied the whole pat_..._... value including both underscores.
A tool says an action is not available for that document type
Not every verb exists for every type. Purchase refunds have no update, delete, pdf, attachment or approve. Purchases have no pdf or send. Recurring invoices have only update, delete, skip and schedule. Estimates, proformas, sales orders and waybills have no payments. The tool refuses before sending anything and lists what is available. The full matrix is in the holded://guide/document-types resource.
A filter seems to be ignored
It probably is. Holded silently ignores undocumented query parameters: query, q and search on /contacts filter nothing and return everything. Use the search action, which calls /contacts/search?name=, or the exact-match filters email, code, phone, mobile and custom_id. page and offset are ignored everywhere; pagination is by cursor.
A resource says it could not be read
The four holded://reference/... resources read live configuration, so they need a working token. They return an explanation rather than throwing, so the server keeps working. The three holded://guide/... resources are static and always readable.
Set DEBUG=true
Every request is then logged to stderr. Never to stdout, which is the MCP transport.
FAQ
Does it work with the old v1 API key? No, deliberately. v1 is deprecated and new keys do not authenticate against it. Supporting both would mean two code paths where one is a dead end.
Can it delete things by accident? Every operation that deletes, overwrites, approves, cancels or emails requires confirm: true. Without it the tool sends nothing and tells you what it would have done. The list is generated from the code into holded://guide/irreversible-operations, and a test fails if the documented list and the code disagree.
What happens if a write times out? It is not retried. Holded has no idempotency key, so a retried write could double-charge or double-issue. Reads retry with backoff; writes are sent exactly once. If a write times out, read the record before trying again.
Why does approving an invoice need confirmation? It assigns the legal invoice number. Under Verifactu that is part of an immutable chained record and cannot be undone: a mistake needs a credit note, not an edit.
Are amounts safe to round-trip? Yes. Amounts are strings and the server passes them through unchanged, so nothing is rounded. Be aware there are two conventions: documents and salary records use a comma, payments, treasury and accounting use a dot.
Can I reach an endpoint that has no dedicated action? Yes, holded_request reaches any v2 path with correct auth. Destructive methods and paths are gated there too.
Is it affiliated with Holded? No. It is a community project that speaks their public API.
Development
Tests run against anonymised fixtures in src/__tests__/fixtures, each a real v2 response with names, tax ids, addresses, bank details and amounts replaced. Add a fixture when you add an endpoint; the schema test fails on any fixture without a schema.
Releasing
Releases are staged by CI and promoted by a human. Bump package.json, commit, tag vX.Y.Z and push the tag. The publish workflow authenticates to npm over OIDC as a trusted publisher (no token anywhere), runs tests and build, and runs npm stage publish. Nothing is installable until a maintainer runs npm stage approve <stage-id> locally, which is where 2FA is proved. See CONTRIBUTING.md for the exact steps.
Security
Report vulnerabilities through GitHub Security Advisories; see SECURITY.md.
License
MIT
来源:README.md,提交 08b13d9
工具
0版本历史
2- v3.0.0最新Nov 5, 2026
- v2.1.1Sep 16, 2026
评分
已满足适用检查项的 50%
该评分衡量此收录条目的完整程度,依据注册中心条目、端点与公开来源计算得出。它不是对服务器本身的评价,也不表示服务器安全或当前可用:可连通性是带日期的检查结果,评分也不包含安装量或用户评价。
- 近 30 天内可连通25 分
请保持端点能够响应 MCP 握手;SourceWeft 每 30 天探测一次。
- 已声明认证方式10 分
请在注册中心条目中声明凭据请求头,或保持端点能够响应探测,以便判定其认证方式。
- 已列出工具15 分
请让服务器在无凭据时响应 tools/list,或发布可在沙箱中无凭据启动的软件包。
- 描述必需15 分
- 图标10 分
- 文档15 分
- 许可证10 分