# tools/call (/docs/reference/mcp/tools-call)

`POST https://api.corespeed.io/mcp` — JSON-RPC method `tools/call`

Billing holds and API-key spend caps are checked before a metered tool
executes. Those refusals arrive as tool-level errors inside HTTP `200`, not as
HTTP failures — see [Errors](/docs/reference/errors). A suspended organization
is refused earlier, with HTTP `403` `org_suspended`, before any tool runs.
Where Smart Approvals is on, a write that needs a person's approval returns an
approval receipt instead of running; call `manage__approval_wait` with its
`approval_id`.

#### Authorizations

* `Authorization` (string, header, required) — `Bearer <token>` — an MCP OAuth token, an `sk-cs-…` API key or `sk-csa-…` agent key, or a user session JWT. `x-api-key: <sk-cs-…>` is accepted in its place.
* `Accept` (string, header, required) — `application/json, text/event-stream` — the Streamable HTTP transport requires both media types. This 2025-era request is answered as one `text/event-stream` event; its `data:` line is the JSON-RPC response.
* `Content-Type` (string, header, required) — `application/json`. The transport rejects anything else with `415`.

#### Request

* `params.name` (string, required) — A name returned by `tools/list`, e.g. `memory__remember`. A name that is unknown or not on this caller's surface is **not** an HTTP error: it comes back as HTTP `200` with a JSON-RPC `error` — code `-32602`, message `Tool <name> not found` — and no `result`. Nothing about a misspelled tool name shows up in the HTTP status.
* `params.arguments` (object, optional) — Arguments matching that tool's `inputSchema`. Optional in the MCP request schema — for a tool that takes none, omit it or send `{}` — but required in practice for every tool whose `inputSchema` has required properties. A schema violation is a tool-level error, not a transport error: HTTP `200` with `result.isError: true` and the text `Input validation error: Invalid arguments for tool <name>: …`.

#### Response fields

* `result.content` (array, required) — Content blocks. Text tools return `{ "type": "text", "text": "…" }`; media tools return signed artifact URLs.
* `result.isError` (boolean, optional) — `true` when the tool refused or failed. The HTTP status is still `200`, so branch on this field before treating a call as successful.
* `result.structuredContent.error` (object, optional) — On a failed call, the machine-readable half: `code` — the value to branch on — plus only publishable facts (a provider's `status`, `request_id`, `retry_after_seconds`, the `aliases` to choose from, the `allowed` tools of a share). The text block is for the agent to read. Hold refusals carry their envelope as JSON in the text instead, and an approval receipt or refusal carries its facts in `_meta`. Every code is in [Errors & status codes](/docs/reference/errors#mcp-tool-errors).
* `result._meta` (object, optional) — CoreSpeed facts about the call: `io.corespeed/hold` on a hold refusal, and `io.corespeed/approval` on any result shaped by Smart Approvals.

The `manage__*` tools answer an API key as its principal, with the
permissions the key was minted with — an agent key holds none. Two carry a
rule of their own: `manage__keys_create` and `manage__agents_key_create`
refuse every API key (`api_key_cannot_mint` — a key does not mint keys),
and `manage__agents_create` and the `environments_*` writes refuse an agent
principal (`member_principal_required`). The remote-connector lifecycle
writes need `connections:org`, and `manage__approval_wait` answers only for
the caller's own approval requests.

**curl**

```bash
curl https://api.corespeed.io/mcp \
  -H "Authorization: Bearer $CORESPEED_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{
    "jsonrpc": "2.0",
    "id": 3,
    "method": "tools/call",
    "params": {
      "name": "memory__remember",
      "arguments": {
        "memory": "Rollout changes require a two-day review window.",
        "scope": "shared"
      }
    }
  }'
```

**TypeScript**

```ts
const res = await fetch("https://api.corespeed.io/mcp", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.CORESPEED_API_KEY}`,
    "Content-Type": "application/json",
    Accept: "application/json, text/event-stream",
  },
  body: JSON.stringify({
    jsonrpc: "2.0",
    id: 3,
    method: "tools/call",
    params: {
      name: "memory__remember",
      arguments: {
        memory: "Rollout changes require a two-day review window.",
        scope: "shared",
      },
    },
  }),
});

// One SSE event: the JSON-RPC response is its data line.
const data = (await res.text()).split("\n").filter((line) => line.startsWith("data: ")).at(-1)!;
const { result, error } = JSON.parse(data.slice("data: ".length));
if (error) throw new Error(error.message);
if (result.isError) throw new Error(result.content[0].text);
```

**Python**

```python
import json
import os
import httpx

res = httpx.post(
    "https://api.corespeed.io/mcp",
    headers={
        "Authorization": f"Bearer {os.environ['CORESPEED_API_KEY']}",
        "Content-Type": "application/json",
        "Accept": "application/json, text/event-stream",
    },
    json={
        "jsonrpc": "2.0",
        "id": 3,
        "method": "tools/call",
        "params": {
            "name": "memory__remember",
            "arguments": {
                "memory": "Rollout changes require a two-day review window.",
                "scope": "shared",
            },
        },
    },
)

# One SSE event: the JSON-RPC response is its data line.
data = [line for line in res.text.splitlines() if line.startswith("data: ")][-1]
message = json.loads(data[len("data: "):])
if "error" in message:
    raise RuntimeError(message["error"]["message"])
result = message["result"]
if result.get("isError"):
    raise RuntimeError(result["content"][0]["text"])
```

**Go**

```go
package main

import (
	"bytes"
	"encoding/json"
	"io"
	"net/http"
	"os"
	"strings"
)

func main() {
	body, _ := json.Marshal(map[string]any{
		"jsonrpc": "2.0",
		"id":      3,
		"method":  "tools/call",
		"params": map[string]any{
			"name": "memory__remember",
			"arguments": map[string]any{
				"memory": "Rollout changes require a two-day review window.",
				"scope":  "shared",
			},
		},
	})

	req, _ := http.NewRequest("POST", "https://api.corespeed.io/mcp", bytes.NewReader(body))
	req.Header.Set("Authorization", "Bearer "+os.Getenv("CORESPEED_API_KEY"))
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Accept", "application/json, text/event-stream")

	res, err := http.DefaultClient.Do(req)
	if err != nil {
		panic(err)
	}
	defer res.Body.Close()

	// One SSE event: the JSON-RPC response is its data line.
	raw, _ := io.ReadAll(res.Body)
	var data string
	for _, line := range strings.Split(string(raw), "\n") {
		if strings.HasPrefix(line, "data: ") {
			data = strings.TrimPrefix(line, "data: ")
		}
	}

	var out struct {
		Error *struct {
			Message string `json:"message"`
		} `json:"error"`
		Result struct {
			Content []struct {
				Text string `json:"text"`
			} `json:"content"`
			IsError bool `json:"isError"`
		} `json:"result"`
	}
	json.Unmarshal([]byte(data), &out)
	if out.Error != nil {
		panic(out.Error.Message)
	}
	if out.Result.IsError {
		panic(out.Result.Content[0].Text)
	}
}
```

**200 OK** — Tool succeeded

```json
{
  "jsonrpc": "2.0",
  "id": 3,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "Memory shared with the organization."
      }
    ],
    "structuredContent": {
      "id": "0b9a6f3e-…",
      "memory": "Rollout changes require a two-day review window.",
      "scope": "shared"
    }
  }
}
```

**200 isError** — Tool failed

```json
{
  "jsonrpc": "2.0",
  "id": 3,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "More than one Notion account is connected (work, personal); pass `account` with one of those aliases (search)."
      }
    ],
    "structuredContent": {
      "error": { "code": "ambiguous_account", "aliases": ["work", "personal"] }
    },
    "isError": true
  }
}
```

**200 isError** — Session required

```json
{
  "jsonrpc": "2.0",
  "id": 3,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "An API key cannot mint a key: a key's spend cap does not carry to the keys it would mint. Mint it from a signed-in CoreSpeed session."
      }
    ],
    "structuredContent": { "error": { "code": "api_key_cannot_mint" } },
    "isError": true
  }
}
```