Skip to content

RE1-T102 Fixing MCP server issue.#280

Merged
ucswift merged 1 commit intomasterfrom
develop
Feb 14, 2026
Merged

RE1-T102 Fixing MCP server issue.#280
ucswift merged 1 commit intomasterfrom
develop

Conversation

@ucswift
Copy link
Member

@ucswift ucswift commented Feb 14, 2026

Summary by CodeRabbit

  • Chores
    • Consolidated application startup configuration and initialization logic.
    • Enhanced error tracking and profiling capabilities.
    • Improved robustness of release version assignment and trace sampling logic.
    • Strengthened API configuration validation with clearer error messaging.

@request-info
Copy link

request-info bot commented Feb 14, 2026

Thanks for opening this, but we'd appreciate a little more information. Could you update it with more details?

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 14, 2026

📝 Walkthrough

Walkthrough

This refactoring consolidates ASP.NET Core application startup configuration by extracting a dedicated Startup class that handles service registration and middleware setup, while Program.cs delegates to UseStartup<Startup>(). The new Startup class initializes MCP server components, configures API clients with connection pooling, validates required configuration, and registers tool providers and infrastructure services.

Changes

Cohort / File(s) Summary
Program.cs Refactoring
Web/Resgrid.Web.Mcp/Program.cs
Replaced in-file service configuration with UseStartup<Startup>() delegation, enabled Sentry profiling integration, improved Release assignment robustness with null-conditional operator, and expanded TracesSampler to handle null contexts and explicitly exclude "/health/getcurrent" path from tracing.
Startup Class (New)
Web/Resgrid.Web.Mcp/Startup.cs
New ASP.NET Core Startup class handling logging initialization, MCP server host registration as hosted service, MVC controller setup, infrastructure service registration (memory cache, response cache, rate limiter, token refresh, audit logger), API configuration validation with exception handling, HttpClient configuration with SocketsHttpHandler connection pooling (5-min lifetime, 2-min idle timeout, max 10 connections), API client registration, tool provider singleton registration (authentication, calls, dispatch, personnel, units, messages, calendar, shifts, inventory, reports), and HTTP request pipeline setup.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐰 Startup's consolidated with care,
Configuration flows everywhere,
Profiling now traces the flight,
Health checks skip without a sight,
MCP server shines, oh what a pair!

🚥 Pre-merge checks | ✅ 1 | ❌ 3
❌ Failed checks (2 warnings, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Merge Conflict Detection ⚠️ Warning ❌ Merge conflicts detected (1 file):

⚔️ Web/Resgrid.Web.Mcp/Program.cs (content)

These conflicts must be resolved before merging into master.
Resolve conflicts locally and push changes to this branch.
Title check ❓ Inconclusive The title is vague and generic. It mentions 'Fixing MCP server issue' without describing what the actual issue is or what specific changes were made to fix it. Revise the title to be more specific and descriptive of the actual changes, such as 'Consolidate MCP startup configuration and add Sentry profiling integration' or 'Refactor Program.cs to use Startup class pattern for MCP server setup'.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch develop
⚔️ Resolve merge conflicts (beta)
  • Auto-commit resolved conflicts to branch develop
  • Create stacked PR with resolved conflicts
  • Post resolved changes as copyable diffs in a comment

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.

❤️ Share

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

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@Web/Resgrid.Web.Mcp/Program.cs`:
- Line 53: The assignment using Assembly.GetEntryAssembly() and Version should
use null-conditional operators like the change in Program.cs; update the same
pattern wherever GetEntryAssembly()?.GetName().Version?.ToString() is needed —
specifically adjust the Release assignment in the Program classes (symbols:
options.Release in Resgrid.Web/Program.cs, Resgrid.Web.Services/Program.cs,
Resgrid.Web.Eventing/Program.cs, Resgrid.Workers.Console/Program.cs) and the
health/version exposures in HealthController classes (symbols: HealthController
in Resgrid.Web.Controllers, Resgrid.Web.Services.Controllers.v4,
Resgrid.Web.Eventing.Controllers) as well as the logging version read in
Resgrid.Framework.Logging (symbol: Logging class); replace direct
Assembly.GetEntryAssembly().GetName().Version.ToString() calls with
null-conditional variants and handle a null result safely (e.g., assign null or
empty string) to avoid NullReferenceException.

In `@Web/Resgrid.Web.Mcp/Startup.cs`:
- Around line 36-41: TokenRefreshService.GetValidTokenAsync has a TOCTOU race
where concurrent callers both see a missing/expired token and perform duplicate
refreshes; change GetValidTokenAsync to atomically coordinate refreshes (e.g.,
use a ConcurrentDictionary<string, Lazy<Task<Token>>> or
ConcurrentDictionary.AddOrUpdate with a factory that returns a single Task to
perform refresh) so only the first caller triggers the refresh and others await
that Task; ensure you replace the current token-read/refresh branches in
TokenRefreshService.GetValidTokenAsync with the AddOrUpdate/Lazy<Task> pattern,
correctly await the stored Task, and handle/remap exceptions and token storage
on completion.
🧹 Nitpick comments (4)
Web/Resgrid.Web.Mcp/Program.cs (1)

51-74: TracesSampleRate on line 51 is redundant when TracesSampler is set.

When both TracesSampleRate and TracesSampler are configured, the Sentry SDK uses TracesSampler and ignores TracesSampleRate. Since the sampler already falls back to ExternalErrorConfig.SentryPerfSampleRate (line 73), line 51 is effectively dead code. Not a bug, but removing it avoids confusion about which value controls sampling.

♻️ Suggested cleanup
 							options.SendDefaultPii = true;
 							options.AutoSessionTracking = true;
-							options.TracesSampleRate = ExternalErrorConfig.SentryPerfSampleRate;
 							options.Environment = ExternalErrorConfig.Environment;
Web/Resgrid.Web.Mcp/Startup.cs (3)

23-27: Side effect in ConfigureServices: logging initialization.

Framework.Logging.Initialize(...) is a one-time global side effect invoked during service registration. While pragmatic for startup, it makes this method harder to test and couples it to the static Logging class. If testability or startup ordering ever becomes a concern, consider moving this to Configure or into a hosted service's StartAsync.


14-14: Configuration property is injected but unused.

IConfiguration is stored on Line 14 and set in the constructor, but ConfigureServices reads all configuration from static classes (ExternalErrorConfig, SystemBehaviorConfig). If there's no plan to use Configuration, consider removing it to avoid misleading readers into thinking it drives behavior.


68-79: Tool providers are registered as concrete types without interfaces.

All 10 tool providers and McpToolRegistry are registered as singletons of their concrete type. This works but limits testability and makes it harder to substitute implementations. If these are consumed by other services, consider extracting interfaces (at minimum for McpToolRegistry) to align with the coding guideline to "prefer composition with interfaces to extend behavior." As per coding guidelines, "Prefer composition with interfaces to extend behavior".

options.TracesSampleRate = ExternalErrorConfig.SentryPerfSampleRate;
options.Environment = ExternalErrorConfig.Environment;
options.Release = Assembly.GetEntryAssembly().GetName().Version.ToString();
options.Release = Assembly.GetEntryAssembly()?.GetName().Version?.ToString();
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find all GetEntryAssembly() usages without null-conditional operators
rg -n 'GetEntryAssembly\(\)\.' --type cs -A2 -B2

Repository: Resgrid/Core

Length of output: 3757


🏁 Script executed:

#!/bin/bash
# Check specifically for the Logging.cs file mentioned in the review
fd -e cs -p 'Logging.cs' | head -20

Repository: Resgrid/Core

Length of output: 89


Good defensive null-safety improvement, but multiple other locations need the same fix.

Using null-conditional operators on GetEntryAssembly() and Version prevents potential NullReferenceException in hosted environments where GetEntryAssembly() can return null. The change in Program.cs follows C# best practices.

However, the codebase has multiple other unsafe usages of this same pattern:

  • Web/Resgrid.Web/Program.cs line 59
  • Web/Resgrid.Web.Services/Program.cs line 58
  • Web/Resgrid.Web.Eventing/Program.cs line 57
  • Workers/Resgrid.Workers.Console/Program.cs line 103
  • Web/Resgrid.Web/Controllers/HealthController.cs line 35
  • Web/Resgrid.Web.Services/Controllers/v4/HealthController.cs line 42
  • Web/Resgrid.Web.Eventing/Controllers/HealthController.cs line 37
  • Core/Resgrid.Framework/Logging.cs line 40

Apply the same null-conditional operator pattern to all these locations for consistency and safety.

🤖 Prompt for AI Agents
In `@Web/Resgrid.Web.Mcp/Program.cs` at line 53, The assignment using
Assembly.GetEntryAssembly() and Version should use null-conditional operators
like the change in Program.cs; update the same pattern wherever
GetEntryAssembly()?.GetName().Version?.ToString() is needed — specifically
adjust the Release assignment in the Program classes (symbols: options.Release
in Resgrid.Web/Program.cs, Resgrid.Web.Services/Program.cs,
Resgrid.Web.Eventing/Program.cs, Resgrid.Workers.Console/Program.cs) and the
health/version exposures in HealthController classes (symbols: HealthController
in Resgrid.Web.Controllers, Resgrid.Web.Services.Controllers.v4,
Resgrid.Web.Eventing.Controllers) as well as the logging version read in
Resgrid.Framework.Logging (symbol: Logging class); replace direct
Assembly.GetEntryAssembly().GetName().Version.ToString() calls with
null-conditional variants and handle a null result safely (e.g., assign null or
empty string) to avoid NullReferenceException.

Comment on lines +36 to +41
// Register infrastructure services
services.AddMemoryCache();
services.AddSingleton<IResponseCache, ResponseCache>();
services.AddSingleton<IRateLimiter, RateLimiter>();
services.AddSingleton<ITokenRefreshService, TokenRefreshService>();
services.AddSingleton<IAuditLogger, AuditLogger>();
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

find . -type f -name "*.cs" | xargs rg -l 'class ResponseCache|class RateLimiter|class TokenRefreshService|class AuditLogger' | head -20

Repository: Resgrid/Core

Length of output: 273


🏁 Script executed:

# Find and examine these implementations
rg -B 2 -A 20 'class ResponseCache\s*[:\(]' --type cs

Repository: Resgrid/Core

Length of output: 1912


🏁 Script executed:

rg -B 2 -A 20 'class RateLimiter\s*[:\(]' --type cs

Repository: Resgrid/Core

Length of output: 1998


🏁 Script executed:

rg -B 2 -A 20 'class TokenRefreshService\s*[:\(]' --type cs

Repository: Resgrid/Core

Length of output: 2199


🏁 Script executed:

rg -B 2 -A 20 'class AuditLogger\s*[:\(]' --type cs

Repository: Resgrid/Core

Length of output: 1756


🏁 Script executed:

cat -n Web/Resgrid.Web.Mcp/Infrastructure/RateLimiter.cs

Repository: Resgrid/Core

Length of output: 5735


🏁 Script executed:

cat -n Web/Resgrid.Web.Mcp/Infrastructure/ResponseCache.cs

Repository: Resgrid/Core

Length of output: 2181


🏁 Script executed:

cat -n Web/Resgrid.Web.Mcp/Infrastructure/TokenRefreshService.cs

Repository: Resgrid/Core

Length of output: 5217


🏁 Script executed:

cat -n Web/Resgrid.Web.Mcp/Infrastructure/AuditLogger.cs

Repository: Resgrid/Core

Length of output: 6643


RateLimiter and ResponseCache are already properly thread-safe, but TokenRefreshService has a race condition.

RateLimiter uses ConcurrentDictionary with explicit locking in RequestCounter.TryAddIfUnderLimit (lines 138-156); ResponseCache delegates to IMemoryCache, which is thread-safe by design. However, TokenRefreshService.GetValidTokenAsync has a time-of-check-time-of-use race: two concurrent requests can both read an expired/missing token (line 36), both decide to refresh (lines 50-54), and both issue redundant token refresh calls. Use ConcurrentDictionary.AddOrUpdate with a factory method or add async coordination to prevent duplicate refresh attempts.

🤖 Prompt for AI Agents
In `@Web/Resgrid.Web.Mcp/Startup.cs` around lines 36 - 41,
TokenRefreshService.GetValidTokenAsync has a TOCTOU race where concurrent
callers both see a missing/expired token and perform duplicate refreshes; change
GetValidTokenAsync to atomically coordinate refreshes (e.g., use a
ConcurrentDictionary<string, Lazy<Task<Token>>> or
ConcurrentDictionary.AddOrUpdate with a factory that returns a single Task to
perform refresh) so only the first caller triggers the refresh and others await
that Task; ensure you replace the current token-read/refresh branches in
TokenRefreshService.GetValidTokenAsync with the AddOrUpdate/Lazy<Task> pattern,
correctly await the stored Task, and handle/remap exceptions and token storage
on completion.

@ucswift
Copy link
Member Author

ucswift commented Feb 14, 2026

Approve

Copy link

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

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

This PR is approved.

@ucswift ucswift merged commit 1da63ab into master Feb 14, 2026
18 of 19 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant