diff --git a/surfsense_backend/alembic/versions/10_update_chattype_enum_to_qna_report_structure.py b/surfsense_backend/alembic/versions/10_update_chattype_enum_to_qna_report_structure.py
index a4f6db0b8..dca37b90e 100644
--- a/surfsense_backend/alembic/versions/10_update_chattype_enum_to_qna_report_structure.py
+++ b/surfsense_backend/alembic/versions/10_update_chattype_enum_to_qna_report_structure.py
@@ -24,9 +24,7 @@ def enum_exists(enum_name: str) -> bool:
"""Check if an enum type exists in the database."""
conn = op.get_bind()
result = conn.execute(
- sa.text(
- "SELECT EXISTS (SELECT 1 FROM pg_type WHERE typname = :enum_name)"
- ),
+ sa.text("SELECT EXISTS (SELECT 1 FROM pg_type WHERE typname = :enum_name)"),
{"enum_name": enum_name},
)
return result.scalar()
diff --git a/surfsense_backend/alembic/versions/1_add_github_connector_enum.py b/surfsense_backend/alembic/versions/1_add_github_connector_enum.py
index 6f3ee2a01..a031e7693 100644
--- a/surfsense_backend/alembic/versions/1_add_github_connector_enum.py
+++ b/surfsense_backend/alembic/versions/1_add_github_connector_enum.py
@@ -22,9 +22,7 @@ def enum_exists(enum_name: str) -> bool:
"""Check if an enum type exists in the database."""
conn = op.get_bind()
result = conn.execute(
- sa.text(
- "SELECT EXISTS (SELECT 1 FROM pg_type WHERE typname = :enum_name)"
- ),
+ sa.text("SELECT EXISTS (SELECT 1 FROM pg_type WHERE typname = :enum_name)"),
{"enum_name": enum_name},
)
return result.scalar()
diff --git a/surfsense_backend/alembic/versions/49_migrate_old_chats_to_new_chat.py b/surfsense_backend/alembic/versions/49_migrate_old_chats_to_new_chat.py
index ef38add26..488f46227 100644
--- a/surfsense_backend/alembic/versions/49_migrate_old_chats_to_new_chat.py
+++ b/surfsense_backend/alembic/versions/49_migrate_old_chats_to_new_chat.py
@@ -197,9 +197,7 @@ def enum_exists(enum_name: str) -> bool:
"""Check if an enum type exists in the database."""
conn = op.get_bind()
result = conn.execute(
- sa.text(
- "SELECT EXISTS (SELECT 1 FROM pg_type WHERE typname = :enum_name)"
- ),
+ sa.text("SELECT EXISTS (SELECT 1 FROM pg_type WHERE typname = :enum_name)"),
{"enum_name": enum_name},
)
return result.scalar()
diff --git a/surfsense_backend/alembic/versions/62_add_mcp_connector_type.py b/surfsense_backend/alembic/versions/62_add_mcp_connector_type.py
index ed0ee4848..5c5ccf106 100644
--- a/surfsense_backend/alembic/versions/62_add_mcp_connector_type.py
+++ b/surfsense_backend/alembic/versions/62_add_mcp_connector_type.py
@@ -5,13 +5,14 @@ Revises: 61
Create Date: 2026-01-09 15:19:51.827647
"""
+
from collections.abc import Sequence
from alembic import op
# revision identifiers, used by Alembic.
-revision: str = '62'
-down_revision: str | None = '61'
+revision: str = "62"
+down_revision: str | None = "61"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
diff --git a/surfsense_backend/alembic/versions/63_allow_multiple_connectors_with_unique_.py b/surfsense_backend/alembic/versions/63_allow_multiple_connectors_with_unique_.py
index 5e61f29bc..ff3f98906 100644
--- a/surfsense_backend/alembic/versions/63_allow_multiple_connectors_with_unique_.py
+++ b/surfsense_backend/alembic/versions/63_allow_multiple_connectors_with_unique_.py
@@ -5,6 +5,7 @@ Revises: 62
Create Date: 2026-01-13 12:23:31.481643
"""
+
from collections.abc import Sequence
from sqlalchemy import text
@@ -12,8 +13,8 @@ from sqlalchemy import text
from alembic import op
# revision identifiers, used by Alembic.
-revision: str = '63'
-down_revision: str | None = '62'
+revision: str = "63"
+down_revision: str | None = "62"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
@@ -21,7 +22,7 @@ depends_on: str | Sequence[str] | None = None
def upgrade() -> None:
"""Upgrade schema."""
connection = op.get_bind()
-
+
# Check if old constraint exists before trying to drop it
old_constraint_exists = connection.execute(
text("""
@@ -31,14 +32,14 @@ def upgrade() -> None:
AND constraint_name='uq_searchspace_user_connector_type'
""")
).scalar()
-
+
if old_constraint_exists:
op.drop_constraint(
- 'uq_searchspace_user_connector_type',
- 'search_source_connectors',
- type_='unique'
+ "uq_searchspace_user_connector_type",
+ "search_source_connectors",
+ type_="unique",
)
-
+
# Check if new constraint already exists before creating it
new_constraint_exists = connection.execute(
text("""
@@ -48,19 +49,19 @@ def upgrade() -> None:
AND constraint_name='uq_searchspace_user_connector_type_name'
""")
).scalar()
-
+
if not new_constraint_exists:
op.create_unique_constraint(
- 'uq_searchspace_user_connector_type_name',
- 'search_source_connectors',
- ['search_space_id', 'user_id', 'connector_type', 'name']
+ "uq_searchspace_user_connector_type_name",
+ "search_source_connectors",
+ ["search_space_id", "user_id", "connector_type", "name"],
)
def downgrade() -> None:
"""Downgrade schema."""
connection = op.get_bind()
-
+
# Check if new constraint exists before trying to drop it
new_constraint_exists = connection.execute(
text("""
@@ -70,14 +71,14 @@ def downgrade() -> None:
AND constraint_name='uq_searchspace_user_connector_type_name'
""")
).scalar()
-
+
if new_constraint_exists:
op.drop_constraint(
- 'uq_searchspace_user_connector_type_name',
- 'search_source_connectors',
- type_='unique'
+ "uq_searchspace_user_connector_type_name",
+ "search_source_connectors",
+ type_="unique",
)
-
+
# Check if old constraint already exists before creating it
old_constraint_exists = connection.execute(
text("""
@@ -87,10 +88,10 @@ def downgrade() -> None:
AND constraint_name='uq_searchspace_user_connector_type'
""")
).scalar()
-
+
if not old_constraint_exists:
op.create_unique_constraint(
- 'uq_searchspace_user_connector_type',
- 'search_source_connectors',
- ['search_space_id', 'user_id', 'connector_type']
+ "uq_searchspace_user_connector_type",
+ "search_source_connectors",
+ ["search_space_id", "user_id", "connector_type"],
)
diff --git a/surfsense_backend/alembic/versions/65_add_message_author_id.py b/surfsense_backend/alembic/versions/65_add_message_author_id.py
index dcae91e37..8d891db81 100644
--- a/surfsense_backend/alembic/versions/65_add_message_author_id.py
+++ b/surfsense_backend/alembic/versions/65_add_message_author_id.py
@@ -44,4 +44,3 @@ def downgrade() -> None:
DROP COLUMN IF EXISTS author_id;
"""
)
-
diff --git a/surfsense_backend/app/agents/new_chat/tools/mcp_client.py b/surfsense_backend/app/agents/new_chat/tools/mcp_client.py
index d91065661..437f93043 100644
--- a/surfsense_backend/app/agents/new_chat/tools/mcp_client.py
+++ b/surfsense_backend/app/agents/new_chat/tools/mcp_client.py
@@ -18,7 +18,9 @@ logger = logging.getLogger(__name__)
class MCPClient:
"""Client for communicating with an MCP server."""
- def __init__(self, command: str, args: list[str], env: dict[str, str] | None = None):
+ def __init__(
+ self, command: str, args: list[str], env: dict[str, str] | None = None
+ ):
"""Initialize MCP client.
Args:
@@ -44,18 +46,16 @@ class MCPClient:
# Merge env vars with current environment
server_env = os.environ.copy()
server_env.update(self.env)
-
+
# Create server parameters with env
server_params = StdioServerParameters(
- command=self.command,
- args=self.args,
- env=server_env
+ command=self.command, args=self.args, env=server_env
)
-
+
# Spawn server process and create session
# Note: Cannot combine these context managers because ClientSession
# needs the read/write streams from stdio_client
- async with stdio_client(server=server_params) as (read, write):
+ async with stdio_client(server=server_params) as (read, write): # noqa: SIM117
async with ClientSession(read, write) as session:
# Initialize the connection
await session.initialize()
@@ -85,7 +85,9 @@ class MCPClient:
"""
if not self.session:
- raise RuntimeError("Not connected to MCP server. Use 'async with client.connect():'")
+ raise RuntimeError(
+ "Not connected to MCP server. Use 'async with client.connect():'"
+ )
try:
# Call tools/list RPC method
@@ -93,11 +95,15 @@ class MCPClient:
tools = []
for tool in response.tools:
- tools.append({
- "name": tool.name,
- "description": tool.description or "",
- "input_schema": tool.inputSchema if hasattr(tool, "inputSchema") else {},
- })
+ tools.append(
+ {
+ "name": tool.name,
+ "description": tool.description or "",
+ "input_schema": tool.inputSchema
+ if hasattr(tool, "inputSchema")
+ else {},
+ }
+ )
logger.info("Listed %d tools from MCP server", len(tools))
return tools
@@ -121,10 +127,14 @@ class MCPClient:
"""
if not self.session:
- raise RuntimeError("Not connected to MCP server. Use 'async with client.connect():'")
+ raise RuntimeError(
+ "Not connected to MCP server. Use 'async with client.connect():'"
+ )
try:
- logger.info("Calling MCP tool '%s' with arguments: %s", tool_name, arguments)
+ logger.info(
+ "Calling MCP tool '%s' with arguments: %s", tool_name, arguments
+ )
# Call tools/call RPC method
response = await self.session.call_tool(tool_name, arguments=arguments)
@@ -147,12 +157,17 @@ class MCPClient:
# Handle validation errors from MCP server responses
# Some MCP servers (like server-memory) return extra fields not in their schema
if "Invalid structured content" in str(e):
- logger.warning("MCP server returned data not matching its schema, but continuing: %s", e)
+ logger.warning(
+ "MCP server returned data not matching its schema, but continuing: %s",
+ e,
+ )
# Try to extract result from error message or return a success message
return "Operation completed (server returned unexpected format)"
raise
except (ValueError, TypeError, AttributeError, KeyError) as e:
- logger.error("Failed to call MCP tool '%s': %s", tool_name, e, exc_info=True)
+ logger.error(
+ "Failed to call MCP tool '%s': %s", tool_name, e, exc_info=True
+ )
return f"Error calling tool: {e!s}"
diff --git a/surfsense_backend/app/agents/new_chat/tools/mcp_tool.py b/surfsense_backend/app/agents/new_chat/tools/mcp_tool.py
index 81c7d074f..0e5f1b993 100644
--- a/surfsense_backend/app/agents/new_chat/tools/mcp_tool.py
+++ b/surfsense_backend/app/agents/new_chat/tools/mcp_tool.py
@@ -21,7 +21,8 @@ logger = logging.getLogger(__name__)
def _create_dynamic_input_model_from_schema(
- tool_name: str, input_schema: dict[str, Any],
+ tool_name: str,
+ input_schema: dict[str, Any],
) -> type[BaseModel]:
"""Create a Pydantic model from MCP tool's JSON schema.
@@ -41,15 +42,18 @@ def _create_dynamic_input_model_from_schema(
for param_name, param_schema in properties.items():
param_description = param_schema.get("description", "")
is_required = param_name in required_fields
-
+
# Use Any type for complex schemas to preserve structure
# This allows the MCP server to do its own validation
from typing import Any as AnyType
from pydantic import Field
-
+
if is_required:
- field_definitions[param_name] = (AnyType, Field(..., description=param_description))
+ field_definitions[param_name] = (
+ AnyType,
+ Field(..., description=param_description),
+ )
else:
field_definitions[param_name] = (
AnyType | None,
@@ -88,7 +92,7 @@ async def _create_mcp_tool_from_definition(
async def mcp_tool_call(**kwargs) -> str:
"""Execute the MCP tool call via the client."""
logger.info(f"MCP tool '{tool_name}' called with params: {kwargs}")
-
+
try:
# Connect to server and call tool
async with mcp_client.connect():
@@ -114,7 +118,8 @@ async def _create_mcp_tool_from_definition(
async def load_mcp_tools(
- session: AsyncSession, search_space_id: int,
+ session: AsyncSession,
+ search_space_id: int,
) -> list[StructuredTool]:
"""Load all MCP tools from user's active MCP server connectors.
@@ -150,7 +155,9 @@ async def load_mcp_tools(
env = server_config.get("env", {})
if not command:
- logger.warning(f"MCP connector {connector.id} missing command, skipping")
+ logger.warning(
+ f"MCP connector {connector.id} missing command, skipping"
+ )
continue
# Create MCP client
@@ -168,7 +175,9 @@ async def load_mcp_tools(
# Create LangChain tools from definitions
for tool_def in tool_definitions:
try:
- tool = await _create_mcp_tool_from_definition(tool_def, mcp_client)
+ tool = await _create_mcp_tool_from_definition(
+ tool_def, mcp_client
+ )
tools.append(tool)
except Exception as e:
logger.exception(
diff --git a/surfsense_backend/app/agents/new_chat/tools/registry.py b/surfsense_backend/app/agents/new_chat/tools/registry.py
index bb8708b2b..6873f864c 100644
--- a/surfsense_backend/app/agents/new_chat/tools/registry.py
+++ b/surfsense_backend/app/agents/new_chat/tools/registry.py
@@ -283,7 +283,8 @@ async def build_tools_async(
):
try:
mcp_tools = await load_mcp_tools(
- dependencies["db_session"], dependencies["search_space_id"],
+ dependencies["db_session"],
+ dependencies["search_space_id"],
)
tools.extend(mcp_tools)
logging.info(
diff --git a/surfsense_backend/app/schemas/search_source_connector.py b/surfsense_backend/app/schemas/search_source_connector.py
index e27cc775c..5fd7a5aab 100644
--- a/surfsense_backend/app/schemas/search_source_connector.py
+++ b/surfsense_backend/app/schemas/search_source_connector.py
@@ -23,7 +23,9 @@ class SearchSourceConnectorBase(BaseModel):
@field_validator("config")
@classmethod
def validate_config_for_connector_type(
- cls, config: dict[str, Any], values: dict[str, Any],
+ cls,
+ config: dict[str, Any],
+ values: dict[str, Any],
) -> dict[str, Any]:
connector_type = values.data.get("connector_type")
return validate_connector_config(connector_type, config)
diff --git a/surfsense_backend/app/tasks/document_processors/file_processors.py b/surfsense_backend/app/tasks/document_processors/file_processors.py
index 307e09897..f3b5cba9d 100644
--- a/surfsense_backend/app/tasks/document_processors/file_processors.py
+++ b/surfsense_backend/app/tasks/document_processors/file_processors.py
@@ -34,7 +34,6 @@ from .base import (
)
from .markdown_processor import add_received_markdown_file_document
-
# Constants for LlamaCloud retry configuration
LLAMACLOUD_MAX_RETRIES = 3
LLAMACLOUD_BASE_DELAY = 5 # Base delay in seconds for exponential backoff
diff --git a/surfsense_web/app/dashboard/user/settings/components/ApiKeyContent.tsx b/surfsense_web/app/dashboard/user/settings/components/ApiKeyContent.tsx
index 40e7b1d34..6bf10a78f 100644
--- a/surfsense_web/app/dashboard/user/settings/components/ApiKeyContent.tsx
+++ b/surfsense_web/app/dashboard/user/settings/components/ApiKeyContent.tsx
@@ -120,4 +120,3 @@ export function ApiKeyContent({ onMenuClick }: ApiKeyContentProps) {
);
}
-
diff --git a/surfsense_web/app/dashboard/user/settings/components/ProfileContent.tsx b/surfsense_web/app/dashboard/user/settings/components/ProfileContent.tsx
index fab978b49..511a09fd1 100644
--- a/surfsense_web/app/dashboard/user/settings/components/ProfileContent.tsx
+++ b/surfsense_web/app/dashboard/user/settings/components/ProfileContent.tsx
@@ -6,8 +6,8 @@ import { AnimatePresence, motion } from "motion/react";
import { useTranslations } from "next-intl";
import { useEffect, useState } from "react";
import { toast } from "sonner";
-import { currentUserAtom } from "@/atoms/user/user-query.atoms";
import { updateUserMutationAtom } from "@/atoms/user/user-mutation.atoms";
+import { currentUserAtom } from "@/atoms/user/user-query.atoms";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
diff --git a/surfsense_web/app/dashboard/user/settings/components/UserSettingsSidebar.tsx b/surfsense_web/app/dashboard/user/settings/components/UserSettingsSidebar.tsx
index e25d318f3..b7040b4e3 100644
--- a/surfsense_web/app/dashboard/user/settings/components/UserSettingsSidebar.tsx
+++ b/surfsense_web/app/dashboard/user/settings/components/UserSettingsSidebar.tsx
@@ -1,7 +1,7 @@
"use client";
-import { ArrowLeft, ChevronRight, X } from "lucide-react";
import type { LucideIcon } from "lucide-react";
+import { ArrowLeft, ChevronRight, X } from "lucide-react";
import { AnimatePresence, motion } from "motion/react";
import { useTranslations } from "next-intl";
import { Button } from "@/components/ui/button";
@@ -152,4 +152,3 @@ export function UserSettingsSidebar({
>
);
}
-
diff --git a/surfsense_web/app/dashboard/user/settings/page.tsx b/surfsense_web/app/dashboard/user/settings/page.tsx
index 973b39076..8e04ce37a 100644
--- a/surfsense_web/app/dashboard/user/settings/page.tsx
+++ b/surfsense_web/app/dashboard/user/settings/page.tsx
@@ -7,7 +7,7 @@ import { useTranslations } from "next-intl";
import { useCallback, useState } from "react";
import { ApiKeyContent } from "./components/ApiKeyContent";
import { ProfileContent } from "./components/ProfileContent";
-import { UserSettingsSidebar, type SettingsNavItem } from "./components/UserSettingsSidebar";
+import { type SettingsNavItem, UserSettingsSidebar } from "./components/UserSettingsSidebar";
export default function UserSettingsPage() {
const t = useTranslations("userSettings");
diff --git a/surfsense_web/atoms/user/user-mutation.atoms.ts b/surfsense_web/atoms/user/user-mutation.atoms.ts
index 02a9f2146..caf4436a5 100644
--- a/surfsense_web/atoms/user/user-mutation.atoms.ts
+++ b/surfsense_web/atoms/user/user-mutation.atoms.ts
@@ -16,4 +16,3 @@ export const updateUserMutationAtom = atomWithMutation((get) => {
},
};
});
-
diff --git a/surfsense_web/components/assistant-ui/document-upload-popup.tsx b/surfsense_web/components/assistant-ui/document-upload-popup.tsx
index 73d34de71..1023c5c40 100644
--- a/surfsense_web/components/assistant-ui/document-upload-popup.tsx
+++ b/surfsense_web/components/assistant-ui/document-upload-popup.tsx
@@ -109,7 +109,9 @@ const DocumentUploadPopupContent: FC<{
Upload and sync your documents to your search space
diff --git a/surfsense_web/components/assistant-ui/thread.tsx b/surfsense_web/components/assistant-ui/thread.tsx index 2507fb8a9..9f844ba2b 100644 --- a/surfsense_web/components/assistant-ui/thread.tsx +++ b/surfsense_web/components/assistant-ui/thread.tsx @@ -36,11 +36,7 @@ import { newLLMConfigsAtom, } from "@/atoms/new-llm-config/new-llm-config-query.atoms"; import { currentUserAtom } from "@/atoms/user/user-query.atoms"; -import { - ComposerAddAttachment, - ComposerAttachments, -} from "@/components/assistant-ui/attachment"; -import { UserMessage } from "@/components/assistant-ui/user-message"; +import { ComposerAddAttachment, ComposerAttachments } from "@/components/assistant-ui/attachment"; import { ConnectorIndicator } from "@/components/assistant-ui/connector-popup"; import { InlineMentionEditor, @@ -53,6 +49,7 @@ import { } from "@/components/assistant-ui/thinking-steps"; import { ToolFallback } from "@/components/assistant-ui/tool-fallback"; import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button"; +import { UserMessage } from "@/components/assistant-ui/user-message"; import { DocumentMentionPicker, type DocumentMentionPickerRef, @@ -636,7 +633,6 @@ const AssistantActionBar: FC = () => { ); }; - const EditComposer: FC = () => { return ({t("file_limit_reached")}
++ {t("file_limit_reached")} +
{t("file_limit_reached_desc", { max: MAX_FILES })}
diff --git a/surfsense_web/lib/apis/user-api.service.ts b/surfsense_web/lib/apis/user-api.service.ts index 94914ebaa..083fd8dee 100644 --- a/surfsense_web/lib/apis/user-api.service.ts +++ b/surfsense_web/lib/apis/user-api.service.ts @@ -1,7 +1,7 @@ import { getMeResponse, - updateUserResponse, type UpdateUserRequest, + updateUserResponse, } from "@/contracts/types/user.types"; import { baseApiService } from "./base-api.service";