Skip to content

Conversation

@AleksandrParamonoff
Copy link
Contributor

No description provided.

@coderabbitai
Copy link

coderabbitai bot commented Sep 23, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Summary by CodeRabbit

  • New Features

    • Unified, config-driven startup with version metadata passed through the app.
    • CLI enhancements for common and HTTP-specific options; supports stdio and HTTP streaming modes.
    • Built-in analytics lifecycle with configurable batch send interval.
    • Added full tool registration for launches and test items.
  • Improvements

    • Stronger HTTP request handling, including request body size limits and enhanced token validation.
    • Consistent pagination, project parameter defaults, and error handling across tools.
    • Clearer logging and graceful startup/shutdown.
  • Refactor

    • Consolidated server initialization and analytics usage for simpler configuration and maintenance.

Walkthrough

Startup moved to a config-driven RunApp(ctx, AppVersion) flow; analytics and MCP helpers were centralized into internal/utils and internal/analytics; HTTP server, middleware, security, and mcp handlers were refactored to use exported utils/analytics surfaces and new Register* tool APIs; batch/JSON‑RPC tests added.

Changes

Cohort / File(s) Summary
App entrypoint
cmd/reportportal-mcp-server/main.go
Construct AppVersion and delegate startup to internal/config.RunApp(ctx, appVersion); remove MCP_MODE-specific inline wiring.
Config & CLI runners
internal/config/cli.go
New CLI/config surface: AppVersion, flag builders, InitLogger, HTTP server config builders, NewMCPServer, RunStdioServer, RunStreamingServer, RunApp, and server error handling.
Analytics core & tests
internal/analytics/analytics.go, internal/analytics/analytics_test.go
Export BatchSendInterval, drop local WithAnalytics (moved to utils), remove MCP imports; tests updated to use utils.WithAnalytics.
HTTP server & batch tests
internal/http/http_server.go, internal/http/http_server_batch_test.go
Server now uses *analytics.Analytics (analytics.NewAnalytics), chi middleware adjustments, request body size cap added, token middleware and utils usage updated; add batch/JSON‑RPC validation tests.
MCP handlers: launches & items
internal/mcp_handlers/*.go, internal/mcp_handlers/launches_test.go
Package renamed mcp_handlers; handlers/resources exported (CamelCase); add RegisterLaunchTools/RegisterTestItemTools; accept *analytics.Analytics; replace helper calls with utils.*; export prompt assets/reader.
MCP server wiring
internal/mcp_handlers/server.go
NewServer returns (*server.MCPServer, *analytics.Analytics, error); initialize analytics via analytics.NewAnalytics; register tools via new Register* functions; export PromptFiles and ReadPrompts.
Middleware & tests
internal/middleware/middleware.go, internal/middleware/middleware_test.go
Package renamed to middleware; use security.GetTokenFromContext, trim/validate token before setting Authorization header; query params read from context via utils.
Security middleware
internal/security/http_token_middleware.go
Package renamed to security; context key type switched to utils.ContextKey; token validation delegated to utils.ValidateRPToken.
Context utils & tests
internal/utils/ctx_utils.go, internal/utils/ctx_utils_test.go
Export ContextKey type and use it for context keys; tests package adjusted.
New MCP utilities
internal/utils/mcp_utils.go
New MCP helpers: SetPaginationOptions, generic ApplyPaginationOptions, NewProjectParameter, ExtractProject, EventTracker + WithAnalytics, and ReadResponseBody.
General utils refactor & tests
internal/utils/utils.go, internal/utils/utils_test.go
Promote many constants and helpers to exported names (e.g., FirstPage, DefaultPageSize, ProcessAttributeKeys, ReadResponseBodyRaw, IsTextContent); move MCP-specific helpers into mcp_utils.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant Main as cmd/main
  participant Config as internal/config.RunApp
  participant Logger as InitLogger
  participant Builder as NewMCPServer / BuildHTTPServerConfig
  participant MCP as MCP Server
  participant HTTP as HTTP Server
  participant Analytics as analytics.NewAnalytics

  User->>Main: start binary (env/flags)
  Main->>Config: RunApp(ctx, AppVersion)
  Config->>Logger: InitLogger()
  Config->>Config: resolve mode (stdio / http)
  alt stdio
    Config->>Builder: NewMCPServer(...)
    Builder->>Analytics: NewAnalytics(...)
    Builder-->>Config: *MCP, *Analytics
    Config->>MCP: run stdio server
  else http
    Config->>Builder: BuildHTTPServerConfig(...)
    Builder->>HTTP: CreateHTTPServerWithMiddleware(...)
    HTTP->>Analytics: NewAnalytics(...)
    HTTP-->>Config: server, *Analytics
    Config->>HTTP: ListenAndServe
  end
  Config->>Config: HandleServerError(err, analytics, type)
Loading
sequenceDiagram
  autonumber
  participant Router as HTTP Router
  participant Sec as security.HTTPTokenMiddleware
  participant Mid as middleware.QueryParamsMiddleware
  participant Handlers as mcp_handlers
  participant Utils as utils
  participant RP as ReportPortal API

  Router->>Sec: incoming request
  Sec->>Router: validate & inject token (utils.ValidateRPToken)
  Router->>Mid: populate query params in ctx (utils.QueryParamsFromContext)
  Router->>Handlers: invoke tool handler (wrapped via utils.WithAnalytics)
  Handlers->>Utils: ExtractProject / ApplyPaginationOptions / ReadResponseBody
  Handlers->>RP: call ReportPortal API
  RP-->>Handlers: response
  Handlers-->>Router: MCP result
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

RunApp stirs with version clear,
Analytics whispers near and dear.
Tokens trimmed, requests confined,
Prompts and tools now neatly lined.
Servers test — the logs cheer! 🎉

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings, 1 inconclusive)
Check name Status Explanation Resolution
Description Check ⚠️ Warning No pull request description has been provided, so there is no context or summary of the changes for reviewers. A brief description that outlines the goals and main modifications would help ensure that all reviewers understand the intent of the refactor. Even a high-level overview is sufficient for a lenient description check. Please add a pull request description summarizing the objectives and key changes introduced by this refactor, including details on the config-driven startup and module separation. This will aid reviewers in understanding the scope and purpose of your changes.
Docstring Coverage ⚠️ Warning Docstring coverage is 55.10% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title Check ❓ Inconclusive The title indicates a refactor of the MCP Server and separation of application parts, which relates to the changeset, but it remains overly generic and does not follow a clear conventional commit style. The phrasing "all application part is separated now" is grammatically awkward and fails to specify the modules or functionality affected. A more descriptive and properly styled title would improve clarity and consistency. Please rewrite the title to adhere to a descriptive conventional commit format, specifying the key change like “refactor(mcp-server): extract application startup into config modules” and correct the grammar for clarity.
✨ 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 refactoring-detaching-app-to-modules

📜 Recent review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 182a251 and 4b82b68.

📒 Files selected for processing (18)
  • cmd/reportportal-mcp-server/main.go (2 hunks)
  • internal/analytics/analytics.go (3 hunks)
  • internal/analytics/analytics_test.go (5 hunks)
  • internal/config/cli.go (1 hunks)
  • internal/http/http_server.go (12 hunks)
  • internal/http/http_server_batch_test.go (1 hunks)
  • internal/mcp_handlers/items.go (23 hunks)
  • internal/mcp_handlers/launches.go (13 hunks)
  • internal/mcp_handlers/launches_test.go (2 hunks)
  • internal/mcp_handlers/server.go (4 hunks)
  • internal/middleware/middleware.go (1 hunks)
  • internal/middleware/middleware_test.go (2 hunks)
  • internal/security/http_token_middleware.go (2 hunks)
  • internal/utils/ctx_utils.go (2 hunks)
  • internal/utils/ctx_utils_test.go (1 hunks)
  • internal/utils/mcp_utils.go (1 hunks)
  • internal/utils/utils.go (8 hunks)
  • internal/utils/utils_test.go (7 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.go

📄 CodeRabbit inference engine (README.md)

**/*.go: Run formatter (task fmt / go fmt) to keep all Go source files consistently formatted
Run linter (task lint) to maintain code quality across Go files

Files:

  • internal/mcp_handlers/items.go
  • internal/mcp_handlers/launches.go
  • internal/mcp_handlers/server.go
  • internal/utils/ctx_utils.go
  • internal/http/http_server.go
  • internal/utils/ctx_utils_test.go
  • internal/middleware/middleware.go
  • internal/security/http_token_middleware.go
  • internal/http/http_server_batch_test.go
  • internal/middleware/middleware_test.go
  • cmd/reportportal-mcp-server/main.go
  • internal/analytics/analytics_test.go
  • internal/utils/utils_test.go
  • internal/utils/utils.go
  • internal/analytics/analytics.go
  • internal/mcp_handlers/launches_test.go
  • internal/utils/mcp_utils.go
  • internal/config/cli.go

⚙️ CodeRabbit configuration file

Focus on Go best practices, error handling, concurrency safety, performance, and security. Check for proper resource cleanup and context handling.

Files:

  • internal/mcp_handlers/items.go
  • internal/mcp_handlers/launches.go
  • internal/mcp_handlers/server.go
  • internal/utils/ctx_utils.go
  • internal/http/http_server.go
  • internal/utils/ctx_utils_test.go
  • internal/middleware/middleware.go
  • internal/security/http_token_middleware.go
  • internal/http/http_server_batch_test.go
  • internal/middleware/middleware_test.go
  • cmd/reportportal-mcp-server/main.go
  • internal/analytics/analytics_test.go
  • internal/utils/utils_test.go
  • internal/utils/utils.go
  • internal/analytics/analytics.go
  • internal/mcp_handlers/launches_test.go
  • internal/utils/mcp_utils.go
  • internal/config/cli.go
**/*_test.go

⚙️ CodeRabbit configuration file

Ensure comprehensive test coverage, proper test isolation, meaningful assertions, and good test naming conventions.

Files:

  • internal/utils/ctx_utils_test.go
  • internal/http/http_server_batch_test.go
  • internal/middleware/middleware_test.go
  • internal/analytics/analytics_test.go
  • internal/utils/utils_test.go
  • internal/mcp_handlers/launches_test.go
cmd/reportportal-mcp-server/**/*.go

📄 CodeRabbit inference engine (README.md)

Keep the main server entrypoint under cmd/reportportal-mcp-server and build from that path

Files:

  • cmd/reportportal-mcp-server/main.go
cmd/**

⚙️ CodeRabbit configuration file

Review CLI interface design, configuration handling, and application startup logic for robustness and usability.

Files:

  • cmd/reportportal-mcp-server/main.go
🧠 Learnings (1)
📚 Learning: 2025-09-17T11:00:40.261Z
Learnt from: CR
PR: reportportal/reportportal-mcp-server#0
File: README.md:0-0
Timestamp: 2025-09-17T11:00:40.261Z
Learning: Applies to cmd/reportportal-mcp-server/**/*.go : Keep the main server entrypoint under cmd/reportportal-mcp-server and build from that path

Applied to files:

  • cmd/reportportal-mcp-server/main.go
🧬 Code graph analysis (13)
internal/mcp_handlers/items.go (4)
internal/analytics/analytics.go (1)
  • Analytics (69-81)
internal/utils/mcp_utils.go (6)
  • NewProjectParameter (65-71)
  • SetPaginationOptions (14-29)
  • WithAnalytics (83-97)
  • ExtractProject (73-75)
  • ApplyPaginationOptions (32-63)
  • ReadResponseBody (102-111)
internal/utils/utils.go (16)
  • DefaultSortingForItems (18-18)
  • DefaultProviderType (21-21)
  • DefaultFilterEqHasStats (23-23)
  • DefaultFilterEqHasChildren (22-22)
  • DefaultFilterInType (24-24)
  • ProcessStartTimeFilter (99-121)
  • ProcessAttributeKeys (124-152)
  • ExtractResponseError (37-62)
  • ReadResponseBodyRaw (193-215)
  • IsTextContent (154-174)
  • FirstPage (15-15)
  • DefaultPageSize (16-16)
  • DefaultSortingForLogs (20-20)
  • DefaultItemLogLevel (26-26)
  • DefaultSortingForSuites (19-19)
  • DefaultFilterInTypeSuites (25-25)
internal/utils/ctx_utils.go (1)
  • WithQueryParams (18-21)
internal/mcp_handlers/launches.go (4)
internal/analytics/analytics.go (1)
  • Analytics (69-81)
internal/utils/mcp_utils.go (6)
  • NewProjectParameter (65-71)
  • SetPaginationOptions (14-29)
  • WithAnalytics (83-97)
  • ExtractProject (73-75)
  • ApplyPaginationOptions (32-63)
  • ReadResponseBody (102-111)
internal/utils/utils.go (4)
  • DefaultSortingForLaunches (17-17)
  • ProcessStartTimeFilter (99-121)
  • ProcessAttributeKeys (124-152)
  • ExtractResponseError (37-62)
internal/utils/ctx_utils.go (1)
  • WithQueryParams (18-21)
internal/mcp_handlers/server.go (5)
internal/analytics/analytics.go (2)
  • Analytics (69-81)
  • NewAnalytics (90-131)
internal/middleware/middleware.go (1)
  • QueryParamsMiddleware (11-32)
internal/mcp_handlers/launches.go (1)
  • RegisterLaunchTools (41-56)
internal/mcp_handlers/items.go (1)
  • RegisterTestItemTools (41-54)
internal/promptreader/promptreader.go (1)
  • PromptHandlerPair (16-19)
internal/http/http_server.go (7)
internal/analytics/analytics.go (3)
  • Analytics (69-81)
  • NewAnalytics (90-131)
  • BatchSendInterval (29-29)
internal/utils/ctx_utils.go (1)
  • ValidateRPToken (31-44)
internal/middleware/middleware.go (1)
  • QueryParamsMiddleware (11-32)
internal/mcp_handlers/launches.go (1)
  • RegisterLaunchTools (41-56)
internal/mcp_handlers/items.go (1)
  • RegisterTestItemTools (41-54)
internal/mcp_handlers/server.go (2)
  • ReadPrompts (71-93)
  • PromptFiles (19-19)
internal/security/http_token_middleware.go (1)
  • HTTPTokenMiddleware (19-42)
internal/middleware/middleware.go (2)
internal/security/http_token_middleware.go (1)
  • GetTokenFromContext (75-78)
internal/utils/ctx_utils.go (1)
  • QueryParamsFromContext (23-27)
internal/security/http_token_middleware.go (1)
internal/utils/ctx_utils.go (2)
  • ContextKey (12-12)
  • ValidateRPToken (31-44)
internal/http/http_server_batch_test.go (1)
internal/http/http_server.go (3)
  • HTTPServer (66-77)
  • HTTPServerConfig (50-63)
  • NewHTTPServer (87-144)
internal/middleware/middleware_test.go (1)
internal/utils/ctx_utils.go (1)
  • WithQueryParams (18-21)
cmd/reportportal-mcp-server/main.go (1)
internal/config/cli.go (2)
  • AppVersion (25-29)
  • RunApp (336-389)
internal/analytics/analytics_test.go (1)
internal/utils/mcp_utils.go (1)
  • WithAnalytics (83-97)
internal/utils/utils_test.go (1)
internal/utils/utils.go (1)
  • ProcessAttributeKeys (124-152)
internal/utils/mcp_utils.go (1)
internal/utils/utils.go (4)
  • FirstPage (15-15)
  • DefaultPageSize (16-16)
  • PaginatedRequest (31-35)
  • ReadResponseBodyRaw (193-215)
internal/config/cli.go (3)
internal/http/http_server.go (2)
  • HTTPServerConfig (50-63)
  • CreateHTTPServerWithMiddleware (224-250)
internal/analytics/analytics.go (3)
  • GetAnalyticArg (232-246)
  • Analytics (69-81)
  • StopAnalytics (403-412)
internal/mcp_handlers/server.go (1)
  • NewServer (21-68)
🔇 Additional comments (15)
internal/mcp_handlers/items.go (1)

40-54: LGTM! Clean refactoring to use centralized utilities and analytics.

The registration function properly integrates analytics and uses the refactored utils package. The tool registration is clear and consistent.

internal/mcp_handlers/launches.go (2)

40-56: LGTM! Well-structured registration with analytics integration.

The launch tools registration is clean and properly integrates the analytics client. The function signature is clear and all tools are registered systematically.


279-281: Good fix: launch_id now uses numeric type.

Correctly using mcp.WithNumber for the launch_id parameter aligns the schema with the runtime RequireInt call, eliminating the previous type mismatch.

Also applies to lines 310-312, 387-389, 431-433.

internal/http/http_server.go (3)

28-31: Good addition: MaxRequestBodySize constant for DoS prevention.

Defining a 4MB limit for request bodies helps prevent memory exhaustion attacks. This is properly used in mcpMiddleware at line 432.


110-126: LGTM! Proper non-fatal analytics initialization.

The analytics setup correctly handles failures by logging warnings and continuing with nil analytics, ensuring the HTTP server remains functional even if analytics configuration is incomplete or invalid.


156-164: Clean integration of refactored registration functions.

The tool registration properly uses the new Register*Tools functions from mcp_handlers, passing the analytics client correctly. This maintains clear separation of concerns.

internal/utils/ctx_utils_test.go (1)

1-1: LGTM! Test package properly aligned with refactored structure.

The package rename from mcpreportportal to utils correctly reflects the refactored package structure. Test logic remains intact and valid.

internal/utils/utils_test.go (1)

149-172: LGTM! Comprehensive test coverage.

The test suite provides excellent coverage of ProcessAttributeKeys with clear test cases, edge case handling, and a performance test. The test structure follows Go best practices with table-driven tests and descriptive test names.

Minor observation: In the performance test (line 156), string(rune(i)) may produce unexpected key names for large values of i (when i > 0x10FFFF it wraps). Consider using fmt.Sprintf("key%d:", i) for predictable key generation in performance tests.

internal/utils/mcp_utils.go (3)

37-63: LGTM! Pagination handling is correct and safe.

The pagination parameter extraction and clamping logic properly:

  • Guards against negative values
  • Clamps to math.MaxInt32 to prevent overflow
  • Uses appropriate defaults from constants
  • Includes gosec nolint comments for the safe int-to-int32 conversions

The fluent API usage via PaginatedRequest interface is clean and type-safe.


82-97: LGTM! Analytics wrapper follows middleware pattern correctly.

The WithAnalytics wrapper properly:

  • Checks for nil tracker before tracking
  • Tracks synchronously (just incrementing a counter)
  • Executes the wrapped handler and returns its result unmodified
  • Uses the standard middleware wrapping pattern

99-111: LGTM! HTTP response helper provides good abstraction.

The ReadResponseBody function:

  • Delegates to ReadResponseBodyRaw for the actual reading
  • Converts errors to MCP tool error results
  • Returns the body as MCP text content on success

The delegation pattern keeps concerns separated and error handling consistent.

internal/config/cli.go (4)

31-74: LGTM! Well-structured CLI flag definitions.

The flag definitions are clear, well-documented, and properly organized into common and HTTP-specific categories. The use of environment variable sources and descriptive usage strings follows best practices.


122-140: LGTM! Clean logger initialization pattern.

The InitLogger function properly:

  • Returns a CLI Before hook
  • Unmarshals log level from flags
  • Configures slog with appropriate handler options
  • Returns context for chaining

222-242: LGTM! Robust error handling with analytics cleanup.

The HandleServerError function properly:

  • Distinguishes between graceful shutdowns and actual errors
  • Handles multiple expected error types
  • Ensures analytics cleanup in all paths
  • Provides clear logging

335-389: LGTM! Clean CLI orchestration with mode-based flag configuration.

The RunApp function provides a well-structured entry point that:

  • Dynamically configures flags based on MCP_MODE
  • Provides comprehensive help documentation
  • Routes to appropriate server implementation
  • Has a sensible fallback to stdio mode

The implementation follows urfave/cli v3 best practices.


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

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR refactors the MCP server codebase by separating the application logic into appropriate packages. The refactoring moves from a single mcpreportportal package to a more modular structure with distinct packages for different concerns.

  • Separates utilities, analytics, security, middleware, HTTP handling, and MCP handlers into their own packages
  • Creates proper separation of concerns with dedicated packages for config, security, analytics, and HTTP handling
  • Maintains all existing functionality while improving code organization and maintainability

Reviewed Changes

Copilot reviewed 19 out of 20 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
internal/utils/ Utility functions moved from main package with proper exports and imports
internal/analytics/ Analytics functionality separated into its own package
internal/security/ HTTP token middleware moved to dedicated security package
internal/middleware/ Query parameter middleware separated into middleware package
internal/mcp_handlers/ MCP handlers moved with registration functions for better organization
internal/http/ HTTP server functionality moved to dedicated package
internal/config/ CLI configuration and command creation moved to config package
cmd/reportportal-mcp-server/main.go Main function simplified to use the new config package

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

@AleksandrParamonoff AleksandrParamonoff force-pushed the refactoring-detaching-app-to-modules branch from 7a1a071 to a456b0b Compare September 23, 2025 15:24
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

Copilot reviewed 19 out of 20 changed files in this pull request and generated 3 comments.


Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

@AleksandrParamonoff AleksandrParamonoff force-pushed the add-remote-local-deploy-tasks branch 2 times, most recently from 9a10381 to 18dce09 Compare September 26, 2025 16:33
Base automatically changed from add-remote-local-deploy-tasks to develop September 29, 2025 10:35
@AleksandrParamonoff AleksandrParamonoff force-pushed the refactoring-detaching-app-to-modules branch from a456b0b to 8d438fb Compare September 29, 2025 16:21
Copy link

@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: 21

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (22)
internal/mcp_handlers/launches_test.go (3)

22-28: Fix argument order in assertions for clearer failures

Testify expects expected, actual. Swap args to avoid confusing diffs.

-	require.Equal(t, vals.Get("filter").String(), "xxx")
-	require.Equal(t, vals.Get("launch").String(), "123")
+	require.Equal(t, "xxx", vals.Get("filter").String())
+	require.Equal(t, "123", vals.Get("launch").String())

33-41: Handle marshal error and use the marshaled bytes consistently

Avoid ignoring errors; use a single var for the JSON and assert against it.

-	launches, _ := json.Marshal(testLaunches())
+	launchesJSON, err := json.Marshal(testLaunches())
+	require.NoError(t, err)
@@
-		_, _ = w.Write(launches)
+		_, _ = w.Write(launchesJSON)
@@
-	assert.Equal(t, string(launches), text)
+	assert.Equal(t, string(launchesJSON), text)

Also applies to: 68-69


64-66: Guard against empty content before indexing

Add a length check to make the failure message actionable.

-	var textContent mcp.TextContent
-	require.IsType(t, textContent, result.Content[0])
+	require.NotEmpty(t, result.Content, "tool response should contain at least one content item")
+	var textContent mcp.TextContent
+	require.IsType(t, textContent, result.Content[0])
internal/utils/ctx_utils_test.go (2)

10-12: Make the test helper unexported (or add a doc comment) to satisfy linters

Exported symbol in tests without doc triggers lint warnings. Unexport for clarity.

-func IsUUIDFormat(token string) bool {
+func isUUIDFormat(token string) bool {
@@
-			result := IsUUIDFormat(tt.token)
+			result := isUUIDFormat(tt.token)

Also applies to: 67-72


76-102: Cover trim and threshold edges in ValidateRPToken tests

Add cases for surrounding spaces (should pass) and 15-char token (should fail with current >=16 rule).

 		{
 			name:     "long token without prefix should pass",
 			token:    "this_is_a_very_long_token_that_should_pass",
 			expected: true,
 		},
+		{
+			name:     "valid UUID with surrounding spaces should pass",
+			token:    "  014b329b-a882-4c2d-9988-c2f6179a421b  ",
+			expected: true,
+		},
+		{
+			name:     "15-char token should fail threshold",
+			token:    "aaaaaaaaaaaaaaa",
+			expected: false,
+		},

If RP tokens have an officially documented minimum length, confirm it and adjust the threshold accordingly.

internal/middleware/middleware.go (1)

17-27: Nit: Skip encode on empty params to avoid needless allocations

Micro-optimization: bail out if no paramsFromContext entries.

-	paramsFromContext, ok := utils.QueryParamsFromContext(rq.Context())
-	if ok && paramsFromContext != nil {
+	paramsFromContext, ok := utils.QueryParamsFromContext(rq.Context())
+	if ok && paramsFromContext != nil && len(paramsFromContext) > 0 {
internal/utils/utils_test.go (1)

154-158: Use strconv.Itoa for clearer, printable perf test data

string(rune(i)) yields control chars for small i. Prefer decimal strings to keep test data readable and consistent.

- for i := 0; i < 1000; i++ {
-     keys = append(keys, "key"+string(rune(i))+":")
- }
+ for i := 0; i < 1000; i++ {
+     keys = append(keys, "key"+strconv.Itoa(i)+":")
+ }

Add: import "strconv".
As per coding guidelines.

internal/analytics/analytics.go (5)

274-291: Stop can block longer than the advertised timeout; make shutdown bounded

If a send is in-flight (httpClient.Timeout = 10s), Stop may wait past the 5s timeout due to the unconditional a.wg.Wait(). Bound the wait and proceed with shutdown.

 func (a *Analytics) Stop() {
   if a == nil || a.stopChan == nil {
     return
   }
 
   slog.Debug("Stopping analytics metrics processor")
   close(a.stopChan)
 
-  // Wait for ticker goroutine to finish
-  select {
-  case <-a.tickerDone:
-    slog.Debug("Analytics metrics processor stopped gracefully")
-  case <-time.After(5 * time.Second):
-    slog.Warn("Analytics metrics processor stop timeout")
-  }
-
-  a.wg.Wait()
+  // Wait for ticker goroutine to finish, but don’t block indefinitely
+  select {
+  case <-a.tickerDone:
+    slog.Debug("Analytics metrics processor stopped gracefully")
+  case <-time.After(5 * time.Second):
+    slog.Warn("Analytics metrics processor stop timeout")
+  }
+  done := make(chan struct{})
+  go func() { a.wg.Wait(); close(done) }()
+  select {
+  case <-done:
+    slog.Debug("Analytics worker goroutine joined")
+  case <-time.After(5 * time.Second):
+    slog.Warn("Analytics worker join timeout; proceeding with shutdown")
+  }
 }

Additionally consider lowering httpClient.Timeout or making it configurable to align with shutdown expectations.
As per coding guidelines.


62-68: Correct the field name to match units and JSON: TimestampMicros

The struct uses TimestampMS for a JSON key timestamp_micros, and you populate it with UnixMicro(). Rename for clarity.

-type GAPayload struct {
-    ClientID    string    `json:"client_id"`
-    UserID      string    `json:"user_id,omitempty"`
-    Events      []GAEvent `json:"events"`
-    TimestampMS int64     `json:"timestamp_micros"`
-}
+type GAPayload struct {
+    ClientID       string    `json:"client_id"`
+    UserID         string    `json:"user_id,omitempty"`
+    Events         []GAEvent `json:"events"`
+    TimestampMicros int64    `json:"timestamp_micros"`
+}

And update the assignment site accordingly.
As per coding guidelines.


152-158: Fix comments to reflect actual identifiers

Both ClientID and UserID are set to the SHA256 hex hash, not “number with '.' postfix”. Update the comments to avoid confusion.

- ClientID:    a.config.UserID, // Contains number with "." postfix
- UserID:      a.config.UserID, // Contains number with "." postfix
+ ClientID:    a.config.UserID, // Stable pseudonymous ID (SHA256 hex of RP token)
+ UserID:      a.config.UserID, // Same as ClientID for GA4 correlation

26-31: HashAlgorithm label is misleading

HashAlgorithm = "SHA256-128bit" contradicts the full SHA256 hex produced by HashToken. Either drop the constant or rename to reflect 256-bit.

- HashAlgorithm = "SHA256-128bit"
+ HashAlgorithm = "SHA256-256bit"

164-177: Consider minimizing payload/response logging to reduce PII footprint and log volume

You pretty-print entire request/response bodies. Even with hashed IDs, this can be high-volume and potentially sensitive. Recommend gating under a more verbose level or redacting IDs.

  • Log counts and status only at debug.
  • Move full payload/response to trace or behind an env flag.
    As per coding guidelines.
internal/mcp_handlers/server.go (1)

76-92: Slice preallocation bug: handlers has len(entries) zero-values then appends prompts.

This returns extra empty elements. Initialize with length 0 and capacity len(entries) (or no prealloc).

-	handlers := make([]promptreader.PromptHandlerPair, len(entries))
+	handlers := make([]promptreader.PromptHandlerPair, 0, len(entries))

Optionally guard directories:

-	for _, entry := range entries {
+	for _, entry := range entries {
+		if entry.IsDir() {
+			continue
+		}

Add a unit test to assert no zero-value entries are present. As per coding guidelines.

internal/http/http_server.go (3)

455-468: Unbounded body read: add max payload limit to prevent memory abuse.

Reading arbitrary-size bodies risks OOM/DoS. Limit to a sane size before json.Unmarshal.

 func (hs *HTTPServer) validateMCPPayload(r *http.Request) bool {
-	// Read the request body
-	body, err := io.ReadAll(r.Body)
+	// Read the request body with a limit (e.g., 2 MiB)
+	const maxMCPPayload = 2 << 20 // 2 MiB
+	body, err := io.ReadAll(io.LimitReader(r.Body, maxMCPPayload))
 	if err != nil {
 		return false
 	}

Optionally add an explicit size check and reject if exactly at limit with trailing data. As per best practices.


444-447: Content-Type check: parse media type instead of substring match.

Substring checks can be brittle. Use mime.ParseMediaType to handle parameters and case correctly.

-	contentType := r.Header.Get("Content-Type")
-	if !strings.Contains(contentType, "application/json") {
+	ct, _, _ := mime.ParseMediaType(r.Header.Get("Content-Type"))
+	if ct != "application/json" {
 		return false
 	}

Add:

+	"mime"

to imports.


31-41: Transport tuning nit: IdleConnTimeout tied to request timeout.

IdleConnTimeout typically benefits from a higher value (e.g., 90s) independent of per-request timeout, improving connection reuse.

-		IdleConnTimeout:     timeout,
+		IdleConnTimeout:     90 * time.Second,

This is a minor performance tweak; keep as-is if constrained by environment.

internal/utils/mcp_utils.go (1)

92-105: ReadResponseBody: good cleanup wrapper; consider propagating HTTP status on errors.

Optional: include response.StatusCode in the error text to aid debugging.

-		return mcp.NewToolResultError(
-			fmt.Sprintf("failed to read response body: %v", err),
-		), nil
+		status := 0
+		if response != nil {
+			status = response.StatusCode
+		}
+		return mcp.NewToolResultError(
+			fmt.Sprintf("failed to read response body (status %d): %v", status, err),
+		), nil
internal/utils/utils.go (3)

154-174: Fix IsTextContent switch: only 'application/csv' currently returns true

Each case is empty except the last; Go doesn't fall through implicitly. As written, JSON/XML/etc. return false.

Apply:

- switch lowerType {
- case "application/json":
- case "application/xml":
- case "application/javascript":
- case "application/xhtml+xml":
- case "application/yaml":
- case "application/csv":
-   return true
- }
+ switch lowerType {
+ case "application/json",
+   "application/xml",
+   "application/javascript",
+   "application/xhtml+xml",
+   "application/yaml",
+   "application/csv":
+   return true
+ }

123-151: Attribute key processing drops the key and keeps the value (likely wrong)

For inputs like "env:prod", the code keeps "prod" instead of ensuring "env:". This corrupts filters.

Use the key prefix before ':' and normalize with a trailing colon; then merge:

- if colonIndex := strings.Index(key, ":"); colonIndex > 0 && colonIndex < len(key)-1 {
-   processed = append(processed, key[colonIndex+1:]) // Extract postfix
- } else if !strings.HasSuffix(key, ":") {
-   processed = append(processed, key+":") // Add suffix
- } else {
-   processed = append(processed, key) // Keep as is
- }
+ // Keep only the key prefix and ensure trailing colon
+ if idx := strings.IndexByte(key, ':'); idx >= 0 {
+   key = key[:idx]
+ }
+ key = strings.TrimSpace(key)
+ if key == "" {
+   continue
+ }
+ processed = append(processed, key+":")

Follow-up: add table-driven tests covering:

  • "key1,key2" -> "key1:,key2:"
  • "key1:,key2:" -> "key1:,key2:"
  • "key1:value,key2:val2" -> "key1:,key2:"
  • Merge with existing filterAttributes.

37-62: Bound response-body reads to avoid unbounded memory on large/error responses

Both ExtractResponseError and ReadResponseBodyRaw call io.ReadAll without limits. A hostile upstream could return very large bodies.

Cap reads (e.g., 1MiB):

+ const maxBodyBytes = 1 << 20 // 1 MiB
...
- if errContent, rErr := io.ReadAll(rs.Body); rErr == nil {
+ if errContent, rErr := io.ReadAll(io.LimitReader(rs.Body, maxBodyBytes)); rErr == nil {
    errText = errText + ": " + string(errContent)
 }

And:

- rawBody, err := io.ReadAll(response.Body)
+ rawBody, err := io.ReadAll(io.LimitReader(response.Body, maxBodyBytes))

Optionally log when truncated; keep it simple for now.

Also applies to: 193-215

internal/mcp_handlers/launches.go (1)

253-256: Improve error detail by extracting response body on failure

Consistent with other tools, include server-provided error text.

- launches, _, err := apiRequest.Execute()
+ launches, rs, err := apiRequest.Execute()
  if err != nil {
-   return mcp.NewToolResultError(err.Error()), nil
+   return mcp.NewToolResultError(utils.ExtractResponseError(err, rs)), nil
  }
internal/mcp_handlers/items.go (1)

584-591: Restore hyphenated MCP request keys

The tool options register parameters like filter-cnt-name, filter-has-compositeAttribute, etc., but the handler now reads dotted variants (filter.cnt.name, filter.has.compositeAttribute, …). That mismatch means every optional filter is silently dropped, so suites are always fetched without user-supplied filters—a regression. Please revert to the hyphenated keys when calling request.GetString.

-			filterName := request.GetString("filter.cnt.name", "")
-			filterAttributes := request.GetString("filter.has.compositeAttribute", "")
-			filterAttributeKeys := request.GetString("filter.has.attributeKey", "")
-			filterDescription := request.GetString("filter.cnt.description", "")
-			filterStartTimeFrom := request.GetString("filter.btw.startTime.from", "")
-			filterStartTimeTo := request.GetString("filter.btw.startTime.to", "")
-			filterParentId := request.GetString("filter.eq.parentId", "")
+			filterName := request.GetString("filter-cnt-name", "")
+			filterAttributes := request.GetString("filter-has-compositeAttribute", "")
+			filterAttributeKeys := request.GetString("filter-has-attributeKey", "")
+			filterDescription := request.GetString("filter-cnt-description", "")
+			filterStartTimeFrom := request.GetString("filter-btw-startTime-from", "")
+			filterStartTimeTo := request.GetString("filter-btw-startTime-to", "")
+			filterParentId := request.GetString("filter-eq-parentId", "")
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c7335a4 and 8d438fb.

📒 Files selected for processing (18)
  • cmd/reportportal-mcp-server/main.go (2 hunks)
  • internal/analytics/analytics.go (3 hunks)
  • internal/analytics/analytics_test.go (5 hunks)
  • internal/config/cli.go (1 hunks)
  • internal/http/http_server.go (12 hunks)
  • internal/http/http_server_batch_test.go (1 hunks)
  • internal/mcp_handlers/items.go (21 hunks)
  • internal/mcp_handlers/launches.go (14 hunks)
  • internal/mcp_handlers/launches_test.go (2 hunks)
  • internal/mcp_handlers/server.go (4 hunks)
  • internal/middleware/middleware.go (1 hunks)
  • internal/middleware/middleware_test.go (2 hunks)
  • internal/security/http_token_middleware.go (2 hunks)
  • internal/utils/ctx_utils.go (2 hunks)
  • internal/utils/ctx_utils_test.go (1 hunks)
  • internal/utils/mcp_utils.go (1 hunks)
  • internal/utils/utils.go (7 hunks)
  • internal/utils/utils_test.go (3 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.go

📄 CodeRabbit inference engine (README.md)

**/*.go: Run formatter (task fmt / go fmt) to keep all Go source files consistently formatted
Run linter (task lint) to maintain code quality across Go files

Files:

  • internal/middleware/middleware_test.go
  • internal/analytics/analytics.go
  • internal/security/http_token_middleware.go
  • internal/http/http_server_batch_test.go
  • internal/mcp_handlers/launches_test.go
  • cmd/reportportal-mcp-server/main.go
  • internal/utils/ctx_utils.go
  • internal/http/http_server.go
  • internal/config/cli.go
  • internal/utils/ctx_utils_test.go
  • internal/utils/utils_test.go
  • internal/utils/mcp_utils.go
  • internal/mcp_handlers/items.go
  • internal/middleware/middleware.go
  • internal/analytics/analytics_test.go
  • internal/utils/utils.go
  • internal/mcp_handlers/launches.go
  • internal/mcp_handlers/server.go

⚙️ CodeRabbit configuration file

Focus on Go best practices, error handling, concurrency safety, performance, and security. Check for proper resource cleanup and context handling.

Files:

  • internal/middleware/middleware_test.go
  • internal/analytics/analytics.go
  • internal/security/http_token_middleware.go
  • internal/http/http_server_batch_test.go
  • internal/mcp_handlers/launches_test.go
  • cmd/reportportal-mcp-server/main.go
  • internal/utils/ctx_utils.go
  • internal/http/http_server.go
  • internal/config/cli.go
  • internal/utils/ctx_utils_test.go
  • internal/utils/utils_test.go
  • internal/utils/mcp_utils.go
  • internal/mcp_handlers/items.go
  • internal/middleware/middleware.go
  • internal/analytics/analytics_test.go
  • internal/utils/utils.go
  • internal/mcp_handlers/launches.go
  • internal/mcp_handlers/server.go
**/*_test.go

⚙️ CodeRabbit configuration file

Ensure comprehensive test coverage, proper test isolation, meaningful assertions, and good test naming conventions.

Files:

  • internal/middleware/middleware_test.go
  • internal/http/http_server_batch_test.go
  • internal/mcp_handlers/launches_test.go
  • internal/utils/ctx_utils_test.go
  • internal/utils/utils_test.go
  • internal/analytics/analytics_test.go
cmd/reportportal-mcp-server/**/*.go

📄 CodeRabbit inference engine (README.md)

Keep the main server entrypoint under cmd/reportportal-mcp-server and build from that path

Files:

  • cmd/reportportal-mcp-server/main.go
cmd/**

⚙️ CodeRabbit configuration file

Review CLI interface design, configuration handling, and application startup logic for robustness and usability.

Files:

  • cmd/reportportal-mcp-server/main.go
🧠 Learnings (1)
📚 Learning: 2025-09-17T11:00:40.261Z
Learnt from: CR
PR: reportportal/reportportal-mcp-server#0
File: README.md:0-0
Timestamp: 2025-09-17T11:00:40.261Z
Learning: Applies to cmd/reportportal-mcp-server/**/*.go : Keep the main server entrypoint under cmd/reportportal-mcp-server and build from that path

Applied to files:

  • internal/mcp_handlers/launches_test.go
  • cmd/reportportal-mcp-server/main.go
  • internal/config/cli.go
  • internal/mcp_handlers/items.go
  • internal/mcp_handlers/launches.go
  • internal/mcp_handlers/server.go
🧬 Code graph analysis (13)
internal/middleware/middleware_test.go (1)
internal/utils/ctx_utils.go (1)
  • WithQueryParams (18-21)
internal/security/http_token_middleware.go (1)
internal/utils/ctx_utils.go (2)
  • ContextKey (12-12)
  • ValidateRPToken (31-44)
internal/http/http_server_batch_test.go (1)
internal/http/http_server.go (3)
  • HTTPServer (62-73)
  • HTTPServerConfig (46-59)
  • NewHTTPServer (83-140)
cmd/reportportal-mcp-server/main.go (1)
internal/config/cli.go (2)
  • AppVersion (26-30)
  • RunApp (336-400)
internal/http/http_server.go (7)
internal/analytics/analytics.go (3)
  • Analytics (71-83)
  • NewAnalytics (92-133)
  • BatchSendInterval (31-31)
internal/utils/ctx_utils.go (1)
  • ValidateRPToken (31-44)
internal/middleware/middleware.go (1)
  • QueryParamsMiddleware (10-28)
internal/mcp_handlers/launches.go (1)
  • RegisterLaunchTools (41-56)
internal/mcp_handlers/items.go (1)
  • RegisterTestItemTools (41-54)
internal/mcp_handlers/server.go (2)
  • ReadPrompts (71-93)
  • PromptFiles (19-19)
internal/security/http_token_middleware.go (1)
  • HTTPTokenMiddleware (19-42)
internal/config/cli.go (3)
internal/http/http_server.go (2)
  • HTTPServerConfig (46-59)
  • CreateHTTPServerWithMiddleware (220-246)
internal/analytics/analytics.go (3)
  • GetAnalyticArg (234-248)
  • Analytics (71-83)
  • StopAnalytics (405-414)
internal/mcp_handlers/server.go (1)
  • NewServer (21-68)
internal/utils/utils_test.go (1)
internal/utils/utils.go (1)
  • ProcessAttributeKeys (124-152)
internal/utils/mcp_utils.go (1)
internal/utils/utils.go (4)
  • FirstPage (15-15)
  • DefaultPageSize (16-16)
  • PaginatedRequest (31-35)
  • ReadResponseBodyRaw (193-215)
internal/mcp_handlers/items.go (4)
internal/analytics/analytics.go (1)
  • Analytics (71-83)
internal/utils/mcp_utils.go (6)
  • NewProjectParameter (58-64)
  • SetPaginationOptions (13-28)
  • WithAnalytics (76-90)
  • ExtractProject (66-68)
  • ApplyPaginationOptions (31-56)
  • ReadResponseBody (95-104)
internal/utils/utils.go (16)
  • DefaultSortingForItems (18-18)
  • DefaultProviderType (21-21)
  • DefaultFilterEqHasStats (23-23)
  • DefaultFilterEqHasChildren (22-22)
  • DefaultFilterInType (24-24)
  • ProcessStartTimeFilter (99-121)
  • ProcessAttributeKeys (124-152)
  • ExtractResponseError (37-62)
  • ReadResponseBodyRaw (193-215)
  • IsTextContent (154-174)
  • FirstPage (15-15)
  • DefaultPageSize (16-16)
  • DefaultSortingForLogs (20-20)
  • DefaultItemLogLevel (26-26)
  • DefaultSortingForSuites (19-19)
  • DefaultFilterInTypeSuites (25-25)
internal/utils/ctx_utils.go (1)
  • WithQueryParams (18-21)
internal/middleware/middleware.go (2)
internal/security/http_token_middleware.go (1)
  • GetTokenFromContext (75-78)
internal/utils/ctx_utils.go (1)
  • QueryParamsFromContext (23-27)
internal/analytics/analytics_test.go (1)
internal/utils/mcp_utils.go (1)
  • WithAnalytics (76-90)
internal/mcp_handlers/launches.go (4)
internal/analytics/analytics.go (1)
  • Analytics (71-83)
internal/utils/mcp_utils.go (6)
  • NewProjectParameter (58-64)
  • SetPaginationOptions (13-28)
  • WithAnalytics (76-90)
  • ExtractProject (66-68)
  • ApplyPaginationOptions (31-56)
  • ReadResponseBody (95-104)
internal/utils/utils.go (4)
  • DefaultSortingForLaunches (17-17)
  • ProcessStartTimeFilter (99-121)
  • ProcessAttributeKeys (124-152)
  • ExtractResponseError (37-62)
internal/utils/ctx_utils.go (1)
  • WithQueryParams (18-21)
internal/mcp_handlers/server.go (5)
internal/analytics/analytics.go (2)
  • Analytics (71-83)
  • NewAnalytics (92-133)
internal/middleware/middleware.go (1)
  • QueryParamsMiddleware (10-28)
internal/mcp_handlers/launches.go (1)
  • RegisterLaunchTools (41-56)
internal/mcp_handlers/items.go (1)
  • RegisterTestItemTools (41-54)
internal/promptreader/promptreader.go (1)
  • PromptHandlerPair (16-19)
🪛 GitHub Check: build
internal/http/http_server.go

[failure] 19-19:
File is not properly formatted (goimports)


[failure] 20-20:
File is not properly formatted (gci)

🪛 GitHub Actions: Build
internal/http/http_server.go

[error] 20-20: golangci-lint: File is not properly formatted (gci).

🔇 Additional comments (10)
internal/middleware/middleware_test.go (1)

13-29: LGTM on query params merge behavior

Adds values without clobbering existing query param, as intended.

internal/utils/ctx_utils.go (1)

31-44: Confirm minimum length for non-UUID tokens

The >=16 heuristic may accept weak inputs. If RP non-UUID tokens are longer, tighten this check.

Would you like me to look up ReportPortal token format/length and adjust the implementation and tests accordingly?

internal/mcp_handlers/launches_test.go (1)

43-49: No stale references to legacy toolGetLaunches found
Search for toolGetLaunches( returned zero matches; no further action required.

internal/security/http_token_middleware.go (2)

15-16: Good switch to a typed context key

Using utils.ContextKey for RPTokenContextKey avoids collisions with other context values and keeps the API explicit. Looks good.


54-59: Token validation routed through utils — confirm tests cover edge cases

The move to utils.ValidateRPToken centralizes validation. Please ensure we have tests for:

  • Lower/upper-case “Bearer” with extra spaces
  • Invalid UUIDs and short tokens rejected
  • Very long tokens accepted

This also addresses a prior note about exporting ValidateRPToken.

internal/utils/utils_test.go (1)

138-145: API usage updated to exported ProcessAttributeKeys — LGTM

Call sites correctly reference utils.ProcessAttributeKeys and assertions remain meaningful.

internal/http/http_server.go (1)

395-396: Use the unified analytics import for interval string.

After deduping imports, ensure this still references analytics.BatchSendInterval.

No code change needed if you applied the import fix; re-run tests to confirm.

internal/utils/utils.go (1)

14-28: Constants export + math.MaxInt32 usage look good

Using math.MaxInt32 via an alias constant is fine and improves clarity.

internal/config/cli.go (2)

32-75: Sources API usage is correct cli.EnvVars returns a ValueSourceChain, which StringFlag.Sources accepts in urfave/cli v3.


243-271: Remove mcpServer.Stop() suggestion (method unavailable)
server.MCPServer does not expose a Stop() method; the stdio server cleanly shuts down when the passed context is cancelled in Listen().

Likely an incorrect or invalid review comment.

@AleksandrParamonoff AleksandrParamonoff force-pushed the refactoring-detaching-app-to-modules branch from 8d438fb to d2483c9 Compare September 29, 2025 16:33
Copy link

@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: 10

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (16)
internal/utils/ctx_utils_test.go (4)

70-71: Use assert.Equalf to format the failure message

assert.Equal doesn’t format msg placeholders. Use the “f” variant so %s/%v are rendered.

-			assert.Equal(t, tt.expected, result,
-				"IsUUIDFormat(%s) = %v, expected %v", tt.token, result, tt.expected)
+			assert.Equalf(t, tt.expected, result,
+				"IsUUIDFormat(%s) = %v, expected %v", tt.token, result, tt.expected)

As per coding guidelines.


106-109: Format the assertion message for ValidateRPToken tests

Same nit as above: switch to Equalf so the token/result are interpolated.

-			assert.Equal(t, tt.expected, result,
-				"ValidateRPToken(%s) = %v, expected %v", tt.token, result, tt.expected)
+			assert.Equalf(t, tt.expected, result,
+				"ValidateRPToken(%s) = %v, expected %v", tt.token, result, tt.expected)

As per coding guidelines.


14-14: Run subtests in parallel

This table test is independent; add t.Parallel() to speed up CI and surface data races.

 func TestIsUUIDFormat(t *testing.T) {
+	t.Parallel()

As per coding guidelines.


76-76: Parallelize the second table test too

Same reasoning as above.

 func TestValidateRPToken(t *testing.T) {
+	t.Parallel()

As per coding guidelines.

internal/utils/ctx_utils.go (1)

37-44: Optionally reject the all‑zero UUID as a valid RP token

Format‑valid but nil UUIDs are trivially guessable; treat them as invalid while keeping non‑UUID tokens supported.

-	// Check for valid UUID format using proper parsing
-	if uuid.Validate(token) == nil {
-		return true
-	}
+	// Accept proper UUIDs, but reject the nil UUID
+	if u, err := uuid.Parse(token); err == nil {
+		return u != uuid.Nil
+	}

As per coding guidelines.

internal/analytics/analytics_test.go (1)

66-73: Clean up analytics instances to avoid goroutine leaks

When NewAnalytics succeeds, register a cleanup to stop background workers.

 			} else {
 				assert.NoError(t, err)
 				assert.NotNil(t, analytics)
+				t.Cleanup(analytics.Stop)
 
 				// Analytics should be created when API secret is provided
 				assert.NotNil(t, analytics.config)
 				assert.NotNil(t, analytics.httpClient)

As per coding guidelines.

internal/utils/utils_test.go (2)

155-157: Avoid control characters in generated keys

Using string(rune(i)) injects control chars/newlines/commas into keys, making assertions noisy and platform-sensitive. Prefer decimal indices.

-import (
-	"strings"
-	"testing"
-)
+import (
+	"strconv"
+	"strings"
+	"testing"
+)
@@
-	for i := 0; i < 1000; i++ {
-		keys = append(keys, "key"+string(rune(i))+":")
-	}
+	for i := 0; i < 1000; i++ {
+		keys = append(keys, "key"+strconv.Itoa(i)+":")
+	}

8-8: Run tests in parallel

These tests are pure and can run in parallel to reduce CI time.

 func TestProcessAttributeKeys(t *testing.T) {
+	t.Parallel()
@@
 func TestProcessAttributeKeys_Performance(t *testing.T) {
+	t.Parallel()

Also applies to: 149-149

internal/security/http_token_middleware.go (2)

47-66: Reject control characters in tokens to harden header injection defenses

Add a fast-path guard for CR/LF and other control chars before calling utils.ValidateRPToken. This prevents tainted tokens from ever reaching context and later header propagation.

 func extractRPTokenFromRequest(r *http.Request) string {
 	auth := r.Header.Get("Authorization")
 	if auth != "" {
-		parts := strings.SplitN(auth, " ", 2)
+		parts := strings.SplitN(auth, " ", 2)
 		if len(parts) == 2 && strings.EqualFold(parts[0], "Bearer") {
-			token := strings.TrimSpace(parts[1])
+			token := strings.TrimSpace(parts[1])
+			// Reject CR/LF and control characters
+			if strings.ContainsAny(token, "\r\n") {
+				slog.Debug("Rejected RP API token containing CR/LF")
+				return ""
+			}
 			// Validate the extracted token before processing
 			if !utils.ValidateRPToken(token) {
 				slog.Debug("Invalid RP API token rejected",
 					"source", "Authorization Bearer",
 					"validation", "failed")
 				return ""
 			}

49-52: Tolerate multiple/irregular whitespace in Authorization

strings.Fields is more robust than SplitN(" ", 2) for headers like Bearer <token>.

-		parts := strings.SplitN(auth, " ", 2)
-		if len(parts) == 2 && strings.EqualFold(parts[0], "Bearer") {
-			token := strings.TrimSpace(parts[1])
+		fields := strings.Fields(auth)
+		if len(fields) == 2 && strings.EqualFold(fields[0], "Bearer") {
+			token := fields[1]
internal/analytics/analytics.go (2)

274-291: Make Stop idempotent to avoid double-close panics.

Stop closes a.stopChan without guarding multiple calls. Use sync.Once.

 type Analytics struct {
@@
-	stopChan   chan struct{}
+	stopChan   chan struct{}
 	wg         sync.WaitGroup
 	tickerDone chan struct{}
+	stopOnce   sync.Once
 }
@@
 func (a *Analytics) Stop() {
 	if a == nil || a.stopChan == nil {
 		return
 	}
 
 	slog.Debug("Stopping analytics metrics processor")
-	close(a.stopChan)
+	a.stopOnce.Do(func() { close(a.stopChan) })

Also applies to: 70-83


64-68: Field name doesn’t match units.

TimestampMS is populated with UnixMicro and marshaled as "timestamp_micros". Rename for clarity.

-type GAPayload struct {
-	ClientID    string    `json:"client_id"`
-	UserID      string    `json:"user_id,omitempty"`
-	Events      []GAEvent `json:"events"`
-	TimestampMS int64     `json:"timestamp_micros"`
-}
+type GAPayload struct {
+	ClientID       string    `json:"client_id"`
+	UserID         string    `json:"user_id,omitempty"`
+	Events         []GAEvent `json:"events"`
+	TimestampMicros int64    `json:"timestamp_micros"`
+}
@@
-		TimestampMS: time.Now().UnixMicro(),
+		TimestampMicros: time.Now().UnixMicro(),

Also applies to: 152-157

internal/mcp_handlers/server.go (1)

70-93: handlers slice preallocation bug causes leading zero entries.

You allocate len(entries) and then append, yielding a slice with len(entries) zero-values plus appended prompts. Initialize with capacity, not length.

-	handlers := make([]promptreader.PromptHandlerPair, len(entries))
+	handlers := make([]promptreader.PromptHandlerPair, 0, len(entries))
@@
-		handlers = append(handlers, prompts...)
+		handlers = append(handlers, prompts...)

Consider skipping non-file entries:

if entry.IsDir() { continue }
internal/mcp_handlers/launches.go (1)

265-267: Return MCP error result instead of a raw error for consistency.

-            if err != nil {
-                return nil, fmt.Errorf("failed to marshal response: %w", err)
-            }
+            if err != nil {
+                return mcp.NewToolResultError(fmt.Sprintf("failed to marshal response: %v", err)), nil
+            }
internal/mcp_handlers/items.go (2)

584-590: Broken filter readers: option names use hyphens, but readers use dots (filters will be ignored).

Align parameter names to the declared options.

-            filterName := request.GetString("filter.cnt.name", "")
-            filterAttributes := request.GetString("filter.has.compositeAttribute", "")
-            filterAttributeKeys := request.GetString("filter.has.attributeKey", "")
-            filterDescription := request.GetString("filter.cnt.description", "")
-            filterStartTimeFrom := request.GetString("filter.btw.startTime.from", "")
-            filterStartTimeTo := request.GetString("filter.btw.startTime.to", "")
-            filterParentId := request.GetString("filter.eq.parentId", "")
+            filterName := request.GetString("filter-cnt-name", "")
+            filterAttributes := request.GetString("filter-has-compositeAttribute", "")
+            filterAttributeKeys := request.GetString("filter-has-attributeKey", "")
+            filterDescription := request.GetString("filter-cnt-description", "")
+            filterStartTimeFrom := request.GetString("filter-btw-startTime-from", "")
+            filterStartTimeTo := request.GetString("filter-btw-startTime-to", "")
+            filterParentId := request.GetString("filter-eq-parentId", "")

401-413: Prefer numeric schema for IDs to avoid manual parsing; simplify parent-item-id handling.

Define parent-item-id as a number and use RequireInt. This removes fragile string parsing and invalid-negative checks remain intact.

@@
-            mcp.WithString("parent-item-id", // ID of the parent item
-                mcp.Description("Items with specific Parent Item ID, this is a required parameter"),
-            ),
+            mcp.WithNumber("parent-item-id", // ID of the parent item
+                mcp.Description("Items with specific Parent Item ID, this is a required parameter"),
+            ),
@@
-            parentIdStr, err := request.RequireString("parent-item-id")
+            parentIdInt, err := request.RequireInt("parent-item-id")
             if err != nil {
                 return mcp.NewToolResultError(err.Error()), nil
             }
@@
-            // Validate parentIdStr and convert it to int64
-            var parentIdValue int64
-            if parentIdStr != "" {
-                var err error
-                parentIdValue, err = strconv.ParseInt(parentIdStr, 10, 64)
-                if err != nil || parentIdValue < 0 {
-                    return mcp.NewToolResultError(
-                        fmt.Sprintf("invalid parent filter ID value: %s", parentIdStr),
-                    ), nil
-                }
-            }
+            // Validate parentId and convert to int64
+            if parentIdInt < 0 {
+                return mcp.NewToolResultError(
+                    fmt.Sprintf("invalid parent filter ID value: %d", parentIdInt),
+                ), nil
+            }
+            parentIdValue := int64(parentIdInt)
@@
-            requiredUrlParams := map[string]string{
-                "parentId": parentIdStr,
-            }
+            requiredUrlParams := map[string]string{
+                "parentId": strconv.FormatInt(parentIdValue, 10),
+            }

Also applies to: 448-453, 479-487

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8d438fb and d2483c9.

📒 Files selected for processing (18)
  • cmd/reportportal-mcp-server/main.go (2 hunks)
  • internal/analytics/analytics.go (3 hunks)
  • internal/analytics/analytics_test.go (5 hunks)
  • internal/config/cli.go (1 hunks)
  • internal/http/http_server.go (11 hunks)
  • internal/http/http_server_batch_test.go (1 hunks)
  • internal/mcp_handlers/items.go (21 hunks)
  • internal/mcp_handlers/launches.go (14 hunks)
  • internal/mcp_handlers/launches_test.go (2 hunks)
  • internal/mcp_handlers/server.go (4 hunks)
  • internal/middleware/middleware.go (1 hunks)
  • internal/middleware/middleware_test.go (2 hunks)
  • internal/security/http_token_middleware.go (2 hunks)
  • internal/utils/ctx_utils.go (2 hunks)
  • internal/utils/ctx_utils_test.go (1 hunks)
  • internal/utils/mcp_utils.go (1 hunks)
  • internal/utils/utils.go (7 hunks)
  • internal/utils/utils_test.go (3 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.go

📄 CodeRabbit inference engine (README.md)

**/*.go: Run formatter (task fmt / go fmt) to keep all Go source files consistently formatted
Run linter (task lint) to maintain code quality across Go files

Files:

  • internal/security/http_token_middleware.go
  • internal/utils/utils_test.go
  • internal/middleware/middleware_test.go
  • internal/mcp_handlers/launches_test.go
  • internal/analytics/analytics.go
  • internal/analytics/analytics_test.go
  • internal/http/http_server.go
  • internal/mcp_handlers/items.go
  • cmd/reportportal-mcp-server/main.go
  • internal/utils/utils.go
  • internal/http/http_server_batch_test.go
  • internal/mcp_handlers/server.go
  • internal/middleware/middleware.go
  • internal/utils/ctx_utils_test.go
  • internal/utils/ctx_utils.go
  • internal/config/cli.go
  • internal/utils/mcp_utils.go
  • internal/mcp_handlers/launches.go

⚙️ CodeRabbit configuration file

Focus on Go best practices, error handling, concurrency safety, performance, and security. Check for proper resource cleanup and context handling.

Files:

  • internal/security/http_token_middleware.go
  • internal/utils/utils_test.go
  • internal/middleware/middleware_test.go
  • internal/mcp_handlers/launches_test.go
  • internal/analytics/analytics.go
  • internal/analytics/analytics_test.go
  • internal/http/http_server.go
  • internal/mcp_handlers/items.go
  • cmd/reportportal-mcp-server/main.go
  • internal/utils/utils.go
  • internal/http/http_server_batch_test.go
  • internal/mcp_handlers/server.go
  • internal/middleware/middleware.go
  • internal/utils/ctx_utils_test.go
  • internal/utils/ctx_utils.go
  • internal/config/cli.go
  • internal/utils/mcp_utils.go
  • internal/mcp_handlers/launches.go
**/*_test.go

⚙️ CodeRabbit configuration file

Ensure comprehensive test coverage, proper test isolation, meaningful assertions, and good test naming conventions.

Files:

  • internal/utils/utils_test.go
  • internal/middleware/middleware_test.go
  • internal/mcp_handlers/launches_test.go
  • internal/analytics/analytics_test.go
  • internal/http/http_server_batch_test.go
  • internal/utils/ctx_utils_test.go
cmd/reportportal-mcp-server/**/*.go

📄 CodeRabbit inference engine (README.md)

Keep the main server entrypoint under cmd/reportportal-mcp-server and build from that path

Files:

  • cmd/reportportal-mcp-server/main.go
cmd/**

⚙️ CodeRabbit configuration file

Review CLI interface design, configuration handling, and application startup logic for robustness and usability.

Files:

  • cmd/reportportal-mcp-server/main.go
🧠 Learnings (1)
📚 Learning: 2025-09-17T11:00:40.261Z
Learnt from: CR
PR: reportportal/reportportal-mcp-server#0
File: README.md:0-0
Timestamp: 2025-09-17T11:00:40.261Z
Learning: Applies to cmd/reportportal-mcp-server/**/*.go : Keep the main server entrypoint under cmd/reportportal-mcp-server and build from that path

Applied to files:

  • internal/mcp_handlers/launches_test.go
  • internal/mcp_handlers/items.go
  • cmd/reportportal-mcp-server/main.go
  • internal/mcp_handlers/server.go
  • internal/config/cli.go
  • internal/mcp_handlers/launches.go
🧬 Code graph analysis (13)
internal/security/http_token_middleware.go (1)
internal/utils/ctx_utils.go (2)
  • ContextKey (12-12)
  • ValidateRPToken (31-44)
internal/utils/utils_test.go (1)
internal/utils/utils.go (1)
  • ProcessAttributeKeys (124-152)
internal/middleware/middleware_test.go (1)
internal/utils/ctx_utils.go (1)
  • WithQueryParams (18-21)
internal/analytics/analytics_test.go (1)
internal/utils/mcp_utils.go (1)
  • WithAnalytics (76-90)
internal/http/http_server.go (7)
internal/analytics/analytics.go (3)
  • Analytics (71-83)
  • NewAnalytics (92-133)
  • BatchSendInterval (31-31)
internal/utils/ctx_utils.go (1)
  • ValidateRPToken (31-44)
internal/middleware/middleware.go (1)
  • QueryParamsMiddleware (10-28)
internal/mcp_handlers/launches.go (1)
  • RegisterLaunchTools (41-56)
internal/mcp_handlers/items.go (1)
  • RegisterTestItemTools (41-54)
internal/mcp_handlers/server.go (2)
  • ReadPrompts (71-93)
  • PromptFiles (19-19)
internal/security/http_token_middleware.go (1)
  • HTTPTokenMiddleware (19-42)
internal/mcp_handlers/items.go (4)
internal/analytics/analytics.go (1)
  • Analytics (71-83)
internal/utils/mcp_utils.go (6)
  • NewProjectParameter (58-64)
  • SetPaginationOptions (13-28)
  • WithAnalytics (76-90)
  • ExtractProject (66-68)
  • ApplyPaginationOptions (31-56)
  • ReadResponseBody (95-104)
internal/utils/utils.go (16)
  • DefaultSortingForItems (18-18)
  • DefaultProviderType (21-21)
  • DefaultFilterEqHasStats (23-23)
  • DefaultFilterEqHasChildren (22-22)
  • DefaultFilterInType (24-24)
  • ProcessStartTimeFilter (99-121)
  • ProcessAttributeKeys (124-152)
  • ExtractResponseError (37-62)
  • ReadResponseBodyRaw (193-215)
  • IsTextContent (154-174)
  • FirstPage (15-15)
  • DefaultPageSize (16-16)
  • DefaultSortingForLogs (20-20)
  • DefaultItemLogLevel (26-26)
  • DefaultSortingForSuites (19-19)
  • DefaultFilterInTypeSuites (25-25)
internal/utils/ctx_utils.go (1)
  • WithQueryParams (18-21)
cmd/reportportal-mcp-server/main.go (1)
internal/config/cli.go (2)
  • AppVersion (26-30)
  • RunApp (336-400)
internal/http/http_server_batch_test.go (1)
internal/http/http_server.go (3)
  • HTTPServer (61-72)
  • HTTPServerConfig (45-58)
  • NewHTTPServer (82-139)
internal/mcp_handlers/server.go (5)
internal/analytics/analytics.go (2)
  • Analytics (71-83)
  • NewAnalytics (92-133)
internal/middleware/middleware.go (1)
  • QueryParamsMiddleware (10-28)
internal/mcp_handlers/launches.go (1)
  • RegisterLaunchTools (41-56)
internal/mcp_handlers/items.go (1)
  • RegisterTestItemTools (41-54)
internal/promptreader/promptreader.go (1)
  • PromptHandlerPair (16-19)
internal/middleware/middleware.go (2)
internal/security/http_token_middleware.go (1)
  • GetTokenFromContext (75-78)
internal/utils/ctx_utils.go (1)
  • QueryParamsFromContext (23-27)
internal/config/cli.go (3)
internal/http/http_server.go (2)
  • HTTPServerConfig (45-58)
  • CreateHTTPServerWithMiddleware (219-245)
internal/analytics/analytics.go (3)
  • GetAnalyticArg (234-248)
  • Analytics (71-83)
  • StopAnalytics (405-414)
internal/mcp_handlers/server.go (1)
  • NewServer (21-68)
internal/utils/mcp_utils.go (1)
internal/utils/utils.go (4)
  • FirstPage (15-15)
  • DefaultPageSize (16-16)
  • PaginatedRequest (31-35)
  • ReadResponseBodyRaw (193-215)
internal/mcp_handlers/launches.go (4)
internal/analytics/analytics.go (1)
  • Analytics (71-83)
internal/utils/mcp_utils.go (6)
  • NewProjectParameter (58-64)
  • SetPaginationOptions (13-28)
  • WithAnalytics (76-90)
  • ExtractProject (66-68)
  • ApplyPaginationOptions (31-56)
  • ReadResponseBody (95-104)
internal/utils/utils.go (4)
  • DefaultSortingForLaunches (17-17)
  • ProcessStartTimeFilter (99-121)
  • ProcessAttributeKeys (124-152)
  • ExtractResponseError (37-62)
internal/utils/ctx_utils.go (1)
  • WithQueryParams (18-21)
🔇 Additional comments (24)
internal/middleware/middleware_test.go (1)

13-29: Add a test to assert Authorization header is sourced from context

Verifies middleware behavior when a token is present in context.

 func TestQueryParamsMiddleware(t *testing.T) {
@@
 	QueryParamsMiddleware(req)
@@
 	assert.Equal(t, []string{"2", "3"}, got["b"])
 }
+
+func TestQueryParamsMiddleware_AddsAuthorizationFromContext(t *testing.T) {
+	req, err := http.NewRequest("GET", "http://example.com/path", nil)
+	assert.NoError(t, err)
+	ctx := context.WithValue(req.Context(), security.RPTokenContextKey, "abc123")
+	req = req.WithContext(ctx)
+
+	QueryParamsMiddleware(req)
+
+	assert.Equal(t, "Bearer abc123", req.Header.Get("Authorization"))
+}

Don’t forget to import context and internal/security.
As per coding guidelines.

internal/analytics/analytics_test.go (1)

250-285: Make the integration test deterministic; drop unused httptest server and sleep

The server is unused (GA URL is constant), and time.Sleep is flaky. Flush metrics synchronously.

-func TestAnalyticsIntegration(t *testing.T) {
-	// Create a test server to mock GA4
-	testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-		w.WriteHeader(http.StatusOK)
-	}))
-	defer testServer.Close()
+func TestAnalyticsIntegration(t *testing.T) {
@@
 	for i := 0; i < 3; i++ {
 		_, err := wrappedHandler(context.Background(), mcp.CallToolRequest{})
 		assert.NoError(t, err)
 	}
 
-	// Give some time for async processing
-	time.Sleep(100 * time.Millisecond)
+	// Process metrics synchronously to avoid time-based flakiness
+	analytics.processMetrics()
 
 	// Stop analytics
 	analytics.Stop()
 }

Also remove now-unused imports (net/http, net/http/httptest). As per coding guidelines.

internal/utils/ctx_utils.go (1)

11-16: Context key should be an unexported struct value; keep backward‑compat during transition

Using an exported string type increases collision risk across packages. Prefer a zero‑sized unexported struct key and expose a value var for cross‑package use. Keep lookup backward‑compatible by checking the legacy key too.

 // ContextKey is a type for context keys to avoid collisions
-type ContextKey string
+type ContextKey string // Deprecated for new keys; kept for backward-compat
 
-var contextKeyQueryParams = ContextKey(
-	"queryParams",
-) // Key for storing query parameters in the context
+// New, collision-safe key for query params
+type queryParamsKey struct{}
+var QueryParamsKey = queryParamsKey{} // exported value for cross-package use

Update helpers to use the new key and read legacy values if present:

 func WithQueryParams(ctx context.Context, queryParams url.Values) context.Context {
-	// Create a new context with the query parameters
-	return context.WithValue(ctx, contextKeyQueryParams, queryParams)
+	// Store query parameters under the struct{} key
+	return context.WithValue(ctx, QueryParamsKey, queryParams)
 }
 
 func QueryParamsFromContext(ctx context.Context) (url.Values, bool) {
-	// Retrieve the query parameters from the context
-	queryParams, ok := ctx.Value(contextKeyQueryParams).(url.Values)
-	return queryParams, ok
+	// Prefer the new key; fall back to legacy string-typed key for compatibility
+	if v, ok := ctx.Value(QueryParamsKey).(url.Values); ok {
+		return v, true
+	}
+	if v, ok := ctx.Value(ContextKey("queryParams")).(url.Values); ok {
+		return v, true
+	}
+	return nil, false
 }

Run to find any direct usages of the legacy string key so we can plan a clean removal later:

#!/bin/bash
rg -nP --type=go -C2 '\bContextKey\("queryParams"\)|context\.WithValue\([^,]+,\s*ContextKey\("queryParams"\)' 

As per coding guidelines.

internal/mcp_handlers/launches_test.go (1)

45-47: Check url.Parse error to prevent hidden failures

Don’t discard the parse error.

-	serverURL, _ := url.Parse(mockServer.URL)
+	serverURL, urlErr := url.Parse(mockServer.URL)
+	require.NoError(t, urlErr)

As per coding guidelines.

internal/utils/utils_test.go (1)

15-136: Good coverage and clear table cases

The table tests comprehensively exercise split/trim/colon behavior and mixed scenarios. Looks solid.

internal/middleware/middleware.go (3)

12-14: Confirm override semantics for Authorization

Header.Set will overwrite any existing Authorization header. Is this intended when both context and request already carry tokens? If not, gate on absence of header.


17-27: Query param merge looks good

Merges context params into URL via Encode(); safe and idiomatic. LGTM.


3-8: Sanitize Authorization and remove redundant non-empty check

Prevent header injection by trimming and rejecting CR/LF before setting the header. Also, GetTokenFromContext already guarantees non-empty when ok is true—drop the extra token != "".

 import (
 	"net/http"
 
 	"github.com/reportportal/reportportal-mcp-server/internal/security"
 	"github.com/reportportal/reportportal-mcp-server/internal/utils"
+	"strings"
 )
@@
-	if token, ok := security.GetTokenFromContext(rq.Context()); ok && token != "" {
-		rq.Header.Set("Authorization", "Bearer "+token)
-	}
+	if token, ok := security.GetTokenFromContext(rq.Context()); ok {
+		token = strings.TrimSpace(token)
+		if token != "" && !strings.ContainsAny(token, "\r\n") {
+			rq.Header.Set("Authorization", "Bearer "+token)
+		}
+	}

Also applies to: 12-14

internal/security/http_token_middleware.go (2)

15-16: Typed context key usage is good

Using a distinct key type avoids collisions with raw strings. Nice.


54-54: ValidateRPToken dependency verified: utils.ValidateRPToken(token string) bool is defined in internal/utils/ctx_utils.go and calls uuid.Validate(token) from github.com/google/uuid v1.6.0. All signatures match across modules.

cmd/reportportal-mcp-server/main.go (1)

31-34: Main handles termination correctly; ensure RunApp doesn’t log.Fatal inside library code.

main’s os.Exit(1) on error is fine. However, internal/config.RunApp still calls log.Fatal on failure, which prevents proper error propagation and testing. Return the error instead and let main exit.

Apply in internal/config/cli.go:

- if err := cmd.Run(ctx, os.Args); err != nil {
-   log.Fatal(err)
- }
- return nil
+ if err := cmd.Run(ctx, os.Args); err != nil {
+   return err
+ }
+ return nil

Run to confirm no other log.Fatal/os.Exit in non-cmd packages:

#!/bin/bash
rg -nP '(log\.Fatal(f)?\(|os\.Exit\()' -g '!cmd/**' -g '!**/*_test.go' -C2
internal/http/http_server_batch_test.go (3)

206-214: Update call site for new helper signature.

-			req := createTestRequest(tt.payload)
+			req := createTestRequest(t, tt.payload)

Run go fmt/lint after changes.


12-17: Make request helper a proper test helper; don’t ignore errors.

Mark as t.Helper(), return *http.Request, and check http.NewRequest error. Also, pass a plain Reader.

-// Test helper to create HTTP requests with JSON payloads
-func createTestRequest(payload string) *http.Request {
-	req, _ := http.NewRequest("POST", "/test", io.NopCloser(bytes.NewBufferString(payload)))
-	req.Header.Set("Content-Type", "application/json")
-	return req
-}
+// Test helper to create HTTP requests with JSON payloads
+func createTestRequest(t *testing.T, payload string) *http.Request {
+	t.Helper()
+	req, err := http.NewRequest("POST", "/test", bytes.NewBufferString(payload))
+	if err != nil {
+		t.Fatalf("failed to create request: %v", err)
+	}
+	req.Header.Set("Content-Type", "application/json")
+	return req
+}

264-272: Don’t ignore http.NewRequest errors; add charset Content-Type case.

-			req, _ := http.NewRequest(
+			req, err := http.NewRequest(
 				tt.method,
 				"/test",
-				io.NopCloser(bytes.NewBufferString(tt.payload)),
+				bytes.NewBufferString(tt.payload),
 			)
+			if err != nil {
+				t.Fatalf("failed to create request: %v", err)
+			}
 			req.Header.Set("Content-Type", tt.contentType)

Optionally extend the table:

@@
 		{
 			name:        "valid POST with JSON content",
@@
 		},
+		{
+			name:        "POST with JSON content and charset",
+			method:      "POST",
+			contentType: "application/json; charset=utf-8",
+			payload:     `{"jsonrpc":"2.0","method":"test","id":1}`,
+			expected:    true,
+		},

Also applies to: 241-262

internal/analytics/analytics.go (2)

31-31: Make BatchSendInterval overridable for tests.

Declare BatchSendInterval as a var, not const, so tests can shorten it.

-const (
+const (
   // Google Analytics 4 Measurement Protocol endpoint
   ga4EndpointURL = "https://www.google-analytics.com/mp/collect"
@@
-	// Batch send interval for analytics data
-	BatchSendInterval = 10 * time.Second
+	// Max events per GA4 request
+	maxPerRequest = 25
 )
 
-	maxPerRequest = 25
-)
+// Batch send interval for analytics data (overridable in tests)
+var BatchSendInterval = 10 * time.Second

No code changes needed at usage sites (ticker/debug). As per coding guidelines.

Also applies to: 255-259


234-248: Don’t remove GetAnalyticArg —it’s actually used
This function is called in internal/config/cli.go (at lines 152 and 197) and covered by internal/analytics/analytics_test.go. Deleting it will break your CLI’s analytics setup and tests. If the obfuscation isn’t needed, refactor or rename it (e.g. GenerateAnalyticsSecret) and add clear documentation instead.

Likely an incorrect or invalid review comment.

internal/mcp_handlers/server.go (1)

41-50: Don’t fail server init if analytics is misconfigured; log and continue.

Align behavior with HTTP server path: analytics init failure should not abort server creation.

 	// Initialize analytics (disabled if analyticsOff is true)
 	var analyticsClient *analytics.Analytics
 	if analyticsOn {
-		var err error
-		// Pass RP API token for secure hashing as user identifier
-		analyticsClient, err = analytics.NewAnalytics(userID, analyticsAPISecret, token)
-		if err != nil {
-			return nil, nil, fmt.Errorf("failed to initialize analytics: %w", err)
-		}
+		ac, err := analytics.NewAnalytics(userID, analyticsAPISecret, token)
+		if err != nil {
+			slog.Warn("analytics initialization skipped", "error", err)
+		} else {
+			analyticsClient = ac
+		}
 	}

Add import:

+	"log/slog"
internal/utils/mcp_utils.go (2)

3-10: Import math for integer bounds.

 import (
 	"context"
 	"fmt"
 	"net/http"
+	"math"
 
 	"github.com/mark3labs/mcp-go/mcp"
 	"github.com/mark3labs/mcp-go/server"
 )

36-56: Fix undefined maxInt32 and clamp pagination to valid ranges before int32 casts.

Use math.MaxInt32 and enforce minimums to avoid overflow/negative values.

 	// Extract the "page" parameter from the request
 	pageInt := request.GetInt("page", FirstPage)
-	if pageInt > maxInt32 {
-		pageInt = maxInt32
-	}
+	if pageInt < FirstPage {
+		pageInt = FirstPage
+	}
+	if pageInt > int(math.MaxInt32) {
+		pageInt = int(math.MaxInt32)
+	}
 
 	// Extract the "page-size" parameter from the request
 	pageSizeInt := request.GetInt("page-size", DefaultPageSize)
-	if pageSizeInt > maxInt32 {
-		pageSizeInt = maxInt32
-	}
+	if pageSizeInt < 1 {
+		pageSizeInt = 1
+	}
+	if pageSizeInt > int(math.MaxInt32) {
+		pageSizeInt = int(math.MaxInt32)
+	}

As per coding guidelines.

internal/config/cli.go (3)

290-299: Reuse the wrapper Handler instead of poking into MCP internals

CreateHTTPServerWithMiddleware already exposes the fully wired handler via its Handler field. Reaching into httpServer.MCP.Router keeps a tight coupling to private structure that we just abstracted away. Please use the wrapper’s Handler so future router changes don’t break this call site.


337-344: Use GetMCPMode() to centralize mode parsing

We now have a dedicated helper that normalizes the MCP_MODE env var. Re-implementing the parsing here invites drift; call GetMCPMode() and reuse its result instead.


394-399: Return the CLI error instead of calling log.Fatal

RunApp is our library-style entry point. Calling log.Fatal terminates the process immediately, bypassing deferred clean-up in callers and making tests impossible to recover from startup failures. Return the error and let main decide how to handle it.
Apply this diff:

-	if err := cmd.Run(ctx, os.Args); err != nil {
-		log.Fatal(err)
-	}
-
-	return nil
+	return cmd.Run(ctx, os.Args)
internal/mcp_handlers/launches.go (2)

279-281: Schema/handler mismatch: launch_id must be numeric (string+RequireInt => runtime error).

Declare launch_id with WithNumber to match RequireInt in all tools listed below.

@@
-            mcp.WithString("launch_id", // Parameter for specifying the launch name
+            mcp.WithNumber("launch_id", // Parameter for specifying the launch ID
               mcp.Description("Launch ID"),
             ),
@@
-            mcp.WithString("launch_id", // Parameter for specifying the launch name
+            mcp.WithNumber("launch_id", // Parameter for specifying the launch ID
               mcp.Description("Launch ID"),
             ),
@@
-            mcp.WithString("launch_id", // Parameter for specifying the launch name
+            mcp.WithNumber("launch_id", // Parameter for specifying the launch ID
               mcp.Description("Launch ID"),
             ),
@@
-            mcp.WithString("launch_id", // Parameter for specifying the launch name
+            mcp.WithNumber("launch_id", // Parameter for specifying the launch ID
               mcp.Description("Launch ID"),
             ),

Tools affected:

  • ToolDeleteLaunch
  • ToolRunAutoAnalysis
  • ToolUniqueErrorAnalysis
  • ToolForceFinishLaunch

Also applies to: 310-313, 387-390, 431-434


429-430: Misleading description: this tool force-finishes, not deletes.

Update the description to reflect the action.

-            mcp.WithDescription("Delete ReportPortal launch"),
+            mcp.WithDescription("Force finish ReportPortal launch"),

@AleksandrParamonoff AleksandrParamonoff force-pushed the refactoring-detaching-app-to-modules branch from d2483c9 to c59c94b Compare October 7, 2025 21:13
Copy link

@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: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (6)
internal/mcp_handlers/server.go (1)

70-92: Fix prompt loader to avoid zero-value entries.

handlers := make(..., len(entries)) pre-populates len(entries) zero-value elements, and the subsequent append pushes real prompt-handler pairs after them. When you later iterate and call s.AddPrompt, the first len(entries) items have empty prompts and nil handlers, which will panic or register bogus prompts. Start with length 0 (capacity only) before appending.

-	handlers := make([]promptreader.PromptHandlerPair, len(entries))
+	handlers := make([]promptreader.PromptHandlerPair, 0, len(entries))
@@
-		handlers = append(handlers, prompts...)
+		handlers = append(handlers, prompts...)
internal/http/http_server.go (1)

163-170: Correct the MIME switch so JSON/XML over HTTP are treated as text.

Go switch cases don’t implicitly fall through; only application/csv currently reaches the return true. As a result, JSON/XML/etc. hit the default branch and get treated as binary blobs. Combine the cases in a single clause or return true in each case.

-	switch lowerType {
-	case "application/json":
-	case "application/xml":
-	case "application/javascript":
-	case "application/xhtml+xml":
-	case "application/yaml":
-	case "application/csv":
-		return true
-	}
+	switch lowerType {
+	case "application/json",
+		"application/xml",
+		"application/javascript",
+		"application/xhtml+xml",
+		"application/yaml",
+		"application/csv":
+		return true
+	}
internal/mcp_handlers/items.go (1)

583-590: Use the actual parameter keys when reading request filters.

The tool declares parameters like "filter-cnt-name" / "filter-has-compositeAttribute", but here we read "filter.cnt.name" / "filter.has.compositeAttribute" (dots instead of hyphens). That mismatch forces every GetString(...) to return the default value, so none of the suite filters ever make it into the API call.

Update each lookup to use the hyphenated keys you registered, e.g.:

filterName := request.GetString("filter-cnt-name", "")
filterAttributes := request.GetString("filter-has-compositeAttribute", "")
filterAttributeKeys := request.GetString("filter-has-attributeKey", "")
filterDescription := request.GetString("filter-cnt-description", "")
filterStartTimeFrom := request.GetString("filter-btw-startTime-from", "")
filterStartTimeTo := request.GetString("filter-btw-startTime-to", "")
filterParentId := request.GetString("filter-eq-parentId", "")

Without this correction, the handler ignores user input.

internal/utils/utils.go (3)

37-62: Consider removing the named return parameter.

The function declares a named return errText but never uses a naked return statement. Go convention suggests either using naked returns with named parameters or avoiding named returns altogether for clarity.

Apply this diff for consistency:

-func ExtractResponseError(err error, rs *http.Response) (errText string) {
-	errText = err.Error()
+func ExtractResponseError(err error, rs *http.Response) string {
+	errText := err.Error()
 	if rs != nil && rs.Body != nil {

65-96: Replace magic numbers with named constants.

Lines 72 and 74 contain magic numbers (32503680000, 10000000000) representing year boundaries. Extract these as package-level constants with descriptive names to improve readability and maintainability.

Apply this diff:

 const (
 	FirstPage                  = 1                       // Default starting page for pagination
 	DefaultPageSize            = 50                      // Default number of elements per page
+	maxEpochMillis             = 32503680000             // Roughly year 3000 in milliseconds
+	maxEpochSeconds            = 10000000000             // Roughly year 2286 in seconds
 	DefaultSortingForLaunches  = "startTime,number,DESC" // default sorting order for launches

Then update the function:

 	if epoch, err := strconv.ParseInt(timestampStr, 10, 64); err == nil {
 		// If it's a reasonable Unix timestamp (after 1970 and before year 3000)
-		if epoch > 0 && epoch < 32503680000 { // roughly year 3000
+		if epoch > 0 && epoch < maxEpochMillis {
 			// If it looks like seconds, convert to milliseconds
-			if epoch < 10000000000 { // less than year 2286 in seconds
+			if epoch < maxEpochSeconds {
 				return epoch * 1000, nil
 			}

154-174: Simplify the switch statement structure.

All cases in the switch fall through to return true. This pattern is harder to maintain than a straightforward list of strings to check against.

Apply this diff:

 func IsTextContent(mediaType string) bool {
 	lowerType := strings.ToLower(mediaType)
 
 	// Text types (most common)
 	if strings.HasPrefix(lowerType, "text/") {
 		return true
 	}
 
-	// Popular application types that are text-based
-	switch lowerType {
-	case "application/json":
-	case "application/xml":
-	case "application/javascript":
-	case "application/xhtml+xml":
-	case "application/yaml":
-	case "application/csv":
+	// Text-based application types
+	textAppTypes := []string{
+		"application/json",
+		"application/xml",
+		"application/javascript",
+		"application/xhtml+xml",
+		"application/yaml",
+		"application/csv",
+	}
+	for _, textType := range textAppTypes {
+		if lowerType == textType {
+			return true
+		}
+	}
-		return true
-	}
 
 	return false
 }
♻️ Duplicate comments (9)
internal/analytics/analytics_test.go (1)

180-181: Also assert metric increment after wrapping

After calling wrappedHandler, assert the counter for "test_tool" incremented (under lock). This verifies the wrapper actually tracks events.

   // Call the wrapped handler
   result, err := wrappedHandler(context.Background(), mcp.CallToolRequest{})

   // Verify handler was called
   assert.NoError(t, err)
   assert.NotNil(t, result)
   assert.True(t, handlerCalled, "Original handler should be called")
+
+  // Verify metric incremented
+  analytics.metricsLock.RLock()
+  if v, ok := analytics.metrics["test_tool"]; ok && v != nil {
+    assert.Equal(t, int64(1), *v)
+  } else {
+    assert.Fail(t, "metric for test_tool not recorded")
+  }
+  analytics.metricsLock.RUnlock()

As per coding guidelines.

Also applies to: 200-206, 272-276

internal/config/cli.go (1)

291-300: Use the pre-wired Handler instead of reaching into MCP.Router

Prefer httpServer.Handler returned by CreateHTTPServerWithMiddleware to avoid tight coupling and keep middleware intact.

- Handler:           httpServer.MCP.Router, // Use Chi router directly with throttling/health/info/metrics
+ Handler:           httpServer.Handler,     // Use pre‑wired Chi router with middleware
internal/mcp_handlers/launches.go (2)

274-303: Critical: Type mismatch between launch_id parameter and RequireInt call

Tools declare launch_id as string (mcp.WithString) but the handler calls request.RequireInt("launch_id"), causing runtime errors when the tool is invoked.

Apply this diff to fix the type mismatch:

-			mcp.WithString("launch_id", // Parameter for specifying the launch name
+			mcp.WithNumber("launch_id", // Parameter for specifying the launch ID
 				mcp.Description("Launch ID"),
 			),

Apply the same fix to ToolRunAutoAnalysis (line 310), ToolUniqueErrorAnalysis (line 387), and ToolForceFinishLaunch (line 431).


215-272: Minor: Tool description should clarify it returns a single launch by name

The description says "Get list of last ReportPortal launches by name" but the tool returns only the most recent launch matching the given name.

Apply this diff to clarify:

-			mcp.WithDescription("Get list of last ReportPortal launches by name"),
+			mcp.WithDescription("Get last ReportPortal launch by name"),
internal/middleware/middleware_test.go (1)

19-21: Check NewRequest error in test

The error from http.NewRequest is discarded, which could mask test setup failures.

Apply this diff:

-	req, _ := http.NewRequest("GET", "http://example.com/path?x=9", nil)
+	req, err := http.NewRequest("GET", "http://example.com/path?x=9", nil)
+	require.NoError(t, err)
 	ctx := utils.WithQueryParams(req.Context(), params)

As per coding guidelines.

internal/mcp_handlers/launches_test.go (1)

45-46: Check url.Parse error to prevent test failures

The error from url.Parse is discarded, which could lead to nil pointer panics if the URL is invalid.

Apply this diff:

-	serverURL, _ := url.Parse(mockServer.URL)
+	serverURL, err := url.Parse(mockServer.URL)
+	require.NoError(t, err)
 	launchTools := NewLaunchResources(gorp.NewClient(serverURL, ""), testProject, nil)
internal/utils/ctx_utils.go (1)

11-16: Consider unexported struct type for context keys to prevent collisions

Exporting ContextKey as a string type risks name collisions across packages. Go best practice recommends unexported, zero-sized struct types for context keys.

Consider this alternative pattern:

-// ContextKey is a type for context keys to avoid collisions
-type ContextKey string
+// unexported struct type prevents collisions
+type queryParamsKey struct{}
 
-var contextKeyQueryParams = ContextKey(
-	"queryParams",
-) // Key for storing query parameters in the context
+// QueryParamsKey is the exported key value for query parameters
+var QueryParamsKey = queryParamsKey{}

Then update WithQueryParams and QueryParamsFromContext to use QueryParamsKey instead of contextKeyQueryParams. Apply the same pattern to token keys in internal/security.

internal/analytics/analytics.go (1)

28-32: Consider making BatchSendInterval a var for testability

Declaring BatchSendInterval as a const prevents tests from temporarily shortening the interval, forcing reliance on sleeps.

Apply this diff:

 const (
 	// Google Analytics 4 Measurement Protocol endpoint
 	ga4EndpointURL = "https://www.google-analytics.com/mp/collect"
 
 	// Configuration
 	measurementID = "G-WJGRCEFLXF"
 
 	userID = "692"
 
-	// Batch send interval for analytics data
-	BatchSendInterval = 10 * time.Second
-
 	maxPerRequest = 25
 )
+
+// BatchSendInterval is the batch send interval for analytics data (overridable in tests)
+var BatchSendInterval = 10 * time.Second

As per coding guidelines.

internal/http/http_server_batch_test.go (1)

266-271: Stop discarding http.NewRequest errors in tests.

We still ignore the error return here, so malformed URLs/payloads will silently pass. Capture and fail the test instead.

-			req, _ := http.NewRequest(
+			req, err := http.NewRequest(
 				tt.method,
 				"/test",
-				io.NopCloser(bytes.NewBufferString(tt.payload)),
+				bytes.NewBufferString(tt.payload),
 			)
+			if err != nil {
+				t.Fatalf("failed to create request: %v", err)
+			}
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d2483c9 and c59c94b.

📒 Files selected for processing (18)
  • cmd/reportportal-mcp-server/main.go (2 hunks)
  • internal/analytics/analytics.go (3 hunks)
  • internal/analytics/analytics_test.go (5 hunks)
  • internal/config/cli.go (1 hunks)
  • internal/http/http_server.go (11 hunks)
  • internal/http/http_server_batch_test.go (1 hunks)
  • internal/mcp_handlers/items.go (21 hunks)
  • internal/mcp_handlers/launches.go (14 hunks)
  • internal/mcp_handlers/launches_test.go (2 hunks)
  • internal/mcp_handlers/server.go (4 hunks)
  • internal/middleware/middleware.go (1 hunks)
  • internal/middleware/middleware_test.go (2 hunks)
  • internal/security/http_token_middleware.go (2 hunks)
  • internal/utils/ctx_utils.go (2 hunks)
  • internal/utils/ctx_utils_test.go (1 hunks)
  • internal/utils/mcp_utils.go (1 hunks)
  • internal/utils/utils.go (8 hunks)
  • internal/utils/utils_test.go (7 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.go

📄 CodeRabbit inference engine (README.md)

**/*.go: Run formatter (task fmt / go fmt) to keep all Go source files consistently formatted
Run linter (task lint) to maintain code quality across Go files

Files:

  • internal/utils/mcp_utils.go
  • internal/mcp_handlers/launches.go
  • internal/mcp_handlers/launches_test.go
  • internal/utils/ctx_utils_test.go
  • internal/security/http_token_middleware.go
  • internal/middleware/middleware_test.go
  • internal/http/http_server_batch_test.go
  • internal/analytics/analytics_test.go
  • internal/http/http_server.go
  • internal/mcp_handlers/server.go
  • internal/utils/utils_test.go
  • internal/utils/ctx_utils.go
  • internal/middleware/middleware.go
  • internal/mcp_handlers/items.go
  • cmd/reportportal-mcp-server/main.go
  • internal/utils/utils.go
  • internal/config/cli.go
  • internal/analytics/analytics.go

⚙️ CodeRabbit configuration file

Focus on Go best practices, error handling, concurrency safety, performance, and security. Check for proper resource cleanup and context handling.

Files:

  • internal/utils/mcp_utils.go
  • internal/mcp_handlers/launches.go
  • internal/mcp_handlers/launches_test.go
  • internal/utils/ctx_utils_test.go
  • internal/security/http_token_middleware.go
  • internal/middleware/middleware_test.go
  • internal/http/http_server_batch_test.go
  • internal/analytics/analytics_test.go
  • internal/http/http_server.go
  • internal/mcp_handlers/server.go
  • internal/utils/utils_test.go
  • internal/utils/ctx_utils.go
  • internal/middleware/middleware.go
  • internal/mcp_handlers/items.go
  • cmd/reportportal-mcp-server/main.go
  • internal/utils/utils.go
  • internal/config/cli.go
  • internal/analytics/analytics.go
**/*_test.go

⚙️ CodeRabbit configuration file

Ensure comprehensive test coverage, proper test isolation, meaningful assertions, and good test naming conventions.

Files:

  • internal/mcp_handlers/launches_test.go
  • internal/utils/ctx_utils_test.go
  • internal/middleware/middleware_test.go
  • internal/http/http_server_batch_test.go
  • internal/analytics/analytics_test.go
  • internal/utils/utils_test.go
cmd/reportportal-mcp-server/**/*.go

📄 CodeRabbit inference engine (README.md)

Keep the main server entrypoint under cmd/reportportal-mcp-server and build from that path

Files:

  • cmd/reportportal-mcp-server/main.go
cmd/**

⚙️ CodeRabbit configuration file

Review CLI interface design, configuration handling, and application startup logic for robustness and usability.

Files:

  • cmd/reportportal-mcp-server/main.go
🧠 Learnings (1)
📚 Learning: 2025-09-17T11:00:40.261Z
Learnt from: CR
PR: reportportal/reportportal-mcp-server#0
File: README.md:0-0
Timestamp: 2025-09-17T11:00:40.261Z
Learning: Applies to cmd/reportportal-mcp-server/**/*.go : Keep the main server entrypoint under cmd/reportportal-mcp-server and build from that path

Applied to files:

  • cmd/reportportal-mcp-server/main.go
🧬 Code graph analysis (13)
internal/utils/mcp_utils.go (1)
internal/utils/utils.go (4)
  • FirstPage (15-15)
  • DefaultPageSize (16-16)
  • PaginatedRequest (31-35)
  • ReadResponseBodyRaw (193-215)
internal/mcp_handlers/launches.go (4)
internal/analytics/analytics.go (1)
  • Analytics (69-81)
internal/utils/mcp_utils.go (6)
  • NewProjectParameter (65-71)
  • SetPaginationOptions (14-29)
  • WithAnalytics (83-97)
  • ExtractProject (73-75)
  • ApplyPaginationOptions (32-63)
  • ReadResponseBody (102-111)
internal/utils/utils.go (4)
  • DefaultSortingForLaunches (17-17)
  • ProcessStartTimeFilter (99-121)
  • ProcessAttributeKeys (124-152)
  • ExtractResponseError (37-62)
internal/utils/ctx_utils.go (1)
  • WithQueryParams (18-21)
internal/security/http_token_middleware.go (1)
internal/utils/ctx_utils.go (2)
  • ContextKey (12-12)
  • ValidateRPToken (31-44)
internal/middleware/middleware_test.go (1)
internal/utils/ctx_utils.go (1)
  • WithQueryParams (18-21)
internal/http/http_server_batch_test.go (1)
internal/http/http_server.go (3)
  • HTTPServer (61-72)
  • HTTPServerConfig (45-58)
  • NewHTTPServer (82-139)
internal/analytics/analytics_test.go (1)
internal/utils/mcp_utils.go (1)
  • WithAnalytics (83-97)
internal/http/http_server.go (7)
internal/analytics/analytics.go (3)
  • Analytics (69-81)
  • NewAnalytics (90-131)
  • BatchSendInterval (29-29)
internal/utils/ctx_utils.go (1)
  • ValidateRPToken (31-44)
internal/middleware/middleware.go (1)
  • QueryParamsMiddleware (11-32)
internal/mcp_handlers/launches.go (1)
  • RegisterLaunchTools (41-56)
internal/mcp_handlers/items.go (1)
  • RegisterTestItemTools (41-54)
internal/mcp_handlers/server.go (2)
  • ReadPrompts (71-93)
  • PromptFiles (19-19)
internal/security/http_token_middleware.go (1)
  • HTTPTokenMiddleware (19-42)
internal/mcp_handlers/server.go (5)
internal/analytics/analytics.go (2)
  • Analytics (69-81)
  • NewAnalytics (90-131)
internal/middleware/middleware.go (1)
  • QueryParamsMiddleware (11-32)
internal/mcp_handlers/launches.go (1)
  • RegisterLaunchTools (41-56)
internal/mcp_handlers/items.go (1)
  • RegisterTestItemTools (41-54)
internal/promptreader/promptreader.go (1)
  • PromptHandlerPair (16-19)
internal/utils/utils_test.go (1)
internal/utils/utils.go (1)
  • ProcessAttributeKeys (124-152)
internal/middleware/middleware.go (2)
internal/security/http_token_middleware.go (1)
  • GetTokenFromContext (75-78)
internal/utils/ctx_utils.go (1)
  • QueryParamsFromContext (23-27)
internal/mcp_handlers/items.go (4)
internal/analytics/analytics.go (1)
  • Analytics (69-81)
internal/utils/mcp_utils.go (6)
  • NewProjectParameter (65-71)
  • SetPaginationOptions (14-29)
  • WithAnalytics (83-97)
  • ExtractProject (73-75)
  • ApplyPaginationOptions (32-63)
  • ReadResponseBody (102-111)
internal/utils/utils.go (16)
  • DefaultSortingForItems (18-18)
  • DefaultProviderType (21-21)
  • DefaultFilterEqHasStats (23-23)
  • DefaultFilterEqHasChildren (22-22)
  • DefaultFilterInType (24-24)
  • ProcessStartTimeFilter (99-121)
  • ProcessAttributeKeys (124-152)
  • ExtractResponseError (37-62)
  • ReadResponseBodyRaw (193-215)
  • IsTextContent (154-174)
  • FirstPage (15-15)
  • DefaultPageSize (16-16)
  • DefaultSortingForLogs (20-20)
  • DefaultItemLogLevel (26-26)
  • DefaultSortingForSuites (19-19)
  • DefaultFilterInTypeSuites (25-25)
internal/utils/ctx_utils.go (1)
  • WithQueryParams (18-21)
cmd/reportportal-mcp-server/main.go (1)
internal/config/cli.go (2)
  • AppVersion (25-29)
  • RunApp (337-390)
internal/config/cli.go (3)
internal/http/http_server.go (2)
  • HTTPServerConfig (45-58)
  • CreateHTTPServerWithMiddleware (219-245)
internal/analytics/analytics.go (3)
  • GetAnalyticArg (232-246)
  • Analytics (69-81)
  • StopAnalytics (403-412)
internal/mcp_handlers/server.go (1)
  • NewServer (21-68)
🔇 Additional comments (12)
internal/utils/ctx_utils_test.go (1)

1-1: Package rename looks good

Consistent with utils package refactor. No issues spotted.

internal/analytics/analytics_test.go (1)

16-18: Switch to utils.WithAnalytics is correct

Decouples analytics wrapper from analytics package; consistent with refactor.

cmd/reportportal-mcp-server/main.go (1)

24-35: Main delegating to config.RunApp with proper error handling

Straightforward, testable entrypoint. Looks good.

internal/utils/mcp_utils.go (3)

37-63: Pagination clamping is correct and safe

Lower/upper bounds prevent negatives and int32 overflow before casting. Good use of math.MaxInt32.


82-97: Nil-safe analytics wrapper

Wrapper cleanly tracks before invoking handler; nil tracker check prevents panics.


102-111: Tool-friendly response reader behavior

Returning a ToolResultError with nil Go error preserves MCP semantics while surfacing the failure to clients.

internal/security/http_token_middleware.go (1)

1-78: LGTM: Token validation properly integrated with utils package

The migration to utils.ContextKey and utils.ValidateRPToken correctly centralizes validation logic and aligns with the broader refactoring.

internal/middleware/middleware.go (1)

11-32: LGTM: Authorization header injection vulnerability addressed

Token sanitization with strings.TrimSpace and strings.ContainsAny check prevents header injection attacks. The implementation correctly validates the token before setting the Authorization header.

internal/utils/utils_test.go (1)

8-172: LGTM: Comprehensive test coverage for ProcessAttributeKeys

Test cases thoroughly validate the attribute key processing logic, including edge cases for whitespace handling, empty keys, multiple colons, and key:value pair preservation. The performance test ensures the function handles large inputs efficiently.

internal/utils/utils.go (3)

99-121: LGTM!

The function correctly validates time intervals, ensures proper ordering, and handles edge cases with clear error messages.


177-189: LGTM!

The string-based error detection is a pragmatic solution for handling various HTTP implementation error messages. The coverage of common error patterns is comprehensive.


193-215: LGTM!

The function properly handles resource cleanup with defensive nil checks and avoids noisy logging for already-closed bodies. The error handling is thorough and follows Go best practices.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

Copilot reviewed 18 out of 19 changed files in this pull request and generated 3 comments.


Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

@AleksandrParamonoff AleksandrParamonoff force-pushed the refactoring-detaching-app-to-modules branch from c59c94b to f7871a6 Compare October 9, 2025 08:58
Copy link

@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: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
internal/http/http_server.go (3)

423-433: Cap request body size in MCP middleware to prevent memory DoS

validateMCPPayload reads the entire body. Add a size limit before parsing.

 func (hs *HTTPServer) mcpMiddleware(next http.Handler) http.Handler {
   return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+    // Cap request body to 4MB to mitigate memory DoS
+    r.Body = http.MaxBytesReader(w, r.Body, 4<<20)
     // Validate that this is a proper MCP request
     if !hs.isMCPRequest(r) {
       http.Error(w, "Invalid MCP request", http.StatusBadRequest)
       return
     }
     next.ServeHTTP(w, r)
   })
 }

310-321: Restrict MCP endpoints to POST for correct semantics

Currently all methods hit these handlers and non-POSTs return 400. Prefer method-limited routes to return 405 instead.

-    mcpRouter.Handle("/mcp", hs.streamableServer)
-    mcpRouter.Handle("/api/mcp", hs.streamableServer)
-    mcpRouter.Handle("/mcp/*", hs.streamableServer)
-    mcpRouter.Handle("/api/mcp/*", hs.streamableServer)
+    mcpRouter.Method(http.MethodPost, "/mcp", hs.streamableServer)
+    mcpRouter.Method(http.MethodPost, "/api/mcp", hs.streamableServer)
+    mcpRouter.Method(http.MethodPost, "/mcp/*", hs.streamableServer)
+    mcpRouter.Method(http.MethodPost, "/api/mcp/*", hs.streamableServer)

161-169: Fix ReadPrompts slice allocation to avoid zero-value entries
Use a zero-length, capacity-preallocated slice so that only real prompts are included.

Diff (internal/mcp_handlers/server.go):

-	handlers := make([]promptreader.PromptHandlerPair, len(entries))
+	handlers := make([]promptreader.PromptHandlerPair, 0, len(entries))
♻️ Duplicate comments (16)
internal/analytics/analytics_test.go (1)

180-189: Verify analytics metric was incremented in test.

The test wraps the handler with utils.WithAnalytics but doesn't assert that the metric counter was actually incremented. This reduces test coverage.

Add metric verification after the wrapped handler call:

 	result, err := wrappedHandler(context.Background(), mcp.CallToolRequest{})
 
 	// Verify handler was called
 	assert.NoError(t, err)
 	assert.NotNil(t, result)
 	assert.True(t, handlerCalled, "Original handler should be called")
+
+	// Verify the metric was incremented
+	analytics.metricsLock.RLock()
+	defer analytics.metricsLock.RUnlock()
+	counter, exists := analytics.metrics["test_tool"]
+	assert.True(t, exists, "Metric for test_tool should exist")
+	assert.NotNil(t, counter, "Counter should not be nil")
+	assert.Equal(t, int64(1), *counter, "Counter should be incremented")

Based on learnings (past review suggestion).

internal/mcp_handlers/launches.go (1)

279-291: Fix launch_id parameter type mismatch.

The parameter is defined as a string with mcp.WithString("launch_id", ...) (line 279) but the handler calls request.RequireInt("launch_id") (line 288), causing runtime errors.

Apply this diff:

-		mcp.WithString("launch_id", // Parameter for specifying the launch name
+		mcp.WithNumber("launch_id", // Parameter for specifying the launch ID
 			mcp.Description("Launch ID"),
 		),

Also apply to ToolRunAutoAnalysis (line 310), ToolUniqueErrorAnalysis (line 387), and ToolForceFinishLaunch (line 431).

Based on learnings (past review concern).

internal/mcp_handlers/items.go (2)

334-353: Use numeric parameter type for attachment-content-id.

The parameter is defined as a string (line 334) then manually parsed as int64 (lines 344-353). Defining it as numeric eliminates the parsing code and provides better type safety.

Apply this diff:

-		mcp.WithString("attachment-content-id", // Parameter for specifying the test item ID
+		mcp.WithNumber("attachment-content-id", // Parameter for specifying the attachment ID
 			mcp.Description("Attachment binary content ID"),
 		),

Then update the handler:

-		// Extract the "attachment-content-id" parameter from the request
-		attachmentIdStr, err := request.RequireString("attachment-content-id")
+		attachmentId, err := request.RequireInt("attachment-content-id")
 		if err != nil {
 			return mcp.NewToolResultError(err.Error()), nil
 		}
-		attachmentId, err := strconv.ParseInt(attachmentIdStr, 10, 64)
-		if err != nil {
-			return mcp.NewToolResultError(
-				fmt.Sprintf("invalid attachment ID value: %s", attachmentIdStr),
-			), nil
-		}
+		if attachmentId < 0 {
+			return mcp.NewToolResultError("attachment ID must be non-negative"), nil
+		}

Based on learnings (past review suggestion).


48-50: Consider exporting toolGetTestItemLogsByFilter for consistency.

All other tool methods in this file are exported (e.g., ToolGetTestItemById, ToolGetTestItemsByFilter), but toolGetTestItemLogsByFilter remains unexported. While not functionally wrong, the inconsistency may confuse API consumers.

Apply this diff:

 	s.AddTool(testItems.ToolGetTestItemById())
 	s.AddTool(testItems.ToolGetTestItemsByFilter())
-	s.AddTool(testItems.toolGetTestItemLogsByFilter())
+	s.AddTool(testItems.ToolGetTestItemLogsByFilter())

Then rename the function definition accordingly (around line 394).

Based on learnings (past review suggestion).

internal/utils/utils.go (2)

27-27: Remove redundant maxInt32 wrapper constant.

The maxInt32 constant simply wraps math.MaxInt32 without adding value. Code should reference math.MaxInt32 directly.

Apply this diff:

 	DefaultFilterInTypeSuites  = "SUITE,TEST"
 	DefaultItemLogLevel        = "TRACE"       // Default log level for test item logs
-	maxInt32                   = math.MaxInt32 // Standard library constant for maximum int32 value
 )

Then search for any usages and replace with math.MaxInt32.

Based on learnings (past review suggestion).


136-137: Clarify comment for better understanding.

The comment "Keep full key with colon" doesn't explain the condition. The check ensures the colon is not at the start or end of the string, preserving valid key:value pairs.

Apply this diff:

-		if colonIndex := strings.Index(key, ":"); colonIndex > 0 && colonIndex < len(key)-1 {
-			processed = append(processed, key) // Keep full key with colon
+		if colonIndex := strings.Index(key, ":"); colonIndex > 0 && colonIndex < len(key)-1 {
+			processed = append(processed, key) // Keep key:value pair with content on both sides

Based on learnings (past review suggestion).

internal/http/http_server_batch_test.go (2)

266-271: Don't ignore http.NewRequest error.

The error from http.NewRequest is discarded with the blank identifier (line 266), which could hide malformed URLs or payload issues and cause test failures to be misattributed.

Apply this diff:

 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			req, _ := http.NewRequest(
+			req, err := http.NewRequest(
 				tt.method,
 				"/test",
-				io.NopCloser(bytes.NewBufferString(tt.payload)),
+				bytes.NewBufferString(tt.payload),
 			)
+			if err != nil {
+				t.Fatalf("failed to create request: %v", err)
+			}
 			req.Header.Set("Content-Type", tt.contentType)

As per coding guidelines (proper error handling).


234-262: Add test case for Content-Type with charset parameter.

Real-world JSON requests often include charset parameters (e.g., application/json; charset=utf-8). Adding a test case ensures the parser handles this correctly.

Add to the test table:

 		{
 			name:        "POST with XML content",
 			method:      "POST",
 			contentType: "application/xml",
 			payload:     `<xml></xml>`,
 			expected:    false,
 		},
+		{
+			name:        "POST with JSON and charset",
+			method:      "POST",
+			contentType: "application/json; charset=utf-8",
+			payload:     `{"jsonrpc":"2.0","method":"test","id":1}`,
+			expected:    true,
+		},
 	}

Based on learnings (past review suggestion).

internal/analytics/analytics.go (1)

28-29: Consider making BatchSendInterval a var for test flexibility.

While the current const works, tests that verify batch processing behavior would benefit from a shorter interval. Making this a var allows tests to temporarily override the duration.

Apply this diff:

-	// Batch send interval for analytics data
-	BatchSendInterval = 10 * time.Second
+)
 
+// Batch send interval for analytics data (can be overridden in tests)
+var BatchSendInterval = 10 * time.Second
 
+const (
 	maxPerRequest = 25

Based on learnings (past review suggestion).

internal/middleware/middleware_test.go (2)

13-29: Add test for Authorization header sourced from context.

To ensure middleware properly retrieves and sets the Authorization header from context, add a test case.

 import (
+	"context"
 	"net/http"
 	"net/url"
 	"testing"
 
 	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
 
 	"github.com/reportportal/reportportal-mcp-server/internal/utils"
+	"github.com/reportportal/reportportal-mcp-server/internal/security"
 )
@@
 	assert.Equal(t, []string{"1"}, got["a"])
 	assert.Equal(t, []string{"2", "3"}, got["b"])
 }
+
+func TestQueryParamsMiddleware_AddsAuthorizationFromContext(t *testing.T) {
+	req, err := http.NewRequest("GET", "http://example.com/path", nil)
+	require.NoError(t, err)
+	ctx := context.WithValue(req.Context(), security.RPTokenContextKey, "abc123")
+	req = req.WithContext(ctx)
+
+	QueryParamsMiddleware(req)
+
+	assert.Equal(t, "Bearer abc123", req.Header.Get("Authorization"))
+}

19-19: Check NewRequest error to prevent test failures.

The error from http.NewRequest is still being ignored. This should be checked to ensure test clarity if the request creation fails.

-	req, _ := http.NewRequest("GET", "http://example.com/path?x=9", nil)
+	req, err := http.NewRequest("GET", "http://example.com/path?x=9", nil)
+	require.NoError(t, err)

As per coding guidelines.

internal/utils/ctx_utils.go (1)

11-16: Context key type remains an exported string; prefer unexported struct to avoid collisions.

The ContextKey type is still an exported string, which can lead to collisions. Best practice is to use an unexported, zero-sized struct type with exported key variables.

For example:

type queryParamsKey struct{}

// QueryParamsKey is the context key for query parameters
var QueryParamsKey = queryParamsKey{}

var contextKeyQueryParams = QueryParamsKey

Apply the same pattern to RPTokenContextKey in internal/security for consistency. Based on learnings.

internal/mcp_handlers/server.go (1)

42-50: Analytics initialization returns hard error; consider non-fatal fallback.

The current implementation treats analytics initialization failure as fatal and returns an error. The past review suggested aligning with HTTP server behavior by logging a warning and continuing with analyticsClient = nil instead.

 	// Initialize analytics (disabled if analyticsOff is true)
 	var analyticsClient *analytics.Analytics
 	if analyticsOn {
-		var err error
-		// Pass RP API token for secure hashing as user identifier
-		analyticsClient, err = analytics.NewAnalytics(userID, analyticsAPISecret, token)
-		if err != nil {
-			return nil, nil, fmt.Errorf("failed to initialize analytics: %w", err)
-		}
+		ac, err := analytics.NewAnalytics(userID, analyticsAPISecret, token)
+		if err != nil {
+			// Fall back to no analytics and continue
+			slog.Warn("analytics initialization skipped", "error", err)
+		} else {
+			analyticsClient = ac
+		}
 	}

You'll need to add "log/slog" to imports. Based on learnings.

internal/mcp_handlers/launches_test.go (1)

45-46: Check url.Parse error despite past review being marked as resolved.

The error from url.Parse is still being ignored on Line 45, even though the past review comment was marked as addressed in commit c59c94b. Ensure URL parsing errors are caught.

-	serverURL, _ := url.Parse(mockServer.URL)
+	serverURL, urlErr := url.Parse(mockServer.URL)
+	require.NoError(t, urlErr)
internal/config/cli.go (2)

111-119: Dedup lowercasing and use structured logging in GetMCPMode

You lowercase twice and log via string concat. Keep it once and use structured slog.

 func GetMCPMode() string {
-	rawMcpMode := strings.ToLower(os.Getenv("MCP_MODE"))
-	slog.Debug("MCP_MODE env variable is set to: " + rawMcpMode)
-	mcpMode := strings.ToLower(rawMcpMode)
+	rawMcpMode := os.Getenv("MCP_MODE")
+	mcpMode := strings.ToLower(rawMcpMode)
+	slog.Debug("MCP_MODE env variable", "value", mcpMode)
 	if mcpMode == "" {
 		mcpMode = "stdio"
 	}
 	return mcpMode
 }

As per coding guidelines.


291-300: Use the wrapper Handler instead of reaching into MCP.Router

Avoid tight coupling; you already have a pre-wired handler from CreateHTTPServerWithMiddleware.

 server := &http.Server{
   Addr:              addr,
-  Handler:           httpServer.MCP.Router, // Use Chi router directly with throttling/health/info/metrics
+  Handler:           httpServer.Handler,    // Use pre‑wired Chi router with middleware
   ReadHeaderTimeout: 10 * time.Second,
   ReadTimeout:       30 * time.Second,
   WriteTimeout:      30 * time.Second,
   IdleTimeout:       120 * time.Second,
 }
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c59c94b and f7871a6.

📒 Files selected for processing (18)
  • cmd/reportportal-mcp-server/main.go (2 hunks)
  • internal/analytics/analytics.go (3 hunks)
  • internal/analytics/analytics_test.go (5 hunks)
  • internal/config/cli.go (1 hunks)
  • internal/http/http_server.go (11 hunks)
  • internal/http/http_server_batch_test.go (1 hunks)
  • internal/mcp_handlers/items.go (21 hunks)
  • internal/mcp_handlers/launches.go (14 hunks)
  • internal/mcp_handlers/launches_test.go (2 hunks)
  • internal/mcp_handlers/server.go (4 hunks)
  • internal/middleware/middleware.go (1 hunks)
  • internal/middleware/middleware_test.go (2 hunks)
  • internal/security/http_token_middleware.go (2 hunks)
  • internal/utils/ctx_utils.go (2 hunks)
  • internal/utils/ctx_utils_test.go (1 hunks)
  • internal/utils/mcp_utils.go (1 hunks)
  • internal/utils/utils.go (8 hunks)
  • internal/utils/utils_test.go (7 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.go

📄 CodeRabbit inference engine (README.md)

**/*.go: Run formatter (task fmt / go fmt) to keep all Go source files consistently formatted
Run linter (task lint) to maintain code quality across Go files

Files:

  • internal/mcp_handlers/launches.go
  • internal/utils/mcp_utils.go
  • internal/middleware/middleware_test.go
  • internal/security/http_token_middleware.go
  • internal/middleware/middleware.go
  • internal/http/http_server_batch_test.go
  • internal/mcp_handlers/launches_test.go
  • cmd/reportportal-mcp-server/main.go
  • internal/analytics/analytics_test.go
  • internal/mcp_handlers/server.go
  • internal/utils/utils.go
  • internal/analytics/analytics.go
  • internal/utils/ctx_utils_test.go
  • internal/config/cli.go
  • internal/utils/utils_test.go
  • internal/http/http_server.go
  • internal/utils/ctx_utils.go
  • internal/mcp_handlers/items.go

⚙️ CodeRabbit configuration file

Focus on Go best practices, error handling, concurrency safety, performance, and security. Check for proper resource cleanup and context handling.

Files:

  • internal/mcp_handlers/launches.go
  • internal/utils/mcp_utils.go
  • internal/middleware/middleware_test.go
  • internal/security/http_token_middleware.go
  • internal/middleware/middleware.go
  • internal/http/http_server_batch_test.go
  • internal/mcp_handlers/launches_test.go
  • cmd/reportportal-mcp-server/main.go
  • internal/analytics/analytics_test.go
  • internal/mcp_handlers/server.go
  • internal/utils/utils.go
  • internal/analytics/analytics.go
  • internal/utils/ctx_utils_test.go
  • internal/config/cli.go
  • internal/utils/utils_test.go
  • internal/http/http_server.go
  • internal/utils/ctx_utils.go
  • internal/mcp_handlers/items.go
**/*_test.go

⚙️ CodeRabbit configuration file

Ensure comprehensive test coverage, proper test isolation, meaningful assertions, and good test naming conventions.

Files:

  • internal/middleware/middleware_test.go
  • internal/http/http_server_batch_test.go
  • internal/mcp_handlers/launches_test.go
  • internal/analytics/analytics_test.go
  • internal/utils/ctx_utils_test.go
  • internal/utils/utils_test.go
cmd/reportportal-mcp-server/**/*.go

📄 CodeRabbit inference engine (README.md)

Keep the main server entrypoint under cmd/reportportal-mcp-server and build from that path

Files:

  • cmd/reportportal-mcp-server/main.go
cmd/**

⚙️ CodeRabbit configuration file

Review CLI interface design, configuration handling, and application startup logic for robustness and usability.

Files:

  • cmd/reportportal-mcp-server/main.go
🧠 Learnings (1)
📚 Learning: 2025-09-17T11:00:40.261Z
Learnt from: CR
PR: reportportal/reportportal-mcp-server#0
File: README.md:0-0
Timestamp: 2025-09-17T11:00:40.261Z
Learning: Applies to cmd/reportportal-mcp-server/**/*.go : Keep the main server entrypoint under cmd/reportportal-mcp-server and build from that path

Applied to files:

  • cmd/reportportal-mcp-server/main.go
🧬 Code graph analysis (13)
internal/mcp_handlers/launches.go (4)
internal/analytics/analytics.go (1)
  • Analytics (69-81)
internal/utils/mcp_utils.go (6)
  • NewProjectParameter (65-71)
  • SetPaginationOptions (14-29)
  • WithAnalytics (83-97)
  • ExtractProject (73-75)
  • ApplyPaginationOptions (32-63)
  • ReadResponseBody (102-111)
internal/utils/utils.go (4)
  • DefaultSortingForLaunches (17-17)
  • ProcessStartTimeFilter (99-121)
  • ProcessAttributeKeys (124-152)
  • ExtractResponseError (37-62)
internal/utils/ctx_utils.go (1)
  • WithQueryParams (18-21)
internal/utils/mcp_utils.go (1)
internal/utils/utils.go (4)
  • FirstPage (15-15)
  • DefaultPageSize (16-16)
  • PaginatedRequest (31-35)
  • ReadResponseBodyRaw (193-215)
internal/middleware/middleware_test.go (1)
internal/utils/ctx_utils.go (1)
  • WithQueryParams (18-21)
internal/security/http_token_middleware.go (1)
internal/utils/ctx_utils.go (2)
  • ContextKey (12-12)
  • ValidateRPToken (31-44)
internal/middleware/middleware.go (2)
internal/security/http_token_middleware.go (1)
  • GetTokenFromContext (75-78)
internal/utils/ctx_utils.go (1)
  • QueryParamsFromContext (23-27)
internal/http/http_server_batch_test.go (1)
internal/http/http_server.go (3)
  • HTTPServer (61-72)
  • HTTPServerConfig (45-58)
  • NewHTTPServer (82-139)
cmd/reportportal-mcp-server/main.go (1)
internal/config/cli.go (2)
  • AppVersion (25-29)
  • RunApp (337-390)
internal/analytics/analytics_test.go (1)
internal/utils/mcp_utils.go (1)
  • WithAnalytics (83-97)
internal/mcp_handlers/server.go (5)
internal/analytics/analytics.go (2)
  • Analytics (69-81)
  • NewAnalytics (90-131)
internal/middleware/middleware.go (1)
  • QueryParamsMiddleware (11-32)
internal/mcp_handlers/launches.go (1)
  • RegisterLaunchTools (41-56)
internal/mcp_handlers/items.go (1)
  • RegisterTestItemTools (41-54)
internal/promptreader/promptreader.go (1)
  • PromptHandlerPair (16-19)
internal/config/cli.go (3)
internal/http/http_server.go (2)
  • HTTPServerConfig (45-58)
  • CreateHTTPServerWithMiddleware (219-245)
internal/analytics/analytics.go (3)
  • GetAnalyticArg (232-246)
  • Analytics (69-81)
  • StopAnalytics (403-412)
internal/mcp_handlers/server.go (1)
  • NewServer (21-68)
internal/utils/utils_test.go (1)
internal/utils/utils.go (1)
  • ProcessAttributeKeys (124-152)
internal/http/http_server.go (7)
internal/analytics/analytics.go (3)
  • Analytics (69-81)
  • NewAnalytics (90-131)
  • BatchSendInterval (29-29)
internal/utils/ctx_utils.go (1)
  • ValidateRPToken (31-44)
internal/middleware/middleware.go (1)
  • QueryParamsMiddleware (11-32)
internal/mcp_handlers/launches.go (1)
  • RegisterLaunchTools (41-56)
internal/mcp_handlers/items.go (1)
  • RegisterTestItemTools (41-54)
internal/mcp_handlers/server.go (2)
  • ReadPrompts (71-93)
  • PromptFiles (19-19)
internal/security/http_token_middleware.go (1)
  • HTTPTokenMiddleware (19-42)
internal/mcp_handlers/items.go (4)
internal/analytics/analytics.go (1)
  • Analytics (69-81)
internal/utils/mcp_utils.go (5)
  • NewProjectParameter (65-71)
  • SetPaginationOptions (14-29)
  • WithAnalytics (83-97)
  • ApplyPaginationOptions (32-63)
  • ReadResponseBody (102-111)
internal/utils/utils.go (16)
  • DefaultSortingForItems (18-18)
  • DefaultProviderType (21-21)
  • DefaultFilterEqHasStats (23-23)
  • DefaultFilterEqHasChildren (22-22)
  • DefaultFilterInType (24-24)
  • ProcessStartTimeFilter (99-121)
  • ProcessAttributeKeys (124-152)
  • ExtractResponseError (37-62)
  • ReadResponseBodyRaw (193-215)
  • IsTextContent (154-174)
  • FirstPage (15-15)
  • DefaultPageSize (16-16)
  • DefaultSortingForLogs (20-20)
  • DefaultItemLogLevel (26-26)
  • DefaultSortingForSuites (19-19)
  • DefaultFilterInTypeSuites (25-25)
internal/utils/ctx_utils.go (1)
  • WithQueryParams (18-21)
🔇 Additional comments (7)
internal/utils/mcp_utils.go (1)

1-111: LGTM! Pagination and MCP utility functions are well-structured.

The file correctly addresses the past review concerns:

  • math.MaxInt32 is now imported and used for bounds checking
  • Pagination parameters are properly clamped to valid ranges (minimum and maximum)
  • The generic ApplyPaginationOptions function provides good type safety

All utility functions follow Go best practices with appropriate error handling and nil checks.

internal/security/http_token_middleware.go (1)

1-78: LGTM! Token validation and context key usage aligned with utilities.

The integration with utils.ValidateRPToken and the updated RPTokenContextKey type are correct. The middleware properly extracts, validates, and stores the token in context.

internal/middleware/middleware.go (1)

11-32: LGTM! Token sanitization and query parameter handling implemented correctly.

The middleware now properly sanitizes tokens (trimming and checking for CR/LF) before setting the Authorization header, addressing the previous security concern. Query parameter handling via utils.QueryParamsFromContext is also correct.

internal/utils/utils_test.go (1)

1-172: LGTM! Comprehensive test coverage for ProcessAttributeKeys.

The tests are thorough and cover basic cases, edge cases, mixed scenarios, whitespace handling, and performance. Test expectations correctly validate that key:value pairs are preserved intact.

internal/mcp_handlers/server.go (2)

53-56: LGTM! Centralized tool registration.

The use of RegisterLaunchTools and RegisterTestItemTools properly centralizes the registration logic and passes the analytics client through, aligning with the refactoring objectives.


70-93: LGTM! ReadPrompts correctly exported and implemented.

The function properly reads multiple YAML files from the embedded filesystem and returns prompt handler pairs. The filepath handling using forward slashes is correct for embed.FS.

cmd/reportportal-mcp-server/main.go (1)

24-35: Approve: Main delegates to config-driven startup Confirmed no log.Fatal or os.Exit calls in internal/config, so config.RunApp handles errors correctly.

@AleksandrParamonoff AleksandrParamonoff force-pushed the refactoring-detaching-app-to-modules branch from f7871a6 to c4b1035 Compare October 9, 2025 10:03
Copy link

@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: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
internal/utils/utils.go (1)

154-174: Critical bug: switch cases don't return, causing incorrect false results.

The switch statement has cases for JSON, XML, etc., but none of them execute return true. All cases fall through to line 173, returning false for content types that should be recognized as text.

Apply this diff to fix the bug:

 	// Popular application types that are text-based
 	switch lowerType {
-	case "application/json":
-	case "application/xml":
-	case "application/javascript":
-	case "application/xhtml+xml":
-	case "application/yaml":
-	case "application/csv":
+	case "application/json",
+		"application/xml",
+		"application/javascript",
+		"application/xhtml+xml",
+		"application/yaml",
+		"application/csv":
 		return true
 	}
internal/mcp_handlers/launches.go (1)

274-302: Type mismatch: schema defines launch_id as string but handler calls RequireInt.

The parameter is declared as mcp.WithString("launch_id", ...) but the handler calls request.RequireInt("launch_id"), causing runtime errors when clients send string values.

Apply this diff:

-		mcp.WithString("launch_id", // Parameter for specifying the launch name
+		mcp.WithNumber("launch_id",
 			mcp.Description("Launch ID"),
 		),

Also apply the same fix to:

  • ToolRunAutoAnalysis (line 310)
  • ToolUniqueErrorAnalysis (line 387)
  • ToolForceFinishLaunch (line 431)
internal/mcp_handlers/items.go (1)

329-390: Consider changing attachment-content-id to numeric type.

The parameter is defined as mcp.WithString (line 334) but immediately parsed as int64 (lines 344-353). Declaring it as mcp.WithNumber would eliminate parsing code and potential conversion errors.

Apply this diff:

-		mcp.WithString("attachment-content-id", // Parameter for specifying the test item ID
+		mcp.WithNumber("attachment-content-id",
 			mcp.Description("Attachment binary content ID"),
 		),

And update the handler:

-		attachmentIdStr, err := request.RequireString("attachment-content-id")
+		attachmentId, err := request.RequireInt("attachment-content-id")
 		if err != nil {
 			return mcp.NewToolResultError(err.Error()), nil
 		}
-		attachmentId, err := strconv.ParseInt(attachmentIdStr, 10, 64)
-		if err != nil {
-			return mcp.NewToolResultError(
-				fmt.Sprintf("invalid attachment ID value: %s", attachmentIdStr),
-			), nil
-		}
♻️ Duplicate comments (6)
internal/mcp_handlers/launches.go (3)

305-379: Same type mismatch in ToolRunAutoAnalysis.

Apply this diff:

-		mcp.WithString("launch_id", // Parameter for specifying the launch name
+		mcp.WithNumber("launch_id",
 			mcp.Description("Launch ID"),
 		),

382-423: Same type mismatch in ToolUniqueErrorAnalysis.

Apply this diff:

-		mcp.WithString("launch_id", // Parameter for specifying the launch name
+		mcp.WithNumber("launch_id",
 			mcp.Description("Launch ID"),
 		),

426-457: Same type mismatch in ToolForceFinishLaunch.

Apply this diff:

-		mcp.WithString("launch_id", // Parameter for specifying the launch name
+		mcp.WithNumber("launch_id",
 			mcp.Description("Launch ID"),
 		),
internal/utils/ctx_utils.go (1)

11-16: Use an unexported sentinel type for context keys to avoid collisions
Re-introducing an exported ContextKey (string-backed) lets any package mint its own utils.ContextKey("queryParams"), so unrelated code can silently overwrite the same context slot. To keep the key unique, stick with an unexported zero-sized struct and expose only the exported helper or key variable. That preserves cross-package sharing without reopening the collision hazard we flagged earlier.

-// ContextKey is a type for context keys to avoid collisions
-type ContextKey string
-
-var contextKeyQueryParams = ContextKey(
-	"queryParams",
-) // Key for storing query parameters in the context
+type contextKeyQueryParams struct{}
+
+var contextKeyQueryParams = contextKeyQueryParams{} // unique sentinel for query params

Callers can continue using WithQueryParams / QueryParamsFromContext (and other packages should expose their own exported key values instead of constructing new ContextKeys).

internal/mcp_handlers/server.go (1)

41-49: Don’t fail server startup when analytics can’t initialize

Unlike the HTTP path, this constructor still hard‑fails if analytics.NewAnalytics returns an error. That makes the whole MCP server unusable whenever analytics is misconfigured (empty GA secret, missing token in tests, etc.), even though analytics is optional.

Prefer to log a warning and continue without analytics:

 	var analyticsClient *analytics.Analytics
 	if analyticsOn {
-		var err error
-		// Pass RP API token for secure hashing as user identifier
-		analyticsClient, err = analytics.NewAnalytics(userID, analyticsAPISecret, token)
-		if err != nil {
-			return nil, nil, fmt.Errorf("failed to initialize analytics: %w", err)
-		}
+		ac, err := analytics.NewAnalytics(userID, analyticsAPISecret, token)
+		if err != nil {
+			slog.Warn("analytics initialization skipped", "error", err)
+		} else {
+			analyticsClient = ac
+		}
 	}

(remember to import "log/slog"). That keeps startup resilient while still surfacing misconfiguration.

internal/http/http_server_batch_test.go (1)

265-271: Handle http.NewRequest errors in tests

Here we still discard http.NewRequest’s error, which can silently hide malformed URLs or payload edge cases. Let the test fail fast so we know why it broke.

A simple, explicit fix:

-			req, _ := http.NewRequest(
-				tt.method,
-				"/test",
-				io.NopCloser(bytes.NewBufferString(tt.payload)),
-			)
+			req, err := http.NewRequest(tt.method, "/test", bytes.NewBufferString(tt.payload))
+			if err != nil {
+				t.Fatalf("failed to create request: %v", err)
+			}

This keeps the test readable while making failures actionable.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f7871a6 and c4b1035.

📒 Files selected for processing (18)
  • cmd/reportportal-mcp-server/main.go (2 hunks)
  • internal/analytics/analytics.go (3 hunks)
  • internal/analytics/analytics_test.go (5 hunks)
  • internal/config/cli.go (1 hunks)
  • internal/http/http_server.go (12 hunks)
  • internal/http/http_server_batch_test.go (1 hunks)
  • internal/mcp_handlers/items.go (21 hunks)
  • internal/mcp_handlers/launches.go (14 hunks)
  • internal/mcp_handlers/launches_test.go (2 hunks)
  • internal/mcp_handlers/server.go (4 hunks)
  • internal/middleware/middleware.go (1 hunks)
  • internal/middleware/middleware_test.go (2 hunks)
  • internal/security/http_token_middleware.go (2 hunks)
  • internal/utils/ctx_utils.go (2 hunks)
  • internal/utils/ctx_utils_test.go (1 hunks)
  • internal/utils/mcp_utils.go (1 hunks)
  • internal/utils/utils.go (8 hunks)
  • internal/utils/utils_test.go (7 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.go

📄 CodeRabbit inference engine (README.md)

**/*.go: Run formatter (task fmt / go fmt) to keep all Go source files consistently formatted
Run linter (task lint) to maintain code quality across Go files

Files:

  • internal/mcp_handlers/launches.go
  • internal/http/http_server_batch_test.go
  • internal/utils/utils_test.go
  • internal/mcp_handlers/launches_test.go
  • internal/security/http_token_middleware.go
  • internal/utils/utils.go
  • internal/middleware/middleware.go
  • internal/utils/ctx_utils_test.go
  • internal/http/http_server.go
  • internal/utils/ctx_utils.go
  • cmd/reportportal-mcp-server/main.go
  • internal/mcp_handlers/server.go
  • internal/config/cli.go
  • internal/middleware/middleware_test.go
  • internal/mcp_handlers/items.go
  • internal/analytics/analytics_test.go
  • internal/utils/mcp_utils.go
  • internal/analytics/analytics.go

⚙️ CodeRabbit configuration file

Focus on Go best practices, error handling, concurrency safety, performance, and security. Check for proper resource cleanup and context handling.

Files:

  • internal/mcp_handlers/launches.go
  • internal/http/http_server_batch_test.go
  • internal/utils/utils_test.go
  • internal/mcp_handlers/launches_test.go
  • internal/security/http_token_middleware.go
  • internal/utils/utils.go
  • internal/middleware/middleware.go
  • internal/utils/ctx_utils_test.go
  • internal/http/http_server.go
  • internal/utils/ctx_utils.go
  • cmd/reportportal-mcp-server/main.go
  • internal/mcp_handlers/server.go
  • internal/config/cli.go
  • internal/middleware/middleware_test.go
  • internal/mcp_handlers/items.go
  • internal/analytics/analytics_test.go
  • internal/utils/mcp_utils.go
  • internal/analytics/analytics.go
**/*_test.go

⚙️ CodeRabbit configuration file

Ensure comprehensive test coverage, proper test isolation, meaningful assertions, and good test naming conventions.

Files:

  • internal/http/http_server_batch_test.go
  • internal/utils/utils_test.go
  • internal/mcp_handlers/launches_test.go
  • internal/utils/ctx_utils_test.go
  • internal/middleware/middleware_test.go
  • internal/analytics/analytics_test.go
cmd/reportportal-mcp-server/**/*.go

📄 CodeRabbit inference engine (README.md)

Keep the main server entrypoint under cmd/reportportal-mcp-server and build from that path

Files:

  • cmd/reportportal-mcp-server/main.go
cmd/**

⚙️ CodeRabbit configuration file

Review CLI interface design, configuration handling, and application startup logic for robustness and usability.

Files:

  • cmd/reportportal-mcp-server/main.go
🧠 Learnings (1)
📚 Learning: 2025-09-17T11:00:40.261Z
Learnt from: CR
PR: reportportal/reportportal-mcp-server#0
File: README.md:0-0
Timestamp: 2025-09-17T11:00:40.261Z
Learning: Applies to cmd/reportportal-mcp-server/**/*.go : Keep the main server entrypoint under cmd/reportportal-mcp-server and build from that path

Applied to files:

  • cmd/reportportal-mcp-server/main.go
🧬 Code graph analysis (13)
internal/mcp_handlers/launches.go (4)
internal/analytics/analytics.go (1)
  • Analytics (69-81)
internal/utils/mcp_utils.go (6)
  • NewProjectParameter (65-71)
  • SetPaginationOptions (14-29)
  • WithAnalytics (83-97)
  • ExtractProject (73-75)
  • ApplyPaginationOptions (32-63)
  • ReadResponseBody (102-111)
internal/utils/utils.go (4)
  • DefaultSortingForLaunches (17-17)
  • ProcessStartTimeFilter (99-121)
  • ProcessAttributeKeys (124-152)
  • ExtractResponseError (37-62)
internal/utils/ctx_utils.go (1)
  • WithQueryParams (18-21)
internal/http/http_server_batch_test.go (1)
internal/http/http_server.go (3)
  • HTTPServer (66-77)
  • HTTPServerConfig (50-63)
  • NewHTTPServer (87-144)
internal/utils/utils_test.go (1)
internal/utils/utils.go (1)
  • ProcessAttributeKeys (124-152)
internal/security/http_token_middleware.go (1)
internal/utils/ctx_utils.go (2)
  • ContextKey (12-12)
  • ValidateRPToken (31-44)
internal/middleware/middleware.go (2)
internal/security/http_token_middleware.go (1)
  • GetTokenFromContext (75-78)
internal/utils/ctx_utils.go (1)
  • QueryParamsFromContext (23-27)
internal/http/http_server.go (7)
internal/analytics/analytics.go (3)
  • Analytics (69-81)
  • NewAnalytics (90-131)
  • BatchSendInterval (29-29)
internal/utils/ctx_utils.go (1)
  • ValidateRPToken (31-44)
internal/middleware/middleware.go (1)
  • QueryParamsMiddleware (11-32)
internal/mcp_handlers/launches.go (1)
  • RegisterLaunchTools (41-56)
internal/mcp_handlers/items.go (1)
  • RegisterTestItemTools (41-54)
internal/mcp_handlers/server.go (2)
  • ReadPrompts (71-93)
  • PromptFiles (19-19)
internal/security/http_token_middleware.go (1)
  • HTTPTokenMiddleware (19-42)
cmd/reportportal-mcp-server/main.go (1)
internal/config/cli.go (2)
  • AppVersion (25-29)
  • RunApp (336-389)
internal/mcp_handlers/server.go (5)
internal/analytics/analytics.go (2)
  • Analytics (69-81)
  • NewAnalytics (90-131)
internal/middleware/middleware.go (1)
  • QueryParamsMiddleware (11-32)
internal/mcp_handlers/launches.go (1)
  • RegisterLaunchTools (41-56)
internal/mcp_handlers/items.go (1)
  • RegisterTestItemTools (41-54)
internal/promptreader/promptreader.go (1)
  • PromptHandlerPair (16-19)
internal/config/cli.go (3)
internal/http/http_server.go (2)
  • HTTPServerConfig (50-63)
  • CreateHTTPServerWithMiddleware (224-250)
internal/analytics/analytics.go (3)
  • GetAnalyticArg (232-246)
  • Analytics (69-81)
  • StopAnalytics (403-412)
internal/mcp_handlers/server.go (1)
  • NewServer (21-68)
internal/middleware/middleware_test.go (1)
internal/utils/ctx_utils.go (1)
  • WithQueryParams (18-21)
internal/mcp_handlers/items.go (4)
internal/analytics/analytics.go (1)
  • Analytics (69-81)
internal/utils/mcp_utils.go (6)
  • NewProjectParameter (65-71)
  • SetPaginationOptions (14-29)
  • WithAnalytics (83-97)
  • ExtractProject (73-75)
  • ApplyPaginationOptions (32-63)
  • ReadResponseBody (102-111)
internal/utils/utils.go (16)
  • DefaultSortingForItems (18-18)
  • DefaultProviderType (21-21)
  • DefaultFilterEqHasStats (23-23)
  • DefaultFilterEqHasChildren (22-22)
  • DefaultFilterInType (24-24)
  • ProcessStartTimeFilter (99-121)
  • ProcessAttributeKeys (124-152)
  • ExtractResponseError (37-62)
  • ReadResponseBodyRaw (193-215)
  • IsTextContent (154-174)
  • FirstPage (15-15)
  • DefaultPageSize (16-16)
  • DefaultSortingForLogs (20-20)
  • DefaultItemLogLevel (26-26)
  • DefaultSortingForSuites (19-19)
  • DefaultFilterInTypeSuites (25-25)
internal/utils/ctx_utils.go (1)
  • WithQueryParams (18-21)
internal/analytics/analytics_test.go (1)
internal/utils/mcp_utils.go (1)
  • WithAnalytics (83-97)
internal/utils/mcp_utils.go (1)
internal/utils/utils.go (4)
  • FirstPage (15-15)
  • DefaultPageSize (16-16)
  • PaginatedRequest (31-35)
  • ReadResponseBodyRaw (193-215)
🔇 Additional comments (3)
internal/http/http_server.go (1)

1-537: LGTM! Clean refactoring with proper analytics integration.

The duplicate import issue has been resolved, analytics is properly integrated throughout the HTTP server, and the MaxRequestBodySize constant provides good protection against DoS attacks. The batch request validation logic is comprehensive.

internal/utils/ctx_utils_test.go (1)

1-111: LGTM! Comprehensive test coverage.

The tests cover valid UUIDs, edge cases (empty, too short/long, invalid format), and various token validation scenarios. Good use of table-driven tests.

internal/security/http_token_middleware.go (1)

1-78: LGTM! Clean refactoring to use centralized utilities.

The migration to utils.ContextKey and utils.ValidateRPToken maintains consistency with the broader refactoring effort. Token extraction and validation logic is sound.

@AleksandrParamonoff AleksandrParamonoff force-pushed the refactoring-detaching-app-to-modules branch from c4b1035 to c987c3a Compare October 9, 2025 10:36
Copy link

@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: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (11)
internal/analytics/analytics.go (4)

19-32: Make BatchSendInterval overridable for tests

Use a var (not const) so tests can shorten it and avoid sleeps.

Apply:

-const (
+const (
   // Google Analytics 4 Measurement Protocol endpoint
   ga4EndpointURL = "https://www.google-analytics.com/mp/collect"
   ...
-  // Batch send interval for analytics data
-  BatchSendInterval = 10 * time.Second
   ...
   maxPerRequest = 25
 )
+
+// Batch send interval for analytics data (overridable in tests)
+var BatchSendInterval = 10 * time.Second

68-81: Make Analytics.Stop idempotent to prevent “close of closed channel” panics

Stop can be called twice (shutdown paths, deferred calls). Guard with sync.Once.

Apply:

 type Analytics struct {
   config     *AnalyticsConfig
   httpClient *http.Client
@@
   stopChan   chan struct{}
   wg         sync.WaitGroup
   tickerDone chan struct{}
+  stopOnce   sync.Once
 }
@@
 func (a *Analytics) Stop() {
-  if a == nil || a.stopChan == nil {
-    return
-  }
-
-  slog.Debug("Stopping analytics metrics processor")
-  close(a.stopChan)
-
-  // Wait for ticker goroutine to finish
-  select {
-  case <-a.tickerDone:
-    slog.Debug("Analytics metrics processor stopped gracefully")
-  case <-time.After(5 * time.Second):
-    slog.Warn("Analytics metrics processor stop timeout")
-  }
-
-  a.wg.Wait()
+  if a == nil || a.stopChan == nil {
+    return
+  }
+  a.stopOnce.Do(func() {
+    slog.Debug("Stopping analytics metrics processor")
+    close(a.stopChan)
+    // Wait for ticker goroutine to finish
+    select {
+    case <-a.tickerDone:
+      slog.Debug("Analytics metrics processor stopped gracefully")
+    case <-time.After(5 * time.Second):
+      slog.Warn("Analytics metrics processor stop timeout")
+    }
+    a.wg.Wait()
+  })
 }

Also applies to: 272-289


61-66: Rename TimestampMS field to match “timestamp_micros” for clarity

The JSON tag is “timestamp_micros” while the field is named TimestampMS. Rename to avoid confusion.

Apply:

 type GAPayload struct {
   ClientID    string    `json:"client_id"`
   UserID      string    `json:"user_id,omitempty"`
   Events      []GAEvent `json:"events"`
-  TimestampMS int64     `json:"timestamp_micros"`
+  TimestampMicros int64 `json:"timestamp_micros"`
 }
@@
   payload := GAPayload{
     ClientID:    a.config.UserID,
     UserID:      a.config.UserID,
     Events:      events,
-    TimestampMS: time.Now().UnixMicro(),
+    TimestampMicros: time.Now().UnixMicro(),
   }

Also applies to: 150-156


232-246: Document GetAnalyticArg’s purpose and obfuscation

  • GetAnalyticArg is consumed in internal/config/cli.go (lines 153, 198) and covered by tests in internal/analytics/analytics_test.go.
  • Add GoDoc above the function explaining why the string is built this way, what it represents (e.g., “Analytics API secret”), and any invariants consumers should know.
internal/utils/utils.go (1)

163-171: Bug: IsTextContent returns true only for “application/csv”

Empty case blocks don’t fall through in Go. Combine cases so all listed types return true.

Apply:

 switch lowerType {
- case "application/json":
- case "application/xml":
- case "application/javascript":
- case "application/xhtml+xml":
- case "application/yaml":
- case "application/csv":
-   return true
+ case "application/json",
+      "application/xml",
+      "application/javascript",
+      "application/xhtml+xml",
+      "application/yaml",
+      "application/csv":
+   return true
 }
internal/http/http_server.go (1)

379-386: /info: AnalyticsEnabled field is never set

You populate Analytics.Enabled but leave AnalyticsEnabled stale (always false). Set it or remove the duplicate field.

Apply:

   info.ServerRunning = hs.running
-  info.Analytics.Enabled = hs.analytics != nil
+  info.Analytics.Enabled = hs.analytics != nil
+  info.AnalyticsEnabled = hs.analytics != nil
   info.Timestamp = time.Now().UTC()
internal/mcp_handlers/items.go (3)

334-354: Attachment ID: use numeric schema and validate non-negative

Define parameter as number and drop string parsing. Also reject negative IDs.

-            mcp.WithString("attachment-content-id", // Parameter for specifying the test item ID
-                mcp.Description("Attachment binary content ID"),
-            ),
+            mcp.WithNumber("attachment-content-id",
+                mcp.Description("Attachment binary content ID"),
+            ),
@@
-            attachmentIdStr, err := request.RequireString("attachment-content-id")
+            attachmentId, err := request.RequireInt("attachment-content-id")
             if err != nil {
                 return mcp.NewToolResultError(err.Error()), nil
             }
-            attachmentId, err := strconv.ParseInt(attachmentIdStr, 10, 64)
-            if err != nil {
-                return mcp.NewToolResultError(
-                    fmt.Sprintf("invalid attachment ID value: %s", attachmentIdStr),
-                ), nil
-            }
+            if attachmentId < 0 {
+                return mcp.NewToolResultError(
+                    fmt.Sprintf("invalid attachment ID value: %d", attachmentId),
+                ), nil
+            }
@@
-            response, err := lr.client.FileStorageAPI.GetFile(ctx, attachmentId, project).
+            response, err := lr.client.FileStorageAPI.GetFile(ctx, int64(attachmentId), project).
                 Execute()
```<!-- review_comment_end -->



Also applies to: 355-360, 362-389

---

`371-389`: **Text-vs-binary detection likely wrong (utils.IsTextContent switch)**

Current IsTextContent returns true only for the last case due to separate case labels. Fix to group cases; otherwise attachments like JSON/XML will be treated as binary.


Based on relevant code in internal/utils/utils.go.
```diff
- switch lowerType {
- case "application/json":
- case "application/xml":
- case "application/javascript":
- case "application/xhtml+xml":
- case "application/yaml":
- case "application/csv":
-     return true
- }
+ switch lowerType {
+ case "application/json", "application/xml", "application/javascript",
+     "application/xhtml+xml", "application/yaml", "application/csv":
+     return true
+ }

Alternatively, consider parsing with mime.ParseMediaType and matching vendor subtypes.


583-590: Critical: wrong request keys (dot vs hyphen) — filters never read

Tool options use hyphenated names, but handler reads dotted keys, so all filters default to empty values.

-            filterName := request.GetString("filter.cnt.name", "")
-            filterAttributes := request.GetString("filter.has.compositeAttribute", "")
-            filterAttributeKeys := request.GetString("filter.has.attributeKey", "")
-            filterDescription := request.GetString("filter.cnt.description", "")
-            filterStartTimeFrom := request.GetString("filter.btw.startTime.from", "")
-            filterStartTimeTo := request.GetString("filter.btw.startTime.to", "")
+            filterName := request.GetString("filter-cnt-name", "")
+            filterAttributes := request.GetString("filter-has-compositeAttribute", "")
+            filterAttributeKeys := request.GetString("filter-has-attributeKey", "")
+            filterDescription := request.GetString("filter-cnt-description", "")
+            filterStartTimeFrom := request.GetString("filter-btw-startTime-from", "")
+            filterStartTimeTo := request.GetString("filter-btw-startTime-to", "")
```<!-- review_comment_end -->

</blockquote></details>
<details>
<summary>internal/utils/utils_test.go (1)</summary><blockquote>

`3-6`: **Fix perf test key generation; avoid control characters and preallocate**

string(rune(i)) yields control characters; use strconv.Itoa and preallocate for performance.



```diff
 import (
+	"strconv"
 	"strings"
 	"testing"
 )
@@
-	var keys []string
+	keys := make([]string, 0, 1000)
 	for i := 0; i < 1000; i++ {
-		keys = append(keys, "key"+string(rune(i))+":")
+		keys = append(keys, "key"+strconv.Itoa(i)+":")
 	}

Also applies to: 149-172

internal/utils/ctx_utils.go (1)

37-44: Use uuid.Parse instead of uuid.Validate in internal/utils/ctx_utils.go

The google/uuid v1.6.0 module doesn’t include Validate, so this code won’t compile. Replace with:

- if uuid.Validate(token) == nil {
-     return true
- }
+ if _, err := uuid.Parse(token); err == nil {
+     return true
+ }
♻️ Duplicate comments (14)
internal/mcp_handlers/launches_test.go (1)

45-49: Check url.Parse error (don’t discard)
Ignoring url.Parse errors can hide failures and complicates debugging. Capture and assert it.

Apply:

- serverURL, _ := url.Parse(mockServer.URL)
+ serverURL, urlErr := url.Parse(mockServer.URL)
+ require.NoError(t, urlErr)
internal/utils/utils.go (2)

27-27: Remove redundant maxInt32 wrapper constant

Use math.MaxInt32 directly; the wrapper adds no value and was previously flagged.

Apply:

- DefaultItemLogLevel        = "TRACE"       // Default log level for test item logs
- maxInt32                   = math.MaxInt32 // Standard library constant for maximum int32 value
+ DefaultItemLogLevel = "TRACE" // Default log level for test item logs

136-138: Clarify comment for key:value preservation

Make the intent explicit.

Apply:

- if colonIndex := strings.Index(key, ":"); colonIndex > 0 && colonIndex < len(key)-1 {
-   processed = append(processed, key) // Keep full key with colon
+ if colonIndex := strings.Index(key, ":"); colonIndex > 0 && colonIndex < len(key)-1 {
+   processed = append(processed, key) // Keep key:value pair unchanged
internal/mcp_handlers/items.go (2)

393-416: Reuse SetPaginationOptions in logs tool to avoid drift

Inline page/page-size/page-sort duplicates logic. Switch to utils.SetPaginationOptions like other tools.

-func (lr *TestItemResources) toolGetTestItemLogsByFilter() (tool mcp.Tool, handler server.ToolHandlerFunc) {
-    return mcp.NewTool(
-        "get_test_item_logs_by_filter",
-        mcp.WithDescription("Get list of logs for test item with specific item ID with optional filters"),
-        lr.projectParameter,
-        mcp.WithString("parent-item-id",
-            mcp.Description("Items with specific Parent Item ID, this is a required parameter"),
-        ),
-        mcp.WithNumber("page",
-            mcp.DefaultNumber(utils.FirstPage),
-            mcp.Description("Page number"),
-        ),
-        mcp.WithNumber("page-size",
-            mcp.DefaultNumber(utils.DefaultPageSize),
-            mcp.Description("Page size"),
-        ),
-        mcp.WithString("page-sort",
-            mcp.DefaultString(utils.DefaultSortingForLogs),
-            mcp.Description("Sorting fields and direction"),
-        ),
-        // ... other options
-    ), utils.WithAnalytics( ...
+func (lr *TestItemResources) toolGetTestItemLogsByFilter() (tool mcp.Tool, handler server.ToolHandlerFunc) {
+    opts := []mcp.ToolOption{
+        mcp.WithDescription("Get list of logs for test item with specific item ID with optional filters"),
+        lr.projectParameter,
+        mcp.WithString("parent-item-id",
+            mcp.Description("Items with specific Parent Item ID, this is a required parameter"),
+        ),
+    }
+    opts = append(opts, utils.SetPaginationOptions(utils.DefaultSortingForLogs)...)
+    opts = append(opts,
+        // ... existing optional filters
+    )
+    return mcp.NewTool("get_test_item_logs_by_filter", opts...),
+        utils.WithAnalytics( ...
```<!-- review_comment_end -->



Also applies to: 498-504

---

`40-54`: **Export for API consistency: ToolGetTestItemLogsByFilter**

All other tool constructors are exported; this one isn’t. Rename for symmetry.


```diff
-   s.AddTool(testItems.toolGetTestItemLogsByFilter())
+   s.AddTool(testItems.ToolGetTestItemLogsByFilter())

Apply corresponding rename on the receiver method and any callsites.

internal/middleware/middleware_test.go (2)

1-29: Add test: Authorization header from context is set

Covers middleware behavior when token is placed in context.

 import (
+    "context"
     "net/http"
     "net/url"
     "testing"
 
     "github.com/stretchr/testify/assert"
 
     "github.com/reportportal/reportportal-mcp-server/internal/utils"
+    "github.com/reportportal/reportportal-mcp-server/internal/security"
 )
@@
 func TestQueryParamsMiddleware(t *testing.T) {
@@
 }
+
+func TestQueryParamsMiddleware_AddsAuthorizationFromContext(t *testing.T) {
+    req, err := http.NewRequest("GET", "http://example.com/path", nil)
+    assert.NoError(t, err)
+    ctx := context.WithValue(req.Context(), security.RPTokenContextKey, "abc123")
+    req = req.WithContext(ctx)
+
+    QueryParamsMiddleware(req)
+
+    assert.Equal(t, "Bearer abc123", req.Header.Get("Authorization"))
+}
```<!-- review_comment_end -->

---

`19-21`: **Assert NewRequest error in tests**

Don’t ignore request creation errors.


```diff
-    req, _ := http.NewRequest("GET", "http://example.com/path?x=9", nil)
+    req, err := http.NewRequest("GET", "http://example.com/path?x=9", nil)
+    assert.NoError(t, err)
```<!-- review_comment_end -->

</blockquote></details>
<details>
<summary>internal/analytics/analytics_test.go (1)</summary><blockquote>

`179-189`: **Also assert analytics counter incremented**

Strengthens the test to verify tracking, not just delegation.


```diff
 result, err := wrappedHandler(context.Background(), mcp.CallToolRequest{})
 
 // Verify handler was called
 assert.NoError(t, err)
 assert.NotNil(t, result)
 assert.True(t, handlerCalled, "Original handler should be called")
+
+// Verify metric incremented
+analytics.metricsLock.RLock()
+if v, ok := analytics.metrics["test_tool"]; ok && v != nil {
+    assert.Equal(t, int64(1), *v)
+} else {
+    assert.Fail(t, "metric for test_tool not recorded")
+}
+analytics.metricsLock.RUnlock()
```<!-- review_comment_end -->

</blockquote></details>
<details>
<summary>internal/utils/ctx_utils.go (1)</summary><blockquote>

`11-16`: **Avoid string-based context keys; use an unexported struct key with an exported var**

String keys can collide across packages. Prefer an unexported, zero-sized struct type and export only the key value.



```diff
-// ContextKey is a type for context keys to avoid collisions
-type ContextKey string
-
-var contextKeyQueryParams = ContextKey(
-	"queryParams",
-) // Key for storing query parameters in the context
+// QueryParamsKey is the context key for query parameters (exported var; unexported type)
+type queryParamsKey struct{}
+var QueryParamsKey = queryParamsKey{}

And update usages below:

- return context.WithValue(ctx, contextKeyQueryParams, queryParams)
+ return context.WithValue(ctx, QueryParamsKey, queryParams)
- queryParams, ok := ctx.Value(contextKeyQueryParams).(url.Values)
+ queryParams, ok := ctx.Value(QueryParamsKey).(url.Values)
internal/mcp_handlers/server.go (1)

41-50: Make analytics initialization non-fatal and gate it (align with HTTP server)

Mirror HTTP server behavior: only attempt when enabled and inputs are valid; warn on failure and continue.

-	// Initialize analytics (disabled if analyticsOff is true)
-	var analyticsClient *analytics.Analytics
-	if analyticsOn {
-		var err error
-		// Pass RP API token for secure hashing as user identifier
-		analyticsClient, err = analytics.NewAnalytics(userID, analyticsAPISecret, token)
-		if err != nil {
-			return nil, nil, fmt.Errorf("failed to initialize analytics: %w", err)
-		}
-	}
+	// Initialize analytics (non-fatal on failure; gated like HTTP server)
+	var analyticsClient *analytics.Analytics
+	if analyticsOn && utils.ValidateRPToken(token) && analyticsAPISecret != "" {
+		if ac, err := analytics.NewAnalytics(userID, analyticsAPISecret, token); err != nil {
+			slog.Warn("analytics initialization skipped", "error", err)
+		} else {
+			analyticsClient = ac
+		}
+	}

Add imports:

 import (
 	"embed"
 	"fmt"
 	"io/fs"
 	"net/url"
 	"path/filepath"
+	"log/slog"
@@
-	"github.com/reportportal/reportportal-mcp-server/internal/middleware"
+	"github.com/reportportal/reportportal-mcp-server/internal/middleware"
+	"github.com/reportportal/reportportal-mcp-server/internal/utils"
internal/http/http_server_batch_test.go (2)

266-271: Don’t ignore http.NewRequest error and avoid NopCloser

Make failures explicit and simplify the body.

-			req, _ := http.NewRequest(
+			req, err := http.NewRequest(
 				tt.method,
 				"/test",
-				io.NopCloser(bytes.NewBufferString(tt.payload)),
+				bytes.NewBufferString(tt.payload),
 			)
+			if err != nil {
+				t.Fatalf("failed to create request: %v", err)
+			}

241-262: Add charset-parameterized JSON case

Ensure requests with “application/json; charset=utf-8” are accepted.

 		{
 			name:        "POST with XML content",
 			method:      "POST",
 			contentType: "application/xml",
 			payload:     `<xml></xml>`,
 			expected:    false,
 		},
+		{
+			name:        "POST with JSON content and charset",
+			method:      "POST",
+			contentType: "application/json; charset=utf-8",
+			payload:     `{"jsonrpc":"2.0","method":"test","id":1}`,
+			expected:    true,
+		},
internal/config/cli.go (2)

112-114: Remove redundant ToLower call.

The code applies strings.ToLower twice: once when reading the environment variable and again on the already-lowercased value.

Apply this diff:

-	rawMcpMode := strings.ToLower(os.Getenv("MCP_MODE"))
+	rawMcpMode := os.Getenv("MCP_MODE")
+	mcpMode := strings.ToLower(rawMcpMode)
 	slog.Debug("MCP_MODE env variable is set to: " + rawMcpMode)
-	mcpMode := strings.ToLower(rawMcpMode)

293-295: Use the Handler returned by CreateHTTPServerWithMiddleware.

The server is tightly coupled to httpServer.MCP.Router. Use the Handler field from the HTTPServerWithMiddleware struct instead to avoid reaching into internal structures.

Apply this diff:

	server := &http.Server{
		Addr:              addr,
-		Handler:           httpServer.MCP.Router, // Use Chi router directly with throttling/health/info/metrics
+		Handler:           httpServer.Handler,    // Use pre‑wired Chi router with middleware
		ReadHeaderTimeout: 10 * time.Second,      // Prevent Slowloris attacks
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c4b1035 and c987c3a.

📒 Files selected for processing (18)
  • cmd/reportportal-mcp-server/main.go (2 hunks)
  • internal/analytics/analytics.go (3 hunks)
  • internal/analytics/analytics_test.go (5 hunks)
  • internal/config/cli.go (1 hunks)
  • internal/http/http_server.go (12 hunks)
  • internal/http/http_server_batch_test.go (1 hunks)
  • internal/mcp_handlers/items.go (23 hunks)
  • internal/mcp_handlers/launches.go (12 hunks)
  • internal/mcp_handlers/launches_test.go (2 hunks)
  • internal/mcp_handlers/server.go (4 hunks)
  • internal/middleware/middleware.go (1 hunks)
  • internal/middleware/middleware_test.go (2 hunks)
  • internal/security/http_token_middleware.go (2 hunks)
  • internal/utils/ctx_utils.go (2 hunks)
  • internal/utils/ctx_utils_test.go (1 hunks)
  • internal/utils/mcp_utils.go (1 hunks)
  • internal/utils/utils.go (8 hunks)
  • internal/utils/utils_test.go (7 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.go

📄 CodeRabbit inference engine (README.md)

**/*.go: Run formatter (task fmt / go fmt) to keep all Go source files consistently formatted
Run linter (task lint) to maintain code quality across Go files

Files:

  • internal/http/http_server.go
  • internal/security/http_token_middleware.go
  • internal/mcp_handlers/items.go
  • internal/mcp_handlers/launches.go
  • cmd/reportportal-mcp-server/main.go
  • internal/utils/ctx_utils_test.go
  • internal/utils/utils_test.go
  • internal/middleware/middleware.go
  • internal/mcp_handlers/launches_test.go
  • internal/utils/utils.go
  • internal/middleware/middleware_test.go
  • internal/utils/mcp_utils.go
  • internal/utils/ctx_utils.go
  • internal/analytics/analytics.go
  • internal/http/http_server_batch_test.go
  • internal/config/cli.go
  • internal/analytics/analytics_test.go
  • internal/mcp_handlers/server.go

⚙️ CodeRabbit configuration file

Focus on Go best practices, error handling, concurrency safety, performance, and security. Check for proper resource cleanup and context handling.

Files:

  • internal/http/http_server.go
  • internal/security/http_token_middleware.go
  • internal/mcp_handlers/items.go
  • internal/mcp_handlers/launches.go
  • cmd/reportportal-mcp-server/main.go
  • internal/utils/ctx_utils_test.go
  • internal/utils/utils_test.go
  • internal/middleware/middleware.go
  • internal/mcp_handlers/launches_test.go
  • internal/utils/utils.go
  • internal/middleware/middleware_test.go
  • internal/utils/mcp_utils.go
  • internal/utils/ctx_utils.go
  • internal/analytics/analytics.go
  • internal/http/http_server_batch_test.go
  • internal/config/cli.go
  • internal/analytics/analytics_test.go
  • internal/mcp_handlers/server.go
cmd/reportportal-mcp-server/**/*.go

📄 CodeRabbit inference engine (README.md)

Keep the main server entrypoint under cmd/reportportal-mcp-server and build from that path

Files:

  • cmd/reportportal-mcp-server/main.go
cmd/**

⚙️ CodeRabbit configuration file

Review CLI interface design, configuration handling, and application startup logic for robustness and usability.

Files:

  • cmd/reportportal-mcp-server/main.go
**/*_test.go

⚙️ CodeRabbit configuration file

Ensure comprehensive test coverage, proper test isolation, meaningful assertions, and good test naming conventions.

Files:

  • internal/utils/ctx_utils_test.go
  • internal/utils/utils_test.go
  • internal/mcp_handlers/launches_test.go
  • internal/middleware/middleware_test.go
  • internal/http/http_server_batch_test.go
  • internal/analytics/analytics_test.go
🧠 Learnings (1)
📚 Learning: 2025-09-17T11:00:40.261Z
Learnt from: CR
PR: reportportal/reportportal-mcp-server#0
File: README.md:0-0
Timestamp: 2025-09-17T11:00:40.261Z
Learning: Applies to cmd/reportportal-mcp-server/**/*.go : Keep the main server entrypoint under cmd/reportportal-mcp-server and build from that path

Applied to files:

  • cmd/reportportal-mcp-server/main.go
  • internal/mcp_handlers/server.go
🧬 Code graph analysis (13)
internal/http/http_server.go (7)
internal/analytics/analytics.go (3)
  • Analytics (69-81)
  • NewAnalytics (90-131)
  • BatchSendInterval (29-29)
internal/utils/ctx_utils.go (1)
  • ValidateRPToken (31-44)
internal/middleware/middleware.go (1)
  • QueryParamsMiddleware (11-32)
internal/mcp_handlers/launches.go (1)
  • RegisterLaunchTools (41-56)
internal/mcp_handlers/items.go (1)
  • RegisterTestItemTools (41-54)
internal/mcp_handlers/server.go (2)
  • ReadPrompts (71-93)
  • PromptFiles (19-19)
internal/security/http_token_middleware.go (1)
  • HTTPTokenMiddleware (19-42)
internal/security/http_token_middleware.go (1)
internal/utils/ctx_utils.go (2)
  • ContextKey (12-12)
  • ValidateRPToken (31-44)
internal/mcp_handlers/items.go (4)
internal/analytics/analytics.go (1)
  • Analytics (69-81)
internal/utils/mcp_utils.go (6)
  • NewProjectParameter (65-71)
  • SetPaginationOptions (14-29)
  • WithAnalytics (83-97)
  • ExtractProject (73-75)
  • ApplyPaginationOptions (32-63)
  • ReadResponseBody (102-111)
internal/utils/utils.go (16)
  • DefaultSortingForItems (18-18)
  • DefaultProviderType (21-21)
  • DefaultFilterEqHasStats (23-23)
  • DefaultFilterEqHasChildren (22-22)
  • DefaultFilterInType (24-24)
  • ProcessStartTimeFilter (99-121)
  • ProcessAttributeKeys (124-152)
  • ExtractResponseError (37-62)
  • ReadResponseBodyRaw (193-215)
  • IsTextContent (154-174)
  • FirstPage (15-15)
  • DefaultPageSize (16-16)
  • DefaultSortingForLogs (20-20)
  • DefaultItemLogLevel (26-26)
  • DefaultSortingForSuites (19-19)
  • DefaultFilterInTypeSuites (25-25)
internal/utils/ctx_utils.go (1)
  • WithQueryParams (18-21)
internal/mcp_handlers/launches.go (4)
internal/analytics/analytics.go (1)
  • Analytics (69-81)
internal/utils/mcp_utils.go (6)
  • NewProjectParameter (65-71)
  • SetPaginationOptions (14-29)
  • WithAnalytics (83-97)
  • ExtractProject (73-75)
  • ApplyPaginationOptions (32-63)
  • ReadResponseBody (102-111)
internal/utils/utils.go (4)
  • DefaultSortingForLaunches (17-17)
  • ProcessStartTimeFilter (99-121)
  • ProcessAttributeKeys (124-152)
  • ExtractResponseError (37-62)
internal/utils/ctx_utils.go (1)
  • WithQueryParams (18-21)
cmd/reportportal-mcp-server/main.go (1)
internal/config/cli.go (2)
  • AppVersion (25-29)
  • RunApp (336-389)
internal/utils/utils_test.go (1)
internal/utils/utils.go (1)
  • ProcessAttributeKeys (124-152)
internal/middleware/middleware.go (2)
internal/security/http_token_middleware.go (1)
  • GetTokenFromContext (75-78)
internal/utils/ctx_utils.go (1)
  • QueryParamsFromContext (23-27)
internal/middleware/middleware_test.go (1)
internal/utils/ctx_utils.go (1)
  • WithQueryParams (18-21)
internal/utils/mcp_utils.go (1)
internal/utils/utils.go (4)
  • FirstPage (15-15)
  • DefaultPageSize (16-16)
  • PaginatedRequest (31-35)
  • ReadResponseBodyRaw (193-215)
internal/http/http_server_batch_test.go (1)
internal/http/http_server.go (3)
  • HTTPServer (66-77)
  • HTTPServerConfig (50-63)
  • NewHTTPServer (87-144)
internal/config/cli.go (3)
internal/http/http_server.go (2)
  • HTTPServerConfig (50-63)
  • CreateHTTPServerWithMiddleware (224-250)
internal/analytics/analytics.go (3)
  • GetAnalyticArg (232-246)
  • Analytics (69-81)
  • StopAnalytics (403-412)
internal/mcp_handlers/server.go (1)
  • NewServer (21-68)
internal/analytics/analytics_test.go (1)
internal/utils/mcp_utils.go (1)
  • WithAnalytics (83-97)
internal/mcp_handlers/server.go (5)
internal/analytics/analytics.go (2)
  • Analytics (69-81)
  • NewAnalytics (90-131)
internal/middleware/middleware.go (1)
  • QueryParamsMiddleware (11-32)
internal/mcp_handlers/launches.go (1)
  • RegisterLaunchTools (41-56)
internal/mcp_handlers/items.go (1)
  • RegisterTestItemTools (41-54)
internal/promptreader/promptreader.go (1)
  • PromptHandlerPair (16-19)
🔇 Additional comments (9)
internal/utils/mcp_utils.go (2)

37-44: Pagination clamping is safe and avoids int32 overflow

Min/max guards on page and page-size with int32 casts look correct and prevent overflow. Good use of constants.

Also applies to: 46-53, 58-63


103-111: Ignore ReadResponseBody “error-swallowing” warning
ReadResponseBody intentionally returns business errors as a CallToolResultError with a nil Go error, and all callers use httpLambdaWrapper which handles nil errors as expected; no change required.

Likely an incorrect or invalid review comment.

internal/http/http_server.go (1)

431-433: Good hardening: cap request body size

Using MaxBytesReader prevents memory DoS on MCP endpoints. Nice.

internal/mcp_handlers/items.go (1)

69-71: Good adoption of shared utils and analytics

Using utils.SetPaginationOptions, utils.ExtractProject, and utils.WithAnalytics reduces duplication and centralizes behavior. LGTM.

Also applies to: 137-145

internal/mcp_handlers/launches.go (1)

66-68: Shared pagination and analytics wiring looks good

Consistent use of utils.SetPaginationOptions, utils.WithAnalytics, and utils.ExtractProject. Solid refactor.

Also applies to: 112-118, 156-166

internal/security/http_token_middleware.go (1)

15-16: LGTM: centralized context key and validation

Good move to reuse utils.ContextKey and utils.ValidateRPToken; header parsing and logging are safe and minimal.

Also applies to: 47-67, 69-78

internal/middleware/middleware.go (1)

13-17: LGTM: header sanitization and query param merge are sound

Token is trimmed and CR/LF-guarded before setting Authorization. Query params are safely encoded into RawQuery.

Also applies to: 21-31

internal/config/cli.go (2)

318-327: Shutdown sequence is correct.

The graceful shutdown properly uses a timeout context and calls both server.Shutdown and httpServer.MCP.Stop without explicitly stopping analytics (which would cause double-stop). The past issue has been addressed.


335-389: Well-structured application entry point.

The RunApp function properly orchestrates the CLI application by:

  • Reading the MCP mode from environment before flag parsing
  • Conditionally including HTTP-specific flags based on mode
  • Delegating to mode-specific runner functions
  • Returning errors to the caller for proper handling

The mode-based flag selection and error propagation follow Go best practices.

@AleksandrParamonoff AleksandrParamonoff force-pushed the refactoring-detaching-app-to-modules branch from c987c3a to 182a251 Compare October 9, 2025 10:55
Copy link

@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: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
internal/http/http_server.go (1)

463-472: Close the original request body before reusing it.

After wrapping the body with http.MaxBytesReader, io.ReadAll drains but does not close the underlying reader. Reassigning r.Body to a new buffer without closing the original reader leaks the connection and defeats pooling. Store the old body (limitedBody := r.Body), read from it, close it (e.g., defer limitedBody.Close()), and only then wrap the bytes with io.NopCloser.

internal/utils/utils.go (1)

162-170: Return true for every listed media type.

With empty case bodies, only "application/csv" hits the return true; "application/json", "application/xml", etc. fall through and incorrectly report false. Collapse the cases into a single clause (e.g., case "application/json", "application/xml", ...: return true) so all intended text types are recognised.

♻️ Duplicate comments (5)
internal/utils/ctx_utils.go (1)

11-12: Exported string context key risks collisions; prefer unexported struct type.

Past review correctly identified this pattern. Exported string types for context keys can collide across packages. Best practice is an unexported zero-sized struct type with exported key variable.

internal/mcp_handlers/launches.go (1)

279-281: Critical: Schema/runtime type mismatch for launch_id parameter.

The parameter is defined as string via mcp.WithString("launch_id", ...) but line 288 calls request.RequireInt("launch_id"), causing runtime errors when the tool is invoked.

Change to numeric schema:

-			mcp.WithString("launch_id",
+			mcp.WithNumber("launch_id",
 				mcp.Description("Launch ID"),
 			),

Apply the same fix to:

  • toolRunAutoAnalysis (lines 310-313)
  • toolUniqueErrorAnalysis (lines 387-389)
  • toolForceFinishLaunch (lines 431-433)
internal/analytics/analytics_test.go (1)

180-183: Optional: Assert that WithAnalytics increments the metric counter.

The test wraps the handler but doesn't verify that the analytics metric was actually incremented. Consider checking the metrics map after calling the wrapped handler to ensure event tracking occurred.

 	result, err := wrappedHandler(context.Background(), mcp.CallToolRequest{})
 
 	// Verify handler was called
 	assert.NoError(t, err)
 	assert.NotNil(t, result)
 	assert.True(t, handlerCalled, "Original handler should be called")
+
+	// Verify metric was incremented
+	analytics.metricsLock.RLock()
+	if counter, ok := analytics.metrics["test_tool"]; ok && counter != nil {
+		assert.Equal(t, int64(1), *counter)
+	} else {
+		assert.Fail(t, "metric for test_tool not recorded")
+	}
+	analytics.metricsLock.RUnlock()

As per coding guidelines.

internal/middleware/middleware_test.go (1)

19-19: Optional: Check http.NewRequest error for test robustness.

Capturing and asserting the error improves test clarity if the URL or request setup ever fails.

-	req, _ := http.NewRequest("GET", "http://example.com/path?x=9", nil)
+	req, err := http.NewRequest("GET", "http://example.com/path?x=9", nil)
+	assert.NoError(t, err)

As per coding guidelines.

internal/mcp_handlers/server.go (1)

42-50: Analytics init failure should be non-fatal for startup robustness.

Currently, missing/invalid analytics secrets cause NewServer to fail. The past review noted this diverges from HTTP server behavior, which logs and continues. Consider logging a warning and proceeding with analyticsClient = nil.

+	"log/slog"
 	// Initialize analytics (disabled if analyticsOff is true)
 	var analyticsClient *analytics.Analytics
 	if analyticsOn {
-		var err error
-		// Pass RP API token for secure hashing as user identifier
-		analyticsClient, err = analytics.NewAnalytics(userID, analyticsAPISecret, token)
-		if err != nil {
-			return nil, nil, fmt.Errorf("failed to initialize analytics: %w", err)
-		}
+		ac, err := analytics.NewAnalytics(userID, analyticsAPISecret, token)
+		if err != nil {
+			slog.Warn("analytics initialization skipped", "error", err)
+		} else {
+			analyticsClient = ac
+		}
 	}

Based on learnings.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c987c3a and 182a251.

📒 Files selected for processing (18)
  • cmd/reportportal-mcp-server/main.go (2 hunks)
  • internal/analytics/analytics.go (3 hunks)
  • internal/analytics/analytics_test.go (5 hunks)
  • internal/config/cli.go (1 hunks)
  • internal/http/http_server.go (12 hunks)
  • internal/http/http_server_batch_test.go (1 hunks)
  • internal/mcp_handlers/items.go (23 hunks)
  • internal/mcp_handlers/launches.go (12 hunks)
  • internal/mcp_handlers/launches_test.go (2 hunks)
  • internal/mcp_handlers/server.go (4 hunks)
  • internal/middleware/middleware.go (1 hunks)
  • internal/middleware/middleware_test.go (2 hunks)
  • internal/security/http_token_middleware.go (2 hunks)
  • internal/utils/ctx_utils.go (2 hunks)
  • internal/utils/ctx_utils_test.go (1 hunks)
  • internal/utils/mcp_utils.go (1 hunks)
  • internal/utils/utils.go (8 hunks)
  • internal/utils/utils_test.go (7 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.go

📄 CodeRabbit inference engine (README.md)

**/*.go: Run formatter (task fmt / go fmt) to keep all Go source files consistently formatted
Run linter (task lint) to maintain code quality across Go files

Files:

  • internal/mcp_handlers/launches.go
  • internal/mcp_handlers/server.go
  • internal/mcp_handlers/launches_test.go
  • internal/utils/utils_test.go
  • internal/middleware/middleware.go
  • cmd/reportportal-mcp-server/main.go
  • internal/utils/ctx_utils.go
  • internal/http/http_server_batch_test.go
  • internal/mcp_handlers/items.go
  • internal/analytics/analytics_test.go
  • internal/middleware/middleware_test.go
  • internal/utils/ctx_utils_test.go
  • internal/http/http_server.go
  • internal/security/http_token_middleware.go
  • internal/utils/utils.go
  • internal/utils/mcp_utils.go
  • internal/analytics/analytics.go
  • internal/config/cli.go

⚙️ CodeRabbit configuration file

Focus on Go best practices, error handling, concurrency safety, performance, and security. Check for proper resource cleanup and context handling.

Files:

  • internal/mcp_handlers/launches.go
  • internal/mcp_handlers/server.go
  • internal/mcp_handlers/launches_test.go
  • internal/utils/utils_test.go
  • internal/middleware/middleware.go
  • cmd/reportportal-mcp-server/main.go
  • internal/utils/ctx_utils.go
  • internal/http/http_server_batch_test.go
  • internal/mcp_handlers/items.go
  • internal/analytics/analytics_test.go
  • internal/middleware/middleware_test.go
  • internal/utils/ctx_utils_test.go
  • internal/http/http_server.go
  • internal/security/http_token_middleware.go
  • internal/utils/utils.go
  • internal/utils/mcp_utils.go
  • internal/analytics/analytics.go
  • internal/config/cli.go
**/*_test.go

⚙️ CodeRabbit configuration file

Ensure comprehensive test coverage, proper test isolation, meaningful assertions, and good test naming conventions.

Files:

  • internal/mcp_handlers/launches_test.go
  • internal/utils/utils_test.go
  • internal/http/http_server_batch_test.go
  • internal/analytics/analytics_test.go
  • internal/middleware/middleware_test.go
  • internal/utils/ctx_utils_test.go
cmd/reportportal-mcp-server/**/*.go

📄 CodeRabbit inference engine (README.md)

Keep the main server entrypoint under cmd/reportportal-mcp-server and build from that path

Files:

  • cmd/reportportal-mcp-server/main.go
cmd/**

⚙️ CodeRabbit configuration file

Review CLI interface design, configuration handling, and application startup logic for robustness and usability.

Files:

  • cmd/reportportal-mcp-server/main.go
🧠 Learnings (1)
📚 Learning: 2025-09-17T11:00:40.261Z
Learnt from: CR
PR: reportportal/reportportal-mcp-server#0
File: README.md:0-0
Timestamp: 2025-09-17T11:00:40.261Z
Learning: Applies to cmd/reportportal-mcp-server/**/*.go : Keep the main server entrypoint under cmd/reportportal-mcp-server and build from that path

Applied to files:

  • cmd/reportportal-mcp-server/main.go
🧬 Code graph analysis (13)
internal/mcp_handlers/launches.go (3)
internal/utils/mcp_utils.go (6)
  • NewProjectParameter (65-71)
  • SetPaginationOptions (14-29)
  • WithAnalytics (83-97)
  • ExtractProject (73-75)
  • ApplyPaginationOptions (32-63)
  • ReadResponseBody (102-111)
internal/utils/utils.go (4)
  • DefaultSortingForLaunches (17-17)
  • ProcessStartTimeFilter (99-121)
  • ProcessAttributeKeys (124-152)
  • ExtractResponseError (37-62)
internal/utils/ctx_utils.go (1)
  • WithQueryParams (18-21)
internal/mcp_handlers/server.go (5)
internal/analytics/analytics.go (2)
  • Analytics (69-81)
  • NewAnalytics (90-131)
internal/middleware/middleware.go (1)
  • QueryParamsMiddleware (11-32)
internal/mcp_handlers/launches.go (1)
  • RegisterLaunchTools (41-56)
internal/mcp_handlers/items.go (1)
  • RegisterTestItemTools (41-54)
internal/promptreader/promptreader.go (1)
  • PromptHandlerPair (16-19)
internal/utils/utils_test.go (1)
internal/utils/utils.go (1)
  • ProcessAttributeKeys (124-152)
internal/middleware/middleware.go (2)
internal/security/http_token_middleware.go (1)
  • GetTokenFromContext (75-78)
internal/utils/ctx_utils.go (1)
  • QueryParamsFromContext (23-27)
cmd/reportportal-mcp-server/main.go (1)
internal/config/cli.go (2)
  • AppVersion (25-29)
  • RunApp (336-389)
internal/http/http_server_batch_test.go (1)
internal/http/http_server.go (3)
  • HTTPServer (66-77)
  • HTTPServerConfig (50-63)
  • NewHTTPServer (87-144)
internal/mcp_handlers/items.go (4)
internal/analytics/analytics.go (1)
  • Analytics (69-81)
internal/utils/mcp_utils.go (6)
  • NewProjectParameter (65-71)
  • SetPaginationOptions (14-29)
  • WithAnalytics (83-97)
  • ExtractProject (73-75)
  • ApplyPaginationOptions (32-63)
  • ReadResponseBody (102-111)
internal/utils/utils.go (16)
  • DefaultSortingForItems (18-18)
  • DefaultProviderType (21-21)
  • DefaultFilterEqHasStats (23-23)
  • DefaultFilterEqHasChildren (22-22)
  • DefaultFilterInType (24-24)
  • ProcessStartTimeFilter (99-121)
  • ProcessAttributeKeys (124-152)
  • ExtractResponseError (37-62)
  • ReadResponseBodyRaw (193-215)
  • IsTextContent (154-174)
  • FirstPage (15-15)
  • DefaultPageSize (16-16)
  • DefaultSortingForLogs (20-20)
  • DefaultItemLogLevel (26-26)
  • DefaultSortingForSuites (19-19)
  • DefaultFilterInTypeSuites (25-25)
internal/utils/ctx_utils.go (1)
  • WithQueryParams (18-21)
internal/analytics/analytics_test.go (1)
internal/utils/mcp_utils.go (1)
  • WithAnalytics (83-97)
internal/middleware/middleware_test.go (1)
internal/utils/ctx_utils.go (1)
  • WithQueryParams (18-21)
internal/http/http_server.go (7)
internal/analytics/analytics.go (3)
  • Analytics (69-81)
  • NewAnalytics (90-131)
  • BatchSendInterval (29-29)
internal/utils/ctx_utils.go (1)
  • ValidateRPToken (31-44)
internal/middleware/middleware.go (1)
  • QueryParamsMiddleware (11-32)
internal/mcp_handlers/launches.go (1)
  • RegisterLaunchTools (41-56)
internal/mcp_handlers/items.go (1)
  • RegisterTestItemTools (41-54)
internal/mcp_handlers/server.go (2)
  • ReadPrompts (71-93)
  • PromptFiles (19-19)
internal/security/http_token_middleware.go (1)
  • HTTPTokenMiddleware (19-42)
internal/security/http_token_middleware.go (1)
internal/utils/ctx_utils.go (2)
  • ContextKey (12-12)
  • ValidateRPToken (31-44)
internal/utils/mcp_utils.go (1)
internal/utils/utils.go (4)
  • FirstPage (15-15)
  • DefaultPageSize (16-16)
  • PaginatedRequest (31-35)
  • ReadResponseBodyRaw (193-215)
internal/config/cli.go (3)
internal/http/http_server.go (2)
  • HTTPServerConfig (50-63)
  • CreateHTTPServerWithMiddleware (224-250)
internal/analytics/analytics.go (3)
  • GetAnalyticArg (232-246)
  • Analytics (69-81)
  • StopAnalytics (403-412)
internal/mcp_handlers/server.go (1)
  • NewServer (21-68)

@AleksandrParamonoff AleksandrParamonoff force-pushed the refactoring-detaching-app-to-modules branch from 182a251 to 4b82b68 Compare October 9, 2025 12:07
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

Copilot reviewed 18 out of 19 changed files in this pull request and generated 7 comments.

Comments suppressed due to low confidence (1)

internal/http/http_server.go:1

  • [nitpick] This line appears to be inconsistent with the variable name context. The variable is being assigned but the surrounding code suggests this might be in the wrong location or the assignment might be incorrect.
package http

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

return mcp.NewTool("get_last_launch_by_name",
// Tool metadata
mcp.WithDescription("Get list of last ReportPortal launches"),
mcp.WithDescription("Get last launch by name from ReportPortal"),
Copy link

Copilot AI Oct 10, 2025

Choose a reason for hiding this comment

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

[nitpick] The description should clarify what 'last' means in this context. Consider changing to 'Get most recent launch by name from ReportPortal' for better clarity.

Suggested change
mcp.WithDescription("Get last launch by name from ReportPortal"),
mcp.WithDescription("Get most recent launch by name from ReportPortal"),

Copilot uses AI. Check for mistakes.
mcp.WithDescription("Delete ReportPortal launch"),
lr.projectParameter,
mcp.WithString("launch_id", // Parameter for specifying the launch name
mcp.WithNumber("launch_id", // Parameter for specifying the launch name
Copy link

Copilot AI Oct 10, 2025

Choose a reason for hiding this comment

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

The comment says 'launch name' but the parameter is 'launch_id' which expects a number. The comment should say 'launch ID' to match the parameter type and name.

Suggested change
mcp.WithNumber("launch_id", // Parameter for specifying the launch name
mcp.WithNumber("launch_id", // Parameter for specifying the launch ID

Copilot uses AI. Check for mistakes.
mcp.WithDescription("Run auto analysis on ReportPortal launch"),
lr.projectParameter,
mcp.WithString("launch_id", // Parameter for specifying the launch name
mcp.WithNumber("launch_id", // Parameter for specifying the launch name
Copy link

Copilot AI Oct 10, 2025

Choose a reason for hiding this comment

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

The comment says 'launch name' but the parameter is 'launch_id' which expects a number. The comment should say 'launch ID' to match the parameter type and name.

Suggested change
mcp.WithNumber("launch_id", // Parameter for specifying the launch name
mcp.WithNumber("launch_id", // Parameter for specifying the launch ID

Copilot uses AI. Check for mistakes.
mcp.WithDescription("Run unique error analysis on ReportPortal launch"),
lr.projectParameter,
mcp.WithString("launch_id", // Parameter for specifying the launch name
mcp.WithNumber("launch_id", // Parameter for specifying the launch name
Copy link

Copilot AI Oct 10, 2025

Choose a reason for hiding this comment

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

The comment says 'launch name' but the parameter is 'launch_id' which expects a number. The comment should say 'launch ID' to match the parameter type and name.

Suggested change
mcp.WithNumber("launch_id", // Parameter for specifying the launch name
mcp.WithNumber("launch_id", // Parameter for specifying the launch ID

Copilot uses AI. Check for mistakes.
mcp.WithDescription("Force-finish ReportPortal launch"),
lr.projectParameter,
mcp.WithString("launch_id", // Parameter for specifying the launch name
mcp.WithNumber("launch_id", // Parameter for specifying the launch name
Copy link

Copilot AI Oct 10, 2025

Choose a reason for hiding this comment

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

The comment says 'launch name' but the parameter is 'launch_id' which expects a number. The comment should say 'launch ID' to match the parameter type and name.

Suggested change
mcp.WithNumber("launch_id", // Parameter for specifying the launch name
mcp.WithNumber("launch_id", // Parameter for specifying the launch ID

Copilot uses AI. Check for mistakes.
}

// toolGetTestItemLogsByFilter creates a tool to get test items logs for a specific launch.
// ToolGetTestItemLogsByFilter creates a tool to get test items logs for a specific launch.
Copy link

Copilot AI Oct 10, 2025

Choose a reason for hiding this comment

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

The function name in the comment should match the actual function name. Change 'ToolGetTestItemLogsByFilter' to 'toolGetTestItemLogsByFilter' to match the actual function name.

Suggested change
// ToolGetTestItemLogsByFilter creates a tool to get test items logs for a specific launch.
// toolGetTestItemLogsByFilter creates a tool to get test items logs for a specific launch.

Copilot uses AI. Check for mistakes.
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