@striderlabs/mcp-orangetheory 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/README.md +199 -0
- package/dist/index.js +22176 -0
- package/package.json +45 -0
- package/server.json +20 -0
- package/src/auth.ts +85 -0
- package/src/browser.ts +1040 -0
- package/src/index.ts +603 -0
- package/tsconfig.json +14 -0
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@striderlabs/mcp-orangetheory",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "MCP server for Orangetheory Fitness - let AI agents find studios, book classes, and track workout performance",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"striderlabs-mcp-orangetheory": "dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"type": "module",
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "esbuild src/index.ts --bundle --platform=node --target=node18 --outfile=dist/index.js --format=esm --banner:js='#!/usr/bin/env node' --external:chromium-bidi --external:patchright --external:patchright-core",
|
|
12
|
+
"start": "node dist/index.js",
|
|
13
|
+
"dev": "tsx src/index.ts"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"mcp",
|
|
17
|
+
"orangetheory",
|
|
18
|
+
"fitness",
|
|
19
|
+
"workout",
|
|
20
|
+
"classes",
|
|
21
|
+
"booking",
|
|
22
|
+
"ai-agent",
|
|
23
|
+
"strider",
|
|
24
|
+
"model-context-protocol",
|
|
25
|
+
"browser-automation",
|
|
26
|
+
"health"
|
|
27
|
+
],
|
|
28
|
+
"author": "Strider Labs <hello@striderlabs.ai>",
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": "https://github.com/striderlabs/mcp-orangetheory"
|
|
33
|
+
},
|
|
34
|
+
"homepage": "https://striderlabs.ai",
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
37
|
+
"patchright": "^1.58.2"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"@types/node": "^20.11.0",
|
|
41
|
+
"esbuild": "^0.20.0",
|
|
42
|
+
"typescript": "^5.3.3"
|
|
43
|
+
},
|
|
44
|
+
"mcpName": "io.github.striderlabs/orangetheory"
|
|
45
|
+
}
|
package/server.json
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
|
|
3
|
+
"name": "io.github.striderlabs/orangetheory",
|
|
4
|
+
"description": "Orangetheory Fitness MCP server — find studios, book classes, and track workout performance. By Strider Labs.",
|
|
5
|
+
"repository": {
|
|
6
|
+
"url": "https://github.com/striderlabs/mcp-orangetheory",
|
|
7
|
+
"source": "github"
|
|
8
|
+
},
|
|
9
|
+
"version": "1.0.0",
|
|
10
|
+
"packages": [
|
|
11
|
+
{
|
|
12
|
+
"registryType": "npm",
|
|
13
|
+
"identifier": "@striderlabs/mcp-orangetheory",
|
|
14
|
+
"version": "1.0.0",
|
|
15
|
+
"transport": {
|
|
16
|
+
"type": "stdio"
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
]
|
|
20
|
+
}
|
package/src/auth.ts
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import os from "os";
|
|
4
|
+
import { BrowserContext } from "patchright";
|
|
5
|
+
|
|
6
|
+
const CONFIG_DIR = path.join(os.homedir(), ".strider", "orangetheory");
|
|
7
|
+
const COOKIES_FILE = path.join(CONFIG_DIR, "cookies.json");
|
|
8
|
+
const SESSION_FILE = path.join(CONFIG_DIR, "session.json");
|
|
9
|
+
|
|
10
|
+
export interface SessionInfo {
|
|
11
|
+
isLoggedIn: boolean;
|
|
12
|
+
userEmail?: string;
|
|
13
|
+
userName?: string;
|
|
14
|
+
memberId?: string;
|
|
15
|
+
homeStudio?: string;
|
|
16
|
+
lastUpdated: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function getConfigDir(): string {
|
|
20
|
+
return CONFIG_DIR;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function ensureConfigDir(): void {
|
|
24
|
+
if (!fs.existsSync(CONFIG_DIR)) {
|
|
25
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export async function saveCookies(context: BrowserContext): Promise<void> {
|
|
30
|
+
ensureConfigDir();
|
|
31
|
+
const cookies = await context.cookies();
|
|
32
|
+
const validCookies = cookies.filter((c) => {
|
|
33
|
+
if (!c.expires || c.expires === -1) return true;
|
|
34
|
+
return c.expires * 1000 > Date.now();
|
|
35
|
+
});
|
|
36
|
+
fs.writeFileSync(COOKIES_FILE, JSON.stringify(validCookies, null, 2));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export async function loadCookies(context: BrowserContext): Promise<boolean> {
|
|
40
|
+
if (!fs.existsSync(COOKIES_FILE)) return false;
|
|
41
|
+
try {
|
|
42
|
+
const raw = fs.readFileSync(COOKIES_FILE, "utf-8");
|
|
43
|
+
const cookies = JSON.parse(raw);
|
|
44
|
+
const validCookies = cookies.filter((c: { expires?: number }) => {
|
|
45
|
+
if (!c.expires || c.expires === -1) return true;
|
|
46
|
+
return c.expires * 1000 > Date.now();
|
|
47
|
+
});
|
|
48
|
+
if (validCookies.length === 0) return false;
|
|
49
|
+
await context.addCookies(validCookies);
|
|
50
|
+
return true;
|
|
51
|
+
} catch {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function saveSessionInfo(info: SessionInfo): void {
|
|
57
|
+
ensureConfigDir();
|
|
58
|
+
fs.writeFileSync(SESSION_FILE, JSON.stringify(info, null, 2));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function loadSessionInfo(): SessionInfo | null {
|
|
62
|
+
if (!fs.existsSync(SESSION_FILE)) return null;
|
|
63
|
+
try {
|
|
64
|
+
const raw = fs.readFileSync(SESSION_FILE, "utf-8");
|
|
65
|
+
return JSON.parse(raw) as SessionInfo;
|
|
66
|
+
} catch {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function clearAuthData(): void {
|
|
72
|
+
if (fs.existsSync(COOKIES_FILE)) fs.unlinkSync(COOKIES_FILE);
|
|
73
|
+
if (fs.existsSync(SESSION_FILE)) fs.unlinkSync(SESSION_FILE);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function hasSavedCookies(): boolean {
|
|
77
|
+
if (!fs.existsSync(COOKIES_FILE)) return false;
|
|
78
|
+
try {
|
|
79
|
+
const raw = fs.readFileSync(COOKIES_FILE, "utf-8");
|
|
80
|
+
const cookies = JSON.parse(raw);
|
|
81
|
+
return Array.isArray(cookies) && cookies.length > 0;
|
|
82
|
+
} catch {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
}
|