The problem
The Digital Prosopography of the Roman Republic (DPRR) is a dataset of political figures in the Roman Republic: magistrates, priests, senators, their offices, dates, and family relationships. It's built from decades of scholarship, primarily Broughton's Magistrates of the Roman Republic (1951–1986). The data is available as RDF, queryable via SPARQL.
The problem: SPARQL is not something most people can write from scratch. The DPRR ontology has its own vocabulary, its own conventions, its own quirks. Even researchers who know the dataset well spend time wrangling queries. And the upstream site experiences frequent downtime.
I wanted to see if an LLM could bridge that gap by taking a natural language question about Roman Republican prosopography and turn it into a valid SPARQL query, execute it, and synthesize the results with appropriate scholarly caveats.
The approach
Projects like sparql-llm from the Swiss Institute of Bioinformatics have explored this space with a full retrieval pipeline of vector stores for query examples, schema indexing, and validation feedback loops. I wanted something lighter. Skip the retrieval infrastructure and give the LLM direct access to the schema and a query executor.
I built dprr-mcp, an MCP server that bundles the DPRR RDF dataset in a local Oxigraph store and exposes three tools:
get_schema. Returns the DPRR ontology: prefixes, classes, properties, and query tipsvalidate_sparql. Syntax check with auto-repair for missing PREFIX declarations, plus semantic validation against the ontologyexecute_sparql. Full validation + execution against the local store
The key design decision: don't try to answer questions directly from the data. Instead, give the LLM the tools to generate, validate, and execute structured queries. The LLM handles the natural language → SPARQL translation. The SPARQL engine handles correctness. The LLM then synthesizes the results.
This is retrieval-augmented generation in the literal sense. The model retrieves structured data through a formal query language, not a vector similarity search. No embeddings. No chunking. No fuzzy matching. The query either returns the right triples or it doesn't.
What works
Ask "Who held the office of praetor in 150 BC?" and the system generates a SPARQL query against the DPRR ontology, validates it, executes it, and returns a synthesized response with source citations. It correctly identifies C. Livius Drusus and P. Sextilius, notes the uncertainty flags on their dates, cites Broughton and Brennan, and, critically, explains why only two names appear when six praetors were elected annually.
The three-tool pipeline (schema → validate → execute) forces the LLM through a structured workflow. It can't hallucinate data because the data comes from SPARQL results. It can't write invalid queries because the validator catches them. The ontology schema grounds its understanding of what the data actually contains.
The pipeline in practice
Here's what actually happens when you ask "Who was praetor in 150 BC?", showing the three tool calls and their results.
Step 1, get_schema. The LLM calls this first to learn the ontology. It discovers that office-holding is modeled as PostAssertion linked to a Person via isAboutPerson and to an Office via hasOffice. Dates are integers (negative = BC). It also learns that uncertainty is modeled as boolean flags (isDateStartUncertain, isUncertain) and that scholarly notes live on PostAssertionNote entities.
Step 2, validate_sparql. The LLM drafts a query and submits it for validation. On the first attempt, it used hasPerson instead of isAboutPerson, a plausible guess, but wrong. The validator returned:
Unknown predicate 'hasPerson' for class 'PostAssertion'. Did you mean: isAboutPerson?
The LLM corrects the query and resubmits. This time it passes:
PREFIX vocab: <http://romanrepublic.ac.uk/rdf/ontology#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
SELECT ?person ?personName ?officeLabel ?dateStart
?dateStartUncertain ?dateEndUncertain
?noteText ?sourceLabel
WHERE {
?assertion a vocab:PostAssertion ;
vocab:isAboutPerson ?person ;
vocab:hasOffice ?office ;
vocab:hasDateStart ?dateStart .
?person vocab:hasPersonName ?personName .
?office rdfs:label ?officeLabel .
FILTER(STRSTARTS(?officeLabel, "Office: praetor"))
FILTER(?dateStart = -150)
OPTIONAL { ?assertion vocab:isDateStartUncertain ?dateStartUncertain }
OPTIONAL { ?assertion vocab:isDateEndUncertain ?dateEndUncertain }
OPTIONAL {
?assertion vocab:hasPostAssertionNote ?note .
?note vocab:hasNoteText ?noteText
}
OPTIONAL {
?assertion vocab:hasSecondarySource ?source .
?source rdfs:label ?sourceLabel
}
}
ORDER BY ?personName
Step 3, execute_sparql. The validated query runs against the local Oxigraph store and returns four rows:
| Person | Date | Uncertain | Source | Note |
|---|---|---|---|---|
| C. Livius Drusus | 150 BC | No | Broughton MRR I | The latest possible date for his praetorship. |
| P. Sextilius | 150 BC | Yes | Broughton MRR II App. 2 | Named, probably as presiding officer, in a fragmentary decree... |
| P. Sextilius | 150 BC | Yes | Brennan 2000 | p. 756, fn. 504 |
Three rows, two persons (deduplicated from four raw rows). The duplicates are by design. There are multiple PostAssertion triples per person (one per secondary source or note). The LLM synthesizes:
- C. Livius Drusus is attested without uncertainty flags, though Broughton notes this is "the latest possible date", a terminus ante quem rather than a fixed year. The date may be inferred rather than independently attested.
- P. Sextilius has both dates flagged uncertain. He's identified from a fragmentary senatorial decree found at Trikkala in Thessaly, "probably before the middle of the second century."
- Six praetors were elected annually by this period (Brennan 2000). Two results out of six is consistent with the documentation gap for the mid-second century. Livy's surviving narrative ends after 167 BC, and the richer sources of the late Republic haven't yet begun. But we can't distinguish missing records from genuine absences.
The LLM didn't invent any of this. The uncertainty flags, the source citations, the scholarly notes all came from the SPARQL results. The LLM's job was to translate the question into a query, then translate the structured results into a readable narrative with appropriate caveats.
More examples
Example 2: What offices did Cicero hold?
A career reconstruction query, tracing every firmly attested office for M. Tullius Cicero, filtered to exclude uncertain assertions.
SELECT DISTINCT ?personName ?officeLabel ?dateStart
WHERE {
?assertion a vocab:PostAssertion ;
vocab:isAboutPerson ?person ;
vocab:hasOffice ?office ;
vocab:hasDateStart ?dateStart .
?person vocab:hasPersonName ?personName .
?office rdfs:label ?officeLabel .
FILTER(CONTAINS(?personName, "Cicero"))
FILTER NOT EXISTS { ?assertion vocab:isUncertain true }
}
ORDER BY ?dateStartThe query returns 37 rows spanning three Tullii Cicerones: the orator (TULL2072), his brother Quintus (TULL2216), and his son (TULL2563). The LLM needs domain knowledge to separate them. For M. Tullius Cicero himself, the cursus honorum emerges clearly from the data:
- 89 BC. Officer (title not preserved)
- 75 BC. Quaestor
- 69 BC. Aedilis plebis
- 66 BC. Praetor
- 63 BC. Consul
- 53 BC. Augur
- 51–47 BC. Proconsul
- 43 BC. Princeps senatus
Cicero is one of the safest cases in the DPRR. His own speeches and correspondence independently attest each office. The dates here come from ancient sources, not from lex Villia back-calculation. The dataset also captures his brother Quintus's long service as legatus under Caesar in Gaul (57–50 BC) and his son's military praefectura. Three careers from one query, distinguishable by the DPRR identifiers.
Example 3: Who governed Sicilia?
A province query, joining through the PostAssertionProvince intermediary class, which is how DPRR links office-holdings to territorial assignments.
SELECT DISTINCT ?personName ?officeLabel ?dateStart
WHERE {
?assertion a vocab:PostAssertion ;
vocab:isAboutPerson ?person ;
vocab:hasOffice ?office ;
vocab:hasDateStart ?dateStart .
?pap a vocab:PostAssertionProvince ;
vocab:hasPostAssertion ?assertion ;
vocab:hasProvince ?province .
?person vocab:hasPersonName ?personName .
?office rdfs:label ?officeLabel .
?province rdfs:label "Province: Sicilia" .
FILTER NOT EXISTS { ?assertion vocab:isUncertain true }
}
ORDER BY ?dateStart
LIMIT 10Sicilia was Rome's first province (241 BC), and DPRR records 189 distinct persons assigned there, the second-highest of any province after Rome itself. The earliest results reach back to 492 BC (legati sent to buy grain during a famine), but the steady stream of proconsuls and praetors begins after 241 BC.
A province-level aggregate shows where the most office-holders are recorded, but these counts are shaped by source survival and scholarly coverage, so they can't be read as a direct measure of administrative importance. Hispania saw decades of continuous warfare from the Second Punic War through the Sertorian conflict, yet records only 80 post-holders across all three DPRR categories, fewer than half of Sicilia's 189. Whether the gap reflects documentation patterns, differences in the scale of operations, or both is exactly the kind of question the raw counts can't answer on their own.
- Rome. 264 persons recorded
- Sicilia. 189
- Macedonia. 166
- Asia. 162
- Hispania. 80
Example 4: Who was Cornelia, mother of the Gracchi?
A relationship query, tracing all family connections for one of the most documented women in the Republic.
SELECT ?personName ?relLabel ?relatedName
WHERE {
?person a vocab:Person ;
vocab:isSex <http://romanrepublic.ac.uk/rdf/entity/Sex/Female> ;
vocab:hasPersonName ?personName .
?assertion a vocab:RelationshipAssertion ;
vocab:isAboutPerson ?person ;
vocab:hasRelatedPerson ?related ;
vocab:hasRelationship ?rel .
?related vocab:hasPersonName ?relatedName .
?rel rdfs:label ?relLabel .
FILTER(CONTAINS(?personName, "Cornelia") && CONTAINS(?personName, "407"))
}
ORDER BY ?relLabelDPRR returns 10 relationship assertions for Cornelia (407):
- daughter of Scipio Africanus Maior and Aemilia Tertia
- married to Ti. Sempronius Gracchus (cos. 177, 163 BC)
- mother of Ti. Gracchus (the tribune), C. Gracchus (the tribune), and Sempronia
- sister of four Cornelii Scipiones
This is the dataset's elite bias made visible. Cornelia is one of the best-documented women in the Republic, and she appears exclusively through her relationships to male office-holders. She held no magistracies (women couldn't), so she has no PostAssertion records. Her entire presence in DPRR is relational. The query works, but what it reveals about the dataset's structure is as important as the results themselves.
Example 5: How many M. Pomponii Mathones are in the dataset?
The DPRR record for M. Pomponius Matho (POMP0853, cos. 231 BC) illustrates what happens when nomenclatural ambiguity meets fragmentary evidence. The dataset assigns nine PostAssertions to a single Person entity, but the scholarly notes reveal that at least three, and possibly four, distinct individuals may be collapsed into one record.
SELECT ?office ?dateStart ?dateEnd ?isUncertain ?isDateStartUncertain
?noteText ?source
WHERE {
?pa a vocab:PostAssertion ;
vocab:isAboutPerson <http://romanrepublic.ac.uk/rdf/entity/Person/853> ;
vocab:hasOffice/rdfs:label ?office .
OPTIONAL { ?pa vocab:hasDateStart ?dateStart }
OPTIONAL { ?pa vocab:hasDateEnd ?dateEnd }
OPTIONAL { ?pa vocab:isUncertain ?isUncertain }
OPTIONAL { ?pa vocab:isDateStartUncertain ?isDateStartUncertain }
OPTIONAL { ?pa vocab:hasPostAssertionNote ?note .
?note vocab:hasNoteText ?noteText }
OPTIONAL { ?pa vocab:hasSecondarySource/rdfs:label ?source }
}
ORDER BY ?dateStartDPRR gives POMP0853 the following career:
| Office | Date | Uncertain | Date uncertain |
|---|---|---|---|
| consul | 231 BC | -- | -- |
| augur | 230–205 BC | yes | -- |
| decemvir sacris faciundis | 230–205 BC | yes | -- |
| praetor | 219 BC | -- | yes |
| praetor | 217 BC | yes | -- |
| magister equitum | 217 BC | -- | -- |
| praetor | 216 BC | yes | -- |
| augur (death) | 204 BC | -- | -- |
| decemvir sacris faciundis (death) | 204 BC | -- | -- |
Four of nine PostAssertions carry isUncertain = true. One carries isDateStartUncertain = true. A 56% uncertainty rate across the career record.
Nomenclatural ambiguity. Broughton's note (MRR I) lays out the core problem: the sources mention a M. Pomponius Matho as consul (231), praetor peregrinus (217), praetor (216), and magister equitum (217). These may be one, two, three, or four individuals. The filiation on the Fasti Capitolini for the magister equitum is partially illegible. The stone is "so worn" that it could read either M'. f. M. n. or M'. f. M'. n. The first reading produces a different person from the consul of 231; the second makes them identical.
Circularity. DPRR resolves this by following Mommsen and Degrassi's reading: the consul of 231 was chosen magister equitum for the elections of 216. But this is an editorial decision, not an independently attested fact. The uncertainty flags on the praetor assertions for 217 and 216 preserve the alternative, that these were held by a different M. Pomponius Matho, perhaps a son or nephew. The dataset encodes both the resolution and the doubt, but a naive query that counts offices would miss the ambiguity entirely.
Back-calculation. The praetor assertion dated to 219 (Brennan 2000) carries isDateStartUncertain = true. This date is inferred from career-sequence reasoning. If Matho was consul in 231, a praetorship fits somewhere in the 220s. It is not independently attested. (The lex Villia annalis wasn't passed until 180 BC, but Broughton applied similar career-ordering logic to earlier periods.)
The "note 4" cascade. Nearly every assertion on this record cross-references "217, note 4", Broughton's extended discussion of the Pomponii identification problem. When a single scholarly note becomes load-bearing for an entire career reconstruction, the uncertainty propagates through every assertion that depends on it. The DPRR model attaches notes to individual PostAssertions rather than to the Person, so you need to read multiple notes to discover they all cite the same underlying problem.
A query asking "who held the most offices between 231 and 204 BC?" would rank M. Pomponius Matho highly. But the record is better understood as a single editorial hypothesis about which Pomponius was which, not as nine independently attested data points. The uncertainty flags are the dataset telling you this, if you know to ask.
Interactive visualizations built from the same pipeline:
- Praetorships of the Middle to Late Republic, a stacked bar chart of 996 praetor records across 23 decades (264–31 BC), broken down by certainty category, with a searchable data table linking back to DPRR
- Family Trees, prosopographic family reconstructions rendered as SVG diagrams with sourced data tables, including the Fulvii Flacci & Calpurnii Pisones and the Atilii Reguli
What doesn't
The system inherits every limitation of its source data, and this is where it gets interesting.
DPRR is a database of secondary sources. It encodes what Broughton, Zmeskal, and Rüpke concluded about the ancient evidence, not the ancient evidence itself. Every query result should be understood as "according to Broughton, as digitized by the DPRR team," not "according to the ancient sources."
I wrote a Caveat Utilitor document that catalogs the risks:
- Elite bias. The dataset covers only the political upper strata. Women appear almost exclusively through family relationships to male office-holders.
- Argument from silence. Zero results for a query doesn't mean something didn't happen. The fasti are fragmentary. Broughton synthesized rather than exhausted.
- Nomenclatural ambiguity. Roman naming conventions create fundamental identification problems. A name that appears to indicate a family connection may reflect coincidence or conventions we don't fully understand.
- Temporal unevenness. The late Republic is densely documented. The early Republic rests on traditions of questionable historicity.
The LLM handles these caveats well when prompted. It includes uncertainty flags, cites the specific secondary sources behind each assertion, and explains gaps. But it wouldn't do this naturally. The system prompt and the Caveat Utilitor document do the heavy lifting. Left to its own devices, the model would present DPRR query results as facts rather than scholarly interpretations.
Why MCP
MCP made this project straightforward. The server runs as a standalone HTTP process. Claude Code or Claude Desktop connects to it over the network. No plugin architecture, no custom API integration, no SDK bindings. Define tools, implement handlers, deploy.
The three-tool design maps cleanly to MCP's tool model: each tool has a clear input/output contract, the LLM decides when and how to call them, and the server handles all the SPARQL and RDF complexity behind the interface.
Bundling the data locally (with auto-download from GitHub releases) solved the upstream reliability problem. The server initializes in seconds and doesn't depend on romanrepublic.ac.uk being up.
The meta-lesson
The interesting thing about this project isn't the Roman Republic. It's the pattern.
Structured data with a formal query language is a better RAG target than unstructured text. You don't need vector embeddings when you have a schema. You don't need fuzzy matching when you have SPARQL. The LLM's job becomes translation (natural language → query language) and synthesis (structured results → readable narrative), both of which it's good at.
But the hardest part wasn't the technical pipeline. It was the epistemological framing, making sure the system represents what it actually knows versus what it appears to know. A query that returns two praetors for 150 BC is only useful if the response explains that six were elected annually and the gap is consistent with source survival patterns for the mid-second century.
That's a human-in-the-loop problem. The system prompt, the Caveat Utilitor document, the uncertainty flags are all domain expertise encoded as guardrails. The LLM is good at following them. It's not good at inventing them.
The tools are ready. The question is whether the people using them know enough to set the right boundaries.
The code is on GitHub: gillisandrew/dprr-mcp
References
- Mouritsen, H., Mayfield, J. & Bradley, J. (2017). Digital Prosopography of the Roman Republic (DPRR). King's Digital Lab, King's College London.
- Bradley, J. (2020). A Prosopography as Linked Open Data. Digital Humanities Quarterly, 14(2).
- Broughton, T. R. S. (1951–1986). The Magistrates of the Roman Republic. 3 vols. American Philological Association / Scholars Press.
- Brennan, T. C. (2000). The Praetorship in the Roman Republic. 2 vols. Oxford University Press.
- Rüpke, J. (2008). Fasti Sacerdotum: A Prosopography of Pagan, Jewish, and Christian Religious Officials in the City of Rome, 300 BC to AD 499. Oxford University Press.
- Zmeskal, K. (2009). Adfinitas: Die Verwandtschaften der senatorischen Führungsschicht der römischen Republik von 218–31 v. Chr. Verlag Karl Stutz.
- Déjean, S. et al. (2024). sparql-llm: SPARQL query generation for RDF knowledge graphs using LLMs. Swiss Institute of Bioinformatics.