/** * PeakPulse MCP tools — peakpulse.sessions.*, peakpulse.routes.*, peakpulse.stats * * Backed by: peakpulse-backend (port 4010). * All tools require admin role. */ import { z } from 'zod'; import { registerTool } from '../tools/registry.js'; import { config } from '../../lib/config.js'; import { peakpulseSessionsList, peakpulseSessionGet, peakpulseSessionExport, peakpulseGetStats, peakpulseRoutesList, peakpulseRouteGet, peakpulseSyncStatus, } from '../../lib/peakpulse-client.js'; import type { McpToolRequest } from '../tools/types.js'; const tokenOf = (req: McpToolRequest) => req.headers.authorization?.slice(7); // ── peakpulse.sessions.list ─────────────────────────────────────────────── registerTool({ name: 'peakpulse.sessions.list', description: 'List adventure sessions for the authenticated user. Filter by activity type (hiking/skiing/cycling/running) or status. Requires admin role.', requiredRole: 'admin', inputSchema: z.object({ activityType: z .enum(['hiking', 'skiing', 'cycling', 'running']) .optional() .describe('Filter by activity type'), status: z .enum(['active', 'completed', 'paused']) .optional() .describe('Filter by session status'), limit: z.coerce.number().min(1).max(config.QUERY_MAX_LIMIT).default(config.QUERY_DEFAULT_LIMIT), offset: z.coerce.number().min(0).default(0), }), async execute(args, req) { return peakpulseSessionsList(args, { token: tokenOf(req), requestId: req.id }); }, }); // ── peakpulse.sessions.export ───────────────────────────────────────────── registerTool({ name: 'peakpulse.sessions.export', description: 'Export a session as a portable JSON summary including GPS bounds, ski metrics, weather snapshot, and elevation data. Useful for sharing or route-review flows. Requires admin role.', requiredRole: 'admin', inputSchema: z.object({ sessionId: z.string().min(1).describe('Session ID to export'), }), async execute(args, req) { return peakpulseSessionExport(args.sessionId, { token: tokenOf(req), requestId: req.id }); }, }); // ── peakpulse.stats ─────────────────────────────────────────────────────── registerTool({ name: 'peakpulse.stats', description: 'Get aggregated activity stats for the authenticated user (total sessions, distance, elevation gain, per-activity breakdown). Requires admin role.', requiredRole: 'admin', inputSchema: z.object({}), async execute(_args, req) { return peakpulseGetStats({ token: tokenOf(req), requestId: req.id }); }, }); // ── peakpulse.sessions.get ────────────────────────────────────────────────── registerTool({ name: 'peakpulse.sessions.get', description: 'Get a single adventure session by ID including activityType, GPS trackPoints summary, ski/hike metrics, weather snapshot, and haptic events. Requires admin role.', requiredRole: 'admin', inputSchema: z.object({ sessionId: z.string().min(1).describe('Session ID'), }), async execute(args, req) { return peakpulseSessionGet(args.sessionId, { token: tokenOf(req), requestId: req.id }); }, }); // ── peakpulse.routes.list ─────────────────────────────────────────────────── registerTool({ name: 'peakpulse.routes.list', description: 'List GPS route documents for the authenticated user. Each route contains track points, haptic milestone events, and a bounding box. Requires admin role.', requiredRole: 'admin', inputSchema: z.object({ limit: z.coerce.number().min(1).max(config.QUERY_MAX_LIMIT).default(config.QUERY_DEFAULT_LIMIT), offset: z.coerce.number().min(0).default(0), }), async execute(args, req) { return peakpulseRoutesList(args, { token: tokenOf(req), requestId: req.id }); }, }); // ── peakpulse.routes.get ──────────────────────────────────────────────────── registerTool({ name: 'peakpulse.routes.get', description: 'Retrieve GPS track points and haptic milestone events for a session. Returns bounding box coordinates plus full track point array. Requires admin role.', requiredRole: 'admin', inputSchema: z.object({ sessionId: z.string().min(1).describe('Session ID to get route data for'), }), async execute(args, req) { return peakpulseRouteGet(args.sessionId, { token: tokenOf(req), requestId: req.id }); }, }); // ── peakpulse.syncStatus ───────────────────────────────────────────────────── registerTool({ name: 'peakpulse.syncStatus', description: 'Check the PeakPulse offline upload queue: pending session count, last successful sync timestamp, and oldest pending item age. Useful for diagnosing sync failures on iOS. Requires admin role.', requiredRole: 'admin', inputSchema: z.object({}), async execute(_args, req) { return peakpulseSyncStatus({ token: tokenOf(req), requestId: req.id }); }, }); // ── peakpulse.weather.getSnapshot ─────────────────────────────────────────────── registerTool({ name: 'peakpulse.weather.getSnapshot', description: 'Get the weather snapshot captured at the start of a session (temperature, wind speed, UV index, condition). Returns null if weather data was not captured. Requires admin role.', requiredRole: 'admin', inputSchema: z.object({ sessionId: z.string().min(1).describe('Session ID to retrieve weather snapshot for'), }), async execute(args, req) { const session = await peakpulseSessionGet(args.sessionId, { token: tokenOf(req), requestId: req.id, }); return { sessionId: args.sessionId, weather: (session as unknown as Record).weather ?? null, }; }, });