# Call it from code

Use the endpoint from a script: plain HTTP, or an MCP SDK in TypeScript or Python.

The endpoint is ordinary HTTPS. It speaks JSON-RPC 2.0 as MCP defines it, over the Streamable HTTP transport, and keeps no session: every `POST` stands on its own and carries your API key. You can use an MCP SDK, or send the requests yourself.

> **Note:** Create an API key under **Settings**, **API & MCP** in Soontra first, with the scopes your script needs. See [Authentication](https://soontra.com/docs/authentication).

## The request

- `POST https://app.soontra.com/api/mcp`. Other methods get `405`.
- `Authorization: Bearer <your key>`
- `Content-Type: application/json`
- `Accept: application/json, text/event-stream`. The transport lets a server answer either way, so a client must accept both.

## List the tools

**curl**

```bash
curl -s https://app.soontra.com/api/mcp \
  -H "Authorization: Bearer $SOONTRA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' \
  | sed -n 's/^data: //p'
```

**TypeScript**

```bash
npm install @modelcontextprotocol/sdk
```

```ts
import { Client } from '@modelcontextprotocol/sdk/client/index.js'
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'

const transport = new StreamableHTTPClientTransport(new URL('https://app.soontra.com/api/mcp'), {
  requestInit: {
    headers: { Authorization: `Bearer ${process.env.SOONTRA_API_KEY}` },
  },
})

const client = new Client({ name: 'my-script', version: '1.0.0' })
await client.connect(transport)

const { tools } = await client.listTools()
console.log(tools.map((tool) => tool.name))
```

**Python**

```bash
pip install mcp
```

```python
import asyncio
import os

import httpx2
from mcp import Client
from mcp.client.streamable_http import streamable_http_client

URL = "https://app.soontra.com/api/mcp"


async def main():
    headers = {"Authorization": f"Bearer {os.environ['SOONTRA_API_KEY']}"}
    async with httpx2.AsyncClient(headers=headers, timeout=30) as http:
        async with Client(streamable_http_client(URL, http_client=http)) as client:
            tools = await client.list_tools()
            print([tool.name for tool in tools.tools])


asyncio.run(main())
```

## Call a tool

**curl**

```bash
curl -s https://app.soontra.com/api/mcp \
  -H "Authorization: Bearer $SOONTRA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{
    "jsonrpc": "2.0",
    "id": 2,
    "method": "tools/call",
    "params": {
      "name": "soontra_projects_list",
      "arguments": { "limit": 5 }
    }
  }' \
  | sed -n 's/^data: //p'
```

**TypeScript**

```ts
const result = await client.callTool({
  name: 'soontra_projects_list',
  arguments: { limit: 5 },
})

if (result.isError) throw new Error(result.content[0].text)
const { projects } = JSON.parse(result.content[0].text)
```

**Python**

```python
import json

result = await client.call_tool("soontra_projects_list", {"limit": 5})
if result.is_error:
    raise RuntimeError(result.content[0].text)
projects = json.loads(result.content[0].text)["projects"]
```

## Reading the answer

A tool’s result is an MCP result whose first content item is text holding JSON. Parse that text to get the data.

Response:

```json
{
  "jsonrpc": "2.0",
  "id": 2,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "{\"projects\":[...],\"count\":5,\"_notice\":\"...\"}"
      }
    ]
  }
}
```

- If `result.isError` is true, the text holds `{"error": "..."}` instead. See [Errors](https://soontra.com/docs/errors).
- Soontra answers as an event stream (`text/event-stream`) with one `message` event: the JSON-RPC message is its `data:` line, which is what the `sed` in the curl examples prints. The SDKs read it for you.
- Ignore `_notice` in your own code. It is there for language models.

> **Note:** The examples on this page were run against the live endpoint on November 10, 2026, with version 1.32 of the TypeScript SDK and version 2.3 of the Python SDK. Earlier Python SDK versions name the client differently.

## Good habits

- Call `soontra_workspace_getContext` once at the start to confirm which workspace and scopes the key has.
- Send an `idempotency_key` with every write, so a retry after a timeout cannot write twice.
- On a rate-limit error, wait the `retry_after_seconds` it gives. See [Limits](https://soontra.com/docs/limits).
- Keep the key in an environment variable or a secret store, never in the script.
- To follow what changes in a workspace, poll `soontra_workspace_changes` with the `next_cursor` from the last call. There are no webhooks.
