Skip to content

Mcp redesign 2#1361

Merged
duckduckhero merged 18 commits intomainfrom
mcp-redesign-2
Aug 18, 2025
Merged

Mcp redesign 2#1361
duckduckhero merged 18 commits intomainfrom
mcp-redesign-2

Conversation

@duckduckhero
Copy link
Contributor

@duckduckhero duckduckhero commented Aug 18, 2025

don't merge yet - work in progress


Summary by cubic

Adds MCP tool calling to chat with a new MCP settings tab, improved streaming UX, and persistent tool event messages. Adds a database column to store message types and updates prompts to reflect tool availability.

  • New Features

    • MCP tool calling in chat
      • Discovers enabled servers (SSE) and registers tools dynamically.
      • Emits start/result/error messages for tools and saves them to the database.
      • Supported on gpt-4.1, gpt-4o, claude sonnet 4, or Hypr Cloud.
    • MCP settings tab
      • Add up to 3 servers with optional auth headers; enable/disable per server.
      • Changes persist and trigger mcp-tools cache invalidation.
    • Streaming and UX
      • Debounced “Thinking…” indicator and smooth auto-scroll.
      • Separate text vs tool messages with stable IDs; skip DB sync while generating.
      • Better error handling and persistence when tools run mid-response.
    • Prompts and limits
      • System prompt lists available MCP tools when supported.
      • Enhance prompt forbids exposing thought process or explanations.
      • Free tier limit set to 3 user messages per session.
  • Migration

    • Adds chat_messages.type with new enums; updates Rust types and TS bindings. Migration runs on startup; no manual steps.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 18, 2025

Note

Other AI code review bot(s) detected

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

📝 Walkthrough

Walkthrough

Adds MCP tooling and UI: new settings page and hook for MCP servers, dynamic tool wrappers, streaming overhaul that emits typed chat events and persists partial/tool messages, DB migration and types for message type, debounced “Thinking” indicator, template updates, and a few debug logs and exports.

Changes

Cohort / File(s) Summary
Chat UI & types
apps/desktop/src/components/right-panel/components/chat/chat-messages-view.tsx, apps/desktop/src/components/right-panel/components/chat/message-content.tsx, apps/desktop/src/components/right-panel/components/chat/types.ts
Adds debounced ThinkingIndicator and new props (isGenerating, isStreamingText); replaces prior generating UI with tool-event card rendering; introduces discriminated message type (text-delta, tool-start, tool-result, tool-error, generating).
Chat logic & queries
apps/desktop/src/components/right-panel/hooks/useChatLogic.ts, apps/desktop/src/components/right-panel/hooks/useChatQueries.ts, apps/desktop/src/components/right-panel/views/chat-view.tsx
Reworks streaming to use fullStream, emits typed chunks (text-delta/tool-*), persists streaming segments and tool events, integrates MCP tools, returns isStreamingText, tightens non-pro gating, and conditions query sync during generation.
MCP tools infrastructure
apps/desktop/src/components/right-panel/hooks/useMcpTools.ts, apps/desktop/src/components/right-panel/utils/chat-utils.ts, packages/utils/src/ai.ts
New useMcpTools hook with per-server MCP client cache, dynamicTool wrappers, client lifecycle utilities (clear/close); adds prepareMessageHistory to include MCP tools and context; re-exports dynamicTool and experimental_createMCPClient.
Settings UI & routing
apps/desktop/src/components/settings/components/types.ts, apps/desktop/src/components/settings/components/tab-icon.tsx, apps/desktop/src/components/settings/views/mcp.tsx, apps/desktop/src/components/settings/views/index.ts, apps/desktop/src/routes/app.settings.tsx
Adds MCP settings tab and icon, new MCP settings view (CRUD for MCP servers with headers, enable/disable, add/delete), re-exports MCP view, and renders MCP tab in settings route.
i18n & broadcasts
apps/desktop/src/locales/en/messages.po, apps/desktop/src/locales/ko/messages.po, apps/desktop/src/utils/broadcast.ts
Adds MCP translation key and updates source anchors; broadcast listener now invalidates ["mcp-tools"] when relevant broadcasts arrive.
DB schema & server-side types
crates/db-user/src/chat_messages_migration_1.sql, crates/db-user/src/chat_messages_ops.rs, crates/db-user/src/chat_messages_types.rs, crates/db-user/src/init.rs, crates/db-user/src/lib.rs
Adds type TEXT column (DEFAULT 'text-delta'), introduces ChatMessageType enum and r#type field on ChatMessage, updates inserts/upserts and seed data, and registers the migration.
Templates
crates/template/assets/ai_chat_system.jinja, crates/template/assets/enhance.system.jinja
Adds dynamic MCP tools listing to system prompt template, adjusts model/api gating, and tightens enhance.system output rules (forbid including thought process).
Editor logs & plugin bindings
apps/desktop/src/components/editor-area/index.tsx, plugins/db/js/bindings.gen.ts
Adds console.log debug statements in useEnhanceMutation (model/provider) and updates generated DB bindings to include ChatMessageType and ChatMessage.type field.
Other
apps/desktop/src/components/settings/components/ai/llm-local-view.tsx, apps/desktop/src/locales/*
Minor ordering change in local model selection mutation calls; locale anchor updates for settings.

Sequence Diagram(s)

sequenceDiagram
  participant UI as ChatView / ChatMessagesView
  participant Hook as useChatLogic
  participant MCP as useMcpTools / MCP Clients
  participant LLM as LLM (fullStream)
  participant DB as DB (chat_messages)

  UI->>Hook: submit user message
  Hook->>DB: persist user message
  Hook->>MCP: fetch enabled servers/tools
  MCP-->>Hook: dynamicTool[] (wrapped)
  Hook->>LLM: start fullStream (history + tools)
  activate LLM
  loop streaming
    LLM-->>Hook: chunk (text-delta | tool-start | tool-result | tool-error)
    alt text-delta
      Hook->>DB: upsert text-delta segment
      Hook-->>UI: update current AI message
    else tool-*
      Hook->>DB: insert tool-* message
      Hook-->>UI: append tool card
    end
  end
  LLM-->>Hook: finish
  deactivate LLM
  Hook->>MCP: close clients
  Hook-->>UI: set isGenerating=false, isStreamingText=false
Loading
sequenceDiagram
  participant Hook as useChatLogic
  participant UI as ChatMessagesView
  participant State as Local debounce state

  Hook-->>UI: isGenerating / isStreamingText
  UI->>State: evaluate shouldShowThinking
  State-->>State: debounce 200ms
  State-->>UI: showThinking=true
  UI->>UI: render ThinkingIndicator and scroll
  Hook-->>UI: streaming text arrives
  UI->>State: showThinking=false (re-render)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch mcp-redesign-2

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

‼️ IMPORTANT
Auto-reply has been disabled for this repository in the CodeRabbit settings. The CodeRabbit bot will not respond to your replies unless it is explicitly tagged.

  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🔭 Outside diff range comments (1)
crates/db-user/src/chat_messages_migration_1.sql (1)

1-5: Make type column NOT NULL with DEFAULT

SQLite (and Postgres) will populate existing rows with a constant DEFAULT, so an extra UPDATE isn’t needed. To align the schema with your non-Option Rust enum, mark the new column NOT NULL:

• File: crates/db-user/src/chat_messages_migration_1.sql

 ALTER TABLE chat_messages
-ADD COLUMN type TEXT DEFAULT 'text-delta';
+ADD COLUMN type TEXT NOT NULL DEFAULT 'text-delta';
♻️ Duplicate comments (1)
apps/desktop/src/components/settings/components/types.ts (1)

37-37: Single source of truth for icons to avoid divergence with TabIcon

There is historical drift between TABS icons and TabIcon’s switch for some tabs. Suggest consuming TABS in TabIcon (see TabIcon comment) to prevent future inconsistencies.

🧹 Nitpick comments (29)
apps/desktop/src/components/editor-area/index.tsx (1)

480-482: Gate debug logs and avoid dumping full provider object

The raw console.log calls at lines 480–482 will ship to production and can expose internal details. Replace them with gated debug statements in development builds:

--- a/apps/desktop/src/components/editor-area/index.tsx
+++ b/apps/desktop/src/components/editor-area/index.tsx
@@ -478,6 +478,11 @@
       : provider.languageModel("defaultModel");
 
-      console.log("model: ", model);
-      console.log("provider: ", provider);
+      if (import.meta.env.DEV) {
+        console.debug("[enhance] model:", model);
+        console.debug(
+          "[enhance] provider:",
+          (provider as any)?.constructor?.name ?? "unknown"
+        );
+      }
 
     if (sessionId !== onboardingSessionId) {

• File to update:
– apps/desktop/src/components/editor-area/index.tsx (lines 480–482)

Optionally, scan for any remaining unguarded console.* calls before merging:

#!/bin/bash
rg -n -C2 'console\.(log|debug|info|warn|error)\(' -g '*.ts' -g '*.tsx'
apps/desktop/src/utils/broadcast.ts (1)

78-82: MCP-tools cache invalidation verified

All occurrences of "mcp-tools" in your query keys have been located and match the invalidation logic:

  • apps/desktop/src/utils/broadcast.ts (lines 78–82) – invalidates on keys[0] === "mcp-tools".
  • apps/desktop/src/components/right-panel/hooks/useMcpTools.ts (line 12) – issues useQuery({ queryKey: ["mcp-tools"], … }).

This ensures the hook’s cache is kept in sync across windows.

Optional future-proofing: switch to a predicate to catch any parameterized variants (e.g. ["mcp-tools", id]):

-      if (keys[0] === "mcp-tools") {
-        queryClient.invalidateQueries({
-          queryKey: ["mcp-tools"],
-        });
-      }
+      if (keys[0] === "mcp-tools") {
+        queryClient.invalidateQueries({
+          predicate: (q) =>
+            Array.isArray(q.queryKey) &&
+            q.queryKey[0] === "mcp-tools",
+        });
+      }
crates/template/assets/enhance.system.jinja (2)

38-39: Strengthened no–chain-of-thought directive: consider tightening for consistency

Good addition. To avoid mixed signals with later “Think step by step”, consider removing or rephrasing that line to reduce the chance of models emitting reasoning.

-CRITICAL: Do not include any of your thought process or comments. Just return the end result of the enhanced note, do not explain or justify your results (regardless of whether template is given or not)
+CRITICAL: Do not include any thought process or comments. Return only the final enhanced note; do not explain or justify results (regardless of whether a template is provided).

Outside this hunk, consider updating the guidance at Line 52:

-You will be given multiple inputs from the user. Below are useful information that you will use to write the best enhanced meeting note. Think step by step.
+You will be given multiple inputs from the user. Use them carefully to produce the best enhanced meeting note. Plan internally; do not include your reasoning in the output.

85-86: Duplicate/no‑CoT instruction: consistent and clear, minor wording tweak

To keep tone consistent with the earlier CRITICAL block and avoid accidental CoT leakage, small clarity pass suggested.

-- Go straight to the actual note part, never include any of your thought process or comments. Just return the meeting note, do not explain or justify your results (regardless of whether template is given or not)
+- Go straight to the note content. Never include thought process or comments. Return only the meeting note; do not explain or justify results (regardless of whether a template is provided).
apps/desktop/src/locales/ko/messages.po (1)

1029-1032: Avoid empty label: provide a Korean translation (or fallback) for “MCP”.

Empty msgstr can render as a blank tab label depending on i18n fallback behavior. Safe choice is to mirror English here.

Apply this diff:

-#: src/routes/app.settings.tsx:73
-msgid "MCP"
-msgstr ""
+#: src/routes/app.settings.tsx:73
+msgid "MCP"
+msgstr "MCP"
crates/db-user/src/chat_messages_types.rs (1)

12-26: Reduce duplication with enum-level rename directives.

Use enum-level kebab-case for serde and strum to remove per-variant attributes.

Apply this diff:

 user_common_derives! {
-    #[derive(strum::EnumString, strum::Display)]
+    #[derive(strum::EnumString, strum::Display)]
+    #[serde(rename_all = "kebab-case")]
+    #[strum(serialize_all = "kebab-case")]
     pub enum ChatMessageType {
-       #[serde(rename = "text-delta")]
-       #[strum(serialize = "text-delta")]
        TextDelta,
-       #[serde(rename = "tool-start")]
-       #[strum(serialize = "tool-start")]
        ToolStart,
-       #[serde(rename = "tool-result")]
-       #[strum(serialize = "tool-result")]
        ToolResult,
-       #[serde(rename = "tool-error")]
-       #[strum(serialize = "tool-error")]
        ToolError,
     }
 }
crates/db-user/src/chat_messages_ops.rs (1)

45-49: *Harden reads against legacy NULLs; avoid SELECT .

Coalesce type to the default and enumerate columns explicitly to prevent decode failures and insulate future schema changes.

Apply this diff:

-            .query(
-                "SELECT * FROM chat_messages 
-                WHERE group_id = ? 
-                ORDER BY created_at ASC",
-                vec![group_id.into()],
-            )
+            .query(
+                "SELECT
+                    id,
+                    group_id,
+                    created_at,
+                    role,
+                    content,
+                    COALESCE(type, 'text-delta') AS type
+                 FROM chat_messages
+                 WHERE group_id = ?
+                 ORDER BY created_at ASC",
+                vec![group_id.into()],
+            )
apps/desktop/src/components/settings/views/mcp.tsx (3)

85-96: Avoid persisting on every keystroke to the Tauri backend.

handleUpdateServerHeader mutates and persists on each onChange, which can spam setServers and race mutations. Prefer debouncing, saving on blur, or a dedicated “Save” action to batch updates.

Example direction (no full refactor here):

  • Update local state onChange.
  • Trigger saveServersMutation onBlur or via an explicit Save button.

214-216: Use a stable key instead of the array index.

Index keys can cause subtle UI issues on insert/delete. Use a stable identifier when possible (e.g., URL) or a composite.

- key={index}
+ key={`${server.url}-${index}`}

21-25: Trim “what” comments or add “why”.

Comment on Line 21 states the obvious. Either remove or add rationale (e.g., why lazy-loading is required or any constraints).

apps/desktop/src/components/settings/components/tab-icon.tsx (1)

17-41: Avoid duplicate tab→icon mappings (centralize to TABS to prevent drift)

You now define icons in both TABS and this switch. This easily drifts (e.g., some tabs already differ between files). Consider deriving the icon from TABS and rendering it here to keep a single source of truth.

Apply this diff to simplify TabIcon to use the TABS mapping (and avoid future inconsistencies):

 import { type Tab } from "./types";
+import { TABS } from "./types";
 
 export function TabIcon({ tab }: { tab: Tab }) {
-  switch (tab) {
-    case "general":
-      return <SettingsIcon className="h-4 w-4" />;
-    case "notifications":
-      return <BellIcon className="h-4 w-4" />;
-    case "sound":
-      return <AudioLinesIcon className="h-4 w-4" />;
-    case "feedback":
-      return <MessageSquareIcon className="h-4 w-4" />;
-    case "ai-llm":
-      return <SparklesIcon className="h-4 w-4" />;
-    case "ai-stt":
-      return <BirdIcon className="h-4 w-4" />;
-    case "calendar":
-      return <CalendarIcon className="h-4 w-4" />;
-    case "templates":
-      return <LayoutTemplateIcon className="h-4 w-4" />;
-    case "integrations":
-      return <BlocksIcon className="h-4 w-4" />;
-    case "billing":
-      return <CreditCardIcon className="h-4 w-4" />;
-    case "mcp":
-      return <NetworkIcon className="h-4 w-4" />;
-    default:
-      return null;
-  }
+  const Icon = TABS.find(t => t.name === tab)?.icon;
+  return Icon ? <Icon className="h-4 w-4" /> : null;
 }

Note: You can then drop the individual icon imports at the top of this file.

apps/desktop/src/components/right-panel/components/chat/message-content.tsx (3)

126-152: Prefer Tailwind classes over inline styles; add aria-live for status updates

Inline styles won’t adapt to themes/dark mode and are harder to keep consistent. Use utility classes and mark as live region so assistive tech announces tool events.

-    return (
-      <div
-        style={{
-          backgroundColor: "rgb(250 250 250)",
-          border: "1px solid rgb(229 229 229)",
-          borderRadius: "6px",
-          padding: "12px 16px",
-        }}
-      >
-        <div
-          style={{
-            color: "rgb(115 115 115)",
-            fontSize: "0.875rem",
-            display: "flex",
-            alignItems: "center",
-            gap: "8px",
-          }}
-        >
-          <PencilRuler size={16} color="rgb(115 115 115)" />
-          <span style={{ fontWeight: "400" }}>
-            Called tool: {message.content}
-          </span>
-        </div>
-      </div>
-    );
+    return (
+      <div className="bg-neutral-50 border border-neutral-200 rounded-md px-4 py-3" aria-live="polite">
+        <div className="flex items-center gap-2 text-neutral-500 text-sm">
+          <PencilRuler size={16} className="text-neutral-500" />
+          <span className="font-normal">Called tool: {message.content}</span>
+        </div>
+      </div>
+    );

154-180: Same here: switch to classes for theme consistency

Unify styling with utility classes to match the rest of the app.

-    return (
-      <div
-        style={{
-          backgroundColor: "rgb(248 248 248)",
-          border: "1px solid rgb(224 224 224)",
-          borderRadius: "6px",
-          padding: "12px 16px",
-        }}
-      >
-        <div
-          style={{
-            color: "rgb(115 115 115)",
-            fontSize: "0.875rem",
-            display: "flex",
-            alignItems: "center",
-            gap: "8px",
-          }}
-        >
-          <PencilRuler size={16} color="rgb(115 115 115)" />
-          <span style={{ fontWeight: "400" }}>
-            {message.content}
-          </span>
-        </div>
-      </div>
-    );
+    return (
+      <div className="bg-neutral-50 border border-neutral-200 rounded-md px-4 py-3" aria-live="polite">
+        <div className="flex items-center gap-2 text-neutral-500 text-sm">
+          <PencilRuler size={16} className="text-neutral-500" />
+          <span className="font-normal">{message.content}</span>
+        </div>
+      </div>
+    );

182-207: Use an error-specific icon/styling for tool-error; avoid inline styles

Minor UX nit: show an alert icon and error hues to differentiate error from normal tool output; also switch to classes.

-import { PencilRuler } from "lucide-react";
+import { PencilRuler, AlertCircle } from "lucide-react";
@@
-  if (message.type === "tool-error") {
-    return (
-      <div
-        style={{
-          backgroundColor: "rgb(252 252 252)",
-          border: "1px solid rgb(229 229 229)",
-          borderRadius: "6px",
-          padding: "12px 16px",
-        }}
-      >
-        <div
-          style={{
-            color: "rgb(115 115 115)",
-            fontSize: "0.875rem",
-            display: "flex",
-            alignItems: "center",
-            gap: "8px",
-          }}
-        >
-          <PencilRuler size={16} color="rgb(115 115 115)" />
-          <span style={{ fontWeight: "400" }}>
-            Tool Error: {message.content}
-          </span>
-        </div>
-      </div>
-    );
-  }
+  if (message.type === "tool-error") {
+    return (
+      <div className="bg-red-50 border border-red-200 rounded-md px-4 py-3" aria-live="polite">
+        <div className="flex items-center gap-2 text-red-600 text-sm">
+          <AlertCircle size={16} className="text-red-600" />
+          <span className="font-normal">Tool Error: {message.content}</span>
+        </div>
+      </div>
+    );
+  }
apps/desktop/src/components/right-panel/hooks/useChatQueries.ts (1)

99-107: Potential stale state after generation finishes; refetch once to sync DB

With the justFinishedGenerating guard, you skip syncing on the transition frame and rely on another change to retrigger. If the query data doesn’t change immediately, UI may remain stale. Refetch once when generation completes, and gate the log for dev only.

-    if (chatMessagesQuery.data) {
-      if (!isGenerating && !justFinishedGenerating) {
-        // Safe to sync from database
-        setMessages(chatMessagesQuery.data);
-        setHasChatStarted(chatMessagesQuery.data.length > 0);
-      } else {
-        // Currently generating - DON'T override local state
-        console.log("Skipping DB sync - currently generating");
-      }
-    }
+    if (chatMessagesQuery.data) {
+      if (!isGenerating && !justFinishedGenerating) {
+        // Safe to sync from database
+        setMessages(chatMessagesQuery.data);
+        setHasChatStarted(chatMessagesQuery.data.length > 0);
+      } else if (justFinishedGenerating) {
+        // Why: Force-refresh after completion so the next pass syncs fresh DB state
+        chatMessagesQuery.refetch();
+      } else {
+        // Currently generating - DON'T override local state
+        if (process.env.NODE_ENV !== "production") {
+          console.debug("Skipping DB sync - currently generating");
+        }
+      }
+    }
apps/desktop/src/components/right-panel/components/chat/chat-messages-view.tsx (2)

14-41: Avoid injecting a <style> tag per render; prefer CSS class or Tailwind

Embedding a <style> tag in a component leads to repeated style injection and harder maintenance. Move keyframes/classes to a stylesheet or Tailwind utilities.

For example, extract the CSS to a module or global stylesheet and keep ThinkingIndicator purely presentational. If you want, I can draft a Tailwind-friendly variant with equivalent dot animations.


110-112: Comment style: prefer “why” over “what”

The comment describes what the indicator does. Consider either removing it (code is self-explanatory) or replacing with a brief “why” (e.g., “Debounce to avoid flicker on short-lived generating phases”).

Apply this diff to reword:

-      {/* Thinking indicator with debounce - no flicker! */}
+      {/* Debounce to avoid flicker during short-lived generating states */}
apps/desktop/src/components/right-panel/hooks/useMcpTools.ts (3)

6-8: Client cache could race under concurrent initializations

Two concurrent callers may both miss the cache and create duplicate clients for the same URL. A promise cache (per-URL in-flight creation) would dedupe safely.

I can provide a follow-up diff that adds mcpClientPromiseCache: Map<string, Promise> and reuses it to serialize client creation per URL.


120-128: Close-and-clear to prevent stale cached clients

closeMcpClients closes clients but leaves them in the cache. Clearing them avoids reusing closed instances inadvertently.

Apply this diff:

 export function closeMcpClients() {
   for (const client of mcpClientCache.values()) {
     try {
       client.close();
     } catch (error) {
       console.error(`[MCP] Error closing client:`, error);
     }
   }
+  mcpClientCache.clear();
 }

10-19: Excessive logging—gate behind a debug flag

Current logs print server lists, tool names, and timings. Consider gating behind a debug flag to avoid noisy consoles and potential information exposure in production.

If desired, I can add a simple isDebug helper (e.g., process.env.NODE_ENV !== "production") and wrap logs.

apps/desktop/src/components/right-panel/utils/chat-utils.ts (3)

115-130: Shadowing outer sessionData creates confusion

The local const sessionData inside the mention.type === "note" branch shadows the outer parameter sessionData, harming readability and risking mistakes. Rename the inner variable.

Apply this diff:

-          const sessionData = await dbCommands.getSession({ id: mention.id });
+          const noteSession = await dbCommands.getSession({ id: mention.id });
-          if (sessionData) {
+          if (noteSession) {
-            if (sessionData.enhanced_memo_html && sessionData.enhanced_memo_html.trim() !== "") {
-              noteContent = sessionData.enhanced_memo_html;
+            if (noteSession.enhanced_memo_html && noteSession.enhanced_memo_html.trim() !== "") {
+              noteContent = noteSession.enhanced_memo_html;
-            } else if (sessionData.raw_memo_html && sessionData.raw_memo_html.trim() !== "") {
-              noteContent = sessionData.raw_memo_html;
+            } else if (noteSession.raw_memo_html && noteSession.raw_memo_html.trim() !== "") {
+              noteContent = noteSession.raw_memo_html;
             } else {
               continue;
             }

71-87: System prompt logging may leak sensitive data

console.log("system prompt", systemContent) can expose user/session data in logs. Gate behind a dev/debug flag or remove.

Apply this diff:

-  console.log("system prompt", systemContent);
+  if (import.meta.env?.DEV) {
+    console.debug("system prompt (dev)", systemContent);
+  }

41-46: Comment style: remove “✅ Add missing parameters”

This comment is “what”-oriented and not needed. Prefer minimal “why”-style comments.

Apply this diff:

-  // ✅ Add missing parameters
apps/desktop/src/components/right-panel/hooks/useChatLogic.ts (6)

166-217: Duplicate MCP client/tool fetching logic—prefer centralization via useMcpTools

This block re-implements MCP server discovery, client creation, and tool wrapping, duplicating the new useMcpTools hook. Duplication increases maintenance cost and may diverge (e.g., headers handling, caching).

Refactor to use useMcpTools for tool discovery and cache management, and only handle client lifecycle here if absolutely necessary (e.g., per-stream lifecycle).


365-406: Persisting AI text before tool-call is good—consider awaiting the save

saveAiText() is invoked without await. If the stream progresses quickly, the DB operation may race with subsequent writes. Awaiting here can simplify ordering guarantees at the cost of slight latency.

If ordering is important, await dbCommands.upsertChatMessage; otherwise leave as-is for throughput.


431-449: Tool error content may be non-string

chunk.error might be an object; string interpolation would produce “[object Object]”. Consider normalizing using String(...) or JSON.stringify with fallback.

Apply this diff:

-            content: `Tool error: ${chunk.error}`,
+            content: `Tool error: ${typeof chunk.error === "string" ? chunk.error : JSON.stringify(chunk.error)}`,

125-143: User messages marked as type "text-delta" may be semantically misleading

Using "text-delta" for user-authored messages can blur UI logic if “text-delta” is interpreted as streaming assistant output elsewhere. Consider a distinct type or omit type for user messages if not needed.

If you want, I can propose a small change to define a “user”/“assistant” semantic or add a “role” field consistently and use type only for assistant subtypes.


158-161: Production logs—gate or remove

Logging llmConnection, model id, api base in production is noisy and may reveal environment details.

Wrap with a debug guard:

-      console.log("llmConnection", llmConnection);
-      console.log("model id", model.modelId);
-      console.log("api base", apiBase);
+      if (import.meta.env?.DEV) {
+        console.debug("llmConnection", llmConnection);
+        console.debug("model id", model.modelId);
+        console.debug("api base", apiBase);
+      }

472-496: Minor: variable name typo finalErrorMesage

finalErrorMesage is misspelled; not harmful but worth correcting for readability.

Apply this diff:

-      let finalErrorMesage = "";
+      let finalErrorMessage = "";
@@
-        finalErrorMesage =
+        finalErrorMessage =
@@
-        finalErrorMesage = "Sorry, I encountered an error. Please try again. " + "\n\n" + errorMsg;
+        finalErrorMessage = "Sorry, I encountered an error. Please try again. " + "\n\n" + errorMsg;
@@
-        content: finalErrorMesage,
+        content: finalErrorMessage,
@@
-        content: finalErrorMesage,
+        content: finalErrorMessage,
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 8c5eb4f and 1023c4c.

📒 Files selected for processing (26)
  • apps/desktop/src/components/editor-area/index.tsx (1 hunks)
  • apps/desktop/src/components/right-panel/components/chat/chat-messages-view.tsx (3 hunks)
  • apps/desktop/src/components/right-panel/components/chat/message-content.tsx (2 hunks)
  • apps/desktop/src/components/right-panel/components/chat/types.ts (1 hunks)
  • apps/desktop/src/components/right-panel/hooks/useChatLogic.ts (7 hunks)
  • apps/desktop/src/components/right-panel/hooks/useChatQueries.ts (2 hunks)
  • apps/desktop/src/components/right-panel/hooks/useMcpTools.ts (1 hunks)
  • apps/desktop/src/components/right-panel/utils/chat-utils.ts (2 hunks)
  • apps/desktop/src/components/right-panel/views/chat-view.tsx (2 hunks)
  • apps/desktop/src/components/settings/components/tab-icon.tsx (2 hunks)
  • apps/desktop/src/components/settings/components/types.ts (3 hunks)
  • apps/desktop/src/components/settings/views/index.ts (1 hunks)
  • apps/desktop/src/components/settings/views/mcp.tsx (1 hunks)
  • apps/desktop/src/locales/en/messages.po (10 hunks)
  • apps/desktop/src/locales/ko/messages.po (10 hunks)
  • apps/desktop/src/routes/app.settings.tsx (3 hunks)
  • apps/desktop/src/utils/broadcast.ts (1 hunks)
  • crates/db-user/src/chat_messages_migration_1.sql (1 hunks)
  • crates/db-user/src/chat_messages_ops.rs (1 hunks)
  • crates/db-user/src/chat_messages_types.rs (1 hunks)
  • crates/db-user/src/init.rs (2 hunks)
  • crates/db-user/src/lib.rs (2 hunks)
  • crates/template/assets/ai_chat_system.jinja (1 hunks)
  • crates/template/assets/enhance.system.jinja (2 hunks)
  • packages/utils/src/ai.ts (1 hunks)
  • plugins/db/js/bindings.gen.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{js,ts,tsx,rs}

⚙️ CodeRabbit Configuration File

**/*.{js,ts,tsx,rs}: 1. No error handling.
2. No unused imports, variables, or functions.
3. For comments, keep it minimal. It should be about "Why", not "What".

Files:

  • apps/desktop/src/utils/broadcast.ts
  • apps/desktop/src/components/right-panel/hooks/useMcpTools.ts
  • apps/desktop/src/components/settings/components/tab-icon.tsx
  • crates/db-user/src/chat_messages_ops.rs
  • packages/utils/src/ai.ts
  • apps/desktop/src/components/right-panel/components/chat/types.ts
  • apps/desktop/src/components/editor-area/index.tsx
  • apps/desktop/src/components/settings/views/index.ts
  • crates/db-user/src/chat_messages_types.rs
  • apps/desktop/src/components/settings/views/mcp.tsx
  • apps/desktop/src/routes/app.settings.tsx
  • apps/desktop/src/components/settings/components/types.ts
  • plugins/db/js/bindings.gen.ts
  • apps/desktop/src/components/right-panel/components/chat/message-content.tsx
  • apps/desktop/src/components/right-panel/views/chat-view.tsx
  • apps/desktop/src/components/right-panel/hooks/useChatQueries.ts
  • crates/db-user/src/lib.rs
  • apps/desktop/src/components/right-panel/hooks/useChatLogic.ts
  • apps/desktop/src/components/right-panel/utils/chat-utils.ts
  • apps/desktop/src/components/right-panel/components/chat/chat-messages-view.tsx
  • crates/db-user/src/init.rs
🧬 Code Graph Analysis (7)
apps/desktop/src/components/right-panel/hooks/useMcpTools.ts (1)
packages/utils/src/ai.ts (3)
  • experimental_createMCPClient (10-10)
  • tool (17-17)
  • dynamicTool (9-9)
apps/desktop/src/components/settings/views/mcp.tsx (2)
plugins/mcp/js/bindings.gen.ts (3)
  • setServers (13-15)
  • McpServer (28-28)
  • commands (9-16)
packages/ui/src/components/ui/button.tsx (1)
  • Button (37-89)
apps/desktop/src/routes/app.settings.tsx (1)
apps/desktop/src/components/settings/views/mcp.tsx (1)
  • MCP (11-285)
plugins/db/js/bindings.gen.ts (1)
apps/desktop/src/components/right-panel/components/chat/chat-message.tsx (1)
  • ChatMessage (11-48)
apps/desktop/src/components/right-panel/hooks/useChatLogic.ts (6)
apps/desktop/src/components/right-panel/components/chat/types.ts (1)
  • Message (7-14)
apps/desktop/src/components/right-panel/types/chat-types.ts (1)
  • Message (20-20)
plugins/local-llm/src/server.rs (1)
  • content (267-273)
packages/utils/src/ai.ts (6)
  • modelProvider (70-77)
  • experimental_createMCPClient (10-10)
  • tool (17-17)
  • dynamicTool (9-9)
  • streamText (16-16)
  • stepCountIs (15-15)
apps/desktop/src/components/right-panel/utils/chat-utils.ts (1)
  • prepareMessageHistory (35-202)
apps/desktop/src/components/right-panel/utils/markdown-parser.ts (1)
  • parseMarkdownBlocks (3-62)
apps/desktop/src/components/right-panel/utils/chat-utils.ts (3)
apps/desktop/src/components/right-panel/types/chat-types.ts (1)
  • Message (20-20)
packages/tiptap/src/editor/mention.tsx (1)
  • mention (259-316)
apps/admin/src/lib/db/schema/auth.ts (1)
  • session (13-23)
crates/db-user/src/init.rs (1)
plugins/db/js/bindings.gen.ts (3)
  • ChatMessage (165-165)
  • ChatMessageRole (166-166)
  • ChatMessageType (167-167)
🪛 ast-grep (0.38.6)
apps/desktop/src/components/right-panel/utils/chat-utils.ts

[warning] 162-162: Direct modification of innerHTML or outerHTML properties detected. Modifying these properties with unsanitized user input can lead to XSS vulnerabilities. Use safe alternatives or sanitize content first.
Context: div.innerHTML = session.enhanced_memo_html
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://owasp.org/www-community/xss-filter-evasion-cheatsheet
- https://cwe.mitre.org/data/definitions/79.html

(dom-content-modification)


[warning] 166-166: Direct modification of innerHTML or outerHTML properties detected. Modifying these properties with unsanitized user input can lead to XSS vulnerabilities. Use safe alternatives or sanitize content first.
Context: div.innerHTML = session.raw_memo_html
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://owasp.org/www-community/xss-filter-evasion-cheatsheet
- https://cwe.mitre.org/data/definitions/79.html

(dom-content-modification)


[warning] 162-162: Direct HTML content assignment detected. Modifying innerHTML, outerHTML, or using document.write with unsanitized content can lead to XSS vulnerabilities. Use secure alternatives like textContent or sanitize HTML with libraries like DOMPurify.
Context: div.innerHTML = session.enhanced_memo_html
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://www.dhairyashah.dev/posts/why-innerhtml-is-a-bad-idea-and-how-to-avoid-it/
- https://cwe.mitre.org/data/definitions/79.html

(unsafe-html-content-assignment)


[warning] 166-166: Direct HTML content assignment detected. Modifying innerHTML, outerHTML, or using document.write with unsanitized content can lead to XSS vulnerabilities. Use secure alternatives like textContent or sanitize HTML with libraries like DOMPurify.
Context: div.innerHTML = session.raw_memo_html
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://www.dhairyashah.dev/posts/why-innerhtml-is-a-bad-idea-and-how-to-avoid-it/
- https://cwe.mitre.org/data/definitions/79.html

(unsafe-html-content-assignment)

🔇 Additional comments (25)
apps/desktop/src/components/right-panel/components/chat/types.ts (1)

13-14: Validate DB & interop for updated Message.type union
The addition of "generating" makes sense for UI workflows, but it isn’t a persisted DB value. Please confirm that no producers or consumers of Message
– especially the parts that read/write from your store or backend – will break when encountering this new variant.

• Review your database schema (or API definitions) to ensure only the four persisted types—"text-delta" | "tool-start" | "tool-result" | "tool-error"—are stored or expected.
• Audit every site where a Message object is constructed or deserialized and verify that a valid type is always set (or that there’s a safe default).
• Check code paths that handle incoming Message objects to ensure "generating" is treated appropriately (e.g. ignored/passthrough in persistence layers).
• Add or update unit/integration tests covering the new "generating" state and the defaulting behavior for Message.type.

apps/desktop/src/locales/en/messages.po (1)

1029-1032: New translation key “MCP”: LGTM

String and reference align with the new settings tab.

packages/utils/src/ai.ts (1)

8-18: No change needed for experimental_createMCPClient export

Confirmed via the official Vercel AI SDK documentation that the exported symbol is indeed experimental_createMCPClient (uppercase “MCP”). The current re-export matches the SDK and will compile correctly.

crates/db-user/src/chat_messages_types.rs (2)

11-27: Good addition: typed message kinds with stable string mapping.

Enum + serde/strum mapping aligns with bindings (TS: "text-delta" | "tool-start" | "tool-result" | "tool-error"). Cross-layer consistency looks solid.


36-37: Confirm nullability expectations for r#type.

Deserialization will fail if DB returns NULL for “type”. Ensure the migration/backfill guarantees non-null values, or queries coalesce to a default.

crates/db-user/src/chat_messages_ops.rs (1)

12-20: INSERT now includes type: correct and consistent.

Column order, placeholders, and value mapping (to_string via strum Display) are aligned with the new enum serialization.

crates/db-user/src/lib.rs (1)

131-156: Migrations updated correctly (append-only, count adjusted).

Array length and placement look correct; new chat_messages migration is appended, honoring the append-only rule.

crates/db-user/src/init.rs (2)

4-6: Imports updated to include ChatMessageType — aligned with schema changes.

The additional import matches the new DB column and enum wiring introduced elsewhere in the PR. Looks good.


791-798: Seeding ChatMessage with an explicit type is correct.

Assigning r#type: ChatMessageType::TextDelta (Line 797) ensures seeds persist with the new discriminator and aligns with the DB default and frontend bindings.

plugins/db/js/bindings.gen.ts (1)

165-167: Type surface extended correctly (ChatMessage.type and ChatMessageType).

The discriminated field addition is minimal and consistent with backend enums and UI render logic. No issues spotted.

apps/desktop/src/routes/app.settings.tsx (2)

72-74: Tab title mapping extended — LGTM.

Adds MCP to getTabTitle switch. Matches the new settings view and keeps localization via t macro.


141-142: MCP tab wiring confirmed
All required pieces are present and in sync:

  • apps/desktop/src/components/settings/components/types.ts:37
    TABS includes { name: "mcp", icon: NetworkIcon }
  • apps/desktop/src/components/settings/components/tab-icon.tsx:39
    case "mcp": return <NetworkIcon …/>;
  • apps/desktop/src/components/settings/views/index.ts:8
    export { default as MCP } from "./mcp";

No further changes needed.

apps/desktop/src/components/settings/components/tab-icon.tsx (2)

10-10: Import of NetworkIcon is correct and used

The new icon import is used below and aligns with the new MCP tab. No issues.


39-41: MCP tab icon wiring looks good

Rendering NetworkIcon for the new "mcp" tab is consistent with the types/TABS definition.

apps/desktop/src/components/settings/views/index.ts (1)

8-8: Export for MCP view added correctly

The index now re-exports MCP, enabling the route to import it from a single barrel. Looks good.

apps/desktop/src/components/settings/components/types.ts (3)

9-9: NetworkIcon import is correct and used

The icon is referenced in TABS for the "mcp" tab; no unused imports.


25-25: Tab union extended with "mcp"

Type surface updated cleanly; downstream code can now rely on the discriminated tab.


37-37: MCP added to TABS with NetworkIcon

The new tab is discoverable by any UI that iterates TABS. Consistent with TabIcon.

apps/desktop/src/components/right-panel/components/chat/message-content.tsx (1)

3-3: New PencilRuler icon import is used

Import matches usage in the new tool message UI blocks.

apps/desktop/src/components/right-panel/hooks/useChatQueries.ts (1)

87-89: No normalization needed: ChatMessageType is already a lowercase hyphenated union
The plugins/db/js/bindings.gen.ts clearly defines

export type ChatMessageType = "text-delta" | "tool-start" | "tool-result" | "tool-error"

so msg.type will always match the expected string union. No additional runtime normalization is required here.

apps/desktop/src/components/right-panel/views/chat-view.tsx (2)

62-63: Prop plumbing looks correct

Forwarding isStreamingText from useChatLogic and passing both isGenerating and isStreamingText down to ChatMessagesView is consistent with the new streaming UI. No issues spotted.


169-176: No other usages of ChatMessagesView found
A project-wide search revealed only the single call site in
apps/desktop/src/components/right-panel/views/chat-view.tsx
on lines 169–176. There are no other TSX files invoking , so no further prop-alignment updates are needed elsewhere.

apps/desktop/src/components/right-panel/components/chat/chat-messages-view.tsx (1)

50-70: Edge cases in thinking logic—tool chunks vs. streaming

shouldShowThinking returns true whenever the last message is not from the user and isStreamingText is false. If the last message is a tool-* message (tool-start/result/error), this will show “Thinking,” which might be intended. If not, consider checking message.type to gate the indicator (e.g., only when last is assistant text or after a user message).

Would you like me to adjust the predicate to be type-aware (e.g., ignore tool-result/error as “thinking” states)?

apps/desktop/src/components/right-panel/hooks/useMcpTools.ts (1)

26-31: No direct consumers found—please verify downstream usage
I ran a repository-wide search for useMcpTools( in .ts and .tsx files and didn’t find any call sites. That means there are currently no consumers to break, but be sure that any future components using this hook handle its return value as an object map (Record<string, any>) and not an array.

apps/desktop/src/components/right-panel/hooks/useChatLogic.ts (1)

297-308: Ensure generating flags are reset on finish as well

onFinish closes clients; flags are reset after the for-await loop (good). Just calling out that if onFinish fires before the loop naturally ends in all providers, there’s no additional cleanup here. Keep an eye on provider semantics.

If you see orphaned “Thinking” indicators after finish, consider toggling setIsGenerating(false)/setIsStreamingText(false) in onFinish as a safety.

Comment on lines +48 to +49
const thinkingTimeoutRef = useRef<NodeJS.Timeout | null>(null);

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix type for thinkingTimeoutRef to avoid TS/browser typing mismatch

NodeJS.Timeout is incorrect in browser React apps and may cause typing errors. Use ReturnType.

Apply this diff:

-  const thinkingTimeoutRef = useRef<NodeJS.Timeout | null>(null);
+  const thinkingTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
📝 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
const thinkingTimeoutRef = useRef<NodeJS.Timeout | null>(null);
const thinkingTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
🤖 Prompt for AI Agents
In
apps/desktop/src/components/right-panel/components/chat/chat-messages-view.tsx
around lines 48-49, the thinkingTimeoutRef is typed as NodeJS.Timeout | null
which mismatches browser setTimeout types; change its type to ReturnType<typeof
setTimeout> | null (i.e. use ReturnType<typeof setTimeout> instead of
NodeJS.Timeout) so the ref matches the DOM/browser timer return type and avoid
TS errors.

Comment on lines +189 to +192
if (noteContents.length > 0) {
currentUserMessage = currentUserMessage + noteContents.join("");
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Same bug when concatenating noteContents

Guard against undefined here as well.

Apply this diff:

-    if (noteContents.length > 0) {
-      currentUserMessage = currentUserMessage + noteContents.join("");
-    }
+    if (noteContents.length > 0) {
+      currentUserMessage = (currentUserMessage ?? "") + noteContents.join("");
+    }
📝 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
if (noteContents.length > 0) {
currentUserMessage = currentUserMessage + noteContents.join("");
}
}
if (noteContents.length > 0) {
currentUserMessage = (currentUserMessage ?? "") + noteContents.join("");
}
}
🤖 Prompt for AI Agents
In apps/desktop/src/components/right-panel/utils/chat-utils.ts around lines 189
to 192, the code concatenates noteContents without guarding against it being
undefined; update the logic to first verify noteContents is defined and an array
(or coerce to an empty array) before checking length and joining — e.g. treat
noteContents as (noteContents || []) or add a conditional if
(Array.isArray(noteContents) && noteContents.length > 0) then append
noteContents.join("") to currentUserMessage so the concatenation won’t throw
when noteContents is undefined.

Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

cubic analysis

6 issues found across 26 files • Review in cubic

React with 👍 or 👎 to teach cubic. You can also tag @cubic-dev-ai to give feedback, ask questions, or re-run the review.

<div className="space-y-3">
{servers.map((server, index) => (
<div
key={index}
Copy link

Choose a reason for hiding this comment

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

Using the array index as the React key can break component identity when servers are added or removed, leading to incorrect re-rendering or stale state. A stable, unique property such as the server URL should be used instead.

Prompt for AI agents
Address the following comment on apps/desktop/src/components/settings/views/mcp.tsx at line 215:

<comment>Using the array index as the React key can break component identity when servers are added or removed, leading to incorrect re-rendering or stale state. A stable, unique property such as the server URL should be used instead.</comment>

<file context>
@@ -0,0 +1,285 @@
+import { commands, type McpServer } from &quot;@hypr/plugin-mcp&quot;;
+import { Button } from &quot;@hypr/ui/components/ui/button&quot;;
+import { Input } from &quot;@hypr/ui/components/ui/input&quot;;
+import { Label } from &quot;@hypr/ui/components/ui/label&quot;;
+import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from &quot;@hypr/ui/components/ui/select&quot;;
+import { Switch } from &quot;@hypr/ui/components/ui/switch&quot;;
+import { useMutation } from &quot;@tanstack/react-query&quot;;
+import { PlusIcon, Trash2Icon } from &quot;lucide-react&quot;;
+import { useEffect, useState } from &quot;react&quot;;
</file context>

"

{% if modelId == "gpt-4.1" or modelId == "openai/gpt-4.1" or modelId == "anthropic/claude-4-sonnet" or modelId == "openai/gpt-4o" or modelId == "gpt-4o"%}
{% if modelId == "gpt-4.1" or modelId == "openai/gpt-4.1" or modelId == "anthropic/claude-sonnet-4" or modelId == "openai/gpt-4o" or modelId == "gpt-4o" or "pro.hyprnote.com" in apiBase%}
Copy link

Choose a reason for hiding this comment

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

If apiBase is undefined or None, the membership test "pro.hyprnote.com" in apiBase raises a TypeError at render time, breaking the whole template. Guard the test with a truthiness check on apiBase first.

Prompt for AI agents
Address the following comment on crates/template/assets/ai_chat_system.jinja at line 147:

<comment>If apiBase is undefined or None, the membership test &quot;pro.hyprnote.com&quot; in apiBase raises a TypeError at render time, breaking the whole template. Guard the test with a truthiness check on apiBase first.</comment>

<file context>
@@ -144,10 +144,17 @@ Your response would mostly be either of the two formats:
 
 &quot;
 
-{% if modelId == &quot;gpt-4.1&quot; or modelId == &quot;openai/gpt-4.1&quot; or modelId == &quot;anthropic/claude-4-sonnet&quot; or modelId == &quot;openai/gpt-4o&quot; or modelId == &quot;gpt-4o&quot;%}
+{% if modelId == &quot;gpt-4.1&quot; or modelId == &quot;openai/gpt-4.1&quot; or modelId == &quot;anthropic/claude-sonnet-4&quot; or modelId == &quot;openai/gpt-4o&quot; or modelId == &quot;gpt-4o&quot; or &quot;pro.hyprnote.com&quot; in apiBase%}
 [Tool Calling]
 Here are available tools that you can call to get more information.
</file context>
Suggested change
{% if modelId == "gpt-4.1" or modelId == "openai/gpt-4.1" or modelId == "anthropic/claude-sonnet-4" or modelId == "openai/gpt-4o" or modelId == "gpt-4o" or "pro.hyprnote.com" in apiBase%}
{% if modelId == "gpt-4.1" or modelId == "openai/gpt-4.1" or modelId == "anthropic/claude-sonnet-4" or modelId == "openai/gpt-4o" or modelId == "gpt-4o" or (apiBase and "pro.hyprnote.com" in apiBase) %}

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (6)
crates/template/assets/ai_chat_system.jinja (2)

147-147: Good guard on apiBase and updated model gating

The additional (apiBase and "pro.hyprnote.com" in apiBase) prevents a TypeError when apiBase is falsy and broadens tool enablement as intended.


151-151: Typo: “producitvity” → “productivity”

Fix the spelling in user-facing instructions.

Apply this diff:

- - custom MCP tools: user will be able to request you to call MCP tools. Often times, they are related to other producitvity tools like Slack, Notion, Google workspace, etc.
+ - custom MCP tools: user will be able to request you to call MCP tools. Often times, they are related to other productivity tools like Slack, Notion, Google Workspace, etc.
apps/desktop/src/components/right-panel/utils/chat-utils.ts (4)

86-86: Remove debug log from production path

Leaking the full system prompt clutters logs and can expose sensitive user data. Remove or gate behind a DEV flag.

-  console.log("system prompt", systemContent);
+  // Consider enabling only in development:
+  // if (import.meta?.env?.DEV) console.debug("system prompt", systemContent);

102-106: Fix undefined concatenation when appending mention preface

Using += on possibly undefined currentUserMessage coerces it to "undefined…".

-  if (mentionedContent && mentionedContent.length > 0) {
-    currentUserMessage +=
-      "[[From here is an automatically appended content from the mentioned notes & people, not what the user wrote. Use this only as a reference for more context. Your focus should always be the current meeting user is viewing]]"
-      + "\n\n";
-  }
+  if (mentionedContent && mentionedContent.length > 0) {
+    const preface =
+      "[[From here is an automatically appended content from the mentioned notes & people, not what the user wrote. Use this only as a reference for more context. Your focus should always be the current meeting user is viewing]]\n\n";
+    currentUserMessage = (currentUserMessage ?? "") + preface;
+  }

158-168: Avoid assigning unsanitized HTML to innerHTML; parse safely to text

Direct innerHTML assignment is unnecessary and risks XSS if this ever runs in a context where HTML executes. Use DOMParser and read textContent.

-                    if (session.enhanced_memo_html && session.enhanced_memo_html.trim() !== "") {
-                      const div = document.createElement("div");
-                      div.innerHTML = session.enhanced_memo_html;
-                      briefContent = (div.textContent || div.innerText || "").slice(0, 200) + "...";
-                    } else if (session.raw_memo_html && session.raw_memo_html.trim() !== "") {
-                      const div = document.createElement("div");
-                      div.innerHTML = session.raw_memo_html;
-                      briefContent = (div.textContent || div.innerText || "").slice(0, 200) + "...";
-                    }
+                    if (session.enhanced_memo_html && session.enhanced_memo_html.trim() !== "") {
+                      const parsed = new DOMParser().parseFromString(session.enhanced_memo_html, "text/html");
+                      briefContent = (parsed.body.textContent || "").slice(0, 200) + "...";
+                    } else if (session.raw_memo_html && session.raw_memo_html.trim() !== "") {
+                      const parsed = new DOMParser().parseFromString(session.raw_memo_html, "text/html");
+                      briefContent = (parsed.body.textContent || "").slice(0, 200) + "...";
+                    }

188-191: Guard against undefined when appending aggregated note contents

Avoid "undefined..." prefix by using nullish coalescing.

-    if (noteContents.length > 0) {
-      currentUserMessage = currentUserMessage + noteContents.join("");
-    }
+    if (noteContents.length > 0) {
+      currentUserMessage = (currentUserMessage ?? "") + noteContents.join("");
+    }
🧹 Nitpick comments (5)
apps/desktop/src/components/right-panel/utils/chat-utils.ts (1)

118-128: Strip HTML before injecting note content into the prompt to reduce token bloat

You currently inject raw HTML into the LLM prompt. Convert to plaintext to keep prompts clean and avoid markup artifacts.

-            noteContents.push(`\n\n--- Content from the note"${mention.label}" ---\n${noteContent}`);
+            const parsedNote = new DOMParser().parseFromString(noteContent, "text/html");
+            const plainText = parsedNote.body.textContent ?? "";
+            noteContents.push(`\n\n--- Content from the note "${mention.label}" ---\n${plainText}`);
apps/desktop/src/components/right-panel/hooks/useChatLogic.ts (4)

169-173: Nit: fix typo in variable name

Use enabledServers for clarity.

-        const enabledSevers = mcpServers.filter((server) => server.enabled);
+        const enabledServers = mcpServers.filter((server) => server.enabled);
 
-        for (const server of enabledSevers) {
+        for (const server of enabledServers) {

193-201: Avoid shadowing the imported tool symbol; also build the prompt’s tool list from source definitions

The loop variable named tool shadows the imported tool() helper. Rename to avoid confusion and capture inputSchema/description directly from the source tool.

-            const tools = await mcpClient.tools();
-            for (const [toolName, tool] of Object.entries(tools as Record<string, any>)) {
-              newMcpTools[toolName] = dynamicTool({
-                description: tool.description,
-                inputSchema: tool.inputSchema || z.any(),
-                execute: tool.execute,
-              });
-            }
+            const tools = await mcpClient.tools();
+            for (const [toolName, srvTool] of Object.entries(tools as Record<string, any>)) {
+              // Register executable tool for the model
+              newMcpTools[toolName] = dynamicTool({
+                description: srvTool.description,
+                inputSchema: srvTool.inputSchema || z.any(),
+                execute: srvTool.execute,
+              });
+              // Also accumulate a prompt-facing summary with original schema
+              mcpToolsArray.push({
+                name: toolName,
+                description: srvTool.description || `Tool: ${toolName}`,
+                inputSchema: srvTool.inputSchema ?? null,
+              });
+            }

183-189: Gate SSE client logs behind a DEV flag

Avoid noisy console logs in production.

-                onerror: (error) => {
-                  console.log("mcp client error: ", error);
-                },
-                onclose: () => {
-                  console.log("mcp client closed");
-                },
+                onerror: (error) => {
+                  if (import.meta?.env?.DEV) console.debug("mcp client error:", error);
+                },
+                onclose: () => {
+                  if (import.meta?.env?.DEV) console.debug("mcp client closed");
+                },

58-59: isGeneratingRef appears unnecessary

You set isGeneratingRef.current but never read it. Remove to reduce noise unless you plan to use it for cancellation.

-  const [isStreamingText, setIsStreamingText] = useState(false);
-  const isGeneratingRef = useRef(false);
+  const [isStreamingText, setIsStreamingText] = useState(false);

Follow-ups within this file:

  • Remove writes to isGeneratingRef.current at lines 121, 296, 464, 482.
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 1023c4c and 2938e4b.

📒 Files selected for processing (4)
  • apps/desktop/src/components/right-panel/hooks/useChatLogic.ts (7 hunks)
  • apps/desktop/src/components/right-panel/hooks/useMcpTools.ts (1 hunks)
  • apps/desktop/src/components/right-panel/utils/chat-utils.ts (2 hunks)
  • crates/template/assets/ai_chat_system.jinja (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/desktop/src/components/right-panel/hooks/useMcpTools.ts
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{js,ts,tsx,rs}

⚙️ CodeRabbit Configuration File

**/*.{js,ts,tsx,rs}: 1. No error handling.
2. No unused imports, variables, or functions.
3. For comments, keep it minimal. It should be about "Why", not "What".

Files:

  • apps/desktop/src/components/right-panel/hooks/useChatLogic.ts
  • apps/desktop/src/components/right-panel/utils/chat-utils.ts
🧬 Code Graph Analysis (2)
apps/desktop/src/components/right-panel/hooks/useChatLogic.ts (5)
apps/desktop/src/components/right-panel/components/chat/types.ts (1)
  • Message (7-14)
apps/desktop/src/components/right-panel/types/chat-types.ts (1)
  • Message (20-20)
packages/utils/src/ai.ts (2)
  • modelProvider (70-77)
  • tool (17-17)
apps/desktop/src/components/right-panel/utils/chat-utils.ts (1)
  • prepareMessageHistory (35-201)
apps/desktop/src/components/right-panel/utils/markdown-parser.ts (1)
  • parseMarkdownBlocks (3-62)
apps/desktop/src/components/right-panel/utils/chat-utils.ts (3)
apps/desktop/src/components/right-panel/types/chat-types.ts (1)
  • Message (20-20)
packages/tiptap/src/editor/mention.tsx (1)
  • mention (259-316)
apps/admin/src/lib/db/schema/auth.ts (1)
  • session (13-23)
🪛 ast-grep (0.38.6)
apps/desktop/src/components/right-panel/utils/chat-utils.ts

[warning] 161-161: Direct modification of innerHTML or outerHTML properties detected. Modifying these properties with unsanitized user input can lead to XSS vulnerabilities. Use safe alternatives or sanitize content first.
Context: div.innerHTML = session.enhanced_memo_html
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://owasp.org/www-community/xss-filter-evasion-cheatsheet
- https://cwe.mitre.org/data/definitions/79.html

(dom-content-modification)


[warning] 165-165: Direct modification of innerHTML or outerHTML properties detected. Modifying these properties with unsanitized user input can lead to XSS vulnerabilities. Use safe alternatives or sanitize content first.
Context: div.innerHTML = session.raw_memo_html
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://owasp.org/www-community/xss-filter-evasion-cheatsheet
- https://cwe.mitre.org/data/definitions/79.html

(dom-content-modification)


[warning] 161-161: Direct HTML content assignment detected. Modifying innerHTML, outerHTML, or using document.write with unsanitized content can lead to XSS vulnerabilities. Use secure alternatives like textContent or sanitize HTML with libraries like DOMPurify.
Context: div.innerHTML = session.enhanced_memo_html
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://www.dhairyashah.dev/posts/why-innerhtml-is-a-bad-idea-and-how-to-avoid-it/
- https://cwe.mitre.org/data/definitions/79.html

(unsafe-html-content-assignment)


[warning] 165-165: Direct HTML content assignment detected. Modifying innerHTML, outerHTML, or using document.write with unsanitized content can lead to XSS vulnerabilities. Use secure alternatives like textContent or sanitize HTML with libraries like DOMPurify.
Context: div.innerHTML = session.raw_memo_html
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://www.dhairyashah.dev/posts/why-innerhtml-is-a-bad-idea-and-how-to-avoid-it/
- https://cwe.mitre.org/data/definitions/79.html

(unsafe-html-content-assignment)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: ci (windows, windows-latest)
  • GitHub Check: ci (macos, macos-latest)
🔇 Additional comments (2)
apps/desktop/src/components/right-panel/utils/chat-utils.ts (1)

70-74: Timeline filter expects a JSON string, JSON.stringify is correct
The timeline filter in crates/template/src/filters.rs is defined as:

pub fn timeline(words: String) -> String {
    let words: Vec<Word2> = serde_json::from_str(&words).unwrap();}

It explicitly deserializes a JSON string into a Vec. Passing an array directly would break at runtime. Keep the current usage:

words: JSON.stringify(freshSessionData?.words || []),

Likely an incorrect or invalid review comment.

apps/desktop/src/components/right-panel/hooks/useChatLogic.ts (1)

162-167: MCP tool gating logic LGTM

Checks both supported model IDs and apiBase, and excludes HyprLocal. Matches the system prompt gating.

Comment on lines +206 to +213
mcpToolsArray = Object.keys(newMcpTools).length > 0
? Object.entries(newMcpTools).map(([name, tool]) => ({
name,
description: tool.description || `Tool: ${name}`,
inputSchema: tool.inputSchema || "No input schema provided",
}))
: [];
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Don’t rebuild mcpToolsArray from wrapped tools; keep original schema/description

The dynamic wrappers may not expose inputSchema cleanly and can lead to [object Object] in the prompt. You already accumulate summaries above; remove this block.

-        mcpToolsArray = Object.keys(newMcpTools).length > 0
-          ? Object.entries(newMcpTools).map(([name, tool]) => ({
-            name,
-            description: tool.description || `Tool: ${name}`,
-            inputSchema: tool.inputSchema || "No input schema provided",
-          }))
-          : [];
+        // mcpToolsArray is already populated from source tool definitions
📝 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
mcpToolsArray = Object.keys(newMcpTools).length > 0
? Object.entries(newMcpTools).map(([name, tool]) => ({
name,
description: tool.description || `Tool: ${name}`,
inputSchema: tool.inputSchema || "No input schema provided",
}))
: [];
}
// mcpToolsArray is already populated from source tool definitions
}
🤖 Prompt for AI Agents
In apps/desktop/src/components/right-panel/hooks/useChatLogic.ts around lines
206 to 213, remove the block that rebuilds mcpToolsArray from newMcpTools (the
Object.keys/Object.entries mapping) because wrappers can hide
inputSchema/description and produce [object Object]; do not reassign or rebuild
mcpToolsArray here—keep and use the original accumulated summaries collected
earlier, so the prompt uses the original schema/description values instead of
reconstructed objects.

Comment on lines +154 to +157
{% for tool in mcpTools %}

- {{ tool.name }}: {{ tool.description }}. Input schema is {{ tool.inputSchema }}.
{% endfor %}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Guard against undefined mcpTools and truncate verbose schemas

Iterating over an undefined mcpTools can error at render-time. Also, dumping full inputSchema can bloat prompts.

Apply this diff to make the loop safe and reduce token usage:

-{% for tool in mcpTools %}
+{% for tool in (mcpTools | default([])) %}
 
-- {{ tool.name }}: {{ tool.description }}. Input schema is {{ tool.inputSchema }}.
+- {{ tool.name }}: {{ tool.description }}.{% if tool.inputSchema %} Input schema (truncated): {{ tool.inputSchema | truncate(300) }}{% endif %}
   {% endfor %}

Optionally, if your Jinja environment supports tojson, prefer:

- Input schema (truncated): {{ tool.inputSchema | truncate(300) }}
+ Input schema (truncated): {{ tool.inputSchema | tojson | truncate(300) }}
📝 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
{% for tool in mcpTools %}
- {{ tool.name }}: {{ tool.description }}. Input schema is {{ tool.inputSchema }}.
{% endfor %}
{% for tool in (mcpTools | default([])) %}
- {{ tool.name }}: {{ tool.description }}{% if tool.inputSchema %} Input schema (truncated): {{ tool.inputSchema | truncate(300) }}{% endif %}
{% endfor %}
🤖 Prompt for AI Agents
In crates/template/assets/ai_chat_system.jinja around lines 154 to 157, the
template currently iterates mcpTools directly and prints full inputSchema which
can error if mcpTools is undefined and can bloat prompts; update the loop to
first guard against undefined/empty mcpTools (e.g., default to empty list or use
an if-statement) and iterate only when present, and truncate or summarize
tool.inputSchema (limit to a short substring or replace with a brief
type/summary) to reduce tokens; if your Jinja environment supports tojson,
prefer rendering a compact JSON snippet via tojson for the schema output instead
of dumping the full object.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (5)
apps/desktop/src/components/settings/components/ai/llm-local-view.tsx (5)

220-225: Remove debug logs from the click handler

Keeps the UI clean and avoids noisy consoles in production.

-                      onClick={(e) => {
-                        console.log("model download clicked");
-                        e.stopPropagation();
-                        console.log("model download clicked 2");
-                        handleModelDownload(model.key);
-                      }}
+                      onClick={(e) => {
+                        e.stopPropagation();
+                        handleModelDownload(model.key);
+                      }}

201-203: Use OS-agnostic label

“Show in Finder” is macOS-specific; “Show in Folder” is clearer cross-platform.

-                      Show in Finder
+                      Show in Folder

39-41: Async function without await

Either remove async or use await for clarity. Prefer the latter for readability.

-  const handleShowFileLocation = async () => {
-    localLlmCommands.modelsDir().then((path) => openPath(path));
-  };
+  const handleShowFileLocation = async () => {
+    const path = await localLlmCommands.modelsDir();
+    await openPath(path);
+  };

45-47: Avoid redundant state updates in effect

Guarding avoids unnecessary re-renders if the selected model is already current.

-    if (currentLLMModel.data && !customLLMEnabled.data) {
-      setSelectedLLMModel(currentLLMModel.data);
-    }
+    if (currentLLMModel.data && !customLLMEnabled.data) {
+      setSelectedLLMModel((prev) =>
+        prev === currentLLMModel.data ? prev : currentLLMModel.data,
+      );
+    }

59-66: No need for additional verification—side-effects are limited to query refetches, and mutateAsync is available. Converting both toggles to mutateAsync within Promise.all will batch-disable flags before restarting the server, eliminating flicker without breaking onSuccess handlers.

Update handleLocalModelSelection to atomically disable both flags

  // Disable BOTH HyprCloud and custom when selecting local
- setCustomLLMEnabledMutation.mutate(false);
- setHyprCloudEnabledMutation.mutate(false);
+ await Promise.all([
+   setCustomLLMEnabledMutation.mutateAsync(false),
+   setHyprCloudEnabledMutation.mutateAsync(false),
+ ]);
  setOpenAccordion(null);

  // Restart server for local model
  localLlmCommands.restartServer();
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between f1862f8 and e6b815c.

📒 Files selected for processing (2)
  • apps/desktop/src/components/settings/components/ai/llm-local-view.tsx (1 hunks)
  • apps/desktop/src/components/settings/components/tab-icon.tsx (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/desktop/src/components/settings/components/tab-icon.tsx
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{js,ts,tsx,rs}

⚙️ CodeRabbit Configuration File

**/*.{js,ts,tsx,rs}: 1. No error handling.
2. No unused imports, variables, or functions.
3. For comments, keep it minimal. It should be about "Why", not "What".

Files:

  • apps/desktop/src/components/settings/components/ai/llm-local-view.tsx
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: ci (windows, windows-latest)
  • GitHub Check: ci (macos, macos-latest)

@duckduckhero duckduckhero merged commit 61d58d8 into main Aug 18, 2025
9 checks passed
@ComputelessComputer ComputelessComputer deleted the mcp-redesign-2 branch December 14, 2025 15:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant