All integrations use the same underlying protocol:
async def query_mcp_server( self, server_url: str, tool_name: str, args: dict, auth_token: str | None = None,) -> list[dict] | dict: """Generic JSON-RPC 2.0 MCP tool call over HTTP.""" payload = { "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": {"name": tool_name, "arguments": args}, } headers = {"Content-Type": "application/json"} if auth_token: headers["Authorization"] = f"Bearer {auth_token}" async with httpx.AsyncClient(timeout=10.0) as client: response = await client.post(f"{server_url}/", json=payload, headers=headers) response.raise_for_status() data = response.json() # Unwrap MCP content array result = data.get("result", data) if isinstance(result, dict): content = result.get("content", result) if isinstance(content, list): items = [] for item in content: if item.get("type") == "text": parsed = json.loads(item["text"]) if isinstance(parsed, list): items.extend(parsed) else: items.append(parsed) return items return []
Why 10-second timeout?
MCP context is best-effort. A slow external server should not block the PR review from completing. If a call times out, Nectr logs a warning and continues without that context.
Called by: ReviewToolExecutor (get_linked_issues tool)
async def get_linear_issues(self, team_id: str, query: str) -> list[dict]: """Pull issues / tasks from the Linear MCP server matching *query*. Args: team_id: Linear team identifier (e.g., "ENG"). query: Free-text search query (topic, feature area, keyword). Returns: List of issue dicts: {id, title, state, url, description}. Empty list if Linear MCP is not configured or the call fails. """ if not settings.LINEAR_MCP_URL: logger.info("LINEAR_MCP_URL not configured — skipping Linear issue fetch") return [] return await self.query_mcp_server( server_url=settings.LINEAR_MCP_URL, tool_name="search_issues", args={"team_id": team_id, "query": query}, auth_token=settings.LINEAR_API_KEY, )
Example Response
[ { "id": "ENG-123", "title": "Add JWT token refresh endpoint", "state": "In Progress", "url": "https://linear.app/team/issue/ENG-123", "description": "Users need to refresh their tokens without re-authenticating..." }]
When Claude calls get_linked_issues(query="JWT token refresh", source="linear"), Nectr:
Calls Linear MCP server with search_issues tool
Returns list of matching issues
Claude includes issue context in review (e.g., “This PR addresses ENG-123”)
Why not use Linear API directly?MCP provides a standardized protocol for tool calling. If Linear changes their API, only the MCP server needs updating — Nectr’s code stays the same.
Called by: ReviewToolExecutor (get_related_errors tool)
async def get_sentry_errors(self, project: str, filename: str) -> list[dict]: """Get recent Sentry errors related to a file being reviewed. Args: project: Sentry project slug (e.g., "backend"). filename: File path from the PR diff to filter errors by. Returns: List of error dicts: {id, title, culprit, count, last_seen}. Empty list if Sentry MCP is not configured or the call fails. """ if not settings.SENTRY_MCP_URL: logger.info("SENTRY_MCP_URL not configured — skipping Sentry error fetch") return [] return await self.query_mcp_server( server_url=settings.SENTRY_MCP_URL, tool_name="search_errors", args={"project": project, "filename": filename}, auth_token=settings.SENTRY_AUTH_TOKEN, )
Claude includes error context in review (e.g., “This PR might fix the recurring JWTDecodeError”)
Why filter by filename?
Sentry error culprits include stack traces. The MCP server filters errors where the top frame matches the given filename. This surfaces production errors directly related to the code being reviewed.
async def get_github_issues( self, repo: str, labels: list[str] | None = None, query: str = "",) -> list[dict]: """Pull open GitHub issues that a PR might be addressing. Returns an empty list — the native GitHub REST client in ReviewToolExecutor covers issue fetching directly. """ return []
GitHub issues are fetched directly via GitHub REST API (not MCP) because Nectr already has GitHub credentials. This method is a placeholder for future use.
🟡 Moderate: The verify_token() function has 42 production errors in the last 30 days (JWTDecodeError: Invalid token signature). This PR changes signature validation logic — ensure backward compatibility.
User impact: Review continues without MCP context. Claude doesn’t know about Linear issues or Sentry errors for this PR.
HTTP Error (4xx/5xx)
except httpx.HTTPStatusError as exc: logger.warning( "MCP server returned HTTP %s for tool=%s: %s", exc.response.status_code, tool_name, exc, ) return []
User impact: Same as timeout — graceful degradation.
JSON Parse Error
except (json.JSONDecodeError, KeyError): items.append(item) # Keep raw item if JSON parse fails
User impact: MCP result is included as-is (might be less useful, but doesn’t break review).
Planned: Pull relevant channel messages during PR reviews.Use case: If a PR mentions “authentication refactor”, search Slack for recent discussions about auth to provide context.Config:
SLACK_MCP_URL=https://your-slack-mcp-server.com
Datadog
Planned: Pull APM traces and metrics for files being modified.Use case: If a PR touches a high-latency endpoint, surface recent performance data.Config: