UNPKG

officernd-mcp-server

Version:

MCP server for OfficeRnD workspace management - create, search, update and cancel bookings

1,136 lines (1,135 loc) 48.9 kB
#!/usr/bin/env node import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js'; import { OfficeRnDClient } from './officernd-client.js'; import dotenv from 'dotenv'; import { promises as fs } from 'fs'; import path from 'path'; import { fileURLToPath } from 'url'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); dotenv.config(); // Initialize OfficeRnD client variable let officeRndClient = null; // Check if environment variables are configured const requiredEnvVars = ['OFFICERND_CLIENT_ID', 'OFFICERND_CLIENT_SECRET', 'OFFICERND_ORG_SLUG']; const isConfigured = requiredEnvVars.every(envVar => process.env[envVar]); // Detect if running via npx (in npm cache) or local development const isRunningViaNpx = __dirname.includes('/.npm/') || __dirname.includes('\\.npm\\'); const isDevelopmentMode = !isRunningViaNpx; // Get optional default company from environment const defaultCompanyId = process.env.OFFICERND_DEFAULT_COMPANY_ID || null; if (isConfigured) { // Initialize OfficeRnD client if configured const RESOURCE_CACHE_MINUTES = parseInt(process.env.RESOURCE_CACHE_MINUTES || '60', 10); officeRndClient = new OfficeRnDClient(process.env.OFFICERND_CLIENT_ID, process.env.OFFICERND_CLIENT_SECRET, process.env.OFFICERND_ORG_SLUG, RESOURCE_CACHE_MINUTES); console.error('OfficeRnD client initialized with existing configuration'); if (defaultCompanyId) { console.error(`Default company ID configured: ${defaultCompanyId}`); } } else { if (isDevelopmentMode) { console.error('OfficeRnD not configured. Use configure_officernd tool to set up credentials.'); } else { console.error('OfficeRnD not configured. Please check your Claude Desktop configuration.'); } } // Define available tools const tools = [ // Only include configure tool in development mode ...(isDevelopmentMode ? [{ name: 'configure_officernd', description: 'Configure OfficeRnD API credentials and validate the connection. This updates the .env file and reinitializes the client. (Only available in local development mode)', inputSchema: { type: 'object', properties: { clientId: { type: 'string', description: 'OfficeRnD OAuth client ID', }, clientSecret: { type: 'string', description: 'OfficeRnD OAuth client secret', }, orgSlug: { type: 'string', description: 'Your OfficeRnD organization slug (e.g., "acme-corp")', }, cacheDurationMinutes: { type: 'number', description: 'Resource cache duration in minutes (default: 60) - Advanced setting for performance optimization', minimum: 0, maximum: 1440, }, defaultCompanyId: { type: 'string', description: 'Default company ID for bookings (optional) - Will be used automatically when creating bookings unless a different company is specified', }, }, required: ['clientId', 'clientSecret', 'orgSlug'], }, }] : []), { name: 'search_bookings', description: 'Search for bookings in OfficeRnD. Returns booking information including dates, members, resources, and fees.', inputSchema: { type: 'object', properties: { bookingId: { type: 'string', description: 'Specific booking ID to search for', }, companyId: { type: 'string', description: 'Filter bookings by company ID', }, memberId: { type: 'string', description: 'Filter bookings by member ID', }, locationId: { type: 'string', description: 'Filter bookings by location ID', }, resourceId: { type: 'string', description: 'Filter bookings by resource ID', }, seriesStartFrom: { type: 'string', description: 'Filter bookings with series start date >= this date (ISO 8601 format in UTC). Note: Convert local time to UTC by subtracting the timezone offset (e.g., for Zurich CEST subtract 2 hours, for CET subtract 1 hour)', }, seriesStartTo: { type: 'string', description: 'Filter bookings with series start date <= this date (ISO 8601 format in UTC). Note: Convert local time to UTC by subtracting the timezone offset (e.g., for Zurich CEST subtract 2 hours, for CET subtract 1 hour)', }, seriesEndFrom: { type: 'string', description: 'Filter bookings with series end date >= this date (ISO 8601 format in UTC). Note: Convert local time to UTC by subtracting the timezone offset (e.g., for Zurich CEST subtract 2 hours, for CET subtract 1 hour)', }, seriesEndTo: { type: 'string', description: 'Filter bookings with series end date <= this date (ISO 8601 format in UTC). Note: Convert local time to UTC by subtracting the timezone offset (e.g., for Zurich CEST subtract 2 hours, for CET subtract 1 hour)', }, limit: { type: 'number', description: 'Maximum number of bookings to return (default: 50)', minimum: 1, maximum: 50, }, cursorNext: { type: 'string', description: 'Pagination cursor for next page', }, cursorPrev: { type: 'string', description: 'Pagination cursor for previous page', }, }, }, }, { name: 'search_bookings_with_resources', description: 'Search for bookings and include detailed resource information (meeting rooms, desks, etc.). This performs additional API calls to enrich booking data with resource details.', inputSchema: { type: 'object', properties: { bookingId: { type: 'string', description: 'Specific booking ID to search for', }, companyId: { type: 'string', description: 'Filter bookings by company ID', }, memberId: { type: 'string', description: 'Filter bookings by member ID', }, locationId: { type: 'string', description: 'Filter bookings by location ID', }, resourceId: { type: 'string', description: 'Filter bookings by resource ID', }, seriesStartFrom: { type: 'string', description: 'Filter bookings with series start date >= this date (ISO 8601 format in UTC). Note: Convert local time to UTC by subtracting the timezone offset (e.g., for Zurich CEST subtract 2 hours, for CET subtract 1 hour)', }, seriesStartTo: { type: 'string', description: 'Filter bookings with series start date <= this date (ISO 8601 format in UTC). Note: Convert local time to UTC by subtracting the timezone offset (e.g., for Zurich CEST subtract 2 hours, for CET subtract 1 hour)', }, seriesEndFrom: { type: 'string', description: 'Filter bookings with series end date >= this date (ISO 8601 format in UTC). Note: Convert local time to UTC by subtracting the timezone offset (e.g., for Zurich CEST subtract 2 hours, for CET subtract 1 hour)', }, seriesEndTo: { type: 'string', description: 'Filter bookings with series end date <= this date (ISO 8601 format in UTC). Note: Convert local time to UTC by subtracting the timezone offset (e.g., for Zurich CEST subtract 2 hours, for CET subtract 1 hour)', }, limit: { type: 'number', description: 'Maximum number of bookings to return (default: 50)', minimum: 1, maximum: 50, }, cursorNext: { type: 'string', description: 'Pagination cursor for next page', }, cursorPrev: { type: 'string', description: 'Pagination cursor for previous page', }, }, }, }, { name: 'get_booking_details', description: 'Get detailed information about a specific booking by ID', inputSchema: { type: 'object', properties: { bookingId: { type: 'string', description: 'The booking ID to retrieve details for', }, }, required: ['bookingId'], }, }, { name: 'search_members', description: 'Search for members in OfficeRnD. Returns member information including name, email, company, location, and status.', inputSchema: { type: 'object', properties: { memberId: { type: 'string', description: 'Specific member ID to search for', }, name: { type: 'string', description: 'Filter members by name', }, email: { type: 'string', description: 'Filter members by email address', }, locationId: { type: 'string', description: 'Filter members by location ID', }, companyId: { type: 'string', description: 'Filter members by company ID', }, status: { type: 'string', enum: ['active', 'former'], description: 'Filter members by status (active or former)', }, createdAtFrom: { type: 'string', description: 'Filter members created after this date (ISO 8601 format in UTC). Note: Convert local time to UTC by subtracting the timezone offset (e.g., for Zurich CEST subtract 2 hours, for CET subtract 1 hour)', }, createdAtTo: { type: 'string', description: 'Filter members created before this date (ISO 8601 format in UTC). Note: Convert local time to UTC by subtracting the timezone offset (e.g., for Zurich CEST subtract 2 hours, for CET subtract 1 hour)', }, modifiedAtFrom: { type: 'string', description: 'Filter members modified after this date (ISO 8601 format in UTC). Note: Convert local time to UTC by subtracting the timezone offset (e.g., for Zurich CEST subtract 2 hours, for CET subtract 1 hour)', }, modifiedAtTo: { type: 'string', description: 'Filter members modified before this date (ISO 8601 format in UTC). Note: Convert local time to UTC by subtracting the timezone offset (e.g., for Zurich CEST subtract 2 hours, for CET subtract 1 hour)', }, limit: { type: 'number', description: 'Maximum number of members to return (default: 50)', minimum: 1, maximum: 50, }, cursorNext: { type: 'string', description: 'Pagination cursor for next page', }, cursorPrev: { type: 'string', description: 'Pagination cursor for previous page', }, sort: { type: 'string', description: 'Sort order for results (e.g., "name", "-createdAt" for descending)', }, }, }, }, { name: 'get_member_details', description: 'Get detailed information about a specific member by ID', inputSchema: { type: 'object', properties: { memberId: { type: 'string', description: 'The member ID to retrieve details for', }, }, required: ['memberId'], }, }, { name: 'search_resources', description: 'Search for resources (meeting rooms, desks, etc.) in OfficeRnD. Returns resource information including name, type, location, and capacity.', inputSchema: { type: 'object', properties: { resourceId: { type: 'string', description: 'Specific resource ID to search for', }, name: { type: 'string', description: 'Filter resources by name', }, locationId: { type: 'string', description: 'Filter resources by location ID', }, resourceTypeId: { type: 'string', description: 'Filter resources by resource type ID', }, limit: { type: 'number', description: 'Maximum number of resources to return (default: 50)', minimum: 1, maximum: 50, }, cursorNext: { type: 'string', description: 'Pagination cursor for next page', }, cursorPrev: { type: 'string', description: 'Pagination cursor for previous page', }, sort: { type: 'string', description: 'Sort order for results (e.g., "name", "-capacity" for descending)', }, }, }, }, { name: 'get_resource_details', description: 'Get detailed information about a specific resource by ID', inputSchema: { type: 'object', properties: { resourceId: { type: 'string', description: 'The resource ID to retrieve details for', }, }, required: ['resourceId'], }, }, { name: 'get_resource_types', description: 'Get all available resource types (e.g., meeting rooms, desks, parking spots). Resource types define categories of bookable resources.', inputSchema: { type: 'object', properties: {}, }, }, { name: 'search_companies', description: 'Search for companies in OfficeRnD. Returns company information including name, email, location, and status.', inputSchema: { type: 'object', properties: { companyId: { type: 'string', description: 'Specific company ID to search for', }, name: { type: 'string', description: 'Filter companies by name', }, email: { type: 'string', description: 'Filter companies by email address', }, locationId: { type: 'string', description: 'Filter companies by location ID', }, status: { type: 'string', enum: ['active', 'former'], description: 'Filter companies by status (active or former)', }, createdAtFrom: { type: 'string', description: 'Filter companies created after this date (ISO 8601 format in UTC). Note: Convert local time to UTC by subtracting the timezone offset (e.g., for Zurich CEST subtract 2 hours, for CET subtract 1 hour)', }, createdAtTo: { type: 'string', description: 'Filter companies created before this date (ISO 8601 format in UTC). Note: Convert local time to UTC by subtracting the timezone offset (e.g., for Zurich CEST subtract 2 hours, for CET subtract 1 hour)', }, modifiedAtFrom: { type: 'string', description: 'Filter companies modified after this date (ISO 8601 format in UTC). Note: Convert local time to UTC by subtracting the timezone offset (e.g., for Zurich CEST subtract 2 hours, for CET subtract 1 hour)', }, modifiedAtTo: { type: 'string', description: 'Filter companies modified before this date (ISO 8601 format in UTC). Note: Convert local time to UTC by subtracting the timezone offset (e.g., for Zurich CEST subtract 2 hours, for CET subtract 1 hour)', }, limit: { type: 'number', description: 'Maximum number of companies to return (default: 50)', minimum: 1, maximum: 50, }, cursorNext: { type: 'string', description: 'Pagination cursor for next page', }, cursorPrev: { type: 'string', description: 'Pagination cursor for previous page', }, sort: { type: 'string', description: 'Sort order for results (e.g., "name", "-createdAt" for descending)', }, }, }, }, { name: 'get_company_details', description: 'Get detailed information about a specific company by ID', inputSchema: { type: 'object', properties: { companyId: { type: 'string', description: 'The company ID to retrieve details for', }, }, required: ['companyId'], }, }, { name: 'create_booking', description: `Create a new booking in OfficeRnD. Requires either a member ID or company ID, along with resource, start time, and end time. ${defaultCompanyId ? `If no company is specified, the default company ID (${defaultCompanyId}) will be used. ` : ''}IMPORTANT: Times must be provided in UTC format - convert local time by subtracting the timezone offset (CEST: -2 hours, CET: -1 hour).`, inputSchema: { type: 'object', properties: { start: { type: 'string', description: 'Start date and time of the booking (ISO 8601 format in UTC). Note: Convert local time to UTC by subtracting the timezone offset (e.g., for Zurich CEST subtract 2 hours, for CET subtract 1 hour)', }, end: { type: 'string', description: 'End date and time of the booking (ISO 8601 format in UTC). Note: Convert local time to UTC by subtracting the timezone offset (e.g., for Zurich CEST subtract 2 hours, for CET subtract 1 hour)', }, resource: { type: 'string', description: 'Resource ID to book (meeting room, desk, etc.)', }, member: { type: 'string', description: 'Member ID for the booking (required if company is not provided)', }, company: { type: 'string', description: 'Company ID for the booking (required if member is not provided)', }, title: { type: 'string', description: 'Title or subject of the booking', }, description: { type: 'string', description: 'Description or notes for the booking', }, isTentative: { type: 'boolean', description: 'Whether this is a tentative booking', }, isPrivate: { type: 'boolean', description: 'Whether this is a private booking', }, isFree: { type: 'boolean', description: 'Whether this is a free booking (no charges)', }, isSilent: { type: 'boolean', description: 'Whether to skip some validations (useful for automated bookings)', }, }, required: ['start', 'end', 'resource'], }, }, { name: 'update_booking', description: 'Update an existing booking in OfficeRnD. The API requires start, end, and resource fields to be included in every update request - these will be automatically fetched from the existing booking if not provided. IMPORTANT: Times must be provided in UTC format - convert local time by subtracting the timezone offset (CEST: -2 hours, CET: -1 hour).', inputSchema: { type: 'object', properties: { bookingId: { type: 'string', description: 'The booking ID to update', }, start: { type: 'string', description: 'New start date and time (ISO 8601 format in UTC). Note: Convert local time to UTC by subtracting the timezone offset (e.g., for Zurich CEST subtract 2 hours, for CET subtract 1 hour)', }, end: { type: 'string', description: 'New end date and time (ISO 8601 format in UTC). Note: Convert local time to UTC by subtracting the timezone offset (e.g., for Zurich CEST subtract 2 hours, for CET subtract 1 hour)', }, resource: { type: 'string', description: 'New resource ID', }, member: { type: 'string', description: 'New member ID', }, company: { type: 'string', description: 'New company ID', }, title: { type: 'string', description: 'New title', }, description: { type: 'string', description: 'New description', }, isTentative: { type: 'boolean', description: 'Update tentative status', }, isPrivate: { type: 'boolean', description: 'Update private status', }, isFree: { type: 'boolean', description: 'Update free booking status', }, }, required: ['bookingId'], }, }, { name: 'cancel_booking', description: 'Cancel an existing booking in OfficeRnD', inputSchema: { type: 'object', properties: { bookingId: { type: 'string', description: 'The booking ID to cancel', }, }, required: ['bookingId'], }, }, { name: 'search_resource_rates', description: 'Search for resource rates (pricing information) in OfficeRnD. Returns rate information including price, currency, and billing period.', inputSchema: { type: 'object', properties: { resourceRateId: { type: 'string', description: 'Specific resource rate ID to search for', }, resourceId: { type: 'string', description: 'Filter rates by resource ID', }, name: { type: 'string', description: 'Filter rates by name', }, limit: { type: 'number', description: 'Maximum number of rates to return (default: 50)', minimum: 1, maximum: 50, }, cursorNext: { type: 'string', description: 'Pagination cursor for next page', }, cursorPrev: { type: 'string', description: 'Pagination cursor for previous page', }, sort: { type: 'string', description: 'Sort order for results (e.g., "name", "-price" for descending)', }, }, }, }, { name: 'get_resource_rate_details', description: 'Get detailed information about a specific resource rate by ID', inputSchema: { type: 'object', properties: { resourceRateId: { type: 'string', description: 'The resource rate ID to retrieve details for', }, }, required: ['resourceRateId'], }, }, { name: 'get_rates_for_resource', description: 'Get all pricing rates for a specific resource (meeting room, desk, etc.)', inputSchema: { type: 'object', properties: { resourceId: { type: 'string', description: 'The resource ID to get rates for', }, }, required: ['resourceId'], }, }, { name: 'get_bookable_resources_with_rates', description: 'Get all bookable resources with their pricing information. Combines resource details with active rates.', inputSchema: { type: 'object', properties: { locationId: { type: 'string', description: 'Filter by location ID', }, resourceTypeId: { type: 'string', description: 'Filter by resource type ID', }, limit: { type: 'number', description: 'Maximum number of resources to return (default: 50)', minimum: 1, maximum: 50, }, }, }, }, ]; // Create MCP server const server = new Server({ name: 'officernd-mcp-server', version: '1.2.1', }, { capabilities: { tools: {}, }, }); // Handle list tools request server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools, }; }); // Helper function to ensure client is configured function ensureClientConfigured() { if (!officeRndClient) { if (isDevelopmentMode) { throw new Error('OfficeRnD not configured. Please use configure_officernd tool first.'); } else { throw new Error('OfficeRnD not configured. Please check your Claude Desktop configuration.'); } } return officeRndClient; } // Handle tool calls server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; try { switch (name) { case 'configure_officernd': { // Only allow in development mode if (!isDevelopmentMode) { throw new Error('Configuration tool is only available in development mode. Please configure via Claude Desktop config.'); } // Type assertion for args const typedArgs = args; if (!typedArgs?.clientId || !typedArgs?.clientSecret || !typedArgs?.orgSlug) { throw new Error('clientId, clientSecret, and orgSlug are required'); } const cacheDuration = typedArgs.cacheDurationMinutes || 60; // Validate credentials by attempting to create a client and make a test API call try { const testClient = new OfficeRnDClient(typedArgs.clientId, typedArgs.clientSecret, typedArgs.orgSlug, cacheDuration); // Make a simple API call to validate credentials await testClient.getResourceTypes(); // If validation succeeds, write the .env file const envPath = path.join(path.dirname(__dirname), '.env'); let envContent = `# OfficeRnD Client Credentials OFFICERND_CLIENT_ID=${typedArgs.clientId} OFFICERND_CLIENT_SECRET=${typedArgs.clientSecret} OFFICERND_ORG_SLUG=${typedArgs.orgSlug} # Cache Configuration (optional) RESOURCE_CACHE_MINUTES=${cacheDuration} `; if (typedArgs.defaultCompanyId) { envContent += ` # Default Company (optional) OFFICERND_DEFAULT_COMPANY_ID=${typedArgs.defaultCompanyId} `; } await fs.writeFile(envPath, envContent, 'utf8'); // Update the global client officeRndClient = testClient; // Update process.env process.env.OFFICERND_CLIENT_ID = typedArgs.clientId; process.env.OFFICERND_CLIENT_SECRET = typedArgs.clientSecret; process.env.OFFICERND_ORG_SLUG = typedArgs.orgSlug; process.env.RESOURCE_CACHE_MINUTES = cacheDuration.toString(); if (typedArgs.defaultCompanyId) { process.env.OFFICERND_DEFAULT_COMPANY_ID = typedArgs.defaultCompanyId; } return { content: [ { type: 'text', text: `OfficeRnD configuration successful!\n\nOrganization: ${typedArgs.orgSlug}\nCache Duration: ${cacheDuration} minutes${typedArgs.defaultCompanyId ? `\nDefault Company ID: ${typedArgs.defaultCompanyId}` : ''}\n\nThe .env file has been updated and the client is ready to use.`, }, ], }; } catch (validationError) { const errorMsg = validationError instanceof Error ? validationError.message : String(validationError); throw new Error(`Failed to validate OfficeRnD credentials: ${errorMsg}`); } } case 'search_bookings': { // Type assertion for args const typedArgs = args; const params = { bookingId: typedArgs?.bookingId, companyId: typedArgs?.companyId, memberId: typedArgs?.memberId, locationId: typedArgs?.locationId, resourceId: typedArgs?.resourceId, seriesStartFrom: typedArgs?.seriesStartFrom, seriesStartTo: typedArgs?.seriesStartTo, seriesEndFrom: typedArgs?.seriesEndFrom, seriesEndTo: typedArgs?.seriesEndTo, limit: typedArgs?.limit || 50, cursorNext: typedArgs?.cursorNext, cursorPrev: typedArgs?.cursorPrev, }; const client = ensureClientConfigured(); const result = await client.searchBookings(params); return { content: [ { type: 'text', text: JSON.stringify(result, null, 2), }, ], }; } case 'search_bookings_with_resources': { // Type assertion for args const typedArgs = args; const params = { bookingId: typedArgs?.bookingId, companyId: typedArgs?.companyId, memberId: typedArgs?.memberId, locationId: typedArgs?.locationId, resourceId: typedArgs?.resourceId, seriesStartFrom: typedArgs?.seriesStartFrom, seriesStartTo: typedArgs?.seriesStartTo, seriesEndFrom: typedArgs?.seriesEndFrom, seriesEndTo: typedArgs?.seriesEndTo, limit: typedArgs?.limit || 50, cursorNext: typedArgs?.cursorNext, cursorPrev: typedArgs?.cursorPrev, }; const client = ensureClientConfigured(); const result = await client.searchBookingsWithResources(params); return { content: [ { type: 'text', text: JSON.stringify(result, null, 2), }, ], }; } case 'get_booking_details': { // Type assertion for args const typedArgs = args; if (!typedArgs?.bookingId) { throw new Error('bookingId is required'); } const client = ensureClientConfigured(); const result = await client.getBookingDetailsWithResource(typedArgs.bookingId); return { content: [ { type: 'text', text: JSON.stringify(result, null, 2), }, ], }; } case 'search_members': { // Type assertion for args const typedArgs = args; const params = { memberId: typedArgs?.memberId, name: typedArgs?.name, email: typedArgs?.email, locationId: typedArgs?.locationId, companyId: typedArgs?.companyId, status: typedArgs?.status, createdAtFrom: typedArgs?.createdAtFrom, createdAtTo: typedArgs?.createdAtTo, modifiedAtFrom: typedArgs?.modifiedAtFrom, modifiedAtTo: typedArgs?.modifiedAtTo, limit: typedArgs?.limit || 50, cursorNext: typedArgs?.cursorNext, cursorPrev: typedArgs?.cursorPrev, sort: typedArgs?.sort, }; const client = ensureClientConfigured(); const result = await client.searchMembers(params); return { content: [ { type: 'text', text: JSON.stringify(result, null, 2), }, ], }; } case 'get_member_details': { // Type assertion for args const typedArgs = args; if (!typedArgs?.memberId) { throw new Error('memberId is required'); } const client = ensureClientConfigured(); const result = await client.getMemberDetails(typedArgs.memberId); return { content: [ { type: 'text', text: JSON.stringify(result, null, 2), }, ], }; } case 'search_resources': { // Type assertion for args const typedArgs = args; const params = { resourceId: typedArgs?.resourceId, name: typedArgs?.name, locationId: typedArgs?.locationId, resourceTypeId: typedArgs?.resourceTypeId, limit: typedArgs?.limit || 50, cursorNext: typedArgs?.cursorNext, cursorPrev: typedArgs?.cursorPrev, sort: typedArgs?.sort, }; const client = ensureClientConfigured(); const result = await client.searchResources(params); return { content: [ { type: 'text', text: JSON.stringify(result, null, 2), }, ], }; } case 'get_resource_details': { // Type assertion for args const typedArgs = args; if (!typedArgs?.resourceId) { throw new Error('resourceId is required'); } const client = ensureClientConfigured(); const result = await client.getResourceDetails(typedArgs.resourceId); return { content: [ { type: 'text', text: JSON.stringify(result, null, 2), }, ], }; } case 'get_resource_types': { const client = ensureClientConfigured(); const result = await client.getResourceTypes(); return { content: [ { type: 'text', text: JSON.stringify(result, null, 2), }, ], }; } case 'search_companies': { // Type assertion for args const typedArgs = args; const params = { companyId: typedArgs?.companyId, name: typedArgs?.name, email: typedArgs?.email, locationId: typedArgs?.locationId, status: typedArgs?.status, createdAtFrom: typedArgs?.createdAtFrom, createdAtTo: typedArgs?.createdAtTo, modifiedAtFrom: typedArgs?.modifiedAtFrom, modifiedAtTo: typedArgs?.modifiedAtTo, limit: typedArgs?.limit || 50, cursorNext: typedArgs?.cursorNext, cursorPrev: typedArgs?.cursorPrev, sort: typedArgs?.sort, }; const client = ensureClientConfigured(); const result = await client.searchCompanies(params); return { content: [ { type: 'text', text: JSON.stringify(result, null, 2), }, ], }; } case 'get_company_details': { // Type assertion for args const typedArgs = args; if (!typedArgs?.companyId) { throw new Error('companyId is required'); } const client = ensureClientConfigured(); const result = await client.getCompanyDetails(typedArgs.companyId); return { content: [ { type: 'text', text: JSON.stringify(result, null, 2), }, ], }; } case 'create_booking': { // Type assertion for args const typedArgs = args; if (!typedArgs?.start || !typedArgs?.end || !typedArgs?.resource) { throw new Error('start, end, and resource are required fields'); } // Use default company ID if no member or company is provided const effectiveCompany = typedArgs.company || (!typedArgs.member && defaultCompanyId ? defaultCompanyId : undefined); if (!typedArgs.member && !effectiveCompany) { throw new Error('Either member or company ID is required (no default company configured)'); } const params = { start: typedArgs.start, end: typedArgs.end, resource: typedArgs.resource, member: typedArgs.member, company: effectiveCompany, title: typedArgs.title, description: typedArgs.description, isTentative: typedArgs.isTentative, isPrivate: typedArgs.isPrivate, isFree: typedArgs.isFree, }; const client = ensureClientConfigured(); const result = await client.createBooking(params, { isSilent: typedArgs.isSilent }); return { content: [ { type: 'text', text: JSON.stringify(result, null, 2), }, ], }; } case 'update_booking': { // Type assertion for args const typedArgs = args; if (!typedArgs?.bookingId) { throw new Error('bookingId is required'); } const params = { start: typedArgs.start, end: typedArgs.end, resource: typedArgs.resource, member: typedArgs.member, company: typedArgs.company, title: typedArgs.title, description: typedArgs.description, isTentative: typedArgs.isTentative, isPrivate: typedArgs.isPrivate, isFree: typedArgs.isFree, }; const client = ensureClientConfigured(); const result = await client.updateBooking(typedArgs.bookingId, params); return { content: [ { type: 'text', text: JSON.stringify(result, null, 2), }, ], }; } case 'cancel_booking': { // Type assertion for args const typedArgs = args; if (!typedArgs?.bookingId) { throw new Error('bookingId is required'); } const client = ensureClientConfigured(); const result = await client.cancelBooking(typedArgs.bookingId); return { content: [ { type: 'text', text: JSON.stringify(result, null, 2), }, ], }; } case 'search_resource_rates': { // Type assertion for args const typedArgs = args; const params = { resourceRateId: typedArgs?.resourceRateId, resourceId: typedArgs?.resourceId, name: typedArgs?.name, limit: typedArgs?.limit || 50, cursorNext: typedArgs?.cursorNext, cursorPrev: typedArgs?.cursorPrev, sort: typedArgs?.sort, }; const client = ensureClientConfigured(); const result = await client.searchResourceRates(params); return { content: [ { type: 'text', text: JSON.stringify(result, null, 2), }, ], }; } case 'get_resource_rate_details': { // Type assertion for args const typedArgs = args; if (!typedArgs?.resourceRateId) { throw new Error('resourceRateId is required'); } const client = ensureClientConfigured(); const result = await client.getResourceRateDetails(typedArgs.resourceRateId); return { content: [ { type: 'text', text: JSON.stringify(result, null, 2), }, ], }; } case 'get_rates_for_resource': { // Type assertion for args const typedArgs = args; if (!typedArgs?.resourceId) { throw new Error('resourceId is required'); } const client = ensureClientConfigured(); const result = await client.getRatesForResource(typedArgs.resourceId); return { content: [ { type: 'text', text: JSON.stringify(result, null, 2), }, ], }; } case 'get_bookable_resources_with_rates': { // Type assertion for args const typedArgs = args; const client = ensureClientConfigured(); const result = await client.getBookableResourcesWithRates({ locationId: typedArgs?.locationId, resourceTypeId: typedArgs?.resourceTypeId, limit: typedArgs?.limit, }); return { content: [ { type: 'text', text: JSON.stringify(result, null, 2), }, ], }; } default: throw new Error(`Unknown tool: ${name}`); } } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); return { content: [ { type: 'text', text: `Error: ${errorMessage}`, }, ], isError: true, }; } }); // Start the server async function main() { const transport = new StdioServerTransport(); await server.connect(transport); console.error('OfficeRnD MCP Server started'); } main().catch((error) => { console.error('Server error:', error); process.exit(1); }); //# sourceMappingURL=index.js.map