#!/usr/bin/env node
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { z } from 'zod';
import { EventHorizonClient, Event, Registration } from './api-client.js';
import { config, validateConfig } from './config.js';
// Initialize MCP server
const server = new McpServer({
name: 'eventhorizon-mcp',
version: '0.1.0',
});
// Lazy-initialized client (created on first use)
let client: EventHorizonClient | null = null;
function getClient(): EventHorizonClient {
if (!client) {
const errors = validateConfig();
if (errors.length > 0) {
throw new Error(`Configuration errors: ${errors.join('; ')}`);
}
client = new EventHorizonClient();
}
return client;
}
// Helper to format event for display
function formatEvent(event: Event): string {
return `Event: ${event.title} (ID: ${event.id})
Description: ${event.description}
Location: ${event.location}
Start: ${event.start_time}
End: ${event.end_time}
Capacity: ${event.capacity}
Organizer: ${event.organizer.username}
Registered: ${event.is_registered ? 'Yes' : 'No'}`;
}
// Helper to format registration for display
function formatRegistration(reg: Registration): string {
const eventInfo = typeof reg.event === 'object' ? reg.event.title : `Event ID: ${reg.event}`;
const userInfo = typeof reg.user === 'object' ? reg.user.username : `User ID: ${reg.user}`;
return `Registration (ID: ${reg.id})
Event: ${eventInfo}
User: ${userInfo}
Status: ${reg.status}
Registered at: ${reg.registered_at}`;
}
// ============================================================================
// Event Tools
// ============================================================================
server.tool(
'list_events',
'List all available events. Optionally filter by search term or location.',
{
search: z.string().optional().describe('Search term to filter events by title or description'),
location: z.string().optional().describe('Filter events by location')
},
async ({ search, location }) => {
try {
const apiClient = getClient();
const events = await apiClient.listEvents({ search, location });
if (events.length === 0) {
return {
content: [{ type: 'text', text: 'No events found matching your criteria.' }]
};
}
const formatted = events.map(formatEvent).join('\n\n---\n\n');
return {
content: [{ type: 'text', text: `Found ${events.length} event(s):\n\n${formatted}` }]
};
} catch (error) {
return {
content: [{ type: 'text', text: `Error: ${error instanceof Error ? error.message : String(error)}` }],
isError: true
};
}
}
);
server.tool(
'get_event',
'Get detailed information about a specific event by its ID.',
{
event_id: z.number().describe('The ID of the event to retrieve')
},
async ({ event_id }) => {
try {
const apiClient = getClient();
const event = await apiClient.getEvent(event_id);
return {
content: [{ type: 'text', text: formatEvent(event) }]
};
} catch (error) {
return {
content: [{ type: 'text', text: `Error: ${error instanceof Error ? error.message : String(error)}` }],
isError: true
};
}
}
);
server.tool(
'create_event',
'Create a new event. Requires title, description, start/end times, location, and capacity.',
{
title: z.string().describe('Title of the event'),
description: z.string().describe('Description of the event'),
start_time: z.string().describe('Start time in ISO 8601 format (e.g., 2024-12-25T10:00:00Z)'),
end_time: z.string().describe('End time in ISO 8601 format (e.g., 2024-12-25T18:00:00Z)'),
location: z.string().describe('Location of the event'),
capacity: z.number().describe('Maximum number of attendees')
},
async ({ title, description, start_time, end_time, location, capacity }) => {
try {
const apiClient = getClient();
const event = await apiClient.createEvent({
title,
description,
start_time,
end_time,
location,
capacity
});
return {
content: [{ type: 'text', text: `Event created successfully!\n\n${formatEvent(event)}` }]
};
} catch (error) {
return {
content: [{ type: 'text', text: `Error: ${error instanceof Error ? error.message : String(error)}` }],
isError: true
};
}
}
);
server.tool(
'update_event',
'Update an existing event. Only provide the fields you want to change.',
{
event_id: z.number().describe('The ID of the event to update'),
title: z.string().optional().describe('New title for the event'),
description: z.string().optional().describe('New description for the event'),
start_time: z.string().optional().describe('New start time in ISO 8601 format'),
end_time: z.string().optional().describe('New end time in ISO 8601 format'),
location: z.string().optional().describe('New location for the event'),
capacity: z.number().optional().describe('New capacity for the event')
},
async ({ event_id, title, description, start_time, end_time, location, capacity }) => {
try {
const apiClient = getClient();
const updateData: Record<string, unknown> = {};
if (title !== undefined) updateData.title = title;
if (description !== undefined) updateData.description = description;
if (start_time !== undefined) updateData.start_time = start_time;
if (end_time !== undefined) updateData.end_time = end_time;
if (location !== undefined) updateData.location = location;
if (capacity !== undefined) updateData.capacity = capacity;
const event = await apiClient.updateEvent(event_id, updateData);
return {
content: [{ type: 'text', text: `Event updated successfully!\n\n${formatEvent(event)}` }]
};
} catch (error) {
return {
content: [{ type: 'text', text: `Error: ${error instanceof Error ? error.message : String(error)}` }],
isError: true
};
}
}
);
server.tool(
'delete_event',
'Delete an event. Only the organizer can delete their events.',
{
event_id: z.number().describe('The ID of the event to delete')
},
async ({ event_id }) => {
try {
const apiClient = getClient();
await apiClient.deleteEvent(event_id);
return {
content: [{ type: 'text', text: `Event ${event_id} deleted successfully.` }]
};
} catch (error) {
return {
content: [{ type: 'text', text: `Error: ${error instanceof Error ? error.message : String(error)}` }],
isError: true
};
}
}
);
// ============================================================================
// Registration Tools
// ============================================================================
server.tool(
'register_for_event',
'Register the current user for an event.',
{
event_id: z.number().describe('The ID of the event to register for'),
answers: z.record(z.unknown()).optional().describe('Answers to registration form questions (if any)')
},
async ({ event_id, answers }) => {
try {
const apiClient = getClient();
const registration = await apiClient.registerForEvent(event_id, answers || {});
return {
content: [{ type: 'text', text: `Successfully registered!\n\n${formatRegistration(registration)}` }]
};
} catch (error) {
return {
content: [{ type: 'text', text: `Error: ${error instanceof Error ? error.message : String(error)}` }],
isError: true
};
}
}
);
server.tool(
'unregister_from_event',
'Unregister the current user from an event.',
{
event_id: z.number().describe('The ID of the event to unregister from')
},
async ({ event_id }) => {
try {
const apiClient = getClient();
await apiClient.unregisterFromEvent(event_id);
return {
content: [{ type: 'text', text: `Successfully unregistered from event ${event_id}.` }]
};
} catch (error) {
return {
content: [{ type: 'text', text: `Error: ${error instanceof Error ? error.message : String(error)}` }],
isError: true
};
}
}
);
server.tool(
'get_event_registrations',
'Get all registrations for an event. Only available to the event organizer.',
{
event_id: z.number().describe('The ID of the event')
},
async ({ event_id }) => {
try {
const apiClient = getClient();
const registrations = await apiClient.getEventRegistrations(event_id);
if (registrations.length === 0) {
return {
content: [{ type: 'text', text: 'No registrations found for this event.' }]
};
}
const formatted = registrations.map(formatRegistration).join('\n\n---\n\n');
return {
content: [{ type: 'text', text: `Found ${registrations.length} registration(s):\n\n${formatted}` }]
};
} catch (error) {
return {
content: [{ type: 'text', text: `Error: ${error instanceof Error ? error.message : String(error)}` }],
isError: true
};
}
}
);
server.tool(
'manage_registration',
'Manage a registration (approve, waitlist, or cancel). Only available to the event organizer.',
{
registration_id: z.number().describe('The ID of the registration to manage'),
action: z.enum(['approve', 'waitlist', 'cancel']).describe('Action to take on the registration')
},
async ({ registration_id, action }) => {
try {
const apiClient = getClient();
const registration = await apiClient.manageRegistration(registration_id, action);
return {
content: [{ type: 'text', text: `Registration ${action}d successfully!\n\n${formatRegistration(registration)}` }]
};
} catch (error) {
return {
content: [{ type: 'text', text: `Error: ${error instanceof Error ? error.message : String(error)}` }],
isError: true
};
}
}
);
// ============================================================================
// User Tools
// ============================================================================
server.tool(
'get_my_profile',
'Get the current user\'s profile information.',
{},
async () => {
try {
const apiClient = getClient();
const profile = await apiClient.getCurrentProfile();
return {
content: [{
type: 'text',
text: `User Profile:
Username: ${profile.username}
Email: ${profile.email}
Name: ${profile.first_name} ${profile.last_name}
Bio: ${profile.profile.bio || 'Not set'}
Location: ${profile.profile.location || 'Not set'}
Phone: ${profile.profile.phone_number || 'Not set'}
Joined: ${profile.date_joined}`
}]
};
} catch (error) {
return {
content: [{ type: 'text', text: `Error: ${error instanceof Error ? error.message : String(error)}` }],
isError: true
};
}
}
);
server.tool(
'get_my_registrations',
'Get all events the current user is registered for.',
{},
async () => {
try {
const apiClient = getClient();
const registrations = await apiClient.getUserRegistrations();
if (registrations.length === 0) {
return {
content: [{ type: 'text', text: 'You are not registered for any events.' }]
};
}
const formatted = registrations.map(formatRegistration).join('\n\n---\n\n');
return {
content: [{ type: 'text', text: `You have ${registrations.length} registration(s):\n\n${formatted}` }]
};
} catch (error) {
return {
content: [{ type: 'text', text: `Error: ${error instanceof Error ? error.message : String(error)}` }],
isError: true
};
}
}
);
server.tool(
'get_my_hosted_events',
'Get all events organized by the current user.',
{},
async () => {
try {
const apiClient = getClient();
const events = await apiClient.getUserHostedEvents();
if (events.length === 0) {
return {
content: [{ type: 'text', text: 'You are not organizing any events.' }]
};
}
const formatted = events.map(formatEvent).join('\n\n---\n\n');
return {
content: [{ type: 'text', text: `You are organizing ${events.length} event(s):\n\n${formatted}` }]
};
} catch (error) {
return {
content: [{ type: 'text', text: `Error: ${error instanceof Error ? error.message : String(error)}` }],
isError: true
};
}
}
);
server.tool(
'health_check',
'Check the connection to the EventHorizon API and verify authentication.',
{},
async () => {
try {
const apiClient = getClient();
const isHealthy = await apiClient.healthCheck();
if (isHealthy) {
return {
content: [{ type: 'text', text: `EventHorizon API is healthy and authenticated.\nConnected to: ${apiClient.getBaseURL()}` }]
};
} else {
return {
content: [{ type: 'text', text: 'EventHorizon API connection failed.' }],
isError: true
};
}
} catch (error) {
return {
content: [{ type: 'text', text: `Error: ${error instanceof Error ? error.message : String(error)}` }],
isError: true
};
}
}
);
// ============================================================================
// Start Server
// ============================================================================
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error('EventHorizon MCP Server started');
}
main().catch((error) => {
console.error('Failed to start server:', error);
process.exit(1);
});