Skip to content

Commit 91948a7

Browse files
committed
feat(arguments): add possibility to provide key as argument
1 parent 15fcff1 commit 91948a7

6 files changed

Lines changed: 163 additions & 33 deletions

File tree

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,4 +35,4 @@ USER nodejs
3535

3636
ENV NODE_ENV=production
3737

38-
CMD [ "npm", "run", "start:http" ]
38+
CMD [ "npm", "start", "--", "--http" ]

README.md

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -64,13 +64,28 @@ Make sure to replace `your-api-key-here` with your actual Hevy API key.
6464

6565
## Configuration
6666

67-
Create a `.env` file in the project root (you can copy from [.env.sample](.env.sample)) with the following content:
67+
You can supply your Hevy API key in two ways:
68+
69+
1. Environment variable (`HEVY_API_KEY`)
70+
2. Command-line argument (`--hevy-api-key=your_key` or `hevy-api-key=your_key` after `--` when using npm scripts)
71+
72+
Create a `.env` file in the project root (you can copy from [.env.sample](.env.sample)) with the following content if using the environment variable approach:
6873

6974
```env
7075
HEVY_API_KEY=your_hevy_api_key_here
7176
```
7277

73-
Replace `your_hevy_api_key_here` with your actual Hevy API key.
78+
Replace `your_hevy_api_key_here` with your actual Hevy API key. If you prefer the command argument approach you can skip setting the environment variable and start the server with for example:
79+
80+
```bash
81+
npm start -- --hevy-api-key=your_hevy_api_key_here
82+
```
83+
84+
Or in HTTP mode:
85+
86+
```bash
87+
npm start -- --http --hevy-api-key=your_hevy_api_key_here
88+
```
7489

7590
## Transport Modes
7691

@@ -91,10 +106,12 @@ node dist/index.js
91106
The server can also run in HTTP mode for remote access or web-based integrations:
92107

93108
```bash
94-
# Start in HTTP mode
109+
# Start in HTTP mode (env var)
95110
npm start -- --http
96-
# or
97-
node dist/index.js --http
111+
# Start in HTTP mode (CLI arg)
112+
npm start -- --http --hevy-api-key=your_hevy_api_key_here
113+
# Or using node directly
114+
node dist/index.js --http --hevy-api-key=your_hevy_api_key_here
98115

99116
# Using environment variable
100117
MCP_TRANSPORT=http npm start
@@ -161,6 +178,13 @@ docker run -d \
161178
-e HEVY_API_KEY=your_api_key_here \
162179
-p 3000:3000 \
163180
ghcr.io/chrisdoc/hevy-mcp:latest
181+
182+
# Or using CLI argument for the key (omit env var)
183+
docker run -d \
184+
--name hevy-mcp \
185+
-p 3000:3000 \
186+
ghcr.io/chrisdoc/hevy-mcp:latest \
187+
hevy-api-key=your_api_key_here
164188
```
165189

166190
#### Building Locally
@@ -175,6 +199,13 @@ docker run -d \
175199
-e HEVY_API_KEY=your_api_key_here \
176200
-p 3000:3000 \
177201
hevy-mcp
202+
203+
# Or with CLI argument
204+
docker run -d \
205+
--name hevy-mcp \
206+
-p 3000:3000 \
207+
hevy-mcp \
208+
hevy-api-key=your_api_key_here
178209
```
179210

180211
#### Docker Compose Example

smithery.yaml

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,13 @@ startCommand:
88
configSchema:
99
# JSON Schema defining the configuration options for the MCP.
1010
type: object
11-
required:
12-
- hevyApiKey
11+
required: ["hevy-api-key"]
1312
properties:
14-
hevyApiKey:
13+
hevy-api-key:
1514
type: string
1615
description: Your Hevy API key to authenticate with the Hevy Fitness API.
1716
exampleConfig:
18-
hevyApiKey: "your-hevy-api-key-here"
17+
hevy-api-key: "your-hevy-api-key-here"
1918
build:
2019
dockerfile: "Dockerfile"
2120
dockerBuildPath: "."

src/index.ts

Lines changed: 15 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#!/usr/bin/env node
22
import "@dotenvx/dotenvx/config";
3+
// Import tool registration functions
34
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
45
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
56
import { name, version } from "../package.json";
@@ -9,38 +10,27 @@ import { registerTemplateTools } from "./tools/templates.js";
910
import { registerWebhookTools } from "./tools/webhooks.js";
1011
// Import tool registration functions
1112
import { registerWorkoutTools } from "./tools/workouts.js";
13+
import { assertApiKey, parseConfig } from "./utils/config.js";
1214
import { createClient } from "./utils/hevyClient.js";
1315
import { createHttpServer } from "./utils/httpServer.js";
1416

1517
const HEVY_API_BASEURL = "https://api.hevyapp.com";
1618

17-
// Parse command line arguments
19+
// Parse config (CLI args + env)
1820
const args = process.argv.slice(2);
19-
const transportMode =
20-
args.includes("--http") || process.env.MCP_TRANSPORT === "http"
21-
? "http"
22-
: "stdio";
23-
const httpPort = Number.parseInt(process.env.PORT || "3000", 10);
24-
const httpHost = process.env.MCP_HTTP_HOST || "127.0.0.1";
25-
const enableDnsRebindingProtection =
26-
process.env.MCP_DNS_REBINDING_PROTECTION === "true";
27-
const allowedHosts = process.env.MCP_ALLOWED_HOSTS?.split(",") || ["127.0.0.1"];
21+
const cfg = parseConfig(args, process.env);
2822

2923
// Create server instance
3024
const server = new McpServer({
3125
name,
3226
version,
3327
});
3428

35-
// Check for API key
36-
if (!process.env.HEVY_API_KEY) {
37-
console.error("HEVY_API_KEY environment variable is not set");
38-
process.exit(1);
39-
}
29+
// Validate API key presence
30+
assertApiKey(cfg.apiKey);
4031

4132
// Configure client
42-
// We've already checked for HEVY_API_KEY existence above, so it's safe to use here
43-
const apiKey = process.env.HEVY_API_KEY || "";
33+
const apiKey = cfg.apiKey;
4434
const hevyClient = createClient(apiKey, HEVY_API_BASEURL);
4535

4636
// Register all tools
@@ -52,13 +42,15 @@ registerWebhookTools(server, hevyClient);
5242

5343
// Start the server
5444
async function runServer() {
55-
if (transportMode === "http") {
56-
console.log(`Starting MCP server in HTTP mode on ${httpHost}:${httpPort}`);
45+
if (cfg.transportMode === "http") {
46+
console.log(
47+
`Starting MCP server in HTTP mode on ${cfg.httpHost}:${cfg.httpPort}`,
48+
);
5749
const httpServer = createHttpServer(server, {
58-
port: httpPort,
59-
host: httpHost,
60-
enableDnsRebindingProtection,
61-
allowedHosts,
50+
port: cfg.httpPort,
51+
host: cfg.httpHost,
52+
enableDnsRebindingProtection: cfg.enableDnsRebindingProtection,
53+
allowedHosts: cfg.allowedHosts,
6254
});
6355
await httpServer.startServer();
6456
} else {

src/utils/config.test.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { describe, expect, it } from "vitest";
2+
import { parseConfig } from "./config.js";
3+
4+
function env(vars: Record<string, string | undefined>): NodeJS.ProcessEnv {
5+
return { ...process.env, ...vars } as NodeJS.ProcessEnv;
6+
}
7+
8+
describe("parseConfig", () => {
9+
it("prefers --hevy-api-key= over env", () => {
10+
const cfg = parseConfig(
11+
["--hevy-api-key=cliKey"],
12+
env({ HEVY_API_KEY: "envKey" }),
13+
);
14+
expect(cfg.apiKey).toBe("cliKey");
15+
});
16+
17+
it("supports bare hevy-api-key= form", () => {
18+
const cfg = parseConfig(["hevy-api-key=bareKey"], env({}));
19+
expect(cfg.apiKey).toBe("bareKey");
20+
});
21+
22+
it("falls back to env HEVY_API_KEY", () => {
23+
const cfg = parseConfig([], env({ HEVY_API_KEY: "envOnly" }));
24+
expect(cfg.apiKey).toBe("envOnly");
25+
});
26+
27+
it("detects http transport via --http", () => {
28+
const cfg = parseConfig(["--http"], env({ HEVY_API_KEY: "k" }));
29+
expect(cfg.transportMode).toBe("http");
30+
});
31+
32+
it("detects http transport via env", () => {
33+
const cfg = parseConfig(
34+
[],
35+
env({ MCP_TRANSPORT: "http", HEVY_API_KEY: "k" }),
36+
);
37+
expect(cfg.transportMode).toBe("http");
38+
});
39+
});

src/utils/config.ts

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
export interface HevyConfig {
2+
apiKey: string;
3+
transportMode: "http" | "stdio";
4+
httpHost: string;
5+
httpPort: number;
6+
enableDnsRebindingProtection: boolean;
7+
allowedHosts: string[];
8+
}
9+
10+
/**
11+
* Parse CLI arguments and environment to derive configuration.
12+
* Priority order for API key: CLI flag forms > environment variable.
13+
* Supported CLI arg forms:
14+
* --hevy-api-key=KEY
15+
* --hevyApiKey=KEY
16+
* hevy-api-key=KEY (bare, e.g. when passed after npm start -- )
17+
*/
18+
export function parseConfig(
19+
argv: string[],
20+
env: NodeJS.ProcessEnv,
21+
): HevyConfig {
22+
let apiKey = "";
23+
const apiKeyArgPatterns = [
24+
/^--hevy-api-key=(.+)$/i,
25+
/^--hevyApiKey=(.+)$/i,
26+
/^hevy-api-key=(.+)$/i,
27+
];
28+
for (const raw of argv) {
29+
for (const pattern of apiKeyArgPatterns) {
30+
const m = raw.match(pattern);
31+
if (m) {
32+
apiKey = m[1];
33+
break;
34+
}
35+
}
36+
if (apiKey) break;
37+
}
38+
if (!apiKey) {
39+
apiKey = env.HEVY_API_KEY || "";
40+
}
41+
42+
const transportMode: "http" | "stdio" =
43+
argv.includes("--http") || env.MCP_TRANSPORT === "http" ? "http" : "stdio";
44+
const httpPort = Number.parseInt(env.MCP_HTTP_PORT || "3000", 10);
45+
const httpHost = env.MCP_HTTP_HOST || "127.0.0.1";
46+
const enableDnsRebindingProtection =
47+
env.MCP_DNS_REBINDING_PROTECTION === "true";
48+
const allowedHosts = env.MCP_ALLOWED_HOSTS?.split(",")
49+
.map((h) => h.trim())
50+
.filter(Boolean) || ["127.0.0.1"];
51+
52+
return {
53+
apiKey,
54+
transportMode,
55+
httpHost,
56+
httpPort,
57+
enableDnsRebindingProtection,
58+
allowedHosts,
59+
};
60+
}
61+
62+
export function assertApiKey(apiKey: string) {
63+
if (!apiKey) {
64+
console.error(
65+
"Hevy API key is required. Provide via HEVY_API_KEY env variable or --hevy-api-key=YOUR_KEY command argument.",
66+
);
67+
process.exit(1);
68+
}
69+
}

0 commit comments

Comments
 (0)