enc-charts-mcp
Version:
MCP server for Electronic Navigational Charts (ENC) data
272 lines (265 loc) • 8.01 kB
text/typescript
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 type { Tool } from '@modelcontextprotocol/sdk/types.js';
import { getChartHandler } from './handlers/getChart.js';
import { searchChartsHandler } from './handlers/searchCharts.js';
import { getChartMetadataHandler } from './handlers/getChartMetadata.js';
// import { calculateRouteHandler } from './handlers/calculateRoute.js';
import { getObjectClassesHandler } from './handlers/getObjectClasses.js';
const server = new Server(
{
name: 'enc-charts-mcp',
version: '1.0.0',
},
{
capabilities: {
tools: {},
},
}
);
const TOOLS: Tool[] = [
{
name: 'get_chart',
description: 'Retrieve chart data for a specific area by chart ID or coordinates',
inputSchema: {
type: 'object',
properties: {
chartId: {
type: 'string',
description: 'The unique identifier of the chart',
},
coordinates: {
type: 'object',
properties: {
lat: { type: 'number', minimum: -90, maximum: 90 },
lon: { type: 'number', minimum: -180, maximum: 180 },
},
required: ['lat', 'lon'],
description: 'GPS coordinates to find chart for',
},
boundingBox: {
type: 'object',
properties: {
minLat: { type: 'number' },
maxLat: { type: 'number' },
minLon: { type: 'number' },
maxLon: { type: 'number' },
},
description: 'Optional bounding box to filter chart data',
},
featureTypes: {
type: 'array',
items: { type: 'string' },
description: 'S-57 object classes to include (e.g., DEPARE, LIGHTS)',
},
depthRange: {
type: 'object',
properties: {
min: { type: 'number' },
max: { type: 'number' },
},
description: 'Filter features by depth range in meters',
},
includeNearby: {
type: 'boolean',
description: 'Include features within reasonable distance of coordinates',
},
limit: {
type: 'integer',
minimum: 1,
maximum: 1000,
default: 100,
description: 'Maximum number of features to return (default: 100, max: 1000)',
},
offset: {
type: 'integer',
minimum: 0,
default: 0,
description: 'Number of features to skip for pagination (default: 0)',
},
},
oneOf: [
{ required: ['chartId'] },
{ required: ['coordinates'] },
],
},
},
{
name: 'search_charts',
description: 'Search available charts by criteria',
inputSchema: {
type: 'object',
properties: {
query: {
type: 'string',
description: 'Search query for chart name or producer',
},
scale: {
type: 'object',
properties: {
min: { type: 'number' },
max: { type: 'number' },
},
description: 'Scale range to filter charts',
},
boundingBox: {
type: 'object',
properties: {
minLat: { type: 'number' },
maxLat: { type: 'number' },
minLon: { type: 'number' },
maxLon: { type: 'number' },
},
description: 'Geographic area to search within',
},
format: {
type: 'string',
enum: ['S-57', 'S-101'],
description: 'Chart format filter',
},
limit: {
type: 'integer',
minimum: 1,
maximum: 100,
default: 50,
description: 'Maximum number of charts to return (default: 50, max: 100)',
},
offset: {
type: 'integer',
minimum: 0,
default: 0,
description: 'Number of charts to skip for pagination (default: 0)',
},
},
},
},
{
name: 'get_chart_metadata',
description: 'Get information about a specific chart by ID or coordinates',
inputSchema: {
type: 'object',
properties: {
chartId: {
type: 'string',
description: 'The unique identifier of the chart',
},
coordinates: {
type: 'object',
properties: {
lat: { type: 'number', minimum: -90, maximum: 90 },
lon: { type: 'number', minimum: -180, maximum: 180 },
},
required: ['lat', 'lon'],
description: 'GPS coordinates to find chart for',
},
},
oneOf: [
{ required: ['chartId'] },
{ required: ['coordinates'] },
],
},
},
// {
// name: 'calculate_route',
// description: 'Calculate navigation route between waypoints',
// inputSchema: {
// type: 'object',
// properties: {
// waypoints: {
// type: 'array',
// items: {
// type: 'object',
// properties: {
// lat: { type: 'number' },
// lon: { type: 'number' },
// name: { type: 'string' },
// },
// required: ['lat', 'lon'],
// },
// minItems: 2,
// description: 'List of waypoints for the route',
// },
// checkHazards: {
// type: 'boolean',
// description: 'Check for navigation hazards along the route',
// },
// minDepth: {
// type: 'number',
// description: 'Minimum safe depth in meters for the vessel',
// },
// avoidAreas: {
// type: 'array',
// items: { type: 'string' },
// description: 'S-57 area types to avoid (e.g., RESARE, PRCARE)',
// },
// },
// required: ['waypoints'],
// },
// },
{
name: 'get_object_classes',
description: 'Get information about S-57 object classes and their representations',
inputSchema: {
type: 'object',
properties: {
category: {
type: 'string',
enum: ['navAids', 'depths', 'areas', 'infrastructure', 'natural', 'administrative', 'hazards', 'traffic', 'services', 'signals', 'other'],
description: 'Filter by category',
},
search: {
type: 'string',
description: 'Search by acronym or description',
},
includeAttributes: {
type: 'boolean',
description: 'Include standard attributes for each class',
},
},
},
},
];
server.setRequestHandler(ListToolsRequestSchema, () => ({
tools: TOOLS,
}));
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
try {
switch (name) {
case 'get_chart':
return await getChartHandler(args);
case 'search_charts':
return await searchChartsHandler(args);
case 'get_chart_metadata':
return await getChartMetadataHandler(args);
// case 'calculate_route':
// return await calculateRouteHandler(args);
case 'get_object_classes':
return await getObjectClassesHandler(args);
default:
throw new Error(`Unknown tool: ${name}`);
}
} catch (error) {
return {
content: [
{
type: 'text',
text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
},
],
};
}
});
async function main(): Promise<void> {
const transport = new StdioServerTransport();
await server.connect(transport);
// Server is running - no console output to avoid interfering with JSON-RPC
}
main().catch(() => {
// Exit silently on error to avoid corrupting JSON-RPC stream
process.exit(1);
});