Spring til indhold
DAmcpoauthcimdclaudechatgpt

Da min MCP-server mødte Claude og ChatGPT

Jeg testede remote MCP-onboarding i Claude og ChatGPT og endte dybt i DCR, CIMD, OAuth discovery og to virkelige Keycloak-fejl.

Scoret for

Remote MCP virker. En bruger, jeg aldrig har mødt, kan tilføje Agentics i Claude eller ChatGPT, oprette en konto midt i chatten og få et projekt op at køre uden at forlade samtalen.

Det overraskende var, hvor lidt af det svære der handlede om selve værktøjerne. Det svære var alt det rundt om: discovery-dokumenter, client registration, redirect URIs, tool-navne, caches og forskelle mellem de hosts, der kalder sig MCP-klienter.

Det, jeg lærte mest af, var CIMD — Client ID Metadata Documents. Jeg kendte ikke mekanismen, da jeg begyndte. Vi startede med Claude, som registrerede en OAuth-klient automatisk med DCR. Først da ChatGPT viste en grå CIMD-mulighed i sin connector-UI, blev jeg nysgerrig. Den nysgerrighed endte med, at vi fik CIMD til at virke i ChatGPT og opdagede, at Claude også brugte det.

Det her er historien om, hvordan det hænger sammen, og hvorfor jeg nu bygger tre forskellige offentlige MCP-flader til Agentics.

human prompt
1 lines2026-07-16 16:34
What is the currete state of mcp servers - a experiment i would like some research on. Is it possible to design a onboarding flow pure in mcp? I mean, can an unauthenticated user add a mcpsserver https://agentics.dk/mcp and it knows nothing about the user and then when added to claude code as example it will simply drive the user thoiugh a onboarding flow in chat only and signup/create user, get requried information and then when signed in more tools is avaible?

Eksperimentet

Mit succeskriterium var ret enkelt:

  1. En bruger tilføjer én offentlig MCP-URL i Claude eller ChatGPT.
  2. Klienten kan se nogle få anonyme værktøjer og begynde onboarding.
  3. Når et værktøj kræver identitet, starter et rigtigt OAuth-flow.
  4. Login med magic link opretter også en ny bruger, hvis mailadressen ikke findes.
  5. Klienten prøver værktøjet igen med access token.
  6. Brugeren vælger handle og opretter sit første Agentics-projekt i chatten.

Jeg ville teste mere end et pænt tools/list-svar. En remote MCP-server er først interessant, når en person uden lokal konfiguration kan komme hele vejen fra URL til en reel handling.

Den første version blev koblet på Claude. Senere tilføjede jeg den samme URL i ChatGPT. Begge kunne ende samme sted, men vejen derhen viste sig at være forskellig nok til, at “MCP-kompatibel” ikke er en komplet testplan.

OAuth-delen i et remote MCP-kald

Der er tre roller i flowet:

  • MCP-serveren er den beskyttede resource server.
  • Claude eller ChatGPT er OAuth-klienten, som handler på brugerens vegne.
  • Keycloak er authorization serveren, som logger brugeren ind og udsteder tokens.

Når klienten kalder et beskyttet værktøj uden token, skal serveren svare med HTTP 401 Unauthorized og en WWW-Authenticate-header. Et almindeligt MCP-tool-resultat med isError: true og teksten “log venligst ind” er ikke nok. Det ser bare ud som et fejlet værktøj; det starter ikke OAuth.

401-svaret peger klienten mod MCP-serverens Protected Resource Metadata. For en server på /mcp ligger dokumentet typisk under en URL som:

  • https://agentics.dk/.well-known/oauth-protected-resource/mcp

Dokumentet fortæller blandt andet, hvilken authorization server der beskytter ressourcen. Klienten læser derefter authorization serverens OAuth- eller OpenID Connect-discovery-dokument. I mit Keycloak-setup var den praktiske indgang:

  • https://auth.example/realms/agentics/.well-known/openid-configuration

Her finder klienten authorization_endpoint, token_endpoint, understøttede PKCE-metoder og eventuelt registration_endpoint.

Det er meget /.well-known/ for at nå frem til en login-side. Til gengæld kan en vilkårlig klient opdage hele flowet ud fra MCP-URL’en. MCP-authorization-specifikationen og OpenAI Apps SDK's auth-guide beskriver den samme rollefordeling.

Først virkede DCR

Claude kom først igennem med Dynamic Client Registration.

DCR betyder, at klienten opretter sig selv hos authorization serveren, før login begynder. Den sender metadata som redirect URIs og ønsket token-auth-metode til registration_endpoint, og Keycloak svarer med et nyt client_id.

Det omtrentlige flow var:

  1. Claude finder MCP-serverens protected-resource metadata.
  2. Claude finder Keycloaks discovery-dokument.
  3. Claude laver POST til Keycloaks registration_endpoint.
  4. Keycloak gemmer en ny client registration og returnerer et client_id.
  5. Claude starter authorization code-flow med PKCE.
  6. Efter login bytter Claude koden til tokens og kalder MCP-serveren igen.

Det virkede, men hver connector-test efterlod nye dynamisk registrerede klienter i Keycloak. DCR kræver også, at registration endpoint er tilgængeligt og beskyttet med de rigtige policies. Det er automatisk for brugeren, men authorization serveren opretter stadig state.

DCR opretter en klientregistrering, mens CIMD lader client_id pege på et HTTPS-dokument.

DCR laver en registrering hos authorization serveren. Med CIMD er client_id selv en HTTPS-URL, som serveren kan hente metadata fra.

ChatGPT viste mig den grå mulighed

Jeg havde egentlig ikke planlagt at undersøge en ny OAuth-mekanisme. ChatGPT gjorde mig opmærksom på den.

I connector-opsætningen stod der, at ChatGPT ville bruge DCR, fordi CIMD ikke var annonceret af serveren. CIMD-delen var grå. Jeg kendte ikke forkortelsen og spurgte ind til den.

human prompt
3 lines2026-07-17 14:50
ChatGPT will dynamically register an OAuth client using the Registration URL in the OAuth endpoints section. CIMD is unavailable because the server did not advertise CIMD support. is this something we can do? how is that advertised?

Client ID Metadata Documents vender registration-flowet om. I stedet for først at sende metadata til et registration endpoint bruger klienten en HTTPS-URL som sit client_id:

  • https://chatgpt.com/oauth/.../client.json
  • https://claude.ai/oauth/mcp-oauth-client-metadata

Authorization serveren henter dokumentet fra URL’en og validerer metadata som redirect URIs, grant types og token endpoint authentication. Der er ikke længere brug for en separat DCR-request for at få tildelt et client ID. Klientens stabile metadata-URL er identiteten.

OpenAI dokumenterer i dag, at ChatGPT understøtter CIMD, DCR, predefined OAuth clients og PKCE. Keycloak har tilsvarende en guide til MCP authorization server-integration.

Det gav mening til remote MCP. Store klienter som ChatGPT og Claude har allerede offentligt tilgængelige metadata-dokumenter. Min authorization server kan validere dem, uden at alle brugere opretter endnu en permanent client record.

CIMD virkede ikke bare, fordi vi annoncerede det

Vi opgraderede Keycloak og aktiverede CIMD-featuret. Discovery-dokumentet begyndte at annoncere:

{
  "client_id_metadata_document_supported": true
}

ChatGPT skiftede straks fra DCR til CIMD. Det var et godt tegn. Derefter begyndte de virkelige interoperabilitetsproblemer.

ChatGPT's metadata-dokument indeholdt et felt, som Keycloaks parser ikke kendte. Keycloak afviste hele dokumentet i stedet for at ignorere den ukendte metadata. En lokal patch flyttede flowet fra Client metadata fetch failed til den rigtige login-side. Derefter skulle allowlisten udvides, fordi Keycloak med rette validerede alle URL-domæner i dokumentet, inklusive OpenAI's asset-domæne.

Da ChatGPT virkede, gik jeg tilbage til Claude. Keycloak-loggen viste nu et URL-baseret client_id under claude.ai. Claude brugte altså også CIMD, men dens dokument ramte en anden edge case: en public client med token_endpoint_auth_method: none og samtidig jwt-bearer i grant_types. Keycloak afviste hele klienten med:

JWT authorization grant cannot be enabled in a public client

Jeg ville ikke falde tilbage til en manuelt pre-registreret Claude-klient. Vi fulgte begge fejl ind i Keycloaks kildekode, lagde to små patches ind som klasse-overlays i vores eget Docker-image og genafspillede de virkelige authorize-requests. Først derefter virkede CIMD for begge klienter.

Det interessante for mig var rækkefølgen: Claude kom først igennem med DCR. ChatGPT's UI viste mig, at der fandtes noget nyere. Først derefter så jeg i loggen, at Claude faktisk også understøttede det.

Hele Keycloak-forløbet—Dockerfile-patchene, de eksisterende upstream-issues og måden vi beviste den rigtige løsning på—står i opfølgningsposten Jeg måtte patche Keycloak for at få CIMD til at virke.

MCP-hosts er ikke ens, selv når protokollen er det

OAuth var den største del af arbejdet, men ikke den eneste forskel.

Jeg ramte blandt andet:

  • Claude-connectoren accepterede ikke punktummer i tool-navne. handle.check måtte blive til handle_check på grund af hostens regex.
  • Værktøjslister blev cachelagret, så en korrekt rename eller et nyt værktøj kunne kræve, at connectoren blev fjernet og tilføjet igen.
  • tools/listChanged er nyttigt, men jeg vil ikke gøre sikkerhed eller korrekt scope afhængig af, at alle hosts reagerer ens på det.
  • Remote OAuth skulle være tilgængelig på almindelig offentlig HTTPS. En udviklingstunnel på en særskilt port gav problemer længe før MCP-serveren selv blev kaldt.
  • En proxy-fejl med videresendte hop-by-hop headers lignede først en tunnel-fejl. Jeg måtte teste hvert hop direkte for at placere fejlen i den rigtige komponent.

Min praktiske konklusion er at bygge efter den strengeste klient, jeg vil understøtte, og teste med de rigtige connector-flows. Et syntetisk MCP SDK-testkald finder ikke nødvendigvis hostens tool-name-regler, cache eller OAuth-registration-adfærd.

De tre Agentics MCP-servere

Eksperimentet begyndte med én onboarding-server. Undervejs blev det tydeligt, at én stor værktøjskasse ikke er det rigtige slutdesign.

Tre scopes: Agentics Platform MCP, Project MCP og Personal MCP.

Agentics Platform MCP

Platform-serveren er indgangen. En ny bruger kan opdage Agentics, tjekke identitet, vælge handle, oprette et projekt og få de relevante næste URLs tilbage.

Det er hubben. Den skal ikke kende alle værktøjer i alle projekter. Den hjælper klienten og brugeren med at finde det rigtige scope.

Agentics Project MCP

Hvert projekt får sin egen MCP-flade, eksempelvis:

  • /p/{owner}/{project}/mcp

Jeg overvejede først at sætte project_id på alle tools eller have et skjult set_active_project. Begge muligheder lægger ansvar over på modellens hukommelse. Efter en lang chat, compaction eller reconnect kan serverens state og modellens forståelse glide fra hinanden.

Med projektet i URL’en er tvetydigheden strukturelt fjernet. Serveren viser kun værktøjer og data for det ene projekt. Svar kan stadig gentage projektets identitet som ekstra sikkerhed, men modellen skal ikke huske et id gennem hele samtalen.

Agentics Personal MCP

Den personlige server er mere eksperimenterende:

  • /u/{handle}/mcp

For ejeren er den en personlig assistents værktøjskasse: beskeder, aktiviteter, projekter og senere kalender og booking. For andre kan den være en kontrolleret social flade: send en besked, følg offentlige aktiviteter eller anmod om et møde.

Den langsigtede idé er, at vores assistenter kan tale sammen på vores vegne, men gennem eksplicitte værktøjer og tilladelser. Min assistent må ikke udgive sig for at være mig. Den må udføre de handlinger, jeg konkret har givet den lov til.

Er Claude og ChatGPT klar til remote MCP?

Ja, i den betydning der betyder noget for mig: Jeg kan bygge en rigtig offentlig MCP-server, få en ukendt bruger gennem onboarding og OAuth og udføre handlinger i både Claude og ChatGPT.

Nej, hvis “klar” betyder, at én vellykket test i én klient beviser, at alle andre virker. Platformene har forskellige begrænsninger, og de møder authorization servere på måder, som først bliver tydelige med virkelige client metadata documents.

Det har ikke fået mig til at vente. Tværtimod. Jeg er nu ved at gøre Platform MCP, Project MCP og Personal MCP klar til eksterne klienter.

Men jeg tænker ikke længere på en remote MCP-server som “nogle tools over HTTP”. Den er samtidig en offentlig API, en OAuth-integration, en sikkerhedsgrænse og en kompatibilitetsmatrix.

Det er nok den vigtigste læring fra forsøget.

Den tekniske fortsættelse er Jeg måtte patche Keycloak for at få CIMD til at virke: to virkelige client documents, to kirurgiske Dockerfile-patches og måden vi arbejdede fra lokal proof tilbage mod upstream.