Conversation
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
shadcn-hooks | 76fcafe | Commit Preview URL Branch Preview URL |
Apr 27 2026, 03:40 AM |
|
Warning Rate limit exceeded
To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the 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. ℹ️ Review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
📝 WalkthroughWalkthroughThis PR updates dependencies (pnpm to 10.33.2, React/Next.js stacks, design libraries), introduces agent discovery endpoints (MCP server card, API catalog, OAuth/OpenID configurations), adds a WebMCP provider for client-side tool registration, refactors UI component styling with Tailwind class reordering, and integrates new agent metadata infrastructure. Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 13
Note
Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (5)
src/components/ui/pagination.tsx (1)
65-99:⚠️ Potential issue | 🟡 Minor
aria-labelnot synchronized with the newtextprop.When consumers pass a custom
text(e.g. for localization liketext="上一页"/text="下一页"), the visible label updates butaria-labelstays hardcoded as"Go to previous page"/"Go to next page". This creates a mismatch for screen-reader users on non-English UIs and partially defeats the purpose of exposing thetextprop.Consider accepting an optional
aria-label(or deriving it fromtext) so the accessible name stays consistent with the visible label.♻️ Proposed adjustment
function PaginationPrevious({ className, text = "Previous", + "aria-label": ariaLabel = "Go to previous page", ...props }: React.ComponentProps<typeof PaginationLink> & { text?: string }) { return ( <PaginationLink - aria-label="Go to previous page" + aria-label={ariaLabel} size="default" className={cn("pl-2!", className)} {...props} > <ChevronLeftIcon data-icon="inline-start" /> <span className="hidden sm:block">{text}</span> </PaginationLink> ) } @@ function PaginationNext({ className, text = "Next", + "aria-label": ariaLabel = "Go to next page", ...props }: React.ComponentProps<typeof PaginationLink> & { text?: string }) { return ( <PaginationLink - aria-label="Go to next page" + aria-label={ariaLabel} size="default" className={cn("pr-2!", className)} {...props} > <span className="hidden sm:block">{text}</span> <ChevronRightIcon data-icon="inline-end" /> </PaginationLink> ) }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/ui/pagination.tsx` around lines 65 - 99, PaginationPrevious and PaginationNext hardcode aria-labels that can diverge from the visible text when consumers pass a custom text (e.g. localized labels); update both components to accept an optional ariaLabel prop (or derive the accessible name from the text prop) and use aria-label={ariaLabel ?? text} instead of the fixed English string so screen reader names always match the visible label; adjust the prop types for PaginationPrevious and PaginationNext (the props union with React.ComponentProps<typeof PaginationLink>) to include ariaLabel?: string and apply the new aria value to the PaginationLink elements.src/components/ui/tabs.tsx (1)
14-22:⚠️ Potential issue | 🔴 CriticalFix Tailwind selectors to match Base UI's
data-orientationattribute, not non-existentdata-horizontal/data-verticalattributes.Base UI's
Tabs.Rootsets adata-orientationattribute with values'horizontal'or'vertical'— not separatedata-horizontalanddata-verticalboolean attributes. The Tailwind selectors on line 18 (data-horizontal:flex-col) and throughout the file (group-data-horizontal/tabs:*,group-data-vertical/tabs:*) will not match anything and styles will silently fail to apply.Change selectors to use
data-[orientation=horizontal]:flex-col,group-data-[orientation=horizontal]/tabs:*, andgroup-data-[orientation=vertical]/tabs:*instead. The manualdata-orientation={orientation}prop on line 16 is correct and should be kept.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/ui/tabs.tsx` around lines 14 - 22, Tailwind selectors are using non-existent data-horizontal/data-vertical attributes so update all selectors to target Base UI's data-orientation attribute: in the TabsPrimitive.Root JSX (symbol: TabsPrimitive.Root) keep data-orientation={orientation} but replace class names like "data-horizontal:flex-col" with "data-[orientation=horizontal]:flex-col" and update any group selectors (e.g., "group-data-horizontal/tabs:*", "group-data-vertical/tabs:*") to "group-data-[orientation=horizontal]/tabs:*" and "group-data-[orientation=vertical]/tabs:*" respectively so styles correctly match orientation values.src/components/mdx/container-with-demo.client.tsx (1)
52-60:⚠️ Potential issue | 🟡 MinorVerify the second
<ResizablePanel />is still needed.With
<ResizableHandle>commented out and the first panel pinned atdefaultSize={100}, the empty second<ResizablePanel />will receive 0% size and effectively renders nothing. If the goal is just to showchildrenwith no resizing UX, consider dropping the second panel (and likely the entire group wrapper) to remove dead structure. If it’s a placeholder for upcoming work, a brief TODO comment would help future readers.♻️ Optional simplification if the second panel/handle aren't planned
- <ResizablePanelGroup - orientation='horizontal' - className='bg-transparent' - > - <ResizablePanel defaultSize={100} className='bg-transparent'> - {children} - </ResizablePanel> - {/* <ResizableHandle withHandle className='bg-transparent' /> */} - <ResizablePanel /> - </ResizablePanelGroup> + <div className='bg-transparent'>{children}</div>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/mdx/container-with-demo.client.tsx` around lines 52 - 60, The second empty ResizablePanel inside ResizablePanelGroup is currently dead (first panel is pinned with defaultSize={100} and ResizableHandle is commented out); either remove the trailing <ResizablePanel /> and collapse the group (or remove ResizablePanelGroup entirely) to eliminate the unused element, or if it's intentionally reserved for future work, add a clear TODO comment near ResizablePanelGroup indicating the placeholder purpose and restore or uncomment ResizableHandle to enable resizing; reference the ResizablePanelGroup, ResizablePanel, ResizableHandle and the defaultSize={100} usage when making the change.src/components/ui/sidebar.tsx (1)
152-216:⚠️ Potential issue | 🟡 Minor
diris only forwarded on mobile; the desktop wrapper silently drops it.
diris destructured at Line 158 and applied to<SheetContent dir={dir} />(Line 186) on the mobile branch, but in the desktop branch (Lines 208–251) it never lands on any rendered element — the{...props}spread on the inner<div data-slot="sidebar-container">no longer containsdirbecause it was destructured out. If the prop is meant to support per-component RTL on desktop, apply it to one of the desktop wrappers; otherwise consider removing it from the API to avoid confusion.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/ui/sidebar.tsx` around lines 152 - 216, The Sidebar component currently destructures dir but only uses it on the mobile SheetContent; for desktop the prop is dropped. Fix by forwarding dir to the desktop wrapper: add dir={dir} (or spread {...props} onto the outer desktop <div className="group peer..."> with data-slot="sidebar") so the desktop branch receives the dir prop (instead of relying on the inner data-slot="sidebar-container" spread which no longer has dir). Update the Sidebar function (referenced by name) to apply dir to that desktop wrapper element.package.json (1)
1-120:⚠️ Potential issue | 🔴 CriticalCI is red —
vitestis failing on this branch.The pipeline log shows
nr test(vitest) exiting with code 1. Given the simultaneous bumps ofvitest@^4.1.5,@vitejs/plugin-react@^6.0.1,jsdom@^29,@testing-library/react@^16.3.2, and React 19.2.5, this is most likely a breaking-change knock-on. Please rerun locally and either pin compatible versions or update tests/setup before merging.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@package.json` around lines 1 - 120, CI is failing because vitest tests break after simultaneous dependency bumps (vitest, `@vitejs/plugin-react`, jsdom, `@testing-library/react`, react); reproduce the failure locally by running the "test" script (vitest), identify the breaking package(s) from the stack traces, and either pin the offending packages back to known-compatible versions in package.json (e.g., adjust "vitest", "@vitejs/plugin-react", "jsdom", "@testing-library/react", or "react") or update the test environment/setup (vitest config, jsdom environment initialization, React testing-library adapters or wrappers) to match the new major versions before committing; ensure rollback/pins are committed to package.json and rerun "pnpm install" and "pnpm test" to confirm CI will pass.
🟡 Minor comments (9)
src/components/ui/navigation-menu.tsx-116-116 (1)
116-116:⚠️ Potential issue | 🟡 MinorReplace
data-[ending-style]:easing-[ease]withdata-[ending-style]:ease-[ease]Tailwind v4 uses the
ease-*prefix for transition timing functions (e.g.,ease-linear,ease-[ease],ease-[cubic-bezier(...)]). Theeasing-*prefix does not exist and will not generate any CSS rule, causing the intended ease override to be dropped.Additionally,
xs:is not a configured breakpoint in this project—Tailwind v4 ships only withsmand up by default—so that utility will also not apply.Also note: The same line uses
data-ending-style:anddata-starting-style:in later selectors (e.g.,data-ending-style:scale-90), which should use bracket syntax to match the variant syntax:data-[ending-style]:scale-90anddata-[starting-style]:opacity-0.Proposed fix
- <NavigationMenuPrimitive.Popup className="data-[ending-style]:easing-[ease] xs:w-(--popup-width) relative h-(--popup-height) w-(--popup-width) origin-(--transform-origin) rounded-lg bg-popover text-popover-foreground shadow ring-1 ring-foreground/10 transition-[opacity,transform,width,height,scale,translate] duration-[0.35s] ease-[cubic-bezier(0.22,1,0.36,1)] outline-none data-ending-style:scale-90 data-ending-style:opacity-0 data-ending-style:duration-150 data-starting-style:scale-90 data-starting-style:opacity-0"> + <NavigationMenuPrimitive.Popup className="data-[ending-style]:ease-[ease] xs:w-(--popup-width) relative h-(--popup-height) w-(--popup-width) origin-(--transform-origin) rounded-lg bg-popover text-popover-foreground shadow ring-1 ring-foreground/10 transition-[opacity,transform,width,height,scale,translate] duration-[0.35s] ease-[cubic-bezier(0.22,1,0.36,1)] outline-none data-[ending-style]:scale-90 data-[ending-style]:opacity-0 data-[ending-style]:duration-150 data-[starting-style]:scale-90 data-[starting-style]:opacity-0">🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/ui/navigation-menu.tsx` at line 116, In the NavigationMenuPrimitive.Popup className, fix invalid Tailwind utilities: replace the non-existent "data-[ending-style]:easing-[ease]" with "data-[ending-style]:ease-[ease]", change any "data-ending-style:scale-90" and "data-starting-style:opacity-0" occurrences to the bracket-variant form "data-[ending-style]:scale-90" and "data-[starting-style]:opacity-0", and remove or replace the unused "xs:" breakpoint (use an existing project breakpoint such as "sm:" or omit it) so the intended utilities actually generate CSS.packages/shadcn-hooks/package.json-54-54 (1)
54-54:⚠️ Potential issue | 🟡 Minor
@types/node@^25targets Node 25.x but CI runs on LTS (currently 22.x) — addengines.nodefield or align types.The package uses
@types/node@^25.6.0in devDependencies while CI tests againstlts/*(Node 22.x). This mismatch means TypeScript may suggest APIs not available in the LTS runtime. Add anengines.nodefield topackage.jsonclarifying supported versions (e.g.,"engines": {"node": ">=20"}) or downgrade@types/nodeto a version aligned with the Node LTS versions you support.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/shadcn-hooks/package.json` at line 54, The package.json currently references `@types/node`@^25.6.0 in devDependencies which targets Node 25.x while CI runs LTS (22.x); either add an engines.node field in package.json (e.g., "engines": {"node": ">=20"} or a specific range you support) to declare the supported Node runtime, or align the types by downgrading the devDependency `@types/node` to a version matching your LTS (e.g., Node 22.x) so TypeScript typings match CI; update package.json's devDependencies/@types/node or add the engines.node entry accordingly and run npm install to update lockfile.src/components/ui/switch.tsx-19-19 (1)
19-19:⚠️ Potential issue | 🟡 Minor
h-[18.4px]looks like a typo or a sub-pixel value.Fractional pixel heights are unusual for a control like this and can render inconsistently across DPR / zoom levels. Given the thumb is
size-4(16px), an integer height likeh-[18px]orh-5(20px) is more likely what was intended. Please double-check this isn't a typo fromh-[18px].🛠️ Likely intended value
- "peer group/switch relative inline-flex shrink-0 items-center rounded-full border border-transparent shadow-xs transition-all outline-none after:absolute after:-inset-x-3 after:-inset-y-2 focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 data-[size=default]:h-[18.4px] data-[size=default]:w-[32px] data-[size=sm]:h-[14px] data-[size=sm]:w-[24px] dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 data-checked:bg-primary data-unchecked:bg-input dark:data-unchecked:bg-input/80 data-disabled:cursor-not-allowed data-disabled:opacity-50", + "peer group/switch relative inline-flex shrink-0 items-center rounded-full border border-transparent shadow-xs transition-all outline-none after:absolute after:-inset-x-3 after:-inset-y-2 focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 data-[size=default]:h-[18px] data-[size=default]:w-[32px] data-[size=sm]:h-[14px] data-[size=sm]:w-[24px] dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 data-checked:bg-primary data-unchecked:bg-input dark:data-unchecked:bg-input/80 data-disabled:cursor-not-allowed data-disabled:opacity-50",🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/ui/switch.tsx` at line 19, The class string in the Switch component includes a fractional height token "h-[18.4px]" which is likely a typo and can cause inconsistent rendering; update the token inside the class list in src/components/ui/switch.tsx (the string used to define the switch root/button styles) to an integer pixel value such as "h-[18px]" or a Tailwind utility like "h-5" to match the 16px thumb (size-4), and verify the corresponding data-[size=sm] value remains consistent.src/lib/agent-discovery.ts-145-163 (1)
145-163:⚠️ Potential issue | 🟡 MinorOAuth metadata stubs publish empty arrays for required fields.
Per RFC 8414,
response_types_supportedis REQUIRED for an authorization server metadata document, and per RFC 9728 theauthorization_serverslist is what tells a client where to authenticate against a protected resource. Returning empty arrays at/.well-known/oauth-authorization-serverand/.well-known/oauth-protected-resourceis technically a malformed AS metadata document and may cause OAuth-aware agents/scanners to flag the site or attempt fallbacks.If this site genuinely doesn't offer OAuth, consider not exposing these
.well-knownendpoints at all (a 404 is the spec-compliant signal that auth is not required); or, if MCP discovery requires their presence, add a comment explaining the stub-by-design intent.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/lib/agent-discovery.ts` around lines 145 - 163, The current handlers oauthAuthorizationServer() and oauthProtectedResource() return empty arrays for required OAuth metadata fields; update oauthAuthorizationServer() to return a minimally valid AS metadata document (e.g., set issuer to websiteConfig.baseUrl, response_types_supported to at least ["code"], grant_types_supported to appropriate values like ["authorization_code","refresh_token"], and populate scopes_supported with whatever scopes the app exposes or ["openid"] if applicable), and update oauthProtectedResource() to populate authorization_servers with the AS issuer URL (e.g., websiteConfig.baseUrl or the value returned by oauthAuthorizationServer().issuer), add at least one scope in scopes_supported and keep bearer_methods_supported, and include a short comment above both functions noting these are minimal/stub values by design (or alternatively remove/expose 404 behavior if you want the endpoints absent). Ensure you modify the functions named oauthAuthorizationServer and oauthProtectedResource accordingly.src/lib/agent-discovery.ts-42-44 (1)
42-44:⚠️ Potential issue | 🟡 Minor
markdownTokenCountreturns"1"for empty/whitespace input.
"".trim().split(/\s+/)is[""](length 1), somarkdownTokenCount("")andmarkdownTokenCount(" ")both report"1". Not a correctness bug for the current call site (homeMarkdownis non-empty), but the value is exposed publicly via thex-markdown-tokensresponse header and a future caller could trip over it. Also worth nothing the function name implies LLM-token counting while the implementation counts whitespace-separated words.🐛 Proposed guard + clarified name
-export function markdownTokenCount(markdown: string): string { - return markdown.trim().split(/\s+/).length.toString() -} +export function markdownWordCount(markdown: string): string { + const trimmed = markdown.trim() + if (!trimmed) return "0" + return trimmed.split(/\s+/).length.toString() +}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/lib/agent-discovery.ts` around lines 42 - 44, The current markdownTokenCount implementation returns "1" for empty or whitespace-only input and also misleadingly counts words, not LLM tokens; update the function markdownTokenCount(markdown: string) so that it trims input and if the result is an empty string it returns "0", otherwise split on /\s+/ and return the length as a string; also either rename the function to markdownWordCount or add a clear comment that it returns a whitespace-separated word count (not LLM tokens) to avoid future confusion.src/components/ui/chart.tsx-60-60 (1)
60-60:⚠️ Potential issue | 🟡 Minor
id ?? uniqueIdallows an empty string to slip through.Switching from
||to??means a caller passingid=""now produceschart-(no suffix). The previous||form fell back touniqueIdfor any falsy value. Probably benign, but worth flagging sinceidis a<div>attribute and an empty string is a plausible incoming value. If you want to keep the strict-null-coalesce style, consider normalizing empty strings:🐛 Optional guard
- const chartId = `chart-${id ?? uniqueId.replace(/:/g, "")}` + const chartId = `chart-${id || uniqueId.replace(/:/g, "")}`🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/ui/chart.tsx` at line 60, The current chartId computation uses the nullish coalescing form id ?? uniqueId which lets an empty string slip through and produce a bare "chart-" id; change the logic to treat empty strings as missing by normalizing id before composing chartId (e.g., compute a resolvedId from id that treats ""/all-whitespace as absent, then use resolvedId || uniqueId.replace(/:/g, "") to build chartId). Update the identifier construction around the chartId variable so it references the normalized resolvedId and still strips colons from uniqueId as before.src/components/ui/sidebar.tsx-291-298 (1)
291-298:⚠️ Potential issue | 🟡 MinorIdentical
ltr:/rtl:translate looks like a copy-paste bug.ltr:-translate-x-1/2 rtl:-translate-x-1/2Both directions apply the same
-translate-x-1/2, which is functionally equivalent to writing-translate-x-1/2with no direction modifier. Combined with the logicalafter:start-1/2(which flips X position between LTR and RTL), the rail won't be visually centered in RTL. The intent is almost certainly to mirror in RTL.🐛 Suggested fix
- "absolute inset-y-0 z-20 hidden w-4 transition-all ease-linear group-data-[side=left]:-right-4 group-data-[side=right]:left-0 after:absolute after:inset-y-0 after:start-1/2 after:w-[2px] hover:after:bg-sidebar-border sm:flex ltr:-translate-x-1/2 rtl:-translate-x-1/2", + "absolute inset-y-0 z-20 hidden w-4 transition-all ease-linear group-data-[side=left]:-right-4 group-data-[side=right]:left-0 after:absolute after:inset-y-0 after:start-1/2 after:w-[2px] hover:after:bg-sidebar-border sm:flex ltr:-translate-x-1/2 rtl:translate-x-1/2",🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/ui/sidebar.tsx` around lines 291 - 298, Update the mirrored translate utility in the class string used by the Sidebar component's className (the cn(...) call) so RTL flips the X translation instead of repeating the LTR negative value: change the current "ltr:-translate-x-1/2 rtl:-translate-x-1/2" pattern to use the opposite sign for rtl (so RTL uses translate-x-1/2), ensuring the rail is visually centered when combined with after:start-1/2; modify the token inside the className array/string in src/components/ui/sidebar.tsx where the cn(...) is defined.src/app/robots.txt/route.ts-5-16 (1)
5-16:⚠️ Potential issue | 🟡 MinorVerify the baked-in
SitemapURL in production builds.Because the route is
force-static,siteUrl('/sitemap.xml')is evaluated at build time. ThewebsiteConfig.baseUrlswitches onprocess.env.NODE_ENV === 'development', so if a build ever runs withoutNODE_ENV=productionthe staticrobots.txtwill shiphttp://localhost:3000/sitemap.xml. Worth confirming the deploy pipeline always builds withNODE_ENV=production, or switch to a non-NODE_ENVconfig (e.g. aNEXT_PUBLIC_SITE_URL) to make the baseUrl deterministic.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/app/robots.txt/route.ts` around lines 5 - 16, The robots.txt GET handler bakes siteUrl('/sitemap.xml') at build time (route is force-static) which can produce a localhost URL if the build runs without NODE_ENV=production; update the GET implementation to use a deterministic public config/value instead of NODE_ENV-driven websiteConfig (e.g. read process.env.NEXT_PUBLIC_SITE_URL or another NEXT_PUBLIC_* variable and build the sitemap URL from that), or change the route to be runtime/dynamic so siteUrl is resolved at request time; ensure you update references in GET and any helper that calls siteUrl('/sitemap.xml') to use the chosen deterministic source.src/components/webmcp-provider.tsx-55-72 (1)
55-72:⚠️ Potential issue | 🟡 MinorAdd error handling around the search fetch.
If
/api/searchis unreachable or returns a non-OK status,fetcheither throws (network error) or returns an error body that is still JSON-parsed/text-read silently. The tool will surface a raw rejection toprovideContextwith no actionable message for the agent.🛠️ Proposed fix
- const response = await fetch( - `/api/search?query=${encodeURIComponent(query)}`, - ) - const contentType = response.headers.get('content-type') ?? '' - - if (contentType.includes('application/json')) { - return response.json() - } - - return { text: await response.text() } + try { + const response = await fetch( + `/api/search?query=${encodeURIComponent(query)}`, + ) + if (!response.ok) { + return { error: `search failed with status ${response.status}` } + } + const contentType = response.headers.get('content-type') ?? '' + return contentType.includes('application/json') + ? await response.json() + : { text: await response.text() } + } catch (err) { + return { error: err instanceof Error ? err.message : 'search failed' } + }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/webmcp-provider.tsx` around lines 55 - 72, Wrap the fetch call inside execute(...) in a try/catch and validate response.ok: after getting query (via readStringField), perform fetch in a try block, check response.ok and if false read the response body (use content-type to decide json vs text) and return a structured error like { error: `Search failed`, status: response.status, body: ... }; also catch network exceptions and return { error: 'Network error contacting /api/search', details: error.message } so callers (e.g., provideContext) get actionable messages instead of raw rejections.
🧹 Nitpick comments (16)
src/components/ui/native-select.tsx (2)
27-27: Nit:selection:*andplaceholder:*utilities have no effect on<select>.
<select>elements don’t render a text selection or a placeholder pseudo-element, soselection:bg-primary,selection:text-primary-foreground, andplaceholder:text-muted-foregroundare dead classes carried over from input-style components. Safe to drop to keep the class list focused.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/ui/native-select.tsx` at line 27, The class list on the select element in the NativeSelect component contains irrelevant utilities (`selection:bg-primary`, `selection:text-primary-foreground`, `placeholder:text-muted-foreground`) that have no effect on <select> elements; remove these dead classes from the className string in src/components/ui/native-select.tsx (the className assigned inside the NativeSelect component) so the class list only contains applicable utilities for <select> elements.
10-33:classNameis applied to the wrapper, not the<select>.Consumers passing
className(e.g. width, focus-visible utilities, sizing) expect to style the underlying<select>, sinceNativeSelectis presented as a select replacement and...propsare spread to the<select>. Currently it merges into the wrapper<div>, which makes overriding select-specific styles awkward and asymmetric withSelectTriggerinsrc/components/ui/select.tsx(whereclassNamestyles the trigger itself).Consider routing
classNameto the<select>and exposing a separate prop (e.g.wrapperClassName) if wrapper-level overrides are needed.♻️ Suggested change
function NativeSelect({ className, size = "default", ...props }: NativeSelectProps) { return ( <div - className={cn( - "group/native-select relative w-fit has-[select:disabled]:opacity-50", - className - )} + className="group/native-select relative w-fit has-[select:disabled]:opacity-50" data-slot="native-select-wrapper" data-size={size} > <select data-slot="native-select" data-size={size} - className="h-9 w-full min-w-0 appearance-none rounded-md border border-input bg-transparent py-1 pr-8 pl-2.5 text-sm shadow-xs transition-[color,box-shadow] outline-none select-none selection:bg-primary selection:text-primary-foreground placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:cursor-not-allowed aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 data-[size=sm]:h-8 dark:bg-input/30 dark:hover:bg-input/50 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40" + className={cn( + "h-9 w-full min-w-0 appearance-none rounded-md border border-input bg-transparent py-1 pr-8 pl-2.5 text-sm shadow-xs transition-[color,box-shadow] outline-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:cursor-not-allowed aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 data-[size=sm]:h-8 dark:bg-input/30 dark:hover:bg-input/50 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40", + className + )} {...props} />🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/ui/native-select.tsx` around lines 10 - 33, The component NativeSelect currently applies the incoming className to the wrapper div instead of the <select>, so change the API to forward className to the <select> and add a new wrapperClassName (or similar) prop for the outer div; update the NativeSelectProps type to include wrapperClassName, leave the wrapper's default classes intact but merge wrapperClassName into the div, and move the existing className merge into the <select> (ensure spread {...props} does not accidentally override the computed className by applying className on the <select> after spreading or by composing it before spreading).src/components/ui/select.tsx (1)
165-166: Nit: collapse empty multi-line JSX self-closing tags.
<ChevronUpIcon />and<ChevronDownIcon />are split across two lines with no props, which is awkward formatting. Consider collapsing them onto a single line for readability (a formatter like Prettier should normalize this automatically).✏️ Proposed tweak
- <ChevronUpIcon - /> + <ChevronUpIcon />- <ChevronDownIcon - /> + <ChevronDownIcon />Also applies to: 184-185
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/ui/select.tsx` around lines 165 - 166, Collapse the empty multi-line JSX self-closing tags for the icons into single-line form: replace the multi-line instances of ChevronUpIcon and ChevronDownIcon in the Select component with single-line self-closing tags (use <ChevronUpIcon /> and <ChevronDownIcon />) and run the project's formatter (e.g., Prettier) to normalize formatting.src/components/ui/dropdown-menu.tsx (1)
138-138: Add missing inline-start/inline-end slide variants to DropdownMenuSubContent for consistency.
DropdownMenuContentincludesdata-[side=inline-end]:slide-in-from-left-2anddata-[side=inline-start]:slide-in-from-right-2slide variants (line 44), butDropdownMenuSubContent(line 138) is missing these. Since Base UI's Positioner can resolve sides to logicalinline-startandinline-endvalues—especially in RTL contexts or depending on positioning logic—the sub-content will lack animations in those scenarios. Mirror the same side selectors for consistency.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/ui/dropdown-menu.tsx` at line 138, DropdownMenuSubContent's className is missing the logical side slide variants present in DropdownMenuContent; update the className on DropdownMenuSubContent to also include data-[side=inline-end]:slide-in-from-left-2 and data-[side=inline-start]:slide-in-from-right-2 so the sub-menu gets the same slide animations when Positioner resolves to inline-start/inline-end (look for the DropdownMenuSubContent component and mirror the data-[side=inline-*] selectors used in DropdownMenuContent).src/components/ui/breadcrumb.tsx (1)
110-111: Consider formattingMoreHorizontalIconconsistently.The
ChevronRightIconon line 89 was reformatted to a single line, butMoreHorizontalIconhere remains split across two lines. For consistency, consider formatting it similarly.✨ Formatting suggestion
- <MoreHorizontalIcon - /> + <MoreHorizontalIcon />🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/ui/breadcrumb.tsx` around lines 110 - 111, The MoreHorizontalIcon JSX should be formatted consistently with ChevronRightIcon; locate the MoreHorizontalIcon usage in breadcrumb.tsx (the instance currently split over two lines) and collapse it to a single self-closing tag on one line (e.g., <MoreHorizontalIcon />) matching the style used for ChevronRightIcon to keep component formatting uniform.src/components/ui/calendar.tsx (2)
189-189: Tightenlocaletyping to matchDayPicker's own type.
Calendaralready typeslocaleviaReact.ComponentProps<typeof DayPicker>(i.e.,Locale | undefined). UsingPartial<Locale>here is looser than what is actually passed in and inconsistent with the upstream prop. Consider aligning toLocalefor type accuracy.🧹 Proposed fix
-}: React.ComponentProps<typeof DayButton> & { locale?: Partial<Locale> }) { +}: React.ComponentProps<typeof DayButton> & { locale?: Locale }) {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/ui/calendar.tsx` at line 189, The Calendar component's prop typing currently uses locale?: Partial<Locale>, which is looser than DayPicker's locale type; update the Calendar props to use the exact DayPicker locale type (e.g., replace locale?: Partial<Locale> with locale?: React.ComponentProps<typeof DayPicker>['locale'] or Locale | undefined) so the locale prop matches DayPicker's typing for the Calendar function signature.
164-166: Memoize theDayButtoncomponent to prevent per-render remounts.Defining
DayButtonas an inline arrow function creates a new component reference on every render ofCalendar. In react-day-picker v9, thecomponentsobject uses reference equality to determine if a component has changed; a new reference causes React to unmount and remount all day buttons on each parent render. This triggers unnecessary DOM churn and causes theuseEffectinCalendarDayButton(which focuses the ref whenmodifiers.focusedchanges) to re-fire on remount, potentially disrupting focus management.Memoize the component with
React.useCallbackkeyed on[locale]:Suggested fix
const defaultClassNames = getDefaultClassNames() +const DayButtonComponent = React.useCallback( + (props: React.ComponentProps<typeof DayButton>) => ( + <CalendarDayButton locale={locale} {...props} /> + ), + [locale] +) + return ( <DayPicker ... components={{ ... - DayButton: ({ ...props }) => ( - <CalendarDayButton locale={locale} {...props} /> - ), + DayButton: DayButtonComponent,🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/ui/calendar.tsx` around lines 164 - 166, The inline DayButton arrow creates a new component reference each render causing remounts; change the inline definition to a memoized callback (e.g. const DayButton = React.useCallback(({ ...props }) => <CalendarDayButton locale={locale} {...props} />, [locale])) and pass that DayButton variable into the components prop of Calendar instead of the inline function so reference equality is stable and CalendarDayButton won't remount on every parent render.src/components/ui/radio-group.tsx (1)
28-33: Indicator centering is redundant.
RadioPrimitive.Indicatoralready centers children viaflex items-center justify-center, but the inner<span>then usesabsolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2(positioning against therelativeRoot). Either approach works alone — combining them is just dead styling. Pick one for clarity.♻️ Proposed simplification
<RadioPrimitive.Indicator data-slot="radio-group-indicator" className="flex size-4 items-center justify-center" > - <span className="absolute top-1/2 left-1/2 size-2 -translate-x-1/2 -translate-y-1/2 rounded-full bg-primary-foreground" /> + <span className="size-2 rounded-full bg-primary-foreground" /> </RadioPrimitive.Indicator>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/ui/radio-group.tsx` around lines 28 - 33, The Indicator centering is redundant: decide between using the parent RadioPrimitive.Indicator's flex centering (className="flex items-center justify-center") or the child's absolute centering (the inner <span> with absolute/top-1/2/left-1/2 and translate classes) and remove the other approach for clarity; for example, keep RadioPrimitive.Indicator's flex centering and strip the absolute/top/left/-translate classes from the inner span (leave size/rounded/bg classes), or alternatively remove the flex classes on RadioPrimitive.Indicator and keep the span's absolute centering—apply the change in the RadioPrimitive.Indicator block and the inner span element.src/components/ui/toggle.tsx (1)
39-39: InconsistentclassNamecomposition pattern.Other components updated in this PR (e.g.,
src/components/ui/badge.tsxline 40, pluscard.tsx,alert-dialog.tsx,button.tsx) moved from passingclassNameinto thecvacall to appending it after viacn(variants({ variant }), className). This file still uses the oldertoggleVariants({ variant, size, className })form, which makes the PR's normalization inconsistent.♻️ Proposed alignment
- className={cn(toggleVariants({ variant, size, className }))} + className={cn(toggleVariants({ variant, size }), className)}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/ui/toggle.tsx` at line 39, The toggle component currently passes className into toggleVariants causing inconsistent composition; update the JSX so cn composes the variant output and className separately by calling toggleVariants with only variant and size (e.g., toggleVariants({ variant, size })) and then passing className as a second argument to cn (cn(toggleVariants(...), className)), matching the pattern used in badge/card/alert-dialog/button components.src/components/ui/resizable.tsx (2)
23-23: Tiny nit: empty rest destructure can be inlined.
({ ...props }: ResizablePrimitive.PanelProps)is equivalent to(props: ResizablePrimitive.PanelProps). Optional cleanup, not blocking.♻️ Optional simplification
-function ResizablePanel({ ...props }: ResizablePrimitive.PanelProps) { +function ResizablePanel(props: ResizablePrimitive.PanelProps) { return <ResizablePrimitive.Panel data-slot="resizable-panel" {...props} /> }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/ui/resizable.tsx` at line 23, The ResizablePanel component currently uses an unnecessary rest destructure in its parameter list; change the function signature from "function ResizablePanel({ ...props }: ResizablePrimitive.PanelProps)" to simply "function ResizablePanel(props: ResizablePrimitive.PanelProps)" and update any internal references accordingly (keep using props as before) to remove the redundant spread.
1-50: LGTM — migration toreact-resizable-panelsv4 is correct.The rename to
Group/Separator, prop type updates (GroupProps/SeparatorProps/PanelProps), and switch toaria-[orientation=…]:selectors all align with v4 API (v4 renamesPanelResizeHandle→Separatoranddirection→orientationto match ARIA semantics).Minor refinement: Line 23's
ResizablePaneldestructures only...props, which is redundant — directly type the parameter instead:function ResizablePanel(props: ResizablePrimitive.PanelProps) { return <ResizablePrimitive.Panel data-slot="resizable-panel" {...props} /> }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/ui/resizable.tsx` around lines 1 - 50, The ResizablePanel function unnecessarily destructures only ...props in its parameter; change its signature from function ResizablePanel({ ...props }: ResizablePrimitive.PanelProps) to function ResizablePanel(props: ResizablePrimitive.PanelProps) and keep the body return <ResizablePrimitive.Panel data-slot="resizable-panel" {...props} /> so the parameter is directly typed as PanelProps (update the ResizablePanel function declaration accordingly).src/components/ui/chart.tsx (1)
200-210: Using arrayindexas Reactkeyfor tooltip/legend rows.Both list maps now key children by
index. For static, never-reordered rows this is fine, but Recharts can re-order thepayloadarray (e.g., when series are toggled via the legend). Prefer a stable identifier likeitem.dataKey ?? item.name ?? indexso React can preserve DOM/animation state across updates.Also applies to: 299-308
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/ui/chart.tsx` around lines 200 - 210, Replace the unstable index-based React keys used when mapping payload rows with a stable identifier so DOM/animation state persists when Recharts reorders series; specifically, in the map that computes const key = `${nameKey ?? item.name ?? item.dataKey ?? "value"}` (and the similar map around lines 299-308), use a deterministic key such as item.dataKey ?? item.name ?? nameKey ?? String(index) (or a concatenation that includes dataKey/name/nameKey) instead of just index, and pass that as the element key where getPayloadConfigFromPayload, payload, and item are used so rows remain stable across updates.src/components/ui/combobox.tsx (2)
39-50:ComboboxClearis defined but never exported.
ComboboxClearis implemented (Lines 39–50) and used internally byComboboxInput, but it is not present in the publicexport { ... }block. Consumers can configure<ComboboxInput showClear />but cannot place<ComboboxClear />themselves (e.g., inside a custom input layout). Either remove it (if internal-only) or add it to the export list to match the other primitives.♻️ Proposed export addition
export { Combobox, + ComboboxClear, ComboboxInput, ComboboxContent,Also applies to: 280-297
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/ui/combobox.tsx` around lines 39 - 50, ComboboxClear is implemented but not exported, preventing consumers from using it directly (while ComboboxInput supports showClear); either remove the component if it should remain internal or add ComboboxClear to the module's public export block alongside the other combobox primitives so it is accessible to consumers; update the export list to include ComboboxClear (and mirror the same change for the similar unexported component referenced around lines 280–297) and ensure imports/exports remain consistent with ComboboxInput and other Combobox* symbols.
215-219: Remove redundant type intersection onComboboxChipsprops.
React.ComponentPropsWithRef<typeof ComboboxPrimitive.Chips>is unnecessary here. Every other wrapper in this file uses justComboboxPrimitive.X.Props, and the underlying@base-ui/reactcomponent doesn't actually support ref forwarding (it usesComponentPropsWithoutRef). Removing the intersection improves consistency and avoids type confusion.♻️ Proposed simplification
function ComboboxChips({ className, ...props -}: React.ComponentPropsWithRef<typeof ComboboxPrimitive.Chips> & - ComboboxPrimitive.Chips.Props) { +}: ComboboxPrimitive.Chips.Props) {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/ui/combobox.tsx` around lines 215 - 219, The ComboboxChips props type unnecessarily intersects React.ComponentPropsWithRef<typeof ComboboxPrimitive.Chips> with ComboboxPrimitive.Chips.Props; remove the redundant intersection and use only ComboboxPrimitive.Chips.Props for the function signature of ComboboxChips (keeping the same parameters like className and ...props) so it matches other wrappers and avoids implying ref forwarding for ComboboxPrimitive.Chips.src/app/index.md/route.ts (1)
9-17: Consider reusingtextResponsefor consistency.The sibling markdown route (
.well-known/agent-skills/shadcn-hooks.md/route.ts) usestextResponse(...). ExtendingtextResponseto accept extra headers (or adding a thinmarkdownResponsehelper) would centralizecharset,Content-Type,Link, and the token header so all markdown endpoints behave identically.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/app/index.md/route.ts` around lines 9 - 17, Refactor the GET handler to reuse the existing textResponse helper by either extending textResponse to accept extra headers or by adding a thin markdownResponse wrapper; specifically, change the GET function that currently returns new Response(homeMarkdown, { headers: { 'Content-Type': 'text/markdown; charset=utf-8', 'Link': linkHeaderValue, 'x-markdown-tokens': markdownTokenCount(homeMarkdown) } }) to call textResponse(homeMarkdown, { headers: { 'Link': linkHeaderValue, 'x-markdown-tokens': markdownTokenCount(homeMarkdown) } }) after updating textResponse to set Content-Type with charset by default (or create markdownResponse(homeMarkdown, { link: linkHeaderValue, tokens: ... }) which delegates to textResponse), so GET, textResponse/markdownResponse, homeMarkdown, markdownTokenCount, and linkHeaderValue are used consistently.src/components/webmcp-provider.tsx (1)
81-92: Resource list duplicates the agent-discovery source of truth.
linkHeaderValue,mcpServerCard(), andagentSkillsIndex()insrc/lib/agent-discovery.tsalready enumerate these endpoints. Hardcoding them again here will drift (already missing several.well-known/*endpoints introduced in this PR). Consider exposing a small helper fromagent-discovery.ts(e.g.agentResources()) and consuming it here so both server discovery routes and the WebMCP tool stay in sync.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/webmcp-provider.tsx` around lines 81 - 92, The WebMCP provider's execute() hardcodes the same resource list already defined in agent-discovery.ts, causing drift; add a small exported helper (e.g. agentResources()) to src/lib/agent-discovery.ts that returns the canonical resources used by linkHeaderValue, mcpServerCard(), and agentSkillsIndex(), then import and call that helper from the execute() implementation in src/components/webmcp-provider.tsx (replace the inline resources array with the returned agentResources() value) so both server discovery routes and the WebMCP tool stay in sync.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 92fb299d-e4b3-4fbb-8aed-4f89847092d3
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (71)
package.jsonpackages/shadcn-hooks/package.jsonpublic/_headerssrc/app/(home)/page.tsxsrc/app/.well-known/agent-skills/index.json/route.tssrc/app/.well-known/agent-skills/shadcn-hooks.md/route.tssrc/app/.well-known/api-catalog/route.tssrc/app/.well-known/mcp/server-card.json/route.tssrc/app/.well-known/oauth-authorization-server/route.tssrc/app/.well-known/oauth-protected-resource/route.tssrc/app/.well-known/openid-configuration/route.tssrc/app/health/route.tssrc/app/index.md/route.tssrc/app/openapi.json/route.tssrc/app/robots.tssrc/app/robots.txt/route.tssrc/components/mdx/container-with-demo.client.tsxsrc/components/ui/accordion.tsxsrc/components/ui/alert-dialog.tsxsrc/components/ui/alert.tsxsrc/components/ui/avatar.tsxsrc/components/ui/badge.tsxsrc/components/ui/breadcrumb.tsxsrc/components/ui/button-group.tsxsrc/components/ui/button.tsxsrc/components/ui/calendar.tsxsrc/components/ui/card.tsxsrc/components/ui/carousel.tsxsrc/components/ui/chart.tsxsrc/components/ui/checkbox.tsxsrc/components/ui/combobox.tsxsrc/components/ui/command.tsxsrc/components/ui/context-menu.tsxsrc/components/ui/dialog.tsxsrc/components/ui/direction.tsxsrc/components/ui/drawer.tsxsrc/components/ui/dropdown-menu.tsxsrc/components/ui/empty.tsxsrc/components/ui/field.tsxsrc/components/ui/hover-card.tsxsrc/components/ui/input-group.tsxsrc/components/ui/input-otp.tsxsrc/components/ui/input.tsxsrc/components/ui/item.tsxsrc/components/ui/kbd.tsxsrc/components/ui/label.tsxsrc/components/ui/menubar.tsxsrc/components/ui/native-select.tsxsrc/components/ui/navigation-menu.tsxsrc/components/ui/pagination.tsxsrc/components/ui/popover.tsxsrc/components/ui/progress.tsxsrc/components/ui/radio-group.tsxsrc/components/ui/resizable.tsxsrc/components/ui/scroll-area.tsxsrc/components/ui/select.tsxsrc/components/ui/separator.tsxsrc/components/ui/sheet.tsxsrc/components/ui/sidebar.tsxsrc/components/ui/skeleton.tsxsrc/components/ui/slider.tsxsrc/components/ui/switch.tsxsrc/components/ui/table.tsxsrc/components/ui/tabs.tsxsrc/components/ui/textarea.tsxsrc/components/ui/toggle-group.tsxsrc/components/ui/toggle.tsxsrc/components/ui/tooltip.tsxsrc/components/webmcp-provider.tsxsrc/lib/agent-discovery.tssrc/registry/hooks/use-event-listener/demo/demo-01.tsx
💤 Files with no reviewable changes (1)
- src/app/robots.ts
| "fumadocs-docgen": "^3.0.10", | ||
| "fumadocs-mdx": "^14.3.1", | ||
| "fumadocs-ui": "^16.8.4", | ||
| "glob": "^13.0.6", |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
Duplicate entries in dependencies and devDependencies.
glob (^13.0.6) and tailwindcss-animate (^1.0.7) are listed in both sections. Pick one section per package — keeping them in both is redundant and can produce ambiguous resolutions during pnpm install or publish flows.
♻️ Proposed cleanup
"devDependencies": {
...
- "glob": "^13.0.6",
...
- "tailwindcss-animate": "^1.0.7",Also applies to: 62-62, 90-90, 105-105
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@package.json` at line 46, package.json currently lists duplicate entries for
the same packages in both "dependencies" and "devDependencies" (e.g., "glob" and
"tailwindcss-animate"); remove the redundant copy so each package appears in
only one section: decide whether each package is needed at runtime (keep in
"dependencies") or only for development/build (keep in "devDependencies"),
delete the other entry, and run your package manager's install/lock update to
verify resolution; specifically search for "glob" and "tailwindcss-animate" (and
the other duplicates referenced in the review) and consolidate them into the
correct single dependency section.
| / | ||
| Link: </.well-known/api-catalog>; rel="api-catalog", </docs>; rel="service-doc", </index.md>; rel="alternate"; type="text/markdown", </.well-known/agent-skills/index.json>; rel="service-desc"; type="application/json" | ||
| Vary: Accept | ||
|
|
||
| /index.html | ||
| Link: </.well-known/api-catalog>; rel="api-catalog", </docs>; rel="service-doc", </index.md>; rel="alternate"; type="text/markdown", </.well-known/agent-skills/index.json>; rel="service-desc"; type="application/json" | ||
| Vary: Accept |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
Link header duplicated and out of sync with new discovery endpoints.
The same Link string is repeated for / and /index.html, and it duplicates linkHeaderValue in src/lib/agent-discovery.ts. With this PR adding oauth-authorization-server, openid-configuration, oauth-protected-resource, mcp/server-card.json, and openapi.json, those are not advertised here, so anything relying on the Link header will miss them.
Two options:
- Generate
public/_headersfromlinkHeaderValue(or vice versa) in a build step so they cannot drift. - At minimum, extend
linkHeaderValue(and this file) with the new endpoints, e.g.</.well-known/mcp/server-card.json>; rel="service-desc"; type="application/json"and</openapi.json>; rel="service-desc"; type="application/openapi+json".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@public/_headers` around lines 1 - 7, The Link header in public/_headers is
duplicated and out of sync with the canonical linkHeaderValue in
src/lib/agent-discovery.ts; update the source of truth so discovery endpoints
aren’t missed: either (preferred) make public/_headers generated from
linkHeaderValue at build time, or (simpler) extend linkHeaderValue in
src/lib/agent-discovery.ts and mirror the same entries into public/_headers to
include the new endpoints (</.well-known/mcp/server-card.json>;
rel="service-desc"; type="application/json", </openapi.json>;
rel="service-desc"; type="application/openapi+json",
</.well-known/oauth-authorization-server>; rel="authorization-server" or
appropriate rel, </.well-known/openid-configuration>;
rel="openid-configuration", </.well-known/oauth-protected-resource>;
rel="protected-resource") so both places stay in sync and advertise the new
endpoints.
| import { jsonResponse, oauthAuthorizationServer } from '~/lib/agent-discovery' | ||
|
|
||
| export const dynamic = 'force-static' | ||
|
|
||
| export function GET() { | ||
| return jsonResponse(oauthAuthorizationServer()) | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
OpenID Connect Discovery 1.0 required fields openid-configuration
💡 Result:
The required fields (marked as REQUIRED) in the OpenID Provider Configuration document (/.well-known/openid-configuration) per OpenID Connect Discovery 1.0 incorporating errata set 2 are: - issuer: URL using the https scheme with no query or fragment components that the OP asserts as its Issuer Identifier. - authorization_endpoint: URL of the OP's OAuth 2.0 Authorization Endpoint. - response_types_supported: JSON array containing a list of the OAuth 2.0 Response Type values that this OP supports. Dynamic OpenID Providers MUST support the code, id_token, and the id_token token Response Type values. - subject_types_supported: JSON array containing a list of the Subject Identifier types that this OP supports. Valid types include pairwise and public. - id_token_signing_alg_values_supported: JSON array containing a list of the JWS signing algorithms (alg values) supported by the OP for the ID Token to encode the Claims in a JWT. The algorithm RS256 MUST be included. Note: token_endpoint is REQUIRED unless only the Implicit Flow is used.
Citations:
- 1: https://openid.net/specs/openid-connect-discovery-1_0.html
- 2: https://openid.net/specs/openid-connect-discovery-1_0.html.
- 3: https://openid.net/specs/openid-connect-discovery-1_0-errata2.html
🏁 Script executed:
# Find the agent-discovery file and examine oauthAuthorizationServer function
fd -t f agent-discovery src/lib/Repository: Debbl/shadcn-hooks
Length of output: 89
🏁 Script executed:
# Search for oauthAuthorizationServer definition
rg "oauthAuthorizationServer" -A 20Repository: Debbl/shadcn-hooks
Length of output: 1140
🏁 Script executed:
# Check if openidConfiguration function exists
rg "openidConfiguration"Repository: Debbl/shadcn-hooks
Length of output: 44
🏁 Script executed:
# Search for all .well-known routes
fd -t f route.ts | xargs grep -l "well-known" 2>/dev/nullRepository: Debbl/shadcn-hooks
Length of output: 46
🏁 Script executed:
# Also check for any openid or oidc related files/routes
fd -t f -e ts | xargs grep -l "openid-configuration" 2>/dev/nullRepository: Debbl/shadcn-hooks
Length of output: 46
🏁 Script executed:
# Simpler check for the route file mentioned in the review
cat -n src/app/.well-known/openid-configuration/route.tsRepository: Debbl/shadcn-hooks
Length of output: 306
Wrong payload for /.well-known/openid-configuration.
This endpoint serves the result of oauthAuthorizationServer(), which only returns issuer, service_documentation, and empty *_supported arrays. OpenID Connect Discovery 1.0 mandates additional fields (at minimum authorization_endpoint, jwks_uri, subject_types_supported, and id_token_signing_alg_values_supported). Strict OIDC clients will reject this document.
Either introduce a dedicated openidConfiguration() generator in src/lib/agent-discovery.ts that returns the proper OIDC metadata, or drop this route until a real OIDC provider is wired up — exposing /.well-known/openid-configuration advertises an OP that doesn't actually exist here, which can confuse agents/crawlers.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/app/.well-known/openid-configuration/route.ts` around lines 1 - 7, The
route GET currently returns oauthAuthorizationServer() which lacks required
OpenID Connect Discovery fields; add a new openidConfiguration() in
src/lib/agent-discovery.ts that builds a full OIDC metadata document (use issuer
from oauthAuthorizationServer(), include authorization_endpoint, token_endpoint
if applicable, jwks_uri, subject_types_supported,
id_token_signing_alg_values_supported, response_types_supported,
scopes_supported, grant_types_supported, and any relevant endpoints) and change
the route's GET to return jsonResponse(openidConfiguration()); alternatively
remove/disable this route until a real OP is available to avoid advertising a
non-compliant provider.
| orientation={orientation} | ||
| className={cn( | ||
| "bg-input relative self-stretch data-[orientation=horizontal]:mx-px data-[orientation=horizontal]:w-auto data-[orientation=vertical]:my-px data-[orientation=vertical]:h-auto", | ||
| "relative self-stretch bg-input data-horizontal:mx-px data-horizontal:w-auto data-vertical:my-px data-vertical:h-auto", |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Inspect what data attributes the wrapper Separator (and its underlying primitive) emit.
fd -t f 'separator.tsx' --exec sh -c 'echo "== {} =="; cat {}'Repository: Debbl/shadcn-hooks
Length of output: 647
🏁 Script executed:
# Check the button-group.tsx usage context around line 74
fd -t f 'button-group.tsx' --exec sh -c 'echo "== {} =="; head -80 {} | tail -20'Repository: Debbl/shadcn-hooks
Length of output: 547
🌐 Web query:
Base UI React Separator data-horizontal data-vertical attributes
💡 Result:
In Base UI React Separator component, there is no data-horizontal or data-vertical attributes. The component renders a div element with role="separator" and a data-orientation attribute that is set to either "horizontal" or "vertical" based on the orientation prop (default: "horizontal"). To style differently for orientations in CSS: [role="separator"][data-orientation="horizontal"] { /* styles / } [role="separator"][data-orientation="vertical"] { / styles */ } Note: Some shadcn/ui implementations or Tailwind configs incorrectly reference data-horizontal/data-vertical selectors, but official Base UI uses data-orientation exclusively, as confirmed in docs and type definitions.
Citations:
- 1: https://base-ui.com/react/components/separator
- 2: https://mui-base-ui.mintlify.app/components/separator
- 3: [bug]: shadcn/Create: Tailwind data attribute selector fix: data-horizontal / data-vertical → data-[orientation=horizontal] shadcn-ui/ui#9196
- 4: https://cdn.jsdelivr.net/npm/@base-ui/react@1.1.0/separator/Separator.d.ts
- 5: https://base-ui.com/react/handbook/styling
data-horizontal: / data-vertical: selectors won't match the Separator DOM.
Base UI's SeparatorPrimitive only emits data-orientation="horizontal" or data-orientation="vertical", not boolean data-horizontal / data-vertical attributes. The margin/sizing rules here (mx-px w-auto / my-px h-auto) will silently fail to apply, leaving only the base bg-input styling.
Use data-[orientation=horizontal]: and data-[orientation=vertical]: instead:
Suggested fix
- "relative self-stretch bg-input data-horizontal:mx-px data-horizontal:w-auto data-vertical:my-px data-vertical:h-auto",
+ "relative self-stretch bg-input data-[orientation=horizontal]:mx-px data-[orientation=horizontal]:w-auto data-[orientation=vertical]:my-px data-[orientation=vertical]:h-auto",📝 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.
| "relative self-stretch bg-input data-horizontal:mx-px data-horizontal:w-auto data-vertical:my-px data-vertical:h-auto", | |
| "relative self-stretch bg-input data-[orientation=horizontal]:mx-px data-[orientation=horizontal]:w-auto data-[orientation=vertical]:my-px data-[orientation=vertical]:h-auto", |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/components/ui/button-group.tsx` at line 74, Update the responsive
attribute selectors used on the separator element in the ButtonGroup styles:
replace the incorrect `data-horizontal:` and `data-vertical:` tokens with the
correct attribute selectors `data-[orientation=horizontal]:` and
`data-[orientation=vertical]:` so the Separator (from SeparatorPrimitive)
matches `data-orientation="horizontal"` / `data-orientation="vertical"` and the
`mx-px w-auto` / `my-px h-auto` sizing rules apply; locate the class string in
the ButtonGroup component (the string containing "relative self-stretch bg-input
data-horizontal:mx-px data-horizontal:w-auto data-vertical:my-px
data-vertical:h-auto") and make the substitution there.
| data-slot="field-description" | ||
| className={cn( | ||
| "text-muted-foreground text-left text-sm [[data-variant=legend]+&]:-mt-1.5 leading-normal font-normal group-has-data-[orientation=horizontal]/field:text-balance", | ||
| "text-left text-sm leading-normal font-normal text-muted-foreground group-has-data-horizontal/field:text-balance [[data-variant=legend]+&]:-mt-1.5", |
There was a problem hiding this comment.
group-has-data-horizontal/field:text-balance will never match.
This expands to .group\/field:has([data-horizontal]) & (presence-based), but neither Field nor any of its descendants render a data-horizontal attribute — Field only sets data-orientation={orientation} (line 81). The text-balance rule for horizontal orientation will therefore be dead CSS.
Also note: even the pre-refactor group-has-data-[orientation=horizontal]/field: was likely wrong, because :has() only matches descendants and data-orientation lives on the group/field element itself. The correct variant for "the field group is horizontal" is group-data-[orientation=horizontal]/field: (no has-).
🛠️ Suggested fix
- "text-left text-sm leading-normal font-normal text-muted-foreground group-has-data-horizontal/field:text-balance [[data-variant=legend]+&]:-mt-1.5",
+ "text-left text-sm leading-normal font-normal text-muted-foreground group-data-[orientation=horizontal]/field:text-balance [[data-variant=legend]+&]:-mt-1.5",📝 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.
| "text-left text-sm leading-normal font-normal text-muted-foreground group-has-data-horizontal/field:text-balance [[data-variant=legend]+&]:-mt-1.5", | |
| "text-left text-sm leading-normal font-normal text-muted-foreground group-data-[orientation=horizontal]/field:text-balance [[data-variant=legend]+&]:-mt-1.5", |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/components/ui/field.tsx` at line 136, The generated Tailwind variant
"group-has-data-horizontal/field:text-balance" will never match because Field
sets data-orientation (data-orientation={orientation}) on the group element, not
a descendant; change the variant to use the group data attribute form
"group-data-[orientation=horizontal]/field:text-balance" (or
"group-data-[orientation=vertical]" as appropriate) in the class list used by
the Field component so the text-balance rule applies when the field group has
orientation="horizontal"; update the string containing
"group-has-data-horizontal/field:text-balance" inside the Field component's
className definition accordingly.
| style={{ "--gap": spacing } as React.CSSProperties} | ||
| className={cn( | ||
| "rounded-md data-[spacing=0]:data-[variant=outline]:shadow-xs group/toggle-group flex w-fit flex-row items-center gap-[--spacing(var(--gap))] data-[orientation=vertical]:flex-col data-[orientation=vertical]:items-stretch", | ||
| "group/toggle-group flex w-fit flex-row items-center gap-[--spacing(var(--gap))] rounded-md data-[spacing=0]:data-[variant=outline]:shadow-xs data-vertical:flex-col data-vertical:items-stretch", |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
Does @base-ui/react ToggleGroup emit a data-vertical or data-horizontal boolean attribute on its root element, or only data-orientation?
💡 Result:
No, @base-ui/react ToggleGroup emits only the data-orientation boolean attribute on its root element, not data-vertical or data-horizontal.
Citations:
- 1: https://base-ui.com/react/components/toggle-group
- 2: https://github.com/mui/base-ui/blob/master/packages/react/src/toggle-group/ToggleGroup.tsx
Fix Tailwind selectors to use data-[orientation=vertical] instead of data-vertical:.
In Tailwind v4, data-vertical: matches a boolean [data-vertical] attribute, but @base-ui/react ToggleGroup only emits data-orientation={orientation}. The current data-vertical:flex-col data-vertical:items-stretch rules will never apply when orientation="vertical".
Replace with value-based selectors:
Suggested fix
- "group/toggle-group flex w-fit flex-row items-center gap-[--spacing(var(--gap))] rounded-md data-[spacing=0]:data-[variant=outline]:shadow-xs data-vertical:flex-col data-vertical:items-stretch",
+ "group/toggle-group flex w-fit flex-row items-center gap-[--spacing(var(--gap))] rounded-md data-[spacing=0]:data-[variant=outline]:shadow-xs data-[orientation=vertical]:flex-col data-[orientation=vertical]:items-stretch",📝 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.
| "group/toggle-group flex w-fit flex-row items-center gap-[--spacing(var(--gap))] rounded-md data-[spacing=0]:data-[variant=outline]:shadow-xs data-vertical:flex-col data-vertical:items-stretch", | |
| "group/toggle-group flex w-fit flex-row items-center gap-[--spacing(var(--gap))] rounded-md data-[spacing=0]:data-[variant=outline]:shadow-xs data-[orientation=vertical]:flex-col data-[orientation=vertical]:items-stretch", |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/components/ui/toggle-group.tsx` at line 45, The Tailwind selectors in the
ToggleGroup class string use the boolean-style `data-vertical:` which never
matches the component's `data-orientation={orientation}`; update the class
definition in src/components/ui/toggle-group.tsx (the string containing
"group/toggle-group ... data-vertical:flex-col data-vertical:items-stretch") to
use value-based selectors `data-[orientation=vertical]:flex-col` and
`data-[orientation=vertical]:items-stretch` (or equivalent
`data-[orientation=vertical]:...`) so the vertical orientation styles apply when
orientation="vertical".
| data-spacing={context.spacing} | ||
| className={cn( | ||
| "data-[state=on]:bg-muted group-data-[spacing=0]/toggle-group:rounded-none group-data-[spacing=0]/toggle-group:px-2 group-data-[spacing=0]/toggle-group:shadow-none group-data-horizontal/toggle-group:data-[spacing=0]:first:rounded-l-md group-data-vertical/toggle-group:data-[spacing=0]:first:rounded-t-md group-data-horizontal/toggle-group:data-[spacing=0]:last:rounded-r-md group-data-vertical/toggle-group:data-[spacing=0]:last:rounded-b-md shrink-0 focus:z-10 focus-visible:z-10 group-data-horizontal/toggle-group:data-[spacing=0]:data-[variant=outline]:border-l-0 group-data-vertical/toggle-group:data-[spacing=0]:data-[variant=outline]:border-t-0 group-data-horizontal/toggle-group:data-[spacing=0]:data-[variant=outline]:first:border-l group-data-vertical/toggle-group:data-[spacing=0]:data-[variant=outline]:first:border-t", | ||
| "shrink-0 group-data-[spacing=0]/toggle-group:rounded-none group-data-[spacing=0]/toggle-group:px-2 group-data-[spacing=0]/toggle-group:shadow-none focus:z-10 focus-visible:z-10 group-data-[spacing=0]/toggle-group:has-data-[icon=inline-end]:pr-1.5 group-data-[spacing=0]/toggle-group:has-data-[icon=inline-start]:pl-1.5 group-data-horizontal/toggle-group:data-[spacing=0]:first:rounded-l-md group-data-vertical/toggle-group:data-[spacing=0]:first:rounded-t-md group-data-horizontal/toggle-group:data-[spacing=0]:last:rounded-r-md group-data-vertical/toggle-group:data-[spacing=0]:last:rounded-b-md data-[state=on]:bg-muted group-data-horizontal/toggle-group:data-[spacing=0]:data-[variant=outline]:border-l-0 group-data-vertical/toggle-group:data-[spacing=0]:data-[variant=outline]:border-t-0 group-data-horizontal/toggle-group:data-[spacing=0]:data-[variant=outline]:first:border-l group-data-vertical/toggle-group:data-[spacing=0]:data-[variant=outline]:first:border-t", |
There was a problem hiding this comment.
Downstream impact of the data-horizontal/data-vertical selectors.
Same root cause as the comment on line 45: the group-data-horizontal/toggle-group:* and group-data-vertical/toggle-group:* rules look up an ancestor with [data-horizontal] / [data-vertical], which the parent does not currently set. As a result, the per-orientation first:rounded-*, last:rounded-*, and data-[variant=outline]:border-* rules will likely no-op in production. If the verification on line 45 confirms this, please apply the equivalent value-based selectors here too.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/components/ui/toggle-group.tsx` at line 75, The orientation-specific
selectors in the long class string (the ones starting with
group-data-horizontal/toggle-group: and group-data-vertical/toggle-group:) rely
on an ancestor setting [data-horizontal]/[data-vertical] which we don't set, so
those rules no-op; update those selectors to use the value-based form used
elsewhere (e.g. group-data-[orientation=horizontal]/toggle-group:... and
group-data-[orientation=vertical]/toggle-group:...) for every occurrence that
controls per-orientation styling (the first:rounded-*, last:rounded-*, and
data-[variant=outline]:border-* rules) so the orientation-specific rounding and
border rules apply in production.
| execute(input) { | ||
| const path = readStringField(input, 'path') | ||
|
|
||
| if (!path?.startsWith('/docs')) { | ||
| return { error: 'path must begin with /docs' } | ||
| } | ||
|
|
||
| window.location.assign(path) | ||
| return { opened: path } | ||
| }, |
There was a problem hiding this comment.
open_docs path validation is too permissive.
path.startsWith('/docs') accepts inputs like /docs..%2f..%2fadmin, /docsanything, or /docs/\\evil.com (protocol-relative once the browser normalizes backslashes). Since this is invoked by an LLM-supplied argument and triggers a top-level navigation, it should be tightened.
🛡️ Suggested hardening
execute(input) {
const path = readStringField(input, 'path')
- if (!path?.startsWith('/docs')) {
- return { error: 'path must begin with /docs' }
- }
-
- window.location.assign(path)
- return { opened: path }
+ if (
+ !path ||
+ !/^\/docs(?:\/[\w\-./]*)?$/.test(path) ||
+ path.includes('..') ||
+ path.startsWith('//')
+ ) {
+ return { error: 'path must be an absolute /docs path' }
+ }
+
+ window.location.assign(path)
+ return { opened: path }
},📝 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.
| execute(input) { | |
| const path = readStringField(input, 'path') | |
| if (!path?.startsWith('/docs')) { | |
| return { error: 'path must begin with /docs' } | |
| } | |
| window.location.assign(path) | |
| return { opened: path } | |
| }, | |
| execute(input) { | |
| const path = readStringField(input, 'path') | |
| if ( | |
| !path || | |
| !/^\/docs(?:\/[\w\-./]*)?$/.test(path) || | |
| path.includes('..') || | |
| path.startsWith('//') | |
| ) { | |
| return { error: 'path must be an absolute /docs path' } | |
| } | |
| window.location.assign(path) | |
| return { opened: path } | |
| }, |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/components/webmcp-provider.tsx` around lines 107 - 116, The execute
method (open_docs handler) currently allows dangerous paths via
path.startsWith('/docs'); tighten validation by: 1) require the normalized
pathname to be within the /docs prefix (use new URL(path,
window.location.origin).pathname and ensure it equals '/docs' or
startsWith('/docs/')); 2) reject any path containing backslashes or
percent-encoded traversal (reject if path includes '\\' or /%2f|%5c|%2e%2e/i or
if decodeURIComponent(path) contains '..' or '\\'); 3) after validation perform
a safe navigation using window.location.assign(new URL(path,
window.location.origin).toString()) and return opened path; update references:
execute, readStringField, and window.location.assign accordingly.
| export function WebMcpProvider() { | ||
| useEffect(() => { | ||
| void navigator.modelContext?.provideContext({ tools }) | ||
| }, []) | ||
|
|
||
| return null | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
WebMCP navigator.modelContext provideContext idempotent unregister
💡 Result:
navigator.modelContext is the core API in WebMCP (Web Model Context Protocol), a W3C draft spec from the Web Machine Learning Community Group that enables websites to register JavaScript tools callable by AI agents. Available only in secure contexts (HTTPS). Key methods: - registerTool(tool): Registers a single tool. Throws InvalidStateError on duplicate name, empty name/description, or invalid inputSchema. - unregisterTool(name): Removes tool by name. No-op if not registered. Not natively idempotent: registerTool fails on duplicates to prevent accidental overwrites. For idempotent registration (e.g., HMR/SPA re-init), check existing tools first via navigator.modelContextTesting.listTools (testing API), then register if absent: const tools = navigator.modelContextTesting?.listTools || []; if (!tools.some(t => t.name === tool.name)) { navigator.modelContext.registerTool(tool); } Or safe unregister-then-register pattern: try { navigator.modelContext.unregisterTool(tool.name); } catch {} navigator.modelContext.registerTool(tool); provideContext: Early proposal method to atomically replace all tools (clears existing first). Removed from current spec due to security concerns (allows overwriting others' tools). See GitHub issue #101, resolved by dropping in PR #132. Official spec: https://webmachinelearning.github.io/webmcp/ (Draft Community Group Report 23 Apr 2026). Core IDL: [Exposed=Window, SecureContext] interface ModelContext { undefined registerTool(ModelContextTool tool, optional ModelContextRegisterToolOptions options = {}); }; Chrome 146+ Canary supports natively (behind flag). Polyfills like @mcp-b/webmcp-polyfill available.
Citations:
- 1: https://webmachinelearning.github.io/webmcp/
- 2: https://docs.mcp-b.ai/explanation/webmcp/standard-api
- 3:
navigator.modelContext.provideContextallows overwriting of previously registered tools in the same environment webmachinelearning/webmcp#101 - 4: Make WebMCP tool registration idempotent via navigator.modelContext WordPress/wordpress-playground#3416
- 5: https://github.com/webmachinelearning/webmcp
- 6: https://docs.mcp-b.ai/explanation/architecture/tool-lifecycle-and-context-replacement
- 7: https://mcpcat.io/guides/register-first-webmcp-tool
🏁 Script executed:
# First, let's look at the actual file around the specified lines
cat -n src/components/webmcp-provider.tsx | head -n 130 | tail -n 20Repository: Debbl/shadcn-hooks
Length of output: 598
🏁 Script executed:
# Search for all uses of provideContext or registerTool in the codebase
rg "provideContext|registerTool" --type ts --type tsx -nRepository: Debbl/shadcn-hooks
Length of output: 90
🏁 Script executed:
# Check for any imports or type definitions related to modelContext
rg "modelContext|WebMCP|navigator\." --type ts --type tsx -B 2 -A 2 src/components/webmcp-provider.tsxRepository: Debbl/shadcn-hooks
Length of output: 90
🏁 Script executed:
# Search for any imports of WebMCP or tool definitions
rg "tools" src/components/webmcp-provider.tsx -B 5 -A 2Repository: Debbl/shadcn-hooks
Length of output: 516
🏁 Script executed:
# Look for imports at the top of the file
head -n 30 src/components/webmcp-provider.tsxRepository: Debbl/shadcn-hooks
Length of output: 632
🏁 Script executed:
# Search for any WebMCP imports or setup in the project
rg "webmcp|modelContext" -iRepository: Debbl/shadcn-hooks
Length of output: 1963
Migrate from deprecated provideContext to the standard WebMCP API.
The provideContext method used here was removed from the official WebMCP specification (as of April 2026, per PR #132) due to security concerns. The current standard API provides only registerTool, which throws InvalidStateError on duplicate names—not idempotent by design.
Additionally, with the empty dependency array and React 19 Strict Mode, the effect fires twice during development. Migrate to the official registerTool API, checking for existing tools first to avoid duplicates:
const existing = navigator.modelContextTesting?.listTools || [];
tools.forEach(tool => {
if (!existing.some(t => t.name === tool.name)) {
navigator.modelContext?.registerTool(tool);
}
});Or use the unregister-then-register pattern for idempotency. Define the tools array in the dependency array and add cleanup to prevent accumulation across re-mounts.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/components/webmcp-provider.tsx` around lines 120 - 126, The
WebMcpProvider currently calls the deprecated
navigator.modelContext.provideContext inside a useEffect; replace it with the
standard API by registering each tool via navigator.modelContext.registerTool
while avoiding duplicate registrations (use
navigator.modelContextTesting?.listTools or navigator.modelContext?.listTools to
check existing tool names before calling registerTool), include the tools array
in the useEffect dependency list, and add a cleanup/unregister step or
unregister-then-register pattern to ensure idempotency across re-mounts and
React 19 Strict Mode double-invocations; update references to provideContext to
use registerTool and listTools (and optionally unregister) inside the
WebMcpProvider function.
| export const linkHeaderValue = [ | ||
| '</.well-known/api-catalog>; rel="api-catalog"', | ||
| '</docs>; rel="service-doc"', | ||
| '</index.md>; rel="alternate"; type="text/markdown"', | ||
| '</.well-known/agent-skills/index.json>; rel="service-desc"; type="application/json"', | ||
| ].join(', ') |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Confirm route handlers / static assets exist for each advertised path
fd -HI -t f 'route\.ts' src/app/.well-known src/app | sort
fd -HI -t f 'llms(\.|-full\.)txt' .
fd -HI -t d 'docs' src/app | headRepository: Debbl/shadcn-hooks
Length of output: 1067
🏁 Script executed:
cat -n src/lib/agent-discovery.tsRepository: Debbl/shadcn-hooks
Length of output: 6601
🏁 Script executed:
# Check for dynamic routes under /docs
fd -HI -t f 'route\.(ts|tsx)' src/app/docs
# Search for /r route
fd -HI -t f 'route\.(ts|tsx)' src/app/rRepository: Debbl/shadcn-hooks
Length of output: 215
🏁 Script executed:
# Check docs directory structure
ls -la src/app/docs/ | head -20
# Search for /r anywhere
fd -HI 'r' src/app | grep -E '^\./.*r$|^./.*r/$' | head -20
# Check if there are page files
find src/app/docs -name 'page.*' -o -name 'layout.*' | sortRepository: Debbl/shadcn-hooks
Length of output: 445
🏁 Script executed:
# Search for /r route anywhere
fd -HI -type d 'r' src/app | head -20
# Also search with rg for any route files that might handle /r
rg -t ts -t tsx 'export.*route' src/app | grep -i '\br\b'Repository: Debbl/shadcn-hooks
Length of output: 261
🏁 Script executed:
# Search for directories named 'r' under src/app
find src/app -maxdepth 1 -type d -name 'r'
# Search for route files that might handle /r
rg -t ts '/r' src/app --max-count 5Repository: Debbl/shadcn-hooks
Length of output: 141
🏁 Script executed:
# Final verification: search entire codebase for /r route
find src/app -name 'route.*' -type f | xargs rg 'export.*route|^/r$'Repository: Debbl/shadcn-hooks
Length of output: 46
🏁 Script executed:
# Direct check for /r directory
ls -la src/app/r 2>&1 || echo "No /r directory found"
# List all route.ts files to confirm no /r route exists
find src/app -name 'route.ts' -o -name 'route.tsx' | sortRepository: Debbl/shadcn-hooks
Length of output: 770
Fix broken /r route advertised in agentSkillMarkdown.
The /r path referenced in agentSkillMarkdown (line 39) has no corresponding route handler. All other advertised URLs in linkHeaderValue and homeMarkdown resolve correctly:
linkHeaderValueroutes: ✓/.well-known/api-catalog,/.well-known/agent-skills/index.json,/index.md,/docshomeMarkdownroutes: ✓/llms.txt,/llms-full.txt,/docs/*(dynamic route)agentSkillMarkdownbroken route: ✗/r(no route handler exists)
Either implement the missing /r route or remove the reference from agentSkillMarkdown.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/lib/agent-discovery.ts` around lines 4 - 9, The agentSkillMarkdown string
references a non-existent "/r" route causing a broken advertised URL; update
agentSkillMarkdown to either remove the "/r" reference or replace it with an
existing route (for example one of the routes already used in linkHeaderValue or
homeMarkdown such as "/docs" or "/index.md"), and ensure advertised endpoints in
agentSkillMarkdown match the actual handlers; locate the agentSkillMarkdown
constant in src/lib/agent-discovery.ts and edit its contents so no advertised
path lacks a corresponding route handler.
Summary by CodeRabbit
New Features
Improvements