normattiva-mcp 0.1.3 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +35 -3
- package/dist/index.js +12 -12
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -12,11 +12,11 @@ It wraps the synchronous read endpoints (search, article detail, recent updates)
|
|
|
12
12
|
|
|
13
13
|
| Tool | Purpose |
|
|
14
14
|
| --- | --- |
|
|
15
|
-
| `search_acts` | Search Italian legislation by free text, title, act type, year/number, date range, vigenza class, or in-force date. Returns metadata for each matching atto including `codice_redazionale` and `data_gu` (which `read_article` needs). |
|
|
15
|
+
| `search_acts` | Search Italian legislation by free text, title, act type, year/number, date range, vigenza class, or in-force date. Returns metadata for each matching atto including `codice_redazionale` and `data_gu` (which `read_article` needs). To find acts **newly published** in Gazzetta Ufficiale within a window, set `data_pubblicazione_da` / `data_pubblicazione_a` — this is the right tool for "what was published recently?", not `recent_updates`. |
|
|
16
16
|
| `list_articles` | Discover the article numbers of an atto by sequentially probing `read_article` from `articolo=1`. Finds base articles by default; pass `include_suffixes: true` to also probe `bis`/`ter`/`quater`/… for each one. Each entry carries optional `is_preamble` and `is_abrogated` flags (informational; nothing is filtered server-side). Articles inside structured groups (`id_gruppo != 0`) are still not enumerated automatically. |
|
|
17
17
|
| `read_article` | Fetch the text of a single article of a specific atto at a given date of vigenza. Returns plain text by default; pass `format: "html"` for the original markup (preserves amendment markers). In text mode, hyperlink targets are inlined as `L. 5/2003 [urn:nir:stato:legge:2003-06-05;131]` so URN citations survive the conversion. Successful responses include `found: true`; non-existent articles (wrong `sotto_articolo`, `id_gruppo`, or out-of-range `articolo`) return `{ found: false, reason, richiesta }` instead of throwing — so probing is exception-free. The LLM can call this in parallel for several articles of the same atto. |
|
|
18
18
|
| `read_act` | Aggregate-read of an entire atto: internally enumerates articles and fetches each one, returning them in order capped by `max_chars` (default 80 000 ≈ 20k Claude tokens). Honors `include_suffixes` and `id_gruppo` like `list_articles`. When the budget is exhausted the response includes `truncated: true`, `truncated_reason`, and `articolo_successivo` for resuming via a follow-up call with `articolo_da` set to that value. |
|
|
19
|
-
| `recent_updates` | Lists atti normativi
|
|
19
|
+
| `recent_updates` | Lists atti normativi whose **consolidated text was amended** within a date window (max 12 months) — i.e. the in-force version changed because another act modified it. Does **not** list acts newly published in Gazzetta Ufficiale; for those, use `search_acts` with `data_pubblicazione_da` / `data_pubblicazione_a`. |
|
|
20
20
|
|
|
21
21
|
### Resources
|
|
22
22
|
|
|
@@ -32,7 +32,7 @@ It wraps the synchronous read endpoints (search, article detail, recent updates)
|
|
|
32
32
|
| Name | Args | Purpose |
|
|
33
33
|
| --- | --- | --- |
|
|
34
34
|
| `ricerca-articolo` | `argomento` | Search Italian law on a topic and read the most relevant articles. |
|
|
35
|
-
| `monitoraggio-modifiche` | `giorni` (default `7`) | Summarize
|
|
35
|
+
| `monitoraggio-modifiche` | `giorni` (default `7`) | Summarize amendments to the consolidated text of existing acts in the last *N* days (uses `recent_updates`; for newly published acts use `search_acts` with `data_pubblicazione_da/a`). |
|
|
36
36
|
|
|
37
37
|
## Installation & client configuration
|
|
38
38
|
|
|
@@ -78,6 +78,38 @@ The server always talks to Normattiva's **production OpenData API** (`https://ap
|
|
|
78
78
|
|
|
79
79
|
The OpenData API is open and does not require authentication.
|
|
80
80
|
|
|
81
|
+
## HTTP transport
|
|
82
|
+
|
|
83
|
+
In addition to the npm-published stdio binary, this repo also ships `packages/http-server/` — a Vercel deployment of the same MCP server over the Streamable HTTP transport. Useful for clients that need a remote endpoint instead of a local subprocess.
|
|
84
|
+
|
|
85
|
+
**Hosted endpoint:** `https://normattiva-mcp.adellorto.com/api/mcp` — free, no signup, no auth.
|
|
86
|
+
|
|
87
|
+
Client config:
|
|
88
|
+
|
|
89
|
+
```json
|
|
90
|
+
{
|
|
91
|
+
"mcpServers": {
|
|
92
|
+
"normattiva": {
|
|
93
|
+
"url": "https://normattiva-mcp.adellorto.com/api/mcp"
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Self-hosting
|
|
100
|
+
|
|
101
|
+
To run your own copy on a Vercel account:
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
git clone https://github.com/adellorto/normattiva-mcp.git
|
|
105
|
+
cd normattiva-mcp
|
|
106
|
+
pnpm install
|
|
107
|
+
cd packages/http-server
|
|
108
|
+
npx vercel deploy
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
The handler exposes only Streamable HTTP (SSE is removed from the 2025-03-26 MCP spec). No authentication by default — anyone with the URL can call it.
|
|
112
|
+
|
|
81
113
|
## Building from source
|
|
82
114
|
|
|
83
115
|
Requires Node.js ≥ 20.
|
package/dist/index.js
CHANGED
|
@@ -15,7 +15,7 @@ var COMMON_HEADERS = {
|
|
|
15
15
|
Accept: "application/json, text/plain, */*",
|
|
16
16
|
"Accept-Language": "it-IT,it;q=0.9,en-US;q=0.8,en;q=0.7",
|
|
17
17
|
Origin: "https://dati.normattiva.it",
|
|
18
|
-
"User-Agent": "normattiva-mcp/0.1.
|
|
18
|
+
"User-Agent": "normattiva-mcp/0.1.4 (+https://www.npmjs.com/package/normattiva-mcp)"
|
|
19
19
|
};
|
|
20
20
|
var REQUEST_TIMEOUT_MS = 2e4;
|
|
21
21
|
async function request(path, init) {
|
|
@@ -165,7 +165,7 @@ function jsonContent(data) {
|
|
|
165
165
|
function registerTools(server, _ctx) {
|
|
166
166
|
server.registerTool("search_acts", {
|
|
167
167
|
title: "Search Italian legislation (Normattiva)",
|
|
168
|
-
description: "Search Italian legislation on Normattiva by free text, title, act type, year/number, date range, or vigenza class. Returns metadata for each matching atto including codice_redazionale and data_gu, which are required to fetch article text via read_article. The denominazione parameter expects an Italian act-type label like 'LEGGE' or 'DECRETO LEGISLATIVO' \u2014 see resource normattiva://tipologiche/denominazioni for the full list.",
|
|
168
|
+
description: "Search Italian legislation on Normattiva by free text, title, act type, year/number, date range, or vigenza class. Returns metadata for each matching atto including codice_redazionale and data_gu, which are required to fetch article text via read_article. The denominazione parameter expects an Italian act-type label like 'LEGGE' or 'DECRETO LEGISLATIVO' \u2014 see resource normattiva://tipologiche/denominazioni for the full list. To find acts newly published in Gazzetta Ufficiale within a window, set data_pubblicazione_da and data_pubblicazione_a (this is the right tool for 'what was published recently?', not recent_updates).",
|
|
169
169
|
inputSchema: {
|
|
170
170
|
testo: z.string().optional().describe("Keywords searched in the act body."),
|
|
171
171
|
titolo: z.string().optional().describe("Keywords searched in the act title."),
|
|
@@ -176,8 +176,8 @@ function registerTools(server, _ctx) {
|
|
|
176
176
|
giorno: z.number().int().min(1).max(31).optional(),
|
|
177
177
|
data_emanazione_da: dateString.optional(),
|
|
178
178
|
data_emanazione_a: dateString.optional(),
|
|
179
|
-
data_pubblicazione_da: dateString.optional(),
|
|
180
|
-
data_pubblicazione_a: dateString.optional(),
|
|
179
|
+
data_pubblicazione_da: dateString.optional().describe("Start of the publication window in Gazzetta Ufficiale (YYYY-MM-DD). Use this (with data_pubblicazione_a) to answer 'which acts were published recently'."),
|
|
180
|
+
data_pubblicazione_a: dateString.optional().describe("End of the publication window in Gazzetta Ufficiale (YYYY-MM-DD)."),
|
|
181
181
|
classe: z.enum(["1", "2", "3"]).optional().describe("Vigenza class: 1=senza aggiornamenti (single-version atto), 2=aggiornato, 3=abrogato."),
|
|
182
182
|
codice_tipo: z.string().optional().describe("Faceted filter: act-type code (e.g. 'PLE' for LEGGE)."),
|
|
183
183
|
data_vigenza: dateString.optional().describe("Restrict results to atti in force on this date (YYYY-MM-DD). Maps to the spec's 'vigenza' field."),
|
|
@@ -456,11 +456,11 @@ function registerTools(server, _ctx) {
|
|
|
456
456
|
});
|
|
457
457
|
});
|
|
458
458
|
server.registerTool("recent_updates", {
|
|
459
|
-
title: "Atti
|
|
460
|
-
description: "Lists Italian normative acts whose text was
|
|
459
|
+
title: "Atti whose consolidated text was amended on Normattiva",
|
|
460
|
+
description: "Lists Italian normative acts whose consolidated text was amended within a date window (i.e. the in-force version changed because another act modified it). Does NOT return acts newly published in Gazzetta Ufficiale \u2014 for 'what was published recently', call search_acts with data_pubblicazione_da / data_pubblicazione_a instead. Useful for monitoring how existing legislation evolves. The window must be at most 12 months wide and data_fine cannot precede data_inizio.",
|
|
461
461
|
inputSchema: {
|
|
462
|
-
data_inizio: dateString.describe("Start of the
|
|
463
|
-
data_fine: dateString.describe("End of the
|
|
462
|
+
data_inizio: dateString.describe("Start of the amendment window (YYYY-MM-DD). Refers to the date the consolidated text changed, not to publication in Gazzetta Ufficiale."),
|
|
463
|
+
data_fine: dateString.describe("End of the amendment window (YYYY-MM-DD), max 12 months after data_inizio.")
|
|
464
464
|
}
|
|
465
465
|
}, async ({ data_inizio, data_fine }) => {
|
|
466
466
|
const data = await ricercaAggiornati({
|
|
@@ -698,8 +698,8 @@ Procedi cos\xEC:
|
|
|
698
698
|
]
|
|
699
699
|
}));
|
|
700
700
|
server.registerPrompt("monitoraggio-modifiche", {
|
|
701
|
-
title: "Monitora modifiche
|
|
702
|
-
description: "Recupera gli atti normativi italiani
|
|
701
|
+
title: "Monitora modifiche al testo consolidato di atti esistenti",
|
|
702
|
+
description: "Recupera gli atti normativi italiani il cui testo consolidato \xE8 stato modificato negli ultimi N giorni e ne fornisce un riepilogo. Per gli atti pubblicati di recente in Gazzetta Ufficiale usa invece search_acts con data_pubblicazione_da/a.",
|
|
703
703
|
argsSchema: {
|
|
704
704
|
giorni: z2.string().regex(/^\d+$/, "Numero di giorni come stringa intera").default("7").describe("Quanti giorni indietro guardare (default 7).")
|
|
705
705
|
}
|
|
@@ -714,7 +714,7 @@ Procedi cos\xEC:
|
|
|
714
714
|
role: "user",
|
|
715
715
|
content: {
|
|
716
716
|
type: "text",
|
|
717
|
-
text: `Recupera con recent_updates gli atti normativi italiani
|
|
717
|
+
text: `Recupera con recent_updates gli atti normativi italiani il cui testo consolidato \xE8 stato modificato tra il ${fmt(inizio)} e il ${fmt(oggi)} (non si tratta di nuove pubblicazioni in Gazzetta Ufficiale: per quelle usa search_acts con data_pubblicazione_da/a). Raggruppa i risultati per tipologia di atto e fornisci un riepilogo delle modifiche pi\xF9 rilevanti, evidenziando gli atti modificanti principali.`
|
|
718
718
|
}
|
|
719
719
|
}
|
|
720
720
|
]
|
|
@@ -726,7 +726,7 @@ Procedi cos\xEC:
|
|
|
726
726
|
async function main() {
|
|
727
727
|
const server = new McpServer({
|
|
728
728
|
name: "normattiva-mcp",
|
|
729
|
-
version: "0.1.
|
|
729
|
+
version: "0.1.4"
|
|
730
730
|
});
|
|
731
731
|
const ctx = {};
|
|
732
732
|
registerTools(server, ctx);
|
package/package.json
CHANGED