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
JavaScript
#!/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