#
tokens: 9883/50000 8/8 files
lines: on (toggle) GitHub
raw markdown copy reset
# 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:
--------------------------------------------------------------------------------

```
1 | node_modules
2 | 
```

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

```markdown
  1 | # GIS Data Conversion MCP
  2 | 
  3 | [![smithery badge](https://smithery.ai/badge/@ronantakizawa/gis-dataconvertersion-mcp)](https://smithery.ai/server/@ronantakizawa/gis-dataconvertersion-mcp)
  4 | 
  5 | <a href="https://glama.ai/mcp/servers/@ronantakizawa/gis-dataconvertersion-mcp">
  6 |   <img width="380" height="200" src="https://glama.ai/mcp/servers/@ronantakizawa/gis-dataconvertersion-mcp/badge" />
  7 | </a>
  8 | 
  9 | ![Copy of Untitled Design](https://github.com/user-attachments/assets/c143d9f0-710f-4164-ada9-128563746d66)
 10 | 
 11 | The GIS Data Conversion MCP is an MCP (Model Context Protocol) server that gives LLMs access to geographic data conversion tools.
 12 | 
 13 | This server uses various GIS libraries to allow LLMs to convert between different geographic data formats, coordinate systems, and spatial references.
 14 | 
 15 | ## Features
 16 | 
 17 | - **Reverse Geocoding** - Convert coordinates to location information
 18 | - **WKT/GeoJSON Conversion** - Convert between Well-Known Text and GeoJSON formats
 19 | - **CSV/GeoJSON Conversion** - Transform tabular data with coordinates to GeoJSON and vice versa
 20 | - **TopoJSON/GeoJSON Conversion** - Convert between GeoJSON and TopoJSON (topology-preserving format)
 21 | - **KML/GeoJSON Conversion** - Transform KML files to GeoJSON format
 22 | 
 23 | ## Demo
 24 | ### Reverse Geocoding
 25 | https://github.com/user-attachments/assets/e21b10c3-bb67-4322-9742-efa8c7d8b332
 26 | 
 27 | ### TopoJSON to GeoJSON
 28 | https://github.com/user-attachments/assets/a5d56051-8aed-48bb-8de1-820df8d34fe3
 29 | 
 30 | ## Installation
 31 | To use this server with Claude Desktop, you need to configure it in the MCP settings:
 32 | 
 33 | **For macOS:**
 34 | Edit the file at `'~/Library/Application Support/Claude/claude_desktop_config.json'`
 35 | 
 36 | ```
 37 | {
 38 |   "mcpServers": {
 39 |     "gis-dataconversion-mcp": {
 40 |     "command": "npx",
 41 |     "args": [
 42 |       "-y",
 43 |       "a11y-mcp-server"
 44 |     ]
 45 |    }
 46 |   }
 47 | }
 48 | ```
 49 | 
 50 | **For Windows:**
 51 | Edit the file at `%APPDATA%\Claude\settings\claude_mcp_settings.json`
 52 | 
 53 | **For Linux:**
 54 | Edit the file at `~/.config/Claude/settings/claude_mcp_settings.json`
 55 | Replace `/path/to/axe-mcp-server/build/index.js` with the actual path to your compiled server file.
 56 | 
 57 | 
 58 | ## Available Tools
 59 | 
 60 | ### wkt_to_geojson
 61 | Converts Well-Known Text (WKT) to GeoJSON format.
 62 | 
 63 | ### geojson_to_wkt
 64 | Converts GeoJSON to Well-Known Text (WKT) format.
 65 | 
 66 | ### csv_to_geojson
 67 | Converts CSV with geographic data to GeoJSON.
 68 | 
 69 | **Parameters:**
 70 | 
 71 | - `csv` (required): CSV string to convert
 72 | - `latfield` (required): Field name for latitude
 73 | - `lonfield` (required): Field name for longitude
 74 | - `delimiter` (optional): CSV delimiter (default is comma)
 75 | 
 76 | ### geojson_to_csv
 77 | Converts GeoJSON to CSV format.
 78 | 
 79 | ### geojson_to_topojson
 80 | Converts GeoJSON to TopoJSON format (more compact with shared boundaries).
 81 | 
 82 | **Parameters:**
 83 | 
 84 | - `geojson` (required): GeoJSON object to convert
 85 | - `objectName` (optional): Name of the TopoJSON object to create (default: "data")
 86 | - `quantization` (optional): Quantization parameter for simplification (default: 1e4, 0 to disable)
 87 | 
 88 | ### topojson_to_geojson
 89 | Converts TopoJSON to GeoJSON format.
 90 | 
 91 | **Parameters:**
 92 | 
 93 | - `geojson` (required): GeoJSON object to convert
 94 | - `objectName` (optional): Name of the TopoJSON object to create (default: "data")
 95 | 
 96 | ### kml_to_geojson
 97 | Converts KML to GeoJSON format.
 98 | 
 99 | ### geojson_to_kml
100 | Converts GeoJSON to KML format.
101 | 
102 | ### coordinates_to_location
103 | Converts latitude/longitude coordinates to location name using reverse geocoding.
104 | 
105 | 
106 | ## Dependencies
107 | 
108 | - @modelcontextprotocol/sdk
109 | - wellknown
110 | - csv2geojson
111 | - topojson-client
112 | - topojson-server
113 | - @tmcw/togeojson
114 | - xmldom
115 | 
```

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

```dockerfile
 1 | # Use Node 15 (specifically a stable minor version)
 2 | FROM node:15.14.0
 3 | 
 4 | # Create app directory
 5 | WORKDIR /app
 6 | 
 7 | # Copy only package files first for faster caching
 8 | COPY package*.json ./
 9 | 
10 | # Install dependencies
11 | RUN npm install --ignore-scripts
12 | 
13 | # Copy source files (including tsconfig.json etc)
14 | COPY . .
15 | 
16 | # Build TypeScript
17 | RUN npm run build
18 | 
19 | # Default command
20 | ENTRYPOINT ["node", "dist/index.js"]
21 | 
```

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

```yaml
 1 | # Smithery configuration file: https://smithery.ai/docs/deployments
 2 | 
 3 | startCommand:
 4 |   type: stdio
 5 |   configSchema:
 6 |     # JSON Schema defining the configuration options for the MCP.
 7 |     type: object
 8 |     required: []
 9 |     properties: {}
10 |   commandFunction:
11 |     # A function that produces the CLI command to start the MCP on stdio.
12 |     |-
13 |     config => ({ command: 'node', args: ['dist/index.js'] })
14 | 
```

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

```json
 1 | {
 2 |     "compilerOptions": {
 3 |       "target": "ES2019",
 4 |       "module": "Node16",
 5 |       "moduleResolution": "Node16",
 6 |       "outDir": "./dist",
 7 |       "rootDir": "./src",
 8 |       "strict": true,
 9 |       "esModuleInterop": true,
10 |       "skipLibCheck": true,
11 |       "forceConsistentCasingInFileNames": true,
12 |       "resolveJsonModule": true,
13 |       "typeRoots": ["./node_modules/@types", "./src/types"],
14 |       "noImplicitAny": false
15 |     },
16 |     "include": ["src/**/*"],
17 |     "exclude": ["node_modules"]
18 |   }
```

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

```json
 1 | {
 2 |   "name": "gis-dataconversion-mcp",
 3 |   "version": "1.0.3",
 4 |   "description": "An MCP server for converting GIS data in different formats",
 5 |   "type": "module",
 6 |   "main": "dist/index.js",
 7 |   "module": "dist/index.js",
 8 |   "bin": {
 9 |     "gis-mcp-server": "dist/index.js"
10 |   },
11 |   "engines": {
12 |     "node": ">=15.0.0"
13 |   },
14 |   "access": "public",
15 |   "files": [
16 |     "dist"
17 |   ],
18 |   "scripts": {
19 |     "build": "tsc && shx chmod +x dist/*.js",
20 |     "prepare": "npm run build",
21 |     "start": "node dist/index.js"
22 |   },
23 |   "keywords": [
24 |     "gis",
25 |     "geojson",
26 |     "wkt",
27 |     "csv",
28 |     "topojson",
29 |     "kml",
30 |     "mcp",
31 |     "claude",
32 |     "ai"
33 |   ],
34 |   "author": "Ronan Takizawa",
35 |   "license": "MIT",
36 |   "dependencies": {
37 |     "@modelcontextprotocol/sdk": "^0.6.0",
38 |     "@tmcw/togeojson": "^5.6.2",
39 |     "csv2geojson": "^5.1.1",
40 |     "shpjs": "^3.6.3",
41 |     "tokml": "^0.4.0",
42 |     "topojson-client": "^3.1.0",
43 |     "topojson-server": "^3.0.1",
44 |     "wellknown": "^0.5.0",
45 |     "xmldom": "^0.6.0"
46 |   },
47 |   "devDependencies": {
48 |     "@types/node": "^15.14.9",
49 |     "@types/topojson-client": "^3.1.1",
50 |     "@types/topojson-server": "^3.0.1",
51 |     "shx": "^0.3.3",
52 |     "typescript": "^4.5.5"
53 |   },
54 |   "repository": {
55 |     "type": "git",
56 |     "url": "git+https://github.com/ronantakizawa/gis-dataconversion-mcp.git"
57 |   },
58 |   "bugs": {
59 |     "url": "https://github.com/ronantakizawa/gis-dataconvertersion-mcp/issues"
60 |   },
61 |   "homepage": "https://github.com/ronantakizawa/gis-dataconversion-mcp#readme"
62 | }
63 | 
```

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

```typescript
 1 | // Type definitions for missing modules
 2 | 
 3 | declare module 'wellknown' {
 4 |   export function parse(wkt: string): GeoJSON.Geometry | GeoJSON.Feature | null;
 5 |   export function stringify(geojson: GeoJSON.Geometry | GeoJSON.Feature): string;
 6 |   
 7 |   const _default: {
 8 |     parse: typeof parse;
 9 |     stringify: typeof stringify;
10 |   };
11 |   export default _default;
12 | }
13 | 
14 | declare module 'csv2geojson' {
15 |   export interface Csv2GeoJSONOptions {
16 |     latfield: string;
17 |     lonfield: string;
18 |     delimiter?: string;
19 |   }
20 |   
21 |   export interface GeoJSONResult {
22 |     type: string;
23 |     features: Array<{
24 |       type: string;
25 |       properties: Record<string, any>;
26 |       geometry: {
27 |         type: string;
28 |         coordinates: number[];
29 |       };
30 |     }>;
31 |   }
32 |   
33 |   export function csv2geojson(
34 |     csvString: string, 
35 |     options: Csv2GeoJSONOptions, 
36 |     callback: (err: Error | null, data: GeoJSONResult | null) => void
37 |   ): void;
38 |   
39 |   const _default: {
40 |     csv2geojson: typeof csv2geojson;
41 |   };
42 |   export default _default;
43 | }
44 | 
45 | declare module 'quadkeytools' {
46 |   export interface BoundingBox {
47 |     north: number;
48 |     south: number;
49 |     east: number;
50 |     west: number;
51 |   }
52 |   
53 |   export interface Location {
54 |     lat: number;
55 |     lng: number;
56 |   }
57 |   
58 |   export function locationToQuadkey(location: Location, detail: number): string;
59 |   export function bbox(quadkey: string): BoundingBox;
60 |   export function children(quadkey: string): string[];
61 |   export function inside(location: Location, quadkey: string): boolean;
62 |   export function sibling(quadkey: string, direction: 'left' | 'right' | 'up' | 'down'): string;
63 |   
64 |   const _default: {
65 |     locationToQuadkey: typeof locationToQuadkey;
66 |     bbox: typeof bbox;
67 |     children: typeof children;
68 |     inside: typeof inside;
69 |     sibling: typeof sibling;
70 |   };
71 |   export default _default;
72 | }
```

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

```typescript
  1 | #!/usr/bin/env node
  2 | import { Server } from '@modelcontextprotocol/sdk/server/index.js';
  3 | import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
  4 | import {
  5 |   CallToolRequestSchema,
  6 |   ErrorCode,
  7 |   ListToolsRequestSchema,
  8 |   McpError,
  9 | } from '@modelcontextprotocol/sdk/types.js';
 10 | 
 11 | // Import GIS conversion libraries
 12 | import wellknown from 'wellknown';
 13 | import csv2geojson from 'csv2geojson';
 14 | 
 15 | // Import TopoJSON libraries
 16 | import * as topojsonClient from 'topojson-client';
 17 | import * as topojsonServer from 'topojson-server';
 18 | 
 19 | // Import KML/KMZ conversion libraries
 20 | import { kml as kmlToGeoJSON } from '@tmcw/togeojson';
 21 | import tokml from 'tokml';
 22 | import { DOMParser } from 'xmldom';
 23 | 
 24 | // Import https for making requests (Node.js built-in)
 25 | import * as https from 'https';
 26 | 
 27 | // Define the tool response type to match what the MCP SDK expects
 28 | type ToolResponse = {
 29 |   content: Array<{
 30 |     type: 'text';
 31 |     text: string;
 32 |   }>;
 33 | };
 34 | 
 35 | class GisFormatServer {
 36 |   private server: Server;
 37 | 
 38 |   constructor() {
 39 |     console.error('[Setup] Initializing GIS Format Conversion MCP server...');
 40 |     
 41 |     this.server = new Server(
 42 |       {
 43 |         name: 'gis-format-conversion-server',
 44 |         version: '0.1.0',
 45 |       },
 46 |       {
 47 |         capabilities: {
 48 |           tools: {},
 49 |         },
 50 |       }
 51 |     );
 52 | 
 53 |     this.setupToolHandlers();
 54 |     
 55 |     this.server.onerror = (error) => console.error('[Error]', error);
 56 |     process.on('SIGINT', async () => {
 57 |       await this.server.close();
 58 |       process.exit(0);
 59 |     });
 60 |   }
 61 | 
 62 |   // Define a consistent return type for all tool methods
 63 |   private formatToolResponse(text: string): ToolResponse {
 64 |     return {
 65 |       content: [
 66 |         {
 67 |           type: 'text',
 68 |           text
 69 |         },
 70 |       ],
 71 |     };
 72 |   }
 73 | 
 74 |   // Helper function to calculate centroid of polygon
 75 |   private getCentroid(points: number[][]): number[] {
 76 |     const n = points.length;
 77 |     let sumX = 0;
 78 |     let sumY = 0;
 79 |     
 80 |     for (let i = 0; i < n; i++) {
 81 |       sumX += points[i][0];
 82 |       sumY += points[i][1];
 83 |     }
 84 |     
 85 |     return [sumX / n, sumY / n];
 86 |   }
 87 | 
 88 |   private setupToolHandlers() {
 89 |     this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
 90 |       tools: [
 91 |         {
 92 |           name: 'wkt_to_geojson',
 93 |           description: 'Convert Well-Known Text (WKT) to GeoJSON format',
 94 |           inputSchema: {
 95 |             type: 'object',
 96 |             properties: {
 97 |               wkt: {
 98 |                 type: 'string',
 99 |                 description: 'Well-Known Text (WKT) string to convert',
100 |               },
101 |             },
102 |             required: ['wkt'],
103 |           },
104 |         },
105 |         {
106 |           name: 'geojson_to_wkt',
107 |           description: 'Convert GeoJSON to Well-Known Text (WKT) format',
108 |           inputSchema: {
109 |             type: 'object',
110 |             properties: {
111 |               geojson: {
112 |                 type: 'object',
113 |                 description: 'GeoJSON object to convert',
114 |               },
115 |             },
116 |             required: ['geojson'],
117 |           },
118 |         },
119 |         {
120 |           name: 'csv_to_geojson',
121 |           description: 'Convert CSV with geographic data to GeoJSON',
122 |           inputSchema: {
123 |             type: 'object',
124 |             properties: {
125 |               csv: {
126 |                 type: 'string',
127 |                 description: 'CSV string to convert',
128 |               },
129 |               latfield: {
130 |                 type: 'string',
131 |                 description: 'Field name for latitude',
132 |               },
133 |               lonfield: {
134 |                 type: 'string',
135 |                 description: 'Field name for longitude',
136 |               },
137 |               delimiter: {
138 |                 type: 'string',
139 |                 description: 'CSV delimiter (default is comma)',
140 |                 default: ',',
141 |               },
142 |             },
143 |             required: ['csv', 'latfield', 'lonfield'],
144 |           },
145 |         },
146 |         {
147 |           name: 'geojson_to_csv',
148 |           description: 'Convert GeoJSON to CSV format',
149 |           inputSchema: {
150 |             type: 'object',
151 |             properties: {
152 |               geojson: {
153 |                 type: 'object',
154 |                 description: 'GeoJSON object to convert',
155 |               },
156 |               includeAllProperties: {
157 |                 type: 'boolean',
158 |                 description: 'Include all feature properties in the CSV',
159 |                 default: true,
160 |               },
161 |             },
162 |             required: ['geojson'],
163 |           },
164 |         },
165 |         {
166 |           name: 'geojson_to_topojson',
167 |           description: 'Convert GeoJSON to TopoJSON format (more compact with shared boundaries)',
168 |           inputSchema: {
169 |             type: 'object',
170 |             properties: {
171 |               geojson: {
172 |                 type: 'object',
173 |                 description: 'GeoJSON object to convert',
174 |               },
175 |               objectName: {
176 |                 type: 'string',
177 |                 description: 'Name of the TopoJSON object to create',
178 |                 default: 'data',
179 |               },
180 |               quantization: {
181 |                 type: 'number',
182 |                 description: 'Quantization parameter for simplification (0 to disable)',
183 |                 default: 1e4,
184 |               },
185 |             },
186 |             required: ['geojson'],
187 |           },
188 |         },
189 |         {
190 |           name: 'topojson_to_geojson',
191 |           description: 'Convert TopoJSON to GeoJSON format',
192 |           inputSchema: {
193 |             type: 'object',
194 |             properties: {
195 |               topojson: {
196 |                 type: 'object',
197 |                 description: 'TopoJSON object to convert',
198 |               },
199 |               objectName: {
200 |                 type: 'string',
201 |                 description: 'Name of the TopoJSON object to convert (if not provided, first object is used)',
202 |               },
203 |             },
204 |             required: ['topojson'],
205 |           },
206 |         },
207 |         {
208 |           name: 'kml_to_geojson',
209 |           description: 'Convert KML to GeoJSON format',
210 |           inputSchema: {
211 |             type: 'object',
212 |             properties: {
213 |               kml: {
214 |                 type: 'string',
215 |                 description: 'KML content to convert',
216 |               },
217 |             },
218 |             required: ['kml'],
219 |           },
220 |         },
221 |         {
222 |           name: 'geojson_to_kml',
223 |           description: 'Convert GeoJSON to KML format',
224 |           inputSchema: {
225 |             type: 'object',
226 |             properties: {
227 |               geojson: {
228 |                 type: 'object',
229 |                 description: 'GeoJSON object to convert',
230 |               },
231 |               documentName: {
232 |                 type: 'string',
233 |                 description: 'Name for the KML document',
234 |                 default: 'GeoJSON Conversion',
235 |               },
236 |               documentDescription: {
237 |                 type: 'string',
238 |                 description: 'Description for the KML document',
239 |                 default: 'Converted from GeoJSON by GIS Format Conversion MCP',
240 |               },
241 |               nameProperty: {
242 |                 type: 'string',
243 |                 description: 'Property name in GeoJSON to use as KML name',
244 |                 default: 'name',
245 |               },
246 |               descriptionProperty: {
247 |                 type: 'string',
248 |                 description: 'Property name in GeoJSON to use as KML description',
249 |                 default: 'description',
250 |               }
251 |             },
252 |             required: ['geojson'],
253 |           },
254 |         },
255 |         {
256 |           name: 'coordinates_to_location',
257 |           description: 'Convert latitude/longitude coordinates to location name using reverse geocoding',
258 |           inputSchema: {
259 |             type: 'object',
260 |             properties: {
261 |               latitude: {
262 |                 type: 'number',
263 |                 description: 'Latitude coordinate',
264 |               },
265 |               longitude: {
266 |                 type: 'number',
267 |                 description: 'Longitude coordinate',
268 |               }
269 |             },
270 |             required: ['latitude', 'longitude'],
271 |           },
272 |         }
273 |       ],
274 |     }));
275 | 
276 |     // Using the 'as any' type assertion to bypass the TypeScript error
277 |     this.server.setRequestHandler(CallToolRequestSchema, (async (request: any) => {
278 |       try {
279 |         switch (request.params.name) {
280 |           case 'wkt_to_geojson':
281 |             return await this.wktToGeoJSON(request.params.arguments);
282 |           case 'geojson_to_wkt':
283 |             return await this.geoJSONToWKT(request.params.arguments);
284 |           case 'csv_to_geojson':
285 |             return await this.csvToGeoJSON(request.params.arguments);
286 |           case 'geojson_to_csv':
287 |             return await this.geojsonToCSV(request.params.arguments);
288 |           case 'geojson_to_topojson':
289 |             return await this.geojsonToTopoJSON(request.params.arguments);
290 |           case 'topojson_to_geojson':
291 |             return await this.topojsonToGeoJSON(request.params.arguments);
292 |           case 'kml_to_geojson':
293 |             return await this.kmlToGeoJSON(request.params.arguments);
294 |           case 'geojson_to_kml':
295 |             return await this.geojsonToKML(request.params.arguments);
296 |           case 'coordinates_to_location':
297 |             return await this.coordinatesToLocation(request.params.arguments);
298 |           default:
299 |             throw new McpError(
300 |               ErrorCode.MethodNotFound,
301 |               `Unknown tool: ${request.params.name}`
302 |             );
303 |         }
304 |       } catch (error: unknown) {
305 |         if (error instanceof Error) {
306 |           console.error('[Error] Failed to process request:', error);
307 |           throw new McpError(
308 |             ErrorCode.InternalError,
309 |             `Failed to process request: ${error.message}`
310 |           );
311 |         }
312 |         throw error;
313 |       }
314 |     }) as any);
315 |   }
316 | 
317 |   async wktToGeoJSON(args: any): Promise<ToolResponse> {
318 |     const { wkt } = args;
319 | 
320 |     if (!wkt) {
321 |       throw new McpError(
322 |         ErrorCode.InvalidParams,
323 |         'Missing required parameter: wkt'
324 |       );
325 |     }
326 | 
327 |     try {
328 |       console.error(`[Converting] WKT to GeoJSON: "${wkt.substring(0, 50)}${wkt.length > 50 ? '...' : ''}"`);
329 |       
330 |       const geojson = wellknown.parse(wkt);
331 |       
332 |       if (!geojson) {
333 |         throw new Error('Failed to parse WKT string');
334 |       }
335 |       
336 |       return this.formatToolResponse(JSON.stringify(geojson, null, 2));
337 |     } catch (error) {
338 |       console.error('[Error] WKT to GeoJSON conversion failed:', error);
339 |       throw new McpError(
340 |         ErrorCode.InternalError,
341 |         `WKT to GeoJSON conversion failed: ${error instanceof Error ? error.message : String(error)}`
342 |       );
343 |     }
344 |   }
345 | 
346 |   async geoJSONToWKT(args: any): Promise<ToolResponse> {
347 |     const { geojson } = args;
348 | 
349 |     if (!geojson) {
350 |       throw new McpError(
351 |         ErrorCode.InvalidParams,
352 |         'Missing required parameter: geojson'
353 |       );
354 |     }
355 | 
356 |     try {
357 |       console.error(`[Converting] GeoJSON to WKT: ${JSON.stringify(geojson).substring(0, 50)}...`);
358 |       
359 |       const wkt = wellknown.stringify(geojson);
360 |       
361 |       if (!wkt) {
362 |         throw new Error('Failed to convert GeoJSON to WKT');
363 |       }
364 |       
365 |       return this.formatToolResponse(wkt);
366 |     } catch (error) {
367 |       console.error('[Error] GeoJSON to WKT conversion failed:', error);
368 |       throw new McpError(
369 |         ErrorCode.InternalError,
370 |         `GeoJSON to WKT conversion failed: ${error instanceof Error ? error.message : String(error)}`
371 |       );
372 |     }
373 |   }
374 | 
375 |   async csvToGeoJSON(args: any): Promise<ToolResponse> {
376 |     const { csv, latfield, lonfield, delimiter = ',' } = args;
377 | 
378 |     if (!csv || !latfield || !lonfield) {
379 |       throw new McpError(
380 |         ErrorCode.InvalidParams,
381 |         'Missing required parameters: csv, latfield, lonfield'
382 |       );
383 |     }
384 | 
385 |     return new Promise<ToolResponse>((resolve, reject) => {
386 |       try {
387 |         console.error(`[Converting] CSV to GeoJSON using lat field ${latfield} and lon field ${lonfield}`);
388 |         
389 |         csv2geojson.csv2geojson(csv, {
390 |           latfield,
391 |           lonfield,
392 |           delimiter
393 |         }, (err: Error | null, data: any) => {
394 |           if (err) {
395 |             console.error('[Error] CSV to GeoJSON conversion failed:', err);
396 |             reject(new McpError(
397 |               ErrorCode.InternalError,
398 |               `CSV to GeoJSON conversion failed: ${err.message}`
399 |             ));
400 |             return;
401 |           }
402 |           
403 |           resolve(this.formatToolResponse(JSON.stringify(data, null, 2)));
404 |         });
405 |       } catch (error) {
406 |         console.error('[Error] CSV to GeoJSON conversion failed:', error);
407 |         reject(new McpError(
408 |           ErrorCode.InternalError,
409 |           `CSV to GeoJSON conversion failed: ${error instanceof Error ? error.message : String(error)}`
410 |         ));
411 |       }
412 |     });
413 |   }
414 | 
415 |   async geojsonToCSV(args: any): Promise<ToolResponse> {
416 |     const { geojson, includeAllProperties = true } = args;
417 | 
418 |     if (!geojson || !geojson.features) {
419 |       throw new McpError(
420 |         ErrorCode.InvalidParams,
421 |         'Invalid GeoJSON: missing features array'
422 |       );
423 |     }
424 | 
425 |     try {
426 |       console.error('[Converting] GeoJSON to CSV');
427 |       
428 |       // Extract all unique property keys
429 |       const properties = new Set<string>();
430 |       geojson.features.forEach((feature: any) => {
431 |         if (feature.properties) {
432 |           Object.keys(feature.properties).forEach(key => properties.add(key));
433 |         }
434 |       });
435 |       
436 |       // Always include geometry columns
437 |       const headers = ['latitude', 'longitude', ...Array.from(properties)];
438 |       
439 |       // Generate CSV rows
440 |       let csvRows = [headers.join(',')];
441 |       
442 |       geojson.features.forEach((feature: any) => {
443 |         // Extract coordinates (handling different geometry types)
444 |         let lat: number | string = '';
445 |         let lon: number | string = '';
446 |         
447 |         if (feature.geometry.type === 'Point') {
448 |           [lon, lat] = feature.geometry.coordinates;
449 |         } else if (feature.geometry.type === 'Polygon') {
450 |           const centroid = this.getCentroid(feature.geometry.coordinates[0]);
451 |           lon = centroid[0];
452 |           lat = centroid[1];
453 |         } else if (feature.geometry.type === 'LineString' || feature.geometry.type === 'MultiPoint') {
454 |           // Use first coordinate for these types
455 |           [lon, lat] = feature.geometry.coordinates[0];
456 |         } else if (feature.geometry.type === 'MultiPolygon') {
457 |           // Use the centroid of the first polygon
458 |           const centroid = this.getCentroid(feature.geometry.coordinates[0][0]);
459 |           lon = centroid[0];
460 |           lat = centroid[1];
461 |         } else if (feature.geometry.type === 'MultiLineString') {
462 |           // Use the first point of the first linestring
463 |           [lon, lat] = feature.geometry.coordinates[0][0];
464 |         } else if (feature.geometry.type === 'GeometryCollection') {
465 |           // Use the first geometry
466 |           if (feature.geometry.geometries && feature.geometry.geometries.length > 0) {
467 |             const firstGeom = feature.geometry.geometries[0];
468 |             if (firstGeom.type === 'Point') {
469 |               [lon, lat] = firstGeom.coordinates;
470 |             } else if (firstGeom.type === 'Polygon') {
471 |               const centroid = this.getCentroid(firstGeom.coordinates[0]);
472 |               lon = centroid[0];
473 |               lat = centroid[1];
474 |             }
475 |           }
476 |         }
477 |         
478 |         // Convert coordinates to strings for CSV
479 |         const latStr = String(lat);
480 |         const lonStr = String(lon);
481 |         
482 |         // Build row with all properties
483 |         const row = [latStr, lonStr];
484 |         properties.forEach(prop => {
485 |           const value = feature.properties && feature.properties[prop] !== undefined ? 
486 |             feature.properties[prop] : '';
487 |           // Make sure strings with commas are properly quoted
488 |           row.push(typeof value === 'string' ? `"${value.replace(/"/g, '""')}"` : value);
489 |         });
490 |         
491 |         csvRows.push(row.join(','));
492 |       });
493 |       
494 |       return this.formatToolResponse(csvRows.join('\n'));
495 |     } catch (error) {
496 |       console.error('[Error] GeoJSON to CSV conversion failed:', error);
497 |       throw new McpError(
498 |         ErrorCode.InternalError,
499 |         `GeoJSON to CSV conversion failed: ${error instanceof Error ? error.message : String(error)}`
500 |       );
501 |     }
502 |   }
503 | 
504 |   async geojsonToTopoJSON(args: any): Promise<ToolResponse> {
505 |     const { geojson, objectName = 'data', quantization = 1e4 } = args;
506 |     
507 |     if (!geojson) {
508 |       throw new McpError(
509 |         ErrorCode.InvalidParams,
510 |         'Missing required parameter: geojson'
511 |       );
512 |     }
513 |     
514 |     try {
515 |       console.error('[Converting] GeoJSON to TopoJSON');
516 |       
517 |       // Create a topology object from the GeoJSON
518 |       const objectsMap: Record<string, any> = {};
519 |       objectsMap[objectName] = geojson;
520 |       
521 |       // Generate the topology
522 |       const topology = topojsonServer.topology(objectsMap);
523 |       
524 |       // Apply quantization if specified
525 |       let result = topology;
526 |       if (quantization > 0) {
527 |         // Use type assertion to work around TypeScript type incompatibility
528 |         result = topojsonClient.quantize(topology as any, quantization);
529 |       }
530 |       
531 |       return this.formatToolResponse(JSON.stringify(result, null, 2));
532 |     } catch (error) {
533 |       console.error('[Error] GeoJSON to TopoJSON conversion failed:', error);
534 |       throw new McpError(
535 |         ErrorCode.InternalError,
536 |         `GeoJSON to TopoJSON conversion failed: ${error instanceof Error ? error.message : String(error)}`
537 |       );
538 |     }
539 |   }
540 |   
541 |   async topojsonToGeoJSON(args: any): Promise<ToolResponse> {
542 |     const { topojson, objectName } = args;
543 |     
544 |     if (!topojson) {
545 |       throw new McpError(
546 |         ErrorCode.InvalidParams,
547 |         'Missing required parameter: topojson'
548 |       );
549 |     }
550 |     
551 |     try {
552 |       console.error('[Converting] TopoJSON to GeoJSON');
553 |       
554 |       // Determine which object to convert
555 |       let objName = objectName;
556 |       
557 |       // If no object name provided, use the first object in the topology
558 |       if (!objName && topojson.objects) {
559 |         objName = Object.keys(topojson.objects)[0];
560 |       }
561 |       
562 |       if (!objName || !topojson.objects || !topojson.objects[objName]) {
563 |         throw new Error('No valid object found in TopoJSON');
564 |       }
565 |       
566 |       // Convert TopoJSON to GeoJSON
567 |       const geojson = topojsonClient.feature(topojson, topojson.objects[objName]);
568 |       
569 |       return this.formatToolResponse(JSON.stringify(geojson, null, 2));
570 |     } catch (error) {
571 |       console.error('[Error] TopoJSON to GeoJSON conversion failed:', error);
572 |       throw new McpError(
573 |         ErrorCode.InternalError,
574 |         `TopoJSON to GeoJSON conversion failed: ${error instanceof Error ? error.message : String(error)}`
575 |       );
576 |     }
577 |   }
578 |   
579 |   async kmlToGeoJSON(args: any): Promise<ToolResponse> {
580 |     const { kml } = args;
581 |     
582 |     if (!kml) {
583 |       throw new McpError(
584 |         ErrorCode.InvalidParams,
585 |         'Missing required parameter: kml'
586 |       );
587 |     }
588 |     
589 |     try {
590 |       console.error('[Converting] KML to GeoJSON');
591 |       
592 |       // Parse KML string to XML DOM
593 |       const parser = new DOMParser();
594 |       const kmlDoc = parser.parseFromString(kml, 'text/xml');
595 |       
596 |       // Convert KML to GeoJSON
597 |       const geojson = kmlToGeoJSON(kmlDoc);
598 |       
599 |       return this.formatToolResponse(JSON.stringify(geojson, null, 2));
600 |     } catch (error) {
601 |       console.error('[Error] KML to GeoJSON conversion failed:', error);
602 |       throw new McpError(
603 |         ErrorCode.InternalError,
604 |         `KML to GeoJSON conversion failed: ${error instanceof Error ? error.message : String(error)}`
605 |       );
606 |     }
607 |   }
608 | 
609 |   async geojsonToKML(args: any): Promise<ToolResponse> {
610 |     const { 
611 |       geojson, 
612 |       documentName = 'GeoJSON Conversion', 
613 |       documentDescription = 'Converted from GeoJSON by GIS Format Conversion MCP', 
614 |       nameProperty = 'name',
615 |       descriptionProperty = 'description'
616 |     } = args;
617 |     
618 |     if (!geojson) {
619 |       throw new McpError(
620 |         ErrorCode.InvalidParams,
621 |         'Missing required parameter: geojson'
622 |       );
623 |     }
624 |     
625 |     try {
626 |       console.error('[Converting] GeoJSON to KML');
627 |       
628 |       // Set up options for tokml
629 |       const options = {
630 |         documentName: documentName,
631 |         documentDescription: documentDescription,
632 |         name: nameProperty,
633 |         description: descriptionProperty
634 |       };
635 |       
636 |       // Convert GeoJSON to KML using tokml
637 |       const kml = tokml(geojson, options);
638 |       
639 |       return this.formatToolResponse(kml);
640 |     } catch (error) {
641 |       console.error('[Error] GeoJSON to KML conversion failed:', error);
642 |       throw new McpError(
643 |         ErrorCode.InternalError,
644 |         `GeoJSON to KML conversion failed: ${error instanceof Error ? error.message : String(error)}`
645 |       );
646 |     }
647 |   }
648 | 
649 |   async coordinatesToLocation(args: any): Promise<ToolResponse> {
650 |     const { latitude, longitude } = args;
651 |     
652 |     if (latitude === undefined || longitude === undefined) {
653 |       throw new McpError(
654 |         ErrorCode.InvalidParams,
655 |         'Missing required parameters: latitude, longitude'
656 |       );
657 |     }
658 |     
659 |     try {
660 |       console.error(`[Converting] Coordinates (${latitude}, ${longitude}) to location name`);
661 |       
662 |       // Using Nominatim OSM service (free, but has usage limitations)
663 |       const url = `https://nominatim.openstreetmap.org/reverse?format=json&lat=${latitude}&lon=${longitude}`;
664 |       
665 |       return new Promise<ToolResponse>((resolve, reject) => {
666 |         // Use the imported https module directly
667 |         const req = https.request(url, {
668 |           method: 'GET',
669 |           headers: {
670 |             'User-Agent': 'GisFormatMcpServer/1.0'
671 |           }
672 |         }, (res) => {
673 |           if (res.statusCode !== 200) {
674 |             reject(new Error(`Geocoding service returned ${res.statusCode}: ${res.statusMessage}`));
675 |             return;
676 |           }
677 |           
678 |           let data = '';
679 |           res.on('data', (chunk) => {
680 |             data += chunk;
681 |           });
682 |           
683 |           res.on('end', () => {
684 |             try {
685 |               const parsedData = JSON.parse(data);
686 |               
687 |               // Always return detailed format
688 |               const result = {
689 |                 displayName: parsedData.display_name,
690 |                 address: parsedData.address,
691 |                 type: parsedData.type,
692 |                 osmId: parsedData.osm_id,
693 |                 osmType: parsedData.osm_type,
694 |                 category: parsedData.category
695 |               };
696 |               
697 |               resolve(this.formatToolResponse(JSON.stringify(result, null, 2)));
698 |             } catch (error) {
699 |               reject(new Error(`Failed to parse geocoding response: ${error instanceof Error ? error.message : String(error)}`));
700 |             }
701 |           });
702 |         });
703 |         
704 |         req.on('error', (error) => {
705 |           reject(new Error(`Geocoding request failed: ${error.message}`));
706 |         });
707 |         
708 |         req.end();
709 |       });
710 |     } catch (error) {
711 |       console.error('[Error] Coordinates to location conversion failed:', error);
712 |       throw new McpError(
713 |         ErrorCode.InternalError,
714 |         `Coordinates to location conversion failed: ${error instanceof Error ? error.message : String(error)}`
715 |       );
716 |     }
717 |   }
718 | 
719 |   async run() {
720 |     const transport = new StdioServerTransport();
721 |     await this.server.connect(transport);
722 |     console.error('GIS Format Conversion MCP server running on stdio');
723 |   }
724 | }
725 | 
726 | const server = new GisFormatServer();
727 | server.run().catch(console.error);
```