WLO MCP Server
Click on "Install 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., "@WLO MCP ServerFinde Materialien zum Thema Bruchrechnen"
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.
WLO MCP Server
WLO MCP ist ein Model Context Protocol Server für WirLernenOnline.de (WLO) – die deutsche OER-Plattform für freie Bildungsmaterialien.
Kompatibel mit OpenAI (Responses API + native MCP), Anthropic Claude und allen anderen MCP-fähigen Clients.
Inhaltsverzeichnis
Related MCP server: Hugging Face Hub Semantic Search MCP
Was ist neu in v2
v2.1 — Performance & kombinierte Suche (2026-06)
search_wlo_all— neues Tool: Einzel-Inhalte + Sammlungen + Themenseiten in EINEM parallelen Aufruf, mit getrennten Töpfen (content/collections/topicPages). Spart dem Client mehrere separate Such-Aufrufe (= weniger Round-Trips / Cold-Starts).Kuratierter
propertyFilter— Such-/Bulk-Fetches fordern nur die ~24 real genutzten Felder statt-all-(~59) → deutlich kleinere Payloads._DISPLAYNAME-Labels bleiben erhalten;get_node_detailsbleibt bewusst auf-all-.Einheitliches Reranking —
rerankNodesgreift jetzt auch für Sammlungen und Themenseiten (zuvor nur Inhalte): Exakt-Treffer rücken nach oben, off-topic raus. Sicher — nur Umsortierung + Entfernen gelöschter Knoten, kein Score-Drop.Such-Varianten gedeckelt — Query-Expansion auf max. 5 parallele
ngsearch-Calls (Einzelterm-Varianten entfernt).WLO_POOL_SIZE(Env, Default 25, vorher fix 40) — Kandidaten-Pool je Such-Variante.Details, Einstellungen + Messungen: siehe
PERFORMANCE.md.
Hinzugekommen
get_subject_portals— listet die Fachportale (Top-Level-Sammlungen unter dem WLO-Wurzelknoten) deterministisch alphabetisch.browse_collection_tree— strukturierter Drilldown in Sub-Sammlungen (Tiefe 1 oder 2), optional mit File-Counts.wlo_health_check— Probe gegen die WLO-API, gibt Latenz + Status zurück.get_nodes_details— Bulk-Metadata für mehrerenodeIdsparallel.outputFormat: "json"für alle Such- und Detail-Tools — strukturierte Daten statt Markdown-Parsing.excludeNodeIds: string[]für die drei Such-Tools — bereits gesehene IDs aus Folge-Calls ausblenden.License- und TargetGroup-Vocabularies in
lookup_wlo_vocabulary(vocabulary: "license"/"targetGroup").
Verbessert
search_wlo_topic_pages:Mode C (Liste-aller) zeigt jetzt die Sammlungsnamen als Titel statt kryptischer
PAGE_VARIANT_TEMPLATE_xxx-Slugs (Auto-Resolve viagetNodeParents).Mehrere Varianten derselben Sammlung werden per
mergeVariants=true(Default) zu einer Karte zusammengefasst.targetGroupLabelliefert lesbare Labels ("Lehrkräfte"/"Lernende"/"Allgemein") statt Slugs /ccrep://-URIs.sort: "alpha" | "relevance"mit deterministischem Tie-Breaker aufnodeId.
get_node_details:Output-Felder sind jetzt identisch zu
formatNode()der Such-Tools —disciplines,educationalContexts,userRoles,learningResourceTypes,licenseals menschenlesbare Labels ("Mathematik","CC BY-SA 4.0") statt URIs.Optional
includeRaw: truezeigt zusätzlich die unaufgelösten URIs für Debugging / Spezialfälle.
Lizenz-Mapping: Roh-Keys wie
CC_BY_SAwerden überall zu"CC BY-SA 4.0"aufgelöst.Reranker (
reranker.ts): deterministischer Tie-Breaker aufnodeIdbei gleichem Score; neuesortByTitle()für stabile alphabetische Sortierung.
Entfernt (Breaking Changes)
get_wirlernenonline_info,get_edu_sharing_network_info,get_edu_sharing_product_info,get_metaventis_info— die Webseiten-Crawler-Tools sind weg. Diese Aufgabe übernimmt jetzt das Konsumenten-eigene RAG (z.B. das BadBoerdi-RAG).Damit auch
WEB_CONTENT_WHITELISTundfetchWebContent()auswlo-api.tsentfernt.environment-Parameter aus allen Tool-Schemas entfernt. Die Repository-Auswahl läuft jetzt ausschließlich über die Env-VariableWLO_REPOSITORY_URL(siehe „Umgebungsvariablen"). Pro Server-Instanz wird genau eine Edu-Sharing-Welt adressiert; Konsumenten zeigen ihren MCP-Client auf die jeweilige URL.WLO_ENVist weg — ersetzt durchWLO_REPOSITORY_URL.WLO_ROOT_COLLECTION_IDS(Record) wurde zuWLO_ROOT_COLLECTION_ID(eine Konstante, optional per Env überschreibbar).
Backward-Kompatibilität für MCP-Clients: Tool-Inputs werden mit Zod's Default-Mode (
.strip) validiert — schickt ein alter Client denenvironment-Key trotzdem mit, wird er silent ignoriert, ohne Crash. Der Server antwortet aber immer mit den Daten der konfigurierten Repository-URL. Wer aktiv Staging-Daten wollte, muss seinen MCP-Client auf den Staging-Deployment-Endpoint umstellen (z.B.https://wlo-mcp-server-staging.vercel.app/mcp).
Installation
Voraussetzungen
Node.js ≥ 18.x
npm ≥ 9.x
Lokale Installation
git clone https://github.com/yourorg/wlomcp.git
cd wlomcp
npm install
npm run buildUmgebungsvariablen
cp .env.example .envVariable | Werte | Standard | Beschreibung |
| URL des Edu-Sharing-Frontend-Hosts (z.B. | WLO-Production | Edu-Sharing-Instanz, gegen die der Server arbeitet. Pfade sind in allen Instanzen identisch ( |
| UUID |
| Wurzelknoten der Sammlungs-Hierarchie. Identisch auf WLO-Production und -Staging. Override nur nötig, wenn ein eigenständiges Repository mit anderem Root läuft. |
| Zahl |
| HTTP-Port (nur HTTP-Modus) |
| Zahl |
| Kandidaten-Pool je Such-Variante ( |
Ein Server = ein Repository. Der MCP zeigt pro Prozess auf genau eine Edu-Sharing-Instanz. Wer parallel beide Welten anbieten will, deployt zwei Instanzen (z.B. zwei Vercel-Projekte mit unterschiedlichem
WLO_REPOSITORY_URL).
Server starten
node dist/http.js # HTTP-Modus → http://localhost:3000/mcp
node dist/stdio.js # stdio (Claude Desktop, lokale Clients)
npm run dev # stdio mit Auto-Reload
npm run dev:http # HTTP mit Auto-ReloadTools im Überblick
# | Tool | Zweck | Output-Formate |
1 |
| Sammlungen suchen (Volltext + Tree-Fallback) | markdown / json |
2 |
| Globale Volltextsuche nach Bildungsinhalten | markdown / json |
3 |
| Inhalte/Sub-Sammlungen einer Sammlung (paginierbar) | markdown / json |
4 |
| Detail-Metadaten + optional Volltext + Eltern | markdown / json |
5 |
| Vokabular-Werte: Bildungsstufe, Fach, Zielgruppe, LRT, Lizenzen, TargetGroup | markdown |
6 |
| Themenseiten finden / listen, Varianten zusammenfassen | markdown / json |
7 |
| Fachportale (Top-Level-Sammlungen unter WLO-Root) | markdown / json |
8 |
| Drilldown in Sub-Sammlungen (Tiefe 1–2), optional mit File-Counts | markdown / json |
9 |
| Status + Latenz der WLO-API | json |
10 |
| Bulk-Metadata für mehrere | json |
11 |
| Kombiniert: Inhalte + Sammlungen + Themenseiten in EINEM parallelen Aufruf, getrennte Töpfe | json / markdown |
Konzept: In WLO sind Sammlungen und Themenseiten dasselbe. Eine Sammlung wird im Repository als Themenseite angezeigt und bündelt Inhalte in Schwimmlinien (Swimlanes/Karussells). Sub-Sammlungen entsprechen Unter-Themenseiten. Sammlungen mit
ccm:page_config_refhaben eine kuratierte Themenseite mit zielgruppenspezifischen Varianten (Lehrkräfte / Lernende / Allgemein).
Tool-Routing-Heuristik (für LLMs):
User fragt breit nach einem Thema und will Inhalte + Sammlungen + Themenseiten gemeinsam →
search_wlo_all(ein Aufruf, getrennte Töpfe)User fragt nach Material/Inhaltstyp (Video, Arbeitsblatt, …) →
search_wlo_contentUser fragt nach Themenseite/Sammlung zu einem Thema →
search_wlo_topic_pages(Mode B mit query)User will durch ein Fach navigieren (Drilldown) → erst
get_subject_portals, dannbrowse_collection_treeUser klickt eine Karte →
get_node_detailsmit dieser nodeIdBot zeigt 10 Karten und braucht Metadaten zu allen →
get_nodes_details(nodeIds=[...])(1 Aufruf statt 10)
Tools – Detail & API-Endpunkte
1. search_wlo_collections
Sucht thematische Sammlungen. Drei-stufige Strategie: Volltext-API → Baum-Traversierung Level 2 (≤25 Parents) → Level 3 (≤15 Parents). Treffer werden bei vorhandenem query nach Relevanz gerankt (rerankNodes), bevor maxResults greift — bringt Exakt-Treffer nach oben.
Parameter | Typ | Default | Beschreibung |
| string |
| Suchbegriff. Leer = Top-Level-Sammlungen unter Root oder |
| string | – | nodeId einer Eltern-Sammlung für Sub-Tree-Suche |
| string | – | Bildungsstufe (Label oder URI) |
| string | – | Fach (Label oder URI) |
| string | – | Zielgruppe (Label oder URI) |
| int |
| 1–20 |
| string[] | – | Diese IDs in der Antwort überspringen |
| enum |
|
|
API-Endpunkte:
POST /search/v1/queries/-home-/mds_oeh/collections?contentType=COLLECTIONS (Volltext)
GET /node/v1/nodes/-home-/{nodeId}/children?filter=folders (Tree)2. search_wlo_content
Globale Volltextsuche nach Bildungsmaterialien (Files). Mit Multi-Query-Expansion + RRF (clientseitig, ohne Transformer-Modell — Vercel-tauglich).
Parameter | Typ | Default | Beschreibung |
| string | Pflicht | Suchbegriff |
| string | – | Bildungsstufe |
| string | – | Fach |
| string | – | Zielgruppe |
| string | – | Ressourcentyp (Arbeitsblatt, Video, …) |
| string | – | Anbieter-Filter (Klexikon, Serlo, ZUM, …) |
| int |
| 1–20 |
| string[] | – | IDs überspringen |
| enum |
|
|
Reranking-Pipeline:
Query Expansion: Volltext, Title-Match, Keyword-Hits, Synonym-Map — gedeckelt auf max. 5 Varianten (nach Gewicht,
full:immer dabei)Parallele API-Calls (
WLO_POOL_SIZETreffer / Variante, Default 25)RRF (Reciprocal Rank Fusion) mit Variant-Gewichtung
Quality Score (Titel: 30 Pt., Keywords: 10 Pt., Beschreibung: 8 Pt., Metadaten-Qualität)
Endformel:
0.8 × quality + 0.1 × rrf + 0.1 × multi_appearance_bonusTie-Breaker:
nodeId.localeCompare()für deterministische Reihenfolge bei gleichen Scores
3. get_collection_contents
Inhalte einer Sammlung. Unterstützt Pagination via skipCount und Filter.
Parameter | Typ | Default | Beschreibung |
| string | Pflicht | nodeId der Sammlung |
| string | – | Optional zum Reranking |
| enum |
|
|
| bool |
| Rekursive BFS-Traversierung (nur bei |
| int |
| 1–100 |
| int |
| Pagination-Offset |
| string[] | – | IDs überspringen |
| enum |
|
|
Tipp: Statt
contentFilter="folders"istbrowse_collection_tree(Tool 8) klarer und liefert direkt File-Counts.
4. get_node_details
Detail-Metadaten zu einem einzelnen Node (Content oder Sammlung).
Parameter | Typ | Default | Beschreibung |
| string | Pflicht | – |
| bool |
| Gespeicherten Volltext (gecrawlt) abrufen, max. 4000 Zeichen |
| bool |
| Eltern-Sammlungen mitliefern |
| bool |
| Zusätzlich die rohen URI-Werte (vor Label-Resolution) |
| enum |
|
|
Output-Konsistenz: Im JSON-Modus liefert get_node_details dieselben Felder wie ein Eintrag aus search_wlo_* (Output-Format formatNode()):
{
"nodeId": "bd8be6d5-…",
"title": "Mathematik",
"description": "…",
"keywords": ["…"],
"disciplines": ["Mathematik"],
"educationalContexts": ["Sekundarstufe i", "Hochschule"],
"userRoles": [],
"learningResourceTypes": [],
"license": "CC BY-SA 4.0",
"publisher": "ZUM",
"url": "https://…",
"previewUrl": "https://…",
"topicPageUrl": "https://…/topic-pages?collectionId=…",
"renderUrl": "https://…/components/render/…",
"nodeType": "collection",
"parents": [{"nodeId": "…", "title": "…"}], // wenn includeParents
"textContent": "…", // wenn includeTextContent
"raw": { … } // wenn includeRaw
}5. lookup_wlo_vocabulary
Listet Vokabular-Werte. Quelle: lokale src/vocabs.ts (keine API-Calls).
Parameter | Werte |
|
|
6. search_wlo_topic_pages
Themenseiten finden, listen oder eine spezifische Sammlung prüfen.
Parameter | Typ | Default | Beschreibung |
| string |
| Thematische Suche (Mode B) |
| enum | – |
|
| string | – | Bildungsstufe |
| string | – | Direkt-Check einer Sammlung (Mode A) |
| bool |
| Mehrere Varianten derselben Sammlung in eine Karte zusammenfassen |
| enum | Query→ |
|
| int |
| 1–20 |
| enum |
|
|
Drei Suchmodi:
Mode A — Direkt-Check (
collectionIdgesetzt): prüftccm:page_config_refder Sammlung, traversiert Config-Kinder → VariantenMode B — Thematische Suche (
querygesetzt): Sucht Sammlungen per Keyword, filtert aufpage_config_refMode C — Alle auflisten (kein
query, keincollectionId): Nutzt diepage_variant-API. Variant-Owner-Sammlung wird übergetNodeParents-Walk aufgelöst → echter Sammlungsname stattPAGE_VARIANT_TEMPLATE_xxx
JSON-Output (Beispiel):
{
"total": 1,
"results": [{
"title": "Physik",
"collectionId": "94f22c9b-…",
"topicPageUrl": "https://…/topic-pages?collectionId=…",
"educationalContexts": ["Sekundarstufe I", "Sekundarstufe II"],
"variants": [
{"variantId": "…", "targetGroup": "teacher", "targetGroupLabel": "Lehrkräfte", "topicPageUrl": "…"},
{"variantId": "…", "targetGroup": "learner", "targetGroupLabel": "Lernende", "topicPageUrl": "…"}
]
}]
}7. get_subject_portals
Liefert die WLO-Fachportale — die direkten Sub-Sammlungen unter dem Wurzel-Knoten.
Parameter | Typ | Default | Beschreibung |
| string | – | Filter (Default: alle) |
| bool |
| Zusätzlich |
| enum |
|
|
Output ist deterministisch alphabetisch (Tie-Breaker nodeId). Ideal als Einstiegspunkt für geführte Drilldowns.
API:
GET /node/v1/nodes/-home-/{ROOT_ID}/children?filter=folders8. browse_collection_tree
Drilldown unter eine bestimmte Sammlung — Tiefe 1 (direkte Kinder) oder 2 (Enkel-Knoten).
Parameter | Typ | Default | Beschreibung |
| string | Pflicht | Eltern-Sammlung |
| int |
|
|
| bool |
| Pro Sammlung den File-Count mitholen |
| int |
| 1–100 |
| enum |
|
|
Achtung Latenz:
depth=2+includeContentCounts=truemachtO(parents × children + parents)API-Calls. Bei breiten Bäumen entsprechend lang.
9. wlo_health_check
Probe gegen die WLO-API. Nimmt keine Parameter — testet die durch WLO_REPOSITORY_URL konfigurierte Instanz.
Output (JSON):
{
"ok": true,
"repositoryUrl": "https://redaktion.openeduhub.net/edu-sharing",
"baseUrl": "https://redaktion.openeduhub.net/edu-sharing/rest",
"rootNodeId": "5e40e372-735c-…",
"rootResolved": "Portale",
"latencyMs": 392,
"checkedAt": "2026-04-27T16:28:04.108Z"
}Bei Fehler: ok: false, error: "<message>", isError: true im MCP-Result. Nützlich für Konsumenten, um "WLO ist down" von "deine Query liefert keine Treffer" zu unterscheiden.
10. get_nodes_details
Bulk-Fetch für mehrere nodeIds (max. 50 / Aufruf, parallel).
Parameter | Typ | Default |
| string[] | Pflicht |
Output:
{
"requested": 3,
"resolved": 2,
"failed": ["00000000-0000-0000-0000-000000000000"],
"results": {
"bd8be6d5-…": { "title": "Mathematik", "disciplines": ["Mathematik"], … },
"742d8c87-…": { "title": "Informatik", "disciplines": ["Informatik"], … }
}
}Einzelne Fehler (gelöschte Node, Netzwerk-Fehler) landen im failed[] — der Batch crasht nicht.
11. search_wlo_all
Kombinierte Suche: liefert Einzel-Inhalte + Sammlungen + Themenseiten in EINEM Aufruf, intern parallel (Promise.all). Spart dem Client mehrere separate Such-Aufrufe. Themenseiten = Sammlungen mit ccm:page_config_ref → eine Sammlungssuche bedient beide Töpfe (kein separater Durchlauf). Nutzt bewusst den schnellen Keyword-Pfad für Sammlungen (nicht den Baum-Fallback) → niedrige Concurrency.
Parameter | Typ | Default | Beschreibung |
| string | Pflicht | Suchbegriff |
| string | – | Bildungsstufe (Label oder URI) |
| string | – | Fach (Label oder URI) |
| string | – | Zielgruppe (Label oder URI) |
| string | – | Ressourcentyp (Label oder URI) |
| string | – | Anbieter-Filter |
| int |
| Max. Einzel-Inhalte (1–50) |
| int |
| Max. je Sammlungen / Themenseiten (1–20) |
| string[] | alle | Teilmenge aus |
| string[] | – | Bereits gesehene IDs überspringen |
| enum |
|
|
JSON-Output (Envelope mit getrennten Töpfen):
{
"query": "Photosynthese",
"content": { "total": 209, "count": 8, "results": [ /* FormattedNode[] */ ] },
"collections": { "total": 6, "count": 5, "results": [ /* … */ ] },
"topicPages": { "total": 1, "count": 1, "results": [ /* Sammlungen mit topicPageUrl */ ] }
}Jeder Topf trägt zusätzlich ein _queryMeta (Such-URL für den jeweiligen Topf). Alle drei Listen sind reranked (Relevanz). Der Client kann die Töpfe direkt in getrennte Anzeige-Bereiche einsortieren.
Filter-Parameter & Vokabular
Alle Vocab-Filter akzeptieren deutsche Labels, englische Synonyme oder vollständige URIs:
Parameter | Beispielwerte | Vocabulary-Key |
|
|
|
|
|
|
|
|
|
|
|
|
Vocab-Resolution: zwei asymmetrische Pfade
Der Server unterscheidet bewusst zwischen Anzeige (URI → Label) und Filter-Eingabe (Label → URI):
Anzeige-Pfad (URI → Label) — vollständige Abdeckung über _DISPLAYNAME
Beim Aufbau eines FormattedNode (alle Such-/Detail-Tools) werden Vocab-Felder so resolvt:
Bevorzugt: server-seitiges
<property>_DISPLAYNAME, das edu-sharing für jeden indexierten Knoten direkt mitliefert.Funktioniert für
ccm:taxonid,ccm:educationalcontext,ccm:oeh_lrt_aggregated,ccm:oeh_intended_end_user_role.Deckt automatisch beide Disziplin-Vokabulare ab: das Schulfächer-Vocab UND die Hochschulfächersystematik, ohne dass wir die ~100 Hochschul-Klassen lokal pflegen müssen.
Beispiel: ein Hochschul-Knoten mit
taxonid=[".../hochschulfaechersystematik/n71", ".../n8"]zeigtdisciplines: ["Studienbereich Informatik", "Ingenieurwissenschaften"].
Fallback: lokales
labelFromUri-Lookup (vocabs.ts).Greift, wenn
_DISPLAYNAMEleer ist (z.B.ccm:commonlicense_keyhat nie ein DISPLAYNAME — daher die lokale License-Map).
Letzter Fallback: rohe URI — damit der Konsument zumindest etwas zurückbekommt.
Rauschfilter: Vokabular-Root-URIs (z.B. nur .../discipline/ ohne konkretes Fach-Slug) werden ausgefiltert — sonst würde DISPLAYNAME den Vokabular-Titel ("Schulfächer", "Destatis-Systematik der Fächergruppen, Studienbereiche und Studienfächer") als Disziplin anzeigen.
Filter-Eingabe-Pfad (Label → URI) — bewusst konservativ
resolveVocab (vocabs.ts) deckt nur die Schulfächer-Liste ab. Begründung:
Das Hochschul-Vocab und das Schul-Vocab teilen sich Labels:
"Mathematik"existiert in beiden, mit unterschiedlicher Semantik (Schul-Mathematik =discipline/380; Hochschul-n4= "Mathematik, Naturwissenschaften" — viel breiter).Wer User-Input automatisch auch ins Hochschul-Vocab mappt, riskiert ungewollte Filter (z.B. wird auch Physik/Chemie/Bio mit-gefiltert).
Mehrheit der WLO-Inhalte ist schulisch.
Wer gezielt Hochschul-Inhalte will, kann orthogonal über
educationalContext: "Hochschule"filtern.
Resolver-Schritte für Label-Eingabe:
URI? → durchreichen
Direkt-Match auf Label / Alias (case-insensitive)
Substring-Match (Label im Input ODER Input im Label) — fängt Tippfehler/Paraphrasen wie
"Naturwiss"(matcht"naturwissenschaften")Kein Match → null (Caller kann den Wert trotzdem an die WLO-API durchgeben)
Empfehlung für Konsumenten (LLM-gestützte Apps):
Bei freier Nutzereingabe ("sciences", "Mathe Klasse 11 Gym") zuerst per LLM auf einen lookup_wlo_vocabulary-Eintrag mappen, dann den canonical Label / URI an die Such-Tools übergeben. So fängst du Paraphrasen ab, die Substring-Matching nicht mehr trifft.
Alle Werte mit URIs: lookup_wlo_vocabulary({ vocabulary: "discipline" }).
Offizielle Vocab-Quellen
Wer das lokale Vocab in src/vocabs.ts aktualisieren oder Aliases ergänzen will:
Vocab | URL |
Schulfächer ( | https://vocabs.openeduhub.de/w3id.org/openeduhub/vocabs/discipline/index.json |
Bildungsstufe ( | https://vocabs.openeduhub.de/w3id.org/openeduhub/vocabs/educationalContext/index.json |
Zielgruppe ( | https://vocabs.openeduhub.de/w3id.org/openeduhub/vocabs/intendedEndUserRole/index.json |
Lernressourcentypen (aggregiert) ( | https://vocabs.openeduhub.de/w3id.org/openeduhub/vocabs/new_lrt_aggregated/index.json |
Hochschulfächersystematik (NICHT lokal gepflegt) | https://vocabs.openeduhub.de/w3id.org/openeduhub/vocabs/hochschulfaechersystematik/index.json |
Anzeige-Resolution für Hochschul-URIs erfolgt automatisch über _DISPLAYNAME aus dem edu-sharing-Index — ein lokales Hochschul-Vocab ist daher nicht nötig.
Output-Formate (markdown vs. json)
Alle Tools mit Suchergebnissen unterstützen outputFormat:
outputFormat: "markdown" (Default)
Menschenlesbare Karten, gut für Chat-Bots, die das LLM die Cards selbst rendern lassen. Beispiel:
## Bruchrechnung Einführung
nodeId: dc6b3f33-…
Beschreibung: …
Fach: Mathematik
Bildungsstufe: Sekundarstufe i
Lizenz: CC BY-SA 4.0
URL: https://…
Vorschaubild: https://…
Themenseite: https://…
Typ: InhaltoutputFormat: "json"
Strukturierter JSON-String, optimal für Konsumenten, die Daten programmatisch weiterverarbeiten:
{
"total": 287,
"count": 5,
"results": [
{
"nodeId": "dc6b3f33-…",
"title": "Bruchrechnung Einführung",
"description": "…",
"keywords": ["Bruch", "Mathematik"],
"disciplines": ["Mathematik"],
"educationalContexts": ["Sekundarstufe i"],
"userRoles": [],
"learningResourceTypes": ["Video"],
"license": "CC BY-SA 4.0",
"publisher": "Mathe by Daniel Jung",
"url": "https://…",
"previewUrl": "https://…",
"topicPageUrl": "",
"nodeType": "content"
}
]
}Wichtig:
disciplines,educationalContexts,userRoles,learningResourceTypes,licensesind immer Labels, nie URIs. Konsumenten könnencard.disciplines[0]direkt anzeigen.
Determinismus & Stabilität
Was du erwarten kannst:
Bei identischer Input + identischem WLO-Server-Pool liefern alle Tools deterministische Reihenfolge:
Tie-Breaker
nodeId.localeCompare()in allen RankingssortByTitle()mit Tie-Breaker für alphabetische Sortierungenget_subject_portals,browse_collection_tree,search_wlo_topic_pages(sort=alpha) sind vollständig deterministisch.
Mode C von
search_wlo_topic_pagesist deterministisch (alphabetisch über Sammlungsnamen).
Was außerhalb unserer Kontrolle liegt:
Die WLO-API kann bei sehr ähnlichen Score-Treffern in seltenen Fällen einen anderen Pool liefern (Solr-Cache-Reshuffles serverseitig). Das beeinflusst
search_wlo_content/ Mode B vonsearch_wlo_topic_pages. Mitsort: "alpha"umgehst du das.
Deployment
Option A: Vercel (empfohlen)
Da die Web-Crawler-Tools entfernt sind, läuft v2 ohne Einschränkung auf Vercel Hobby (
maxDuration: 10sreicht). Dievercel.jsonsetzt trotzdemmaxDuration: 30als Sicherheitspolster fürbrowse_collection_tree(depth=2, includeContentCounts=true)bei breiten Bäumen.
Production-Deploy
Repo auf GitHub pushen
Vercel → New Project → Repo importieren
Environment Variable (optional, Default greift):
WLO_REPOSITORY_URL=https://redaktion.openeduhub.net/edu-sharingDeploy → MCP-Endpoint:
https://dein-projekt.vercel.app/mcp
Staging-Deploy (zweite Instanz)
Um parallel zur Production-Instanz eine Staging-Instanz zu betreiben, ein zweites Vercel-Projekt aus demselben Repo anlegen:
Vercel → New Project → gleiches Repo, anderer Project Name (z.B.
wlo-mcp-server-staging)Settings → Environment Variables →
WLO_REPOSITORY_URL=https://repository.staging.openeduhub.net/edu-sharingsetzen (überschreibt den Default ausvercel.json)Deploy → MCP-Endpoint:
https://wlo-mcp-server-staging.vercel.app/mcp
Konsumenten zeigen dann ihren MCP-Client je nach Welt auf die passende URL — kein Code-Switch im Konsumenten nötig.
Option B: Docker
docker build -t wlomcp .
# Production
docker run -p 3000:3000 wlomcp # nutzt Default
docker run -p 3000:3000 -e WLO_REPOSITORY_URL=https://redaktion.openeduhub.net/edu-sharing wlomcp
# Staging
docker run -p 3000:3000 -e WLO_REPOSITORY_URL=https://repository.staging.openeduhub.net/edu-sharing wlomcp
# → http://localhost:3000/mcpOption C: Lokal
npm install
npm run build
# Production (Default)
node dist/http.js
# Staging
WLO_REPOSITORY_URL=https://repository.staging.openeduhub.net/edu-sharing node dist/http.js
# → http://localhost:3000/mcpKonfiguration in AI-Clients
OpenAI (Responses API)
response = client.responses.create(
model="gpt-5",
tools=[{
"type": "mcp",
"server_label": "wlo",
"server_url": "https://dein-projekt.vercel.app/mcp",
"require_approval": "never",
}],
input="Finde Unterrichtsmaterialien zu Bruchrechnung für die Grundschule",
)Anthropic (Claude)
import anthropic
client = anthropic.Anthropic()
response = client.beta.messages.create(
model="claude-opus-4-5",
max_tokens=4096,
mcp_servers=[{"type": "url", "url": "https://dein-projekt.vercel.app/mcp", "name": "wlo"}],
messages=[{"role": "user", "content": "Suche WLO-Sammlungen zum Thema Klimawandel"}],
betas=["mcp-client-2025-04-04"],
)Claude Desktop
{
"mcpServers": {
"wlo": {
"command": "node",
"args": ["/pfad/zu/wlomcp/dist/stdio.js"],
"env": { "WLO_REPOSITORY_URL": "https://redaktion.openeduhub.net/edu-sharing" }
}
}
}Für eine Staging-Verbindung den Wert auf
https://repository.staging.openeduhub.net/edu-sharingändern (oder ein zweites Server-Profil hinzufügen).
Architektur
wlomcp/
├── src/
│ ├── server.ts # 11 Tool-Definitionen (transport-agnostisch)
│ ├── vocabs.ts # Label ↔ URI Mappings (educationalContext, discipline,
│ │ # userRole, lrt, license, targetGroup)
│ ├── wlo-api.ts # WLO/EduSharing-API-Client + resolveVariantCollection
│ ├── reranker.ts # Multi-Query-Expansion + RRF + Quality-Score (pure JS)
│ ├── formatter.ts # WloNode → FormattedNode → Markdown / JSON
│ ├── stdio.ts # Entry: stdio-Transport
│ └── http.ts # Entry: Streamable HTTP
├── api/
│ └── mcp.ts # Vercel-Serverless-Function-Wrapper
├── vercel.json # Vercel-Config
├── Dockerfile
└── .env.exampleFormattedNode-Schema (das gemeinsame Output-Format aller Tools):
{
nodeId: string;
title: string;
description: string;
keywords: string[];
disciplines: string[]; // Labels: ["Mathematik"]
educationalContexts: string[]; // Labels: ["Sekundarstufe i"]
userRoles: string[]; // Labels: ["Lehrer/in"]
learningResourceTypes: string[]; // Labels: ["Arbeitsblatt"]
url: string;
previewUrl: string;
license: string; // Label: "CC BY-SA 4.0"
publisher: string;
nodeType: 'collection' | 'content';
topicPageUrl: string; // wenn ccm:page_config_ref vorhanden
}API-Basis-URLs:
Die REST-API liegt unter <WLO_REPOSITORY_URL>/rest/..., das Frontend (Render- und Themenseiten-Links) unter <WLO_REPOSITORY_URL>/components/.... Die Pfade sind in allen Edu-Sharing-Instanzen identisch — der einzige Unterschied zwischen Welten ist die konfigurierte Repository-URL.
Welt |
| Beispiel-Endpunkt (REST) |
Production |
|
|
Staging |
|
|
Custom | beliebige Edu-Sharing-Instanz |
|
Migration v1 → v2
Wenn du heute v1 nutzt
v1-Verhalten | v2-Verhalten |
|
|
|
|
|
|
|
|
Mehrere Varianten = N separate Karten | 1 Karte mit |
Gleicher Score → unspezifizierte Reihenfolge | Tie-Breaker |
| Entfernt. Konsument soll RAG/eigene Page-Extraction nutzen |
Empfohlene Migration
Sofort ausführen:
wlo_health_checkeinmalig nach Deploy, Latenz prüfen, Server-Status verifizierenJSON-Output progressiv aktivieren: Beginne mit
get_node_detailsundsearch_wlo_topic_pages— die hatten in v1 die meisten Inkonsistenzen. Vorher: Markdown-Parsing → Nachher:JSON.parse()Webseiten-Tool-Aufrufe entfernen: Wenn dein Client
get_wirlernenonline_infoetc. nutzt → entweder ein eigenes RAG aufsetzen oder das Tool aus dem Allowlist deines AI-Clients nehmenVariant-Merge-Code entfernen: Wenn dein Client mehrere Varianten desselben Themas selbst gemerged hat → das macht der Server jetzt
Discipline-URI-Resolver in Konsumenten: Wer früher Karten-Disciplines manuell auf Labels resolvte, kann diese Logik abklemmen — Server liefert immer Labels
Kompatibilität
Tool-Namen sind ausschließlich Kleinbuchstaben + Unterstriche → kompatibel mit OpenAI / Anthropic
Input-Schemas sind JSON Schema (via Zod) → Standard-konform
Transport: Streamable HTTP (MCP spec 2025-03-26) für Vercel/Docker; stdio für lokale Clients
Stateless: Kein Session-State → skaliert auf Vercel Serverless
Vercel-tauglich: Reranker ist pure JS, kein Transformer-Modell, kein Speicherbedarf > Function-Limit
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Latest Blog Posts
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/janschachtschabel/wlo-mcp-server'
If you have feedback or need assistance with the MCP directory API, please join our Discord server