space
Query OfficeRnD coworking space data including resources, bookings, assignments, and amenities. List, retrieve, or check availability of meeting rooms, desks, floors, and related entities.
Instructions
Query space/resource data in OfficeRnD.
action=list: List entities with optional filters and pagination (max 50 per page). action=get: Get a single entity by ID. action=status: Check current availability of a resource (requires id).
Entity-specific filters when listing:
resources: type (meeting_room|team_room|desk|hotdesk|desk_tr|desk_na), name, location
bookings: resourceId, member, company, location, startAfter, startBefore (ISO dates)
booking_occurrences: seriesStart (REQUIRED), seriesEnd (REQUIRED), resourceId, member, location
floors: location, name
assignments: resourceId, membershipId (at least one recommended)
amenities: title
passes: member, company
credits: member, company
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| action | Yes | Action to perform (status only for resources) | |
| entity | Yes | Entity type to query | |
| id | No | Entity ID (required for action=get and action=status) | |
| type | No | Resource type filter (resources only) | |
| resourceId | No | Filter by resource ID (bookings, booking_occurrences, assignments) | |
| membershipId | No | Filter by membership ID (assignments) | |
| member | No | Filter by member ID (bookings, booking_occurrences, passes, credits) | |
| company | No | Filter by company ID (bookings, passes, credits) | |
| location | No | Filter by location ID (resources, bookings, booking_occurrences, floors) | |
| name | No | Filter by exact name (resources, floors) | |
| title | No | Filter by title (amenities) | |
| startAfter | No | Bookings starting on/after this ISO date | |
| startBefore | No | Bookings starting before this ISO date | |
| seriesStart | No | Start of date range for booking occurrences (REQUIRED for booking_occurrences) | |
| seriesEnd | No | End of date range for booking occurrences (REQUIRED for booking_occurrences) | |
| cursorNext | No | Cursor token for next page of results | |
| limit | No | Results per page (max 50, default 50) |
Implementation Reference
- src/tools/space.ts:321-442 (handler)The handler function for the "space" tool, which routes actions (list, get, status) based on the entity type.
async ({ action, entity, id, type, resourceId, membershipId, member, company, location, name, title, startAfter, startBefore, seriesStart, seriesEnd, cursorNext, limit, }) => { try { // action=status: resource availability if (action === "status") { if (!id) { return { content: [{ type: "text" as const, text: "id is required for action=status." }], isError: true, }; } const s = await apiGet<ResourceStatus>(`/resources/${id}/status`); return { content: [{ type: "text" as const, text: formatResourceStatus(s) }] }; } const cfg = ENTITIES[entity]; // action=get if (action === "get") { if (!cfg.getPath) { return { content: [{ type: "text" as const, text: `Entity "${entity}" does not support get by ID.` }], isError: true, }; } if (!id) { return { content: [{ type: "text" as const, text: "id is required for action=get." }], isError: true, }; } const item = await apiGet<Record<string, unknown>>(`${cfg.getPath}/${id}`); return { content: [{ type: "text" as const, text: cfg.formatter(item) }] }; } // action=list const params: Record<string, string> = {}; if (cursorNext) params["$cursorNext"] = cursorNext; if (limit) params["$limit"] = limit; switch (entity) { case "resources": if (type) params["type"] = type; if (name) params["name"] = name; if (location) params["location"] = location; break; case "bookings": if (resourceId) params["resource"] = resourceId; if (member) params["member"] = member; if (company) params["company"] = company; if (location) params["location"] = location; if (startAfter) params["start.dateTime[$gte]"] = startAfter; if (startBefore) params["start.dateTime[$lte]"] = startBefore; break; case "booking_occurrences": if (seriesStart) params["seriesStart"] = seriesStart; if (seriesEnd) params["seriesEnd"] = seriesEnd; if (resourceId) params["resource"] = resourceId; if (member) params["member"] = member; if (location) params["location"] = location; break; case "floors": if (location) params["location"] = location; if (name) params["name"] = name; break; case "assignments": if (resourceId) params["resource"] = resourceId; if (membershipId) params["membership"] = membershipId; break; case "amenities": if (title) params["title"] = title; break; case "passes": case "credits": if (member) params["member"] = member; if (company) params["company"] = company; break; } const data = await apiGet<PaginatedResponse<Record<string, unknown>>>(cfg.listPath, params); if (data.results.length === 0) { return { content: [{ type: "text" as const, text: `No ${cfg.label} found.` }] }; } const text = data.results.map(cfg.formatter).join("\n---\n"); let result = `Found ${data.results.length} ${cfg.label} (range ${data.rangeStart}-${data.rangeEnd}):\n\n${text}`; if (data.cursorNext) { result += `\n\n[More results available — use cursorNext: "${data.cursorNext}"]`; } return { content: [{ type: "text" as const, text: result }] }; } catch (error) { return { content: [ { type: "text" as const, text: `Error querying ${entity}: ${error instanceof Error ? error.message : String(error)}`, }, ], isError: true, }; } } ); - src/tools/space.ts:226-320 (schema)Schema definition for the "space" tool input parameters.
{ title: "Space", description: `Query space/resource data in OfficeRnD. action=list: List entities with optional filters and pagination (max 50 per page). action=get: Get a single entity by ID. action=status: Check current availability of a resource (requires id). Entity-specific filters when listing: - resources: type (meeting_room|team_room|desk|hotdesk|desk_tr|desk_na), name, location - bookings: resourceId, member, company, location, startAfter, startBefore (ISO dates) - booking_occurrences: seriesStart (REQUIRED), seriesEnd (REQUIRED), resourceId, member, location - floors: location, name - assignments: resourceId, membershipId (at least one recommended) - amenities: title - passes: member, company - credits: member, company`, inputSchema: { action: z .enum(["list", "get", "status"]) .describe("Action to perform (status only for resources)"), entity: z .enum([ "resources", "bookings", "booking_occurrences", "floors", "assignments", "amenities", "passes", "credits", ]) .describe("Entity type to query"), id: z .string() .optional() .describe("Entity ID (required for action=get and action=status)"), type: z .string() .optional() .describe("Resource type filter (resources only)"), resourceId: z .string() .optional() .describe("Filter by resource ID (bookings, booking_occurrences, assignments)"), membershipId: z .string() .optional() .describe("Filter by membership ID (assignments)"), member: z .string() .optional() .describe("Filter by member ID (bookings, booking_occurrences, passes, credits)"), company: z .string() .optional() .describe("Filter by company ID (bookings, passes, credits)"), location: z .string() .optional() .describe("Filter by location ID (resources, bookings, booking_occurrences, floors)"), name: z .string() .optional() .describe("Filter by exact name (resources, floors)"), title: z .string() .optional() .describe("Filter by title (amenities)"), startAfter: z .string() .optional() .describe("Bookings starting on/after this ISO date"), startBefore: z .string() .optional() .describe("Bookings starting before this ISO date"), seriesStart: z .string() .optional() .describe("Start of date range for booking occurrences (REQUIRED for booking_occurrences)"), seriesEnd: z .string() .optional() .describe("End of date range for booking occurrences (REQUIRED for booking_occurrences)"), cursorNext: z .string() .optional() .describe("Cursor token for next page of results"), limit: z .string() .optional() .describe("Results per page (max 50, default 50)"), }, }, - src/tools/space.ts:223-443 (registration)Tool registration function for "space".
export function registerSpaceTool(server: McpServer): void { server.registerTool( "space", { title: "Space", description: `Query space/resource data in OfficeRnD. action=list: List entities with optional filters and pagination (max 50 per page). action=get: Get a single entity by ID. action=status: Check current availability of a resource (requires id). Entity-specific filters when listing: - resources: type (meeting_room|team_room|desk|hotdesk|desk_tr|desk_na), name, location - bookings: resourceId, member, company, location, startAfter, startBefore (ISO dates) - booking_occurrences: seriesStart (REQUIRED), seriesEnd (REQUIRED), resourceId, member, location - floors: location, name - assignments: resourceId, membershipId (at least one recommended) - amenities: title - passes: member, company - credits: member, company`, inputSchema: { action: z .enum(["list", "get", "status"]) .describe("Action to perform (status only for resources)"), entity: z .enum([ "resources", "bookings", "booking_occurrences", "floors", "assignments", "amenities", "passes", "credits", ]) .describe("Entity type to query"), id: z .string() .optional() .describe("Entity ID (required for action=get and action=status)"), type: z .string() .optional() .describe("Resource type filter (resources only)"), resourceId: z .string() .optional() .describe("Filter by resource ID (bookings, booking_occurrences, assignments)"), membershipId: z .string() .optional() .describe("Filter by membership ID (assignments)"), member: z .string() .optional() .describe("Filter by member ID (bookings, booking_occurrences, passes, credits)"), company: z .string() .optional() .describe("Filter by company ID (bookings, passes, credits)"), location: z .string() .optional() .describe("Filter by location ID (resources, bookings, booking_occurrences, floors)"), name: z .string() .optional() .describe("Filter by exact name (resources, floors)"), title: z .string() .optional() .describe("Filter by title (amenities)"), startAfter: z .string() .optional() .describe("Bookings starting on/after this ISO date"), startBefore: z .string() .optional() .describe("Bookings starting before this ISO date"), seriesStart: z .string() .optional() .describe("Start of date range for booking occurrences (REQUIRED for booking_occurrences)"), seriesEnd: z .string() .optional() .describe("End of date range for booking occurrences (REQUIRED for booking_occurrences)"), cursorNext: z .string() .optional() .describe("Cursor token for next page of results"), limit: z .string() .optional() .describe("Results per page (max 50, default 50)"), }, }, async ({ action, entity, id, type, resourceId, membershipId, member, company, location, name, title, startAfter, startBefore, seriesStart, seriesEnd, cursorNext, limit, }) => { try { // action=status: resource availability if (action === "status") { if (!id) { return { content: [{ type: "text" as const, text: "id is required for action=status." }], isError: true, }; } const s = await apiGet<ResourceStatus>(`/resources/${id}/status`); return { content: [{ type: "text" as const, text: formatResourceStatus(s) }] }; } const cfg = ENTITIES[entity]; // action=get if (action === "get") { if (!cfg.getPath) { return { content: [{ type: "text" as const, text: `Entity "${entity}" does not support get by ID.` }], isError: true, }; } if (!id) { return { content: [{ type: "text" as const, text: "id is required for action=get." }], isError: true, }; } const item = await apiGet<Record<string, unknown>>(`${cfg.getPath}/${id}`); return { content: [{ type: "text" as const, text: cfg.formatter(item) }] }; } // action=list const params: Record<string, string> = {}; if (cursorNext) params["$cursorNext"] = cursorNext; if (limit) params["$limit"] = limit; switch (entity) { case "resources": if (type) params["type"] = type; if (name) params["name"] = name; if (location) params["location"] = location; break; case "bookings": if (resourceId) params["resource"] = resourceId; if (member) params["member"] = member; if (company) params["company"] = company; if (location) params["location"] = location; if (startAfter) params["start.dateTime[$gte]"] = startAfter; if (startBefore) params["start.dateTime[$lte]"] = startBefore; break; case "booking_occurrences": if (seriesStart) params["seriesStart"] = seriesStart; if (seriesEnd) params["seriesEnd"] = seriesEnd; if (resourceId) params["resource"] = resourceId; if (member) params["member"] = member; if (location) params["location"] = location; break; case "floors": if (location) params["location"] = location; if (name) params["name"] = name; break; case "assignments": if (resourceId) params["resource"] = resourceId; if (membershipId) params["membership"] = membershipId; break; case "amenities": if (title) params["title"] = title; break; case "passes": case "credits": if (member) params["member"] = member; if (company) params["company"] = company; break; } const data = await apiGet<PaginatedResponse<Record<string, unknown>>>(cfg.listPath, params); if (data.results.length === 0) { return { content: [{ type: "text" as const, text: `No ${cfg.label} found.` }] }; } const text = data.results.map(cfg.formatter).join("\n---\n"); let result = `Found ${data.results.length} ${cfg.label} (range ${data.rangeStart}-${data.rangeEnd}):\n\n${text}`; if (data.cursorNext) { result += `\n\n[More results available — use cursorNext: "${data.cursorNext}"]`; } return { content: [{ type: "text" as const, text: result }] }; } catch (error) { return { content: [ { type: "text" as const, text: `Error querying ${entity}: ${error instanceof Error ? error.message : String(error)}`, }, ], isError: true, }; } } ); }