#
tokens: 7489/50000 8/8 files
lines: off (toggle) GitHub
raw markdown copy
# Directory Structure

```
├── .gitignore
├── dist
│   └── index.js
├── Dockerfile
├── LICENSE
├── package-lock.json
├── package.json
├── README.md
├── smithery.yaml
├── src
│   ├── index.ts
│   └── types
│       └── modules.d.ts
└── tsconfig.json
```

# Files

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
node_modules

```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
# GIS Data Conversion MCP

[![smithery badge](https://smithery.ai/badge/@ronantakizawa/gis-dataconvertersion-mcp)](https://smithery.ai/server/@ronantakizawa/gis-dataconvertersion-mcp)

<a href="https://glama.ai/mcp/servers/@ronantakizawa/gis-dataconvertersion-mcp">
  <img width="380" height="200" src="https://glama.ai/mcp/servers/@ronantakizawa/gis-dataconvertersion-mcp/badge" />
</a>

![Copy of Untitled Design](https://github.com/user-attachments/assets/c143d9f0-710f-4164-ada9-128563746d66)

The GIS Data Conversion MCP is an MCP (Model Context Protocol) server that gives LLMs access to geographic data conversion tools.

This server uses various GIS libraries to allow LLMs to convert between different geographic data formats, coordinate systems, and spatial references.

## Features

- **Reverse Geocoding** - Convert coordinates to location information
- **WKT/GeoJSON Conversion** - Convert between Well-Known Text and GeoJSON formats
- **CSV/GeoJSON Conversion** - Transform tabular data with coordinates to GeoJSON and vice versa
- **TopoJSON/GeoJSON Conversion** - Convert between GeoJSON and TopoJSON (topology-preserving format)
- **KML/GeoJSON Conversion** - Transform KML files to GeoJSON format

## Demo
### Reverse Geocoding
https://github.com/user-attachments/assets/e21b10c3-bb67-4322-9742-efa8c7d8b332

### TopoJSON to GeoJSON
https://github.com/user-attachments/assets/a5d56051-8aed-48bb-8de1-820df8d34fe3

## Installation
To use this server with Claude Desktop, you need to configure it in the MCP settings:

**For macOS:**
Edit the file at `'~/Library/Application Support/Claude/claude_desktop_config.json'`

```
{
  "mcpServers": {
    "gis-dataconversion-mcp": {
    "command": "npx",
    "args": [
      "-y",
      "a11y-mcp-server"
    ]
   }
  }
}
```

**For Windows:**
Edit the file at `%APPDATA%\Claude\settings\claude_mcp_settings.json`

**For Linux:**
Edit the file at `~/.config/Claude/settings/claude_mcp_settings.json`
Replace `/path/to/axe-mcp-server/build/index.js` with the actual path to your compiled server file.


## Available Tools

### wkt_to_geojson
Converts Well-Known Text (WKT) to GeoJSON format.

### geojson_to_wkt
Converts GeoJSON to Well-Known Text (WKT) format.

### csv_to_geojson
Converts CSV with geographic data to GeoJSON.

**Parameters:**

- `csv` (required): CSV string to convert
- `latfield` (required): Field name for latitude
- `lonfield` (required): Field name for longitude
- `delimiter` (optional): CSV delimiter (default is comma)

### geojson_to_csv
Converts GeoJSON to CSV format.

### geojson_to_topojson
Converts GeoJSON to TopoJSON format (more compact with shared boundaries).

**Parameters:**

- `geojson` (required): GeoJSON object to convert
- `objectName` (optional): Name of the TopoJSON object to create (default: "data")
- `quantization` (optional): Quantization parameter for simplification (default: 1e4, 0 to disable)

### topojson_to_geojson
Converts TopoJSON to GeoJSON format.

**Parameters:**

- `geojson` (required): GeoJSON object to convert
- `objectName` (optional): Name of the TopoJSON object to create (default: "data")

### kml_to_geojson
Converts KML to GeoJSON format.

### geojson_to_kml
Converts GeoJSON to KML format.

### coordinates_to_location
Converts latitude/longitude coordinates to location name using reverse geocoding.


## Dependencies

- @modelcontextprotocol/sdk
- wellknown
- csv2geojson
- topojson-client
- topojson-server
- @tmcw/togeojson
- xmldom

```

--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------

```dockerfile
# Use Node 15 (specifically a stable minor version)
FROM node:15.14.0

# Create app directory
WORKDIR /app

# Copy only package files first for faster caching
COPY package*.json ./

# Install dependencies
RUN npm install --ignore-scripts

# Copy source files (including tsconfig.json etc)
COPY . .

# Build TypeScript
RUN npm run build

# Default command
ENTRYPOINT ["node", "dist/index.js"]

```

--------------------------------------------------------------------------------
/smithery.yaml:
--------------------------------------------------------------------------------

```yaml
# Smithery configuration file: https://smithery.ai/docs/deployments

startCommand:
  type: stdio
  configSchema:
    # JSON Schema defining the configuration options for the MCP.
    type: object
    required: []
    properties: {}
  commandFunction:
    # A function that produces the CLI command to start the MCP on stdio.
    |-
    config => ({ command: 'node', args: ['dist/index.js'] })

```

--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------

```json
{
    "compilerOptions": {
      "target": "ES2019",
      "module": "Node16",
      "moduleResolution": "Node16",
      "outDir": "./dist",
      "rootDir": "./src",
      "strict": true,
      "esModuleInterop": true,
      "skipLibCheck": true,
      "forceConsistentCasingInFileNames": true,
      "resolveJsonModule": true,
      "typeRoots": ["./node_modules/@types", "./src/types"],
      "noImplicitAny": false
    },
    "include": ["src/**/*"],
    "exclude": ["node_modules"]
  }
```

--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------

```json
{
  "name": "gis-dataconversion-mcp",
  "version": "1.0.3",
  "description": "An MCP server for converting GIS data in different formats",
  "type": "module",
  "main": "dist/index.js",
  "module": "dist/index.js",
  "bin": {
    "gis-mcp-server": "dist/index.js"
  },
  "engines": {
    "node": ">=15.0.0"
  },
  "access": "public",
  "files": [
    "dist"
  ],
  "scripts": {
    "build": "tsc && shx chmod +x dist/*.js",
    "prepare": "npm run build",
    "start": "node dist/index.js"
  },
  "keywords": [
    "gis",
    "geojson",
    "wkt",
    "csv",
    "topojson",
    "kml",
    "mcp",
    "claude",
    "ai"
  ],
  "author": "Ronan Takizawa",
  "license": "MIT",
  "dependencies": {
    "@modelcontextprotocol/sdk": "^0.6.0",
    "@tmcw/togeojson": "^5.6.2",
    "csv2geojson": "^5.1.1",
    "shpjs": "^3.6.3",
    "tokml": "^0.4.0",
    "topojson-client": "^3.1.0",
    "topojson-server": "^3.0.1",
    "wellknown": "^0.5.0",
    "xmldom": "^0.6.0"
  },
  "devDependencies": {
    "@types/node": "^15.14.9",
    "@types/topojson-client": "^3.1.1",
    "@types/topojson-server": "^3.0.1",
    "shx": "^0.3.3",
    "typescript": "^4.5.5"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/ronantakizawa/gis-dataconversion-mcp.git"
  },
  "bugs": {
    "url": "https://github.com/ronantakizawa/gis-dataconvertersion-mcp/issues"
  },
  "homepage": "https://github.com/ronantakizawa/gis-dataconversion-mcp#readme"
}

```

--------------------------------------------------------------------------------
/src/types/modules.d.ts:
--------------------------------------------------------------------------------

```typescript
// Type definitions for missing modules

declare module 'wellknown' {
  export function parse(wkt: string): GeoJSON.Geometry | GeoJSON.Feature | null;
  export function stringify(geojson: GeoJSON.Geometry | GeoJSON.Feature): string;
  
  const _default: {
    parse: typeof parse;
    stringify: typeof stringify;
  };
  export default _default;
}

declare module 'csv2geojson' {
  export interface Csv2GeoJSONOptions {
    latfield: string;
    lonfield: string;
    delimiter?: string;
  }
  
  export interface GeoJSONResult {
    type: string;
    features: Array<{
      type: string;
      properties: Record<string, any>;
      geometry: {
        type: string;
        coordinates: number[];
      };
    }>;
  }
  
  export function csv2geojson(
    csvString: string, 
    options: Csv2GeoJSONOptions, 
    callback: (err: Error | null, data: GeoJSONResult | null) => void
  ): void;
  
  const _default: {
    csv2geojson: typeof csv2geojson;
  };
  export default _default;
}

declare module 'quadkeytools' {
  export interface BoundingBox {
    north: number;
    south: number;
    east: number;
    west: number;
  }
  
  export interface Location {
    lat: number;
    lng: number;
  }
  
  export function locationToQuadkey(location: Location, detail: number): string;
  export function bbox(quadkey: string): BoundingBox;
  export function children(quadkey: string): string[];
  export function inside(location: Location, quadkey: string): boolean;
  export function sibling(quadkey: string, direction: 'left' | 'right' | 'up' | 'down'): string;
  
  const _default: {
    locationToQuadkey: typeof locationToQuadkey;
    bbox: typeof bbox;
    children: typeof children;
    inside: typeof inside;
    sibling: typeof sibling;
  };
  export default _default;
}
```

--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------

```typescript
#!/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';

// Define the tool response type to match what the MCP SDK expects
type ToolResponse = {
  content: Array<{
    type: 'text';
    text: string;
  }>;
};

class GisFormatServer {
  private server: Server;

  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
  private formatToolResponse(text: string): ToolResponse {
    return {
      content: [
        {
          type: 'text',
          text
        },
      ],
    };
  }

  // Helper function to calculate centroid of polygon
  private getCentroid(points: number[][]): number[] {
    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];
  }

  private 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: any) => {
      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: unknown) {
        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;
      }
    }) as any);
  }

  async wktToGeoJSON(args: any): Promise<ToolResponse> {
    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: any): Promise<ToolResponse> {
    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: any): Promise<ToolResponse> {
    const { csv, latfield, lonfield, delimiter = ',' } = args;

    if (!csv || !latfield || !lonfield) {
      throw new McpError(
        ErrorCode.InvalidParams,
        'Missing required parameters: csv, latfield, lonfield'
      );
    }

    return new Promise<ToolResponse>((resolve, reject) => {
      try {
        console.error(`[Converting] CSV to GeoJSON using lat field ${latfield} and lon field ${lonfield}`);
        
        csv2geojson.csv2geojson(csv, {
          latfield,
          lonfield,
          delimiter
        }, (err: Error | null, data: any) => {
          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: any): Promise<ToolResponse> {
    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<string>();
      geojson.features.forEach((feature: any) => {
        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: any) => {
        // Extract coordinates (handling different geometry types)
        let lat: number | string = '';
        let lon: number | string = '';
        
        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: any): Promise<ToolResponse> {
    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: Record<string, any> = {};
      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 as any, 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: any): Promise<ToolResponse> {
    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: any): Promise<ToolResponse> {
    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: any): Promise<ToolResponse> {
    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: any): Promise<ToolResponse> {
    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<ToolResponse>((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);
```