MCP Server
Original Workspace exposes an MCP (Model Context Protocol) server that lets external AI tools -- such as Cursor, Claude Desktop, or custom agents -- interact with your workspace data.
Base URL
All MCP communication happens over a single endpoint:
https://api-workspace.original.land/mcp
The server is stateless -- every request is self-contained and authenticated via API key. No sessions or persistent connections required.
| Method | Purpose |
|---|---|
POST /mcp | Send JSON-RPC messages (initialize or call tools) |
Authentication
Every request must include the x-api-key header with a valid API key. Any workspace member can create API keys in Settings > Profile > API Keys; each key is scoped to a workspace (and optionally to a single company) and is bound to the user who created it.
| Header | Required | Description |
|---|---|---|
x-api-key | Always | Your API key (format: bsk_...). Determines workspace, company scope, and the acting user. |
Permissions
The MCP path enforces the same role and module permissions as the app. A key acts with the permissions of the user who created it, so a tool call can never reach data or actions that the user could not access in the UI:
- Role gating -- Each tool requires the same roles as the matching area in Settings > Members > Role permissions. For example,
list_invoices/create_invoiceare only available toadminandaccounting; an agent run by adeveloperorsalesuser gets an access-denied error. - Module gating -- A tool is unavailable if its workspace module is disabled (e.g. invoice tools require the
finance_collectionsmodule). - Company scoping -- For users limited to specific companies, list tools only return data from those companies.
- User binding required -- Legacy keys not bound to a user are rejected; recreate the key to use the MCP API.
How it works
The server is fully stateless. Each POST /mcp request:
- Validates the API key and extracts the workspace/company scope.
- Builds a fresh MCP server with all available tools.
- Handles the JSON-RPC message and returns the response.
There are no sessions to manage, no SSE streams, and no cleanup required. This makes it compatible with horizontally-scaled deployments and simple to integrate.
Available tools
Projects
| Tool | Description | Parameters |
|---|---|---|
list_projects | List all projects in the workspace | status? (in_progress, completed, invoiced) |
get_project | Get full project details by ID | projectId (UUID) |
create_project | Create a new project | name (string), plus optional: clientId, companyId, offerId, taskPrefix, description, startDate, endDate, budget, estimatedHours, notes, currency |
update_project | Update project fields | projectId (UUID), plus optional: name, description, status, startDate, endDate, budget, estimatedHours, notes, taskPrefix |
update_project_status | Change project status | projectId (UUID), status (in_progress, completed, invoiced) |
Tasks
| Tool | Description | Parameters |
|---|---|---|
list_tasks | List or search top-level tasks (includes childTaskCount, displayId, and derived isOverdue/isForecastLate flags). For scheduling / "when will it be done" questions use gantt_plan instead. | Optional: projectIds (UUID[]), taskScope (me, my_team, all), targetUserId (UUID), query (string), taskNumber (number), projectPrefix (string), overdue (boolean) |
get_task | Get full task details including subtasks, comments, and assignees | taskId (UUID) |
create_task | Create a new top-level task | title (string), plus optional: description, status, priority, estimatedHours, dueDate, projectId, assigneeIds |
create_subtask | Create a subtask under a parent task (inherits project, max 1 level) | parentTaskId (UUID), title (string), plus optional: description, status, priority, estimatedHours, dueDate, assigneeIds, isPrivate |
list_subtasks | List all subtasks of a parent task | parentTaskId (UUID) |
update_task | Update task fields (works for both tasks and subtasks) | taskId (UUID), plus optional: title, description, status, priority, estimatedHours, actualHours, dueDate, sortOrder, projectId, assigneeIds, isPrivate |
update_task_status | Change task status (works for both tasks and subtasks) | taskId (UUID), status (todo, in_progress, frozen, done) |
set_task_queue_position | Place a task at a given slot of a user's Workload queue (mirrors drag&drop in the Carico view). Writes status=todo, assigns the user, and computes a sortOrder so the task lands at slotIndex. Reorders Gantt ghost bars of the same user automatically; solid bars (with startDate) keep their timeline position. | taskId (UUID), userId (UUID), slotIndex (int, 0-indexed) |
move_task_to_focus | Promote a task to a user's Workload Focus slot (status=in_progress). If the user already had a different focus, that previous task is automatically demoted to slot #1 of their queue. | taskId (UUID), userId (UUID) |
gantt_plan | Return Gantt bars for a time window: solid bars (tasks with startDate) plus auto-planned ghost bars (todo + in_progress tasks without startDate) packed in sortOrder after each user's solid work | from (YYYY-MM-DD), to (YYYY-MM-DD), plus optional: taskScope (me, my_team, all), targetUserId (UUID), projectIds (UUID[]), statuses (string[]), search (string) |
Clients
| Tool | Description | Parameters |
|---|---|---|
list_clients | List all clients in the workspace (the customers you invoice), sorted by company name | None |
create_client | Create a new client | companyName (string), plus optional: firstName, lastName, email, phone, vatNumber, address, department, postalCode, city, country, vatRate, intermediary, commission, companyId |
update_client | Update one or more fields of an existing client | clientId (UUID), plus optional: same fields as create_client |
Offers
| Tool | Description | Parameters |
|---|---|---|
create_offer | Create a new offer/quote | Optional: subject, description, clientId, companyId, offerDate, validUntil, currency, notes, rows (line items), tranches (payment plan: each by percentage or amount) |
Invoices
| Tool | Description | Parameters |
|---|---|---|
list_invoices | List invoices and credit notes. Credit notes (isCreditNote: true) are returned with already-negative amounts, matching the UI, the PDF and the dashboard statistics | Optional: status (draft, sent, partially_paid, paid), type (recurring, project, credit_note), clientId, limit |
create_invoice | Create a new invoice or credit note (totals are computed by the backend from the line items, VAT rate and rounding). Set isCreditNote to create a credit note: amounts are stored positive (negative input is normalized automatically, never failing) but are treated as negative in lists, PDF and statistics; numbering is shared with invoices | Optional: clientId, companyId, projectId, subject, description, status (draft, sent, partially_paid, paid), invoiceDate, dueDate, currency, vatRate, roundingAdjustment, collectedAmount, notes, paymentMethod, isCreditNote, originalInvoiceId, rows (line items) |
generate_invoice_from_offer_tranche | Generate an invoice from an offer payment tranche (tranche must be to_invoice; it is marked invoiced and linked on success) | Required: offerId, trancheId. Optional: clientId, companyId, subject, description, invoiceDate, dueDate, currency, vatRate, notes, paymentMethod, rows |
Ideas
| Tool | Description | Parameters |
|---|---|---|
create_idea | Create a new idea/proposal | title (string), plus optional: description, estimatedBudget, currency, priority (1=low, 2=medium, 3=high), notes, companyId, clientId |
Contacts
| Tool | Description | Parameters |
|---|---|---|
list_contacts | List all CRM contacts (leads/prospects) in the workspace, most recent first | None |
create_contact | Create a new business contact | Optional: companyName, contactName, email, phone, website, address, city, country, source, status, notes, nextFollowUp |
update_contact | Update one or more fields of an existing contact | contactId (UUID), plus optional: companyName, contactName, email, phone, status (new, contacted, qualified, proposal, negotiation, won, lost), source, notes, nextFollowUp, assignedTo, and the other contact fields |
Activities
| Tool | Description | Parameters |
|---|---|---|
list_activities | List CRM activities (calls, emails, meetings, etc.), mirroring the Activities page | Optional: contactId, clientId, collaboratorId, supplierId, type (call, email, meeting, lunch, visit, video_call, message, other), search |
create_activity | Log a new CRM activity linked to exactly one reference entity | title (string), plus optional: type, description, one of contactId / clientId / collaboratorId / supplierId, activityDate, startDate, endDate, isAllDay, durationMinutes, isCompleted, completedAt, outcome, notes |
update_activity | Update one or more fields of an existing activity | activityId (UUID), plus optional: same fields as create_activity |
AI Activities
| Tool | Description | Parameters |
|---|---|---|
track_ai_activity | Create a Workspace AI Activity for user-visible AI work | kind, title, plus optional: source, description, agentName, status, companyId, aiTaskId, phases, resultMarkdown, metadata |
update_ai_activity_phase | Update or append a phase and optionally update status/result markdown | activityId, phaseName, phaseStatus, plus optional: phaseSummary, phaseError, startedAt, finishedAt, activityStatus, resultMarkdown, replaceResultMarkdown |
list_ai_activities | List Workspace AI Activities with status, phases and result summaries | Optional: kind, source, status, search, limit, offset |
get_ai_activity | Get one AI Activity with phases, metadata, result markdown and linked documents | activityId (UUID) |
Workspace Documents
| Tool | Description | Parameters |
|---|---|---|
create_workspace_document | Create a first-class Workspace Document, optionally linked to an AI Activity | title, kind, plus optional: source, description, companyId, mimeType, contentMarkdown, storage fields, activityId, relationship, metadata |
update_workspace_document | Update a Workspace Document, including markdown content | documentId, plus optional: title, description, kind, source, companyId, mimeType, contentMarkdown, storage fields, metadata |
list_workspace_documents | List Workspace Documents such as generated reports and notes | Optional: kind, source, search, limit, offset |
get_workspace_document | Get one Workspace Document with content and linked AI Activities | documentId (UUID) |
Recordings
| Tool | Description | Parameters |
|---|---|---|
list_recordings | List the most recent audio recordings for the current user | limit? (number, default 10, max 10) |
get_recording | Get one recording with summary, action items, notes, and transcription | recordingId (UUID) |
Time Logs
| Tool | Description | Parameters |
|---|---|---|
list_time_logs | List the current user's time logs | startDate? (YYYY-MM-DD), endDate? (YYYY-MM-DD) |
time_log_summary | Aggregated totals of the current user's time logs (totalHours, entryCount, byProject, byCategory), mirroring the personal Timesheet | Optional: startDate, endDate, projectIds (UUID[]) |
list_time_categories | List the workspace's active timesheet reasons (vacation, sick leave, internal meeting, training, ...). Use the returned id as categoryId when calling create_time_log for non-task activities. | None |
create_time_log | Create a new time log for the current user. Exactly one of taskId / categoryId must be provided (XOR). When categoryId is set you may also pass projectId to attach the activity to a project (e.g. an internal meeting on project Pippo); projectId is forbidden together with taskId. | date (YYYY-MM-DD), startTime (HH:MM), endTime (HH:MM), one of taskId (UUID) or categoryId (UUID), plus optional: projectId (UUID, only with categoryId), description, isBillable, hourlyRate |
update_time_log | Update an existing time log. When swapping a slot between a task and a category, set the new field; the previous one (and any direct projectId) is cleared automatically. projectId is only valid on category-linked logs. | timeLogId (UUID), plus optional: date, startTime, endTime, description, taskId, categoryId, projectId, isBillable, hourlyRate |
delete_time_log | Delete an existing time log | timeLogId (UUID) |
Appointments
| Tool | Description | Parameters |
|---|---|---|
list_calendars | List calendars available to the current user | None |
list_appointments | List the current user's appointments | startDate? (YYYY-MM-DD), endDate? (YYYY-MM-DD) |
create_appointment | Create a new appointment in the current workspace | date (YYYY-MM-DD), startTime (HH:MM), endTime (HH:MM), plus optional: title, clientName, clientEmail, clientPhone, isAllDay, location (studio, online, home), address, onlinePlatform, onlineLink, notes, status, projectId |
update_appointment | Update an existing appointment | appointmentId (UUID), plus optional: title, clientName, clientEmail, date, startTime, endTime, isAllDay, location, address, onlinePlatform, onlineLink, notes, status, projectId |
Online Booking
| Tool | Description | Parameters |
|---|---|---|
get_booking_settings | Get the current user's online booking page configuration (slug, active status, weekly hours, slot duration, destination calendar, etc.) | None |
update_booking_settings | Create or update the online booking page configuration | All optional: isActive (boolean), slotDuration (minutes), bufferMinutes, weeklyHours (object keyed "1".."7" for Mon..Sun, each value an array of { start, end } HH:MM ranges), maxDaysAhead, minimumNoticeMinutes, welcomeMessage, locationType (none, in_person, google_meet), locationAddress, bookingCalendarId (UUID), companyId |
The create_task, create_subtask, create_offer, create_idea, create_activity, track_ai_activity, create_workspace_document, list_recordings, get_recording, list_time_logs, time_log_summary, list_time_categories, create_time_log, update_time_log, delete_time_log, list_calendars, list_appointments, create_appointment, update_appointment, get_booking_settings, and update_booking_settings tools require user context: they only work when the caller is tied to a specific user — either the AI Copilot session or an API key created for a specific user. API keys that are not bound to a user cannot call them.
Quick example
Initialize the connection and list all in-progress projects in a single request flow:
// POST /mcp
// Headers: x-api-key: bsk_your_api_key_here
{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2025-03-26",
"capabilities": {},
"clientInfo": {
"name": "my-client",
"version": "1.0.0"
}
}
}
Then call a tool (same endpoint, same header):
// POST /mcp
// Headers: x-api-key: bsk_your_api_key_here
{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {
"name": "list_projects",
"arguments": {
"status": "in_progress"
}
}
}
The server responds with a JSON array of project summaries (id, name, status, budget, client, etc.).
Security
- Workspace isolation -- The API key determines which workspace data is accessible. Tools can only read/write data within that workspace.
- Role & module enforcement -- Tools are gated by the acting user's role and the workspace's enabled modules, mirroring the in-app permission matrix. A key can never do more than its owner could in the UI.
- Company scoping -- If the API key is scoped to a specific company, tools will only return and accept modifications to data belonging to that company. Users with access to a subset of companies only see those companies' data.
- Own keys only -- Non-admin members can only see and revoke the API keys they created; admins manage every key in the workspace.
- Key rotation -- API keys can be revoked at any time from Settings. Create new keys and revoke old ones to rotate credentials.