mcp-fitbit
Version:
Model Context Protocol (MCP) server for accessing Fitbit health and fitness data. Connect AI assistants like Claude to your Fitbit data for personalized health insights.
107 lines (106 loc) • 4.99 kB
JavaScript
import { z } from 'zod';
import { makeFitbitRequest } from './utils.js';
const FITBIT_API_BASE = 'https://api.fitbit.com/1';
// --- Tool Registration ---
/**
* Registers Fitbit heart rate tools with the MCP server.
* @param server The McpServer instance.
* @param getAccessTokenFn Function to retrieve the current access token.
*/
export function registerHeartRateTools(server, getAccessTokenFn) {
// --- Heart Rate by Date/Period Tool ---
const periodToolName = 'get_heart_rate';
const periodDescription = "Get the raw JSON response for heart rate data from Fitbit for a specified period ending today or on a specific date. Requires a 'period' parameter such as '1d', '7d', '30d', '1w', '1m' and optionally accepts 'date' parameter.";
const periodParametersSchemaShape = {
period: z
.enum(['1d', '7d', '30d', '1w', '1m'])
.describe('The time period for which to retrieve heart rate data.'),
date: z
.string()
.regex(/^\d{4}-\d{2}-\d{2}$|^today$/, "Date must be in YYYY-MM-DD format or 'today'.")
.optional()
.describe("The date for which to retrieve heart rate data (YYYY-MM-DD or 'today'). Defaults to 'today'."),
};
server.tool(periodToolName, periodDescription, periodParametersSchemaShape, async ({ period, date = 'today', }) => {
// Construct the endpoint dynamically
const endpoint = `activities/heart/date/${date}/${period}.json`;
const heartRateData = await makeFitbitRequest(endpoint, getAccessTokenFn, FITBIT_API_BASE);
// Handle API call failure
if (!heartRateData) {
return {
content: [
{
type: 'text',
text: `Failed to retrieve heart rate data from Fitbit API for date '${date}' and period '${period}'. Check token and permissions.`,
},
],
isError: true,
};
}
// Handle no data found for the period
const heartRateEntries = heartRateData['activities-heart'] || [];
if (heartRateEntries.length === 0) {
return {
content: [
{
type: 'text',
text: `No heart rate data found for date '${date}' and period '${period}'.`,
},
],
};
}
// Return successful response with raw JSON
const rawJsonResponse = JSON.stringify(heartRateData, null, 2);
return {
content: [{ type: 'text', text: rawJsonResponse }],
};
});
// --- Heart Rate by Date Range Tool ---
const rangeToolName = 'get_heart_rate_by_date_range';
const rangeDescription = "Get the raw JSON response for heart rate data from Fitbit for a specific date range. Requires 'startDate' and 'endDate' parameters in 'YYYY-MM-DD' format. Note: The API enforces a maximum range of 1 year.";
const rangeParametersSchemaShape = {
startDate: z
.string()
.regex(/^\d{4}-\d{2}-\d{2}$/, 'Start date must be in YYYY-MM-DD format.')
.describe('The start date for which to retrieve heart rate data (YYYY-MM-DD).'),
endDate: z
.string()
.regex(/^\d{4}-\d{2}-\d{2}$/, 'End date must be in YYYY-MM-DD format.')
.describe('The end date for which to retrieve heart rate data (YYYY-MM-DD).'),
};
server.tool(rangeToolName, rangeDescription, rangeParametersSchemaShape, async ({ startDate, endDate, }) => {
// Construct the endpoint dynamically
const endpoint = `activities/heart/date/${startDate}/${endDate}.json`;
// Make the request
const heartRateData = await makeFitbitRequest(endpoint, getAccessTokenFn, FITBIT_API_BASE);
// Handle API call failure
if (!heartRateData) {
return {
content: [
{
type: 'text',
text: `Failed to retrieve heart rate data from Fitbit API for the date range '${startDate}' to '${endDate}'. Check token, permissions, date format, and ensure the range is 1 year or less.`,
},
],
isError: true,
};
}
// Handle no data found
const heartRateEntries = heartRateData['activities-heart'] || [];
if (heartRateEntries.length === 0) {
return {
content: [
{
type: 'text',
text: `No heart rate data found for the date range '${startDate}' to '${endDate}'.`,
},
],
};
}
// Return successful response
const rawJsonResponse = JSON.stringify(heartRateData, null, 2);
return {
content: [{ type: 'text', text: rawJsonResponse }],
};
});
}