Skip to content

refactor: restructure MCP server, upgrade to v2 search, add 37 tests (OPE-94, OPE-91)#263

Merged
DevanshuNEU merged 4 commits into
mainfrom
refactor/mcp-server-restructure
Feb 25, 2026
Merged

refactor: restructure MCP server, upgrade to v2 search, add 37 tests (OPE-94, OPE-91)#263
DevanshuNEU merged 4 commits into
mainfrom
refactor/mcp-server-restructure

Conversation

@DevanshuNEU

@DevanshuNEU DevanshuNEU commented Feb 25, 2026

Copy link
Copy Markdown
Collaborator

Problem

MCP server was a 389-line monolith (server.py) with tool definitions, API calls, response formatting, config, and error handling all in one file. OPE-91 audit flagged: v1 search API (deprecated), zero tests, error messages leaking internal details, emoji in output.

Fix

Module restructuring (OPE-94)

  • server.py: 389 -> 54 lines (entry point only)
  • config.py: Environment config (16 lines)
  • api_client.py: Persistent HTTP client with lazy init
  • tools.py: MCP tool schema definitions
  • handlers.py: Tool dispatch with centralized error handling
  • formatters.py: Pure functions converting API responses to markdown

Search v2 upgrade (OPE-91)

  • Switched from /search to /search/v2
  • Maps max_results (tool schema) to top_k (v2 API)
  • Adds use_reranking: true for better result quality
  • Formatter supports v2 fields: qualified_name, signature, match_reason
  • Backward compat with v1 response shape maintained

Security hardening (OPE-91)

  • _safe_error_message() prevents leaking internal URLs, stack traces
  • HTTP status, timeout, connect errors all get sanitized messages
  • Generic errors get opaque message with tool name and repo ID only

Test coverage -- 37 tests

  • test_config.py (4): Config format validation
  • test_formatters.py (19): All 7 formatters, v1/v2 compat, emoji checks
  • test_handlers.py (11): v2 dispatch, max_results->top_k mapping, error sanitization
  • test_tools.py (7): Schema validation, required fields, MCP protocol compliance

Cleanup

  • Removed emoji from output (CLAUDE.md compliance)
  • Version bump 0.3.0 -> 0.4.0
  • Added pytest + pytest-asyncio to requirements

OPE-91 Audit Status

Finding Status
.env committed with API key Not tracked (.gitignore works)
Emojis in output Removed
Error messages leak details Fixed
config.py copy-pasted Cleaned to 16 lines
Uses v1 search Upgraded to v2
No tests 37 tests added
Auth hardcoded Deferred to OPE-58

Test

37 passed in 0.60s

Closes OPE-94, OPE-91

Summary by CodeRabbit

  • New Features

    • Backend API integration with a persistent client, dynamic tool registry (seven tools), and unified tool dispatcher that returns Markdown-formatted results and ensures clean shutdown.
  • Tests

    • Added comprehensive tests for config, formatters, handlers/dispatch, and tool schemas.
  • Chores

    • Environment-driven configuration, example env placeholder, .gitignore, test deps, and test runner config.

…E-94, OPE-91)

Split 389-line server.py into focused modules:
- config.py (16 lines) -- env config, no decorative headers
- api_client.py (59 lines) -- persistent httpx client, reused across calls
- tools.py (146 lines) -- tool schema definitions
- formatters.py (175 lines) -- response -> markdown, independently testable
- handlers.py (104 lines) -- dispatch + API calls + safe error messages
- server.py (54 lines) -- bootstrap only

Security fixes:
- Remove hardcoded 'dev-secret-key' default (empty string, fails loud)
- Remove 'dev-secret-key' from .env.example
- Add local .gitignore for venv/, __pycache__/, .env
- Safe error messages: no httpx internals leaked, includes tool name + repo_id

CLAUDE.md compliance:
- Remove emoji from cached result indicators (was lightning bolt)
- All files under 200 lines

Note: search endpoint is already correct (/api/v1/search uses query expansion
+ reranking). The 'uses v1 search' finding in OPE-91 is stale -- there is
only one search endpoint and it already uses the latest engine.
- handlers: switch from /search to /search/v2, map max_results to top_k
- formatters: support v2 fields (qualified_name, signature, match_reason)
- formatters: maintain backward compat with v1 response shape
- tests: 37 tests covering config, formatters, handlers, tool schemas
- tests: verify no emoji, error message sanitization, dispatch logic
- config: bump version 0.3.0 -> 0.4.0
- requirements: add pytest, pytest-asyncio as test dependencies
@vercel

vercel Bot commented Feb 25, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
opencodeintel Ignored Ignored Preview Feb 25, 2026 5:19pm

@coderabbitai

coderabbitai Bot commented Feb 25, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@DevanshuNEU has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 22 minutes and 42 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between dc70c70 and d1e3931.

📒 Files selected for processing (1)
  • mcp-server/api_client.py
📝 Walkthrough

Walkthrough

Refactors MCP server into modular components: environment-driven config, a persistent async API client, tool schemas, formatters, a handler dispatcher, updated server orchestration, and extensive tests and test config; adds .gitignore and test dependencies. No external behavior beyond these internal reorganizations is introduced.

Changes

Cohort / File(s) Summary
Environment & Repo Setup
mcp-server/.env.example, mcp-server/.gitignore
Updated env placeholder API_KEY and added Python/venv/.env ignore rules.
Configuration
mcp-server/config.py
Refactored to load dotenv and expose BACKEND_BASE_URL, BACKEND_API_URL, API_KEY, SERVER_NAME, SERVER_VERSION, API_PREFIX/API_VERSION derived from env.
HTTP Client
mcp-server/api_client.py
New singleton async HTTP client with lazy init, get_client(), api_get(), api_post(), header handling (raises if API_KEY missing), and close_client().
Tool Registry
mcp-server/tools.py
New get_tool_schemas() returning JSON-Schema-driven tool definitions for seven MCP tools.
Formatters
mcp-server/formatters.py
New pure formatter functions (seven) converting API response dicts to Markdown strings, handling versioned shapes and optional fields.
Handler Dispatcher
mcp-server/handlers.py
New call_tool dispatcher mapping tool names to async handlers, invoking api_client, formatting results, and centralized error handling for HTTP/timeouts/connection/ValueError/generic errors.
Server Orchestration
mcp-server/server.py
Main reworked to use get_tool_schemas(), delegate execution to call_tool(), use config-driven server_name/version, ensure close_client() on shutdown, and run via asyncio.run(main()). Signature for handle_call_tool simplified to accept `dict
Testing & Test Config
mcp-server/pyproject.toml, mcp-server/requirements.txt, mcp-server/tests/*, mcp-server/tests/conftest.py
Added pytest/pytest-asyncio deps and pytest.ini options; comprehensive tests added for config, tools, formatters, handlers; conftest adjusts sys.path for imports.

Sequence Diagram(s)

sequenceDiagram
    actor LLM as LLM/Client
    participant Server as MCP Server
    participant Tools as Tools Module
    participant Handlers as Handlers Module
    participant APIClient as API Client
    participant Backend as Backend API

    LLM->>Server: request tool list / call_tool(name, args)
    Server->>Tools: get_tool_schemas()
    Tools-->>Server: tool schemas
    Server->>Handlers: call_tool(name, arguments)
    Handlers->>Handlers: resolve handler
    Handlers->>APIClient: api_get/post(path, payload)
    APIClient->>APIClient: ensure singleton client (get_client) with auth header
    APIClient->>Backend: HTTP request (with Bearer token)
    Backend-->>APIClient: JSON response
    APIClient-->>Handlers: parsed JSON
    Handlers->>Handlers: format_* -> Markdown
    Handlers-->>Server: TextContent result
    Server-->>LLM: return result
    Note over Server,APIClient: main() finally -> close_client()
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Poem

🐇 I hopped through code and stitched the parts,
Tools, handlers, client tied like arts.
Config from env, formats made neat,
Tests in place — no missing beat.
A little rabbit clap for this refactor feat!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 45.07% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately captures the main refactoring effort (restructured MCP server from monolith into focused modules), key feature addition (upgraded to v2 search), and comprehensive testing (37 new tests). It is specific, concise, and directly reflects the primary changes.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch refactor/mcp-server-restructure

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 7

🧹 Nitpick comments (6)
mcp-server/.env.example (1)

2-3: Consider reordering keys to satisfy dotenv-linter.

This is only lint hygiene, but reordering API_KEY before BACKEND_API_URL will clear the reported UnorderedKey warning.

♻️ Suggested reorder
-# Backend API Configuration
-BACKEND_API_URL=http://localhost:8000
-API_KEY=your-api-key-here
+# Backend API Configuration
+API_KEY=your-api-key-here
+BACKEND_API_URL=http://localhost:8000
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mcp-server/.env.example` around lines 2 - 3, The keys in the .env example are
out of alphabetical order causing a dotenv-linter UnorderedKey warning; reorder
the environment variables so API_KEY comes before BACKEND_API_URL (i.e., place
the "API_KEY" entry above the "BACKEND_API_URL" entry) and save the updated
.env.example to satisfy the linter.
mcp-server/tests/test_tools.py (1)

48-53: Add a contract test for max_results constraints.

Given search_code exposes max_results, add assertions for type/range so schema regressions are caught early.

✅ Suggested test extension
     def test_search_requires_query_and_repo(self):
         schemas = {t.name: t for t in get_tool_schemas()}
         search = schemas["search_code"]
         assert "query" in search.inputSchema["required"]
         assert "repo_id" in search.inputSchema["required"]
+        max_results = search.inputSchema["properties"]["max_results"]
+        assert max_results["type"] == "integer"
+        assert max_results["minimum"] == 1
+        assert max_results["maximum"] == 100
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mcp-server/tests/test_tools.py` around lines 48 - 53, Extend
test_search_requires_query_and_repo to also validate the search_code max_results
schema: fetch the property at search.inputSchema["properties"]["max_results"]
and assert it declares a numeric/integer type (e.g., "integer" or "number"),
assert it has a "minimum" constraint and that minimum >= 1, and assert it has a
"maximum" constraint and that maximum > minimum so the range is explicit and
sane; use the existing variables schemas and search to locate the schema and
perform these assertions to guard against regressions.
mcp-server/api_client.py (1)

26-35: Lazy singleton is fine for single-event-loop usage, but note the potential double-init race.

If two coroutines concurrently call get_client() when _client is None, both could pass the None check and create separate clients. In a stdio-based MCP server this is practically impossible (sequential dispatch), but if this module is ever reused in a concurrent context, an asyncio.Lock would be needed.

No action required for now — just flagging for awareness.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mcp-server/api_client.py` around lines 26 - 35, get_client currently does an
unsafe lazy singleton check on the module-level _client which can race if two
coroutines call get_client concurrently; to fix, add an asyncio.Lock (e.g.,
_client_lock) and acquire it inside get_client before checking/creating _client
so only one coroutine can create the httpx.AsyncClient (reference get_client,
_client, and introduce _client_lock). Ensure the lock is created at module
import time and held only for the create/check section to avoid blocking other
awaits.
mcp-server/tests/test_formatters.py (2)

10-11: Consider using a conftest.py to manage sys.path instead of repeating in every test file.

All three test files duplicate the same sys.path.insert(...) pattern. A single conftest.py in the tests/ directory (or a pyproject.toml / pytest.ini pythonpath setting) would eliminate this repetition and be the idiomatic pytest approach.

Example tests/conftest.py:

import sys, os
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

Or in pyproject.toml:

[tool.pytest.ini_options]
pythonpath = ["mcp-server"]
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mcp-server/tests/test_formatters.py` around lines 10 - 11, Move the repeated
sys.path manipulation out of individual tests and into a single
tests/conftest.py: remove the sys.path.insert(0,
os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) line from files
like test_formatters.py and create tests/conftest.py that performs that same
sys.path.insert call so pytest applies it globally; alternatively document
adding a pythonpath entry in pyproject.toml/pytest.ini (e.g., pythonpath =
["mcp-server"]) if you prefer configuration over a conftest.py.

72-80: Emoji check only covers the lightning bolt; consider a broader assertion if enforcing a no-emoji policy.

The test only asserts \u26a1 is absent, which is the specific emoji from the OPE-91 audit. If the no-emoji policy is meant to be general, a regex-based check against Unicode emoji ranges would be more resilient against future regressions.

That said, a targeted check for the known offender is a reasonable pragmatic choice for now.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mcp-server/tests/test_formatters.py` around lines 72 - 80, The test
test_no_emoji_in_output currently only checks for the single lightning bolt
("\u26a1") after calling format_search_results, so either broaden the assertion
to detect any emoji or make intent explicit; update the test_no_emoji_in_output
to use a regex that matches Unicode emoji ranges (or use a utility like the
`emoji` package) to assert no emojis appear in output, or add a clarifying
comment that the check is intentionally scoped to "\u26a1" if you want to keep
it targeted.
mcp-server/config.py (1)

11-12: Env var naming is semantically inconsistent but well-documented.

The environment variable BACKEND_API_URL is named to suggest a full API URL, but is consistently documented and used as a base URL (without the /api/v1 prefix). The code then appends the prefix to create the Python constant BACKEND_API_URL. While this naming creates semantic confusion, all documentation (README, deployment docs, docker-compose examples) consistently shows the correct pattern—setting the env var to a base URL like http://localhost:8000.

Consider renaming the env var to BACKEND_BASE_URL for clarity, but this is not urgent since the current naming is consistently documented across the codebase.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mcp-server/config.py` around lines 11 - 12, The env var naming is confusing:
change the os.getenv key so the module-level constant BACKEND_BASE_URL is
derived from "BACKEND_BASE_URL" (not "BACKEND_API_URL"), and keep
BACKEND_API_URL defined as f"{BACKEND_BASE_URL}{API_PREFIX}"; for compatibility,
read the new "BACKEND_BASE_URL" first and fall back to the old "BACKEND_API_URL"
env var if the new one is unset, so both names work during rollout (update the
os.getenv call that currently sets BACKEND_BASE_URL and ensure the relationship
between BACKEND_BASE_URL, BACKEND_API_URL, and API_PREFIX remains intact).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@mcp-server/api_client.py`:
- Around line 17-23: The docstring for _get_headers() is incorrect: it says
"Warns if no API key is configured" but the function raises ValueError when
API_KEY is missing; update the docstring to accurately describe behavior (e.g.,
"Raise ValueError if no API_KEY is configured") and mention that it returns the
Authorization header {"Authorization": f"Bearer {API_KEY}"} on success,
referencing the API_KEY symbol and the _get_headers function so callers know the
function raises rather than warns.

In `@mcp-server/formatters.py`:
- Around line 99-100: The formatter currently uses direct dict indexing (e.g.,
f['id'], f['imports']) inside the sorted/loop blocks (see the "for f in
sorted(high_import, ...)" loop) which can raise KeyError on partial API
payloads; update these accesses to use .get with sensible defaults (e.g.,
f.get('id', '<unknown>'), f.get('imports', 0)) and coerce values for formatting
as needed so the loop still produces degraded output instead of crashing; apply
the same defensive change to the other similar loops/uses referenced in the diff
(the other occurrences that index fields on the loop variable, such as the
accesses at the later blocks around lines noted in the review).
- Line 23: Guard against None/non-numeric score before multiplying: replace the
direct expression res.get("score", 0) * 100 with parsing logic that reads
score_raw = res.get("score"), then try to coerce to float (e.g. score =
float(score_raw) * 100) inside a try/except catching TypeError/ValueError and
falling back to score = 0; ensure you keep the variable name score for
downstream code and apply this change where res.get("score", 0) is used in
formatters.py so non-numeric or None values won't raise.

In `@mcp-server/handlers.py`:
- Around line 91-93: The current branch returns raw str(error) for ValueError
which can leak internals; in the function handling tool errors replace returning
str(error) with a sanitized message (e.g., a generic "Invalid input" or "Tool
input error" including tool_name and repo_id) and ensure the original exception
is recorded to internal logs (use the module's logger or logger.exception with
the error and context) so only safe text is returned to callers while full
details remain in server logs; update the conditional that checks
isinstance(error, ValueError) to return the sanitized string and add a logging
call that records error, tool_name, and repo_id.
- Around line 24-33: In _handle_search validate args.get("max_results") before
assigning to payload["top_k"]: ensure the value (from args["max_results"]) is
present or defaulted, is an integer, and is within an acceptable range (e.g.,
clamp to min 1 and a sensible max like 100) or raise a clear error if invalid;
then set "top_k" to the sanitized value when building the payload for
api_post("/search/v2") and keep use_reranking and format_search_results(result)
unchanged.

In `@mcp-server/tests/test_handlers.py`:
- Around line 70-75: The test test_none_arguments_handled is making a real
network call because api_get isn't mocked; instead mock api_get (or the
higher-level function that performs HTTP) when invoking
call_tool("list_repositories", None) so the test only verifies that call_tool
handles None args without crashing. Locate the test test_none_arguments_handled
and patch/make a fixture to stub api_get to return a predictable value (e.g., a
dummy response or empty list) or raise the same ValueError path, ensuring
call_tool and _get_headers behavior is exercised in isolation and no real HTTP
request occurs.

In `@mcp-server/tools.py`:
- Around line 35-39: The schema entry for "max_results" is currently unbounded
and can accept 0, negatives, or huge values; update the JSON/schema definition
for "max_results" (the property named "max_results" used when mapping to top_k)
to include "minimum": 1 and a reasonable "maximum" (e.g., 100) to prevent
invalid or expensive searches, keep "type": "integer" and "default": 10, and add
any runtime validation in the code path that maps max_results -> top_k to clamp
or reject values outside [1,100].

---

Nitpick comments:
In `@mcp-server/.env.example`:
- Around line 2-3: The keys in the .env example are out of alphabetical order
causing a dotenv-linter UnorderedKey warning; reorder the environment variables
so API_KEY comes before BACKEND_API_URL (i.e., place the "API_KEY" entry above
the "BACKEND_API_URL" entry) and save the updated .env.example to satisfy the
linter.

In `@mcp-server/api_client.py`:
- Around line 26-35: get_client currently does an unsafe lazy singleton check on
the module-level _client which can race if two coroutines call get_client
concurrently; to fix, add an asyncio.Lock (e.g., _client_lock) and acquire it
inside get_client before checking/creating _client so only one coroutine can
create the httpx.AsyncClient (reference get_client, _client, and introduce
_client_lock). Ensure the lock is created at module import time and held only
for the create/check section to avoid blocking other awaits.

In `@mcp-server/config.py`:
- Around line 11-12: The env var naming is confusing: change the os.getenv key
so the module-level constant BACKEND_BASE_URL is derived from "BACKEND_BASE_URL"
(not "BACKEND_API_URL"), and keep BACKEND_API_URL defined as
f"{BACKEND_BASE_URL}{API_PREFIX}"; for compatibility, read the new
"BACKEND_BASE_URL" first and fall back to the old "BACKEND_API_URL" env var if
the new one is unset, so both names work during rollout (update the os.getenv
call that currently sets BACKEND_BASE_URL and ensure the relationship between
BACKEND_BASE_URL, BACKEND_API_URL, and API_PREFIX remains intact).

In `@mcp-server/tests/test_formatters.py`:
- Around line 10-11: Move the repeated sys.path manipulation out of individual
tests and into a single tests/conftest.py: remove the sys.path.insert(0,
os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) line from files
like test_formatters.py and create tests/conftest.py that performs that same
sys.path.insert call so pytest applies it globally; alternatively document
adding a pythonpath entry in pyproject.toml/pytest.ini (e.g., pythonpath =
["mcp-server"]) if you prefer configuration over a conftest.py.
- Around line 72-80: The test test_no_emoji_in_output currently only checks for
the single lightning bolt ("\u26a1") after calling format_search_results, so
either broaden the assertion to detect any emoji or make intent explicit; update
the test_no_emoji_in_output to use a regex that matches Unicode emoji ranges (or
use a utility like the `emoji` package) to assert no emojis appear in output, or
add a clarifying comment that the check is intentionally scoped to "\u26a1" if
you want to keep it targeted.

In `@mcp-server/tests/test_tools.py`:
- Around line 48-53: Extend test_search_requires_query_and_repo to also validate
the search_code max_results schema: fetch the property at
search.inputSchema["properties"]["max_results"] and assert it declares a
numeric/integer type (e.g., "integer" or "number"), assert it has a "minimum"
constraint and that minimum >= 1, and assert it has a "maximum" constraint and
that maximum > minimum so the range is explicit and sane; use the existing
variables schemas and search to locate the schema and perform these assertions
to guard against regressions.

ℹ️ Review info

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1de8893 and dfe732d.

📒 Files selected for processing (15)
  • mcp-server/.env.example
  • mcp-server/.gitignore
  • mcp-server/api_client.py
  • mcp-server/config.py
  • mcp-server/formatters.py
  • mcp-server/handlers.py
  • mcp-server/pyproject.toml
  • mcp-server/requirements.txt
  • mcp-server/server.py
  • mcp-server/tests/__init__.py
  • mcp-server/tests/test_config.py
  • mcp-server/tests/test_formatters.py
  • mcp-server/tests/test_handlers.py
  • mcp-server/tests/test_tools.py
  • mcp-server/tools.py

Comment thread mcp-server/api_client.py
Comment thread mcp-server/formatters.py Outdated
Comment thread mcp-server/formatters.py Outdated
Comment thread mcp-server/handlers.py
Comment thread mcp-server/handlers.py
Comment thread mcp-server/tests/test_handlers.py
Comment thread mcp-server/tools.py
…or sanitization

- api_client: fix docstring (warns -> raises), add asyncio.Lock for safe lazy init
- formatters: guard against None/non-numeric score, use .get() for all dict access
- handlers: sanitize ValueError (no longer leaks internals), log errors server-side
- handlers: add _clamp_max_results to validate and bound top_k to [1, 100]
- tools: add minimum/maximum constraints to max_results schema
- tests: mock api_get in test_none_arguments_handled (no real HTTP)
- tests: add 8 new tests (score=None, clamp bounds, ValueError sanitized, schema bounds)
- tests: extract sys.path to conftest.py, remove from all test files
- .env.example: alphabetical key ordering (dotenv-linter compliance)

Skipped: N3 (env var rename -- deployment concern), N5 (emoji regex -- overkill)

45 tests pass.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
mcp-server/tests/test_formatters.py (1)

76-85: Tighten the no-emoji assertions to match the stated policy.

These tests currently block only one code point (). If the requirement is truly “no emojis anywhere,” consider a shared helper that checks a broader emoji range so regressions don’t slip through.

Also applies to: 197-199

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mcp-server/tests/test_formatters.py` around lines 76 - 85, The test
test_no_emoji_in_output currently only checks for the single code point U+26A1;
update it to assert no emojis anywhere by using a shared helper (e.g.,
assert_no_emojis_in_string) that scans the output of format_search_results for
emoji code point ranges or via a robust emoji regex and fails if any emoji is
found; add/update this helper in the test module and replace the lone assert
"\u26a1" not in output with a call to that helper, and make the same change for
the other test(s) referenced at lines 197-199.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@mcp-server/api_client.py`:
- Around line 61-66: get_client() guards _client with _client_lock but
close_client() manipulates _client without that lock, creating a race; fix by
acquiring the same _client_lock in close_client(), atomically removing/nulling
_client (e.g. copy _client to a local var, set module _client = None) while
holding the lock, then release the lock and await local.aclose() if the local
var exists (so closing happens outside the lock to avoid blocking). Ensure you
reference and use the existing _client_lock, _client, get_client(), and
close_client() symbols.

---

Nitpick comments:
In `@mcp-server/tests/test_formatters.py`:
- Around line 76-85: The test test_no_emoji_in_output currently only checks for
the single code point U+26A1; update it to assert no emojis anywhere by using a
shared helper (e.g., assert_no_emojis_in_string) that scans the output of
format_search_results for emoji code point ranges or via a robust emoji regex
and fails if any emoji is found; add/update this helper in the test module and
replace the lone assert "\u26a1" not in output with a call to that helper, and
make the same change for the other test(s) referenced at lines 197-199.

ℹ️ Review info

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dfe732d and dc70c70.

📒 Files selected for processing (10)
  • mcp-server/.env.example
  • mcp-server/api_client.py
  • mcp-server/formatters.py
  • mcp-server/handlers.py
  • mcp-server/tests/conftest.py
  • mcp-server/tests/test_config.py
  • mcp-server/tests/test_formatters.py
  • mcp-server/tests/test_handlers.py
  • mcp-server/tests/test_tools.py
  • mcp-server/tools.py
🚧 Files skipped from review as they are similar to previous changes (5)
  • mcp-server/handlers.py
  • mcp-server/formatters.py
  • mcp-server/tests/test_config.py
  • mcp-server/tests/test_handlers.py
  • mcp-server/tests/test_tools.py

Comment thread mcp-server/api_client.py
@DevanshuNEU

Copy link
Copy Markdown
Collaborator Author

@CodeRabbit review

@coderabbitai

coderabbitai Bot commented Feb 25, 2026

Copy link
Copy Markdown
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@DevanshuNEU DevanshuNEU merged commit d6563d1 into main Feb 25, 2026
11 checks passed
@DevanshuNEU DevanshuNEU deleted the refactor/mcp-server-restructure branch June 19, 2026 21:29
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