Connect your AI to Vitrina and let it act
Authorise the right write packs and let it act for real.
Your assistant can already read the workspace: leads, conversations, the calendar. This recipe lets it also act, only in the areas you authorise. By the end you'll have a connection that books a real appointment. And you'll know exactly how to read what you just gave it permission to do.
Trap
A write permission is not a generic switch: it's a sentence, and you have to read the whole thing
Ticking «Agenda» on the authorisation screen doesn't unlock everything that sounds agenda-shaped. That pack creates and moves appointments, and it says so in full: it doesn't include cancelling them. Cancelling asks for two permissions «Agenda» never grants, because cancelling also messages the customer. Before you trust a pack, read its whole sentence, not just its name.
Before you start
- A workspace account with a role that reaches the areas you're going to authorise. A role with no Leads can't grant the Leads permission, however hard you try.
- The AI client already installed: Claude, Claude Code or Cursor. That's where the step-by-step for authorising lives.
- No credential to copy by hand. The OAuth connection keeps the secret for you.
1. Connect from your client
The path is the same for any workspace: paste https://api.vitrinadev.com/mcp, sign in to Vitrina and authorise. Below the read access, the permissions screen carries one switch per area that can write. Every one starts off. If you leave them all off, the connection changes nothing.
2. Look at what your connection reaches
With «Agenda» authorised, ask your client what it can do with the calendar, or call the generic tool yourself:
curl -X POST https://api.vitrinadev.com/mcp \
-H "Authorization: Bearer $VITRINA_CONNECTOR_TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"search_operations","arguments":{"query":"appointment"}}}'{
"total": 10,
"returned": 10,
"tags": ["Analytics", "Appointments", "Contacts", "Conversations", "Help Centers", "Leads", "Locations", "Pipelines", "Price Approvals", "Public Stock", "Teams", "Tickets", "Vehicles", "WhatsApp Flows"],
"operations": [
{ "operation_id": "appointment_get", "method": "GET", "path": "/appointments/{id}", "tag": "Appointments", "tier": "beta", "destructive": false, "writes": false, "summary": "Fetch one appointment", "scopes": ["appointments:read"] },
{ "operation_id": "appointment_type_get", "method": "GET", "path": "/appointment-types/{id}", "tag": "Appointments", "tier": "beta", "destructive": false, "writes": false, "summary": "Get an appointment type", "scopes": ["appointment_types:read"] },
{ "operation_id": "appointment_types_list", "method": "GET", "path": "/appointment-types", "tag": "Appointments", "tier": "beta", "destructive": false, "writes": false, "summary": "List appointment types", "scopes": ["appointment_types:read"] },
{ "operation_id": "appointment_update", "method": "PATCH", "path": "/appointments/{id}", "tag": "Appointments", "tier": "beta", "destructive": false, "writes": true, "summary": "Reschedule, reassign or close an appointment", "scopes": ["appointments:write"] },
{ "operation_id": "appointments_availability_list", "method": "GET", "path": "/appointments/availability", "tag": "Appointments", "tier": "beta", "destructive": false, "writes": false, "summary": "Open slots", "scopes": ["appointments:read"] },
{ "operation_id": "appointments_calendar_list", "method": "GET", "path": "/appointments/calendar", "tag": "Appointments", "tier": "beta", "destructive": false, "writes": false, "summary": "Appointments in a calendar window", "scopes": ["appointments:read"] },
{ "operation_id": "appointments_config_list", "method": "GET", "path": "/appointments/config", "tag": "Appointments", "tier": "beta", "destructive": false, "writes": false, "summary": "The scheduling configuration", "scopes": ["appointments:read"] },
{ "operation_id": "appointments_create", "method": "POST", "path": "/appointments", "tag": "Appointments", "tier": "beta", "destructive": false, "writes": true, "summary": "Book an appointment", "scopes": ["appointments:write"] },
{ "operation_id": "appointments_list", "method": "GET", "path": "/appointments", "tag": "Appointments", "tier": "beta", "destructive": false, "writes": false, "summary": "List appointments", "scopes": ["appointments:read"] },
{ "operation_id": "contact_timeline_list", "method": "GET", "path": "/contacts/{id}/timeline", "tag": "Contacts", "tier": "beta", "destructive": false, "writes": false, "summary": "One chronological feed of everything that happened to a contact", "scopes": ["contacts:read"] }
]
}Ten operations, eight reads and two writes: creating and updating an appointment. appointment_cancel_create isn't there. That's not a search failure. That operation doesn't exist for this connection, and the next step shows why.
3. Check the pack's promise, instead of trusting it blind
POST /appointments/{id}/cancel asks for appointments:delete and messages:send, two permissions «Agenda» never grants. Ask your assistant to cancel an appointment and look at the real answer:
curl -X POST https://api.vitrinadev.com/mcp \
-H "Authorization: Bearer $VITRINA_CONNECTOR_TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"call_operation","arguments":{"operation_id":"appointment_cancel_create","params":{"id":"6ce1d84e-3740-440e-9cda-69c2ce50027c"},"body":{"reason":"test"}}}}'{
"content": [
{ "type": "text", "text": "Unknown operation 'appointment_cancel_create'. Call search_operations to see what this credential can reach. Writes are granted per domain on the authorization screen, so an operation that exists in the documentation may still be outside this connection." }
],
"isError": true
}It doesn't answer 403: it answers that the operation doesn't exist. It's the same sentence for an area you never authorised, like Leads:
{
"content": [
{ "type": "text", "text": "Unknown operation 'leads_create'. Call search_operations to see what this credential can reach. Writes are granted per domain on the authorization screen, so an operation that exists in the documentation may still be outside this connection." }
],
"isError": true
}Three different causes, one text: the operation doesn't exist, it exists but sits outside your workspace, or it belongs to an area you never authorised.
4. Let it book a real appointment
appointments_create is right there in step 2's list, and it writes. Ask your assistant to book something, and underneath it calls this:
curl -X POST https://api.vitrinadev.com/mcp \
-H "Authorization: Bearer $VITRINA_CONNECTOR_TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{
"jsonrpc": "2.0", "id": 3, "method": "tools/call",
"params": {
"name": "call_operation",
"arguments": {
"operation_id": "appointments_create",
"body": {
"starts_at": "2026-10-05T15:00:00.000Z",
"ends_at": "2026-10-05T15:30:00.000Z",
"kind": "external",
"appointment_type_id": "eed63545-0f09-47a3-9d14-d844ba1b0cd1",
"contact_id": "01a0cbfe-0e3a-7960-9901-ad153b1db135",
"location_id": "6812d9f0-9bed-44b5-91df-4e5b90941f6b",
"customer_name": "Camila Fuentes"
}
}
}
}'{
"data": {
"id": "2c595afc-1420-4e9f-a0f2-9cf8160bf825",
"contact_id": "01a0cbfe-0e3a-7960-9901-ad153b1db135",
"location_id": "6812d9f0-9bed-44b5-91df-4e5b90941f6b",
"kind": "external",
"status": "confirmed",
"starts_at": "2026-10-05T15:00:00.000Z",
"ends_at": "2026-10-05T15:30:00.000Z",
"customer_name": "Camila Fuentes",
"display_id": "A-15",
"created_at": "2026-09-23T11:52:30.303Z"
}
}A-15, confirmed, with nobody opening the app. The call took the same route any REST client would, with your own credential: the same permissions, the same validation, the same audit trail. If your assistant repeats this call after a retry, it doesn't create a second appointment. The identical response gets reused instead of writing twice. The full mechanism, and how to force a deliberate second write, is in Call the whole API from your AI.
When it fails
Unknown operation is always the same message, and it covers three distinct causes: the operation doesn't exist, it isn't published, or it exists but no pack you authorised reaches it. Use search_operations to see what you can call: if an operation isn't there, you can't call it, whatever the reason.
If the authorising member's role narrows after you connect, the connection narrows on its next call. If something that used to work starts answering Unknown operation, check the connecting member's role first.
In the app: the same write packs, with their full description, are authorised under Connections → MCP.
The full list, pack by pack, is in Connector tools, and the seven areas that can be authorised are in Call the whole API from your AI. If all you need is to read, authorising no writes at all, Connect Claude to your calendar is the shorter route.