En mcp server er en programmerbar bro som kobler dataene dine til AI-modeller som ChatGPT og Claude — over en åpen protokoll Anthropic publiserte i 2024. Riktig satt opp gir én mcp server hele teamet ditt et søkbart, agentisk grensesnitt mot interne systemer på 4–8 timers utviklingstid. Denne guiden viser hvordan, basert på en ekte case vi bygget for Tverrkirkelig.
Hos Nettsmed bygde vi nylig en mcp server som eksponerer Cornerstone medlemsregister som lesetilgang for ChatGPT Workspace Agents. Denne artikkelen er den teknisk-konkrete oppskriften — Python-kode, Vercel-deploy og hvilke fellgruver vi traff på underveis.
Hva er en mcp server?
Model Context Protocol (MCP) er en åpen standard som lar AI-modeller kalle eksterne verktøy gjennom et JSON-RPC-grensesnitt. En mcp server eksponerer disse verktøyene — typisk lese- eller skriveoperasjoner mot dine egne datasystemer — så ChatGPT, Claude eller andre AI-klienter kan bruke dem.
Tre typer mcp server-transport finnes i dag:
- stdio — for lokal bruk fra Claude Code, Claude Desktop og lignende. Enklest å starte med.
- Streamable HTTP — for moderne klienter (Claude Apps SDK, OpenAI Agents SDK).
- SSE (Server-Sent Events) — kreves av ChatGPT Workspace Agents per april 2026.
For en mcp server som skal serve ChatGPT-agenter må du støtte SSE. Vi anbefaler å eksponere både SSE og streamable HTTP fra samme prosess slik at samme kode dekker alle klienter.
Stack: hva vi brukte og hvorfor
- Python 3.13 + FastMCP — Anthropics offisielle MCP SDK. Decorator-basert tool-registrering, mindre boilerplate enn å bygge fra bunn.
- FastAPI / Starlette — for HTTP/SSE-endepunkter (FastMCP gir Starlette-app ut av boksen).
- httpx — async HTTP-klient mot underliggende API.
- Vercel — deploy. Auto-detekterer FastAPI, gratis preview-deploys, EU-region tilgjengelig.
- Pydantic — input-validering på tool-argumenter.
Hele oppsettet for vår mcp server tok rundt 4 timer fra blank repo til fungerende endepunkt — inkludert testing mot live-data. Det er ikke et komplekst prosjekt hvis du har erfaring med Python og REST API-er fra før.
Steg 1: Scaffold prosjektet
Lag et nytt Python-prosjekt med FastMCP som hoveddependens. Strukturen vi bruker:
cornerstone-mcp/
├── api/
│ └── index.py # Vercel serverless entry
├── src/
│ └── cornerstone_mcp/
│ ├── __init__.py
│ ├── config.py # env vars
│ ├── client.py # underlying API client
│ ├── server.py # FastMCP + tools
│ └── http.py # HTTP/SSE app
├── pyproject.toml
├── requirements.txt
└── vercel.jsonCode language: PHP (php)
Nøkkeldependenser i requirements.txt:
mcp>=1.2.0
fastapi>=0.110.0
uvicorn[standard]>=0.27.0
httpx>=0.27.0
python-dotenv>=1.0.0
pydantic>=2.6.0
Steg 2: Definer tools med FastMCP
FastMCP bruker Python-decorators for å registrere tools. Hver tool blir et JSON-RPC-method AI-en kan kalle. Slik definerer vi tools i vår mcp server:
from mcp.server.fastmcp import FastMCP
from mcp.types import ToolAnnotations
mcp = FastMCP(
"cornerstone-mcp",
instructions="Read-only access to lokallag, members and Frifond data.",
stateless_http=True,
json_response=True,
)
RO = ToolAnnotations(
readOnlyHint=True,
destructiveHint=False,
idempotentHint=True,
openWorldHint=False,
)
@mcp.tool(annotations=RO)
async def search_groups(query: str) -> str:
"""Search lokallag by name. USE THIS WHEN you know a name and need an id."""
results = await client.search_groups(query)
return json.dumps(results, ensure_ascii=False)Code language: PHP (php)
Tre detaljer som er lett å overse:
readOnlyHint=True— uten denne flagger ChatGPT alle tools som «WRITE / DESTRUCTIVE» i UI-en, og brukerne får advarsler.- «USE THIS WHEN»-pattern i docstring — LLM-en plukker rett tool basert på beskrivelsen. Vær eksplisitt på når tool-en skal brukes.
stateless_http=True— påkrevd for serverless-deploy (Vercel). Hver request er selvstendig.
Steg 3: Slim payloads for LLM-ergonomi
Den vanligste feilen i en ny mcp server er å returnere for mye data. ChatGPT har begrenset context-window, og en tool-respons på 290 000 tokens drukner svaret. Vår første list_groups returnerte 290 KB — agenten klarte ikke å hente ut konkrete grupper.
Løsningen: returner kun det nødvendige feltsettet, og lag separate tools for tunge spørringer:
list_groups— slim records (id, navn, leder), ~11 KB total.search_groups(query)— server-side filter, kun matchende rader.get_group(id)— fulle detaljer for én gruppe, ~2 KB.get_group_members(id, year)— eksplisitt opt-in for tunge medlemslister.
Etter slimmingen klarer ChatGPT å svare på «hvor mange medlemmer har Echo i 2024?» med ett tool-kall i stedet for fem. Hold tools små og fokuserte — det gjelder enhver mcp server.
Steg 4: Eksponer både SSE og streamable HTTP
FastMCP gir deg mcp.streamable_http_app() og mcp.sse_app() som separate Starlette-apper. Begge må aktiveres i samme prosess for å støtte både ChatGPT (SSE) og Claude Apps SDK (streamable HTTP):
from starlette.applications import Starlette
from starlette.routing import Mount, Route
import contextlib
streamable = mcp.streamable_http_app()
sse = mcp.sse_app()
async def dispatch(scope, receive, send):
path = scope.get("path", "")
if path.startswith("/sse") or path.startswith("/messages"):
await sse(scope, receive, send)
else:
await streamable(scope, receive, send)
@contextlib.asynccontextmanager
async def lifespan(_app):
async with mcp.session_manager.run():
yield
app = Starlette(
routes=[Route("/healthz", lambda r: JSONResponse({"ok": 1})),
Mount("/", app=dispatch)],
lifespan=lifespan,
)Code language: JavaScript (javascript)
Husk lifespan-konteksten — uten den får du RuntimeError: Task group is not initialized ved første tool-kall. FastMCPs streamable-håndterer trenger en aktiv async-kontekst.
Steg 5: Fiks DNS rebinding-protection
FastMCP slår på DNS rebinding-beskyttelse som default. Det betyr at en mcp server avviser requests med Host-header som ikke står i allowlist. Resultat: 421 Misdirected Request i prod hvis du glemmer det.
from mcp.server.transport_security import TransportSecuritySettings
security = TransportSecuritySettings(
enable_dns_rebinding_protection=True,
allowed_hosts=[
"localhost",
"127.0.0.1",
"din-mcp.vercel.app",
],
)
mcp = FastMCP(..., transport_security=security)Code language: JavaScript (javascript)
Steg 6: Deploy til Vercel
Vercel auto-detekterer FastAPI/Starlette via app-variabelen. Lag api/index.py som re-eksporterer Starlette-appen:
# api/index.py
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent.parent / "src"))
from cornerstone_mcp.http import app # noqaCode language: PHP (php)
Og en minimal vercel.json:
{
"rewrites": [
{ "source": "/(.*)", "destination": "/api/index" }
]
}Code language: JSON / JSON with Comments (json)
Sett env vars med vercel env add. Bruk printf, ikke echo — sistnevnte legger til newline som bryter HTTP-headere senere. Etter vercel --prod har du en mcp server live.
Steg 7: Auth — den vanskeligste biten
ChatGPT custom MCP-connectorer støtter kun OAuth eller no-auth. Statisk bearer-token i header — som er den intuitive løsningen — fungerer ikke. Tre realistiske valg for en kunde-prod mcp server:
- OAuth med dynamic client registration — korrekt løsning. ChatGPT henter
.well-known/oauth-authorization-server, registrerer seg som klient og henter token. 1–2 dagers utviklingsjobb første gang. - OAuth via proxy (Cloudflare Access, Auth0, Stytch) — outsource kompleksiteten.
- No-auth med URL-obscurity + PII-filter — read-only data, lite gjettbar URL, eksterne kall logges. Akseptabelt for testfase, ikke for langsiktig prod.
For Claude Apps SDK og Apps SDK-baserte klienter er bearer-token i header OK. For ChatGPT må du ned i OAuth-løypa eller bruke proxy.
Steg 8: Test mot ekte AI-klient
Smoke-test først via curl:
curl -sS -X POST https://din-mcp.vercel.app/mcp
-H 'Content-Type: application/json'
-H 'Accept: application/json, text/event-stream'
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'
| jq '.result.tools[].name'Code language: JavaScript (javascript)
Deretter koble på ChatGPT Workspace Agent, Claude Desktop eller egen agent og kjør 5–10 reelle spørsmål mot din mcp server. Mål: hvor mange tool-kall trengs per svar? Mer enn 3 betyr at tool-design eller server-instructions må strammes.
Vanlige fallgruver med en mcp server
- For store payloads. Returnerer du 50 KB+ per tool? Slim ned eller del opp i flere tools.
- Glemt
readOnlyHint. Tools markeres som DESTRUCTIVE i ChatGPT uten denne. Brukerne får advarsler. - Glemt host allowlist. Vercel-deploy gir 421 hvis Host-header ikke er whitelisted.
- Cross-invocation cache. In-memory cache forsvinner mellom Vercel-invocations. Bruk Vercel KV hvis du trenger persistens.
- Statisk bearer for ChatGPT. Fungerer ikke. Du må ha OAuth eller no-auth.
Cornerstone-MCP: vår faktiske case
Vi bygde denne for Tverrkirkelig (Levende Tverrkirkelig Ungdom). Servern eksponerer 14 read-only tools mot Cornerstones GraphQL: lokallag-søk, medlemstall per år, Frifond-utbetalinger, og en formel-basert kalkulator som estimerer LNUs neste tildeling.
- Repo: github.com/Sinfjell/cornerstone-mcp (åpen kildekode)
- Live:
cornerstone-mcp.vercel.app/mcp - Tools: 14 read-only, alle annotert som
readOnlyHint=True - Total kode: ~600 linjer Python, inkludert tester
Den brukes nå som data-lag for en ChatGPT Workspace Agent som svarer ansatte på spørsmål om medlemstall, Frifond-historikk og søknadsvurdering. Brukstidsbesparelse: 30–90 sekunder per spørsmål, 20–40 spørsmål i uken — typisk for en mcp server med fokusert scope.
Spørsmål om mcp server
Klar for å bygge en mcp server?
Hvis du har et internt datasystem teamet bruker daglig og lurer på om en mcp server vil løse problemet, ta kontakt for en gratis discovery-samtale. Vi kartlegger 3–5 høyverdige use cases, gir et fast tilbud, og leverer på 4–6 uker. Ta kontakt eller les mer om custom AI integrasjon generelt.
Trenger du dypere bakgrunn først? Bygge AI agent dekker build vs buy, og AI agent case Nora viser et komplett norsk eksempel. For den offisielle MCP-spec, se modelcontextprotocol.io.