Build Your Own Modeling Tool with Linked.Archi¶
The Build Your Own Metamodel guide ends with a section called "How a Modeling Tool Consumes This" — seven bullet points and a bootstrap query. This article picks up where that one stops. It shows how to build a working modeling tool that loads a Linked.Archi metamodel and provides a palette, canvas, validation, viewpoint filtering, document generation, and AI agent access.
The Cloud Platform Walkthrough provides the worked metamodel example. Everything here uses that metamodel as the running scenario.
What You Are Building¶
A Linked.Archi-aware modeling tool has seven components:
┌─────────────────────────────────────────────────────────────────────┐
│ Modeling Tool │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌───────────────────────────┐ │
│ │ 1. Metamodel │ │ 2. Palette │ │ 3. Canvas / Editor │ │
│ │ Loader │──│ Builder │──│ (elements + edges) │ │
│ └──────┬───────┘ └──────────────┘ └───────────┬───────────────┘ │
│ │ │ │
│ ┌──────┴───────┐ ┌──────────────┐ ┌───────────┴───────────────┐ │
│ │ 4. SHACL │ │ 5. Viewpoint │ │ 6. Document │ │
│ │ Validator │ │ Filter │ │ Generator │ │
│ └──────────────┘ └──────────────┘ └───────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ 7. AI Agent Access (MCP) │ │
│ └──────────────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ RDF Store (Oxigraph / Comunica / rdflib) │ │
│ └──────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
You do not need all seven on day one. The minimum viable tool is components 1–3. Add the rest incrementally.
Technology Choices¶
Two proven stacks, depending on your team:
| Component | TypeScript / Browser | Python / Server |
|---|---|---|
| RDF store | Oxigraph WASM or Comunica | rdflib + Oxigraph (via oxrdflib) |
| SPARQL engine | Oxigraph WASM or Comunica | rdflib or Oxigraph server |
| SHACL validator | rdf-validate-shacl | pyshacl |
| Templating | Handlebars.js | pybars3 or Jinja2 |
| UI framework | React / Svelte / vanilla | Streamlit / Flask / none (CLI) |
| Graph visualization | D3.js / Cytoscape.js / ReactFlow | NetworkX + matplotlib / Graphviz |
| MCP server | @modelcontextprotocol/sdk (TypeScript) | mcp (Python) |
The Linked.Archi ecosystem uses both: the MCP server is TypeScript + Oxigraph, the static navigator is React + Comunica, and the validation pipeline is Java + RDF4J. Pick what fits your team.
This guide shows both stacks side by side where the patterns differ.
Component 1: Metamodel Loader¶
The metamodel manifest (arch:Metamodel) is the single entry point. Load it, follow the properties, and you have the complete modeling language.
The Bootstrap Query¶
PREFIX arch: <https://meta.linked.archi/core#>
PREFIX skos: <http://www.w3.org/2004/02/skos/core#>
SELECT ?property ?value WHERE {
?metamodel a arch:Metamodel ;
skos:prefLabel ?label .
?metamodel ?property ?value .
VALUES ?property {
arch:modelConcepts
arch:formalRules
arch:architectureViewpoints
arch:conceptClassification
arch:hasDeliverableTemplate
arch:referenceData
arch:presentationContextScheme
arch:derivationRules
arch:referenceModels
arch:architectureViewpointsRestrictions
}
}
For the Cloud Platform example, this returns:
| property | value |
|---|---|
arch:modelConcepts |
<https://meta.linked.archi/examples/cloudplatform/onto#> |
arch:formalRules |
<https://meta.linked.archi/examples/cloudplatform/shapes#> |
arch:architectureViewpoints |
<https://meta.linked.archi/examples/cloudplatform/viewpoints#> |
arch:conceptClassification |
cptax:CloudPlatformConceptScheme |
arch:hasDeliverableTemplate |
cpdt:ServiceCatalog, cpdt:DeploymentGuide |
arch:referenceData |
cprd:CloudPlatformReferenceData |
TypeScript Implementation¶
import { Store, Parser } from 'n3';
import { QueryEngine } from '@comunica/query-sparql';
async function loadMetamodel(files: string[]): Promise<Store> {
const store = new Store();
const parser = new Parser();
for (const file of files) {
const ttl = await fs.readFile(file, 'utf-8');
const quads = parser.parse(ttl);
store.addQuads(quads);
}
return store;
}
async function bootstrap(store: Store) {
const engine = new QueryEngine();
const result = await engine.queryBindings(`
PREFIX arch: <https://meta.linked.archi/core#>
SELECT ?metamodel ?label ?property ?value WHERE {
?metamodel a arch:Metamodel ; skos:prefLabel ?label .
?metamodel ?property ?value .
VALUES ?property {
arch:modelConcepts arch:formalRules
arch:architectureViewpoints arch:conceptClassification
arch:hasDeliverableTemplate arch:referenceData
arch:presentationContextScheme
}
}
`, { sources: [store] });
const bindings = await result.toArray();
// Group by property → build a MetamodelDescriptor
return groupByProperty(bindings);
}
Python Implementation¶
from rdflib import Graph, Namespace
ARCH = Namespace("https://meta.linked.archi/core#")
def load_metamodel(files: list[str]) -> Graph:
g = Graph()
for f in files:
g.parse(f, format="turtle")
return g
def bootstrap(g: Graph) -> dict:
query = """
PREFIX arch: <https://meta.linked.archi/core#>
PREFIX skos: <http://www.w3.org/2004/02/skos/core#>
SELECT ?metamodel ?label ?property ?value WHERE {
?metamodel a arch:Metamodel ; skos:prefLabel ?label .
?metamodel ?property ?value .
VALUES ?property {
arch:modelConcepts arch:formalRules
arch:architectureViewpoints arch:conceptClassification
arch:hasDeliverableTemplate arch:referenceData
arch:presentationContextScheme
}
}
"""
results = g.query(query)
return group_by_property(results)
What to Load¶
Load files in dependency order. The metamodel manifest imports the core ontology, which imports nothing. The ontology imports ArchiMate 4.0 (if extending it). Load them all into one graph:
core/core-onto.ttl ← always
modelingLanguages/archimate/4.0/archimate4-onto.ttl ← if extending ArchiMate
examples/custom-metamodel/cloudplatform-onto.ttl ← your metamodel
examples/custom-metamodel/cloudplatform-metamodel.ttl ← the manifest
examples/custom-metamodel/cloudplatform-tax.ttl ← taxonomy
examples/custom-metamodel/cloudplatform-shapes.ttl ← SHACL shapes
examples/custom-metamodel/cloudplatform-viewpoints.ttl
examples/custom-metamodel/cloudplatform-deliverable-templates.ttl
examples/custom-metamodel/cloudplatform-reference-data.ttl
Then load the user's model data on top:
Component 2: Palette Builder¶
The palette is the list of element types and relationship types the user can place on the canvas. It comes entirely from the ontology — no hardcoding.
Discovering Element Types¶
PREFIX arch: <https://meta.linked.archi/core#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX skos: <http://www.w3.org/2004/02/skos/core#>
SELECT ?class ?label ?definition ?icon ?parent WHERE {
?class rdfs:subClassOf+ arch:Element ;
skos:prefLabel ?label .
OPTIONAL { ?class skos:definition ?definition }
OPTIONAL { ?class arch:prefVisNotation ?icon }
OPTIONAL { ?class rdfs:subClassOf ?parent .
?parent rdfs:subClassOf+ arch:Element }
FILTER(lang(?label) = "en" || lang(?label) = "")
}
ORDER BY ?parent ?label
For the Cloud Platform metamodel, this returns Microservice, APIGateway, Container, KubernetesCluster, MessageBroker, EventTopic, ManagedDatabase, CacheStore, ObservabilityStack — plus all inherited ArchiMate 4.0 types.
Discovering Relationship Types¶
PREFIX arch: <https://meta.linked.archi/core#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX skos: <http://www.w3.org/2004/02/skos/core#>
SELECT ?class ?label ?predicate ?sourceDomain ?targetRange WHERE {
?class rdfs:subClassOf arch:QualifiedRelationship ;
skos:prefLabel ?label ;
arch:unqualifiedForm ?predicate .
OPTIONAL { ?predicate arch:domainIncludes ?sourceDomain }
OPTIONAL { ?predicate arch:rangeIncludes ?targetRange }
FILTER(lang(?label) = "en" || lang(?label) = "")
}
This tells the tool: "A Dependency can go from a Microservice to an ApplicationComponent." The tool uses this to enable/disable connection handles on the canvas.
Grouping by Taxonomy¶
The SKOS taxonomy provides palette groups. Elements link to taxonomy concepts via rdfs:seeAlso:
PREFIX skos: <http://www.w3.org/2004/02/skos/core#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
SELECT ?group ?groupLabel ?class ?classLabel WHERE {
?class rdfs:seeAlso ?group .
?group a skos:Concept ;
skos:prefLabel ?groupLabel .
?class skos:prefLabel ?classLabel .
FILTER(lang(?groupLabel) = "en" || lang(?groupLabel) = "")
FILTER(lang(?classLabel) = "en" || lang(?classLabel) = "")
}
ORDER BY ?groupLabel ?classLabel
Result for the Cloud Platform metamodel:
| Group | Elements |
|---|---|
| Runtime Services | Microservice, APIGateway, ManagedDatabase, CacheStore |
| Integration | MessageBroker, EventTopic |
| Infrastructure | Container, KubernetesCluster |
| Observability | ObservabilityStack |
Building the Palette Data Structure¶
interface PaletteGroup {
iri: string;
label: string;
elements: PaletteItem[];
}
interface PaletteItem {
iri: string; // e.g., cp:Microservice
label: string; // "Microservice"
definition: string; // tooltip text
icon: string; // SVG URL from arch:prefVisNotation
parentClass: string; // e.g., am4:ApplicationComponent
}
interface RelationshipType {
iri: string; // e.g., cp:Dependency
label: string; // "Dependency"
predicate: string; // cp:dependsOn
validSources: string[]; // [cp:Microservice]
validTargets: string[]; // [am4:ApplicationComponent]
}
Filtering: Show Only Your Custom Types¶
If you extend ArchiMate 4.0 but only want to show your custom types in the palette (not all 55 ArchiMate types), filter by namespace:
PREFIX arch: <https://meta.linked.archi/core#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
SELECT ?class ?label WHERE {
?class rdfs:subClassOf+ arch:Element ;
skos:prefLabel ?label .
FILTER(STRSTARTS(STR(?class), "https://meta.linked.archi/examples/cloudplatform/"))
}
Or show both, but visually separate them — custom types in the primary palette, inherited types in a collapsible "ArchiMate 4.0" section.
Component 3: Canvas / Editor¶
The canvas is where users place elements and draw relationships. This is the most UI-intensive component and the one where technology choice matters most.
Recommended Libraries¶
| Library | Strengths | Best For |
|---|---|---|
| ReactFlow | React-native, handles, minimap, built-in layout | Web-based tools with React |
| Cytoscape.js | Graph-focused, many layouts, extensions | Graph-heavy tools |
| D3.js | Full control, force layouts | Custom visualizations |
| JointJS | Diagramming-focused, ports, links | Traditional diagramming tools |
Creating Model Data¶
When a user drags a Microservice from the palette onto the canvas, the tool creates RDF triples:
@prefix cp: <https://meta.linked.archi/examples/cloudplatform/onto#> .
@prefix skos: <http://www.w3.org/2004/02/skos/core#> .
@prefix : <https://model.example.com/my-platform#> .
:OrderService a cp:Microservice ;
skos:prefLabel "Order Service"@en ;
cp:resilienceLevel "critical" ;
cp:deploymentStrategy "blue-green" ;
cp:serviceTier "tier-1" .
Creating Relationships¶
When a user draws an edge from OrderService to PaymentService, the tool creates both the direct triple and the qualified relationship:
# Direct triple — for graph traversal
:OrderService cp:dependsOn :PaymentService .
# Qualified relationship — for metadata and management
:OrderService cp:qualifiedDependsOn :dep-order-payment .
:dep-order-payment a cp:Dependency ;
arch:source :OrderService ;
arch:target :PaymentService ;
skos:prefLabel "Order → Payment dependency"@en .
Connection Validation¶
Before allowing a user to complete an edge, check whether the relationship type permits that source→target combination. Use the arch:domainIncludes / arch:rangeIncludes data from the palette query:
function canConnect(
sourceType: string,
targetType: string,
relationshipType: RelationshipType,
classHierarchy: Map<string, string[]> // class → superclasses
): boolean {
const sourceValid = relationshipType.validSources.some(
s => sourceType === s || classHierarchy.get(sourceType)?.includes(s)
);
const targetValid = relationshipType.validTargets.some(
t => targetType === t || classHierarchy.get(targetType)?.includes(t)
);
return sourceValid && targetValid;
}
This uses the class hierarchy — a Microservice is a valid source for any relationship that accepts ApplicationComponent, because cp:Microservice rdfs:subClassOf am4:ApplicationComponent.
Property Panel¶
When the user selects an element, show a property panel. The properties come from the ontology:
PREFIX arch: <https://meta.linked.archi/core#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX skos: <http://www.w3.org/2004/02/skos/core#>
SELECT ?property ?label ?range WHERE {
?property arch:domainIncludes ?elementType ;
skos:prefLabel ?label .
OPTIONAL { ?property rdfs:range ?range }
FILTER(lang(?label) = "en" || lang(?label) = "")
}
For cp:Microservice, this returns resilienceLevel, deploymentStrategy, serviceTier — each with its datatype. The SHACL shapes provide the allowed values (sh:in) for dropdowns.
Extracting Allowed Values from SHACL¶
PREFIX sh: <http://www.w3.org/ns/shacl#>
PREFIX skos: <http://www.w3.org/2004/02/skos/core#>
SELECT ?property ?allowedValue WHERE {
?shape sh:targetClass ?elementType ;
sh:property ?propShape .
?propShape sh:path ?property ;
sh:in/rdf:rest*/rdf:first ?allowedValue .
}
For cp:resilienceLevel on cp:Microservice, this returns: "critical", "high", "medium", "low" — ready for a dropdown.
Component 4: Live SHACL Validation¶
SHACL shapes turn the metamodel's rules into real-time feedback — red squiggles when a required property is missing, warnings when a relationship has an invalid target.
How It Works¶
- The user edits the model (adds an element, changes a property, draws an edge).
- The tool serializes the current model graph.
- The SHACL engine validates the model graph against the shapes graph.
- Validation results are mapped back to canvas elements as errors/warnings.
TypeScript: rdf-validate-shacl¶
import SHACLValidator from 'rdf-validate-shacl';
import { DataFactory } from 'n3';
async function validate(
dataStore: Store,
shapesStore: Store
): Promise<ValidationResult[]> {
const validator = new SHACLValidator(shapesStore, { factory: DataFactory });
const report = validator.validate(dataStore);
return report.results.map(r => ({
focusNode: r.focusNode?.value, // the element IRI
path: r.path?.value, // the property that failed
message: r.message?.[0]?.value, // human-readable message
severity: r.severity?.value, // sh:Violation, sh:Warning, sh:Info
sourceShape: r.sourceShape?.value, // which shape triggered this
}));
}
Python: pyshacl¶
from pyshacl import validate as shacl_validate
def validate(data_graph, shapes_graph):
conforms, results_graph, results_text = shacl_validate(
data_graph,
shacl_graph=shapes_graph,
inference='none',
abort_on_first=False,
)
# Parse results_graph for individual violations
return parse_validation_results(results_graph)
Mapping Results to the Canvas¶
Each validation result has a focusNode — the IRI of the element that failed. Map this back to the canvas node and show the error:
function applyValidationResults(
canvas: CanvasState,
results: ValidationResult[]
) {
// Clear previous markers
canvas.clearAllMarkers();
for (const result of results) {
const node = canvas.findNodeByIRI(result.focusNode);
if (!node) continue;
if (result.severity.endsWith('Violation')) {
node.addMarker('error', result.message);
} else if (result.severity.endsWith('Warning')) {
node.addMarker('warning', result.message);
} else {
node.addMarker('info', result.message);
}
}
}
When to Validate¶
- On every edit — if the model is small (< 500 elements). SHACL validation is fast for small graphs.
- On save — if the model is large. Debounce to avoid blocking the UI.
- On demand — a "Validate" button that runs the full check.
For the CI/CD pipeline, the existing .scripts/validate.sh handles batch validation using RDF4J:
What the Cloud Platform Shapes Catch¶
The cloudplatform-shapes.ttl enforces rules like:
- Every Microservice must have a
skos:prefLabel,resilienceLevel resilienceLevelmust be one of: critical, high, medium, lowdeploymentStrategymust be one of: blue-green, canary, rolling, recreate- Every Container must have a
containerImageand positivereplicaCount - Every Dependency must have
arch:sourceandarch:target - Critical microservices must be tier-1 (conditional rule)
Component 5: Viewpoint Filter¶
Viewpoints restrict which element and relationship types are visible in a given view. When the user opens a "Service Dependency View", only the relevant types appear in the palette and on the canvas.
Discovering Viewpoints¶
PREFIX arch: <https://meta.linked.archi/core#>
PREFIX skos: <http://www.w3.org/2004/02/skos/core#>
SELECT ?viewpoint ?label ?definition ?viewType WHERE {
?viewpoint a arch:Viewpoint ;
skos:prefLabel ?label .
OPTIONAL { ?viewpoint skos:definition ?definition }
OPTIONAL { ?viewpoint arch:viewType ?viewType }
FILTER(lang(?label) = "en" || lang(?label) = "")
}
For the Cloud Platform metamodel, this returns four viewpoints: Service Dependency, Deployment, Data Flow, Platform Overview.
Getting Allowed Concepts for a Viewpoint¶
PREFIX arch: <https://meta.linked.archi/core#>
SELECT ?concept WHERE {
<viewpoint-iri> arch:includesConcept ?concept .
}
The Service Dependency viewpoint includes: cp:Microservice, cp:APIGateway, am4:ApplicationComponent, cp:Dependency. Only these types appear in the palette when this viewpoint is active.
Filtering the Palette¶
function filterPalette(
fullPalette: PaletteGroup[],
allowedConcepts: Set<string>,
classHierarchy: Map<string, string[]>
): PaletteGroup[] {
return fullPalette
.map(group => ({
...group,
elements: group.elements.filter(el =>
allowedConcepts.has(el.iri) ||
classHierarchy.get(el.iri)?.some(parent => allowedConcepts.has(parent))
),
}))
.filter(group => group.elements.length > 0);
}
Filtering the Canvas¶
Elements already on the canvas that don't belong to the active viewpoint can be dimmed or hidden:
function applyViewpointFilter(
canvas: CanvasState,
allowedConcepts: Set<string>
) {
for (const node of canvas.nodes) {
const isAllowed = allowedConcepts.has(node.type) ||
classHierarchy.get(node.type)?.some(p => allowedConcepts.has(p));
node.setOpacity(isAllowed ? 1.0 : 0.2);
}
}
Viewpoint Metadata for the UI¶
Viewpoints also carry stakeholder and concern information — useful for a viewpoint picker:
PREFIX arch: <https://meta.linked.archi/core#>
PREFIX skos: <http://www.w3.org/2004/02/skos/core#>
SELECT ?viewpoint ?vpLabel ?stakeholder ?shLabel ?concern ?cLabel WHERE {
?viewpoint a arch:Viewpoint ;
skos:prefLabel ?vpLabel .
OPTIONAL {
?viewpoint arch:targetsStakeholder ?stakeholder .
?stakeholder skos:prefLabel ?shLabel .
}
OPTIONAL {
?viewpoint arch:viewpointFramesConcern ?concern .
?concern skos:prefLabel ?cLabel .
}
}
This lets you build a viewpoint picker that says: "Service Dependency View — for Developers and Architects — addresses Dependency Management concern."
Component 6: Document Generator¶
The document generation pipeline is fully declarative — the ontology defines what to extract, how to structure it, and how to render it. The tool contains no domain-specific logic.
For the full specification, see the Deliverable Generator Spec. This section shows how to implement it.
The Pipeline¶
arch:DeliverableTemplate
└─ arch:TemplateSection (ordered by arch:sectionOrder)
├─ arch:sectionQuery → SPARQL SELECT
├─ arch:sectionResultKey → JSON key name
└─ arch:sectionViewpoint → which viewpoint this section covers
arch:templateResource → path to .md.hbs Handlebars template
Step 1: Discover Template Sections¶
PREFIX arch: <https://meta.linked.archi/core#>
PREFIX skos: <http://www.w3.org/2004/02/skos/core#>
SELECT ?section ?order ?title ?resultKey ?query WHERE {
<template-iri> arch:templateHasSection ?section .
?section arch:sectionOrder ?order ;
skos:prefLabel ?title ;
arch:sectionResultKey ?resultKey ;
arch:sectionQuery ?query .
}
ORDER BY ?order
Step 2: Execute Queries and Assemble Context¶
import Handlebars from 'handlebars';
async function generateDocument(
templateIRI: string,
store: Store,
meta: Record<string, string>
): Promise<string> {
// 1. Get sections
const sections = await querySections(store, templateIRI);
// 2. Execute each section's SPARQL query
const context: Record<string, any> = { meta };
for (const section of sections) {
const results = await executeSPARQL(store, section.query);
context[section.resultKey] = results;
}
// 3. Load and render the Handlebars template
const templatePath = await getTemplateResource(store, templateIRI);
const templateSource = await fs.readFile(templatePath, 'utf-8');
const template = Handlebars.compile(templateSource);
return template(context);
}
Step 3: Render¶
The Cloud Platform metamodel includes two deliverable templates:
Service Catalog — lists all microservices with their properties:
# Service Catalog
Generated: {{meta.date}}
## Microservices
| Service | Resilience | Deployment | Tier |
|---------|-----------|------------|------|
{{#each microservices}}
| {{this.label}} | {{this.resilience}} | {{this.deployment}} | {{this.tier}} |
{{/each}}
## Dependencies
| Source | Target | Description |
|--------|--------|-------------|
{{#each dependencies}}
| {{this.sourceLabel}} | {{this.targetLabel}} | {{this.description}} |
{{/each}}
Deployment Guide — infrastructure topology:
# Deployment Guide
## Kubernetes Clusters
{{#each clusters}}
### {{this.label}}
- Region: {{this.region}}
- Node count: {{this.nodeCount}}
#### Containers
| Container | Image | Replicas |
|-----------|-------|----------|
{{#each this.containers}}
| {{this.label}} | {{this.image}} | {{this.replicas}} |
{{/each}}
{{/each}}
Output Formats¶
The arch:templateHasFormat property declares supported formats. For Markdown → HTML, use a Markdown renderer. For Markdown → PDF, pipe through Pandoc or WeasyPrint:
# Markdown → HTML
npx marked service-catalog.md -o service-catalog.html
# Markdown → PDF
pandoc service-catalog.md -o service-catalog.pdf
Component 7: AI Agent Access (MCP)¶
The Model Context Protocol (MCP) exposes the knowledge graph to AI agents. The Linked.Archi MCP server (ai-enablement/linked-archi-mcp/) provides a reference implementation using TypeScript + Oxigraph.
What the MCP Server Exposes¶
| Tool | What it does |
|---|---|
query-graph |
Execute arbitrary SPARQL against the model |
list-elements |
List all elements with type and label |
get-element |
Get full details for one element |
get-relationships |
Get all relationships for an element |
graph-stats |
Summary statistics (element counts by type, relationship counts) |
| Prompt | What it does |
|---|---|
impact-analysis |
Trace how a change to one element propagates |
architecture-overview |
Generate a natural-language summary of the model |
explore-ontology |
Explain the metamodel structure |
Building Your Own MCP Server¶
If your modeling tool has its own graph store, you can expose it via MCP:
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
const server = new McpServer({
name: 'my-architecture-tool',
version: '1.0.0',
});
server.tool('query-graph', { query: z.string() }, async ({ query }) => {
const results = await sparqlEngine.query(query);
return { content: [{ type: 'text', text: JSON.stringify(results) }] };
});
server.tool('list-elements', {}, async () => {
const results = await sparqlEngine.query(`
PREFIX arch: <https://meta.linked.archi/core#>
PREFIX skos: <http://www.w3.org/2004/02/skos/core#>
SELECT ?element ?label ?type WHERE {
?element a ?type ; skos:prefLabel ?label .
?type rdfs:subClassOf+ arch:Element .
}
`);
return { content: [{ type: 'text', text: JSON.stringify(results) }] };
});
const transport = new StdioServerTransport();
await server.connect(transport);
Why This Matters¶
With MCP, an AI agent can:
- "What microservices depend on the Payment Service?" →
query-graphwith a SPARQL query - "Show me all critical services without a deployment strategy" →
query-graphwith a SHACL-like filter - "Generate an impact analysis for removing the Order Database" →
impact-analysisprompt - "What's the overall health of this architecture?" →
architecture-overviewprompt
The agent queries the same graph the tool uses — no separate export or sync needed.
Presentation Contexts: Stakeholder-Specific Rendering¶
Presentation contexts are SKOS concepts that define how the same model looks for different audiences. They are not a separate component — they influence how components 2, 3, 5, and 6 behave.
How They Work¶
The metamodel declares a arch:presentationContextScheme — a SKOS ConceptScheme with concepts like Executive, Architect, Developer, Operations, Governance.
Each context implies:
| Aspect | Executive | Developer | Architect |
|---|---|---|---|
| Palette | Business elements only | Application + Technology | All domains |
| Detail level | Labels only | Full properties | Full properties + relationships |
| Viewpoints | Strategy, Capability | Service Dependency, Deployment | All viewpoints |
| Documents | Executive Summary | Service Catalog, Deployment Guide | Architecture Definition |
| Visual style | Simplified icons, large labels | Technical icons, code-style | Full ArchiMate notation |
Querying Presentation Contexts¶
PREFIX skos: <http://www.w3.org/2004/02/skos/core#>
SELECT ?context ?label ?definition WHERE {
?context skos:topConceptOf ?scheme ;
skos:prefLabel ?label ;
skos:definition ?definition .
FILTER(lang(?label) = "en" || lang(?label) = "")
}
Applying a Context¶
The tool reads the context's skos:definition to determine filtering rules. A more structured approach adds custom properties to each context:
:DeveloperContext
a skos:Concept ;
skos:prefLabel "Developer / Engineering"@en ;
arch:contextIncludesDomain :ApplicationDomain, :TechnologyDomain ;
arch:contextDefaultViewpoint :ServiceDependencyViewpoint ;
arch:contextDetailLevel "full" ;
arch:contextDeliverableTemplate :ServiceCatalog, :DeploymentGuide .
The tool queries these properties and adjusts the palette, default viewpoint, and available documents accordingly.
Putting It All Together: The Minimal Viable Tool¶
Here is the simplest possible tool — a CLI that loads a metamodel, validates a model, and generates a document:
// minimal-tool.ts
import { Store, Parser } from 'n3';
import SHACLValidator from 'rdf-validate-shacl';
import Handlebars from 'handlebars';
import { QueryEngine } from '@comunica/query-sparql';
async function main() {
const store = new Store();
const parser = new Parser();
const engine = new QueryEngine();
// 1. Load metamodel + model
const files = [
'core/core-onto.ttl',
'examples/custom-metamodel/cloudplatform-onto.ttl',
'examples/custom-metamodel/cloudplatform-metamodel.ttl',
'examples/custom-metamodel/cloudplatform-shapes.ttl',
'examples/custom-metamodel/cloudplatform-deliverable-templates.ttl',
'examples/custom-metamodel/example-model.ttl',
];
for (const file of files) {
const ttl = await Bun.file(file).text();
store.addQuads(parser.parse(ttl));
}
// 2. Validate
const shapesStore = new Store();
const shapesTtl = await Bun.file(
'examples/custom-metamodel/cloudplatform-shapes.ttl'
).text();
shapesStore.addQuads(parser.parse(shapesTtl));
const validator = new SHACLValidator(shapesStore);
const report = validator.validate(store);
if (!report.conforms) {
console.error('Validation errors:');
for (const r of report.results) {
console.error(` ${r.focusNode?.value}: ${r.message?.[0]?.value}`);
}
} else {
console.log('Model is valid.');
}
// 3. Generate document
const queryResult = await engine.queryBindings(`
PREFIX cp: <https://meta.linked.archi/examples/cloudplatform/onto#>
PREFIX skos: <http://www.w3.org/2004/02/skos/core#>
SELECT ?label ?resilience ?deployment ?tier WHERE {
?ms a cp:Microservice ;
skos:prefLabel ?label ;
cp:resilienceLevel ?resilience .
OPTIONAL { ?ms cp:deploymentStrategy ?deployment }
OPTIONAL { ?ms cp:serviceTier ?tier }
} ORDER BY ?label
`, { sources: [store] });
const microservices = (await queryResult.toArray()).map(b => ({
label: b.get('label')?.value,
resilience: b.get('resilience')?.value,
deployment: b.get('deployment')?.value ?? '—',
tier: b.get('tier')?.value ?? '—',
}));
const template = Handlebars.compile(`# Service Catalog
| Service | Resilience | Deployment | Tier |
|---------|-----------|------------|------|
{{#each microservices}}
| {{this.label}} | {{this.resilience}} | {{this.deployment}} | {{this.tier}} |
{{/each}}
`);
const output = template({ microservices });
console.log(output);
}
main();
Run it:
Output:
# Service Catalog
| Service | Resilience | Deployment | Tier |
|---------|-----------|------------|------|
| API Gateway | high | blue-green | tier-1 |
| Inventory Service | medium | rolling | tier-2 |
| Order Service | critical | blue-green | tier-1 |
| Payment Service | critical | canary | tier-1 |
| Shipping Service | medium | rolling | tier-2 |
From here, add a web UI (ReactFlow for the canvas, a sidebar for the palette, a panel for properties) and you have a visual modeling tool.
Incremental Build Path¶
| Phase | Components | Effort | What You Get |
|---|---|---|---|
| 1. CLI | Loader + Validator + Generator | 1–2 days | Validate models and generate documents from the command line |
| 2. Browser | + Palette + Canvas (ReactFlow) | 1–2 weeks | Visual editor with drag-and-drop elements and edges |
| 3. Smart Editor | + Viewpoint Filter + Property Panel | 1 week | Viewpoint-filtered palette, property editing with dropdowns from SHACL |
| 4. Full Tool | + Presentation Contexts + MCP | 1 week | Stakeholder-specific views and AI agent integration |
Each phase produces a usable tool. You don't need to build everything before shipping.
How This Relates to Existing Linked.Archi Tools¶
The Linked.Archi toolbox already implements several of these components:
| Existing Tool | Components It Implements |
|---|---|
| Static Navigator (React + Comunica) | Palette (type facets), Canvas (D3 graph), SPARQL workbench |
| MCP Server (TypeScript + Oxigraph) | AI Agent Access (all 5 tools + 3 prompts) |
| Validation Script (Java + RDF4J) | SHACL Validator (batch mode) |
| Deliverable Generator | Document Generator (full pipeline spec) |
A new modeling tool can reuse these directly or use them as reference implementations.
References¶
- Build Your Own Metamodel — The prerequisite: creating the semantic assets
- Cloud Platform Walkthrough — The worked metamodel example used throughout this guide
- Deliverable Templates Guide — The SPARQL + Handlebars generation pipeline
- Deliverable Generator Spec — Full tool specification for document generation
- Relationship Modeling Guide — The three-declaration pattern for relationships
- Design Decisions — DD-2 (OWL+SHACL), DD-3 (qualified relationships), DD-17 (metamodel as manifest)
- Validation Guide — SHACL validation pipeline
- Toolbox Overview — Existing tools in the ecosystem
- Semantic Architecture as Code — The Turtle-in-Git workflow
- ReactFlow — React library for node-based editors
- Comunica — Client-side SPARQL engine
- Oxigraph — Rust-based RDF store with SPARQL and WASM support
- rdf-validate-shacl — JavaScript SHACL validator
- pyshacl — Python SHACL validator
- Model Context Protocol — Standard for AI agent tool integration