chore: merge upstream chrisdoc/hevy-mcp v1.23.8 - #317
Conversation
* feat: implement Streamable HTTP transport Replace the HTTP/SSE stub with a real StreamableHTTPServerTransport server. Sessions are tracked per-connection; unknown session IDs return 404 to match SDK semantics. - Add `transport` and `port` to HevyConfig; parse --transport and --port - Replace createHttpServer stub with startHttpServer() returning http.Server - Branch runServer() on --transport=http vs stdio (default) - Update unit and integration tests; add config parsing tests - Document HTTP mode in README, remove "stdio only" deprecation notice * fix: address Copilot review comments on HTTP transport - Wrap async request handler in try/catch; send 500 JSON on unhandled errors - Fix TDZ: declare `let mcpServer` before StreamableHTTPServerTransport construction - Normalize mcp-session-id header to handle string[] values - Add 1 MiB body size limit; drain socket before responding 413 / 400 - Set Content-Type + Content-Length on all JSON error responses via helper - Add HTTP behavior unit tests: non-/mcp 404, unknown session 404 JSON, oversized body 413 - Wrap client.close() in finally block in integration test - Validate --port range (0-65535) in parseConfig; add test
* feat: implement OAuth 2.1 HTTP transport (http+oauth) Adds password-gated OAuth 2.1 authorization server + MCP resource server compatible with claude.ai Connectors. - SQLiteOAuthProvider: full OAuthServerProvider implementation backed by better-sqlite3 (WAL mode, 4-table schema, token rotation, family revocation) - consent router: timing-safe password check, HTML consent form - config: http+oauth transport, --issuer-url / MCP_ISSUER_URL - httpServer: startOAuthHttpServer using SDK mcpAuthRouter + requireBearerAuth - index: http+oauth branch in runServer() - Tests: 43 new tests across oauthProvider, consent, config, and httpServer.oauth - Dockerfile.oauth + docker-compose.yml for local deployment * style: fix oxfmt formatting in httpServer.ts * fix: address PR review comments - consent: validate session ID as 32-hex, HTML-escape all dynamic values (XSS) - consent: exact-length check before timingSafeEqual to prevent prefix bypass - oauthProvider: validate redirectUri before consuming auth code; wrap delete+insert in a single transaction to prevent race/DoS - oauthProvider: move refresh token read+delete+insert into one transaction to prevent concurrent use producing multiple new tokens - oauthProvider: add TTL (10 min) and max-size cap (1000) to pendingSessions to prevent memory leaks from abandoned authorization flows - httpServer: add express.json() with 1 MiB limit before /mcp route so req.body is parsed in OAuth server - httpServer.oauth.test: save/restore MCP_DANGEROUSLY_ALLOW_INSECURE_ISSUER_URL in beforeAll/afterAll to prevent env leak across test files - oauthProvider.test: actually force expiry via DB update to test expired auth code path - docker-compose.yml: remove external network dependency; use default bridge
Brings in body-measurements MCP tools (chrisdoc#306) and the regenerated Kubb client (chrisdoc#305) from upstream; preserves fork-only Streamable HTTP and OAuth 2.1 transports.
|
Opened in wrong repo — this was meant to target the fork (aaearon/hevy-mcp). |
|
Warning Rate limit exceeded
You’ve run out of usage credits. Purchase more in the billing tab. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (17)
📝 WalkthroughWalkthroughThis PR adds OAuth 2.1 authorization to the MCP server with a password-gated consent step and local SQLite persistence, enabling authenticated HTTP transport alongside stdio and HTTP modes. The implementation includes configuration extensions, a SQLite OAuth provider, a password-protected consent router, and dual HTTP server implementations. ChangesOAuth 2.1 Integration
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Suggested labels
Poem
✨ Finishing Touches🧪 Generate unit tests (beta)
Tip 💬 Introducing Slack Agent: The best way for teams to turn conversations into code.Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.
Built for teams:
One agent for your entire SDLC. Right inside Slack. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
✨ PR Review
LGTM
Generated by LinearB AI and added by gitStream.
AI-generated content may contain inaccuracies. Please verify before using.
💡 Tip: You can customize your AI Review using Guidelines Learn how
Greptile SummaryThis PR merges upstream
Confidence Score: 4/5Safe to merge with one item to address before pushing to a public remote: the Traefik config file exposes personal infrastructure. The OAuth implementation is well-structured — code consumption is atomic, refresh tokens are rotated with family-wide revocation, the consent form uses timing-safe comparison, and all user-controlled values are HTML-escaped. The one concrete issue is deploy/traefik-hevy-mcp.yml should be sanitised or removed before the branch is published publicly; src/utils/oauthProvider.ts line 322 deserves a second look regarding resource propagation on refresh.
|
| Filename | Overview |
|---|---|
| deploy/traefik-hevy-mcp.yml | New Traefik routing config that hardcodes a personal public hostname and a private LAN IP, inadvertently publishing home-network topology to a public repo. |
| src/utils/oauthProvider.ts | New SQLite-backed OAuth 2.1 provider; authorization code replay prevention is atomic, refresh token rotation is family-wide, but the resource indicator is silently dropped on refresh exchanges. |
| src/utils/consent.ts | Password-gated consent form using timing-safe comparison, proper HTML escaping, and a consumed-once session mechanism. |
| src/utils/httpServer.ts | Adds startOAuthHttpServer alongside the existing startHttpServer; both handle per-session MCP transport lifecycle correctly. |
| src/index.ts | Adds http+oauth transport branch and wires startOAuthHttpServer; logic is straightforward and consistent with existing transport handling. |
| src/utils/config.ts | Extends config parsing with --transport=http+oauth, --issuer-url, MCP_ISSUER_URL, and assertIssuerUrl; well-tested. |
| docker-compose.yml | New Docker Compose service wiring Dockerfile.oauth with a persistent volume for the SQLite DB; correctly externalizes secrets via .env. |
| Dockerfile.oauth | New Dockerfile for the OAuth transport; functional but includes devDependencies in the image. |
Reviews (1): Last reviewed commit: "chore: merge upstream chrisdoc/hevy-mcp ..." | Re-trigger Greptile
| http: | ||
| routers: | ||
| hevy-mcp: | ||
| rule: "Host(`hevymcp.ams.iosharp.com`)" | ||
| entryPoints: | ||
| - web-secure | ||
| service: hevy-mcp | ||
| tls: | ||
| certResolver: myresolver | ||
| services: | ||
| hevy-mcp: | ||
| loadBalancer: | ||
| servers: | ||
| - url: "http://192.168.178.42:8012" |
There was a problem hiding this comment.
Hardcoded personal infrastructure details committed to a public repository
This file contains a personal public hostname (hevymcp.ams.iosharp.com) and a private LAN IP (192.168.178.42:8012), effectively publishing your home network topology. Anyone reading this public fork can identify a live service at that domain and infer your local network layout. Consider either removing this file, adding it to .gitignore, or replacing the values with generic placeholders (e.g. your-domain.example.com / YOUR_HOST_IP:8012) before pushing to a public repo.
| this.db | ||
| .prepare( | ||
| `INSERT INTO access_tokens (token, client_id, scopes, expires_at, resource, family_id) | ||
| VALUES (?, ?, ?, ?, ?, ?)`, | ||
| ) | ||
| .run( | ||
| accessTok, | ||
| client.client_id, | ||
| JSON.stringify(effectiveScopes), | ||
| now + ACCESS_TOKEN_TTL, | ||
| null, | ||
| familyId, | ||
| ); |
There was a problem hiding this comment.
Pass the
_resource parameter through to the new access token instead of always inserting null, so resource-bound tokens remain bound after rotation.
| this.db | |
| .prepare( | |
| `INSERT INTO access_tokens (token, client_id, scopes, expires_at, resource, family_id) | |
| VALUES (?, ?, ?, ?, ?, ?)`, | |
| ) | |
| .run( | |
| accessTok, | |
| client.client_id, | |
| JSON.stringify(effectiveScopes), | |
| now + ACCESS_TOKEN_TTL, | |
| null, | |
| familyId, | |
| ); | |
| this.db | |
| .prepare( | |
| `INSERT INTO access_tokens (token, client_id, scopes, expires_at, resource, family_id) | |
| VALUES (?, ?, ?, ?, ?, ?)`, | |
| ) | |
| .run( | |
| accessTok, | |
| client.client_id, | |
| JSON.stringify(effectiveScopes), | |
| now + ACCESS_TOKEN_TTL, | |
| _resource?.toString() ?? null, | |
| familyId, | |
| ); |
There was a problem hiding this comment.
Pull request overview
Merges upstream chrisdoc/hevy-mcp v1.23.8 into the fork while keeping/advancing the fork’s HTTP transports by adding Streamable HTTP and an OAuth 2.1–backed HTTP mode (with SQLite persistence + password-gated consent UI), plus corresponding config parsing, docs, and tests.
Changes:
- Added Streamable HTTP server support (
--transport=http) and HTTP+OAuth mode (--transport=http+oauth) with consent flow and SQLite-backed token storage. - Extended CLI configuration parsing to support
--transport,--port, and--issuer-url(plusMCP_ISSUER_URL). - Added/updated unit + integration tests and refreshed docs/deployment artifacts for the new transport options.
Reviewed changes
Copilot reviewed 17 out of 18 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/integration/http-transport.integration.test.ts | Integration test that connects via Streamable HTTP and lists tools. |
| src/utils/oauthProvider.ts | SQLite-backed OAuth provider implementation (auth codes, access/refresh tokens, revocation). |
| src/utils/oauthProvider.test.ts | Unit tests for OAuth provider flows (clients, codes, refresh rotation, revocation). |
| src/utils/httpServer.ts | Streamable HTTP server and Express-based OAuth HTTP server entrypoints. |
| src/utils/httpServer.test.ts | Unit tests for Streamable HTTP server (404s, oversized body handling). |
| src/utils/httpServer.oauth.test.ts | Unit tests for OAuth router + bearer auth middleware behavior. |
| src/utils/consent.ts | Password-gated consent UI + redirect-to-client with auth code. |
| src/utils/consent.test.ts | Unit tests for consent UI/session consumption and password handling. |
| src/utils/config.ts | CLI/env config parsing and assertions for API key + issuer URL. |
| src/utils/config.test.ts | Unit tests for new config flags and issuer URL assertion behavior. |
| src/index.ts | Wires new transport modes into runtime entrypoint. |
| README.md | Adds Streamable HTTP docs (but currently doesn’t cover http+oauth). |
| package.json | Adds dependencies for OAuth HTTP mode (express, better-sqlite3, supertest types). |
| package-lock.json | Lockfile updates for added dependencies. |
| Dockerfile.oauth | Container build/run for --transport=http+oauth. |
| docker-compose.yml | Compose setup with persisted OAuth DB volume. |
| deploy/traefik-hevy-mcp.yml | Traefik router/service config for an HTTP deployment. |
| CLAUDE.md | Documents http+oauth transport usage, env vars, and Docker compose flow. |
| client.client_id, | ||
| JSON.stringify(effectiveScopes), | ||
| now + ACCESS_TOKEN_TTL, | ||
| null, | ||
| familyId, |
|
|
||
| type Session = { | ||
| transport: StreamableHTTPServerTransport; | ||
| server: McpServer; |
| "Issuer URL is required for http+oauth transport. Provide it via the MCP_ISSUER_URL environment variable or the --issuer-url=URL command argument.", | ||
| ); | ||
| process.exit(1); | ||
| } |
| ## Transport Modes | ||
|
|
||
| `hevy-mcp` supports two transport modes: | ||
|
|
||
| ### stdio (default) | ||
|
|
||
| The default mode. Used by all MCP clients that launch the server as a subprocess: | ||
|
|
||
| ```bash | ||
| HEVY_API_KEY=your_key npx -y hevy-mcp | ||
| ``` | ||
|
|
||
| ### Streamable HTTP | ||
|
|
||
| ### Stdio Only | ||
| Use `--transport=http` to start a Streamable HTTP server. The server listens on `/mcp` and manages one MCP session per client connection. |
| hevy-mcp: | ||
| rule: "Host(`hevymcp.ams.iosharp.com`)" | ||
| entryPoints: | ||
| - web-secure | ||
| service: hevy-mcp | ||
| tls: | ||
| certResolver: myresolver | ||
| services: | ||
| hevy-mcp: | ||
| loadBalancer: | ||
| servers: | ||
| - url: "http://192.168.178.42:8012" |
There was a problem hiding this comment.
Code Review
This pull request re-introduces HTTP transport support and implements a new http+oauth transport mode, featuring a password-gated OAuth 2.1 authorization server and a SQLite-backed provider. The update includes new Docker and Traefik configurations, expanded documentation, and a suite of integration tests for the authentication flows. Feedback identifies a potential issue in the HTTP server where a strict URL check might fail if query parameters or trailing slashes are present, suggesting a more robust pathname-based validation.
| if (req.url !== "/mcp") { | ||
| res.writeHead(404).end(); | ||
| return; | ||
| } |
There was a problem hiding this comment.
The current check req.url !== "/mcp" is too strict and will fail for valid requests that include query parameters (e.g., /mcp?foo=bar) or trailing slashes (e.g., /mcp/). It is recommended to parse the URL to check the pathname instead.
| if (req.url !== "/mcp") { | |
| res.writeHead(404).end(); | |
| return; | |
| } | |
| const url = new URL(req.url ?? "/", "http://localhost"); | |
| if (url.pathname !== "/mcp") { |
Summary
chrisdoc/hevy-mcpv1.23.8 into fork main.get-body-measurements,get-body-measurement,create-body-measurement,update-body-measurement.databeforeheaders), rep_rangenull→undefined.package.jsonversion 1.23.8.Test plan
npm run check:types— cleannpm run check— 0 warnings, 0 errorsnpx vitest run --exclude tests/integration/**— 21 test files, 196 tests passednpm run build— clean/.well-known/oauth-authorization-serverresponds; unauthenticated/mcpreturns 401✨ PR Description
Purpose: Merge upstream MCP SDK changes from chrisdoc/hevy-mcp v1.23.8, adding HTTP and OAuth transport modes alongside existing stdio transport for flexible deployment options.
Main changes:
Generated by LinearB AI and added by gitStream.
AI-generated content may contain inaccuracies. Please verify before using.
💡 Tip: You can customize your AI Description using Guidelines Learn how
Summary by CodeRabbit
New Features
--transport=httpand--portflagsMCP_ISSUER_URL,MCP_AUTH_PASSWORD,OAUTH_DB_PATHDocumentation
Tests