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:
- 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 een401die naar datzelfde document wijst. - De client registreert zichzelf bij die server, die registratie van iedereen aanneemt en zelf bepaalt wat de client mag zijn.
- 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.
- 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_writevan 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 metno_model_configured. - Een API-koppeling krijgt het id van het verzoek waar een beurt het id
van de response meestuurt, in de header
Idempotency-Keyen in het veldresponse_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 eenpermitvan de soorttooldie ze matcht geeft ze. Lees de huidige regels metGET /key. - Een
core__-tool draait één niveau diep. De geneste beurt die eencore__create_responsestart, krijgt zelf geen enkelecore__-tool aangeboden, en zo een daar noemen levert400optools:"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 ombackground: trueen 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_responseaanroept, start een beurt één niveau dieper. Core telt die niveaus, langs welke weg de aanroep ook ging: een agent als tool, eencore__-tool of een registratie op het eigen adres van Core. Een beurt op het vijfde niveau wordt elke zulke aanroep geweigerd. Het model leestThis turn is too deep in a chain of calls to start another.als mislukt toolresultaat (call_depth_exceededin 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 metcall_depth_exceededzonder dat de aanroep wordt gedaan. Je operator stelt het getal in metCORE_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_inviteencore__remove_organisation_admin— antwoordt503in 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.