Skip to main content
Glama
UNES97

APM Terminal Operations Intelligence

by UNES97
index.ts7.13 kB
#!/usr/bin/env node import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { CallToolRequestSchema, ListToolsRequestSchema, Tool, } from '@modelcontextprotocol/sdk/types.js'; import { initializeDatabase, executeQuery, closeDatabase } from './database.js'; import { QUERIES } from './queries.js'; // Define interfaces for type safety interface VesselVisit { name: string; visitId: string; phase: string; ata: Date | null; eta: Date; etd: Date; week_number: number; month: number; year: number; totalExecutedMoves: number; totalPlannedMoves: number; } interface VesselDetails { service: string; visitId: string; name: string; phase: string; allfast: Date | null; firstlift: Date | null; firstLine: Date | null; atd: Date | null; eta: Date; etd: Date; PORTHOURS: number; ESTIMATEDMOVES: number; portstayExecuted: number | null; idleArrival: number | null; idleDeparture: number | null; } interface VesselProductivity { visitId: string; vesselName: string; totalMoves: number; workingHours: number; cmph: number; } // Define MCP tools const tools: Tool[] = [ { name: 'get_vessel_visits', description: 'Get all vessel visits with their status, planned and executed moves. Returns up to 100 most recent visits, including inbound, arrived, working, complete, departed, and closed vessels.', inputSchema: { type: 'object', properties: {}, }, }, { name: 'get_inbound_vessels_current_year', description: 'Get all inbound vessels for the current year with details including ETA, ETD, port hours, and estimated moves.', inputSchema: { type: 'object', properties: {}, }, }, { name: 'get_vessel_details', description: 'Get detailed information about a specific vessel visit including service, phase, times (allfast, first lift, first line, ATD), port hours, estimated moves, and idle times.', inputSchema: { type: 'object', properties: { visitId: { type: 'string', description: 'The visit ID of the vessel (e.g., "TNG001")', }, }, required: ['visitId'], }, }, { name: 'get_visits_today', description: 'Get all vessel visits scheduled for today at the terminal. Useful for answering questions like "what visits are at Tangier terminal today?"', inputSchema: { type: 'object', properties: {}, }, }, { name: 'get_vessel_productivity', description: 'Get vessel productivity metrics including CMPH (Container Moves Per Hour) for a specific vessel. Returns total moves, working hours, and CMPH calculation.', inputSchema: { type: 'object', properties: { vesselName: { type: 'string', description: 'The name of the vessel (partial match supported, e.g., "MAERSK")', }, }, required: ['vesselName'], }, }, ]; // Create MCP server const server = new Server( { name: 'apm-terminal-mcp-server', version: '1.0.0', }, { capabilities: { tools: {}, }, } ); // List available tools server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools, }; }); // Handle tool calls server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; try { switch (name) { case 'get_vessel_visits': { const results = await executeQuery<VesselVisit>(QUERIES.VESSEL_VISITS); return { content: [ { type: 'text', text: JSON.stringify(results, null, 2), }, ], }; } case 'get_inbound_vessels_current_year': { const results = await executeQuery(QUERIES.INBOUND_VESSELS_CURRENT_YEAR); return { content: [ { type: 'text', text: JSON.stringify(results, null, 2), }, ], }; } case 'get_vessel_details': { if (!args || typeof args !== 'object' || !('visitId' in args)) { throw new Error('visitId is required'); } const { visitId } = args as { visitId: string }; const results = await executeQuery<VesselDetails>( QUERIES.VESSEL_DETAILS_BY_ID, [visitId] ); if (results.length === 0) { return { content: [ { type: 'text', text: `No vessel found with visit ID: ${visitId}`, }, ], }; } return { content: [ { type: 'text', text: JSON.stringify(results[0], null, 2), }, ], }; } case 'get_visits_today': { const results = await executeQuery(QUERIES.VISITS_BY_TERMINAL); return { content: [ { type: 'text', text: JSON.stringify(results, null, 2), }, ], }; } case 'get_vessel_productivity': { if (!args || typeof args !== 'object' || !('vesselName' in args)) { throw new Error('vesselName is required'); } const { vesselName } = args as { vesselName: string }; const results = await executeQuery<VesselProductivity>( QUERIES.VESSEL_PRODUCTIVITY, [`%${vesselName}%`] ); if (results.length === 0) { return { content: [ { type: 'text', text: `No productivity data found for vessel: ${vesselName}`, }, ], }; } return { content: [ { type: 'text', text: JSON.stringify(results, 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 executing ${name}: ${errorMessage}`, }, ], isError: true, }; } }); // Main function to start the server async function main() { try { // Initialize database connection await initializeDatabase(); // Create transport const transport = new StdioServerTransport(); // Connect server to transport await server.connect(transport); console.error('APM Terminal MCP Server running on stdio'); } catch (error) { console.error('Failed to start server:', error); process.exit(1); } } // Handle graceful shutdown process.on('SIGINT', async () => { console.error('Shutting down...'); await closeDatabase(); process.exit(0); }); process.on('SIGTERM', async () => { console.error('Shutting down...'); await closeDatabase(); process.exit(0); }); // Start the server main();

Latest Blog Posts

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/UNES97/mcp-db-demo'

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