Skip to content

Commit e83816e

Browse files
committed
Fix: inheritance local store
1 parent 1325559 commit e83816e

2 files changed

Lines changed: 144 additions & 0 deletions

File tree

README.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ Nota: questi tool restano utili, ma su `schema.gov.it` sono spesso secondari. Il
6666

6767
### 10. Ontologia Locale
6868
* `inspect_local_ontology`: Carica e riassume un'ontologia RDF/OWL disponibile al server via `file_path`, contenuto inline o `upload_id`. Attenzione: `file_path` indica sempre un path leggibile dal server MCP, non dal laptop dell'utente.
69+
* `inspect_local_concept`: **Deep dive con ereditarietà** su una classe o concetto in un'ontologia locale o caricata. Restituisce definizione, gerarchia, proprietà dirette (`own_properties`) e proprietà ereditate dagli antenati (`inherited_properties`, con indicazione dell'antenato di provenienza). oxigraph non fa inferenza RDFS automatica: questo tool traversa esplicitamente la catena `rdfs:subClassOf+` / `skos:broader+` per restituire il profilo completo.
6970
* `query_local_ontology`: Esegue una query SPARQL SELECT su un'ontologia accessibile dal server o caricata prima via `POST /upload`. Prefissi standard iniettati automaticamente. Risultati compressi come gli altri tool.
7071
* `compare_local_with_remote`: Confronta le classi/proprietà definite in un'ontologia accessibile dal server o via `upload_id` con quelle presenti in schema.gov.it — utile per scoprire cosa riusare o allineare.
7172

@@ -291,6 +292,36 @@ Una volta configurato, puoi chiedere all'agente cose come:
291292
* *"Quali tool open source posso usare per lavorare con SKOS e OWL?"* (Userà `find_semantic_software`)
292293
* *"Cosa manca a schema.gov.it rispetto agli standard semantici internazionali del settore pubblico?"* (Userà `compare_coverage_with_okg`)
293294

295+
## Variabili d'Ambiente
296+
297+
| Variabile | Default | Descrizione |
298+
|---|---|---|
299+
| `MCP_TRANSPORT` | `stdio` | Modalità di trasporto. Usa `http` o `sse` per avviare il server HTTP (obbligatorio per l'upload e per l'uso remoto). |
300+
| `PORT` | `3000` | Porta su cui il server HTTP si mette in ascolto (solo in modalità `http`/`sse`). |
301+
| `HOST` | `0.0.0.0` | Indirizzo di bind del server HTTP. Usa `127.0.0.1` per limitare l'accesso al solo localhost. |
302+
| `MCP_PUBLIC_URL` | _(non impostato)_ | URL esterno del server, usato dal tool `get_upload_instructions` per restituire l'endpoint di upload raggiungibile dal client. Necessario quando la porta interna differisce da quella esposta (Docker, reverse proxy). Esempio: `http://localhost:8080`. |
303+
304+
**Esempi:**
305+
306+
```bash
307+
# Avvio locale su porta 3000 con upload abilitato
308+
MCP_TRANSPORT=http node dist/index.js
309+
310+
# Porta personalizzata
311+
MCP_TRANSPORT=http PORT=8080 node dist/index.js
312+
313+
# Docker con port mapping 8080→3000 (porta interna 3000, esposta 8080)
314+
docker run -d \
315+
-e MCP_TRANSPORT=http \
316+
-e MCP_PUBLIC_URL=http://localhost:8080 \
317+
-p 8080:3000 \
318+
ghcr.io/italia/dati-semantic-mcp:latest
319+
```
320+
321+
> **Nota upload:** La porta HTTP (e di conseguenza `/upload`) è disponibile **solo** in modalità `http` o `sse`. In modalità `stdio` il server non espone nessuna porta; per passare file RDF usa il parametro `content` di `inspect_local_ontology` per file piccoli, oppure attiva la modalità HTTP.
322+
323+
---
324+
294325
## Note Tecniche
295326

296327
* **Endpoint Esterni**: Usa `recommend_external_endpoints` per una lista curata (es. `lod.dati.gov.it` come possibile server SPARQL per `dati.gov.it`, `dati.cultura.gov.it`, endpoint istituzionali italiani, endpoint europei e knowledge graph pubblici) e `list_linked_endpoints` per scoprire quelli pubblicati nel catalogo via metadata DCAT.

src/tools/group-k.ts

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,119 @@ server.registerTool(
144144
}
145145
);
146146

147+
server.registerTool(
148+
"inspect_local_concept",
149+
{
150+
title: "Inspect Concept in Local / Uploaded Ontology",
151+
description: `Get a full profile of a class or concept from a local or uploaded ontology, including the complete inherited property chain.
152+
153+
**Args (provide exactly one of file_path or upload_id):**
154+
- uri: URI of the class/concept to inspect
155+
- file_path: Absolute path on the MCP server filesystem
156+
- upload_id: UUID returned by POST /upload (HTTP/remote mode)
157+
158+
**Returns:**
159+
- definition: Literal annotations of the concept (rdfs:label, rdfs:comment, skos:definition…)
160+
- hierarchy: Direct type, parent classes (rdfs:subClassOf / skos:broader), and child classes
161+
- usage: Count of instances typed as this class in the store
162+
- own_properties: Properties whose rdfs:domain is exactly this class
163+
- inherited_properties: Properties inherited from ancestor classes via rdfs:subClassOf+ / skos:broader+, each annotated with the ancestor it comes from — gives the complete effective property set
164+
- incoming: Properties whose values are instances of this type (data-level)
165+
- outgoing: Properties used by instances of this type (data-level)
166+
167+
**Why this matters:** oxigraph (the local SPARQL engine) does not perform automatic RDFS inference. A plain \`?prop rdfs:domain <class>\` query will miss properties declared on parent classes. This tool traverses the ancestry explicitly using SPARQL property paths (rdfs:subClassOf+) so the result reflects the full OWL/RDFS semantics.
168+
169+
**Use when:** You uploaded an ontology and want to understand what a specific class really models, including everything it inherits.`,
170+
inputSchema: {
171+
uri: z.string().describe("URI of the class or concept to inspect"),
172+
file_path: z.string().optional().describe("Absolute path to the ontology file on the server filesystem"),
173+
upload_id: z.string().optional().describe("Upload UUID returned by POST /upload (HTTP mode)"),
174+
},
175+
annotations: {
176+
readOnlyHint: true,
177+
destructiveHint: false,
178+
idempotentHint: true,
179+
openWorldHint: false,
180+
},
181+
},
182+
async ({ uri, file_path, upload_id }) => {
183+
return executeTool("inspect_local_concept", { uri, file_path, upload_id }, async () => {
184+
const { store } = await resolveLocalStore(file_path, undefined, undefined, upload_id);
185+
186+
const queries: Record<string, string> = {
187+
definition: `
188+
SELECT ?p ?o WHERE { <${uri}> ?p ?o . FILTER(ISLITERAL(?o)) }
189+
`,
190+
hierarchy: `
191+
SELECT ?type ?parent ?parentLabel ?child ?childLabel WHERE {
192+
{ <${uri}> a ?type }
193+
UNION
194+
{ <${uri}> rdfs:subClassOf|skos:broader ?parent .
195+
OPTIONAL { ?parent rdfs:label|skos:prefLabel ?parentLabel . FILTER(LANG(?parentLabel) = "it" || LANG(?parentLabel) = "") }
196+
}
197+
UNION
198+
{ ?child rdfs:subClassOf|skos:broader <${uri}> .
199+
OPTIONAL { ?child rdfs:label|skos:prefLabel ?childLabel . FILTER(LANG(?childLabel) = "it" || LANG(?childLabel) = "") }
200+
}
201+
} LIMIT 50
202+
`,
203+
usage: `
204+
SELECT (COUNT(?s) AS ?instanceCount) WHERE { ?s a <${uri}> }
205+
`,
206+
own_properties: `
207+
SELECT DISTINCT ?prop ?propType ?propLabel ?range ?rangeLabel WHERE {
208+
?prop rdfs:domain <${uri}> .
209+
OPTIONAL { ?prop a ?propType . VALUES ?propType { owl:ObjectProperty owl:DatatypeProperty owl:AnnotationProperty } }
210+
OPTIONAL { ?prop rdfs:label ?propLabel . FILTER(LANG(?propLabel) = "it" || LANG(?propLabel) = "") }
211+
OPTIONAL { ?prop rdfs:range ?range }
212+
OPTIONAL { ?range rdfs:label|skos:prefLabel ?rangeLabel . FILTER(LANG(?rangeLabel) = "it" || LANG(?rangeLabel) = "") }
213+
}
214+
ORDER BY ?prop
215+
LIMIT 50
216+
`,
217+
inherited_properties: `
218+
SELECT DISTINCT ?ancestor ?ancestorLabel ?prop ?propType ?propLabel ?range ?rangeLabel WHERE {
219+
<${uri}> rdfs:subClassOf+|skos:broader+ ?ancestor .
220+
FILTER(isIRI(?ancestor))
221+
?prop rdfs:domain ?ancestor .
222+
OPTIONAL { ?prop a ?propType . VALUES ?propType { owl:ObjectProperty owl:DatatypeProperty owl:AnnotationProperty } }
223+
OPTIONAL { ?prop rdfs:label ?propLabel . FILTER(LANG(?propLabel) = "it" || LANG(?propLabel) = "") }
224+
OPTIONAL { ?prop rdfs:range ?range }
225+
OPTIONAL { ?range rdfs:label|skos:prefLabel ?rangeLabel . FILTER(LANG(?rangeLabel) = "it" || LANG(?rangeLabel) = "") }
226+
OPTIONAL { ?ancestor rdfs:label|skos:prefLabel ?ancestorLabel . FILTER(LANG(?ancestorLabel) = "it" || LANG(?ancestorLabel) = "") }
227+
}
228+
ORDER BY ?ancestor ?prop
229+
LIMIT 100
230+
`,
231+
incoming: `
232+
SELECT DISTINCT ?p ?sType WHERE {
233+
?s ?p ?o .
234+
?o a <${uri}> .
235+
OPTIONAL { ?s a ?sType }
236+
} LIMIT 20
237+
`,
238+
outgoing: `
239+
SELECT DISTINCT ?p ?oType WHERE {
240+
?s a <${uri}> .
241+
?s ?p ?o .
242+
OPTIONAL { ?o a ?oType }
243+
} LIMIT 20
244+
`,
245+
};
246+
247+
const results: Record<string, unknown> = {};
248+
let totalRows = 0;
249+
for (const [key, q] of Object.entries(queries)) {
250+
const r = runLocalSparql(store, q, true);
251+
results[key] = compressSparqlResult(r);
252+
totalRows += r.results.bindings.length;
253+
}
254+
255+
return { success: true, data: results, rowCount: totalRows };
256+
});
257+
}
258+
);
259+
147260
server.registerTool(
148261
"compare_local_with_remote",
149262
{

0 commit comments

Comments
 (0)