Skip to main content

MCP Endpoint

POST /api/v1/mcp speaks the Model Context Protocol over its Streamable HTTP transport. An MCP client that supports remote servers can point at it directly and get the whole platform as tools — prompts, the tool catalog, the gateway, traces and evaluations — without installing anything.

All examples below were verified with real curl output against a running server.

Authentication is the same bearer key as everywhere else

Send Authorization: Bearer acx_sk_…. Create the key in the dashboard under Settings → API keys. A session cookie is deliberately not accepted here — see Authentication.

Transport rules​

  • POST only. GET and DELETE return 405. The endpoint is stateless: every request builds its own server, so there is no session to delete and no server-initiated stream to open.
  • Send Accept: application/json, text/event-stream — the transport requires it.
  • Send MCP-Protocol-Version: <version> on every request after initialize.
  • Responses come back as plain JSON, not SSE frames.

Because it is stateless there is no Mcp-Session-Id: any instance behind the load balancer can answer any request, and a deploy never breaks a client's connection.


initialize​

curl -X POST $ACRUXCORE_BASE_URL/mcp \
-H "Authorization: Bearer $ACRUXCORE_API_KEY" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"curl","version":"1.0"}}}'

Response (status 200) — instructions explains the prompt/version/alias model to the client's model, so it does not have to infer it from the tool names:

{
"result": {
"protocolVersion": "2025-06-18",
"capabilities": {
"tools": { "listChanged": true },
"prompts": { "listChanged": false },
"resources": { "listChanged": true }
},
"serverInfo": { "name": "acruxcore", "title": "AcruxCore", "version": "0.1.0" },
"instructions": "AcruxCore is an LLM-ops platform: a prompt registry, a versioned tool catalog, an AI gateway, tracing, and evaluation.\n\nThree ideas explain almost every tool here:\n- A **prompt** is a named container. It holds no messages itself.\n- A **version** is an immutable commit of that prompt's messages. Version numbers are sequential.\n- An **alias** (\"production\", \"staging\", anything else) is a moving pointer at one version.\n\nCommitting a version never moves an alias — promote_prompt_alias is what ships a change, and promoting an earlier version is how a rollback happens.\n\nRegistry prompts are also exposed through MCP's prompts primitive, so they can be used directly as templates. Tools that take a prompt or a catalog tool accept a name or a UUID."
},
"jsonrpc": "2.0",
"id": 1
}

tools/list​

curl -X POST $ACRUXCORE_BASE_URL/mcp \
-H "Authorization: Bearer $ACRUXCORE_API_KEY" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-H "MCP-Protocol-Version: 2025-06-18" \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}'

Response (status 200) — 32 tools; names only, each entry also carries a description, a JSON Schema for its arguments, and a readOnlyHint annotation:

["list_prompts", "get_prompt", "render_prompt", "diff_prompt_versions", "create_prompt",
"commit_prompt_version", "promote_prompt_alias", "run_prompt", "chat_completion",
"list_models", "get_gateway_usage", "get_spending_limits", "list_catalog_tools",
"get_catalog_tool", "sync_catalog_tool", "execute_catalog_tool", "search_traces",
"get_trace", "get_trace_analytics", "list_sessions", "get_session", "submit_trace_feedback",
"list_datasets", "get_dataset", "create_dataset", "add_dataset_example",
"run_experiment", "list_experiment_runs", "get_experiment_run", "optimize_prompt",
"get_optimize_report", "promote_optimize_candidate"]

tools/call — reading​

Every result is one text block: a summary line, then the JSON payload.

curl -X POST $ACRUXCORE_BASE_URL/mcp \
-H "Authorization: Bearer $ACRUXCORE_API_KEY" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-H "MCP-Protocol-Version: 2025-06-18" \
-d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"list_prompts","arguments":{"limit":2}}}'

Response (status 200):

{
"result": {
"content": [
{
"type": "text",
"text": "Showing 2 of 23 prompts (page 1).\n\n{\n \"data\": [\n {\n \"id\": \"5e9d7286-0113-46bb-b211-429c0eb9309f\",\n \"name\": \"del-demo-prompt\",\n \"description\": null,\n \"createdAt\": \"2026-09-06T12:23:53.397Z\"\n },\n {\n \"id\": \"a290cf2f-a724-4389-8bea-da8b53229e00\",\n \"name\": \"support-reply\",\n \"description\": \"Draft the reply a support agent sends to an inbound customer message.\",\n \"createdAt\": \"2026-09-06T11:10:02.034Z\"\n }\n ],\n \"total\": 23,\n \"page\": 1,\n \"limit\": 2\n}"
}
]
},
"jsonrpc": "2.0",
"id": 3
}

tools/call — budgets and virtual-key limits​

get_spending_limits takes no arguments. It lists every budget with its live spend, what is left and when it resets, and names the virtual key a budget belongs to. Use it after the gateway refuses a call with 402 (a budget is spent) or 429 (a key's rate limit). Keys come back masked: the last four characters only, never the key itself. The tool reads only; budgets and keys are changed in the dashboard or through the budgets and virtual keys endpoints.

curl -X POST $ACRUXCORE_BASE_URL/mcp \
-H "Authorization: Bearer $ACRUXCORE_API_KEY" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-H "MCP-Protocol-Version: 2025-06-18" \
-d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"get_spending_limits","arguments":{}}}'

Response (status 200):

{
"result": {
"content": [
{
"type": "text",
"text": "2 budget(s), 1 virtual key(s). Key \"ci-bot\" day: $0 of $2 spent, resets 2026-10-22T00:00:00.000Z; Team month: $0 of $25 spent, resets 2026-11-01T00:00:00.000Z.\n\n{\n \"budgets\": [\n {\n \"id\": \"e5d1547c-fafa-4f42-8635-916b0e1181b5\",\n \"scope\": \"virtual_key\",\n \"virtualKeyId\": \"280648fa-e802-4810-bef6-b690cf872817\",\n \"virtualKeyName\": \"ci-bot\",\n \"period\": \"day\",\n \"limitUsd\": 2,\n \"spendUsd\": 0,\n \"remainingUsd\": 2,\n \"exhausted\": false,\n \"resetsAt\": \"2026-10-22T00:00:00.000Z\"\n },\n {\n \"id\": \"de03b02b-cea3-4727-84a7-cd2b20600a94\",\n \"scope\": \"team\",\n \"virtualKeyId\": null,\n \"virtualKeyName\": null,\n \"period\": \"month\",\n \"limitUsd\": 25,\n \"spendUsd\": 0,\n \"remainingUsd\": 25,\n \"exhausted\": false,\n \"resetsAt\": \"2026-11-01T00:00:00.000Z\"\n }\n ],\n \"virtualKeys\": [\n {\n \"id\": \"280648fa-e802-4810-bef6-b690cf872817\",\n \"name\": \"ci-bot\",\n \"keyLastFour\": \"3m30\",\n \"allowedModels\": [\n \"gpt-4o-mini\"\n ],\n \"allowedProviders\": null,\n \"maxRpm\": 60,\n \"maxTpm\": null,\n \"cacheTtlSeconds\": null,\n \"createdAt\": \"2026-10-21T16:49:43.279Z\",\n \"revokedAt\": null\n }\n ]\n}"
}
]
},
"jsonrpc": "2.0",
"id": 3
}

tools/call — running a stored prompt​

run_prompt renders a prompt at an alias, attaches the tools bound to that alias, calls the provider, prices the call and records a trace. prompt takes the name or the UUID.

curl -X POST $ACRUXCORE_BASE_URL/mcp \
-H "Authorization: Bearer $ACRUXCORE_API_KEY" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-H "MCP-Protocol-Version: 2025-06-18" \
-d '{"jsonrpc":"2.0","id":6,"method":"tools/call","params":{"name":"run_prompt","arguments":{"prompt":"support-reply","variables":{"message":"My order has not arrived."},"model":"gpt-4o-mini","max_tokens":80,"trace_name":"mcp-doc-verification"}}}'

Response (status 200) — the text block, unescaped for readability:

Ran "support-reply"@production on gpt-4o-mini-2024-07-18 for $0.000042. Reply: You're
experiencing an issue with your order not arriving. …

{
"completion": {
"id": "chatcmpl-ELB3Pl43lsZi6sDATLeWsOsN7SvUY",
"model": "gpt-4o-mini-2024-07-18",
"object": "chat.completion",
"created": 1788716087,
"choices": [ { "index": 0, "message": { "role": "assistant", "content": "…" }, "finish_reason": "stop" } ],
"usage": { "prompt_tokens": 92, "completion_tokens": 47, "total_tokens": 139 }
},
"gateway": {
"request-id": "d619b108-4a8b-4d93-adb6-05c1c9a14361",
"provider": "openai",
"model": "gpt-4o-mini-2024-07-18",
"cost-usd": "0.000042",
"cache": "miss",
"trace-id": "578724bc-7521-423b-a90d-0f13c9dd1ac0",
"span-id": "93e8d920-2867-4b71-8ebb-4dafc661f30f"
}
}

The gateway block is the x-gateway-* response headers. trace-id is the point of it: feed it straight back into get_trace.

curl -X POST $ACRUXCORE_BASE_URL/mcp \
-H "Authorization: Bearer $ACRUXCORE_API_KEY" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-H "MCP-Protocol-Version: 2025-06-18" \
-d '{"jsonrpc":"2.0","id":7,"method":"tools/call","params":{"name":"get_trace","arguments":{"trace_id":"578724bc-7521-423b-a90d-0f13c9dd1ac0","include_payloads":false}}}'

Response (status 200), text block unescaped:

Trace "mcp-doc-verification" — status ok, 1 span(s), 139 tokens, $0.000042.

{
"trace": {
"id": "578724bc-7521-423b-a90d-0f13c9dd1ac0",
"name": "mcp-doc-verification",
"sessionId": null,
"status": "ok",
"startedAt": "2026-09-06T17:34:47.062Z",
"endedAt": "2026-09-06T17:34:48.539Z",
"spanCount": 1,
"totalCostUsd": 0.000042,
"totalTokens": 139,
"tags": [],
"metadata": {}
},
"spans": [ … ]
}

tools/call — running an experiment​

run_experiment creates an experiment over a dataset and starts its run. The run tests every pair of a prompt version in version_ids and a model in models. judge_model is required, and it must be a model registered in the gateway. judge_prompt is optional. It takes the name or UUID of one of your prompts, and that prompt grades every answer instead of the built-in judge. The call returns as soon as the run is queued.

curl -X POST $ACRUXCORE_BASE_URL/mcp \
-H "Authorization: Bearer $ACRUXCORE_API_KEY" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-H "MCP-Protocol-Version: 2025-06-18" \
-d '{"jsonrpc":"2.0","id":8,"method":"tools/call","params":{"name":"run_experiment","arguments":{"dataset_id":"3f04989b-adff-40eb-b86a-d9b5ffc64153","version_ids":["d6f7cca3-b855-4445-8983-269cf7859fdf"],"models":["gpt-4o-mini-or"],"judge_model":"gpt-4o-mini-or","judge_prompt":"ticket-classifier judge","name":"classifier, rubric judge"}}}'

Response (status 200) — the text block, unescaped and shortened:

Experiment 1974b5db-2b84-4545-a413-848b0555e72f created and run 83e9d642-7fee-44ce-b60d-4a7dfd3567f6 is queued. Results arrive asynchronously — poll get_experiment_run.

{
"experiment": {
"id": "1974b5db-2b84-4545-a413-848b0555e72f",
"name": "classifier, rubric judge",
"config": {
"models": ["gpt-4o-mini-or"],
"judgeModel": "gpt-4o-mini-or",
"versionIds": ["d6f7cca3-b855-4445-8983-269cf7859fdf"],
"judgePromptId": "821f5b28-6c7f-43b2-aa8a-841bad82e671"
}
},
"run": {
"run_id": "83e9d642-7fee-44ce-b60d-4a7dfd3567f6",
"status": "queued"
}
}

A judge_prompt name that matches no prompt in your team fails before the experiment is created. See Write a custom judge prompt for how to write one.


tools/call — improving a prompt with the optimizer​

Three tools drive the optimizer. optimize_prompt starts a run. In the run, an optimizer model writes rewrites of the prompt version that alias points at (default production). That version is the baseline. The judge model then scores each rewrite and the baseline on the dataset's examples. prompt takes the prompt's name or its UUID. judge_model is required. draft_count is the number of rewrites to write, 1 to 6 (default 3). judge_prompt is optional and works as in run_experiment. The optimizer also reads that judge prompt, so its rewrites aim at the same rubric. The call returns the run id as soon as the run is queued.

curl -X POST $ACRUXCORE_BASE_URL/mcp \
-H "Authorization: Bearer $ACRUXCORE_API_KEY" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-H "MCP-Protocol-Version: 2025-06-18" \
-d '{"jsonrpc":"2.0","id":6,"method":"tools/call","params":{"name":"optimize_prompt","arguments":{"prompt":"mcp-optimizer-demo","dataset_id":"9e756ad4-f39b-4b64-98da-03a4cca02354","models":["gpt-4o-mini-or"],"judge_model":"gpt-4o-mini-or","draft_count":2}}}'

Response (status 200) — the text block, unescaped for readability:

Optimize run 4d179b65-f0ef-4b35-a267-ac3a986f07b0 is queued; gpt-4o-mini-or drafts the rewrites, then they are scored against the production baseline. Poll get_optimize_report with this run id.

{
"run_id": "4d179b65-f0ef-4b35-a267-ac3a986f07b0",
"status": "queued",
"optimizer_model": "gpt-4o-mini-or"
}

get_optimize_report takes that run id. While the run is queued or running, the tool returns only the status, so call it again later. Once the run has succeeded, the report lists each rewrite with its candidateId, the optimizer's rationale, the rewritten messages, and its score next to the baseline's score on each model. beatsBaseline is true when a rewrite improved on at least one model and was improved or flat on every other. A model with no scored answers counts against the rewrite. recommended is the candidateId with the largest gain among those rewrites, or null when no rewrite has beatsBaseline true.

curl -X POST $ACRUXCORE_BASE_URL/mcp \
-H "Authorization: Bearer $ACRUXCORE_API_KEY" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-H "MCP-Protocol-Version: 2025-06-18" \
-d '{"jsonrpc":"2.0","id":7,"method":"tools/call","params":{"name":"get_optimize_report","arguments":{"run_id":"4d179b65-f0ef-4b35-a267-ac3a986f07b0"}}}'

Response (status 200) — the text block, unescaped:

Optimize run 4d179b65-f0ef-4b35-a267-ac3a986f07b0 succeeded. 2 of 2 rewrite(s) beat the baseline. Recommended: candidate-A (candidate_id 65608412-a11f-4c1b-bb89-c58c2c766853), +90.0 on gpt-4o-mini-or. Promote it with promote_optimize_candidate only if the user agrees.

{
"runId": "4d179b65-f0ef-4b35-a267-ac3a986f07b0",
"status": "succeeded",
"judgeModel": "gpt-4o-mini-or",
"judgePromptVersionId": null,
"recommended": "65608412-a11f-4c1b-bb89-c58c2c766853",
"baseline": [
{
"label": "production",
"model": "gpt-4o-mini-or",
"avgScore": 6.666666666666667,
"passRate": 0
}
],
"candidates": [
{
"candidateId": "65608412-a11f-4c1b-bb89-c58c2c766853",
"label": "candidate-A",
"scores": [
{
"model": "gpt-4o-mini-or",
"avgScore": 96.66666666666667,
"passRate": 1,
"baselineAvgScore": 6.666666666666667,
"scoreDelta": 90,
"verdict": "improved"
}
],
"beatsBaseline": true,
"rationale": "The system message was rewritten to explicitly instruct the assistant to provide an explanation suitable for a child, avoiding technical terms and providing an example, which aligns with the criteria for all failing cases.",
"messages": [
{
"role": "system",
"content": "Explain the following concept in simple terms appropriate for a child. Use at most two short sentences with one everyday example and avoid technical terms."
},
{
"role": "user",
"content": "Define {{ word }}."
}
]
},
{
"candidateId": "5f867403-4e42-48f1-a51e-e80025822817",
"label": "candidate-B",
"scores": [
{
"model": "gpt-4o-mini-or",
"avgScore": 96.66666666666667,
"passRate": 1,
"baselineAvgScore": 6.666666666666667,
"scoreDelta": 90,
"verdict": "improved"
}
],
"beatsBaseline": true,
"rationale": "This version of the system message emphasizes the need for a clear, simplified explanation with an example, directly addressing the criteria for the failing cases.",
"messages": [
{
"role": "system",
"content": "Provide a child-friendly explanation of the given word. Use no more than two short sentences, incorporate one everyday example, and avoid any technical jargon."
},
{
"role": "user",
"content": "Define {{ word }}."
}
]
}
]
}

promote_optimize_candidate saves one rewrite as a new prompt version and points alias at that new version. alias defaults to the alias the run was scored against. The new version keeps the baseline version's default model. The tool refuses a rewrite whose beatsBaseline is false. It also refuses when alias now points at a different version than the baseline, because no score compared the rewrite with that version. Setting promote_without_improvement: true overrides both refusals. The tool also refuses while the run is still queued or running, with no override. Promoting needs the editor role or higher, and the audit log records the new version and the alias move, as it does for a promote in the dashboard.

curl -X POST $ACRUXCORE_BASE_URL/mcp \
-H "Authorization: Bearer $ACRUXCORE_API_KEY" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-H "MCP-Protocol-Version: 2025-06-18" \
-d '{"jsonrpc":"2.0","id":8,"method":"tools/call","params":{"name":"promote_optimize_candidate","arguments":{"run_id":"4d179b65-f0ef-4b35-a267-ac3a986f07b0","candidate_id":"65608412-a11f-4c1b-bb89-c58c2c766853"}}}'

Response (status 200) — the text block, unescaped:

Promoted candidate-A as version 2; alias "production" now points at it.

{
"version": {
"id": "27ef48ea-a611-47ba-a709-75e98124a160",
"promptId": "c6b97f52-27c0-41c1-a53c-a157021fac9a",
"versionNumber": 2,
"messages": [
{
"role": "system",
"content": "Explain the following concept in simple terms appropriate for a child. Use at most two short sentences with one everyday example and avoid technical terms."
},
{
"role": "user",
"content": "Define {{ word }}."
}
],
"variables": [
"word"
],
"createdBy": "18c76b52-ee0d-4001-be8e-c29976488fbb",
"createdAt": "2026-10-22T07:45:08.100Z",
"model": null
},
"alias": {
"id": "78d6bafd-4772-40a9-b3d9-54853415b569",
"alias": "production",
"versionId": "27ef48ea-a611-47ba-a709-75e98124a160",
"versionNumber": 2,
"updatedAt": "2026-10-22T07:45:08.114Z"
}
}

If alias has moved since the run, the promote is refused. In this call, production was moved from version 3, the run's baseline, to version 1 before the promote:

curl -X POST $ACRUXCORE_BASE_URL/mcp \
-H "Authorization: Bearer $ACRUXCORE_API_KEY" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-H "MCP-Protocol-Version: 2025-06-18" \
-d '{"jsonrpc":"2.0","id":4,"method":"tools/call","params":{"name":"promote_optimize_candidate","arguments":{"run_id":"4a20afef-4b43-49f8-8b68-c006fb7a29b1","candidate_id":"a622c9b7-f04e-4e4d-b5d5-790cd5bff376"}}}'

Response (status 200, with isError: true) — the text block:

Error: Refused: alias "production" now points at version 1, but run 4a20afef-4b43-49f8-8b68-c006fb7a29b1 scored its rewrites against a different version. Nothing compared candidate-B with version 1. Start a new optimize run against this alias, or set promote_without_improvement to true only if the user explicitly asked to replace it anyway.

When six long rewrites do not fit in one result, get_optimize_report leaves out every rewrite's messages and sets messagesOmitted: true. Every candidateId and score stays in the result. Pass candidate_id with run_id to read one rewrite with its full messages.

judgePromptVersionId is null when the built-in judge graded the run. When the run used a judge_prompt, it is the id of the judge prompt version that graded it. This run used the ticket-classifier judge prompt, whose rubric scores 0 for any reply that is not one JSON object:

curl -X POST $ACRUXCORE_BASE_URL/mcp \
-H "Authorization: Bearer $ACRUXCORE_API_KEY" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-H "MCP-Protocol-Version: 2025-06-18" \
-d '{"jsonrpc":"2.0","id":9,"method":"tools/call","params":{"name":"optimize_prompt","arguments":{"prompt":"ticket-classifier","dataset_id":"3f04989b-adff-40eb-b86a-d9b5ffc64153","models":["gpt-4o-mini-or"],"optimizer_model":"gpt-5.4-mini-or","judge_model":"gpt-4o-mini-or","judge_prompt":"ticket-classifier judge","draft_count":2}}}'

get_optimize_report for that run, shortened:

Optimize run c5622383-ba91-4975-ad3c-4075349b285c succeeded. 1 of 1 rewrite(s) beat the baseline. Recommended: candidate-A (candidate_id 32e3f29b-3678-4fad-966f-d1c88c102c80), +100.0 on gpt-4o-mini-or. Promote it with promote_optimize_candidate only if the user agrees.

{
"runId": "c5622383-ba91-4975-ad3c-4075349b285c",
"status": "succeeded",
"judgeModel": "gpt-4o-mini-or",
"judgePromptVersionId": "aa5c99fb-f152-4d05-a901-5f612c4cde0c",
"recommended": "32e3f29b-3678-4fad-966f-d1c88c102c80",
"baseline": [
{ "label": "production", "model": "gpt-4o-mini-or", "avgScore": 0, "passRate": 0 }
]
}

The rewrite's rationale says it was changed to "require a single JSON object", which "better aligns the template with the judge rubric".

Read-only mode (--read-only on the local server) removes optimize_prompt and promote_optimize_candidate. get_optimize_report stays.


prompts/list — the registry as MCP prompts​

Every prompt in the team's registry is also an MCP prompt, resolved at the production alias, with the version's template variables as arguments.

curl -X POST $ACRUXCORE_BASE_URL/mcp \
-H "Authorization: Bearer $ACRUXCORE_API_KEY" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-H "MCP-Protocol-Version: 2025-06-18" \
-d '{"jsonrpc":"2.0","id":4,"method":"prompts/list","params":{}}'

Response (status 200), first two entries:

{
"result": {
"prompts": [
{
"name": "del-demo-prompt",
"title": "del-demo-prompt",
"description": "AcruxCore registry prompt (alias \"production\", version 1)",
"arguments": [
{ "name": "name", "description": "Template variable \"name\".", "required": true }
]
},
{
"name": "support-reply",
"title": "support-reply",
"description": "Draft the reply a support agent sends to an inbound customer message. (alias \"production\", version 2)",
"arguments": [
{ "name": "message", "description": "Template variable \"message\".", "required": true }
]
}
]
},
"jsonrpc": "2.0",
"id": 4
}

prompts/get renders one, and returns the messages. MCP's message role is user or assistant only — there is no system role in the protocol — so a template's system message comes back as the leading user message with its text unchanged.

A registry of more than 25 prompts is paged: the result carries nextCursor, which goes back as params.cursor.


Resources​

Two resource templates are registered:

URI templateContent
acruxcore://prompts/{name}/{alias}The raw, unrendered template of the version an alias points at, with its variables and bound default model.
acruxcore://traces/{traceId}One trace with its full span tree.

resources/list enumerates one entry per registry prompt at the default alias. Traces are deliberately not enumerated — a team accumulates them without limit — so find one with search_traces and address it by id.


Error responses​

No Authorization header (status 401):

{ "error": { "code": "UNAUTHORIZED", "message": "API key required." } }

GET or DELETE (status 405):

{
"jsonrpc": "2.0",
"error": { "code": -32000, "message": "Method not allowed. This MCP endpoint is stateless — use POST." },
"id": null
}

A tool that fails comes back as a successful JSON-RPC response carrying isError: true, which is how MCP reports tool failures — the calling model sees the error and can act on it. The AcruxCore error code and message are passed through intact:

{
"result": {
"content": [
{
"type": "text",
"text": "AcruxCore API error (VALIDATION_ERROR)\nRequest: POST /gateway/chat/completions\nStatus: 400\nMessage: model is required"
}
],
"isError": true
},
"jsonrpc": "2.0",
"id": 5
}

Local alternative​

Clients that only speak stdio can run the same server locally:

npx -y @acruxcoreai/mcp-server

with ACRUXCORE_API_KEY and ACRUXCORE_BASE_URL in the environment. The stdio package adds two options the hosted endpoint has no way to express: --toolsets to narrow the surface, and --read-only to drop every mutating tool from the listing. See Connect an MCP client.