← Blog

MCP-servere: sådan åbner vi CVR-, DAR- og XBRL-data for AI-agenter

Mads Kristiansen

CTO, Liviate

For et par år siden var integration mellem en AI-model og en virksomheds egne systemer et håndværk: hver forbindelse (til CRM'et, til dokumentlageret, til det interne API) skulle bygges fra bunden, vedligeholdes for sig, og duplikeres hver gang man skiftede AI-leverandør.

Model Context Protocol (MCP) ændrer den ligning. Vi har tidligere lovet at vende tilbage til det, efter at have set på vektordatabaser og embedding/reranking i de forrige dele af denne serie, så her er den: hvad MCP er, hvorfor det er interessant for virksomheder, og hvordan vi selv har brugt det til at gøre dansk CVR-, adresse- og regnskabsdata tilgængeligt for AI-agenter, med konkrete eksempler fra fem forskellige brancher.

Hvad er MCP, og hvorfor findes det

Model Context Protocol blev introduceret i november 2024 som en åben, standardiseret protokol til at forbinde AI-modeller og AI-agenter med eksterne datakilder og værktøjer. Tanken er enkel, men effekten er stor: i stedet for at hver AI-applikation skal bygge sin egen custom-integration til hvert system, giver MCP ét fælles "stik", lidt ligesom USB-C gjorde det unødvendigt med et særskilt kabel til hver enhed.

Rent teknisk er MCP bygget som en klient-server-arkitektur. En MCP-server eksponerer et sæt "tools" (funktioner AI'en kan kalde), og i nogle tilfælde også "resources" og "prompts". Kommunikationen foregår via JSON-RPC, enten lokalt over stdio, eller som remote-forbindelse over SSE/HTTP, når serveren kører i skyen. Klienten (selve AI-applikationen) opdager dynamisk, hvilke værktøjer en given server tilbyder, og kan kalde dem efter behov, uden at nogen har hardcodet integrationen på forhånd.

Siden lanceringen har protokollen hurtigt fået bred opbakning. Flere store AI-leverandører har adopteret den, og i løbet af 2025-26 er der vokset et helt økosystem frem af connectors og registries, hvor man kan finde og tilslutte færdigbyggede MCP-servere til stort set alt fra projektstyring til finansielle data.

Hvad betyder det i praksis for en virksomhed

Den umiddelbare gevinst er at gøre viden tilgængelig on-demand. En medarbejder behøver ikke vide, hvilket system der har svaret, bare spørge, mens AI'en selv henter det relevante fra CRM, ticketsystem eller dokumentlager.

Det rækker dog videre end opslag. Fordi MCP-servere kan eksponere handlinger, ikke kun data, bliver AI'en i stand til at automatisere rutineopgaver: oprette en opgave, sende en besked, opdatere et felt, uden at brugeren selv skal skifte mellem ti forskellige faner.

For virksomheder, der selv sidder på data eller et API, åbner MCP samtidig en ny distributionskanal. At bygge sin egen MCP-server betyder, at ens data eller funktionalitet bliver "spiseligt" for enhver MCP-kompatibel klient, uden at man skal bygge og vedligeholde en separat integration for hver kunde. Og fordi adgang kan scopes og styres på serverniveau, er der en indbygget mulighed for at tænke compliance ind fra starten, relevant for os, der arbejder i og omkring finanssektoren, hvor adgangsstyring ikke er noget man tager let på.

Business casen er derfor ikke svær at få øje på: tidsbesparelse, færre fejl i manuelle opslag, og hurtigere onboarding, fordi nye medarbejdere kan spørge AI'en i stedet for at skulle lære fem systemer udenad.

Vores case: ejendomsdata fra CVR, DAR og XBRL

Vi har bygget det ud i praksis med et konkret, offentligt datasæt: dansk virksomheds-, adresse- og regnskabsdata. Det er data, der er frit tilgængeligt, men i praksis fragmenteret og besværligt at kombinere programmatisk — CVR-registret, DAR (Danmarks Adresseregister) og XBRL-regnskaber lever hver for sig, i hvert sit format, med hver sin adgangsvej.

Vi har bygget tre MCP-servere, der hver dækker sit hjørne — alle tre er gratis, offentlige og beskyttet med almindelig OAuth 2.0 (Bearer-token, dynamic client registration, PKCE), så enhver MCP-klient kan tilslutte sig uden en manuel nøgle-udveksling. Gratis kvote: 500 kald/måned, samlet på tværs af alle tre servere (ikke 500 hver).

<!DOCTYPE html>
<html lang="da">
<head>
<meta charset="utf-8">
<style>
  :root { --ink:#0f172a; --sub:#475569; --line:#e2e8f0; --bg:#f8fafc; }
  * { box-sizing: border-box; }
  html { overflow: hidden; }
  html, body { margin: 0; padding: 0; background: transparent; }
  body {
    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Inter, Roboto, sans-serif;
    color: var(--ink);
    padding: 14px;
  }
  .card {
    border: 1px solid var(--line);
    border-radius: 12px;
    overflow: hidden;
    background: #fff;
  }
  .tabs {
    display: flex;
    background: var(--bg);
    border-bottom: 1px solid var(--line);
  }
  .tab {
    flex: 1;
    padding: 12px;
    text-align: center;
    font-weight: 600;
    font-size: 14px;
    cursor: pointer;
    color: var(--sub);
    border-bottom: 3px solid transparent;
    background: none;
    border-left: none; border-right: none; border-top: none;
    transition: color .15s, border-color .15s, background .15s;
    font-family: inherit;
  }
  .tab:hover { color: var(--ink); }
  .tab[data-active="true"] { background: #fff; }
  .tab[data-svc="cvr"][data-active="true"]  { color: #0369a1; border-color: #0ea5e9; }
  .tab[data-svc="dar"][data-active="true"]  { color: #0f766e; border-color: #14b8a6; }
  .tab[data-svc="xbrl"][data-active="true"] { color: #4338ca; border-color: #6366f1; }

  .panel { display: none; padding: 16px 18px; }
  .panel[data-active="true"] { display: block; }

  .badge {
    display: inline-flex; align-items: center; gap: 6px;
    font-size: 11px; font-weight: 700; letter-spacing: .03em; text-transform: uppercase;
    padding: 3px 9px; border-radius: 999px; color: #fff; margin-bottom: 8px;
  }
  .panel[data-svc="cvr"] .badge  { background: #0ea5e9; }
  .panel[data-svc="dar"] .badge  { background: #14b8a6; }
  .panel[data-svc="xbrl"] .badge { background: #6366f1; }

  h3 { margin: 0 0 6px; font-size: 17px; }
  /* Fixed height (not min-height -- with a fixed value the tallest description, XBRL's,
     sets the ceiling for all three so switching tabs never reflows the endpoint/row below it). */
  p.desc { margin: 0 0 10px; color: var(--sub); font-size: 14px; line-height: 1.5; height: 42px; overflow: hidden; }

  .endpoint {
    display: flex; align-items: center; justify-content: space-between; gap: 8px;
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 13px;
    background: var(--bg);
    border: 1px solid var(--line);
    border-radius: 8px;
    padding: 8px 12px;
    color: #1e293b;
    word-break: break-all;
    margin-bottom: 10px;
  }

  .endpoint code { font: inherit; min-width: 0; overflow-wrap: anywhere; }
  .copy {
    flex: none; display: inline-flex; align-items: center; gap: 5px;
    height: 24px; padding: 0 8px; margin: -2px -6px -2px 0;
    font: 600 12px/1 inherit; font-family: inherit; color: var(--sub);
    background: #fff; border: 1px solid var(--line); border-radius: 6px; cursor: pointer;
    transition: color .15s, border-color .15s, background .15s;
  }
  .copy:hover { color: var(--ink); border-color: #cbd5e1; }
  .copy:focus-visible { outline: 2px solid #6366f1; outline-offset: 1px; }
  .copy svg { width: 14px; height: 14px; }
  .copy[data-done="true"] { color: #047857; border-color: #6ee7b7; background: #ecfdf5; }
  .copy .ok { display: none; }
  .copy[data-done="true"] .ok { display: inline; }
  .copy[data-done="true"] .cp { display: none; }

  .row { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }
  .quota {
    font-size: 12px; color: var(--sub); background: var(--bg);
    border: 1px solid var(--line); border-radius: 999px; padding: 5px 11px;
  }
  a.docs {
    font-size: 13px; font-weight: 600; text-decoration: none;
    padding: 7px 13px; border-radius: 8px; color: #fff;
  }
  .panel[data-svc="cvr"] a.docs  { background: #0ea5e9; }
  .panel[data-svc="dar"] a.docs  { background: #14b8a6; }
  .panel[data-svc="xbrl"] a.docs { background: #6366f1; }
  a.docs:hover { filter: brightness(0.93); }
</style>
</head>
<body>
  <div class="card">
    <div class="tabs" role="tablist">
      <button class="tab" data-svc="cvr" data-active="true">CVR</button>
      <button class="tab" data-svc="dar">DAR</button>
      <button class="tab" data-svc="xbrl">XBRL</button>
    </div>

    <div class="panel" data-svc="cvr" data-active="true">
      <span class="badge">MCP server</span>
      <h3>CVR — virksomhedsdata</h3>
      <p class="desc">Håndterer opslag og søgning på danske virksomheder — på navn, by, adresse eller koordinat — samt opslag på deltagere/ejere.</p>
      <div class="endpoint"><code>https://cvr.mcp.liviate.com/mcp</code><button class="copy" type="button" data-copy="https://cvr.mcp.liviate.com/mcp" aria-label="Kopier URL"><svg class="cp" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="9" y="9" width="12" height="12" rx="2"/><path d="M5 15V5a2 2 0 0 1 2-2h10"/></svg><svg class="ok" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 12.5l4.5 4.5L19 7"/></svg><span class="lbl">Kopier</span></button></div>
      <div class="row">
        <span class="quota">Gratis kvote: 500 kald/måned (fælles for alle tre servere)</span>
        <a class="docs" href="https://liviate.com/docs/cvr-dar-xbrl-mcp" rel="noopener" data-open-new>Opsætning &amp; auth-guide →</a>
      </div>
    </div>

    <div class="panel" data-svc="dar">
      <span class="badge">MCP server</span>
      <h3>DAR — adressedata</h3>
      <p class="desc">Giver adgang til Danmarks Adresseregister, med både fritekstsøgning og vejbaseret søgning.</p>
      <div class="endpoint"><code>https://dar.mcp.liviate.com/mcp</code><button class="copy" type="button" data-copy="https://dar.mcp.liviate.com/mcp" aria-label="Kopier URL"><svg class="cp" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="9" y="9" width="12" height="12" rx="2"/><path d="M5 15V5a2 2 0 0 1 2-2h10"/></svg><svg class="ok" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 12.5l4.5 4.5L19 7"/></svg><span class="lbl">Kopier</span></button></div>
      <div class="row">
        <span class="quota">Gratis kvote: 500 kald/måned (fælles for alle tre servere)</span>
        <a class="docs" href="https://liviate.com/docs/cvr-dar-xbrl-mcp" rel="noopener" data-open-new>Opsætning &amp; auth-guide →</a>
      </div>
    </div>

    <div class="panel" data-svc="xbrl">
      <span class="badge">MCP server</span>
      <h3>XBRL — regnskabsdata</h3>
      <p class="desc">Gør regnskabsdata og nøgletal på tværs af årsrapporter tilgængeligt, for enkeltselskaber og i batch — med delvis elementmatching.</p>
      <div class="endpoint"><code>https://xbrl.mcp.liviate.com/mcp</code><button class="copy" type="button" data-copy="https://xbrl.mcp.liviate.com/mcp" aria-label="Kopier URL"><svg class="cp" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="9" y="9" width="12" height="12" rx="2"/><path d="M5 15V5a2 2 0 0 1 2-2h10"/></svg><svg class="ok" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 12.5l4.5 4.5L19 7"/></svg><span class="lbl">Kopier</span></button></div>
      <div class="row">
        <span class="quota">Gratis kvote: 500 kald/måned (fælles for alle tre servere)</span>
        <a class="docs" href="https://liviate.com/docs/cvr-dar-xbrl-mcp" rel="noopener" data-open-new>Opsætning &amp; auth-guide →</a>
      </div>
    </div>
  </div>

<script>
  var tabs = document.querySelectorAll('.tab');
  var panels = document.querySelectorAll('.panel');
  tabs.forEach(function (tab) {
    tab.addEventListener('click', function () {
      var svc = tab.getAttribute('data-svc');
      tabs.forEach(function (t) { t.removeAttribute('data-active'); });
      panels.forEach(function (p) { p.removeAttribute('data-active'); });
      tab.setAttribute('data-active', 'true');
      document.querySelector('.panel[data-svc="' + svc + '"]').setAttribute('data-active', 'true');
    });
  });

  // A plain <a target="_blank"> inside a sandboxed srcdoc iframe (allow-popups, no
  // allow-top-navigation) opened TWO tabs in Chromium -- the real target AND a spurious
  // duplicate of this card's own document -- confirmed live 2026-09-20. Explicit window.open()
  // on click, instead of relying on the browser's native target="_blank" handling, doesn't
  // trigger it.
  document.querySelectorAll('a[data-open-new]').forEach(function (a) {
    a.addEventListener('click', function (e) {
      e.preventDefault();
      window.open(a.getAttribute('href'), '_blank', 'noopener');
    });
  });
  function fallbackCopy(text) {
    var ta = document.createElement('textarea');
    ta.value = text; ta.setAttribute('readonly', '');
    ta.style.cssText = 'position:fixed;top:0;left:0;opacity:0';
    document.body.appendChild(ta); ta.select();
    var ok = false; try { ok = document.execCommand('copy'); } catch (e) {}
    document.body.removeChild(ta); return ok;
  }
  document.querySelectorAll('.copy').forEach(function (btn) {
    btn.addEventListener('click', function () {
      var text = btn.getAttribute('data-copy');
      var done = function (ok) {
        if (!ok) return;
        btn.setAttribute('data-done', 'true'); btn.querySelector('.lbl').textContent = 'Kopieret';
        setTimeout(function () { btn.removeAttribute('data-done'); btn.querySelector('.lbl').textContent = 'Kopier'; }, 1800);
      };
      if (navigator.clipboard && navigator.clipboard.writeText) {
        navigator.clipboard.writeText(text).then(function () { done(true); }, function () { done(fallbackCopy(text)); });
      } else { done(fallbackCopy(text)); }
    });
  });
</script>
</body>
</html>

Fuld opsætnings- og autentifikationsguide (inkl. hvordan du tilføjer dem i Claude Desktop eller en anden MCP-klient) findes på docs-siden for de tre servere.

Det interessante sker, når man kombinerer dem. At finde virksomheder i en given by med voksende ejendomsbesiddelser kræver, at man krydser CVR-data (hvem findes, og hvor) med XBRL-data (hvordan har deres bogførte ejendomsværdi, fx "LandAndBuildings", udviklet sig over flere år). Med MCP-serverne foregår det som én sammenhængende samtale med en AI-agent, i stedet for timevis af manuel gennemgang af regnskaber.

Et par praktiske detaljer gør en reel forskel i ydelse og brugbarhed: batch-endpoints, der undgår gentagne enkeltopslag, når man skal tjekke hundredvis af selskaber på én gang; delvis elementmatching i XBRL-data, så man kan søge bredt på fx "PropertyPlantAndEquipment" uden at kende den præcise nøgletalsbetegnelse; og en ærlig erkendelse af de begrænsninger man støder på i praksis, fx paginering og loft på antal søgeresultater. Den slags detaljer er ikke bare tekniske fodnoter — de er ofte forskellen på et værktøj, der virker i teorien, og ét der virker i produktion.

Hvem kan bruge det: fem brancher, fem eksempler

Data om virksomheder, adresser og regnskaber er interessant langt ud over vores eget felt. Her er et bud på, hvordan fem forskellige faggrupper kunne bruge det samme fundament.

Ejendomsmæglere

  • Overvåge ejerskabsskift på ejendomme i et bestemt postnummer
  • Prisfastsætte erhvervsejendomme ud fra sammenlignelige selskabers bogførte ejendomsværdier
  • Identificere porteføljeejere som potentielle sælgere eller købere
  • Automatisk adresseberigelse af egne objektlister via DAR

Kreditvurdering

  • Automatisk nøgletals-"health check" før kreditgivning, trukket direkte fra XBRL i stedet for manuel PDF-læsning
  • Flage virksomheder med faldende egenkapital eller stigende gearing relativt til deres ejendomsbesiddelser
  • Sammenligne en ansøgers nøgletal mod branchegennemsnit
  • Løbende overvågning af en hel kundeportefølje, når nye årsregnskaber offentliggøres

Due diligence

  • Hurtigt overblik over en målvirksomheds koncernstruktur via deltageropslag
  • Diskrepans-check mellem offentlige regnskabstal og det, målselskabet selv har fremlagt
  • Historisk gennemgang af en virksomheds aktivudvikling over ti år på minutter
  • Screening af en hel brancheklynge for red flags

Salg og leadgenerering

  • Leadlister ud fra kriterier som egenkapital, omsætningsvækst og geografi
  • Trigger-baseret outreach, fx når en virksomhed lige har købt en ejendom
  • Berige eksisterende CRM-data med friske CVR- og regnskabstal
  • Segmentere efter reklamebeskyttelse, så man kun kontakter dem der faktisk må kontaktes

Journalister

  • Spore ejerskabsnetværk på tværs af selskaber
  • Bygge historier på faktiske regnskabstal
  • Verificere en virksomheds egne vækstpåstande op mod de faktiske tal
  • Hurtigt baggrundstjek af en virksomhed nævnt i et kildetip

Ikke til at kortlægge nationalstater

MCP flytter grænsen for, hvad en AI-agent kan bidrage med, fra at kunne svare på spørgsmål ud fra det, den allerede ved, til selv at kunne hente, krydse og handle på aktuel data. For os har det konkret betydet, at dansk CVR-, adresse- og regnskabsdata er gået fra at være tre adskilte, besværlige datakilder til ét sammenhængende fundament, en AI-agent kan navigere i på egen hånd.

Og lad os være ærlige: når man sidder med tre-fire MCP-servere, der på et øjeblik kan krydse virksomheder, adresser, regnskaber og ejerskabsnetværk på tværs af tyve selskaber, er det svært ikke at få den der Palantir-fornemmelse — en flowchart-væg fuld af røde tråde og et par selvsikre PowerPoints om "Foundry for the Danish Mittelstand". Den gode nyhed er, at det her nok mest bliver brugt til at finde ud af, om en ejendomsmægler skal ringe til de rette porteføljeejere, eller om en revisor kan spare en eftermiddag på PDF-jagt — ikke til at kortlægge nationalstater. Men skulle nogen få lyst til at bygge en efterretningsplatform for reklamebeskyttede holdingselskaber, er byggeklodserne i hvert fald klar. Vi tager ikke ansvar for konspirationsteorier, der opstår undervejs.

Er det relevant for jer?

Bygger I noget, hvor I i dag krydstjekker offentlige eller interne data manuelt, på tværs af flere kilder? Skriv gerne, så tager vi en snak om, hvor I kunne starte.

Vil du tale om, hvad der reelt blokerer jeres AI-initiativer?

Mads Kristiansen tager gerne en uforpligtende snak.

Book et møde →