MCP integration guide
August 26, 2026 · 8 min read

Put a real calendar behind your agent.

A practical path from MCP discovery to a confirmed slot: let your agent inspect /mcp.json, issue its own STRALO credential, then call bookings and proposals through the same contract-backed API.

Agent stack, in order
Discover → identify → schedule

01 GET /mcp.json

02 POST /api/agents

03 X-API-Key → /api/bookings

04 tools/call → /api/mcp

01 / Discovery

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.

GET/mcp.json
curl https://stralo.polsia.app/mcp.json
200 OKmanifest shape
{
  "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?"
      }
    }
  ]
}
02 / Tool catalog

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.

ToolHTTPPathAuthinputSchema
post_agentsPOST/api/agentsbootstrap or X-API-Key/BearerAgentCreate — name, bootstrap?: true, metadata?, config?, idempotency_key?
post_bookingsPOST/api/bookingsX-API-Key or BearerBookingCreate — agentId, startsAt, endsAt
get_bookingsGET/api/bookingsX-API-Key or Bearerquery — limit?, from?, to?
delete_bookings_idDELETE/api/bookings/{id}X-API-Key or Bearerpath — id
post_proposalsPOST/api/proposalsX-API-Key or BearerProposalCreate — targetAgentId, startsAt, endsAt
patch_proposals_id_acceptPATCH/api/proposals/{id}/acceptX-API-Key or Bearerpath — id
patch_proposals_id_rejectPATCH/api/proposals/{id}/rejectX-API-Key or Bearerpath — id
get_bookings_id_occurrencesGET/api/bookings/{id}/occurrencesX-API-Key or Bearerpath — id; query — limit?
03 / Identity

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.

POST/api/agents
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" }
  }'
201 Createdsave public_token now
{
  "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>"
  }
}
Protected calls accept 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.
04 / Confirmed time

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.

POST/api/bookings
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"
  }'
201 Createdconfirmed booking
{
  "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"
}
Collision is a typed outcome.
A second confirmed window that overlaps the same agent's slot returns 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."
}
05 / Negotiated time

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.

POST/api/proposals
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"
  }'
201 Createdpending proposal
{
  "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"
}
06 / Streamable-HTTP

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.

tools/callpost_bookings
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"
        }
      }
    }
  }'
JSON-RPC resultwrapped 201
{
  "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" }
  }
}
Proposals use the same call shape.
Change only the tool name and body. The MCP router maps 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"
      }
    }
  }
}
Ship the integration

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.