domustudio-mcp-server
A read-only MCP server for querying configured Danea Domustudio archives, covering condomini, persone, and fornitori with no write path.
List the configured archives available to the server.
List or search condomini, and fetch a single condominio by its stable
id.List persone, optionally restricted to one condominio, with filters, pagination, and optional all-page retrieval up to 50 pages.
List fornitori archive-wide with filters, pagination, and optional all-page retrieval up to 50 pages.
Select a specific archive when multiple are configured; with one archive the
archivioparameter may be omitted.Choose
markdownorjsonoutput; enum fields are decoded as{ codice, etichetta }.Read-only only: the API exposes three GET endpoints, so there are no create/update/delete operations.
Persone and fornitori have no API identifier, so they can only be found by filtering list results rather than fetched by id.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@domustudio-mcp-server list all fornitori in the desa archive"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
domustudio-mcp-server
An MCP server that exposes the Danea Domustudio public API — condomini, persone, fornitori — as tools an agent can call.
The API is read-only: its entire public surface is three GET endpoints. This server adds no write path, because there is none to add. Domustudio remains the system of record; anything that must change, changes there, by a human, in the product.
Requirements
Node.js ≥ 22.12
One Domustudio API key per archive (
X-DANEA-API-KEY)
Related MCP server: Invoice4U MCP
Install it in another repository
The package is not on any registry — it installs straight from this repository, pinned to a release tag. One global install serves every repository on the machine.
npm install -g --allow-git=all "github:juriSacchetta/Domustudio-mcp#v0.4.0"--allow-git=all is required on npm 12 and later, which ships
allow-git = "none" as a default and otherwise refuses the install with
EALLOWGIT. To stop passing it every time:
npm config set allow-git allnpm builds dist/ during install, so the machine needs nothing beyond
Node ≥ 22.12 and access to this repository. npm 12 also prints
install scripts blocked … (prepare: npm run build) — that warning is benign
here: it refers to the consumer's tree, and the build has already run inside
npm's isolated step for the git dependency. Confirm the install with:
test -f "$(npm root -g)/domustudio-mcp-server/dist/index.js" && echo installedDo not run the entry point to "check" it: it is a stdio MCP server with no CLI, so it will start and wait for JSON-RPC on stdin rather than print anything.
A global install puts domustudio-mcp on your PATH, which is the name a
shell-launched client needs — confirm with command -v domustudio-mcp. A GUI
client is launched without your PATH and needs the absolute path instead:
echo "$(npm root -g)/domustudio-mcp-server/dist/index.js"See Configure below for both. To upgrade, rerun the install command with a newer tag; every repository on the machine picks it up at once, which is the trade-off of a single global install.
Develop on it here
npm install
npm run buildConfigure
The server reads its credentials from the environment, and falls back to a .env
file in its working directory for anything the environment does not carry.
DOMUSTUDIO_ARCHIVES is a JSON array — one API key addresses exactly one
archive, and several archives may coexist.
Claude Code, one project
Add it at the default local scope: Claude Code stores that in ~/.claude.json
under the project's path, so the server loads in this project only, stays private
to you, and nothing lands in the repository.
claude mcp add domustudio -e DOMUSTUDIO_ARCHIVES='[{"name":"desa","api_key":"..."}]' \
-- domustudio-mcpThe -- is required: everything after it is the server's own command line. A key
passed this way lands in your shell history; to avoid that, drop the -e and put
the key in a .env as below — the server finds it either way.
A committed .mcp.json
.mcp.json sits at the project root and is normally checked in, which is how a
team shares one server definition — and exactly why the key must not appear in
it. Leave the env block out and give each developer their own gitignored
.env:
// .mcp.json — committed, no credentials
{
"mcpServers": {
"domustudio": { "command": "domustudio-mcp" }
}
}# .env at the project root — one per developer, never committed
printf '.env\n' >> .gitignore
printf 'DOMUSTUDIO_ARCHIVES=[{"name":"desa","api_key":"..."}]\n' >> .env
chmod 600 .envIgnore it before you write it. .mcp.json is committed and .env must not
be; nothing in this server can stop a consuming repository from committing one.
Claude Code launches the server with the project root as its working directory,
which is where it looks. If your client launches it somewhere else, name the file
outright in the .mcp.json entry — DOMUSTUDIO_ENV_FILE is a path, not a
secret, so it is safe in a committed file. It has to arrive this way rather than
from inside a .env, since it is what picks the file to read:
{ "mcpServers": { "domustudio": {
"command": "domustudio-mcp",
"env": { "DOMUSTUDIO_ENV_FILE": "/absolute/path/to/.env" }
} } }Real environment variables always win over the file. Keep the array on one line,
or wrap a multi-line one in single quotes — '[ … ]'.
Claude Desktop and other GUI clients
A GUI client is not launched from a shell and does not inherit your PATH, so
the domustudio-mcp bin may not resolve. Give it Node and an absolute path. This
file lives in your user profile, outside any repository, so the key may sit in it:
// claude_desktop_config.json — per user, never committed
{
"mcpServers": {
"domustudio": {
"command": "node",
// output of: echo "$(npm root -g)/domustudio-mcp-server/dist/index.js"
"args": ["/absolute/path/to/domustudio-mcp-server/dist/index.js"],
"env": {
"DOMUSTUDIO_ARCHIVES": "[{\"name\":\"desa\",\"api_key\":\"...\"}]"
}
}
}
}Why ${DOMUSTUDIO_ARCHIVES} does not work
.mcp.json does expand ${VAR}, but supplying the value through
.claude/settings.local.json env did not work: the server never started, and
the failure reproduced with a control confirming the variable was present in the
session. That is what
#4 observed.
Why the expansion missed it — an ordering question between settings env and
.mcp.json loading — was not established, so treat the recipe as "this does not
work", not as a mechanism you can reason around.
The symptom is worth recognising: an unexpanded variable is passed through as the
literal text ${DOMUSTUDIO_ARCHIVES}, which is not JSON, so the server exits 1
and the client reports nothing more useful than
domustudio (CONNECTION_CLOSED): "Connection closed". From 0.2.0 the server
names that literal on stderr instead.
Exporting the variable from a shell rc file does make the expansion work, but it
puts the key in a dotfile — no better than committing it. Prefer .env.
Variables
Variable | Required | Default |
| yes | — |
| no |
|
| no |
|
DOMUSTUDIO_BASE_URL exists for tests and for pointing at a mock; production
should leave it unset. The default is HTTPS even though the OpenAPI servers
entry says http://.
The key is never echoed in a tool result: an authentication failure names the
archive and the header, not the value. The startup line on stderr names the
.env it read, never its contents.
Tools
Tool | What it does |
| Names of the configured archives |
| Compact pages of condomini; optional local |
| One condominio by |
| Persone for a required condominio; paginated |
| Fornitori of the whole archive; paginated |
All five are annotated readOnlyHint: true, destructiveHint: false.
Every tool takes an optional archivio. With a single archive configured it may
be omitted; with several, omitting it returns an error listing the names.
Every tool takes response_format: markdown (default) renders the main fields
for reading, json returns the full API record except for condominium lists,
which are compact in both formats. Integer enums are decoded to
{ codice, etichetta }.
What the API does not give you
Persone and fornitori have no identifier. Neither schema contains an id,
and no endpoint accepts one, so there is no get_persona and no
get_fornitore — a person or supplier is reached by filtering a list. The
condominio id is the only stable handle the API exposes, and it is what
domustudio_list_persone takes as condominio_id.
condominio_id is required and is sent to the API as CondGenID.
For an archive-wide person search, follow all condominium list pages first, then call
domustudio_list_persone for each id and follow each building's pagination.
Compact condominium lists (0.5.0 contract change)
domustudio_list_condomini defaults to pagina: 1, dimensione_pagina: 25
(maximum 100). If the building name or address is known, pass ricerca first.
The case-insensitive local filter runs before pagination. Danea still returns
the complete list upstream; only the selected compact page enters the MCP result.
Both text and structuredContent contain only id, intestazione, indirizzo,
cap, citta, prov, and codFisc where present, with archivio in the envelope.
conteggio counts the current page, totale counts all matches, and
ha_altre_pagine is exact. When true, pass prossima_pagina as the next pagina
with the same archive, filter and page size. Empty and out-of-range pages contain
no elements and no next page. Each call reads the archive afresh; enumeration
assumes the upstream list stays unchanged between calls.
Existing clients must follow pages instead of treating one response as a full
inventory. Use domustudio_get_condominio for the complete record, including
administrator and other details. There is no all-pages bypass for condominium
lists. Other tools retain their existing contracts.
Persone and fornitori have no total count. Responses are bare JSON arrays. ha_altre_pagine
is therefore a heuristic — true when the last page read was full — and is
documented as such in the tool descriptions.
A page is reported to hold at most 100 records. The API caps PageSize
there and says nothing when it does, so dimensione_pagina is refused above
that rather than sent and silently truncated. One tutte_le_pagine call
therefore reaches 5 000 records — 100 per page, 50 pages. npm run test:live
measures the cap against a real archive. See
docs/adr/0002.
Development
Command | |
| compile to |
|
|
| eslint over |
| typecheck + full suite |
| suite only, no build |
| suite against the real API (see below) |
| MCP Inspector against |
The suite runs against a local HTTP mock of the Domustudio API and needs no credentials. It covers the client (headers, 1-based paging, retry policy, the truncated-401), configuration and archive resolution, enum decoding, markdown rendering, every tool over an in-memory MCP transport, and the built binary over real stdio.
Running against the real API
Put real credentials in a .env at the repo root — it is gitignored:
printf 'DOMUSTUDIO_ARCHIVES=[{"name":"desa","api_key":"..."}]\n' > .env
chmod 600 .env
npm run test:liveWithout DOMUSTUDIO_LIVE=1 those tests skip, so npm test stays green on a
machine with no credentials. They only read, and assert on shapes and counts —
no record content is printed.
Documentation
CLAUDE.md— API facts an implementation has to knowdocs/adr/— decisions and why they are hard to reverseCONTRIBUTING.md— the loop, the conventions, how to add a toolSECURITY.md— credential handling and how to report a vulnerabilitydocs/read-only-guard.md— the hook that refuses mutating requestsdocs/verification-hook.md— the hook that runs lint and tests
Trademarks
Danea and Domustudio are trademarks of their respective owners. This project is an independent client of the Domustudio public API, is not affiliated with or endorsed by them, and is not supported by them.
License
MIT — see LICENSE. Copyright (c) 2026 Juri Sacchetta.
Available Tools
5 toolsdomustudio_get_condominioLeggi un condominio per idARead-onlyIdempotent
Restituisce il singolo condominio con l'id indicato.
L'API non espone una lettura per id: lo strumento scarica la lista completa dei condomini e seleziona la voce corrispondente.
Args:
condominio_id (number): id Domustudio del condominio
archivio (string, opzionale): archivio da interrogare
response_format ('markdown' | 'json'): default 'markdown'
Restituisce (json): { "archivio": string, "trovato": boolean, "condominio": object | null }
Errori:
"trovato": false se nessun condominio ha quell'id nell'archivio indicato.
L'API Domustudio è di sola lettura: espone solo tre endpoint GET. Domustudio resta il sistema di registrazione — ogni correzione va fatta da un operatore dentro il gestionale.
Inputs: condominio_id (required), archivio, response_format. Returns: trovato, archivio, condominio
domustudio_list_archiviElenca archivi Domustudio configuratiARead-onlyIdempotent
Elenca i nomi degli archivi Domustudio configurati su questo server.
Una chiave API indirizza esattamente un archivio; ogni altro strumento accetta un parametro "archivio" che seleziona quale interrogare. Se ne è configurato uno solo, quel parametro può essere omesso.
Nessun parametro.
Restituisce: { "archivi": string[], // nomi configurati, nell'ordine di configurazione "conteggio": number }
L'API Domustudio è di sola lettura: espone solo tre endpoint GET. Domustudio resta il sistema di registrazione — ogni correzione va fatta da un operatore dentro il gestionale.
Inputs: no named parameters. Returns: archivi, conteggio
domustudio_list_condominiElenca condominiARead-onlyIdempotent
Elenca i condomini presenti in un archivio Domustudio.
L'endpoint restituisce sempre la lista completa: non è paginato e non accetta filtri lato API. Il parametro "ricerca" filtra i risultati localmente su intestazione, città, provincia, indirizzo e codice fiscale.
L'"id" di un condominio è l'unico identificatore stabile esposto dall'API Domustudio, ed è il valore da passare come "condominio_id" a domustudio_list_persone.
Args:
archivio (string, opzionale): archivio da interrogare
ricerca (string, opzionale): sottostringa, confronto case-insensitive
response_format ('markdown' | 'json'): default 'markdown'
Restituisce (json): { "archivio": string, "conteggio": number, "elementi": [ { "id": number, "intestazione": string, "indirizzo": string, "cap": string, "citta": string, "prov": string, "codFisc": string, "amministratore": { "nome": string, "email": string, ... } } ] } In formato markdown vengono mostrati intestazione, id, indirizzo, codice fiscale e amministratore; il formato json riporta il record completo restituito dall'API.
Usare quando: serve l'id di un condominio, o l'anagrafica dello stabile. Non usare per: elencare le persone di un condominio (domustudio_list_persone) o i fornitori (domustudio_list_fornitori).
L'API Domustudio è di sola lettura: espone solo tre endpoint GET. Domustudio resta il sistema di registrazione — ogni correzione va fatta da un operatore dentro il gestionale.
Inputs: ricerca, archivio, response_format. Returns: ricerca, archivio, elementi, conteggio
domustudio_list_fornitoriElenca fornitoriARead-onlyIdempotent
Elenca i fornitori di un archivio Domustudio.
L'anagrafica fornitori è globale sull'archivio, non per condominio: non esiste un parametro che la restringa a uno stabile. Come per le persone, il record non contiene alcun id, quindi non esiste una lettura per id; per trovare un fornitore si filtra questo elenco.
Args:
archivio (string, opzionale): archivio da interrogare
attivi (boolean, opzionale): solo attivi (true) o solo non attivi (false); omesso, entrambi
dati_incompleti (boolean, opzionale): filtra sui fornitori con dati incompleti
attivita (string, opzionale): attività del fornitore (parametro Attivita)
impianto_servizio_id (number, opzionale): id del servizio collegato (ImpiantoServizioID)
esercizi_id (number[], opzionale): esercizi del fornitore (EserciziID)
search_query (string, opzionale): passato all'API come SearchQuery, semantica non documentata
order_by (string, opzionale): passato all'API come OrderBy, semantica non documentata
pagina (number): pagina 1-based, default 1
dimensione_pagina (number): elementi per pagina, default 50
tutte_le_pagine (boolean): se true scorre le pagine fino a 50, default false
response_format ('markdown' | 'json'): default 'markdown'
Restituisce (json): stesso involucro paginato di domustudio_list_persone, con elementi della forma { "descr": string, "fornitoreAttivita": string, "codFisc": string, "piva": string, "iban": string, "email": string[], "pec": string[], "tel1": string, "fornitoreInattivo": boolean, "descStatoDurc": string, "dataScadenzaDurc": string, "f24CodTributo": { "codice": number, "etichetta": string }, ... } "ha_altre_pagine" è un'euristica: l'API non restituisce un totale, quindi è vero quando l'ultima pagina letta era piena.
Usare quando: serve un fornitore per attività, partita IVA, IBAN o stato DURC. Non usare per: i condòmini e i soggetti dell'anagrafica persone (domustudio_list_persone).
L'API Domustudio è di sola lettura: espone solo tre endpoint GET. Domustudio resta il sistema di registrazione — ogni correzione va fatta da un operatore dentro il gestionale.
Inputs: attivi, pagina, archivio, attivita, +8 more. Returns: pagina, archivio, elementi, conteggio, +5 more
domustudio_list_personeElenca persone (anagrafica soggetti)ARead-onlyIdempotent
Elenca le persone dell'anagrafica Domustudio, opzionalmente ristrette a un condominio.
L'API non espone un identificatore per le persone: il record non contiene alcun id, quindi non esiste una lettura per id e non c'è modo di indirizzare una persona singola. Per trovare qualcuno si filtra questo elenco (search_query, oppure condominio_id più una lettura dei risultati).
Args:
archivio (string, opzionale): archivio da interrogare
condominio_id (number, opzionale): restringe a un condominio (parametro CondGendID). Ottenerlo da domustudio_list_condomini
filtro_subentri (1|2|3|4|5): 1 tutti, 2 attivi, 3 ex, 4 contabilità, 5 destinatari comunicazioni. Default 2
esercizio_id (number, opzionale): parametro EsercizioID
tags_id (number[], opzionale): parametro TagsID
search_query (string, opzionale): passato all'API come SearchQuery; la semantica è decisa dall'API, non documentata nello spec
order_by (string, opzionale): passato all'API come OrderBy, semantica non documentata
pagina (number): pagina 1-based, default 1
dimensione_pagina (number): elementi per pagina, default 50
tutte_le_pagine (boolean): se true scorre le pagine fino a 50, default false
response_format ('markdown' | 'json'): default 'markdown'
Restituisce (json): { "archivio": string, "conteggio": number, // elementi in questa risposta "pagina": number, // prima pagina letta "dimensione_pagina": number, "pagine_lette": number, "ha_altre_pagine": boolean, // euristica: l'API non restituisce un totale, quindi è vero quando l'ultima pagina era piena "prossima_pagina": number, // presente solo se ha_altre_pagine "troncato_al_limite_pagine": boolean, "elementi": [ { "descr": string, "codFisc": string, "piva": string, "indirizzo": string, "cap": string, "citta": string, "prov": string, "email": string[], "pec": string[], "tel1": string, "note": string, "preferenzaPagamentoRate": { "codice": number, "etichetta": string }, ... } ] } I campi enumerativi sono restituiti come { codice, etichetta }. Il formato markdown mostra denominazione, codice fiscale, indirizzo, recapiti; json riporta il record completo.
Usare quando: servono i condòmini di uno stabile, i recapiti di un soggetto, o un conteggio per condominio. Non usare per: i fornitori, che stanno in un'anagrafica separata (domustudio_list_fornitori).
L'API Domustudio è di sola lettura: espone solo tre endpoint GET. Domustudio resta il sistema di registrazione — ogni correzione va fatta da un operatore dentro il gestionale.
Inputs: pagina, tags_id, archivio, order_by, +7 more. Returns: pagina, archivio, elementi, conteggio, +5 more
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 5 tools
Each tool targets a distinct resource: archives, condomini, a single condominio, persone, or fornitori. The descriptions explicitly state when to use each tool and when not to use it, preventing overlap confusion. The only minor redundancy is get_condominio versus list_condomini, but the signature and intent are clearly separated.
All tools share the domustudio_ prefix and use consistent snake_case with a verb_noun pattern: list_archivi, list_condomini, get_condominio, list_persone, list_fornitori. There are no mixed conventions or ambiguous verb styles. The naming is entirely predictable.
Five tools is well-scoped for a read-only integration wrapping three GET endpoints plus archive discovery and a convenience single-condominio lookup. Each tool earns its place, with no thin or bloated surface. The count matches the domain perfectly.
The Domustudio API is explicitly read-only and exposes only three GET endpoints; the server covers all of them plus archive listing and single-condominio retrieval. Missing update/delete operations are by design, not gaps. Agents have workarounds for person and supplier lookup via filtering, and no obvious read path is absent.
Maintenance
Related MCP Connectors
Read and write a CRM built for agents. Every change carries who asserted it and how.
Read and manage your Darwin website AI agent: conversations, leads, meetings, knowledge and tests.
AgentDocs (agentdocs.eu) MCP: read, search, write, comment, share & attach images to Markdown docs.
Read templates, schemas, versions, folders, deliveries and merge history in Docupilot.
Related MCP Servers
- FlicenseCqualityCmaintenanceExposes read-only Aruba Fatturazione Elettronica API operations for managing electronic invoices, notifications, and providing fiscal document helpers.7027 PyPI1-
- AlicenseNot gradedqualityCmaintenanceEnables MCP-compatible agents to securely access an Invoice4U account for searching documents and customers and creating receipts linked to paid invoices, with read-only behavior by default.1MIT
- AlicenseAqualityCmaintenanceIt enables MCP clients such as Claude and ChatGPT to read an estate agency's Dezrez Rezi CRM data, including properties and their timelines, people, groups, and offers. It is read-only and applies privacy redactions by default.10MIT
- AlicenseNot gradedqualityCmaintenanceEnables AI agents to read-only query an MSP's Action1 data — organizations, managed endpoints, missing OS and third-party updates, and scheduled automations — using per-request credentials that are never stored. Supports multi-tenant, stateless access over Streamable HTTP with paginated list results and structured error responses.Apache 2.0