Skip to main content
Glama
index.ts13.4 kB
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js" import { z } from "zod" // Configuration schema requiring the Oura Personal Access Token export const configSchema = z.object({ accessToken: z.string().describe("Your Oura Personal Access Token from https://cloud.ouraring.com/personal-access-tokens"), }) const OURA_API_BASE = "https://api.ouraring.com/v2/usercollection" // Helper function to make authenticated API requests async function fetchOuraData(endpoint: string, accessToken: string, params?: Record<string, string>) { const url = new URL(`${OURA_API_BASE}${endpoint}`) if (params) { Object.entries(params).forEach(([key, value]) => { url.searchParams.append(key, value) }) } const response = await fetch(url.toString(), { headers: { 'Authorization': `Bearer ${accessToken}`, }, }) if (!response.ok) { throw new Error(`Oura API error: ${response.status} ${response.statusText}`) } return await response.json() } export default function createServer({ config, }: { config: z.infer<typeof configSchema> }) { const server = new McpServer({ name: "Oura Ring API", version: "1.0.0", }) // Tool: Get Personal Information server.registerTool( "get-personal-info", { title: "Get Personal Information", description: "Retrieve your personal information from Oura Ring including age, weight, height, and biological sex", inputSchema: {}, }, async () => { try { const data = await fetchOuraData("/personal_info", config.accessToken) return { content: [ { type: "text", text: JSON.stringify(data, null, 2), } ], } } catch (error) { return { content: [ { type: "text", text: `Error fetching personal info: ${error instanceof Error ? error.message : String(error)}`, } ], isError: true, } } }, ) // Tool: Get Daily Sleep Data server.registerTool( "get-daily-sleep", { title: "Get Daily Sleep Data", description: "Retrieve daily sleep data including total sleep time, sleep stages, efficiency, and sleep score", inputSchema: { start_date: z.string().optional().describe("Start date in YYYY-MM-DD format (optional)"), end_date: z.string().optional().describe("End date in YYYY-MM-DD format (optional)"), }, }, async ({ start_date, end_date }) => { try { const params: Record<string, string> = {} if (start_date) params.start_date = start_date if (end_date) params.end_date = end_date const data = await fetchOuraData("/daily_sleep", config.accessToken, params) return { content: [ { type: "text", text: JSON.stringify(data, null, 2), } ], } } catch (error) { return { content: [ { type: "text", text: `Error fetching sleep data: ${error instanceof Error ? error.message : String(error)}`, } ], isError: true, } } }, ) // Tool: Get Daily Activity Data server.registerTool( "get-daily-activity", { title: "Get Daily Activity Data", description: "Retrieve daily activity data including steps, calories, distance, and activity score", inputSchema: { start_date: z.string().optional().describe("Start date in YYYY-MM-DD format (optional)"), end_date: z.string().optional().describe("End date in YYYY-MM-DD format (optional)"), }, }, async ({ start_date, end_date }) => { try { const params: Record<string, string> = {} if (start_date) params.start_date = start_date if (end_date) params.end_date = end_date const data = await fetchOuraData("/daily_activity", config.accessToken, params) return { content: [ { type: "text", text: JSON.stringify(data, null, 2), } ], } } catch (error) { return { content: [ { type: "text", text: `Error fetching activity data: ${error instanceof Error ? error.message : String(error)}`, } ], isError: true, } } }, ) // Tool: Get Daily Readiness Data server.registerTool( "get-daily-readiness", { title: "Get Daily Readiness Data", description: "Retrieve daily readiness data including readiness score, temperature deviation, and recovery indicators", inputSchema: { start_date: z.string().optional().describe("Start date in YYYY-MM-DD format (optional)"), end_date: z.string().optional().describe("End date in YYYY-MM-DD format (optional)"), }, }, async ({ start_date, end_date }) => { try { const params: Record<string, string> = {} if (start_date) params.start_date = start_date if (end_date) params.end_date = end_date const data = await fetchOuraData("/daily_readiness", config.accessToken, params) return { content: [ { type: "text", text: JSON.stringify(data, null, 2), } ], } } catch (error) { return { content: [ { type: "text", text: `Error fetching readiness data: ${error instanceof Error ? error.message : String(error)}`, } ], isError: true, } } }, ) // Tool: Get Heart Rate Data server.registerTool( "get-heart-rate", { title: "Get Heart Rate Data", description: "Retrieve heart rate measurements including resting heart rate and heart rate variability", inputSchema: { start_datetime: z.string().optional().describe("Start datetime in ISO 8601 format (optional)"), end_datetime: z.string().optional().describe("End datetime in ISO 8601 format (optional)"), }, }, async ({ start_datetime, end_datetime }) => { try { const params: Record<string, string> = {} if (start_datetime) params.start_datetime = start_datetime if (end_datetime) params.end_datetime = end_datetime const data = await fetchOuraData("/heartrate", config.accessToken, params) return { content: [ { type: "text", text: JSON.stringify(data, null, 2), } ], } } catch (error) { return { content: [ { type: "text", text: `Error fetching heart rate data: ${error instanceof Error ? error.message : String(error)}`, } ], isError: true, } } }, ) // Tool: Get Workout Data server.registerTool( "get-workouts", { title: "Get Workout Data", description: "Retrieve workout sessions including type, duration, intensity, and calories burned", inputSchema: { start_date: z.string().optional().describe("Start date in YYYY-MM-DD format (optional)"), end_date: z.string().optional().describe("End date in YYYY-MM-DD format (optional)"), }, }, async ({ start_date, end_date }) => { try { const params: Record<string, string> = {} if (start_date) params.start_date = start_date if (end_date) params.end_date = end_date const data = await fetchOuraData("/workout", config.accessToken, params) return { content: [ { type: "text", text: JSON.stringify(data, null, 2), } ], } } catch (error) { return { content: [ { type: "text", text: `Error fetching workout data: ${error instanceof Error ? error.message : String(error)}`, } ], isError: true, } } }, ) // Tool: Get Sleep Sessions server.registerTool( "get-sleep-sessions", { title: "Get Sleep Sessions", description: "Retrieve detailed sleep session data including individual sleep periods with stages and metrics", inputSchema: { start_date: z.string().optional().describe("Start date in YYYY-MM-DD format (optional)"), end_date: z.string().optional().describe("End date in YYYY-MM-DD format (optional)"), }, }, async ({ start_date, end_date }) => { try { const params: Record<string, string> = {} if (start_date) params.start_date = start_date if (end_date) params.end_date = end_date const data = await fetchOuraData("/sleep", config.accessToken, params) return { content: [ { type: "text", text: JSON.stringify(data, null, 2), } ], } } catch (error) { return { content: [ { type: "text", text: `Error fetching sleep sessions: ${error instanceof Error ? error.message : String(error)}`, } ], isError: true, } } }, ) // Tool: Get Daily Stress Data server.registerTool( "get-daily-stress", { title: "Get Daily Stress Data", description: "Retrieve daily stress measurements and recovery indicators", inputSchema: { start_date: z.string().optional().describe("Start date in YYYY-MM-DD format (optional)"), end_date: z.string().optional().describe("End date in YYYY-MM-DD format (optional)"), }, }, async ({ start_date, end_date }) => { try { const params: Record<string, string> = {} if (start_date) params.start_date = start_date if (end_date) params.end_date = end_date const data = await fetchOuraData("/daily_stress", config.accessToken, params) return { content: [ { type: "text", text: JSON.stringify(data, null, 2), } ], } } catch (error) { return { content: [ { type: "text", text: `Error fetching stress data: ${error instanceof Error ? error.message : String(error)}`, } ], isError: true, } } }, ) // Resource: Oura API Documentation server.registerResource( "oura-api-docs", "docs://oura-api", { title: "Oura API Documentation", description: "Official Oura API V2 documentation and reference", }, async uri => ({ contents: [ { uri: uri.href, text: `Oura API V2 Documentation The Oura API provides access to data generated by the Oura Ring, including: - Personal Information: Age, weight, height, biological sex - Sleep Data: Sleep stages, duration, efficiency, sleep score - Activity Data: Steps, calories, distance, activity score - Readiness Data: Readiness score, temperature, recovery indicators - Heart Rate: Resting HR, HRV, continuous measurements - Workouts: Exercise sessions with type, duration, intensity - Stress Data: Daily stress levels and recovery Authentication: Requires Personal Access Token Base URL: https://api.ouraring.com/v2/usercollection Date formats: - Dates: YYYY-MM-DD (e.g., 2024-01-15) - Datetimes: ISO 8601 format (e.g., 2024-01-15T10:30:00Z) Official documentation: https://cloud.ouraring.com/docs/`, mimeType: "text/plain", }, ], }), ) // Prompt: Analyze Sleep Quality server.registerPrompt( "analyze-sleep", { title: "Analyze Sleep Quality", description: "Get an analysis of your recent sleep quality and patterns", argsSchema: { days: z.number().default(7).describe("Number of days to analyze (default: 7)"), }, }, async ({ days }) => { const endDate = new Date() const startDate = new Date() startDate.setDate(startDate.getDate() - days) return { messages: [ { role: "user", content: { type: "text", text: `Please analyze my sleep quality over the last ${days} days. Get my sleep data from ${startDate.toISOString().split('T')[0]} to ${endDate.toISOString().split('T')[0]} and provide insights on: 1. Average sleep duration and quality 2. Sleep stage distribution (deep, REM, light sleep) 3. Sleep efficiency trends 4. Any notable patterns or concerns 5. Recommendations for improvement`, }, }, ], } }, ) // Prompt: Activity Summary server.registerPrompt( "activity-summary", { title: "Activity Summary", description: "Get a summary of your recent activity and movement patterns", argsSchema: { days: z.number().default(7).describe("Number of days to analyze (default: 7)"), }, }, async ({ days }) => { const endDate = new Date() const startDate = new Date() startDate.setDate(startDate.getDate() - days) return { messages: [ { role: "user", content: { type: "text", text: `Please provide an activity summary for the last ${days} days. Get my activity data from ${startDate.toISOString().split('T')[0]} to ${endDate.toISOString().split('T')[0]} and analyze: 1. Daily step counts and trends 2. Total calories burned 3. Activity score patterns 4. Most active vs least active days 5. Suggestions to increase activity levels`, }, }, ], } }, ) // Prompt: Readiness Check server.registerPrompt( "readiness-check", { title: "Readiness Check", description: "Check your current readiness and recovery status", argsSchema: { days: z.number().default(3).describe("Number of days to analyze (default: 3)"), }, }, async ({ days }) => { const endDate = new Date() const startDate = new Date() startDate.setDate(startDate.getDate() - days) return { messages: [ { role: "user", content: { type: "text", text: `Please check my readiness and recovery status for the last ${days} days. Get my readiness data from ${startDate.toISOString().split('T')[0]} to ${endDate.toISOString().split('T')[0]} and provide: 1. Current readiness score and trend 2. Key factors affecting readiness (sleep, HRV, temperature) 3. Recovery indicators 4. Recommendations for today's activity level 5. Any warning signs or areas of concern`, }, }, ], } }, ) return server.server }

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/SauravPahadia/oura-mcp'

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