collaboration
Query collaboration data in OfficeRnD including events, tickets, and posts with filtering options. List entities with pagination or retrieve specific items by ID.
Instructions
Query collaboration data in OfficeRnD.
action=list: List entities with optional filters and pagination (max 50 per page). action=get: Get a single entity by ID.
Entity-specific filters when listing:
events: location, startAfter, startBefore (ISO dates)
tickets: status, member, location
posts: (pagination only)
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| action | Yes | Action to perform | |
| entity | Yes | Entity type to query | |
| id | No | Entity ID (required for action=get) | |
| status | No | Filter by status (tickets only) | |
| member | No | Filter by member ID (tickets only) | |
| location | No | Filter by location ID (events, tickets) | |
| startAfter | No | Events starting on/after this ISO date | |
| startBefore | No | Events starting before this ISO date | |
| cursorNext | No | Cursor token for next page of results | |
| limit | No | Results per page (max 50, default 50) |
Implementation Reference
- src/tools/collaboration.ts:149-208 (handler)The handler function logic for the collaboration tool, which executes list or get actions based on the provided entity and filters.
async ({ action, entity, id, status, member, location, startAfter, startBefore, cursorNext, limit }) => { try { const cfg = ENTITIES[entity]; if (action === "get") { 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) }] }; } // list const params: Record<string, string> = {}; if (cursorNext) params["$cursorNext"] = cursorNext; if (limit) params["$limit"] = limit; switch (entity) { case "events": if (location) params["location"] = location; if (startAfter) params["start[$gte]"] = startAfter; if (startBefore) params["start[$lt]"] = startBefore; break; case "tickets": if (status) params["status"] = status; if (member) params["member"] = member; if (location) params["location"] = location; 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/collaboration.ts:110-147 (schema)The input schema definition using Zod for the collaboration tool.
inputSchema: { action: z.enum(["list", "get"]).describe("Action to perform"), entity: z .enum(["events", "tickets", "posts"]) .describe("Entity type to query"), id: z .string() .optional() .describe("Entity ID (required for action=get)"), status: z .string() .optional() .describe("Filter by status (tickets only)"), member: z .string() .optional() .describe("Filter by member ID (tickets only)"), location: z .string() .optional() .describe("Filter by location ID (events, tickets)"), startAfter: z .string() .optional() .describe("Events starting on/after this ISO date"), startBefore: z .string() .optional() .describe("Events starting before this ISO date"), 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/collaboration.ts:96-208 (registration)Tool registration function for the collaboration tool.
export function registerCollaborationTool(server: McpServer): void { server.registerTool( "collaboration", { title: "Collaboration", description: `Query collaboration data in OfficeRnD. action=list: List entities with optional filters and pagination (max 50 per page). action=get: Get a single entity by ID. Entity-specific filters when listing: - events: location, startAfter, startBefore (ISO dates) - tickets: status, member, location - posts: (pagination only)`, inputSchema: { action: z.enum(["list", "get"]).describe("Action to perform"), entity: z .enum(["events", "tickets", "posts"]) .describe("Entity type to query"), id: z .string() .optional() .describe("Entity ID (required for action=get)"), status: z .string() .optional() .describe("Filter by status (tickets only)"), member: z .string() .optional() .describe("Filter by member ID (tickets only)"), location: z .string() .optional() .describe("Filter by location ID (events, tickets)"), startAfter: z .string() .optional() .describe("Events starting on/after this ISO date"), startBefore: z .string() .optional() .describe("Events starting before this ISO date"), 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, status, member, location, startAfter, startBefore, cursorNext, limit }) => { try { const cfg = ENTITIES[entity]; if (action === "get") { 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) }] }; } // list const params: Record<string, string> = {}; if (cursorNext) params["$cursorNext"] = cursorNext; if (limit) params["$limit"] = limit; switch (entity) { case "events": if (location) params["location"] = location; if (startAfter) params["start[$gte]"] = startAfter; if (startBefore) params["start[$lt]"] = startBefore; break; case "tickets": if (status) params["status"] = status; if (member) params["member"] = member; if (location) params["location"] = location; 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, }; } } ); }