Start with the manifest, not a guessed endpoint.
An MCP-capable agent should begin with a plain GET /mcp.json. The manifest is the machine-readable map: it identifies the server as STRALO, pins the MCP protocolVersion to 2025-03-26, gives the baseUrl, and points to the absolute Streamable-HTTP transport at /api/mcp. It also lists every tool's method, path, auth posture, and input schema before your agent sends a mutation.
The live document is available at /mcp.json. Keep /openapi.json beside it when you need the full REST schema and response contracts.
curl https://stralo.polsia.app/mcp.json
{
"server": {
"name": "STRALO",
"version": "1.0.0",
"protocolVersion": "2025-03-26",
"transport": {
"type": "streamable-http",
"url": "https://stralo.polsia.app/api/mcp"
},
"baseUrl": "https://stralo.polsia.app"
},
"tools": [
{
"name": "post_agents",
"method": "POST",
"path": "/api/agents",
"auth": {
"type": "conditional-bootstrap-or-bearer",
"acceptedHeaders": [
"Authorization",
"X-API-Key"
]
},
"inputSchema": {
"type": "object",
"description": "AgentCreate — name, bootstrap?: true, metadata?, config?, idempotency_key?"
}
},
{
"name": "post_bookings",
"method": "POST",
"path": "/api/bookings",
"auth": {
"type": "bearer",
"acceptedHeaders": [
"Authorization",
"X-API-Key"
]
},
"inputSchema": {
"type": "object",
"description": "BookingCreate — agentId, startsAt, endsAt"
}
},
{
"name": "get_bookings",
"method": "GET",
"path": "/api/bookings",
"auth": {
"type": "bearer",
"acceptedHeaders": [
"Authorization",
"X-API-Key"
]
},
"inputSchema": {
"type": "object",
"description": "query — limit?, from?, to?"
}
},
{
"name": "delete_bookings_id",
"method": "DELETE",
"path": "/api/bookings/{id}",
"auth": {
"type": "bearer",
"acceptedHeaders": [
"Authorization",
"X-API-Key"
]
},
"inputSchema": {
"type": "object",
"description": "path — id"
}
},
{
"name": "post_proposals",
"method": "POST",
"path": "/api/proposals",
"auth": {
"type": "bearer",
"acceptedHeaders": [
"Authorization",
"X-API-Key"
]
},
"inputSchema": {
"type": "object",
"description": "ProposalCreate — targetAgentId, startsAt, endsAt"
}
},
{
"name": "patch_proposals_id_accept",
"method": "PATCH",
"path": "/api/proposals/{id}/accept",
"auth": {
"type": "bearer",
"acceptedHeaders": [
"Authorization",
"X-API-Key"
]
},
"inputSchema": {
"type": "object",
"description": "path — id"
}
},
{
"name": "patch_proposals_id_reject",
"method": "PATCH",
"path": "/api/proposals/{id}/reject",
"auth": {
"type": "bearer",
"acceptedHeaders": [
"Authorization",
"X-API-Key"
]
},
"inputSchema": {
"type": "object",
"description": "path — id"
}
},
{
"name": "get_bookings_id_occurrences",
"method": "GET",
"path": "/api/bookings/{id}/occurrences",
"auth": {
"type": "bearer",
"acceptedHeaders": [
"Authorization",
"X-API-Key"
]
},
"inputSchema": {
"type": "object",
"description": "path — id; query — limit?"
}
}
]
}Eight tools, one predictable shape.
The tool list is deliberately close to the REST surface. A client can use the method and path columns for routing, the auth column for credential policy, and the input schema column to construct arguments without reverse-engineering a handler.
| Tool | HTTP | Path | Auth | inputSchema |
|---|---|---|---|---|
| post_agents | POST | /api/agents | bootstrap or X-API-Key/Bearer | AgentCreate — name, bootstrap?: true, metadata?, config?, idempotency_key? |
| post_bookings | POST | /api/bookings | X-API-Key or Bearer | BookingCreate — agentId, startsAt, endsAt |
| get_bookings | GET | /api/bookings | X-API-Key or Bearer | query — limit?, from?, to? |
| delete_bookings_id | DELETE | /api/bookings/{id} | X-API-Key or Bearer | path — id |
| post_proposals | POST | /api/proposals | X-API-Key or Bearer | ProposalCreate — targetAgentId, startsAt, endsAt |
| patch_proposals_id_accept | PATCH | /api/proposals/{id}/accept | X-API-Key or Bearer | path — id |
| patch_proposals_id_reject | PATCH | /api/proposals/{id}/reject | X-API-Key or Bearer | path — id |
| get_bookings_id_occurrences | GET | /api/bookings/{id}/occurrences | X-API-Key or Bearer | path — id; query — limit? |
Bootstrap the first agent, then issue child seats.
On a brand-new installation, POST /api/agents accepts bootstrap: true without credentials to claim the first agent. After that, use an existing agent credential to issue a child seat. The 201 response includes an id and a plaintext public_token. STRALO shows that token once and retains only its SHA-256 hash. Store it in the agent's secret manager before proceeding.
curl -X POST https://stralo.polsia.app/api/agents \
-H "Content-Type: application/json" \
-d '{
"name": "calendar-agent",
"bootstrap": true,
"metadata": { "team": "operations" },
"config": { "timezone": "UTC" }
}'{
"id": "ag_5b8e3a1c9c2b4e1c8f7d6a5b",
"public_token": "sk_7f1d…shown-once",
"created_at": "2026-08-28T09:00:00.000Z",
"next_step": {
"save_public_token": true,
"authorization": "Authorization: Bearer <public_token>",
"x_api_key": "X-API-Key: <public_token>"
}
}X-API-Key: <sk_…> or the retained fallback Authorization: Bearer <sk_…>. Send exactly one header, not both. The API reference contains the same choice for every protected route.Book with an offset-aware window.
POST /api/bookings takes the agentId plus startsAt and endsAtas RFC 3339 timestamps with offsets. The key resolves to an agent identity, and the route requires that resolved identity to match the body's agentId. This prevents one agent from scheduling on another agent's calendar. The request below uses Eastern time; the response is normalized to UTC by the server.
curl -X POST https://stralo.polsia.app/api/bookings \
-H "Content-Type: application/json" \
-H "X-API-Key: <sk_…>" \
-d '{
"agentId": "ag_5b8e3a1c9c2b4e1c8f7d6a5b",
"startsAt": "2026-09-03T14:00:00-04:00",
"endsAt": "2026-09-03T15:00:00-04:00"
}'{
"id": "bk_4f1a93de8c7240cda0f3b9e2",
"agentId": "ag_5b8e3a1c9c2b4e1c8f7d6a5b",
"startsAt": "2026-09-03T18:00:00.000Z",
"endsAt": "2026-09-03T19:00:00.000Z",
"status": "confirmed",
"rrule": null,
"warningSentAt": null,
"createdAt": "2026-08-26T12:00:00.000Z"
}409 slot_taken; the database exclusion constraint keeps the race safe even when two agents write at once.{
"error": "slot_taken",
"message": "Booking slot already taken for this agent."
}Propose a slot without pretending to own it.
POST /api/proposals uses the same credential channel, but the body names a targetAgentId and a time window. The server derives proposerAgentId from the key; it is never accepted from the body. A successful 201 creates a pending proposal. The target agent can then accept or reject it through the matching PATCH tool.
curl -X POST https://stralo.polsia.app/api/proposals \
-H "Content-Type: application/json" \
-H "X-API-Key: <sk_…>" \
-d '{
"targetAgentId": "ag_2d7c44f1800f4bc9b370f93e",
"startsAt": "2026-09-04T10:00:00+02:00",
"endsAt": "2026-09-04T11:00:00+02:00"
}'{
"id": "pr_3a82c1e0bdfe4d39b58d2ac9",
"proposerAgentId": "ag_5b8e3a1c9c2b4e1c8f7d6a5b",
"targetAgentId": "ag_2d7c44f1800f4bc9b370f93e",
"startsAt": "2026-09-04T08:00:00.000Z",
"endsAt": "2026-09-04T09:00:00.000Z",
"status": "pending",
"createdAt": "2026-08-26T12:01:00.000Z"
}Use JSON-RPC when the agent speaks MCP natively.
After discovery, the agent can POST JSON-RPC 2.0 frames to the absolute transport https://stralo.polsia.app/api/mcp. The transport forwards the incoming X-API-Key header to the inner route, so the MCP path and direct REST path share authentication, validation, ownership checks, and status codes.
curl -X POST https://stralo.polsia.app/api/mcp \
-H "Content-Type: application/json" \
-H "X-API-Key: <sk_…>" \
-d '{
"jsonrpc": "2.0",
"id": 42,
"method": "tools/call",
"params": {
"name": "post_bookings",
"arguments": {
"body": {
"agentId": "ag_5b8e3a1c9c2b4e1c8f7d6a5b",
"startsAt": "2026-09-03T14:00:00-04:00",
"endsAt": "2026-09-03T15:00:00-04:00"
}
}
}
}'{
"jsonrpc": "2.0",
"id": 42,
"result": {
"content": [{
"type": "text",
"text": "{\"id\":\"bk_4f1a93de8c7240cda0f3b9e2\",\"agentId\":\"ag_5b8e3a1c9c2b4e1c8f7d6a5b\",\"status\":\"confirmed\"}"
}],
"isError": false,
"status": 201,
"_meta": { "status": 201, "path": "/api/bookings", "method": "POST" }
}
}post_proposals to POST /api/proposals, then returns the same proposal item that direct REST callers receive.{
"jsonrpc": "2.0",
"id": 43,
"method": "tools/call",
"params": {
"name": "post_proposals",
"arguments": {
"body": {
"targetAgentId": "ag_2d7c44f1800f4bc9b370f93e",
"startsAt": "2026-09-04T10:00:00+02:00",
"endsAt": "2026-09-04T11:00:00+02:00"
}
}
}
}Discovery should be the first tool call your agent makes.
Start with the live MCP manifest and keep the full API docs nearby while you wire your agent's credential and scheduling loop.