Hvordan integrerer jeg en AI-agent med Apier over MCP?
Av Antony Richard Grov, gründer av Apier
I tre steg som hver tar minutter. Pek MCP-klienten din mot det hostede endepunktet, https://www.apier.no/api/mcp, enten direkte over streamable HTTP eller gjennom den publiserte stdio-proxyen. Presenter en legitimasjon: en API-nøkkel i Authorization-headeren når koden din styrer forespørselen, eller et OAuth 2.1-tilgangstoken når en hostet klient logger brukeren inn for deg. Kall så fullmaktsverktøyet for et selskap og les svaret som den unionen det er: hver klausul kommer tilbake enten registerkodet med en transkribert signeringsmodus eller som utolket fritekst returnert hel, og en integrasjon må håndtere begge. Oppdagelse krever ingen legitimasjon, ingenting her skriver til et offentlig system, og avsnittet om kvitteringer mangler med vilje.
Steg 1: koble til MCP-serveren
Det finnes ett hostet endepunkt og to måter å nå det på. Klienter som snakker fjern-MCP innebygd, som i dag betyr VS Code i agentmodus, OpenAI Agents SDK, Azure AI Foundry og nyere Cursor-versjoner, kobler seg rett til URL-en og sender nøkkelen i en Bearer-header selv. Klienter som bare snakker stdio, som betyr Claude Desktop og eldre Cursor, starter den publiserte npm-pakken gjennom npx; den leser APIER_API_KEY fra miljøet og videresender samme header. Ingen verktøylogikk kjører lokalt uansett, så begge veiene gir identiske svar.
{
"servers": {
"apier": {
"type": "http",
"url": "https://www.apier.no/api/mcp",
"headers": { "Authorization": "Bearer ${env:APIER_API_KEY}" }
}
}
}Den direkte blokken over er formen hver innebygd klient bruker, og den leser nøkkelen fra miljøvariabelen APIER_API_KEY i stedet for å bære en, så filen kan sjekkes inn trygt. Stdio-blokken for Claude Desktop og Cursor, sammen med Windows-startfellen som stopper mange førstegangsoppsett, står i guiden om å koble en klient til norske selskapsdata over MCP, og denne siden gjentar den ikke. Uansett vei er håndtrykket og verktøykatalogen nøkkelløse, så en manglende nøkkel viser seg ved det første reelle kallet, aldri ved tilkobling.
Steg 2: OAuth 2.1-flyten for hostede klienter
En hostet klient som koblingen i Claude.ai eller ChatGPT kan ikke sette en egen bearer-header, så den trenger en måte å finne en autorisasjonsserver og logge brukeren inn på. Autorisasjonsmodellen i MCP gir den én, og serveren implementerer den i tre trekk. Først returnerer et verktøykall uten gyldig legitimasjon 401 med en WWW-Authenticate-utfordring der parameteren resource_metadata peker på et metadatadokument for den beskyttede ressursen. Dernest navngir det dokumentet ressursidentifikatoren et token må bære som audience, den betrodde autorisasjonsserveren og de scopene den serveren kan utstede. Til slutt registrerer klienten seg dynamisk, sender brukeren til innlogging og godkjenning av koblingen på samtykkesiden, og prøver på nytt med tilgangstokenet den får.
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="apier-mcp", error="invalid_token",
error_description="A valid Apier API key or an OAuth 2.1 access token for this resource is required",
resource_metadata="https://www.apier.no/.well-known/oauth-protected-resource/api/mcp"
# Dokumentet den URL-en serverer (bare identitets-scopes, aldri Apier-tillatelser):
{
"resource": "https://www.apier.no/api/mcp",
"authorization_servers": ["<den betrodde utstederen, lest fra dette dokumentet>"],
"bearer_methods_supported": ["header"],
"scopes_supported": ["openid", "profile", "email", "phone", "offline_access"]
}To fakta om det dokumentet avgjør hvordan du resonnerer om tilgang. Scopene det annonserer er bare identitets-scopes, fordi et scope autorisasjonsserveren ikke kan utstede, ville fått autorisasjonsforespørselen til å feile rett ut; ingen Apier-tillatelse blir noen gang bedt om under innlogging. Og i det øyeblikket kontoeieren godkjenner koblingen, utstedes en dedikert API-nøkkel med lesetilgang, merket MCP OAuth connection, og knyttes til den innloggede identiteten uten noe operatørsteg. Fra da av verifiseres tokenet på signatur, utsteder, audience og levetid, bare asymmetriske algoritmer, og hvert kall bærer nøkkelens scopes, nivå og hastighetsgrenser nøyaktig som om nøkkelen var presentert direkte. Tokenets eget scope-krav kan snevre inn den tilgangen og kan aldri utvide den.
Steg 3: kall fullmaktsendepunktet
Spørsmålet en agent oftest trenger svar på før den handler på vegne av et selskap, er hvem som er registrert med rett til å signere for det, og hvordan. Ett GET på REST-flaten svarer på det, og verktøyet get_company_authority returnerer samme nyttelast over MCP, så en agent som oppdaget verktøyet i steg 1, trenger ikke mer. Begge krever en nøkkel med scopet read:brreg, som standardnøkkelen gir, og begge tar det 9-sifrede organisasjonsnummeret og ingenting annet. Eksemplene under bruker dokumentasjonens organisasjonsnummer; bytt inn et ekte, og svarets form endrer seg ikke.
# REST: ett GET, Bearer-nøkkel, scope read:brreg.
curl -s https://www.apier.no/api/v1/company/999999999/authority \
-H "Authorization: Bearer apr_live_<your_key_here>"
# MCP: samme svar gjennom tools/call.
curl -s -X POST https://www.apier.no/api/mcp \
-H "Authorization: Bearer apr_live_<your_key_here>" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call",
"params":{"name":"get_company_authority","arguments":{"org_number":"999999999"}}}'Toppen av svaret er en deterministisk klassifisering, én av sole, joint, by_role, prokura_only, no_authority eller unknown, med de registrerte innehaverne listet under. Det er den raske veien, og for en ja-eller-nei-vurdering er den ofte nok. Under ligger coded_authority, delen denne guiden handler om: modellen over de speilede signaturrett- og prokuraklausulene, hver båret med verifiseringsstempelet fra sist registeret ble lest. Den er stempelærlig av design, som betyr at et fravær aldri slås sammen til et nei med mindre registeret selv sa nei.
Les begge grenene av svaret
Hver av signatur og prokura er en trippel: en tilstand, en post og et verifiseringsstempel. Posten er den diskriminerte unionen koden din må forgrene på. Når registeret serverte klausulen under en kode den lukkede taksonomien kjenner, er posten register_coded, og hver klausul bærer den ordrette teksten, registerkodene og en transkribert signeringsmodus som sole, joint eller severally. Det er grenen en agent kan forgrene på, og eksemplet under viser den med et positivt verifisert fravær av prokura ved siden av.
"coded_authority": {
"status": "ok",
"signatur": {
"state": "coded_verified",
"record": {
"kind": "register_coded",
"clauses": [
{ "text": "Daglig leder alene", "codes": ["R0002"], "mode": "sole", "truncated": false }
]
},
"verification": { "verified_at": "2026-09-20T10:00:00+00:00", "outcome": "covered" }
},
"prokura": {
"state": "absent_verified",
"record": { "kind": "empty", "reason": "empty_array" },
"verification": { "verified_at": "2026-09-20T10:00:00+00:00", "outcome": "absent" }
}
}Når ordlyden er en taksonomien ikke kjenner, er posten free_text, og klausulen kommer tilbake hel, med codes og mode begge satt til null. Ingenting tolkes delvis, ingenting gjettes, og tilstanden leser free_text_uninterpreted så en leser ikke kan forveksle den med et kodet svar. Dette er et normalt utfall for et lovlig registrert selskap, ikke en feil, og den ærlige håndteringen er å vise ordlyden til et menneske eller rute beslutningen dit. En agent som runder den av til ja eller nei, finner opp et faktum registeret ikke leverte.
"coded_authority": {
"status": "ok",
"signatur": {
"state": "free_text_uninterpreted",
"record": {
"kind": "free_text",
"clauses": [
{ "text": "<registerets ordlyd, byte for byte>", "codes": null, "mode": null, "truncated": false }
]
},
"verification": { "verified_at": "2026-09-20T10:00:00+00:00", "outcome": "covered" }
}
}| Tilstand | Hva den hevder | Hva en agent gjør |
|---|---|---|
| coded_verified | En registerkodet klausul som siste lesing bekreftet. | Forgren på modusen; siter stempelet. |
| coded_unverified | En kodet klausul uten bekreftende stempel. | Brukbar, men si at stempelet mangler. |
| free_text_uninterpreted | En ordlyd taksonomien ikke kjenner, returnert hel. | Vis teksten til et menneske; utled aldri en modus. |
| absent_verified | Registeret svarte positivt at ingen klausul er registrert. | Den ene tilstanden som kan leses som et nei. |
| no_record_unverified | Ingenting på post og intet positivt fravær. | Behandle som ukjent, ikke som et nei. |
Hva som kommer med kvitteringene
Ett avsnitt en fullstendig integrasjonsguide ville hatt, mangler her med vilje: å verifisere en signert kvittering fra ende til ende. Kvitteringer er en senere arbeidsblokk som ikke er bygget, og en gjennomgang uten noe å verifisere ville lest som en kapabilitet tjenesten ikke har; siden får avsnittet når kvitteringene leveres. Kryptografisk verifisering av selve fullmaktssvaret er live i dag. Legg ?certificate=true på samme GET, og svaret pakker det identiske svaret inn i et sertifikat signert med en frakoblet JWS, som kan verifiseres offline mot det offentlige nøkkelsettet på /.well-known/jwks.json. Guiden om verifisering av sertifikater har nøkkelhentingen og verifiseringsskriptet, så denne siden gjentar dem ikke.
Ofte stilte spørsmål
- Hvilken legitimasjon bør en agentintegrasjon bruke?
- En API-nøkkel når koden din styrer forespørselen, som dekker VS Code, agentkjøretidene fra OpenAI og Azure og alt du kjører selv. OAuth 2.1-flyten finnes for hostede klienter som ikke kan sette en egen bearer-header, som koblingene i Claude.ai og ChatGPT: de finner autorisasjonsserveren i metadatadokumentet, logger brukeren inn og presenterer et tilgangstoken i stedet. Begge kommer i samme Authorization-header, og begge fortsetter å virke.
- Gir et bredere OAuth-scope agenten mer tilgang?
- Nei. Tilgangen en tilkoblet agent har, kommer fra den dedikerte API-nøkkelen den innloggede identiteten er knyttet til, og den nøkkelen utstedes med lesescopes i det øyeblikket kontoeieren godkjenner koblingen. Tokenets eget scope-krav kan bare snevre inn det settet, aldri utvide det, og ingen Apier-tillatelse blir bedt om under innlogging fordi autorisasjonsserveren bare utsteder identitets-scopes.
- Hva er forskjellen på den kodede grenen og fritekstgrenen?
- Om registeret serverte klausulen under en kode den lukkede taksonomien kjenner. En kodet klausul bærer ordlyden ordrett pluss registerkodene og en transkribert signeringsmodus, så en agent kan forgrene på modusen. En fritekstklausul er en ordlyd taksonomien ikke kjenner, returnert hel med codes og mode satt til null og aldri delvis tolket. Den andre grenen er et normalt utfall, ikke en feil, og en agent som behandler den som feil, vil avvise lovlig registrerte selskaper.
- Kan en fraværende klausul leses som ingen fullmakt?
- Bare når tilstanden sier det. absent_verified betyr at registeret selv svarte at ingen klausul er registrert, og det er den ene tilstanden som hevder et fravær. no_record_unverified betyr at ingenting ligger på post og at ingenting bekreftet det, som er et hull i dataene og ikke et faktum om selskapet. Å slå de to sammen til ett nei er nøyaktig feilen stempelparet finnes for å hindre.
- Hvor er avsnittene om JWKS og verifisering av kvitteringer?
- Sertifikatverifisering er live og dokumentert i sin egen guide: legg ?certificate=true på fullmakts-GET-en, hent det offentlige nøkkelsettet fra /.well-known/jwks.json, og verifiser den frakoblede JWS-en offline. Kvitteringsverifisering kommer med kvitteringene, en senere arbeidsblokk som ikke er bygget ennå, så denne siden beskriver den ikke. En plassholder som leses som en kapabilitet er verre enn et ærlig hull, og siden får det avsnittet når kvitteringsarbeidet leveres.