Skip to main content
Glama

explore-segments

Find popular running or cycling segments within a specified geographical area. Filter results by activity type and climb category to discover routes tailored to your fitness goals.

Instructions

Searches for popular segments within a given geographical area.

Input Schema

NameRequiredDescriptionDefault
activityTypeNoFilter segments by activity type (optional: 'running' or 'riding').
boundsYesThe geographical area to search, specified as a comma-separated string: south_west_lat,south_west_lng,north_east_lat,north_east_lng
maxCatNoFilter by maximum climb category (optional, 0-5). Requires riding activityType.
minCatNoFilter by minimum climb category (optional, 0-5). Requires riding activityType.

Input Schema (JSON Schema)

{ "$schema": "http://json-schema.org/draft-07/schema#", "additionalProperties": false, "properties": { "activityType": { "description": "Filter segments by activity type (optional: 'running' or 'riding').", "enum": [ "running", "riding" ], "type": "string" }, "bounds": { "description": "The geographical area to search, specified as a comma-separated string: south_west_lat,south_west_lng,north_east_lat,north_east_lng", "pattern": "^-?\\d+(\\.\\d+)?,-?\\d+(\\.\\d+)?,-?\\d+(\\.\\d+)?,-?\\d+(\\.\\d+)?$", "type": "string" }, "maxCat": { "description": "Filter by maximum climb category (optional, 0-5). Requires riding activityType.", "maximum": 5, "minimum": 0, "type": "integer" }, "minCat": { "description": "Filter by minimum climb category (optional, 0-5). Requires riding activityType.", "maximum": 5, "minimum": 0, "type": "integer" } }, "required": [ "bounds" ], "type": "object" }

Implementation Reference

  • The execute function that implements the core logic of the 'explore-segments' tool. It handles input validation, retrieves the athlete's measurement preferences, calls the Strava API via fetchExploreSegments, formats the response based on units, and returns formatted segment information.
    execute: async ({ bounds, activityType, minCat, maxCat }: ExploreSegmentsInput) => { const token = process.env.STRAVA_ACCESS_TOKEN; if (!token || token === 'YOUR_STRAVA_ACCESS_TOKEN_HERE') { console.error("Missing or placeholder STRAVA_ACCESS_TOKEN in .env"); return { content: [{ type: "text" as const, text: "❌ Configuration Error: STRAVA_ACCESS_TOKEN is missing or not set in the .env file." }], isError: true, }; } if ((minCat !== undefined || maxCat !== undefined) && activityType !== 'riding') { return { content: [{ type: "text" as const, text: "❌ Input Error: Climb category filters (minCat, maxCat) require activityType to be 'riding'." }], isError: true, }; } try { console.error(`Exploring segments within bounds: ${bounds}...`); const athlete = await getAuthenticatedAthlete(token); const response: StravaExplorerResponse = await fetchExploreSegments(token, bounds, activityType, minCat, maxCat); console.error(`Found ${response.segments?.length ?? 0} segments.`); if (!response.segments || response.segments.length === 0) { return { content: [{ type: "text" as const, text: " MNo segments found in the specified area with the given filters." }] }; } const distanceFactor = athlete.measurement_preference === 'feet' ? 0.000621371 : 0.001; const distanceUnit = athlete.measurement_preference === 'feet' ? 'mi' : 'km'; const elevationFactor = athlete.measurement_preference === 'feet' ? 3.28084 : 1; const elevationUnit = athlete.measurement_preference === 'feet' ? 'ft' : 'm'; const segmentItems = response.segments.map(segment => { const distance = (segment.distance * distanceFactor).toFixed(2); const elevDifference = (segment.elev_difference * elevationFactor).toFixed(0); const text = ` 🗺️ **${segment.name}** (ID: ${segment.id}) - Climb: Cat ${segment.climb_category_desc} (${segment.climb_category}) - Distance: ${distance} ${distanceUnit} - Avg Grade: ${segment.avg_grade}% - Elev Difference: ${elevDifference} ${elevationUnit} - Starred: ${segment.starred ? 'Yes' : 'No'} `.trim(); const item: { type: "text", text: string } = { type: "text" as const, text }; return item; }); const responseText = `**Found Segments:**\n\n${segmentItems.map(item => item.text).join("\n---\n")}`; return { content: [{ type: "text" as const, text: responseText }] }; } catch (error) { const errorMessage = error instanceof Error ? error.message : "An unknown error occurred"; console.error("Error in explore-segments tool:", errorMessage); return { content: [{ type: "text" as const, text: `❌ API Error: ${errorMessage}` }], isError: true, }; } }
  • Zod input schema for the explore-segments tool, validating bounds (regex for lat,lng format), activityType enum, and optional climb category filters.
    const ExploreSegmentsInputSchema = z.object({ bounds: z.string() .regex(/^-?\d+(\.\d+)?,-?\d+(\.\d+)?,-?\d+(\.\d+)?,-?\d+(\.\d+)?$/, "Bounds must be in the format: south_west_lat,south_west_lng,north_east_lat,north_east_lng") .describe("The geographical area to search, specified as a comma-separated string: south_west_lat,south_west_lng,north_east_lat,north_east_lng"), activityType: z.enum(["running", "riding"]) .optional() .describe("Filter segments by activity type (optional: 'running' or 'riding')."), minCat: z.number().int().min(0).max(5).optional() .describe("Filter by minimum climb category (optional, 0-5). Requires riding activityType."), maxCat: z.number().int().min(0).max(5).optional() .describe("Filter by maximum climb category (optional, 0-5). Requires riding activityType."), });
  • src/server.ts:92-97 (registration)
    Registration of the explore-segments tool in the MCP server using the imported tool definition.
    server.tool( exploreSegments.name, exploreSegments.description, exploreSegments.inputSchema?.shape ?? {}, exploreSegments.execute );
  • Supporting function in Strava client that makes the actual API call to Strava's /segments/explore endpoint, handles parameters, validation with Zod (ExplorerResponseSchema), and error handling including token refresh.
    export async function exploreSegments( accessToken: string, bounds: string, activityType?: 'running' | 'riding', minCat?: number, maxCat?: number ): Promise<StravaExplorerResponse> { if (!accessToken) { throw new Error("Strava access token is required."); } if (!bounds || !/^-?\d+(\.\d+)?,-?\d+(\.\d+)?,-?\d+(\.\d+)?,-?\d+(\.\d+)?$/.test(bounds)) { throw new Error("Valid bounds (lat,lng,lat,lng) are required for exploring segments."); } const params: Record<string, any> = { bounds: bounds, }; if (activityType) params.activity_type = activityType; if (minCat !== undefined) params.min_cat = minCat; if (maxCat !== undefined) params.max_cat = maxCat; try { const response = await stravaApi.get<unknown>("segments/explore", { headers: { Authorization: `Bearer ${accessToken}` }, params: params }); const validationResult = ExplorerResponseSchema.safeParse(response.data); if (!validationResult.success) { console.error("Strava API validation failed (exploreSegments):", validationResult.error); throw new Error(`Invalid data format received from Strava API: ${validationResult.error.message}`); } return validationResult.data; } catch (error) { return await handleApiError<StravaExplorerResponse>(error, `exploreSegments with bounds ${bounds}`, async () => { // Use new token from environment after refresh const newToken = process.env.STRAVA_ACCESS_TOKEN!; return exploreSegments(newToken, bounds, activityType); }); } }

Latest Blog Posts

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/r-huijts/strava-mcp'

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