Skip to main content
Glama
listSegmentEfforts.ts5.87 kB
// import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; // Removed import { z } from "zod"; import { listSegmentEfforts as fetchSegmentEfforts, // handleApiError, // Removed unused import StravaDetailedSegmentEffort // Type needed for formatter } from "../stravaClient.js"; // We need the formatter, but can't import the full tool. Let's copy it here for now. // TODO: Move formatters to a shared utils.ts file // Zod schema for input validation const ListSegmentEffortsInputSchema = z.object({ segmentId: z.number().int().positive().describe("The ID of the segment for which to list efforts."), startDateLocal: z.string().datetime({ message: "Invalid start date format. Use ISO 8601." }).optional().describe("Filter efforts starting after this ISO 8601 date-time (optional)."), endDateLocal: z.string().datetime({ message: "Invalid end date format. Use ISO 8601." }).optional().describe("Filter efforts ending before this ISO 8601 date-time (optional)."), perPage: z.number().int().positive().max(200).optional().default(30).describe("Number of efforts to return per page (default: 30, max: 200).") }); type ListSegmentEffortsInput = z.infer<typeof ListSegmentEffortsInputSchema>; // Helper Functions (Metric Only) - Copied locally function formatDuration(seconds: number | null | undefined): string { if (seconds === null || seconds === undefined || isNaN(seconds) || seconds < 0) return 'N/A'; const hours = Math.floor(seconds / 3600); const minutes = Math.floor((seconds % 3600) / 60); const secs = Math.floor(seconds % 60); const parts: string[] = []; if (hours > 0) parts.push(hours.toString().padStart(2, '0')); parts.push(minutes.toString().padStart(2, '0')); parts.push(secs.toString().padStart(2, '0')); return parts.join(':'); } function formatDistance(meters: number | null | undefined): string { if (meters === null || meters === undefined) return 'N/A'; return (meters / 1000).toFixed(2) + ' km'; } // Format segment effort summary (Metric Only) function formatSegmentEffort(effort: StravaDetailedSegmentEffort): string { const movingTime = formatDuration(effort.moving_time); const elapsedTime = formatDuration(effort.elapsed_time); const distance = formatDistance(effort.distance); // Basic summary: Effort ID, Date, Moving Time, Distance, PR Rank let summary = `⏱️ Effort ID: ${effort.id} (${new Date(effort.start_date_local).toLocaleDateString()})`; summary += ` | Time: ${movingTime} (Moving), ${elapsedTime} (Elapsed)`; summary += ` | Dist: ${distance}`; if (effort.pr_rank !== null) summary += ` | PR Rank: ${effort.pr_rank}`; if (effort.kom_rank !== null) summary += ` | KOM Rank: ${effort.kom_rank}`; // Add KOM if available return summary; } // Tool definition export const listSegmentEffortsTool = { name: "list-segment-efforts", description: "Lists the authenticated athlete's efforts on a specific segment, optionally filtering by date.", inputSchema: ListSegmentEffortsInputSchema, execute: async ({ segmentId, startDateLocal, endDateLocal, perPage }: ListSegmentEffortsInput) => { const token = process.env.STRAVA_ACCESS_TOKEN; if (!token) { console.error("Missing STRAVA_ACCESS_TOKEN environment variable."); return { content: [{ type: "text" as const, text: "Configuration error: Missing Strava access token." }], isError: true }; } try { console.error(`Fetching segment efforts for segment ID: ${segmentId}...`); // Use the new params object structure const efforts = await fetchSegmentEfforts(token, segmentId, { startDateLocal, endDateLocal, perPage }); if (!efforts || efforts.length === 0) { console.error(`No efforts found for segment ${segmentId} with the given filters.`); return { content: [{ type: "text" as const, text: `No efforts found for segment ${segmentId} matching the criteria.` }] }; } console.error(`Successfully fetched ${efforts.length} efforts for segment ${segmentId}.`); const effortSummaries = efforts.map(effort => formatSegmentEffort(effort)); // Use metric formatter const responseText = `**Segment ${segmentId} Efforts:**\n\n${effortSummaries.join("\n")}`; return { content: [{ type: "text" as const, text: responseText }] }; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); console.error(`Error listing efforts for segment ${segmentId}: ${errorMessage}`); let userFriendlyMessage; if (errorMessage.startsWith("SUBSCRIPTION_REQUIRED:")) { userFriendlyMessage = `🔒 Accessing segment efforts requires a Strava subscription. Please check your subscription status.`; } else if (errorMessage.includes("Record Not Found") || errorMessage.includes("404")) { userFriendlyMessage = `Segment with ID ${segmentId} not found (when listing efforts).`; } else { userFriendlyMessage = `An unexpected error occurred while listing efforts for segment ${segmentId}. Details: ${errorMessage}`; } return { content: [{ type: "text" as const, text: `❌ ${userFriendlyMessage}` }], isError: true }; } } }; // Removed old registration function /* export function registerListSegmentEffortsTool(server: McpServer) { server.tool( listSegmentEfforts.name, listSegmentEfforts.description, listSegmentEfforts.inputSchema.shape, listSegmentEfforts.execute ); } */

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