hevy-mcp-server 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Noah Van Hart
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,135 @@
1
+ # Hevy MCP Server
2
+
3
+ A Model Context Protocol (MCP) server for integrating with the Hevy workout tracking app API. This server provides tools and resources for managing workouts, routines, and exercise data through the MCP protocol.
4
+
5
+ ## Features
6
+
7
+ - **Workout Management**: Create, read, and analyze workouts
8
+ - **Routine Management**: Manage workout templates and routines
9
+ - **Exercise Templates**: Access and search exercise databases
10
+ - **Progress Tracking**: Analyze workout trends and progression
11
+ - **Data Resources**: Access workout and fitness data as MCP resources
12
+
13
+ ## Installation
14
+
15
+ ### Global Installation (Recommended)
16
+
17
+ ```bash
18
+ npm install -g hevy-mcp-server
19
+ ```
20
+
21
+ ### Local Installation
22
+
23
+ ```bash
24
+ npm install hevy-mcp-server
25
+ ```
26
+
27
+ ## Quick Start
28
+
29
+ 1. **Set your Hevy API key**
30
+ ```bash
31
+ export HEVY_API_KEY=your_hevy_api_key_here
32
+ ```
33
+
34
+ 2. **Run the server**
35
+ ```bash
36
+ hevy-mcp-server
37
+ ```
38
+
39
+ ## MCP Configuration
40
+
41
+ ### Claude Desktop App
42
+
43
+ Add this to your Claude Desktop configuration file:
44
+
45
+ **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
46
+ **Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
47
+
48
+ ```json
49
+ {
50
+ "mcpServers": {
51
+ "hevy": {
52
+ "command": "npx",
53
+ "args": ["hevy-mcp-server"],
54
+ "env": {
55
+ "HEVY_API_KEY": "your_hevy_api_key_here"
56
+ }
57
+ }
58
+ }
59
+ }
60
+ ```
61
+
62
+ ### Other MCP Clients
63
+
64
+ For other MCP clients, use:
65
+
66
+ ```bash
67
+ HEVY_API_KEY=your_key hevy-mcp-server
68
+ ```
69
+
70
+ ## MCP Tools Available
71
+
72
+ ### Workout Tools
73
+ - `get_workouts` - Get paginated list of workouts
74
+ - `get_workout` - Get specific workout details
75
+ - `get_all_workouts` - Get all workouts automatically
76
+ - `create_workout` - Create a new workout
77
+ - `get_workout_count` - Get total workout count
78
+ - `analyze_workout_trends` - Analyze workout patterns and progress
79
+
80
+ ### Routine Tools
81
+ - `get_routines` - Get paginated list of routines
82
+ - `get_routine` - Get specific routine details
83
+ - `get_all_routines` - Get all routines automatically
84
+ - `create_routine` - Create a new routine
85
+ - `get_routine_folders` - Get routine organization folders
86
+ - `create_routine_folder` - Create new routine folder
87
+
88
+ ### Exercise Tools
89
+ - `get_exercise_templates` - Get all available exercises
90
+ - `search_exercise_templates` - Search exercises by name/muscle group
91
+ - `get_exercise_history` - Get exercise performance history
92
+
93
+ ## MCP Resources Available
94
+
95
+ - `hevy://workouts` - Complete workout data with statistics
96
+ - `hevy://routines` - All workout routines and templates
97
+ - `hevy://exercise-templates` - Exercise database with muscle groups
98
+ - `hevy://routine-folders` - Routine organization folders
99
+ - `hevy://account-summary` - High-level account statistics
100
+ - `hevy://recent-workouts` - Last 10 workouts with key metrics
101
+ - `hevy://exercise-list` - Simple exercise reference list
102
+
103
+ ## API Documentation
104
+
105
+ This server interfaces with the Hevy API v1. You'll need a valid Hevy API key to use this server.
106
+
107
+ ## Development
108
+
109
+ ### Setup
110
+ ```bash
111
+ npm install
112
+ ```
113
+
114
+ ### Build
115
+ ```bash
116
+ npm run build
117
+ ```
118
+
119
+ ### Test
120
+ ```bash
121
+ npm test
122
+ ```
123
+
124
+ ### Development Mode
125
+ ```bash
126
+ npm run dev
127
+ ```
128
+
129
+ ## License
130
+
131
+ MIT
132
+
133
+ ## Repository
134
+
135
+ https://github.com/noah-vh/hevy-mcp-server
@@ -0,0 +1,147 @@
1
+ /**
2
+ * Hevy API Client for interacting with the Hevy workout tracking API
3
+ * Supports workouts, routines, exercise templates, and more
4
+ */
5
+ export interface HevySet {
6
+ index: number;
7
+ type: 'warmup' | 'normal' | 'failure' | 'dropset';
8
+ weight_kg: number | null;
9
+ reps: number | null;
10
+ distance_meters: number | null;
11
+ duration_seconds: number | null;
12
+ rpe: number | null;
13
+ custom_metric: number | null;
14
+ }
15
+ export interface HevyRoutineSet extends HevySet {
16
+ rep_range?: {
17
+ start: number;
18
+ end: number;
19
+ };
20
+ }
21
+ export interface HevyExercise {
22
+ index: number;
23
+ title: string;
24
+ notes: string;
25
+ exercise_template_id: string;
26
+ superset_id: number | null;
27
+ sets: HevySet[];
28
+ }
29
+ export interface HevyRoutineExercise {
30
+ index: number;
31
+ title: string;
32
+ notes: string;
33
+ exercise_template_id: string;
34
+ superset_id: number | null;
35
+ rest_seconds: number | null;
36
+ sets: HevyRoutineSet[];
37
+ }
38
+ export interface HevyWorkout {
39
+ id: string;
40
+ title: string;
41
+ routine_id?: string;
42
+ description: string;
43
+ start_time: string;
44
+ end_time: string | null;
45
+ updated_at: string;
46
+ created_at: string;
47
+ exercises: HevyExercise[];
48
+ }
49
+ export interface HevyRoutine {
50
+ id: string;
51
+ title: string;
52
+ folder_id: number | null;
53
+ updated_at: string;
54
+ created_at: string;
55
+ exercises: HevyRoutineExercise[];
56
+ }
57
+ export interface HevyRoutineFolder {
58
+ id: number;
59
+ index: number;
60
+ title: string;
61
+ updated_at: string;
62
+ created_at: string;
63
+ }
64
+ export interface HevyExerciseTemplate {
65
+ id: string;
66
+ title: string;
67
+ type: string;
68
+ primary_muscle_group: string;
69
+ secondary_muscle_groups: string[];
70
+ is_custom: boolean;
71
+ }
72
+ export interface HevyExerciseHistoryEntry {
73
+ workout_id: string;
74
+ workout_title: string;
75
+ workout_start_time: string;
76
+ workout_end_time: string | null;
77
+ exercise_template_id: string;
78
+ weight_kg: number | null;
79
+ reps: number | null;
80
+ distance_meters: number | null;
81
+ duration_seconds: number | null;
82
+ rpe: number | null;
83
+ custom_metric: number | null;
84
+ set_type: string;
85
+ }
86
+ export interface HevyAPIResponse<T> {
87
+ page?: number;
88
+ page_count?: number;
89
+ [key: string]: T | any;
90
+ }
91
+ export declare class HevyAPIClient {
92
+ private apiKey;
93
+ private baseUrl;
94
+ constructor(apiKey: string, baseUrl?: string);
95
+ private makeRequest;
96
+ getWorkouts(page?: number, pageSize?: number): Promise<HevyAPIResponse<HevyWorkout[]>>;
97
+ getWorkout(workoutId: string): Promise<{
98
+ workout: HevyWorkout;
99
+ }>;
100
+ createWorkout(workout: Partial<HevyWorkout>): Promise<{
101
+ workout: HevyWorkout;
102
+ }>;
103
+ updateWorkout(workoutId: string, workout: Partial<HevyWorkout>): Promise<{
104
+ workout: HevyWorkout;
105
+ }>;
106
+ getWorkoutCount(): Promise<{
107
+ workout_count: number;
108
+ }>;
109
+ getWorkoutEvents(page?: number, pageSize?: number, since?: string): Promise<HevyAPIResponse<any[]>>;
110
+ getRoutines(page?: number, pageSize?: number): Promise<HevyAPIResponse<HevyRoutine[]>>;
111
+ getRoutine(routineId: string): Promise<{
112
+ routine: HevyRoutine;
113
+ }>;
114
+ createRoutine(routine: Partial<HevyRoutine>): Promise<{
115
+ routine: HevyRoutine;
116
+ }>;
117
+ updateRoutine(routineId: string, routine: Partial<HevyRoutine>): Promise<{
118
+ routine: HevyRoutine;
119
+ }>;
120
+ getRoutineFolders(page?: number, pageSize?: number): Promise<HevyAPIResponse<HevyRoutineFolder[]>>;
121
+ getRoutineFolder(folderId: string): Promise<{
122
+ routine_folder: HevyRoutineFolder;
123
+ }>;
124
+ createRoutineFolder(folderTitle: string): Promise<{
125
+ routine_folder: HevyRoutineFolder;
126
+ }>;
127
+ getExerciseTemplates(page?: number, pageSize?: number): Promise<HevyAPIResponse<HevyExerciseTemplate[]>>;
128
+ getExerciseTemplate(templateId: string): Promise<{
129
+ exercise_template: HevyExerciseTemplate;
130
+ }>;
131
+ createExerciseTemplate(exercise: {
132
+ title: string;
133
+ exercise_type: string;
134
+ equipment_category: string;
135
+ muscle_group: string;
136
+ other_muscles?: string[];
137
+ }): Promise<{
138
+ id: string;
139
+ }>;
140
+ getExerciseHistory(templateId: string, startDate?: string, endDate?: string): Promise<{
141
+ exercise_history: HevyExerciseHistoryEntry[];
142
+ }>;
143
+ getAllWorkouts(): Promise<HevyWorkout[]>;
144
+ getAllRoutines(): Promise<HevyRoutine[]>;
145
+ getAllExerciseTemplates(): Promise<HevyExerciseTemplate[]>;
146
+ getAllRoutineFolders(): Promise<HevyRoutineFolder[]>;
147
+ }
@@ -0,0 +1,165 @@
1
+ /**
2
+ * Hevy API Client for interacting with the Hevy workout tracking API
3
+ * Supports workouts, routines, exercise templates, and more
4
+ */
5
+ const HEVY_API_BASE = 'https://api.hevyapp.com/v1';
6
+ export class HevyAPIClient {
7
+ apiKey;
8
+ baseUrl;
9
+ constructor(apiKey, baseUrl = HEVY_API_BASE) {
10
+ this.apiKey = apiKey;
11
+ this.baseUrl = baseUrl;
12
+ }
13
+ async makeRequest(endpoint, options = {}) {
14
+ const url = `${this.baseUrl}${endpoint}`;
15
+ const response = await fetch(url, {
16
+ ...options,
17
+ headers: {
18
+ 'api-key': this.apiKey,
19
+ 'Content-Type': 'application/json',
20
+ ...options.headers,
21
+ },
22
+ });
23
+ if (response.status === 429) {
24
+ throw new Error('Rate limit exceeded. Please try again later.');
25
+ }
26
+ if (!response.ok) {
27
+ const errorText = await response.text();
28
+ throw new Error(`Hevy API error (${response.status}): ${errorText}`);
29
+ }
30
+ return response.json();
31
+ }
32
+ // Workouts
33
+ async getWorkouts(page = 1, pageSize = 10) {
34
+ return this.makeRequest(`/workouts?page=${page}&pageSize=${pageSize}`);
35
+ }
36
+ async getWorkout(workoutId) {
37
+ return this.makeRequest(`/workouts/${workoutId}`);
38
+ }
39
+ async createWorkout(workout) {
40
+ return this.makeRequest('/workouts', {
41
+ method: 'POST',
42
+ body: JSON.stringify({ workout }),
43
+ });
44
+ }
45
+ async updateWorkout(workoutId, workout) {
46
+ return this.makeRequest(`/workouts/${workoutId}`, {
47
+ method: 'PUT',
48
+ body: JSON.stringify({ workout }),
49
+ });
50
+ }
51
+ async getWorkoutCount() {
52
+ return this.makeRequest('/workouts/count');
53
+ }
54
+ async getWorkoutEvents(page = 1, pageSize = 10, since = '1970-01-01T00:00:00Z') {
55
+ return this.makeRequest(`/workouts/events?page=${page}&pageSize=${pageSize}&since=${encodeURIComponent(since)}`);
56
+ }
57
+ // Routines
58
+ async getRoutines(page = 1, pageSize = 10) {
59
+ return this.makeRequest(`/routines?page=${page}&pageSize=${pageSize}`);
60
+ }
61
+ async getRoutine(routineId) {
62
+ return this.makeRequest(`/routines/${routineId}`);
63
+ }
64
+ async createRoutine(routine) {
65
+ return this.makeRequest('/routines', {
66
+ method: 'POST',
67
+ body: JSON.stringify({ routine }),
68
+ });
69
+ }
70
+ async updateRoutine(routineId, routine) {
71
+ return this.makeRequest(`/routines/${routineId}`, {
72
+ method: 'PUT',
73
+ body: JSON.stringify({ routine }),
74
+ });
75
+ }
76
+ // Routine Folders
77
+ async getRoutineFolders(page = 1, pageSize = 10) {
78
+ return this.makeRequest(`/routine_folders?page=${page}&pageSize=${pageSize}`);
79
+ }
80
+ async getRoutineFolder(folderId) {
81
+ return this.makeRequest(`/routine_folders/${folderId}`);
82
+ }
83
+ async createRoutineFolder(folderTitle) {
84
+ return this.makeRequest('/routine_folders', {
85
+ method: 'POST',
86
+ body: JSON.stringify({ routine_folder: { title: folderTitle } }),
87
+ });
88
+ }
89
+ // Exercise Templates
90
+ async getExerciseTemplates(page = 1, pageSize = 100) {
91
+ return this.makeRequest(`/exercise_templates?page=${page}&pageSize=${pageSize}`);
92
+ }
93
+ async getExerciseTemplate(templateId) {
94
+ return this.makeRequest(`/exercise_templates/${templateId}`);
95
+ }
96
+ async createExerciseTemplate(exercise) {
97
+ return this.makeRequest('/exercise_templates', {
98
+ method: 'POST',
99
+ body: JSON.stringify({ exercise }),
100
+ });
101
+ }
102
+ // Exercise History
103
+ async getExerciseHistory(templateId, startDate, endDate) {
104
+ let url = `/exercise_history/${templateId}`;
105
+ const params = new URLSearchParams();
106
+ if (startDate)
107
+ params.append('start_date', startDate);
108
+ if (endDate)
109
+ params.append('end_date', endDate);
110
+ if (params.toString()) {
111
+ url += `?${params.toString()}`;
112
+ }
113
+ return this.makeRequest(url);
114
+ }
115
+ // Utility methods
116
+ async getAllWorkouts() {
117
+ const firstPage = await this.getWorkouts(1, 10);
118
+ const allWorkouts = [...(firstPage.workouts || [])];
119
+ if (firstPage.page_count && firstPage.page_count > 1) {
120
+ for (let page = 2; page <= firstPage.page_count; page++) {
121
+ const pageData = await this.getWorkouts(page, 10);
122
+ allWorkouts.push(...(pageData.workouts || []));
123
+ // Add delay to respect rate limits
124
+ await new Promise(resolve => setTimeout(resolve, 100));
125
+ }
126
+ }
127
+ return allWorkouts;
128
+ }
129
+ async getAllRoutines() {
130
+ const firstPage = await this.getRoutines(1, 10);
131
+ const allRoutines = [...(firstPage.routines || [])];
132
+ if (firstPage.page_count && firstPage.page_count > 1) {
133
+ for (let page = 2; page <= firstPage.page_count; page++) {
134
+ const pageData = await this.getRoutines(page, 10);
135
+ allRoutines.push(...(pageData.routines || []));
136
+ await new Promise(resolve => setTimeout(resolve, 100));
137
+ }
138
+ }
139
+ return allRoutines;
140
+ }
141
+ async getAllExerciseTemplates() {
142
+ const firstPage = await this.getExerciseTemplates(1, 100);
143
+ const allTemplates = [...(firstPage.exercise_templates || [])];
144
+ if (firstPage.page_count && firstPage.page_count > 1) {
145
+ for (let page = 2; page <= firstPage.page_count; page++) {
146
+ const pageData = await this.getExerciseTemplates(page, 100);
147
+ allTemplates.push(...(pageData.exercise_templates || []));
148
+ await new Promise(resolve => setTimeout(resolve, 100));
149
+ }
150
+ }
151
+ return allTemplates;
152
+ }
153
+ async getAllRoutineFolders() {
154
+ const firstPage = await this.getRoutineFolders(1, 10);
155
+ const allFolders = [...(firstPage.routine_folders || [])];
156
+ if (firstPage.page_count && firstPage.page_count > 1) {
157
+ for (let page = 2; page <= firstPage.page_count; page++) {
158
+ const pageData = await this.getRoutineFolders(page, 10);
159
+ allFolders.push(...(pageData.routine_folders || []));
160
+ await new Promise(resolve => setTimeout(resolve, 100));
161
+ }
162
+ }
163
+ return allFolders;
164
+ }
165
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,71 @@
1
+ #!/usr/bin/env node
2
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
3
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4
+ import { CallToolRequestSchema, ErrorCode, ListResourcesRequestSchema, ListToolsRequestSchema, McpError, ReadResourceRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
5
+ import { HevyAPIClient } from './hevy-api.js';
6
+ import { tools, toolDefinitions } from './tools.js';
7
+ import { resources, resourceDefinitions } from './resources.js';
8
+ const API_KEY = process.env.HEVY_API_KEY;
9
+ if (!API_KEY) {
10
+ console.error('Error: HEVY_API_KEY environment variable is required');
11
+ process.exit(1);
12
+ }
13
+ const hevyClient = new HevyAPIClient(API_KEY);
14
+ const server = new Server({
15
+ name: 'hevy-mcp-server',
16
+ version: '1.0.0',
17
+ }, {
18
+ capabilities: {
19
+ tools: {},
20
+ resources: {},
21
+ },
22
+ });
23
+ // List available tools
24
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
25
+ return {
26
+ tools: toolDefinitions,
27
+ };
28
+ });
29
+ // Handle tool calls
30
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
31
+ const { name, arguments: args } = request.params;
32
+ if (!tools[name]) {
33
+ throw new McpError(ErrorCode.MethodNotFound, `Tool ${name} not found`);
34
+ }
35
+ try {
36
+ return await tools[name](hevyClient, args);
37
+ }
38
+ catch (error) {
39
+ console.error(`Error executing tool ${name}:`, error);
40
+ throw new McpError(ErrorCode.InternalError, `Error executing tool: ${error instanceof Error ? error.message : String(error)}`);
41
+ }
42
+ });
43
+ // List available resources
44
+ server.setRequestHandler(ListResourcesRequestSchema, async () => {
45
+ return {
46
+ resources: resourceDefinitions,
47
+ };
48
+ });
49
+ // Handle resource reads
50
+ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
51
+ const { uri } = request.params;
52
+ if (!resources[uri]) {
53
+ throw new McpError(ErrorCode.InvalidRequest, `Resource ${uri} not found`);
54
+ }
55
+ try {
56
+ return await resources[uri](hevyClient);
57
+ }
58
+ catch (error) {
59
+ console.error(`Error reading resource ${uri}:`, error);
60
+ throw new McpError(ErrorCode.InternalError, `Error reading resource: ${error instanceof Error ? error.message : String(error)}`);
61
+ }
62
+ });
63
+ async function main() {
64
+ const transport = new StdioServerTransport();
65
+ await server.connect(transport);
66
+ console.error('Hevy MCP server running on stdio');
67
+ }
68
+ main().catch((error) => {
69
+ console.error('Server error:', error);
70
+ process.exit(1);
71
+ });
@@ -0,0 +1,8 @@
1
+ /**
2
+ * MCP Resources for Hevy API data
3
+ * Provides read-only access to workout and fitness data as resources
4
+ */
5
+ import { Resource } from '@modelcontextprotocol/sdk/types.js';
6
+ import { HevyAPIClient } from './hevy-api.js';
7
+ export declare const resourceDefinitions: Resource[];
8
+ export declare const resources: Record<string, (client: HevyAPIClient) => Promise<any>>;