Skip to main content
Glama
index.ts10.9 kB
#!/usr/bin/env node /** * MCP Server for Network School Luma Events */ import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { CallToolRequestSchema, ListToolsRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema, } from '@modelcontextprotocol/sdk/types.js'; import { LumaClient } from './luma-client.js'; import { filterTodaysEvents, filterUpcomingEvents, searchEvents, formatEventsList, } from './formatters.js'; import { listWikiResources, readWikiResource, isWikiUri, searchWiki } from './wiki.js'; // Initialize Luma client const lumaClient = new LumaClient(); // Create MCP server const server = new Server( { name: 'network-school-events', version: '1.0.0', }, { capabilities: { tools: {}, resources: {}, }, } ); // Register list_tools handler server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ { name: 'get_todays_events', description: 'Get all Network School events happening today', inputSchema: { type: 'object', properties: {}, }, }, { name: 'get_upcoming_events', description: 'Get Network School events happening in the next N days', inputSchema: { type: 'object', properties: { days: { type: 'number', description: 'Number of days to look ahead (default: 7)', default: 7, }, }, }, }, { name: 'search_events', description: 'Search Network School events by name or description', inputSchema: { type: 'object', properties: { query: { type: 'string', description: 'Search query string', }, }, required: ['query'], }, }, { name: 'register_for_event', description: 'Register for a Network School event using the event ID from the event listing', inputSchema: { type: 'object', properties: { event_id: { type: 'string', description: 'The event API ID (e.g., evt-xxx) from the event listing', }, name: { type: 'string', description: 'Full name for registration', }, email: { type: 'string', description: 'Email address for registration', }, phone_number: { type: 'string', description: 'Phone number (optional)', default: '', }, timezone: { type: 'string', description: 'Timezone (default: Asia/Kuala_Lumpur)', default: 'Asia/Kuala_Lumpur', }, }, required: ['event_id', 'name', 'email'], }, }, { name: 'search_wiki', description: 'Search the Network School wiki for information about visas, internet, food, getting started, and more', inputSchema: { type: 'object', properties: { query: { type: 'string', description: 'Search query (e.g., "wifi password", "visa", "breakfast", "sim card")', }, }, required: ['query'], }, }, ], }; }); // Register list_resources handler server.setRequestHandler(ListResourcesRequestSchema, async () => { const wikiResources = await listWikiResources(); return { resources: wikiResources, }; }); // Register read_resource handler server.setRequestHandler(ReadResourceRequestSchema, async (request) => { const uri = request.params.uri; if (!isWikiUri(uri)) { throw new Error(`Unknown resource URI: ${uri}`); } try { const content = await readWikiResource(uri); return { contents: [ { uri, mimeType: 'text/markdown', text: content, }, ], }; } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; throw new Error(`Failed to read resource: ${errorMessage}`); } }); // Register call_tool handler server.setRequestHandler(CallToolRequestSchema, async (request) => { try { const { name, arguments: args } = request.params; switch (name) { case 'get_todays_events': { const response = await lumaClient.fetchEvents(); const todaysEvents = filterTodaysEvents(response.entries); const formatted = formatEventsList( todaysEvents, 'No events scheduled for today.' ); return { content: [ { type: 'text', text: formatted, }, ], }; } case 'get_upcoming_events': { const days = (args?.days as number) || 7; if (typeof days !== 'number' || days < 1) { return { content: [ { type: 'text', text: 'Error: days parameter must be a positive number', }, ], isError: true, }; } const response = await lumaClient.fetchEvents(); const upcomingEvents = filterUpcomingEvents(response.entries, days); const formatted = formatEventsList( upcomingEvents, `No events scheduled in the next ${days} day${days !== 1 ? 's' : ''}.` ); return { content: [ { type: 'text', text: formatted, }, ], }; } case 'search_events': { const query = args?.query as string; if (!query || typeof query !== 'string') { return { content: [ { type: 'text', text: 'Error: query parameter is required and must be a string', }, ], isError: true, }; } const response = await lumaClient.fetchEvents(); const matchingEvents = searchEvents(response.entries, query); const formatted = formatEventsList( matchingEvents, `No events found matching "${query}".` ); return { content: [ { type: 'text', text: formatted, }, ], }; } case 'search_wiki': { const query = args?.query as string; if (!query || typeof query !== 'string') { return { content: [ { type: 'text', text: 'Error: query parameter is required and must be a string', }, ], isError: true, }; } const results = await searchWiki(query); if (results.length === 0) { return { content: [ { type: 'text', text: `No wiki pages found matching "${query}".`, }, ], }; } // Format the results let response = `Found ${results.length} wiki page${results.length !== 1 ? 's' : ''} matching "${query}":\n\n`; results.forEach((result, index) => { const title = result.page .split('-') .map(word => word.charAt(0).toUpperCase() + word.slice(1)) .join(' '); response += `**${index + 1}. ${title}** (${result.matches} match${result.matches !== 1 ? 'es' : ''})\n\n`; response += result.content; response += '\n\n---\n\n'; }); return { content: [ { type: 'text', text: response.trim(), }, ], }; } case 'register_for_event': { const eventId = args?.event_id as string; const name = args?.name as string; const email = args?.email as string; const phoneNumber = (args?.phone_number as string) || ''; const timezone = (args?.timezone as string) || 'Asia/Kuala_Lumpur'; if (!eventId || typeof eventId !== 'string') { return { content: [ { type: 'text', text: 'Error: event_id parameter is required and must be a string', }, ], isError: true, }; } if (!name || typeof name !== 'string') { return { content: [ { type: 'text', text: 'Error: name parameter is required and must be a string', }, ], isError: true, }; } if (!email || typeof email !== 'string') { return { content: [ { type: 'text', text: 'Error: email parameter is required and must be a string', }, ], isError: true, }; } const registration = await lumaClient.registerForEvent( eventId, name, email, phoneNumber, timezone ); const successMessage = `✅ Successfully registered for the event! Registration Details: - Name: ${name} - Email: ${registration.email} - Status: ${registration.approval_status} - Ticket Key: ${registration.ticket_key} - RSVP ID: ${registration.rsvp_api_id} ${registration.event_tickets.length > 0 ? `Ticket Type: ${registration.event_tickets[0].event_ticket_type_info.name} (${registration.event_tickets[0].event_ticket_type_info.type})` : ''} You should receive a confirmation email at ${registration.email}.`; return { content: [ { type: 'text', text: successMessage, }, ], }; } default: return { content: [ { type: 'text', text: `Unknown tool: ${name}`, }, ], isError: true, }; } } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred'; 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('Network School Events MCP Server running on stdio'); } main().catch((error) => { console.error('Fatal error in main():', error); process.exit(1); });

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/tHeMaskedMan981/ns-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server