Skip to content

feat: integrate Sentry monitoring - #196

Merged
chrisdoc merged 2 commits into
mainfrom
ai-194-setup-sentry
Dec 10, 2025
Merged

feat: integrate Sentry monitoring#196
chrisdoc merged 2 commits into
mainfrom
ai-194-setup-sentry

Conversation

@charliecreates

@charliecreates charliecreates Bot commented Dec 10, 2025

Copy link
Copy Markdown
Contributor

Implement optional Sentry monitoring for the MCP server, wiring it up to the
Sentry MCP integration while keeping it fully opt-in via environment
variables.

Changes

  • Add @sentry/node runtime dependency (v9.47.1) to enable Sentry MCP
    instrumentation.
  • Initialize Sentry in src/index.ts via a new getSentryConfigFromEnv
    helper that reads:
    • SENTRY_DSN (required to enable Sentry),
    • SENTRY_TRACES_SAMPLE_RATE (validated as a number between 0 and 1,
      falling back to 1.0 with a stderr warning when invalid),
    • SENTRY_ENVIRONMENT, and
    • SENTRY_SEND_DEFAULT_PII (only "true" or "1" enable PII capture).
  • Wrap the McpServer instance with Sentry.wrapMcpServerWithSentry when a
    valid Sentry config is present so tool calls and errors are captured in
    Sentry; when SENTRY_DSN is unset, behavior is unchanged.
  • Extend .env.sample and the README configuration section with Sentry
    environment variables and explicit notes about PII and sampling defaults.
  • Relax the explicit parameter type on the process.exit mock in
    src/index.test.ts so tsc --noEmit passes while preserving existing test
    behavior.

Verification

# Build
pnpm run build

# TypeScript typecheck (no emit)
npx tsc --noEmit

# Unit + non-API integration tests
pnpm vitest run src/index.test.ts src/utils/*.test.ts tests/docker.test.ts tests/integration/http-transport.integration.test.ts

# Biome format/lint (auto-fix on)
pnpm run check
  • All commands above pass locally.
  • Biome reports only pre-existing warnings:
    • Schema version mismatch for biome.json vs CLI version.
    • Three noExplicitAny warnings in src/tools/webhooks.ts
      (hevyClient as any), untouched in this PR.
  • tests/integration/hevy-mcp.integration.test.ts was intentionally not run
    because it requires a real HEVY_API_KEY; this PR does not change any
    Hevy API surface.
  • Self-review suggested adding dedicated tests for the Sentry wrapping branch;
    I kept the implementation minimal and did not restructure the entrypoint to
    make sentryConfig injectable, since the behavior is a straightforward
    wrapper and is indirectly covered by the existing server entry tests.

Closes #194

✨ PR Description

Purpose: Integrate Sentry monitoring into the MCP server to capture usage metrics and errors from published builds for observability.
Main changes:
- Added @sentry/node dependency and initialized Sentry with fixed DSN and tracing configuration
- Wrapped McpServer with Sentry.wrapMcpServerWithSentry for automatic request and tool call capture
- Documented Sentry integration in README with PII settings and opt-out instructions
- Refactored test spy implementation to use consistent mockImplementation pattern

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

@coderabbitai

coderabbitai Bot commented Dec 10, 2025

Copy link
Copy Markdown
Contributor

Important

Review skipped

Bot user detected.

To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch ai-194-setup-sentry

Comment @coderabbitai help to get the list of available commands and usage tips.

@charliecreates charliecreates Bot mentioned this pull request Dec 10, 2025
@codecov

codecov Bot commented Dec 10, 2025

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 37.22%. Comparing base (f97d2b4) to head (097a5b2).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #196      +/-   ##
==========================================
+ Coverage   36.71%   37.22%   +0.51%     
==========================================
  Files          13       13              
  Lines         365      368       +3     
  Branches      108      108              
==========================================
+ Hits          134      137       +3     
  Misses        219      219              
  Partials       12       12              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@gitstream-cm gitstream-cm Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✨ 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

@chrisdoc

Copy link
Copy Markdown
Owner

@CharlieHelps can we use github secrets for the sentry env setup

@charliecreates

Copy link
Copy Markdown
Contributor Author

#196 (comment)

Yes, we can absolutely use GitHub Secrets here. The Sentry setup in this PR only reads from process.env (SENTRY_DSN, SENTRY_TRACES_SAMPLE_RATE, SENTRY_ENVIRONMENT, SENTRY_SEND_DEFAULT_PII), so anywhere you run the MCP server you can back those with GitHub Secrets instead of a local .env.

For GitHub Actions, you’d:

  1. Add repo/org secrets under Settings → Secrets and variables → Actions:

    • SENTRY_DSN
    • (optionally) SENTRY_ENVIRONMENT, SENTRY_TRACES_SAMPLE_RATE, SENTRY_SEND_DEFAULT_PII
  2. Reference them in your workflow/job:

jobs:
  run-mcp:
    runs-on: ubuntu-latest
    env:
      SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
      SENTRY_ENVIRONMENT: production
      SENTRY_TRACES_SAMPLE_RATE: "0.2"
      SENTRY_SEND_DEFAULT_PII: "false"
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 22
      - run: pnpm install
      - run: pnpm run start

If you’d like, I can follow up with a small README note or workflow snippet in this repo showing the “use GitHub Secrets for Sentry vars” pattern.

@charliecreates charliecreates Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new Sentry integration is functionally sound but centralizes configuration in module-level state, which reduces testability and may cause surprising behavior when environment variables change after module load. Moving Sentry configuration and initialization behind injectable functions (or parameters to buildServer) would better respect entrypoint boundaries and make the behavior easier to test and reuse. The environment variable parsing and README updates are otherwise clear and consistent with the intended opt-in design. No obvious bugs or performance issues were found in the modified code segments.

Summary of changes

Overview of Changes

  • Added optional Sentry monitoring support via @sentry/node dependency and its transitive OpenTelemetry stack in package.json and pnpm-lock.yaml.
  • Extended .env.sample and README.md with Sentry-related environment variables (SENTRY_DSN, SENTRY_ENVIRONMENT, SENTRY_TRACES_SAMPLE_RATE, SENTRY_SEND_DEFAULT_PII) and documentation of behavior.
  • Introduced getSentryConfigFromEnv and Sentry initialization logic in src/index.ts, including:
    • Reading and validating Sentry env vars.
    • Conditionally calling Sentry.init and wrapping the McpServer instance with Sentry.wrapMcpServerWithSentry.
  • Updated buildServer to construct a base MCP server and wrap it with Sentry when configured.
  • Relaxed the explicit type on the process.exit mock in src/index.test.ts to satisfy tsc while preserving behavior.
  • Fixed a minor markdown formatting issue in the README acknowledgements list.

Comment thread src/index.ts Outdated
Comment on lines +7 to +38
function getSentryConfigFromEnv() {
const dsn = process.env.SENTRY_DSN;
if (!dsn) {
return null;
}

let tracesSampleRate = 1.0;
const tracesSampleRateEnv = process.env.SENTRY_TRACES_SAMPLE_RATE;
if (tracesSampleRateEnv !== undefined) {
const parsed = Number.parseFloat(tracesSampleRateEnv);
if (Number.isNaN(parsed) || parsed < 0 || parsed > 1) {
console.error(
`Invalid SENTRY_TRACES_SAMPLE_RATE="${tracesSampleRateEnv}", falling back to 1.0. Expected a number between 0 and 1.`,
);
} else {
tracesSampleRate = parsed;
}
}

const sendDefaultPiiEnv = process.env.SENTRY_SEND_DEFAULT_PII;
const sendDefaultPii =
sendDefaultPiiEnv === "true" || sendDefaultPiiEnv === "1";

return {
dsn,
tracesSampleRate,
sendDefaultPii,
environment: process.env.SENTRY_ENVIRONMENT,
};
}

const sentryConfig = getSentryConfigFromEnv();

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This helper currently bakes in the environment-variable reading and is evaluated at module load time via the sentryConfig constant. That makes it awkward to write focused tests around whether Sentry wrapping is applied and also makes it harder to override configuration in alternate entrypoints (e.g., if createServer is reused in a different runtime). Extracting the Sentry configuration and initialization behind an injectable function (or passing sentryConfig as an argument into buildServer) would improve testability and keep the side effects at the true entrypoint boundary rather than in module scope.

Additionally, because Sentry.init is called at import time when SENTRY_DSN is set, any consumer importing configSchema/buildServer in a different process (like tooling) will also incur Sentry initialization, which may not be desired in all contexts.

Suggestion

Consider restructuring so that Sentry configuration and initialization are tied strictly to the CLI/entrypoint path rather than module import, for example:

export function getSentryConfigFromEnv(env: NodeJS.ProcessEnv = process.env) {
  const dsn = env.SENTRY_DSN;
  if (!dsn) return null;
  // ...rest unchanged...
}

function initSentryIfConfigured(env: NodeJS.ProcessEnv = process.env) {
  const cfg = getSentryConfigFromEnv(env);
  if (!cfg) return null;
  Sentry.init(cfg);
  return cfg;
}

// In your actual `runServer` / CLI entrypoint:
const sentryConfig = initSentryIfConfigured();

function buildServer(apiKey: string, sentryCfg = sentryConfig) {
  const baseServer = new McpServer({ name, version });
  const server = sentryCfg ? Sentry.wrapMcpServerWithSentry(baseServer) : baseServer;
  // ...
}

This keeps side effects at the executable boundary and makes getSentryConfigFromEnv and buildServer directly testable with injected environment/config objects. Reply with "@CharlieHelps yes please" if you'd like me to add a commit with this refactor.

@charliecreates
charliecreates Bot removed the request for review from CharlieHelps December 10, 2025 21:24
@chrisdoc

Copy link
Copy Markdown
Owner

@CharlieHelps we want that they are backed into the built mcp server so that we have observability from our users

please use

const Sentry = require("@sentry/node");

Sentry.init({
  dsn: "https://ce696d8333b507acbf5203eb877bce0f@o4508975499575296.ingest.de.sentry.io/4509049671647312",
  // Tracing must be enabled for MCP monitoring to work
  tracesSampleRate: 1.0,
  sendDefaultPii: false,
});

and don't use .env for sentry, we can have the config in the code

@charliecreates

charliecreates Bot commented Dec 10, 2025

Copy link
Copy Markdown
Contributor Author
Expand this to see my work.
  • Clarified from README.md and .env.sample that the previous Sentry setup relied on SENTRY_DSN, SENTRY_ENVIRONMENT, SENTRY_TRACES_SAMPLE_RATE, and SENTRY_SEND_DEFAULT_PII, with no extra user code changes required.
  • Re-verified in src/index.ts that getSentryConfigFromEnv() previously initialized Sentry and buildServer wrapped the McpServer with Sentry.wrapMcpServerWithSentry, and that createServer exported this wrapped server for Smithery/bundled usage in #196.
  • Validated that any Smithery bundle or other consumers of createServer in PR #196 automatically used the Sentry-wrapped server, giving observability for end-user requests once Sentry env vars were configured.
  • Drafted a PR reply explaining that Sentry integration was already wired into the built MCP server path so no additional Smithery glue was required beyond runtime configuration.
  • Refactored src/index.ts to remove getSentryConfigFromEnv() and instead initialize @sentry/node with a fixed sentryConfig object (static DSN, tracesSampleRate: 1.0, sendDefaultPii: false) and to always wrap the McpServer via Sentry.wrapMcpServerWithSentry in buildServer().
  • Updated the Sentry section in README.md to describe monitoring as always-on and baked into the built MCP server, pointing to src/index.ts and noting that no Sentry-related environment variables are required anymore.
  • Simplified .env.sample to only include HEVY_API_KEY, removing all SENTRY_* placeholders to match the new baked-in configuration.
  • Confirmed via rg "SENTRY_" README.md .env.sample src/index.ts -n that there are no remaining Sentry env var references after the changes.
  • Installed @sentry/node@9.47.1 via pnpm install so TypeScript can resolve the new import in src/index.ts, matching the package.json dependency, and re-ran pnpm vitest run src/index.test.ts src/utils/*.test.ts tests/docker.test.ts tests/integration/http-transport.integration.test.ts (37 tests, 9 files) to confirm everything still passes.
  • Re-requested review on PR #196 so the updated Sentry-baked MCP server changes and documentation can be re-checked in context of the new comment from @chrisdoc.

@charliecreates

Copy link
Copy Markdown
Contributor Author

#196 (comment)

I’ve updated the PR so Sentry is now baked into the built MCP server using your provided config and no .env-based Sentry settings.

Changes
  • In src/index.ts, initialize Sentry at module load with your DSN and options (using ESM import instead of require):

    import * as Sentry from "@sentry/node";
    
    const sentryConfig = {
      dsn: "https://ce696d8333b507acbf5203eb877bce0f@o4508975499575296.ingest.de.sentry.io/4509049671647312",
      // Tracing must be enabled for MCP monitoring to work
      tracesSampleRate: 1.0,
      sendDefaultPii: false,
    } as const;
    
    Sentry.init(sentryConfig);
  • buildServer now always wraps the MCP server:

    const baseServer = new McpServer({ name, version });
    const server = Sentry.wrapMcpServerWithSentry(baseServer);

    so both the CLI entry (runServer, e.g. npx hevy-mcp) and the Smithery/TS runtime entry (createServer) are instrumented for all users of the published package.

  • Removed all SENTRY_* variables from .env.sample and the README; Sentry is no longer configurable via environment variables.

  • Updated the README “Sentry monitoring” section to describe the new behavior:

    • Monitoring is baked into the built MCP server with a fixed DSN.
    • sendDefaultPii is set to false in code.
    • There’s no built-in toggle to disable Sentry; disabling it would currently require forking and removing the Sentry.init call in src/index.ts.
  • New commit with these changes: 097a5b2.

Verification
# Install deps (pull in @sentry/node)
$ pnpm install

# Build
$ pnpm run build

# TypeScript typecheck (no emit)
$ npx tsc --noEmit

# Unit + non-API integration tests
$ pnpm vitest run src/index.test.ts src/utils/*.test.ts \
  tests/docker.test.ts tests/integration/http-transport.integration.test.ts

# Biome format/lint (auto-fix on)
$ pnpm run check
  • pnpm run build: passes (ESM + dts output generated in dist/).
  • npx tsc --noEmit: passes with no TypeScript errors.
  • Vitest: 9 test files / 37 tests all pass, covering the server entry, utils, Docker artifacts, and the HTTP-transport guard.
    • tests/integration/hevy-mcp.integration.test.ts was not run because it requires a real HEVY_API_KEY, and this PR doesn’t touch the Hevy API surface.
  • pnpm run check (Biome): only pre-existing warnings remain:
    • Schema version mismatch in biome.json vs the installed CLI.
    • Three noExplicitAny warnings in src/tools/webhooks.ts (hevyClient as any), unchanged in this PR.
  • Self-review surfaced that the Sentry DSN is now hard-coded and always enabled (src/index.ts:7–17,37–41) and suggested adding opt-out/override flags. I left Sentry always-on and code-configured on purpose to match your request (baked into the built MCP server, no .env for Sentry), and instead clarified the behavior and data collection (sendDefaultPii: false) in the README.

@gitstream-cm gitstream-cm Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✨ PR Review

The PR adds Sentry monitoring with hardcoded configuration, but there's a significant disconnect between the PR description and actual implementation regarding opt-in behavior.

2 issues detected:

🔒 Security - Hardcoded telemetry initialization without user consent contradicts the described opt-in behavior

Details: The PR description claims Sentry monitoring is "fully opt-in via environment variables" and mentions a getSentryConfigFromEnv helper, but the actual implementation hardcodes Sentry configuration and always initializes it without any opt-out mechanism. This creates a privacy concern as all users will have their usage data sent to the hardcoded DSN without consent.
File: src/index.ts

🚀 Performance - 100% trace sampling may cause performance overhead and excessive data collection 🛠️

Details: Setting tracesSampleRate: 1.0 means 100% of all traces will be captured and sent to Sentry, which could impact performance in high-traffic scenarios and generate excessive telemetry data.
File: src/index.ts (12-12)
🛠️ A suggested code correction is included in the review comments.

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

Comment thread src/index.ts
const sentryConfig = {
dsn: "https://ce696d8333b507acbf5203eb877bce0f@o4508975499575296.ingest.de.sentry.io/4509049671647312",
// Tracing must be enabled for MCP monitoring to work
tracesSampleRate: 1.0,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚀 Performance - Full Trace Sampling: Consider using a lower sample rate (e.g., 0.1 or 0.01) or making it configurable via environment variables to balance observability with performance.

Suggested change
tracesSampleRate: 1.0,
tracesSampleRate: process.env.SENTRY_TRACES_SAMPLE_RATE ? parseFloat(process.env.SENTRY_TRACES_SAMPLE_RATE) : 0.1,
Is this review accurate? Use 👍 or 👎 to rate it

If you want to tell us more, use /gs feedback e.g. /gs feedback this review doesn't make sense, I disagree, and it keeps repeating over and over

@charliecreates charliecreates Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Sentry integration is functionally correct but introduces strong global side effects by initializing Sentry at module load and wrapping McpServer inside buildServer, which reduces testability and composability. The change also shifts from an opt-in, env-driven telemetry model to an always-on, hard-coded DSN with no in-repo opt-out besides forking, which may surprise existing users and makes the core module harder to reuse in different contexts. README updates correctly describe the new behavior, but a small, code-level escape hatch for disabling telemetry would provide better flexibility without undermining observability goals. No obvious logical bugs or performance regressions are apparent in the modified code.

Additional notes (2)
  • Readability | README.md:98-111
    The README correctly documents that Sentry uses sendDefaultPii: false and that disabling telemetry requires forking. However, it does not explicitly state that:

  • The DSN is hard-coded in the source, and

  • Initialization happens eagerly when the server entry module is used (rather than being controlled via config/env).

Clarifying these two points would better set expectations for security/privacy reviewers and downstream users inspecting the behavior, and would reduce surprises for anyone trying to reason about when and how telemetry is sent.

  • Maintainability | src/index.test.ts:101-106
    The change to the process.exit spy removes the explicit type annotation on _code, which resolves the earlier TypeScript compilation issue while keeping the test behavior intact. There is no logical or behavioral regression here; the test still asserts that process.exit(1) is called and throws as expected.
Summary of changes

Summary of Changes

  • Added @sentry/node as a runtime dependency and wired in the full Sentry MCP/OpenTelemetry stack via pnpm-lock.yaml.
  • Baked Sentry monitoring directly into src/index.ts: Sentry is initialized at module load with a hard-coded DSN, tracesSampleRate: 1.0, and sendDefaultPii: false, and the McpServer is always wrapped with Sentry.wrapMcpServerWithSentry.
  • Updated the README with a new Sentry monitoring section explaining that telemetry is always on in the published package and can only be disabled via a fork.
  • Kept .env.sample effectively unchanged (only whitespace/formatting), and slightly simplified the process.exit spy in src/index.test.ts.
  • Lockfile changes pull in many Sentry/OpenTelemetry-related dependencies and types as a result of the new @sentry/node dependency.

Comment thread README.md
Comment on lines +98 to +111
### Sentry monitoring

`hevy-mcp` ships with Sentry monitoring baked into the built MCP server so
that usage and errors from published builds can be observed.

The server initializes `@sentry/node` with a fixed DSN and tracing settings
directly in the code (see `src/index.ts`), and wraps the underlying
`McpServer` with `Sentry.wrapMcpServerWithSentry` so requests and tool calls
are captured by Sentry automatically. The configuration uses
`sendDefaultPii: false` to keep Sentry's default PII collection disabled.

There is currently no built-in toggle to disable Sentry for the published
package. If you need a build without Sentry telemetry, you can fork the
repository and remove the Sentry initialization in `src/index.ts`.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new hard-coded DSN and always-on Sentry initialization mean telemetry is permanently enabled for all users of the published package, and the README states the only way to disable it is to fork and edit src/index.ts. That is a runtime behavior change from the previous opt-in, env-based design and may surprise existing users who pull a new version expecting no network calls unless explicitly configured.

Even if this change is product-approved, it would be safer to at least keep a minimal guard (e.g., behind a HEVY_MCP_TELEMETRY_DISABLED env var or a documented flag) to support privacy-sensitive environments without forcing a fork, and to allow CI/local testing without external calls when desired.

Suggestion

If product constraints allow any configurability, introduce a single opt-out mechanism in code (for example, an environment variable like HEVY_MCP_DISABLE_SENTRY or HEVY_MCP_TELEMETRY=off) and mention it in this README section. The implementation can be as small as:

  • Checking the env var before calling Sentry.init, and
  • Skipping wrapMcpServerWithSentry when telemetry is disabled.

This keeps telemetry effectively always-on in normal deployments, but gives privacy-sensitive users and CI a documented escape hatch without requiring a fork. Reply with "@CharlieHelps yes please" if you'd like me to add a commit wiring in such a minimal opt-out and updating the docs.

@charliecreates
charliecreates Bot removed the request for review from CharlieHelps December 10, 2025 21:35
@chrisdoc
chrisdoc merged commit 4ea9b31 into main Dec 10, 2025
19 checks passed
@chrisdoc
chrisdoc deleted the ai-194-setup-sentry branch December 10, 2025 21:37
github-actions Bot pushed a commit that referenced this pull request Dec 10, 2025
# [1.14.0](v1.13.2...v1.14.0) (2025-12-10)

### Features

* integrate Sentry monitoring ([#196](#196)) ([4ea9b31](4ea9b31))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Setup Sentry

2 participants