Skip to main content

Paragraf

MCP-server som gir AI-assistenter tilgang til alle norske lover og forskrifter via Model Context Protocol.

92 000+ paragrafer fra 770 lover og 3 666 forskrifter — gratis under NLOD 2.0-lisensen.

Hvorfor

LLM-er hallusinerer lovtekst. Denne serveren gir dem presis, oppdatert norsk rett som verktøykall i stedet for gjetting.

Bruker:  "Kan utleier si meg opp?"
AI:      sok("oppsigelse leie") → 4 treff
         lov("husleieloven", "9-7") → full tekst
Svar:    "Etter husleieloven § 9-7 skal oppsigelse fra utleier
          være skriftlig og begrunnet..."

Funksjoner

Funksjon Beskrivelse
Lovoppslag Slå opp enhver lov/forskrift med kortnavn eller full ID
Fulltekstsøk (FTS) PostgreSQL tsvector med norsk stemming, ~6ms
Semantisk søk Hybrid vektor+FTS med Gemini embeddings for naturlig språk
Batch-henting Hent flere paragrafer i ett kall (~80% raskere)
Innholdsfortegnelse Hierarkisk oversikt (Del → Kapittel → §) med token-estimat
Alias-oppløsning aml, avhl, pbl + fuzzy matching for stavefeil
OR-fallback AND-søk som automatisk faller tilbake til OR ved 0 treff

Arkitektur

┌──────────────────┐     HTTPS/JSON-RPC      ┌──────────────────────────┐
│  Claude.ai       │ ──────────────────────►  │  Flask Backend           │
│  Copilot Studio  │                          │                          │
│  Gemini AI       │                          │  ┌────────────────────┐  │
│  (MCP-klient)    │ ◄──────────────────────  │  │  MCP Server        │  │
└──────────────────┘                          │  │  (JSON-RPC router) │  │
                                              │  └────────┬───────────┘  │
                                              │           │              │
                                              │  ┌────────▼───────────┐  │
                                              │  │  LovdataService    │  │
                                              │  │  (alias, validering│  │
                                              │  │   formatering)     │  │
                                              │  └────────┬───────────┘  │
                                              │           │              │
                                              └───────────┼──────────────┘
                                                          │
                                        ┌─────────────────┴──────────────────┐
                                        │                                    │
                                        ▼                                    ▼
                              ┌───────────────────┐              ┌───────────────────┐
                              │  Supabase         │              │  Lovdata API       │
                              │  PostgreSQL       │              │  api.lovdata.no    │
                              │                   │              │                    │
                              │  • FTS (GIN)      │              │  Bulk tar.bz2      │
                              │  • pgvector       │              │  (kun ved sync)    │
                              │  • pg_trgm        │              │                    │
                              └───────────────────┘              └───────────────────┘

Hurtigstart

Forutsetninger

  • Python 3.11+
  • PostgreSQL med pgvector og pg_trgm (Supabase anbefalt)
  • Valgfritt: Gemini API-nøkkel for semantisk søk

Installasjon

pip install paragraf            # Minimal (SQLite backend)
pip install paragraf[supabase]  # Med Supabase PostgreSQL
pip install paragraf[all]       # Alt (Supabase + vektorsøk + HTTP)

Eller fra kildekode:

git clone https://github.com/khjohns/paragraf.git
cd paragraf
pip install -e ".[all]"

Konfigurasjon

# .env
SUPABASE_URL=https://your-project.supabase.co
SUPABASE_SERVICE_ROLE_KEY=eyJ...

# Valgfritt: for semantisk søk
GEMINI_API_KEY=AIza...

Uten Supabase brukes SQLite som lokal fallback.

Kjør databasemigreringer

# Kjør mot Supabase (i rekkefølge)
supabase db push

Migreringene oppretter:

  • lovdata_documents — lover og forskrifter
  • lovdata_sections — paragrafer med FTS og embedding
  • lovdata_structure — hierarkisk struktur (del/kapittel/avsnitt)
  • lovdata_sync_meta — sync-metadata
  • SQL-funksjoner for søk og fuzzy matching

Synkroniser lovdata

paragraf sync                   # Første gang (tar 5-10 min)

Start serveren

paragraf serve                  # stdio MCP (for Claude Desktop, Cursor)
paragraf serve --http           # HTTP MCP (for claude.ai connector)
paragraf serve --http --port 8000

Koble til Claude.ai

  1. Gå til Settings → Connectors → Add custom connector
  2. URL: https://your-domain.com/mcp/
  3. Ferdig — ingen autentisering kreves

MCP-verktøy

Verktøy Beskrivelse Eksempel
lov Slå opp lov lov("aml", "14-9")
forskrift Slå opp forskrift forskrift("foa", "25-2")
sok Fulltekstsøk sok("mangel bolig")
semantisk_sok AI-drevet søk semantisk_sok("skjulte feil i boligen")
hent_flere Batch-henting hent_flere("aml", ["14-9", "15-6"])
sjekk_storrelse Token-estimat sjekk_storrelse("skatteloven", "5-1")
liste Vis aliaser liste()
status Sync-status status()
sync Synkroniser sync(force=True)

Alias-oppløsning

Fire nivåer for å finne riktig lov:

Nivå Eksempel
1. Hardkodet alias aml → LOV-2005-06-17-62
2. Database (short_title) husleieloven → lov/1999-03-26-17
3. Fuzzy (pg_trgm) husleielova → husleieloven (similarity: 0.59)
4. Direkte ID lov/1999-03-26-17 → brukes som-er

Søkesyntaks (FTS)

Syntaks Eksempel Betydning
Standard mangel bolig AND (begge ord)
OR miljø OR klima Minst ett ord
Frase "vesentlig mislighold" Eksakt frase
Ekskludering mangel -bil mangel, ikke bil

AND-søk som gir 0 treff faller automatisk tilbake til OR.

Mappestruktur

src/paragraf/
├── __init__.py              # Pakke-eksport (MCPServer, LovdataService)
├── server.py                # MCP JSON-RPC server (9 verktøy)
├── service.py               # Forretningslogikk, aliaser, validering
├── supabase_backend.py      # Supabase PostgreSQL backend
├── sqlite_backend.py        # SQLite fallback + sync fra API
├── structure_parser.py      # XML → hierarkisk struktur
├── vector_search.py         # Hybrid vektor+FTS søk
├── cli.py                   # CLI (serve, sync, status)
└── web.py                   # Flask blueprint factory

web/
└── app.py                   # Full Flask blueprint (OAuth, SSE, HTTP)

scripts/
└── embed.py                 # Generer embeddings for vektorsøk

migrations/
├── 20260203_create_lovdata_tables.sql
└── 20260206_add_lovdata_structure.sql

API-endepunkter

Metode Sti Beskrivelse
POST /mcp/ MCP JSON-RPC (hovedendepunkt)
HEAD /mcp/ Protokollversjon-sjekk
GET /mcp/ SSE-stream (bakoverkompatibilitet)
GET /mcp/health Helsesjekk
GET /mcp/info Serverinfo og verktøyliste

Ytelse

Metrikk Verdi
FTS-søk (warm cache) ~6ms
FTS-søk (cold cache) ~600ms
Lovoppslag ~50-200ms
Batch 3 paragrafer ~100ms (vs 491ms separat)
Database-størrelse ~160MB tabell + 42MB TOAST + 37MB GIN
Vektorsøk (hybrid) ~200-500ms (inkl. embedding)

Datamodell

Tabeller

lovdata_documents (4 439 rader)
├── dok_id TEXT UNIQUE        "lov/2005-05-20-28"
├── title TEXT                "Lov om arbeidsmiljø..."
├── short_title TEXT          "Arbeidsmiljøloven"
├── doc_type TEXT             "lov" | "forskrift"
├── ministry TEXT             "Arbeids- og inkluderingsdepartementet"
└── search_vector TSVECTOR

lovdata_sections (92 130 rader)
├── dok_id + section_id       UNIQUE
├── content TEXT               Paragraftekst
├── search_vector TSVECTOR     Norsk stemming
├── embedding VECTOR(1536)     Gemini embedding
├── char_count INTEGER         GENERATED ALWAYS
└── structure_id UUID FK       → lovdata_structure

lovdata_structure (13 909 rader)
├── structure_type TEXT        "del" | "kapittel" | "avsnitt" | "vedlegg"
├── title TEXT                 "Kapittel 2. Arbeidsgivers plikter"
├── parent_id UUID FK          Hierarkisk (self-ref)
└── sort_order INTEGER

Indekser

  • GIN på search_vector — fulltekstsøk
  • GIN på short_title med pg_trgm — fuzzy matching
  • IVFFlat på embedding (lists=100) — vektorsøk
  • B-tree på dok_id, section_id, structure_id — oppslag

Sikkerhet

  • Ingen brukerdata lagres — authless design
  • Parameteriserte queries — ingen SQL injection
  • Input-validering på alle MCP-verktøy
  • Rate limiting anbefalt i produksjon (flask-limiter)
  • NLOD 2.0-lisens — alle data er offentlige

Testet mot

Angrep Resultat
SQL injection ('; DROP TABLE--) Blokkert
Path traversal (../../../etc/passwd) Ingen filsystem-tilgang
XSS (<script>alert('xss')</script>) Behandlet som tekst

Begrensninger

Inkludert (gratis via Lovdata Public API)

  • Gjeldende lover (770+)
  • Sentrale forskrifter (3 666+)
  • Lokale forskrifter, delegeringer, instrukser

IKKE inkludert

  • Rettsavgjørelser (Høyesterett, lagmannsrett) — krever Lovdata Pro
  • Forarbeider (NOU, Prop., Ot.prp.) — krever Lovdata Pro
  • Juridiske artikler

Deploy

Render

# render.yaml
services:
  - type: web
    name: paragraf
    runtime: python
    buildCommand: pip install paragraf[all]
    startCommand: paragraf serve --http
    healthCheckPath: /mcp/health

Miljøvariabler

Variabel Påkrevd Beskrivelse
SUPABASE_URL Ja* Supabase prosjekt-URL
SUPABASE_SERVICE_ROLE_KEY Ja* Service role nøkkel
GEMINI_API_KEY Nei For semantisk søk
MCP_REQUIRE_AUTH Nei true for OAuth 2.1
LOVDATA_CACHE_DIR Nei SQLite cache-sti (default: /tmp/lovdata-cache)

* SQLite brukes som fallback uten Supabase.

Utvikling

# Generer embeddings (krever GEMINI_API_KEY)
python scripts/embed.py

# Helsesjekk
curl http://localhost:8000/mcp/health

# Test MCP-kall
curl -X POST http://localhost:8000/mcp/ \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
      "name": "lov",
      "arguments": {"lov_id": "aml", "paragraf": "14-9"}
    }
  }'

Teknologi

Komponent Teknologi
Server Flask + Python 3.11
Database Supabase PostgreSQL (SQLite fallback)
Fulltekstsøk PostgreSQL tsvector + GIN
Vektorsøk pgvector IVFFlat + Gemini embeddings
Fuzzy matching pg_trgm
Protokoll MCP 2025-06-18, Streamable HTTP
Datakilde Lovdata Public API (NLOD 2.0)

Lisens

Inneholder data under Norsk lisens for offentlige data (NLOD 2.0) tilgjengeliggjort av Lovdata.

Relatert dokumentasjon

Metadata

Release files for paragraf 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for paragraf 0.1.0
File Size Uploaded
paragraf-0.1.0.tar.gz 70.0 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for paragraf 0.1.0
File Interpreter ABI Platform
paragraf-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 121.4 kB

Release files / paragraf-0.1.0.tar.gz

Download URL paragraf-0.1.0.tar.gz
Size 70.0 kB
Tags Source
SHA-256 checksum
How to use checksums
abc19ed5f70bfb8460f0274416a3da1526d668d3d5faca913e669849b1e071b9
BLAKE2b-256 checksum
How to use checksums
91b880990f7a1ec44ad6fc8af8b3ee33f4d31c95f0562ef786f3b33055812dfe
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.11.9

Release files / paragraf-0.1.0-py3-none-any.whl

Download URL paragraf-0.1.0-py3-none-any.whl
Size 51.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
9bfe183c970268ead80343f58d4a95dc4fd7338a54c28d70fb75c2636219de53
BLAKE2b-256 checksum
How to use checksums
f785c522e4a8dab69f8477ab155c4267a434a0a7061977b391db7f378dcfa4ec
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.11.9

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page