refactor(mcp): extract all MCP tools into dedicated modules and add shared helpers

This commit is contained in:
jubnl
2026-04-09 18:09:08 +02:00
parent 91c9421b5e
commit 63784d86a3
36 changed files with 8154 additions and 1249 deletions
+109
View File
@@ -0,0 +1,109 @@
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp';
import { z } from 'zod';
import { searchPlaces, getPlaceDetails, reverseGeocode, resolveGoogleMapsUrl } from '../../services/mapsService';
import { getWeather, getDetailedWeather } from '../../services/weatherService';
import {
TOOL_ANNOTATIONS_READONLY,
ok,
} from './_shared';
export function registerMapsWeatherTools(server: McpServer, userId: number): void {
// --- MAPS EXTRAS ---
server.registerTool(
'get_place_details',
{
description: 'Fetch detailed information about a place by its Google Place ID.',
inputSchema: {
placeId: z.string().describe('Google Place ID'),
lang: z.string().optional().default('en'),
},
annotations: TOOL_ANNOTATIONS_READONLY,
},
async ({ placeId, lang }) => {
const details = await getPlaceDetails(userId, placeId, lang ?? 'en');
if (!details) return { content: [{ type: 'text' as const, text: 'Place not found or maps service not configured.' }], isError: true };
return ok({ details });
}
);
server.registerTool(
'reverse_geocode',
{
description: 'Get a human-readable address for given coordinates.',
inputSchema: {
lat: z.number(),
lng: z.number(),
lang: z.string().optional().default('en'),
},
annotations: TOOL_ANNOTATIONS_READONLY,
},
async ({ lat, lng, lang }) => {
const result = await reverseGeocode(String(lat), String(lng), lang ?? 'en');
if (!result) return { content: [{ type: 'text' as const, text: 'Reverse geocode failed or maps service not configured.' }], isError: true };
return ok(result);
}
);
server.registerTool(
'resolve_maps_url',
{
description: 'Resolve a Google Maps share URL to coordinates and place name.',
inputSchema: {
url: z.string().describe('Google Maps share URL'),
},
annotations: TOOL_ANNOTATIONS_READONLY,
},
async ({ url }) => {
const result = await resolveGoogleMapsUrl(url);
if (!result) return { content: [{ type: 'text' as const, text: 'Could not resolve URL or maps service not configured.' }], isError: true };
return ok(result);
}
);
// --- WEATHER ---
server.registerTool(
'get_weather',
{
description: 'Get weather forecast for a location and date.',
inputSchema: {
lat: z.number(),
lng: z.number(),
date: z.string().describe('ISO date YYYY-MM-DD'),
lang: z.string().optional().default('en'),
},
annotations: TOOL_ANNOTATIONS_READONLY,
},
async ({ lat, lng, date, lang }) => {
try {
const weather = await getWeather(String(lat), String(lng), date, lang ?? 'en');
return ok({ weather });
} catch (err: any) {
return { content: [{ type: 'text' as const, text: err?.message ?? 'Weather service not available.' }], isError: true };
}
}
);
server.registerTool(
'get_detailed_weather',
{
description: 'Get hourly/detailed weather forecast for a location and date.',
inputSchema: {
lat: z.number(),
lng: z.number(),
date: z.string().describe('ISO date YYYY-MM-DD'),
lang: z.string().optional().default('en'),
},
annotations: TOOL_ANNOTATIONS_READONLY,
},
async ({ lat, lng, date, lang }) => {
try {
const weather = await getDetailedWeather(String(lat), String(lng), date, lang ?? 'en');
return ok({ weather });
} catch (err: any) {
return { content: [{ type: 'text' as const, text: err?.message ?? 'Weather service not available.' }], isError: true };
}
}
);
}