Skip to main content
Glama

GIS Data Conversion MCP

index.js26.6 kB
#!/usr/bin/env node import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { CallToolRequestSchema, ErrorCode, ListToolsRequestSchema, McpError, } from '@modelcontextprotocol/sdk/types.js'; // Import GIS conversion libraries import wellknown from 'wellknown'; import csv2geojson from 'csv2geojson'; // Import TopoJSON libraries import * as topojsonClient from 'topojson-client'; import * as topojsonServer from 'topojson-server'; // Import KML/KMZ conversion libraries import { kml as kmlToGeoJSON } from '@tmcw/togeojson'; import tokml from 'tokml'; import { DOMParser } from 'xmldom'; // Import https for making requests (Node.js built-in) import * as https from 'https'; class GisFormatServer { constructor() { console.error('[Setup] Initializing GIS Format Conversion MCP server...'); this.server = new Server({ name: 'gis-format-conversion-server', version: '0.1.0', }, { capabilities: { tools: {}, }, }); this.setupToolHandlers(); this.server.onerror = (error) => console.error('[Error]', error); process.on('SIGINT', async () => { await this.server.close(); process.exit(0); }); } // Define a consistent return type for all tool methods formatToolResponse(text) { return { content: [ { type: 'text', text }, ], }; } // Helper function to calculate centroid of polygon getCentroid(points) { const n = points.length; let sumX = 0; let sumY = 0; for (let i = 0; i < n; i++) { sumX += points[i][0]; sumY += points[i][1]; } return [sumX / n, sumY / n]; } setupToolHandlers() { this.server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: [ { name: 'wkt_to_geojson', description: 'Convert Well-Known Text (WKT) to GeoJSON format', inputSchema: { type: 'object', properties: { wkt: { type: 'string', description: 'Well-Known Text (WKT) string to convert', }, }, required: ['wkt'], }, }, { name: 'geojson_to_wkt', description: 'Convert GeoJSON to Well-Known Text (WKT) format', inputSchema: { type: 'object', properties: { geojson: { type: 'object', description: 'GeoJSON object to convert', }, }, required: ['geojson'], }, }, { name: 'csv_to_geojson', description: 'Convert CSV with geographic data to GeoJSON', inputSchema: { type: 'object', properties: { csv: { type: 'string', description: 'CSV string to convert', }, latfield: { type: 'string', description: 'Field name for latitude', }, lonfield: { type: 'string', description: 'Field name for longitude', }, delimiter: { type: 'string', description: 'CSV delimiter (default is comma)', default: ',', }, }, required: ['csv', 'latfield', 'lonfield'], }, }, { name: 'geojson_to_csv', description: 'Convert GeoJSON to CSV format', inputSchema: { type: 'object', properties: { geojson: { type: 'object', description: 'GeoJSON object to convert', }, includeAllProperties: { type: 'boolean', description: 'Include all feature properties in the CSV', default: true, }, }, required: ['geojson'], }, }, { name: 'geojson_to_topojson', description: 'Convert GeoJSON to TopoJSON format (more compact with shared boundaries)', inputSchema: { type: 'object', properties: { geojson: { type: 'object', description: 'GeoJSON object to convert', }, objectName: { type: 'string', description: 'Name of the TopoJSON object to create', default: 'data', }, quantization: { type: 'number', description: 'Quantization parameter for simplification (0 to disable)', default: 1e4, }, }, required: ['geojson'], }, }, { name: 'topojson_to_geojson', description: 'Convert TopoJSON to GeoJSON format', inputSchema: { type: 'object', properties: { topojson: { type: 'object', description: 'TopoJSON object to convert', }, objectName: { type: 'string', description: 'Name of the TopoJSON object to convert (if not provided, first object is used)', }, }, required: ['topojson'], }, }, { name: 'kml_to_geojson', description: 'Convert KML to GeoJSON format', inputSchema: { type: 'object', properties: { kml: { type: 'string', description: 'KML content to convert', }, }, required: ['kml'], }, }, { name: 'geojson_to_kml', description: 'Convert GeoJSON to KML format', inputSchema: { type: 'object', properties: { geojson: { type: 'object', description: 'GeoJSON object to convert', }, documentName: { type: 'string', description: 'Name for the KML document', default: 'GeoJSON Conversion', }, documentDescription: { type: 'string', description: 'Description for the KML document', default: 'Converted from GeoJSON by GIS Format Conversion MCP', }, nameProperty: { type: 'string', description: 'Property name in GeoJSON to use as KML name', default: 'name', }, descriptionProperty: { type: 'string', description: 'Property name in GeoJSON to use as KML description', default: 'description', } }, required: ['geojson'], }, }, { name: 'coordinates_to_location', description: 'Convert latitude/longitude coordinates to location name using reverse geocoding', inputSchema: { type: 'object', properties: { latitude: { type: 'number', description: 'Latitude coordinate', }, longitude: { type: 'number', description: 'Longitude coordinate', } }, required: ['latitude', 'longitude'], }, } ], })); // Using the 'as any' type assertion to bypass the TypeScript error this.server.setRequestHandler(CallToolRequestSchema, (async (request) => { try { switch (request.params.name) { case 'wkt_to_geojson': return await this.wktToGeoJSON(request.params.arguments); case 'geojson_to_wkt': return await this.geoJSONToWKT(request.params.arguments); case 'csv_to_geojson': return await this.csvToGeoJSON(request.params.arguments); case 'geojson_to_csv': return await this.geojsonToCSV(request.params.arguments); case 'geojson_to_topojson': return await this.geojsonToTopoJSON(request.params.arguments); case 'topojson_to_geojson': return await this.topojsonToGeoJSON(request.params.arguments); case 'kml_to_geojson': return await this.kmlToGeoJSON(request.params.arguments); case 'geojson_to_kml': return await this.geojsonToKML(request.params.arguments); case 'coordinates_to_location': return await this.coordinatesToLocation(request.params.arguments); default: throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${request.params.name}`); } } catch (error) { if (error instanceof Error) { console.error('[Error] Failed to process request:', error); throw new McpError(ErrorCode.InternalError, `Failed to process request: ${error.message}`); } throw error; } })); } async wktToGeoJSON(args) { const { wkt } = args; if (!wkt) { throw new McpError(ErrorCode.InvalidParams, 'Missing required parameter: wkt'); } try { console.error(`[Converting] WKT to GeoJSON: "${wkt.substring(0, 50)}${wkt.length > 50 ? '...' : ''}"`); const geojson = wellknown.parse(wkt); if (!geojson) { throw new Error('Failed to parse WKT string'); } return this.formatToolResponse(JSON.stringify(geojson, null, 2)); } catch (error) { console.error('[Error] WKT to GeoJSON conversion failed:', error); throw new McpError(ErrorCode.InternalError, `WKT to GeoJSON conversion failed: ${error instanceof Error ? error.message : String(error)}`); } } async geoJSONToWKT(args) { const { geojson } = args; if (!geojson) { throw new McpError(ErrorCode.InvalidParams, 'Missing required parameter: geojson'); } try { console.error(`[Converting] GeoJSON to WKT: ${JSON.stringify(geojson).substring(0, 50)}...`); const wkt = wellknown.stringify(geojson); if (!wkt) { throw new Error('Failed to convert GeoJSON to WKT'); } return this.formatToolResponse(wkt); } catch (error) { console.error('[Error] GeoJSON to WKT conversion failed:', error); throw new McpError(ErrorCode.InternalError, `GeoJSON to WKT conversion failed: ${error instanceof Error ? error.message : String(error)}`); } } async csvToGeoJSON(args) { const { csv, latfield, lonfield, delimiter = ',' } = args; if (!csv || !latfield || !lonfield) { throw new McpError(ErrorCode.InvalidParams, 'Missing required parameters: csv, latfield, lonfield'); } return new Promise((resolve, reject) => { try { console.error(`[Converting] CSV to GeoJSON using lat field ${latfield} and lon field ${lonfield}`); csv2geojson.csv2geojson(csv, { latfield, lonfield, delimiter }, (err, data) => { if (err) { console.error('[Error] CSV to GeoJSON conversion failed:', err); reject(new McpError(ErrorCode.InternalError, `CSV to GeoJSON conversion failed: ${err.message}`)); return; } resolve(this.formatToolResponse(JSON.stringify(data, null, 2))); }); } catch (error) { console.error('[Error] CSV to GeoJSON conversion failed:', error); reject(new McpError(ErrorCode.InternalError, `CSV to GeoJSON conversion failed: ${error instanceof Error ? error.message : String(error)}`)); } }); } async geojsonToCSV(args) { const { geojson, includeAllProperties = true } = args; if (!geojson || !geojson.features) { throw new McpError(ErrorCode.InvalidParams, 'Invalid GeoJSON: missing features array'); } try { console.error('[Converting] GeoJSON to CSV'); // Extract all unique property keys const properties = new Set(); geojson.features.forEach((feature) => { if (feature.properties) { Object.keys(feature.properties).forEach(key => properties.add(key)); } }); // Always include geometry columns const headers = ['latitude', 'longitude', ...Array.from(properties)]; // Generate CSV rows let csvRows = [headers.join(',')]; geojson.features.forEach((feature) => { // Extract coordinates (handling different geometry types) let lat = ''; let lon = ''; if (feature.geometry.type === 'Point') { [lon, lat] = feature.geometry.coordinates; } else if (feature.geometry.type === 'Polygon') { const centroid = this.getCentroid(feature.geometry.coordinates[0]); lon = centroid[0]; lat = centroid[1]; } else if (feature.geometry.type === 'LineString' || feature.geometry.type === 'MultiPoint') { // Use first coordinate for these types [lon, lat] = feature.geometry.coordinates[0]; } else if (feature.geometry.type === 'MultiPolygon') { // Use the centroid of the first polygon const centroid = this.getCentroid(feature.geometry.coordinates[0][0]); lon = centroid[0]; lat = centroid[1]; } else if (feature.geometry.type === 'MultiLineString') { // Use the first point of the first linestring [lon, lat] = feature.geometry.coordinates[0][0]; } else if (feature.geometry.type === 'GeometryCollection') { // Use the first geometry if (feature.geometry.geometries && feature.geometry.geometries.length > 0) { const firstGeom = feature.geometry.geometries[0]; if (firstGeom.type === 'Point') { [lon, lat] = firstGeom.coordinates; } else if (firstGeom.type === 'Polygon') { const centroid = this.getCentroid(firstGeom.coordinates[0]); lon = centroid[0]; lat = centroid[1]; } } } // Convert coordinates to strings for CSV const latStr = String(lat); const lonStr = String(lon); // Build row with all properties const row = [latStr, lonStr]; properties.forEach(prop => { const value = feature.properties && feature.properties[prop] !== undefined ? feature.properties[prop] : ''; // Make sure strings with commas are properly quoted row.push(typeof value === 'string' ? `"${value.replace(/"/g, '""')}"` : value); }); csvRows.push(row.join(',')); }); return this.formatToolResponse(csvRows.join('\n')); } catch (error) { console.error('[Error] GeoJSON to CSV conversion failed:', error); throw new McpError(ErrorCode.InternalError, `GeoJSON to CSV conversion failed: ${error instanceof Error ? error.message : String(error)}`); } } async geojsonToTopoJSON(args) { const { geojson, objectName = 'data', quantization = 1e4 } = args; if (!geojson) { throw new McpError(ErrorCode.InvalidParams, 'Missing required parameter: geojson'); } try { console.error('[Converting] GeoJSON to TopoJSON'); // Create a topology object from the GeoJSON const objectsMap = {}; objectsMap[objectName] = geojson; // Generate the topology const topology = topojsonServer.topology(objectsMap); // Apply quantization if specified let result = topology; if (quantization > 0) { // Use type assertion to work around TypeScript type incompatibility result = topojsonClient.quantize(topology, quantization); } return this.formatToolResponse(JSON.stringify(result, null, 2)); } catch (error) { console.error('[Error] GeoJSON to TopoJSON conversion failed:', error); throw new McpError(ErrorCode.InternalError, `GeoJSON to TopoJSON conversion failed: ${error instanceof Error ? error.message : String(error)}`); } } async topojsonToGeoJSON(args) { const { topojson, objectName } = args; if (!topojson) { throw new McpError(ErrorCode.InvalidParams, 'Missing required parameter: topojson'); } try { console.error('[Converting] TopoJSON to GeoJSON'); // Determine which object to convert let objName = objectName; // If no object name provided, use the first object in the topology if (!objName && topojson.objects) { objName = Object.keys(topojson.objects)[0]; } if (!objName || !topojson.objects || !topojson.objects[objName]) { throw new Error('No valid object found in TopoJSON'); } // Convert TopoJSON to GeoJSON const geojson = topojsonClient.feature(topojson, topojson.objects[objName]); return this.formatToolResponse(JSON.stringify(geojson, null, 2)); } catch (error) { console.error('[Error] TopoJSON to GeoJSON conversion failed:', error); throw new McpError(ErrorCode.InternalError, `TopoJSON to GeoJSON conversion failed: ${error instanceof Error ? error.message : String(error)}`); } } async kmlToGeoJSON(args) { const { kml } = args; if (!kml) { throw new McpError(ErrorCode.InvalidParams, 'Missing required parameter: kml'); } try { console.error('[Converting] KML to GeoJSON'); // Parse KML string to XML DOM const parser = new DOMParser(); const kmlDoc = parser.parseFromString(kml, 'text/xml'); // Convert KML to GeoJSON const geojson = kmlToGeoJSON(kmlDoc); return this.formatToolResponse(JSON.stringify(geojson, null, 2)); } catch (error) { console.error('[Error] KML to GeoJSON conversion failed:', error); throw new McpError(ErrorCode.InternalError, `KML to GeoJSON conversion failed: ${error instanceof Error ? error.message : String(error)}`); } } async geojsonToKML(args) { const { geojson, documentName = 'GeoJSON Conversion', documentDescription = 'Converted from GeoJSON by GIS Format Conversion MCP', nameProperty = 'name', descriptionProperty = 'description' } = args; if (!geojson) { throw new McpError(ErrorCode.InvalidParams, 'Missing required parameter: geojson'); } try { console.error('[Converting] GeoJSON to KML'); // Set up options for tokml const options = { documentName: documentName, documentDescription: documentDescription, name: nameProperty, description: descriptionProperty }; // Convert GeoJSON to KML using tokml const kml = tokml(geojson, options); return this.formatToolResponse(kml); } catch (error) { console.error('[Error] GeoJSON to KML conversion failed:', error); throw new McpError(ErrorCode.InternalError, `GeoJSON to KML conversion failed: ${error instanceof Error ? error.message : String(error)}`); } } async coordinatesToLocation(args) { const { latitude, longitude } = args; if (latitude === undefined || longitude === undefined) { throw new McpError(ErrorCode.InvalidParams, 'Missing required parameters: latitude, longitude'); } try { console.error(`[Converting] Coordinates (${latitude}, ${longitude}) to location name`); // Using Nominatim OSM service (free, but has usage limitations) const url = `https://nominatim.openstreetmap.org/reverse?format=json&lat=${latitude}&lon=${longitude}`; return new Promise((resolve, reject) => { // Use the imported https module directly const req = https.request(url, { method: 'GET', headers: { 'User-Agent': 'GisFormatMcpServer/1.0' } }, (res) => { if (res.statusCode !== 200) { reject(new Error(`Geocoding service returned ${res.statusCode}: ${res.statusMessage}`)); return; } let data = ''; res.on('data', (chunk) => { data += chunk; }); res.on('end', () => { try { const parsedData = JSON.parse(data); // Always return detailed format const result = { displayName: parsedData.display_name, address: parsedData.address, type: parsedData.type, osmId: parsedData.osm_id, osmType: parsedData.osm_type, category: parsedData.category }; resolve(this.formatToolResponse(JSON.stringify(result, null, 2))); } catch (error) { reject(new Error(`Failed to parse geocoding response: ${error instanceof Error ? error.message : String(error)}`)); } }); }); req.on('error', (error) => { reject(new Error(`Geocoding request failed: ${error.message}`)); }); req.end(); }); } catch (error) { console.error('[Error] Coordinates to location conversion failed:', error); throw new McpError(ErrorCode.InternalError, `Coordinates to location conversion failed: ${error instanceof Error ? error.message : String(error)}`); } } async run() { const transport = new StdioServerTransport(); await this.server.connect(transport); console.error('GIS Format Conversion MCP server running on stdio'); } } const server = new GisFormatServer(); server.run().catch(console.error);

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/ronantakizawa/gis-dataconvertersion-mcp'

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