@einlogic/mcp-fitbit 2.0.0 → 2.1.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/README.md +1 -0
- package/build/auth-tools.js +31 -0
- package/build/auth.js +49 -0
- package/build/index.js +4 -2
- package/package.json +74 -74
package/README.md
CHANGED
|
@@ -131,6 +131,7 @@ List tools accept `pageSize` and `pageToken` (use `nextPageToken` from the respo
|
|
|
131
131
|
| `get_identity` | `users/me/identity` | none |
|
|
132
132
|
| `get_settings` | `users/me/settings` | none |
|
|
133
133
|
| `list_paired_devices` | `users/me/pairedDevices` | none |
|
|
134
|
+
| `reauthenticate` | not an API call: deletes the local tokens (memory + `.google-health-token.json`) and opens a new Google sign-in; new tokens are saved on approval, no restart needed. Cancels a pending sign-in. Confirm with `get_identity` | none |
|
|
134
135
|
| `list_data_points` | any of the 38 data types | `dataType`, optional raw `filter` |
|
|
135
136
|
| `reconcile_data_points` | `dataPoints:reconcile` for any data type | `dataType`, optional `filter`, `dataSourceFamily` |
|
|
136
137
|
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { registerTool, createSuccessResponse, createErrorResponse, } from './utils.js';
|
|
2
|
+
/**
|
|
3
|
+
* Registers the re-authorization tool.
|
|
4
|
+
* @param server The McpServer instance.
|
|
5
|
+
* @param reauthenticate Clears the local tokens and starts a new OAuth flow.
|
|
6
|
+
*/
|
|
7
|
+
export function registerAuthTools(server, reauthenticate) {
|
|
8
|
+
registerTool(server, {
|
|
9
|
+
name: 'reauthenticate',
|
|
10
|
+
description: "Clear this MCP server's locally stored Google Health tokens (memory and .google-health-token.json) and start a new Google sign-in. A browser window opens for consent; once approved, new tokens are saved and the other tools work again without restarting the server. Use when the token has expired (testing-mode refresh tokens last 7 days), was revoked, or the wrong Google account is connected. Cancels any sign-in that is still pending. Verify afterwards with get_identity.",
|
|
11
|
+
parametersSchema: {},
|
|
12
|
+
handler: async () => {
|
|
13
|
+
try {
|
|
14
|
+
const { tokenFileCleared, authUrl } = await reauthenticate();
|
|
15
|
+
if (!authUrl) {
|
|
16
|
+
return createErrorResponse('Local tokens were cleared, but the authorization flow could not start. Check GOOGLE_HEALTH_CLIENT_ID / GOOGLE_HEALTH_CLIENT_SECRET and that the OAuth callback port is free, then call reauthenticate again.');
|
|
17
|
+
}
|
|
18
|
+
return createSuccessResponse({
|
|
19
|
+
status: 'authorization_started',
|
|
20
|
+
tokenFileCleared,
|
|
21
|
+
authUrl,
|
|
22
|
+
next: `Approve access in the browser window that opened (or open ${authUrl} manually). Tools will fail until you finish; then call get_identity to confirm.`,
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
console.error('Re-authentication failed:', error);
|
|
27
|
+
return createErrorResponse(`Re-authentication failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
}
|
package/build/auth.js
CHANGED
|
@@ -280,6 +280,55 @@ export function startAuthorizationFlow() {
|
|
|
280
280
|
oauthServer = null;
|
|
281
281
|
});
|
|
282
282
|
}
|
|
283
|
+
/**
|
|
284
|
+
* Stops a pending authorization callback server (if any) and waits until its
|
|
285
|
+
* port is free, so a new flow can bind to it.
|
|
286
|
+
*/
|
|
287
|
+
async function stopAuthorizationServer() {
|
|
288
|
+
const server = oauthServer;
|
|
289
|
+
if (!server)
|
|
290
|
+
return;
|
|
291
|
+
oauthServer = null;
|
|
292
|
+
await new Promise((resolve) => {
|
|
293
|
+
server.close(() => resolve());
|
|
294
|
+
// Drop keep-alive connections (e.g. an open browser tab) so close() completes
|
|
295
|
+
server.closeAllConnections?.();
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* Forgets the current token: clears it from memory and deletes the token file.
|
|
300
|
+
* @returns true when a token file was deleted, false when there was none.
|
|
301
|
+
*/
|
|
302
|
+
export async function clearStoredToken() {
|
|
303
|
+
accessToken = null;
|
|
304
|
+
tokenData = null;
|
|
305
|
+
try {
|
|
306
|
+
await fs.unlink(TOKEN_FILE_PATH);
|
|
307
|
+
console.error(`Token file deleted: ${TOKEN_FILE_PATH}`);
|
|
308
|
+
return true;
|
|
309
|
+
}
|
|
310
|
+
catch (error) {
|
|
311
|
+
if (error.code === 'ENOENT')
|
|
312
|
+
return false;
|
|
313
|
+
throw error;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Clears the local tokens and starts a fresh authorization flow. Any flow that
|
|
318
|
+
* is still waiting for a callback is cancelled first. The new token is stored
|
|
319
|
+
* (in memory and on disk) once the user finishes consent in the browser.
|
|
320
|
+
* @returns whether a token file was removed, and the URL that starts consent
|
|
321
|
+
* (null when the flow could not start, e.g. missing client credentials).
|
|
322
|
+
*/
|
|
323
|
+
export async function reauthenticate() {
|
|
324
|
+
await stopAuthorizationServer();
|
|
325
|
+
const tokenFileCleared = await clearStoredToken();
|
|
326
|
+
startAuthorizationFlow();
|
|
327
|
+
return {
|
|
328
|
+
tokenFileCleared,
|
|
329
|
+
authUrl: oauthServer ? `http://localhost:${PORT}/auth` : null,
|
|
330
|
+
};
|
|
331
|
+
}
|
|
283
332
|
/**
|
|
284
333
|
* Retrieves the current Google Health access token.
|
|
285
334
|
* Automatically checks for token expiry and refreshes if necessary.
|
package/build/index.js
CHANGED
|
@@ -5,7 +5,7 @@ import dotenv from 'dotenv';
|
|
|
5
5
|
import path from 'path';
|
|
6
6
|
import { fileURLToPath } from 'url';
|
|
7
7
|
// Import authentication functions
|
|
8
|
-
import { initializeAuth, startAuthorizationFlow, getAccessToken, } from './auth.js';
|
|
8
|
+
import { initializeAuth, startAuthorizationFlow, getAccessToken, reauthenticate, } from './auth.js';
|
|
9
9
|
// Import tool registration function(s)
|
|
10
10
|
import { registerWeightTool } from './weight.js';
|
|
11
11
|
import { registerSleepTool } from './sleep.js';
|
|
@@ -18,6 +18,7 @@ import { registerActivityTimeSeriesTool } from './activity-timeseries.js';
|
|
|
18
18
|
import { registerAzmTimeSeriesTool } from './azm-timeseries.js';
|
|
19
19
|
import { registerHrvTools } from './hrv.js';
|
|
20
20
|
import { registerAccountTools } from './account.js';
|
|
21
|
+
import { registerAuthTools } from './auth-tools.js';
|
|
21
22
|
import { registerDataPointTools } from './data-points.js';
|
|
22
23
|
import { registerCompatTools } from './compat/tools.js';
|
|
23
24
|
import { resolveClientCredentials, resolveOutputMode } from './config.js';
|
|
@@ -58,7 +59,7 @@ console.error('✅ Environment variables loaded successfully');
|
|
|
58
59
|
// Create the main MCP server instance
|
|
59
60
|
const server = new McpServer({
|
|
60
61
|
name: 'google-health',
|
|
61
|
-
version: '2.
|
|
62
|
+
version: '2.1.0',
|
|
62
63
|
capabilities: {
|
|
63
64
|
resources: {},
|
|
64
65
|
tools: {}, // Tools are registered dynamically below
|
|
@@ -84,6 +85,7 @@ else {
|
|
|
84
85
|
registerProfileTool(server, getAccessToken);
|
|
85
86
|
registerActivitiesTool(server, getAccessToken);
|
|
86
87
|
registerAccountTools(server, getAccessToken);
|
|
88
|
+
registerAuthTools(server, reauthenticate);
|
|
87
89
|
registerDataPointTools(server, getAccessToken);
|
|
88
90
|
// --- Main Application Entry Point ---
|
|
89
91
|
async function main() {
|
package/package.json
CHANGED
|
@@ -1,74 +1,74 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@einlogic/mcp-fitbit",
|
|
3
|
-
"version": "2.
|
|
4
|
-
"type": "module",
|
|
5
|
-
"main": "./build/index.js",
|
|
6
|
-
"bin": {
|
|
7
|
-
"mcp-fitbit": "./build/index.js",
|
|
8
|
-
"mkoziel2000-mcp-fitbit": "./build/index.js"
|
|
9
|
-
},
|
|
10
|
-
"repository": {
|
|
11
|
-
"type": "git",
|
|
12
|
-
"url": "https://github.com/mkoziel2000/mcp-fitbit.git"
|
|
13
|
-
},
|
|
14
|
-
"homepage": "https://github.com/mkoziel2000/mcp-fitbit",
|
|
15
|
-
"bugs": {
|
|
16
|
-
"url": "https://github.com/mkoziel2000/mcp-fitbit/issues"
|
|
17
|
-
},
|
|
18
|
-
"scripts": {
|
|
19
|
-
"test": "vitest run",
|
|
20
|
-
"test:ui": "vitest --ui",
|
|
21
|
-
"test:coverage": "vitest run --coverage",
|
|
22
|
-
"build": "tsc",
|
|
23
|
-
"start": "node build/index.js",
|
|
24
|
-
"dev": "npm run build && npx @modelcontextprotocol/inspector node build/index.js",
|
|
25
|
-
"lint": "eslint src/**/*.ts --ignore-pattern src/**/*.test.ts",
|
|
26
|
-
"lint:fix": "eslint src/**/*.ts --fix",
|
|
27
|
-
"format": "prettier --write src/**/*.ts",
|
|
28
|
-
"format:check": "prettier --check src/**/*.ts",
|
|
29
|
-
"prepublishOnly": "npm run build && npm run test && npm run lint",
|
|
30
|
-
"prepack": "npm run build"
|
|
31
|
-
},
|
|
32
|
-
"keywords": [
|
|
33
|
-
"mcp",
|
|
34
|
-
"fitbit",
|
|
35
|
-
"google-health-api",
|
|
36
|
-
"mcp-server",
|
|
37
|
-
"claude",
|
|
38
|
-
"model-context-protocol",
|
|
39
|
-
"health-data",
|
|
40
|
-
"fitness-tracking",
|
|
41
|
-
"ai-assistant",
|
|
42
|
-
"typescript"
|
|
43
|
-
],
|
|
44
|
-
"author": "mkoziel2000",
|
|
45
|
-
"license": "MIT",
|
|
46
|
-
"description": "MCP server for accessing Fitbit / Pixel Watch health and fitness data through the Google Health API. Connect AI assistants like Claude to your data for personalized health insights.",
|
|
47
|
-
"files": [
|
|
48
|
-
"build/",
|
|
49
|
-
"README.md",
|
|
50
|
-
"LICENSE"
|
|
51
|
-
],
|
|
52
|
-
"dependencies": {
|
|
53
|
-
"@modelcontextprotocol/sdk": "^1.11.0",
|
|
54
|
-
"@types/express": "^5.0.1",
|
|
55
|
-
"dotenv": "^16.5.0",
|
|
56
|
-
"express": "^5.1.0",
|
|
57
|
-
"open": "^10.1.2",
|
|
58
|
-
"simple-oauth2": "^5.1.0",
|
|
59
|
-
"zod": "^3.24.3"
|
|
60
|
-
},
|
|
61
|
-
"devDependencies": {
|
|
62
|
-
"@eslint/js": "^9.27.0",
|
|
63
|
-
"@types/node": "^22.15.3",
|
|
64
|
-
"@types/simple-oauth2": "^5.0.7",
|
|
65
|
-
"@typescript-eslint/eslint-plugin": "^8.32.1",
|
|
66
|
-
"@typescript-eslint/parser": "^8.32.1",
|
|
67
|
-
"@vitest/coverage-v8": "^3.1.4",
|
|
68
|
-
"@vitest/ui": "^3.1.4",
|
|
69
|
-
"eslint": "^9.27.0",
|
|
70
|
-
"prettier": "^3.5.3",
|
|
71
|
-
"typescript": "^5.8.3",
|
|
72
|
-
"vitest": "^3.1.4"
|
|
73
|
-
}
|
|
74
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@einlogic/mcp-fitbit",
|
|
3
|
+
"version": "2.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "./build/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"mcp-fitbit": "./build/index.js",
|
|
8
|
+
"mkoziel2000-mcp-fitbit": "./build/index.js"
|
|
9
|
+
},
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "https://github.com/mkoziel2000/mcp-fitbit.git"
|
|
13
|
+
},
|
|
14
|
+
"homepage": "https://github.com/mkoziel2000/mcp-fitbit",
|
|
15
|
+
"bugs": {
|
|
16
|
+
"url": "https://github.com/mkoziel2000/mcp-fitbit/issues"
|
|
17
|
+
},
|
|
18
|
+
"scripts": {
|
|
19
|
+
"test": "vitest run",
|
|
20
|
+
"test:ui": "vitest --ui",
|
|
21
|
+
"test:coverage": "vitest run --coverage",
|
|
22
|
+
"build": "tsc",
|
|
23
|
+
"start": "node build/index.js",
|
|
24
|
+
"dev": "npm run build && npx @modelcontextprotocol/inspector node build/index.js",
|
|
25
|
+
"lint": "eslint src/**/*.ts --ignore-pattern src/**/*.test.ts",
|
|
26
|
+
"lint:fix": "eslint src/**/*.ts --fix",
|
|
27
|
+
"format": "prettier --write src/**/*.ts",
|
|
28
|
+
"format:check": "prettier --check src/**/*.ts",
|
|
29
|
+
"prepublishOnly": "npm run build && npm run test && npm run lint",
|
|
30
|
+
"prepack": "npm run build"
|
|
31
|
+
},
|
|
32
|
+
"keywords": [
|
|
33
|
+
"mcp",
|
|
34
|
+
"fitbit",
|
|
35
|
+
"google-health-api",
|
|
36
|
+
"mcp-server",
|
|
37
|
+
"claude",
|
|
38
|
+
"model-context-protocol",
|
|
39
|
+
"health-data",
|
|
40
|
+
"fitness-tracking",
|
|
41
|
+
"ai-assistant",
|
|
42
|
+
"typescript"
|
|
43
|
+
],
|
|
44
|
+
"author": "mkoziel2000",
|
|
45
|
+
"license": "MIT",
|
|
46
|
+
"description": "MCP server for accessing Fitbit / Pixel Watch health and fitness data through the Google Health API. Connect AI assistants like Claude to your data for personalized health insights.",
|
|
47
|
+
"files": [
|
|
48
|
+
"build/",
|
|
49
|
+
"README.md",
|
|
50
|
+
"LICENSE"
|
|
51
|
+
],
|
|
52
|
+
"dependencies": {
|
|
53
|
+
"@modelcontextprotocol/sdk": "^1.11.0",
|
|
54
|
+
"@types/express": "^5.0.1",
|
|
55
|
+
"dotenv": "^16.5.0",
|
|
56
|
+
"express": "^5.1.0",
|
|
57
|
+
"open": "^10.1.2",
|
|
58
|
+
"simple-oauth2": "^5.1.0",
|
|
59
|
+
"zod": "^3.24.3"
|
|
60
|
+
},
|
|
61
|
+
"devDependencies": {
|
|
62
|
+
"@eslint/js": "^9.27.0",
|
|
63
|
+
"@types/node": "^22.15.3",
|
|
64
|
+
"@types/simple-oauth2": "^5.0.7",
|
|
65
|
+
"@typescript-eslint/eslint-plugin": "^8.32.1",
|
|
66
|
+
"@typescript-eslint/parser": "^8.32.1",
|
|
67
|
+
"@vitest/coverage-v8": "^3.1.4",
|
|
68
|
+
"@vitest/ui": "^3.1.4",
|
|
69
|
+
"eslint": "^9.27.0",
|
|
70
|
+
"prettier": "^3.5.3",
|
|
71
|
+
"typescript": "^5.8.3",
|
|
72
|
+
"vitest": "^3.1.4"
|
|
73
|
+
}
|
|
74
|
+
}
|