Docs Guides
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.
The request
POST https://app.soontra.com/api/mcp. Other methods get405.Authorization: Bearer <your key>Content-Type: application/jsonAccept: application/json, text/event-stream. The transport lets a server answer either way, so a client must accept both.
List the tools
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'npm install @modelcontextprotocol/sdkimport { 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))pip install mcpimport 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 -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'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)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.
{
"jsonrpc": "2.0",
"id": 2,
"result": {
"content": [
{
"type": "text",
"text": "{\"projects\":[...],\"count\":5,\"_notice\":\"...\"}"
}
]
}
}- If
result.isErroris true, the text holds{"error": "..."}instead. See Errors. - Soontra answers as an event stream (
text/event-stream) with onemessageevent: the JSON-RPC message is itsdata:line, which is what thesedin the curl examples prints. The SDKs read it for you. - Ignore
_noticein your own code. It is there for language models.
Good habits
- Call
soontra_workspace_getContextonce at the start to confirm which workspace and scopes the key has. - Send an
idempotency_keywith every write, so a retry after a timeout cannot write twice. - On a rate-limit error, wait the
retry_after_secondsit gives. See 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_changeswith thenext_cursorfrom the last call. There are no webhooks.