Skip to content

feat(web): add HTTP MCP server for dashboard icon search - #2885

Open
gfrancodev wants to merge 11 commits into
homarr-labs:mainfrom
gfrancodev:feat/mcp-http-server
Open

feat(web): add HTTP MCP server for dashboard icon search#2885
gfrancodev wants to merge 11 commits into
homarr-labs:mainfrom
gfrancodev:feat/mcp-http-server

Conversation

@gfrancodev

@gfrancodev gfrancodev commented Jul 30, 2026

Copy link
Copy Markdown

Summary

Captura de tela de 2026-07-31 07-15-37 Captura de tela de 2026-07-31 07-16-11 Captura de tela de 2026-07-31 07-18-22

This PR adds an HTTP MCP server for Dashboard Icons and ships the full in-app onboarding experience so users can connect Cursor, Claude, VS Code, and other MCP clients without leaving the site.

MCP HTTP server

  • Exposes /api/mcp with tools: search_icons, get_icon, get_icon_url, and suggest_icon
  • Extracts icon search, validation, rate limiting, and URL building into reusable server modules under web/src/lib/icons
  • Adds getDashboardIconsServerInfo() so MCP initialize responses include site branding (title, description, websiteUrl) and favicon URLs (favicon-96x96.png, favicon.svg)
  • Documents usage in web/docs/MCP.md

MCP setup UI

  • AddMcpButton and AddMcpLink open a setup dialog from multiple entry points:
    • Hero section
    • Icon browse page (/icons)
    • Icon detail page (sidebar button + inline "Set up MCP" link)
  • McpSetupDialogContent provides:
    • Intro copy and a Tools section listing all MCP tools with icons
    • Setup accordion with guides for VS Code, Cursor, Claude (Code + Desktop), Windsurf, Zed, and a custom MCP client
    • Copy-to-clipboard for each config snippet with syntax highlighting (McpHighlightedCode)
    • Optional Try with this icon contextual example when opened from an icon detail page
    • Links to full docs and modelcontextprotocol.io
  • mcp-setup-config.ts centralizes endpoint URLs, JSON/bash snippet builders, client metadata, and PostHog mcp_setup_opened source tracking (hero, icon_search, icon_details)
  • Uses the LobeHub MCP icon (light/dark PNG from jsDelivr) instead of the native asset with a black background
  • Dialog width set to sm:max-w-3xl for readable config blocks

Image and UI fixes

  • UnoptimizedImage: applies width/height: auto only when className uses flexible sizing (max-w-*, max-h-*, w-full, h-full); fixed dimensions otherwise. Fixes Next.js aspect-ratio console warnings and prevents oversized MCP icon rendering
  • Replaced next/image fill usage with explicit UnoptimizedImage sizing in icon cards, recently added icons, icon details, editable icon details, and submission details
  • Button: added React.forwardRef so Radix DialogTrigger asChild works reliably on desktop and mobile
  • Updated next.config.ts image remote patterns for jsDelivr dashboard-icons paths and Simple Icons CDN

Build, lint, and platform hardening (earlier commits on this branch)

  • Server-only boundaries, cached PocketBase/GitHub fetches, Next.js 16 proxy migration (middleware.tsproxy.ts)
  • Lint/a11y fixes, dependency updates, and expanded Vitest coverage across MCP handlers, icon library, and API helpers

Testing

  • Vitest: 100% coverage on included files; added tests for mcp-setup-config, server-info, and additional api.test.ts branches
  • Playwright (web/tests/mcp-setup.spec.ts): 7 scenarios × 2 projects (chromium + mobile-chrome) = 14 tests
    • Hero dialog: setup guides, tools list, client accordion, Claude CLI/desktop snippets, copy button, documentation links
    • Entry points: /icons browse page, /icons/plex with contextual example via link and sidebar button
    • Stable helpers: license notice dismissed via localStorage, scrollIntoViewIfNeeded, serial entry-point tests

Test plan

  • pnpm lint passes
  • pnpm test passes with 100% Vitest coverage on included files
  • pnpm build completes successfully
  • pnpm exec playwright test tests/mcp-setup.spec.ts passes (chromium + mobile-chrome)
  • Verify MCP tools via web/docs/MCP.md against a running dev server (pnpm dev:web on port 3005)
  • Smoke test MCP setup dialog from hero, icon browse, and icon detail pages (desktop + mobile)
  • Confirm initialize response at /api/mcp includes favicon in serverInfo.icons

View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

Summary by CodeRabbit

  • New Features

    • Added Dashboard Icons MCP support for icon search, metadata, CDN URLs, and suggestions.
    • Added setup dialogs with client-specific guides, copyable snippets, tool descriptions, and icon examples.
    • Added MCP access from the homepage, search controls, and icon details.
    • Added themed SVG, PNG, and WebP icon URL options.
  • Bug Fixes

    • Improved icon preview consistency and sizing.
    • Added MCP request limits and security safeguards.
  • Documentation

    • Added MCP setup, endpoint, client, tool, configuration, and usage guides.

Expose /api/mcp with search_icons, get_icon, get_icon_url, and
suggest_icon tools. Extract shared icon service with layered metadata
cache, input validation, per-IP rate limiting, and unit tests.
Expand Vitest coverage for MCP handlers, icon services, and API helpers with
100% thresholds on scoped modules and shared test setup for server-only code.
Rename the edge request handler to the new proxy convention while preserving
canonical host redirects, HTTPS enforcement, and tracking param stripping.
Mark icon services as server-only, cache PocketBase and GitHub fetches during
static generation, fix OpenTelemetry instrumentation, and resolve lint/a11y
issues across dashboard and icon UI components.
Update Next.js, Vitest, OpenTelemetry, and security overrides, and document
the MCP endpoint in the web README.

@ajnart ajnart left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I haven't tested the changes yet but thank you for this PR !
Should / could we have some kind of advertisement for this feature inside of the web app itself ? Like a "new - add MCP" button with instructions for all compatible clients ?

I haven't checked out the whole PR yet so apologies if it's already there

@ajnart

ajnart commented Jul 30, 2026

Copy link
Copy Markdown
Member

I think afterwards I'll add https://posthog.com/docs/mcp-analytics so that we can have some stats of how people use the MCP

@gfrancodev

Copy link
Copy Markdown
Author

Thanks @ajnart!

No in-app entry point yet, only docs. I am adding an "Add to MCP client" button today (same pattern as "Search from browser") on the home, /icons, and icon detail pages, with copy/paste setup for Cursor, Claude Desktop, Windsurf, and mcp-remote. Icon pages will include a contextual example for the current icon.

Re PostHog MCP Analytics: sounds great for tracking real tool usage on /api/mcp. I can add simple setup funnel events on the website if helpful. Happy to help wire up the server instrumentation whenever you are ready.

Pushing to this PR shortly.

Expose in-app MCP onboarding with client-specific config snippets, site
favicon in serverInfo, image rendering fixes, and Playwright tests.
@gfrancodev

Copy link
Copy Markdown
Author

@ajnart as promised, the in-app MCP entry points are in. "Add to MCP client" on home, /icons, and icon detail, with setup instructions for the clients above + contextual examples on icon pages. Screenshots attached.

@gfrancodev
gfrancodev requested a review from ajnart July 31, 2026 11:14
@ajnart

ajnart commented Jul 31, 2026

Copy link
Copy Markdown
Member

Thanks, I will check it out later today.

For now i'll let @coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown

@ajnart I will review the pull request.

✅ Action performed

Review finished.

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.

@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 834da725-e028-43d2-91e3-3b85caa9d49b

📥 Commits

Reviewing files that changed from the base of the PR and between dca8c70 and 6049b75.

📒 Files selected for processing (4)
  • web/src/instrumentation-node.ts
  • web/src/lib/icons/__tests__/service.test.ts
  • web/src/mcp/__tests__/handler.test.ts
  • web/src/mcp/handler.ts
💤 Files with no reviewable changes (1)
  • web/src/mcp/handler.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • web/src/instrumentation-node.ts
  • web/src/mcp/tests/handler.test.ts
  • web/src/lib/icons/tests/service.test.ts

📝 Walkthrough

Walkthrough

The change adds an MCP server for icon search, metadata lookup, URL generation, and suggestions. It adds MCP setup UI, icon-service caching and validation, request protection, image rendering updates, runtime configuration, documentation, and tests.

Changes

MCP icon service

Layer / File(s) Summary
Icon service contracts, search, caching, and validation
web/src/lib/icons/*, web/src/lib/api.ts, web/src/lib/external-icons.ts, web/src/lib/community.ts, web/src/lib/icon-url.ts, web/src/lib/utils.ts
Adds typed icon metadata, URL builders, validation schemas, fuzzy search, suggestions, caching, author lookup handling, and related-icon retrieval.
MCP tools, handler, route, and request protection
web/src/mcp/*, web/src/app/api/mcp/route.ts, web/src/lib/icons/rate-limit.ts
Registers four MCP tools and exposes them through a guarded Node.js route with body-size, request, and tool-call limits.
MCP setup configuration and dialog experience
web/src/lib/mcp-setup-config.ts, web/src/components/mcp-*.tsx, web/src/components/add-mcp-button.tsx, web/tests/mcp-setup.spec.ts
Adds client-specific setup snippets, setup guides, syntax-highlighted code blocks, MCP tool descriptions, analytics, and entry points from the hero, search, and icon details views.

Rendering and platform updates

Layer / File(s) Summary
Shared image and component rendering updates
web/src/components/unoptimized-image.tsx, web/src/components/icon-*.tsx, web/src/components/submission*.tsx, web/src/components/command-menu.tsx, web/src/components/hero.tsx, web/src/components/ui/button.tsx, web/src/components/magicui/*
Adds shared unoptimized image rendering, migrates icon and submission previews to explicit dimensions, forwards button refs, and attaches native pointer listeners when shared pointer state is disabled.
Platform configuration, runtime setup, and documentation
web/Dockerfile, web/next.config.ts, web/package.json, web/src/instrumentation.ts, web/src/mcp/analytics.ts, web/src/instrumentation-node.ts, web/README.md, web/docs/MCP.md, README.md, web/vitest.config.ts, web/vitest.setup.ts
Updates the Node.js image, Turbopack root, image source patterns, MCP route headers, dependencies, telemetry initialization, cache warming, MCP documentation, and coverage configuration.
Compatibility and small correctness updates
web/src/app/not-found.tsx, web/src/app/global-error.tsx, web/src/hooks/*, web/src/proxy.ts, web/src/lib/svg-color-utils.ts, web/src/app/og/__tests__/external-route.test.ts
Adjusts unused bindings, narrows file-reader results, renames the request handler export, improves numeric validation, and updates import formatting.

Suggested reviewers: ajnart

Sequence Diagram(s)

sequenceDiagram
  participant MCPClient
  participant MCPRoute
  participant RateLimiter
  participant MCPHandler
  participant IconService
  MCPClient->>MCPRoute: Send MCP request
  MCPRoute->>RateLimiter: Check IP and tool-call quotas
  RateLimiter->>MCPRoute: Allow or reject request
  MCPRoute->>MCPHandler: Forward accepted request
  MCPHandler->>IconService: Execute icon tool
  IconService->>MCPHandler: Return icon data
  MCPHandler->>MCPClient: Return MCP response
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely identifies the main change: adding an HTTP MCP server for Dashboard Icons search.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Yagni / Over-Engineering ✅ Passed The added complexity maps to explicit requirements: four MCP tools, six client guides, validation, rate limits, analytics, and onboarding; existing search logic was relocated and reused.
Docs Are Up To Date ✅ Passed No apps/docs directory exists; the MCP integration is documented in web/docs/MCP.md and linked from both README files, covering the endpoint, four tools, client setup, limits, environment variables...
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

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

Caution

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

⚠️ Outside diff range comments (1)
web/src/components/submissions-data-table.tsx (1)

633-650: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Keep the checkbox outside the row button.

The mobile row button contains an interactive Checkbox, creating nested interactive controls. This can break keyboard focus and screen-reader semantics even with stopPropagation(). Render the checkbox as a sibling of a separate detail button instead.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/src/components/submissions-data-table.tsx` around lines 633 - 650,
Restructure the mobile submission row around the button with onClick={() =>
setMobileDetailSubmission(submission)} so the selectable Checkbox is rendered
outside that button as a sibling control. Preserve the existing selection state,
toggleMobileSelection behavior, styling, and detail-button behavior while
ensuring the checkbox and detail action are separate interactive elements.
🧹 Nitpick comments (11)
web/README.md (1)

53-110: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

MCP documentation is duplicated across two files.

This section repeats the endpoint URL, tools table, Cursor/mcp-remote configuration, environment variables, and rate limits already documented in web/docs/MCP.md. Keeping both in sync manually risks drift as the MCP server evolves. Consider trimming this section to a short summary that links to web/docs/MCP.md as the single source of truth.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/README.md` around lines 53 - 110, The MCP section in web/README.md
duplicates the detailed documentation maintained in docs/MCP.md. Replace the
endpoint, tools, configuration, environment-variable, and rate-limit details
with a brief MCP server summary and a link to docs/MCP.md as the single source
of truth.
web/src/lib/community.ts (1)

105-112: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider exposing a reset for hasLoggedCommunityFetchFailure.

web/src/lib/external-icons.ts and web/src/lib/api.ts both expose test helpers that reset their one-time log flags. This module keeps the flag permanently, so tests that assert the warning cannot run in isolation after another test triggers it.

♻️ Proposed helper
+export function clearCommunityFetchFailureStateForTests(): void {
+	hasLoggedCommunityFetchFailure = false
+}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/src/lib/community.ts` around lines 105 - 112, Expose a test helper near
logCommunityFetchFailure that resets hasLoggedCommunityFetchFailure to false,
matching the reset helpers in external-icons.ts and api.ts. Keep the one-time
warning behavior unchanged in production and make the helper available to tests.
web/src/lib/icons/search.ts (2)

7-25: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Reduce the cost of levenshteinDistance.

The function allocates a full (b.length + 1) × (a.length + 1) matrix. fuzzySearch calls calculateStringSimilarity for the name, every alias, every category, and every query-word/text-word pair. filterAndSortIcons runs that for every icon, and web/src/components/command-menu.tsx runs it on every keystroke. For a few thousand icons this allocates thousands of arrays per query.

Use a two-row rolling buffer, and return early when the length difference already exceeds any useful similarity.

♻️ Proposed refactor
 export function levenshteinDistance(a: string, b: string): number {
-	const matrix: number[][] = []
-
-	for (let i = 0; i <= b.length; i++) {
-		matrix[i] = [i]
-	}
-	for (let j = 0; j <= a.length; j++) {
-		matrix[0][j] = j
-	}
-
-	for (let i = 1; i <= b.length; i++) {
-		for (let j = 1; j <= a.length; j++) {
-			const cost = a[j - 1] === b[i - 1] ? 0 : 1
-			matrix[i][j] = Math.min(matrix[i - 1][j] + 1, matrix[i][j - 1] + 1, matrix[i - 1][j - 1] + cost)
-		}
-	}
-
-	return matrix[b.length][a.length]
+	if (a === b) return 0
+	if (!a.length) return b.length
+	if (!b.length) return a.length
+
+	let prev = new Array<number>(a.length + 1)
+	let curr = new Array<number>(a.length + 1)
+	for (let j = 0; j <= a.length; j++) prev[j] = j
+
+	for (let i = 1; i <= b.length; i++) {
+		curr[0] = i
+		for (let j = 1; j <= a.length; j++) {
+			const cost = a[j - 1] === b[i - 1] ? 0 : 1
+			curr[j] = Math.min(prev[j] + 1, curr[j - 1] + 1, prev[j - 1] + cost)
+		}
+		const swap = prev
+		prev = curr
+		curr = swap
+	}
+
+	return prev[a.length]
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/src/lib/icons/search.ts` around lines 7 - 25, Optimize
levenshteinDistance by replacing the full matrix allocation with two rolling
rows that are reused across iterations. Before calculating the rows, return
early when the input length difference exceeds the maximum useful similarity
threshold expected by calculateStringSimilarity, while preserving the existing
distance result for all other inputs.

108-159: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Share the scoring weights between scoreIcon and filterAndSortIcons.

NAME_WEIGHT and ALIAS_WEIGHT are declared twice, and the name/alias scoring expression is duplicated. web/src/lib/icons/service.ts calls filterAndSortIcons for ranking and scoreIcon for the reported score, so the two formulas must stay in agreement. Extract the weights to module constants and reuse scoreIcon inside the mapping step.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/src/lib/icons/search.ts` around lines 108 - 159, Update scoreIcon and
filterAndSortIcons to share module-level NAME_WEIGHT and ALIAS_WEIGHT constants,
removing their local duplicates. In filterAndSortIcons, reuse scoreIcon for the
name/alias scoring portion of each mapped icon, while preserving the existing
category scoring, penalty, sorting, and ranking behavior so reported scores
remain consistent.
web/src/lib/icons/urls.ts (1)

18-33: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Reuse buildIconUrl for the color variants.

Lines 27 and 30 repeat the URL format. Icon.base is typed as string, so a value other than svg, png, or webp produces a path with an unsupported extension. Narrow base to IconFormat and delegate to buildIconUrl.

♻️ Proposed refactor
 export function buildIconUrls(name: string, icon: Icon) {
-	const base = typeof icon.base === "string" && !icon.base.startsWith("http") ? icon.base : "svg"
+	const base: IconFormat = icon.base === "png" || icon.base === "webp" ? icon.base : "svg"
 	const urls: { svg: string; png: string; webp: string; light?: string; dark?: string } = {
 		svg: buildIconUrl(name, "svg", "default"),
 		png: buildIconUrl(name, "png", "default"),
 		webp: buildIconUrl(name, "webp", "default"),
 	}
 
 	if (icon.colors?.light) {
-		urls.light = `${BASE_URL}/${base}/${icon.colors.light}.${base}`
+		urls.light = buildIconUrl(icon.colors.light, base)
 	}
 	if (icon.colors?.dark) {
-		urls.dark = `${BASE_URL}/${base}/${icon.colors.dark}.${base}`
+		urls.dark = buildIconUrl(icon.colors.dark, base)
 	}
 	return urls
 }

This also aligns the format selection with resolveFormat in web/src/lib/icon-url.ts.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/src/lib/icons/urls.ts` around lines 18 - 33, Update buildIconUrls to
narrow the selected base format to the IconFormat type, allowing only svg, png,
or webp values and defaulting unsupported bases to svg. Replace the manually
interpolated light and dark variant URLs with buildIconUrl calls using the
selected base format and each color variant, preserving the existing default
URLs.
web/src/lib/icons/__tests__/search.test.ts (1)

56-72: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Add a regression test for long text with a very short query.

The current cases assert only > 0 or === 0. They do not bound the upper value, so the unbounded return value of containsCharsInOrder flagged in web/src/lib/icons/search.ts passes unnoticed. Add an upper-bound assertion.

💚 Proposed test
 	it("returns 0 when chars are out of order", () => {
 		expect(containsCharsInOrder("plex", "xlp")).toBe(0)
 	})
+
+	it("never exceeds 1 for long text with a short query", () => {
+		expect(containsCharsInOrder("plex media server for home", "p")).toBeLessThanOrEqual(1)
+	})
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/src/lib/icons/__tests__/search.test.ts` around lines 56 - 72, Extend the
containsCharsInOrder test coverage with a long-text, very-short-query case and
assert that the returned score stays within its intended upper bound, not merely
greater than zero. Add the regression assertion in the existing
containsCharsInOrder describe block while preserving the current empty-input and
ordering cases.
web/src/mcp/__tests__/handler.test.ts (1)

9-14: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Mock does not catch the createMcpHandler argument-arity issue.

The mock only reads the first parameter and ignores the rest, so it cannot detect whether the call site passes two or three arguments. See the related comment on handler.ts about mcp-handler 2.x's argument signature; a stricter mock that asserts on arguments.length or inspects the second argument's shape would have caught that the verboseLogs option is passed in an argument position the real library ignores.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/src/mcp/__tests__/handler.test.ts` around lines 9 - 14, The
createMcpHandler mock in the test setup is too permissive and cannot detect
incorrect argument placement. Update the mock to validate the invocation arity
or inspect the second argument’s expected options shape, so tests fail when
verboseLogs is passed in an unsupported third argument position while preserving
the existing initialization behavior.
web/src/app/api/mcp/route.ts (1)

26-34: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consolidate the repeated, inconsistent security headers.

The request-limit 429 (Lines 29-33) sets Retry-After, Cache-Control, X-Content-Type-Options, and X-Frame-Options. The tool-limit 429 (Lines 49-51) sets only Retry-After and Cache-Control, omitting the other two. The success path (Lines 58-60) repeats the same Cache-Control/X-Content-Type-Options/X-Frame-Options triplet a third time.

Extract a shared headers object (or a small helper) for the common security headers, and apply it consistently on both 429 responses and the success response.

♻️ Proposed fix
+const SECURITY_HEADERS = {
+	"Cache-Control": "no-store",
+	"X-Content-Type-Options": "nosniff",
+	"X-Frame-Options": "DENY",
+} as const
+
 async function guardedHandler(req: NextRequest) {
 	const ip = getClientIp(req.headers)

 	const requestLimit = checkRateLimit(ip, "request")
 	if (!requestLimit.allowed) {
 		return new NextResponse("Too Many Requests", {
 			status: 429,
 			headers: {
 				"Retry-After": String(requestLimit.retryAfter ?? 60),
-				"Cache-Control": "no-store",
-				"X-Content-Type-Options": "nosniff",
-				"X-Frame-Options": "DENY",
+				...SECURITY_HEADERS,
 			},
 		})
 	}
 	...
 			if (!toolLimit.allowed) {
 				return new NextResponse("Too Many Requests", {
 					status: 429,
 					headers: {
 						"Retry-After": String(toolLimit.retryAfter ?? 60),
-						"Cache-Control": "no-store",
+						...SECURITY_HEADERS,
 					},
 				})
 			}
 	...
 	const response = await mcpHandler(req)
-	response.headers.set("Cache-Control", "no-store")
-	response.headers.set("X-Content-Type-Options", "nosniff")
-	response.headers.set("X-Frame-Options", "DENY")
+	for (const [key, value] of Object.entries(SECURITY_HEADERS)) {
+		response.headers.set(key, value)
+	}
 	return response
 }

Also applies to: 46-53, 58-60

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/src/app/api/mcp/route.ts` around lines 26 - 34, Consolidate the
duplicated common security headers in the MCP route by introducing a shared
headers object or helper for Cache-Control, X-Content-Type-Options, and
X-Frame-Options. Reuse it in the request-limit 429 response, the tool-limit 429
response, and the success response, while preserving each response’s Retry-After
header.
web/src/mcp/__tests__/route.test.ts (1)

34-43: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Add a test for the missing/understated content-length bypass.

This test only verifies that an overstated content-length header triggers 413 for a small body. It does not verify the more important direction: a large body sent without a content-length header (or with an understated one) is still rejected. Add that case once the underlying check in route.ts is fixed to measure actual body size.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/src/mcp/__tests__/route.test.ts` around lines 34 - 43, Extend the POST
body-size tests around the existing “returns 413 for oversized POST bodies”
case, and update the route’s body validation to measure actual body bytes rather
than trusting content-length alone. Add coverage for an oversized body with no
content-length and with an understated content-length, asserting both return
413.
web/src/lib/icons/__tests__/validate.test.ts (1)

44-58: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Add coverage for invalid name through the schema, not just through assertIconName directly.

The suite tests assertIconName throwing directly (Lines 19-26), but never calls getIconSchema.parse({ name: <invalid> }) or getIconUrlSchema.parse({ name: <invalid> }). Given the transform-throw behavior noted in validate.ts, add a test asserting what error type these schemas actually throw for an invalid name, so a future regression (or fix) is caught here.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/src/lib/icons/__tests__/validate.test.ts` around lines 44 - 58, Extend
the existing getIconSchema and/or getIconUrlSchema tests to parse an invalid
icon name through the schema and assert the actual error type produced by the
transform. Use the existing invalid-name fixture or assertion conventions from
the assertIconName tests, and cover the schema path rather than only calling
assertIconName directly.
web/src/components/icon-details.tsx (1)

718-763: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Memoize getAvailableSvgVariants so getSvgUrl/svgUrl memoization is effective.

getAvailableSvgVariants() is called directly at Line 718 and returns a new array on every render. getSvgUrl's useCallback (Lines 720-761) depends on this unstable availableVariants reference, so the callback is recreated on every render regardless of the useCallback wrapper. svgUrl's useMemo (Line 763) depends on getSvgUrl, so it also recomputes every render. The same unstable reference feeds the useEffect at Lines 765-769, causing its dependency check to run on every render as well.

Wrap getAvailableSvgVariants in useMemo so the derived array is stable across renders that do not change its inputs. This makes the useCallback/useMemo additions provide their intended benefit.

♻️ Proposed fix to stabilize `availableVariants`
-	const availableVariants = getAvailableSvgVariants()
+	const availableVariants = useMemo(
+		() => getAvailableSvgVariants(),
+		[isExternalIcon, externalIcon, isCommunityIcon, assetUrls, mainIconUrl, iconData, icon],
+	)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/src/components/icon-details.tsx` around lines 718 - 763, Memoize the
availableVariants result from getAvailableSvgVariants using useMemo, with
dependencies covering the inputs that determine the available variants. Keep
getSvgUrl and svgUrl using this stable derived array so their existing
memoization remains effective.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@web/src/app/api/mcp/route.ts`:
- Around line 37-42: Replace the content-length-only guard in the POST branch of
the MCP route with a bounded body-reading path that counts actual bytes and
aborts once MAX_REQUEST_BODY_BYTES is exceeded, including chunked requests
without a Content-Length header. Ensure the validated body is then reused by
req.clone().json() and mcpHandler(req) without allowing the original unbounded
stream to be consumed.

In `@web/src/components/unoptimized-image.tsx`:
- Around line 20-21: Update the sizeStyle assignment in UnoptimizedImage so
usesFlexibleSizing(className) returns an empty style object rather than inline
width:auto and height:auto; retain the existing width and height values for
non-flexible sizing.

In `@web/src/instrumentation.ts`:
- Around line 1-9: Move the OpenTelemetry imports and setup currently in
instrumentation.ts into a dedicated Node-only module, then update register to
dynamically await-import that module only within the NEXT_RUNTIME === "nodejs"
branch. Keep Edge execution free of any OpenTelemetry loading while preserving
the existing Node setup behavior.

In `@web/src/lib/api.ts`:
- Around line 161-169: Update getAuthorData so it skips authorDataCache.set when
resolveAuthorData returns the UNKNOWN_AUTHOR fallback with login "unknown" and
no author metadata fallback was applied; continue caching valid resolved author
data and preserving the existing cache lookup behavior.
- Around line 105-126: Update fetchGitHubAuthorData to build request headers
without an Authorization entry when GITHUB_TOKEN is unset, while preserving
Bearer authentication when a token exists. Add an AbortSignal.timeout to the
fetch options using the timeout pattern already established by the remote icon
service, so the request terminates rather than hanging.

In `@web/src/lib/external-icons.ts`:
- Around line 101-121: Move the try/catch from the unstable_cache callback in
getCachedExternalIcons to fetchExternalIconsWithTTL, allowing
fetchAllExternalIcons failures to propagate without caching an empty fallback.
In fetchExternalIconsWithTTL, return the empty fallback on failure but only
update _memCache when the fetched data is non-empty; preserve existing TTL
behavior for valid cached results.

In `@web/src/lib/icons/__tests__/service.test.ts`:
- Around line 158-163: The vi.unstubAllEnvs() call in the "rejects local
metadata path in production" test only executes if the assertion passes, causing
NODE_ENV to remain as production in subsequent tests when the assertion fails.
Move vi.unstubAllEnvs() from the end of the test case to the existing afterEach
block to guarantee cleanup runs regardless of test outcome.

In `@web/src/lib/icons/rate-limit.ts`:
- Around line 10-11: Update the module-level buckets Map and its rate-limit
update logic to evict expired entries during periodic or write-time cleanup,
ensuring stale IP+scope keys cannot accumulate indefinitely. Preserve existing
rate-limit behavior for active buckets, and use the existing resetAt timestamps
when determining expiration.

In `@web/src/lib/icons/search.ts`:
- Around line 36-58: Update containsCharsInOrder so its successful-match score
is always normalized to the 0–1 range, preventing long strings with early
matches from exceeding 1 and distorting fuzzySearch ranking. Preserve the
existing zero results for empty or unmatched inputs, and update the affected
expectations in the search tests to reflect the normalized scores.

In `@web/src/lib/icons/service.ts`:
- Around line 27-49: Update fetchMetadataFromRemote so the ETag is stored with
the cached IconFile data, keeping the validator and response body synchronized.
When a 304 response has no cached body available, retry the metadata request
without the If-None-Match header, then return the fetched data and refreshed
cache entry; preserve the existing cached-data path for valid 304 responses.
- Around line 68-85: Update MetadataCacheState and getAllIcons so the global
cache stores the load timestamp alongside data and etag. Before returning
globalThis.__dashboardIconsMetadata.data, compare its age with
CACHE_TTL_SECONDS; reuse it only while fresh, otherwise call getCachedMetadata
and replace the state with the refreshed data, etag, and current timestamp.
- Around line 98-114: Update searchIcons and the filterAndSortIcons usage so
total reflects all icons matching the trimmed query and category before applying
limit, while results remain limited and mapped as currently. Return the
pre-truncation match count instead of results.length, reusing the existing
filtering and sorting behavior.

In `@web/src/lib/icons/validate.ts`:
- Around line 18-42: Update iconNameField and the schemas that use it, including
getIconSchema and getIconUrlSchema, to validate names without allowing
assertIconName() exceptions to escape from a Zod transform. Use superRefine with
ctx.addIssue or an equivalent non-throwing Zod validation path, while preserving
the existing trimming and icon-name validation rules and structured validation
failures.

In `@web/src/mcp/handler.ts`:
- Around line 5-16: Update createDashboardIconsMcpHandler so createMcpHandler
receives a single options object containing both serverInfo and verboseLogs;
remove the separate third argument to ensure MCP_VERBOSE_LOGS is honored by the
two-argument API.

In `@web/src/mcp/tools.ts`:
- Around line 5-61: Update the not-found response branches in
registerDashboardIconsTools for get_icon and get_icon_url to include isError:
true on the returned MCP tool result, while preserving their existing JSON error
payloads and successful responses.

---

Outside diff comments:
In `@web/src/components/submissions-data-table.tsx`:
- Around line 633-650: Restructure the mobile submission row around the button
with onClick={() => setMobileDetailSubmission(submission)} so the selectable
Checkbox is rendered outside that button as a sibling control. Preserve the
existing selection state, toggleMobileSelection behavior, styling, and
detail-button behavior while ensuring the checkbox and detail action are
separate interactive elements.

---

Nitpick comments:
In `@web/README.md`:
- Around line 53-110: The MCP section in web/README.md duplicates the detailed
documentation maintained in docs/MCP.md. Replace the endpoint, tools,
configuration, environment-variable, and rate-limit details with a brief MCP
server summary and a link to docs/MCP.md as the single source of truth.

In `@web/src/app/api/mcp/route.ts`:
- Around line 26-34: Consolidate the duplicated common security headers in the
MCP route by introducing a shared headers object or helper for Cache-Control,
X-Content-Type-Options, and X-Frame-Options. Reuse it in the request-limit 429
response, the tool-limit 429 response, and the success response, while
preserving each response’s Retry-After header.

In `@web/src/components/icon-details.tsx`:
- Around line 718-763: Memoize the availableVariants result from
getAvailableSvgVariants using useMemo, with dependencies covering the inputs
that determine the available variants. Keep getSvgUrl and svgUrl using this
stable derived array so their existing memoization remains effective.

In `@web/src/lib/community.ts`:
- Around line 105-112: Expose a test helper near logCommunityFetchFailure that
resets hasLoggedCommunityFetchFailure to false, matching the reset helpers in
external-icons.ts and api.ts. Keep the one-time warning behavior unchanged in
production and make the helper available to tests.

In `@web/src/lib/icons/__tests__/search.test.ts`:
- Around line 56-72: Extend the containsCharsInOrder test coverage with a
long-text, very-short-query case and assert that the returned score stays within
its intended upper bound, not merely greater than zero. Add the regression
assertion in the existing containsCharsInOrder describe block while preserving
the current empty-input and ordering cases.

In `@web/src/lib/icons/__tests__/validate.test.ts`:
- Around line 44-58: Extend the existing getIconSchema and/or getIconUrlSchema
tests to parse an invalid icon name through the schema and assert the actual
error type produced by the transform. Use the existing invalid-name fixture or
assertion conventions from the assertIconName tests, and cover the schema path
rather than only calling assertIconName directly.

In `@web/src/lib/icons/search.ts`:
- Around line 7-25: Optimize levenshteinDistance by replacing the full matrix
allocation with two rolling rows that are reused across iterations. Before
calculating the rows, return early when the input length difference exceeds the
maximum useful similarity threshold expected by calculateStringSimilarity, while
preserving the existing distance result for all other inputs.
- Around line 108-159: Update scoreIcon and filterAndSortIcons to share
module-level NAME_WEIGHT and ALIAS_WEIGHT constants, removing their local
duplicates. In filterAndSortIcons, reuse scoreIcon for the name/alias scoring
portion of each mapped icon, while preserving the existing category scoring,
penalty, sorting, and ranking behavior so reported scores remain consistent.

In `@web/src/lib/icons/urls.ts`:
- Around line 18-33: Update buildIconUrls to narrow the selected base format to
the IconFormat type, allowing only svg, png, or webp values and defaulting
unsupported bases to svg. Replace the manually interpolated light and dark
variant URLs with buildIconUrl calls using the selected base format and each
color variant, preserving the existing default URLs.

In `@web/src/mcp/__tests__/handler.test.ts`:
- Around line 9-14: The createMcpHandler mock in the test setup is too
permissive and cannot detect incorrect argument placement. Update the mock to
validate the invocation arity or inspect the second argument’s expected options
shape, so tests fail when verboseLogs is passed in an unsupported third argument
position while preserving the existing initialization behavior.

In `@web/src/mcp/__tests__/route.test.ts`:
- Around line 34-43: Extend the POST body-size tests around the existing
“returns 413 for oversized POST bodies” case, and update the route’s body
validation to measure actual body bytes rather than trusting content-length
alone. Add coverage for an oversized body with no content-length and with an
understated content-length, asserting both return 413.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 703227ac-11d7-4c03-b7bb-3bca5253801d

📥 Commits

Reviewing files that changed from the base of the PR and between 03e8f8e and f3c202c.

⛔ Files ignored due to path filters (1)
  • web/pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (71)
  • README.md
  • web/Dockerfile
  • web/README.md
  • web/docs/MCP.md
  • web/next.config.ts
  • web/package.json
  • web/src/app/api/icons/search/route.ts
  • web/src/app/api/mcp/route.ts
  • web/src/app/community/[icon]/page.tsx
  • web/src/app/dashboard/page.tsx
  • web/src/app/global-error.tsx
  • web/src/app/icons/[icon]/page.tsx
  • web/src/app/icons/external/[slug]/page.tsx
  • web/src/app/not-found.tsx
  • web/src/app/og/__tests__/external-route.test.ts
  • web/src/components/add-mcp-button.tsx
  • web/src/components/add-to-search-bar-button.tsx
  • web/src/components/command-menu.tsx
  • web/src/components/editable-icon-details.tsx
  • web/src/components/hero.tsx
  • web/src/components/icon-card.tsx
  • web/src/components/icon-details.tsx
  • web/src/components/icon-grid.tsx
  • web/src/components/icon-search.tsx
  • web/src/components/icon-submission-form.tsx
  • web/src/components/magicui/magic-card-pointer.tsx
  • web/src/components/magicui/magic-card.tsx
  • web/src/components/mcp-highlighted-code.tsx
  • web/src/components/mcp-setup-dialog-content.tsx
  • web/src/components/recently-added-icons.tsx
  • web/src/components/submission-details.tsx
  • web/src/components/submissions-data-table.tsx
  • web/src/components/ui/button.tsx
  • web/src/components/unoptimized-image.tsx
  • web/src/components/user-button.tsx
  • web/src/hooks/use-icon-submission-form.ts
  • web/src/hooks/use-submissions.ts
  • web/src/instrumentation.ts
  • web/src/lib/__tests__/api.test.ts
  • web/src/lib/__tests__/icon-url.test.ts
  • web/src/lib/__tests__/mcp-setup-config.test.ts
  • web/src/lib/api.ts
  • web/src/lib/community.ts
  • web/src/lib/external-icon-urls.ts
  • web/src/lib/external-icons.ts
  • web/src/lib/icon-url.ts
  • web/src/lib/icons/__tests__/rate-limit.test.ts
  • web/src/lib/icons/__tests__/search.test.ts
  • web/src/lib/icons/__tests__/service.test.ts
  • web/src/lib/icons/__tests__/urls.test.ts
  • web/src/lib/icons/__tests__/validate.test.ts
  • web/src/lib/icons/rate-limit.ts
  • web/src/lib/icons/search.ts
  • web/src/lib/icons/service.ts
  • web/src/lib/icons/types.ts
  • web/src/lib/icons/urls.ts
  • web/src/lib/icons/validate.ts
  • web/src/lib/mcp-setup-config.ts
  • web/src/lib/svg-color-utils.ts
  • web/src/lib/utils.ts
  • web/src/mcp/__tests__/handler.test.ts
  • web/src/mcp/__tests__/route.test.ts
  • web/src/mcp/__tests__/server-info.test.ts
  • web/src/mcp/__tests__/tools.test.ts
  • web/src/mcp/handler.ts
  • web/src/mcp/server-info.ts
  • web/src/mcp/tools.ts
  • web/src/proxy.ts
  • web/tests/mcp-setup.spec.ts
  • web/vitest.config.ts
  • web/vitest.setup.ts

Comment thread web/src/app/api/mcp/route.ts
Comment thread web/src/components/unoptimized-image.tsx Outdated
Comment thread web/src/instrumentation.ts Outdated
Comment thread web/src/lib/api.ts
Comment thread web/src/lib/api.ts
Comment thread web/src/lib/icons/service.ts Outdated
Comment thread web/src/lib/icons/service.ts
Comment thread web/src/lib/icons/validate.ts
Comment thread web/src/mcp/handler.ts
Comment on lines +5 to +16
export const createDashboardIconsMcpHandler = () =>
createMcpHandler(
(server) => {
registerDashboardIconsTools(server)
},
{
serverInfo: getDashboardIconsServerInfo(),
},
{
verboseLogs: process.env.MCP_VERBOSE_LOGS === "true",
},
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== files =="
git ls-files | sed -n '1,120p'

echo "== dependency declarations =="
for f in package.json web/package.json pnpm-lock.yaml package-lock.json yarn.lock; do
  if [ -f "$f" ]; then
    echo "--- $f ---"
    rg -n '"mcp-handler"|mcp-handler@|/mcp-handler/|version: "2\.0\.1"|version: 2\.0\.1' "$f" -C 3 || true
  fi
done

echo "== handler.ts =="
if [ -f web/src/mcp/handler.ts ]; then
  cat -n web/src/mcp/handler.ts
fi

echo "== imports for mcp-handler =="
rg -n "createMcpHandler|from ['\"].*mcp-handler|require\\(['\"].*mcp-handler" web -S || true

echo "== package metadata from lock/package if available =="
python3 - <<'PY'
import json, os
for path in ["package.json","web/package.json","pnpm-lock.yaml","package-lock.json","yarn.lock"]:
    if not os.path.exists(path): continue
    print(f"\n--- {path} ---")
    if path.endswith((".json",".yaml")):
        print(open(path,encoding='utf-8').read())
PY

Repository: homarr-labs/dashboard-icons

Length of output: 10196


🌐 Web query:

mcp-handler createMcpHandler 2.0.1 signature serverInfo verboseLogs

💡 Result:

In mcp-handler version 2.0.1, the createMcpHandler function signature has been simplified to combine server options and configuration [1]. The signature is: createMcpHandler(initialize, options) [1] Where: - initialize: A function that defines the MCP server (e.g., registering tools, resources, and prompts) [1][2]. - options: A single object that consolidates the previous separate server options and the additional configuration keys [1]. This options object replaces the multi-argument structure used in earlier versions. Key properties within this object include: - serverInfo: An object providing server identity details [1][3]. - verboseLogs: A boolean flag used to enable verbose logging [1][4]. - onEvent: A callback for handling server events [1]. As of version 2.0.1, mcp-handler requires MCP SDK v2 packages (@modelcontextprotocol/server ^2.0.0), zod ^4.2.0, and Node.js 20 or later [1][2]. This version also adopts the 2026-07-28 Model Context Protocol specification, native support for request metadata, and simplified routing, while removing support for legacy transport endpoint routing [1][5][2].

Citations:


Merge verboseLogs into the createMcpHandler options object.

mcp-handler@2.0.1 uses the two-argument createMcpHandler(initialize, options) form, with serverInfo and verboseLogs as options properties. Passing verboseLogs as a third argument makes Node pass the server options as the second argument and ignore the config object, so MCP_VERBOSE_LOGS=true has no effect.

♻️ Proposed fix
 export const createDashboardIconsMcpHandler = () =>
 	createMcpHandler(
 		(server) => {
 			registerDashboardIconsTools(server)
 		},
 		{
 			serverInfo: getDashboardIconsServerInfo(),
+			verboseLogs: process.env.MCP_VERBOSE_LOGS === "true",
 		},
-		{
-			verboseLogs: process.env.MCP_VERBOSE_LOGS === "true",
-		},
 	)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const createDashboardIconsMcpHandler = () =>
createMcpHandler(
(server) => {
registerDashboardIconsTools(server)
},
{
serverInfo: getDashboardIconsServerInfo(),
},
{
verboseLogs: process.env.MCP_VERBOSE_LOGS === "true",
},
)
export const createDashboardIconsMcpHandler = () =>
createMcpHandler(
(server) => {
registerDashboardIconsTools(server)
},
{
serverInfo: getDashboardIconsServerInfo(),
verboseLogs: process.env.MCP_VERBOSE_LOGS === "true",
},
)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/src/mcp/handler.ts` around lines 5 - 16, Update
createDashboardIconsMcpHandler so createMcpHandler receives a single options
object containing both serverInfo and verboseLogs; remove the separate third
argument to ensure MCP_VERBOSE_LOGS is honored by the two-argument API.

Comment thread web/src/mcp/tools.ts

@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

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@web/docs/MCP.md`:
- Line 101: Update the runtime exporter in instrumentation initialization to use
NEXT_PUBLIC_POSTHOG_HOST when configuring the PostHog ingestion endpoint, while
preserving the EU endpoint as the default when the variable is unset;
alternatively remove the configuration entry from the MCP documentation if host
customization is not intended.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: e1976666-6ce3-44e3-b489-7f3f16d1c192

📥 Commits

Reviewing files that changed from the base of the PR and between f3c202c and 5f2cb5e.

⛔ Files ignored due to path filters (1)
  • web/pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (8)
  • web/docs/MCP.md
  • web/package.json
  • web/src/app/api/mcp/route.ts
  • web/src/mcp/__tests__/analytics.test.ts
  • web/src/mcp/__tests__/handler.test.ts
  • web/src/mcp/__tests__/route.test.ts
  • web/src/mcp/analytics.ts
  • web/src/mcp/handler.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • web/package.json
  • web/src/mcp/tests/handler.test.ts
  • web/src/mcp/tests/route.test.ts
  • web/src/mcp/handler.ts

Comment thread web/docs/MCP.md

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

🧹 Nitpick comments (2)
web/src/lib/icons/__tests__/service.test.ts (1)

209-220: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Verify that the retry is unconditional.

The test only checks that fetch runs twice. It passes if the retry still sends If-None-Match. Assert that the second request has no If-None-Match header.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/src/lib/icons/__tests__/service.test.ts` around lines 209 - 220, Update
the “retries an unexpected 304 without a cached body” test to inspect the second
fetch request and assert it does not include an If-None-Match header, while
retaining the existing two-call and metadata assertions.
web/src/instrumentation-node.ts (1)

4-4: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Use a batch processor for production PostHog logs.

SimpleLogRecordProcessor exports each record separately. Request-level logs can therefore create one OTLP request per record. OpenTelemetry warns about this overhead and recommends BatchLogRecordProcessor for production use. (open-telemetry.github.io)

Proposed change
-import { LoggerProvider, SimpleLogRecordProcessor } from "`@opentelemetry/sdk-logs`"
+import { BatchLogRecordProcessor, LoggerProvider } from "`@opentelemetry/sdk-logs`"

-		processors: [new SimpleLogRecordProcessor({ exporter: logExporter })],
+		processors: [new BatchLogRecordProcessor({ exporter: logExporter })],

Also applies to: 15-17

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/src/instrumentation-node.ts` at line 4, In the logger setup using
LoggerProvider, replace SimpleLogRecordProcessor with BatchLogRecordProcessor so
records are buffered and exported in batches for production PostHog logs. Update
the import and processor construction while preserving the existing exporter
configuration.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@web/src/instrumentation-node.ts`:
- Line 4: In the logger setup using LoggerProvider, replace
SimpleLogRecordProcessor with BatchLogRecordProcessor so records are buffered
and exported in batches for production PostHog logs. Update the import and
processor construction while preserving the existing exporter configuration.

In `@web/src/lib/icons/__tests__/service.test.ts`:
- Around line 209-220: Update the “retries an unexpected 304 without a cached
body” test to inspect the second fetch request and assert it does not include an
If-None-Match header, while retaining the existing two-call and metadata
assertions.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 9f3653f2-c10f-4b29-a4b9-e7042077404d

📥 Commits

Reviewing files that changed from the base of the PR and between 5f2cb5e and 7dd3caf.

📒 Files selected for processing (24)
  • web/README.md
  • web/src/app/api/mcp/route.ts
  • web/src/components/icon-details.tsx
  • web/src/components/submissions-data-table.tsx
  • web/src/components/unoptimized-image.tsx
  • web/src/instrumentation-node.ts
  • web/src/instrumentation.ts
  • web/src/lib/__tests__/api.test.ts
  • web/src/lib/api.ts
  • web/src/lib/community.ts
  • web/src/lib/external-icons.ts
  • web/src/lib/icons/__tests__/rate-limit.test.ts
  • web/src/lib/icons/__tests__/search.test.ts
  • web/src/lib/icons/__tests__/service.test.ts
  • web/src/lib/icons/__tests__/validate.test.ts
  • web/src/lib/icons/rate-limit.ts
  • web/src/lib/icons/search.ts
  • web/src/lib/icons/service.ts
  • web/src/lib/icons/urls.ts
  • web/src/lib/icons/validate.ts
  • web/src/mcp/__tests__/handler.test.ts
  • web/src/mcp/__tests__/route.test.ts
  • web/src/mcp/__tests__/tools.test.ts
  • web/src/mcp/tools.ts
🚧 Files skipped from review as they are similar to previous changes (21)
  • web/src/lib/icons/tests/validate.test.ts
  • web/src/lib/community.ts
  • web/src/components/unoptimized-image.tsx
  • web/src/mcp/tests/tools.test.ts
  • web/src/lib/external-icons.ts
  • web/src/components/submissions-data-table.tsx
  • web/src/mcp/tests/route.test.ts
  • web/src/lib/icons/urls.ts
  • web/src/mcp/tools.ts
  • web/README.md
  • web/src/lib/icons/search.ts
  • web/src/lib/icons/validate.ts
  • web/src/lib/api.ts
  • web/src/lib/icons/service.ts
  • web/src/app/api/mcp/route.ts
  • web/src/lib/icons/rate-limit.ts
  • web/src/mcp/tests/handler.test.ts
  • web/src/lib/icons/tests/rate-limit.test.ts
  • web/src/lib/icons/tests/search.test.ts
  • web/src/components/icon-details.tsx
  • web/src/lib/tests/api.test.ts

Merge verboseLogs into the mcp-handler options object, batch PostHog OTLP
log exports, and assert unconditional metadata retry without If-None-Match.
@gfrancodev

Copy link
Copy Markdown
Author

All CodeRabbit actionable items should now be resolved across 7dd3caf, dca8c70, and 6049b75. pnpm test (1019) and pnpm lint pass locally.

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.

2 participants