Ga naar inhoud

Core gebruiken vanuit een MCP-client

Het concept achter MCP staat in MCP.

Met een MCP-client kun je de API-operaties van Core als tools aanroepen. Gebruik bijvoorbeeld een codeerassistent of je eigen agent. Core past dezelfde toegangscontroles en registratie toe als bij een HTTP-aanroep.

Kies het endpoint dat bij je inloggegevens hoort:

Endpoint Inloggegevens Beschikbare tools
POST /api/v1/mcp API-sleutel Operaties die je sleutel mag uitvoeren. Met management kun je ook organisaties beheren.
POST /api/v1/mcp/toolsets/{name} API-sleutel Eén van je eigen toolsets: je connectors, MCP-servers, kennis en de rest, uitgevoerd door Core (hieronder).
POST /api/v1/management/mcp Inloggen als organisatiebeheerder Beheeroperaties voor de organisaties waartoe je toegang hebt.

Een API-sleutel werkt alleen op de eerste twee endpoints; een beheerdertoken alleen op het derde. Alle drie gebruiken streamable HTTP zonder MCP-sessie. GET en DELETE geven 405: er is geen losse stream om te hervatten of sessie om te beëindigen.

Elke operatie is een tool

Tools heten naar hun operatie, in de vorm <werkwoord>_<zelfstandig naamwoord>: create_skill, list_prompts, search_index, create_response, rotate_key. De argumenten zijn de padparameters, de queryparameters en de JSON-body van de operatie, in één schema. Waar de HTTP-aanroep een header Idempotency-Key accepteert, heeft de tool een argument idempotency-key.

Je krijgt de tools te zien die je inloggegeven echt kan gebruiken. Op het sleutelendpoint zijn dat de operaties die de capabilities van je sleutel toestaan: een sleutel zonder knowledge krijgt search_index niet aangeboden. De lijst volgt de API, dus een operatie die aan Core wordt toegevoegd is een tool zonder dat je iets instelt.

Zes soorten operaties blijven er bewust buiten. Een toolaanroep kan niet dragen wat de eerste vier nodig hebben; de laatste twee blijven erbuiten om wat ze antwoorden of schrijven:

Wat ontbreekt Waarom
een bestand uploaden (POST /files en de beheerdersvariant ervan) een toolaanroep kan geen bytes versturen
bytes lezen (/files/{id}/content, /artifacts/{id}/content, een skill-bestand, de auditexport) een toolresultaat kan geen bestand dragen
GET /api/health, GET /api/ready, de OAuth-callback ze vragen geen inloggegeven en horen dus bij geen van de drie endpoints
de MCP-endpoints zelf dat is dit
een weergaveadres aanvragen (POST .../view) en een deellink maken, opvragen of lezen (POST en GET .../links), op een bestand, een artefact of een pagina het antwoord is een adres dat een bestand opent zonder inloggegeven, en een model dat er een las, zou het aan elke andere tool kunnen doorgeven (Een document schrijven). Een link intrekken geeft geen adres terug en is wel een tool
de eigen auditgebeurtenissen van een product leveren (POST /audit/events) alleen de eigen backend van het product schrijft het spoor in naam van het product, nooit een model (De eigen gebeurtenissen van je product leveren)

Gebruik daarvoor HTTP. De gewone route is: een bestand over HTTP uploaden en daarna het id ervan noemen in een toolaanroep — zie Bestanden en artefacten via de API.

Verbinden met een API-sleutel

Het sleutelendpoint neemt je sleutelgeheim als bearer-token bij elk verzoek, precies zoals de HTTP-API. Er is geen inlog en geen tokenuitwisseling.

Claude Code:

claude mcp add --transport http artific-core https://<jouw Core-adres>/api/v1/mcp \
    --header "Authorization: Bearer ack_..."

Cursor, in .cursor/mcp.json:

{
  "mcpServers": {
    "artific-core": {
      "url": "https://<jouw Core-adres>/api/v1/mcp",
      "headers": { "Authorization": "Bearer ack_..." }
    }
  }
}

Claude Desktop voegt een externe server toe als eigen connector, en die neemt een URL en logt in met OAuth. Er is geen veld voor een header, dus je kunt er geen API-sleutel mee meesturen. Gebruik een client die wél een header kan sturen, of verbind als persoon met het beheerendpoint.

Behandel het configuratiebestand als een kluis: het bevat het sleutelgeheim in leesbare tekst. Een verbinding die je niet meer wilt, beëindig je door de sleutel te roteren of in te trekken.

Een weigering komt voordat er een toollijst wordt opgebouwd, en het is dezelfde weigering die de HTTP-API geeft: 401 bij een geheim dat Core niet kent, 403 bij een token van het verkeerde soort, 409 bij een organisatie die niet actief is. Elk daarvan is een probleemdocument met een title en een detail.

Verbinden als beheerder

POST /api/v1/management/mcp is één endpoint voor het hele platform: er staat geen organisatie in het adres, dus één verbinding beheert alle organisaties die je mag bereiken. Begin met list_tenants om ze te vinden; elke andere tool noemt er één bij zijn tenant_id.

Wat je mag doen is wat je in de console mag doen, en de tools verbergen dat niet: je bereikt de organisaties die je beheert en alles wat daaronder valt — hun sleutels, bibliotheken, verbruik en auditgegevens. Een tool die een organisatie daarbuiten noemt, antwoordt dezelfde 404 die de console zou laten zien. De lijst zelf is niet gefilterd: tools voor wat alleen je operator mag — de prijslijst van de installatie, de outbox van het platform — staan er ook in, en weigeren zodra je ze aanroept.

Je client logt de persoon in in plaats van een geheim te bewaren:

  1. De client vraagt Core waar hij moet inloggen, via GET /.well-known/oauth-protected-resource/api/v1/management/mcp. Dat document noemt het endpoint en de ene autorisatieserver die er tokens voor uitgeeft — dezelfde inlog als de console gebruikt. Een client die zonder token verbindt, krijgt een 401 die naar datzelfde document wijst.
  2. De client registreert zichzelf bij die server, die registratie van iedereen aanneemt en zelf bepaalt wat de client mag zijn.
  3. De client doorloopt een authorization code-flow met PKCE bij die server, in je browser, en de eerste keer keur je hem bij naam goed.
  4. Het token dat hij krijgt draagt jouw eigen rollen en organisaties. Core geeft niets uit en bewaart niets over de client.

Er valt dus niets in te stellen en je hoeft je operator niet om een client-id te vragen. Wat de client mag zijn, kiest hij ook niet zelf: hij mag alleen een persoon inloggen, hij bewaart geen geheim, en zijn callback is een loopback-adres, zijn eigen applicatieschema, https of — omdat de inlogserver die twee niet uit elkaar kan houden — een gewoon http-adres. Een client die iets anders eist, een callback met een jokerteken voorop, wordt bij de registratie geweigerd in plaats van later.

Claude Code:

claude mcp add --transport http artific-core-management \
    https://<jouw Core-adres>/api/v1/management/mcp

Draai daarna /mcp in Claude Code en kies om in te loggen; de browser gaat open voor de inlog.

Cursor, in .cursor/mcp.json — zonder headers, want de inlog levert het token:

{
  "mcpServers": {
    "artific-core-management": {
      "url": "https://<jouw Core-adres>/api/v1/management/mcp"
    }
  }
}

Claude Desktop: voeg hetzelfde adres toe als eigen connector en log in wanneer hij erom vraagt.

Het sleutel- en het beheerendpoint noemen zichzelf in de verbinding, artific-core en artific-core-management, zodat een client die ze allebei heeft ingesteld ze uit elkaar houdt.

Een toolset als MCP-server aanbieden

Het sleutelendpoint en het beheerendpoint bieden de eigen API van Core aan. POST /api/v1/mcp/toolsets/{name} biedt je eigen tools aan: één toolset — je API-connectors, de tools van de MCP-servers die je registreerde, je kennis- en geheugenindexen, je agents, de builtins, en de operaties van Core zelf voor zover je die erin groepeerde — als een server waarmee een agent elders kan verbinden. Er wordt niets uitgerold en niets opgeslagen: het endpoint ís de toolset die je al hebt.

{name} is de naam van de toolset zelf, dezelfde naam die een verzoek met {"type": "toolset", "name": …} schrijft. Elke geregistreerde MCP-server is ook een toolset zonder dat je er een aanmaakt, onder mcp__<servernaam>. Het inloggegeven is het geheim van je sleutel, en de sleutel heeft de capability tooling nodig; zonder die capability antwoordt het endpoint 404, net als een adres dat niet bestaat.

Claude Code:

claude mcp add --transport http bibliothecaris \
    https://<jouw Core-adres>/api/v1/mcp/toolsets/bibliothecaris \
    --header "Authorization: Bearer ack_..."

Cursor, in .cursor/mcp.json:

{
  "mcpServers": {
    "bibliothecaris": {
      "url": "https://<jouw Core-adres>/api/v1/mcp/toolsets/bibliothecaris",
      "headers": { "Authorization": "Bearer ack_..." }
    }
  }
}

Wat er in de lijst staat is wat een beurt die deze toolset noemt aangeboden zou krijgen, één keer en op dezelfde manier bepaald: de patronen worden uitgeklapt tegen je catalogus, een tool die de capabilities van je sleutel niet toestaan blijft weg, en de regels van de soort tool in je beleid halen eruit wat ze niet toestaan. Een toolset die je niet hebt antwoordt 404, en een groep die uitklapt naar meer dan de 128 tools die één beurt mag dragen antwoordt 422 — noem wat de agent nodig heeft, niet alles. Een sleutel waarvan het beleid een agent vereist (settings.agents_required) krijgt helemaal geen toolset: het endpoint antwoordt 403 voordat er een tool wordt getoond of aangeroepen, omdat een aanroep hier buiten elke agent valt.

Wat niet wordt aangeboden: builtin__subagent, want hier is geen beurt om subagent van te zijn, builtin__load_skill, dat bij een response met skills hoort, en een agent waarvan de versie background.mode: "always" zegt, die alleen als taak van een beurt draait. Al het andere dat je toolset noemt wel.

Een agent in de toolset staat in de lijst als agent__<agent>, met de variabelen van zijn prompt en input als argumenten. Hem aanroepen draait één beurt van die agent, gecontroleerd zoals POST /responses met die agent dat zou doen, en geeft het antwoord van de agent terug, of Agent could not be run. als fout wanneer de beurt niet kon starten; bij je maandlimiet is het resultaat een fout met de zin van de limiet. Die beurt laat een eigen response-id en eigen gebruiksregels achter naast de tool_invocation van de aanroep.

Een aanroep voert de tool echt uit, onder dezelfde regels als in een beurt: het beleid van je sleutel beslist per aanroep, uitgaand verkeer wordt net zo gecontroleerd, de tool krijgt hetzelfde tijdsbudget, en een weigering is dezelfde zin — Denied by policy., Unknown tool., Tool timed out., Tool failed. Bytes die een tool maakte komen mee in het antwoord zelf, een afbeelding als afbeelding en al het andere als ingesloten bestand — als je sleutel de capability artifacts heeft, en tot de 25 MiB die een artefact mag zijn, per bestand én voor het antwoord bij elkaar; daarboven krijg je een notitie met het aantal weggelaten bytes. Er wordt niets bewaard, want een artefact hoort bij een response en een directe aanroep heeft er geen.

Drie dingen gaan hier anders dan in een beurt, omdat er geen beurt is:

  • Een tool die iets afleidt uit je aanroep en wegschrijft — de memory_write van een index — bewaart dat als untrusted inhoud. Niemand heeft de tekst hier beoordeeld, dus hij komt nooit terug als vertrouwde instructie in een latere beurt.
  • Een kennistool met een stap die een eigen model heeft, roept dat model echt aan, en die aanroep wordt niet getraceerd — net als bij POST /knowledge/query. Een stap die zelf geen model noemt, heeft niets om op terug te vallen, want het model van de beurt zou dat zijn geweest, en de run faalt met no_model_configured.
  • Een API-koppeling krijgt het id van het verzoek waar een beurt het id van de response meestuurt, in de header Idempotency-Key en in het veld response_id. Het blijft één id per aanroep; wat je endpoint niet kan herkennen is een herhaling, want opnieuw proberen is een nieuw verzoek.

Wat een aanroep achterlaat. Eén regel tool_invocation in Verbruik en de beleidsbeslissingen in je auditspoor, op naam van de sleutel die aanriep en zonder response-id — er is geen response. Om dezelfde reden is er geen traceregel: de traces in de console zijn beurten, en deze aanroep is er geen. Zoek hem in verbruik en audit.

Veel tools via twee

Zet ?tool_search=router achter het sleuteladres of het adres van een toolset, en de server toont twee tools in plaats van allemaal: builtin__tool_search, die tools vindt doordat de agent beschrijft wat hij nodig heeft en van elke tool de naam, beschrijving en parameters teruggeeft, en builtin__tool_run, die er één op naam uitvoert met zijn argumenten. Gebruik het wanneer een agent anders meer tools krijgt dan hij goed kan kiezen, zoals de eigen API van Core met honderden operaties:

claude mcp add --transport http artific-core \
    "https://<jouw Core-adres>/api/v1/mcp?tool_search=router" \
    --header "Authorization: Bearer ack_..."

Een zoekopdracht vindt alleen een tool die de server ook zelf had getoond, en een uitvoering wordt beslist, uitgevoerd en gefactureerd zoals een aanroep van die tool. Je sleutel heeft tooling.tool_search.enabled nodig en je organisatie moet tool search toestaan; anders antwoordt het adres 422 met tool_search voordat er een tool wordt getoond. Een zoekopdracht embedt wat de agent vroeg met het embeddingmodel van je organisatie, gefactureerd aan je sleutel, en rangschikt alleen op trefwoord zodra het maandplafond is bereikt. Hoe de router werkt, staat op De toolrouter.

Een agent het platform geven

Dezelfde operaties van de sleutelkant zijn ook beschikbaar voor een beurt die binnen Core draait, helemaal zonder MCP-client. Het zijn tools met de naam core__<tool> — core__create_skill, core__search_index — en je biedt ze aan als elke andere tool:

{
  "model": "<een model-id>",
  "input": "Schrijf een skill voor het triëren van terugbetalingsverzoeken en sla hem op.",
  "tools": [{ "type": "tool", "name": "core__create_skill" }]
}

Een core__-tool draait als de sleutel van wie de beurt is, dus hij bereikt precies wat die sleutel over HTTP bereikt en niets meer. Daar hoort ook het beheer van de eigen organisatie bij, als de sleutel de capability management heeft: core__create_organisation, core__register_organisation_key, core__update_own_organisation en de rest ervan. Een sleutel zonder management krijgt er geen enkele van te zien. Wat zo'n sleutel mag, en waar, staat op Organisaties beheren via de API.

Zet in een toolset wat een agent nodig heeft en hang die aan een verzoek of aan een agentversie:

curl -s -X POST "$CORE_URL/toolsets" \
    -H "Authorization: Bearer $CORE_KEY" -H "Content-Type: application/json" \
    -d '{"name": "bibliothecaris", "description": "Onderhoudt skills en prompts.",
         "tools": ["core__*_skill", "core__list_skills", "core__list_prompts"]}'

Noem wat de agent nodig heeft, niet alles. core__* breidt uit naar elke operatie van de sleutelkant, meer dan honderddertig — boven de 128 tools die één beurt mag dragen — dus een verzoek dat die glob aanbiedt wordt geweigerd met 400 en "This request offers more tools than a turn may carry." Een lange toollijst kost het model bovendien budget voordat het je invoer leest.

Bovenop de toolset gelden deze grenzen:

  • Het beleid van je sleutel bepaalt wat hij überhaupt mag uitvoeren. Eén regel, {"effect": "forbid", "kind": "tool", "match": {"names": ["core__*"]}}, neemt deze tools weg bij één sleutel, en een permit van de soort tool die ze matcht geeft ze. Lees de huidige regels met GET /key.
  • Een core__-tool draait één niveau diep. De geneste beurt die een core__create_response start, krijgt zelf geen enkele core__-tool aangeboden, en zo een daar noemen levert 400 op tools: "core__ tools run on Core's own request path, one level deep: not inside a turn Core itself started." Wil je vanuit een beurt een lange aanroep naar Core doen, vraag die geneste aanroep dan om background: true en pol hem.
  • Een keten van beurten die beurten aanroepen stopt op vijf niveaus. Je mag het eigen MCP-endpoint van deze installatie als MCP-server registreren of een API-connector naar de API ervan laten wijzen, en een beurt die daarlangs create_response aanroept, start een beurt één niveau dieper. Core telt die niveaus, langs welke weg de aanroep ook ging: een agent als tool, een core__-tool of een registratie op het eigen adres van Core. Een beurt op het vijfde niveau wordt elke zulke aanroep geweigerd. Het model leest This turn is too deep in a chain of calls to start another. als mislukt toolresultaat (call_depth_exceeded in de trace), de beurt gaat door en antwoordt, en voor de geweigerde aanroep wordt niets gerekend. Een aanroep die als taak wordt gestart is de enige uitzondering: de beurt start de taak, wat telt als de aanroep van de tool, en de taak mislukt daarna met call_depth_exceeded zonder dat de aanroep wordt gedaan. Je operator stelt het getal in met CORE_TOOL_CALL_DEPTH_MAX. De telling volgt het adres dat je operator deze installatie gaf: een aanroep die via een eigen server van jou bij Core terugkomt, of via een ander adres van dezelfde installatie, begint weer op niveau één.
  • Een beurt die Core voor jou draait, is dat niet. Een achtergrondrun, een geplande beurt en een stap van een evaluatie krijgen dezelfde core__-tools aangeboden als je eigen verzoek, onder dezelfde sleutel en dezelfde capabilities, dus een agent die werkt als jij hem aanroept, werkt ook als de planning hem afvuurt. Één uitzondering: mensen beheren — core__list_own_organisation_admins, core__list_organisation_admins, core__invite_organisation_admin, core__resend_organisation_admin_invite en core__remove_organisation_admin — antwoordt 503 in een beurt die Core voor jou draait, en blijft een aanroep die je eigen applicatie doet.

GET /tools?source=core geeft deze tools met het argumentenschema dat het model ziet, net zoals de lijst elke andere bron geeft.

Wat een toolaanroep antwoordt

Een geslaagde aanroep antwoordt de JSON van de operatie zelf, zowel als gestructureerde inhoud als als tekst. Een 204 antwoordt een leeg object.

Al het andere is een foutresultaat met de woorden van het probleemdocument zelf — "<title>: <detail> (<status>)" — nooit een protocolfout. Zo leest een 404 van een capability die uitstaat voor het model precies zoals hij voor een HTTP-aanroeper leest, en gaat de beurt door.

Drie antwoorden zijn een vaste zin van Core in plaats van die van de operatie, omdat er niets anders te melden valt:

Wat er gebeurde Wat je te horen krijgt
de operatie antwoordt iets anders dan JSON That operation answers with a … body, which a tool result cannot carry.
de operatie liep stuk tijdens het antwoorden That operation failed while answering. The failure is in Core's own log.
een gestreamde beurt stopte voordat hij klaar was The turn's stream ended without a result. It was cut short rather than completed…

Een verzoek dat geen geldig MCP-bericht is, wordt beantwoord voordat er een tool draait, als een JSON-RPC-fout met id: null: -32700 met 400 voor een body die geen JSON is, -32600 met 400 voor een body die niet één JSON-RPC 2.0-bericht is of waarvan het id geen string en geen geheel getal is, en -32600 met 413 voor een body groter dan 1 MiB.

Roept een client create_response aan met stream: true en stuurt hij een progress-token mee, dan komt elk stukje tekst van het model tijdens de beurt binnen als een voortgangsmelding, en is het afgeronde antwoordobject het resultaat — hetzelfde object dat de niet-gestreamde aanroep geeft. Zonder progress-token is het resultaat gelijk; er wordt onderweg alleen niets teruggestuurd.

Verbruiks- en auditgegevens schrijft de operatie zelf, op naam van dezelfde sleutel of dezelfde persoon als bij de gelijkwaardige HTTP-aanroep. Er is geen aparte MCP-registratie om te lezen: je vindt een aanroep terug in Gebruik en audit via de API en in het auditspoor van de console, waar je elke andere aanroep vindt.

Alles wat een tool antwoordt is gegeven, geen instructie. Een resultaat is de eigen inhoud van je organisatie en een weigering herhaalt de argumenten van het model; Core merkt beide aan als onbetrouwbaar, net als elk ander toolresultaat.