Skip to content

fix: changed truncation length for MCP tools and projects#8494

Merged
lucaseduoli merged 8 commits into
mainfrom
fix/truncation
Jun 12, 2025
Merged

fix: changed truncation length for MCP tools and projects#8494
lucaseduoli merged 8 commits into
mainfrom
fix/truncation

Conversation

@lucaseduoli
Copy link
Copy Markdown
Collaborator

@lucaseduoli lucaseduoli commented Jun 11, 2025

This pull request focuses on improving the handling of name truncation and uniqueness for various entities across the backend and frontend. Key changes include increasing the maximum allowed length for names, ensuring unique names by appending numeric suffixes when necessary, and updating related logic in both backend and frontend components.

Backend Changes

Name Truncation and Uniqueness:

  • Updated async def handle_list_tools() in src/backend/base/langflow/api/v1/mcp.py to truncate names to a maximum of 30 characters and ensure uniqueness by appending numeric suffixes when duplicates are detected.
  • Updated async def handle_list_project_tools() in src/backend/base/langflow/api/v1/mcp_projects.py to apply similar truncation and uniqueness logic for project tool names. [1] [2]

MCP Server Name Length:

  • Increased the maximum length for MCP server names from 11 to 26 characters in async def install_mcp_config() and async def check_installed_mcp_servers() in src/backend/base/langflow/api/v1/mcp_projects.py. [1] [2]

Frontend Changes

Name Length Adjustments:

  • Increased the maximum allowed length for MCP server names from 20 to 30 characters in AddMcpServerModal (src/frontend/src/modals/addMcpServerModal/index.tsx). [1] [2] [3]
  • Adjusted the MCP server name truncation logic in McpServerTab (src/frontend/src/pages/MainPage/pages/homePage/components/McpServerTab.tsx) to allow up to 25 characters.
  • Updated extractMcpServersFromJson in src/frontend/src/utils/mcpUtils.ts to reflect the increased name length limit of 30 characters.

Summary by CodeRabbit

  • New Features
    • Tool and server names now support longer lengths, allowing up to 30 characters for tool names and server names.
  • Bug Fixes
    • Ensured tool names are unique by automatically appending numeric suffixes when duplicates are detected.
  • Style
    • Updated descriptions and display logic to reflect new unique naming conventions.

@lucaseduoli lucaseduoli self-assigned this Jun 11, 2025
@dosubot dosubot Bot added the size:M This PR changes 30-99 lines, ignoring generated files. label Jun 11, 2025
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jun 11, 2025

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

The changes adjust the maximum allowed length for generated server and tool names throughout both backend and frontend components, increasing their limits. Additionally, new logic is introduced to ensure tool names are unique by appending numeric suffixes if necessary. No public API signatures are altered, and the updates are internal to helper functions and UI logic.

Changes

Files/Group Change Summary
src/backend/base/langflow/api/v1/mcp.py Updates tool name generation in handle_list_tools: truncates to 30 chars, enforces uniqueness with suffixes.
src/backend/base/langflow/api/v1/mcp_projects.py Increases server name prefix length to 26; ensures unique tool names (max 30 chars, numeric suffixes).
src/frontend/src/modals/addMcpServerModal/index.tsx,
src/frontend/src/utils/mcpUtils.ts
Increases max server name length from 20 to 30 characters in form submission and JSON extraction logic.
src/frontend/src/pages/MainPage/pages/homePage/components/McpServerTab.tsx Increases parsed folder name substring length for server keys from 11 to 25 characters.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Frontend
    participant Backend

    User->>Frontend: Submit MCP server/tool name
    Frontend->>Frontend: Truncate and sanitize name (30 chars)
    Frontend->>Backend: Send name for registration/listing
    Backend->>Backend: Truncate name (30 chars)
    Backend->>Backend: Check for uniqueness, append suffix if needed
    Backend->>Frontend: Return unique name/confirmation
Loading

Suggested labels

bug, lgtm

Suggested reviewers

  • deon-sanchez
  • mfortman11
  • Cristhianzl
✨ Finishing Touches
🧪 Generate Unit Tests
  • Create PR with Unit Tests
  • Commit Unit Tests in branch fix/truncation
  • Post Copyable Unit Tests in Comment

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • 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. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • 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 src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

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

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai auto-generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

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.

Documentation and Community

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

@github-actions github-actions Bot added bug Something isn't working and removed bug Something isn't working labels Jun 11, 2025
Copy link
Copy Markdown
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: 1

🔭 Outside diff range comments (1)
src/frontend/src/pages/MainPage/pages/homePage/components/McpServerTab.tsx (1)

182-188: ⚠️ Potential issue

Truncation length (25) does not match backend (26) – server detection will fail

lf-${…}.slice(0, 25) generates the key stored in the client config, but the backend now looks for a 26-char prefix ([:26] in mcp_projects.py).
As a result, check_installed_mcp_servers will never find the entry and the UI will falsely report the server as “not installed”.

-    "lf-${parseString(folderName ?? "project", ["snake_case", "no_blank", "lowercase"]).slice(0, 25)}": {
+    "lf-${parseString(folderName ?? "project", ["snake_case", "no_blank", "lowercase"]).slice(0, 26)}": {

Better yet, import a shared constant to avoid future drift.

🧹 Nitpick comments (4)
src/frontend/src/utils/mcpUtils.ts (1)

155-161: Avoid magic numbers – introduce a shared MAX_SERVER_NAME_LENGTH constant

name.slice(0, 30) hard-codes the truncation limit.
The same limit is scattered across several files (30 here, 25 in McpServerTab.tsx, 26 in the backend, etc.). Divergence is already creeping in.

Create one authoritative constant (e.g., MAX_SERVER_NAME_LENGTH = 30) in a shared module (or .env) and reference it everywhere. This guarantees the front- and back-end stay in sync and prevents subtle bugs when any side changes the limit again.

src/frontend/src/modals/addMcpServerModal/index.tsx (1)

133-139: Three identical slice(0, 30) occurrences – DRY it up

The same truncation logic appears for STDIO, SSE and JSON branches. Repetition invites inconsistency.

Suggested approach:

  1. Add at top of file (or shared util):
export const MAX_SERVER_NAME_LENGTH = 30;
  1. Replace every .slice(0, 30) with .slice(0, MAX_SERVER_NAME_LENGTH).

This keeps the modal aligned with other components and makes future changes trivial.

Also applies to: 167-173, 199-207

src/backend/base/langflow/api/v1/mcp.py (1)

183-201: Good uniqueness logic – consider extracting MAX_TOOL_NAME_LENGTH

The new duplicate-aware block looks solid and handles suffix overflow correctly.
Minor improvement: pull max_length = 30 up to a module-level constant shared with mcp_projects.py to avoid silent divergence.

src/backend/base/langflow/api/v1/mcp_projects.py (1)

520-541: Duplicate-name resolver looks good – factor out limit

Same comment as in mcp.py: promote max_length = 30 to a shared constant.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1dbc8d9 and 9b5c686.

📒 Files selected for processing (5)
  • src/backend/base/langflow/api/v1/mcp.py (1 hunks)
  • src/backend/base/langflow/api/v1/mcp_projects.py (4 hunks)
  • src/frontend/src/modals/addMcpServerModal/index.tsx (3 hunks)
  • src/frontend/src/pages/MainPage/pages/homePage/components/McpServerTab.tsx (1 hunks)
  • src/frontend/src/utils/mcpUtils.ts (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (3)
src/frontend/src/pages/MainPage/pages/homePage/components/McpServerTab.tsx (1)
src/frontend/src/utils/stringManipulation.ts (1)
  • parseString (75-137)
src/backend/base/langflow/api/v1/mcp.py (1)
src/backend/base/langflow/helpers/flow.py (1)
  • json_schema_from_flow (319-359)
src/backend/base/langflow/api/v1/mcp_projects.py (1)
src/backend/tests/conftest.py (1)
  • flow (516-532)
⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: Optimize new Python code in this PR
  • GitHub Check: Ruff Style Check (3.13)
🔇 Additional comments (1)
src/backend/base/langflow/api/v1/mcp.py (1)

211-214: base_name in log message may be undefined

If an exception occurs before base_name is set (e.g., during json_schema_from_flow), the except block still has base_name in scope, so it’s safe.
LGTM.

Comment on lines 392 to 396
# Create the MCP configuration
mcp_config = {
"mcpServers": {f"lf-{project.name.lower().replace(' ', '_')[:11]}": {"command": command, "args": args}}
"mcpServers": {f"lf-{project.name.lower().replace(' ', '_')[:26]}": {"command": command, "args": args}}
}

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot Jun 11, 2025

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

26-char prefix differs from other limits – unify constant

[:26] conflicts with 25 (frontend) and 30 (elsewhere). Use a single MAX_SERVER_NAME_PREFIX constant shared with the frontend to avoid mismatches.

🤖 Prompt for AI Agents
In src/backend/base/langflow/api/v1/mcp_projects.py around lines 392 to 396, the
code uses a hardcoded 26-character limit for the server name prefix, which
conflicts with other limits like 25 in the frontend and 30 elsewhere. Define a
shared constant MAX_SERVER_NAME_PREFIX and replace the hardcoded 26 with this
constant to unify the limit across the codebase and avoid mismatches.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

MAX_SERVER_NAME_PREFIX @lucaseduoli This is a Good review can you add it?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

Comment thread src/backend/base/langflow/api/v1/mcp_projects.py Outdated
Copy link
Copy Markdown
Collaborator

@edwinjosechittilappilly edwinjosechittilappilly left a comment

Choose a reason for hiding this comment

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

use MAX_SERVER_NAME_PREFIX It seems to make the backend config consistent.

Comment thread src/backend/base/langflow/api/v1/mcp_projects.py Outdated
@github-actions github-actions Bot added bug Something isn't working and removed bug Something isn't working labels Jun 12, 2025
@github-actions github-actions Bot added bug Something isn't working and removed bug Something isn't working labels Jun 12, 2025
@github-actions github-actions Bot added bug Something isn't working and removed bug Something isn't working labels Jun 12, 2025
Copy link
Copy Markdown
Collaborator

@edwinjosechittilappilly edwinjosechittilappilly left a comment

Choose a reason for hiding this comment

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

LGTM

Copy link
Copy Markdown
Collaborator

@edwinjosechittilappilly edwinjosechittilappilly left a comment

Choose a reason for hiding this comment

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

LGTM

@dosubot dosubot Bot added the lgtm This PR has been approved by a maintainer label Jun 12, 2025
@github-actions github-actions Bot added bug Something isn't working and removed bug Something isn't working labels Jun 12, 2025
@lucaseduoli lucaseduoli enabled auto-merge June 12, 2025 18:51
codeflash-ai Bot added a commit that referenced this pull request Jun 12, 2025
…ation`)

Here is an optimized version of your function.

- **Optimize membership test by converting `existing_names` to a `set` (if not already) for `O(1)` lookup.**
- **Avoid repeated string slicing and concatenation by precomputing values where possible.**
- **Minimize the number of function calls in the loop.**



**Key changes:**
- Ensure `existing_names` is a set for much faster lookup.
- Avoid redundant `len(base_name)` and `len(suffix)` calculation.
- Minimize slice operations by reusing precomputed string parts.

**All function signatures and return values are preserved.**
Comment on lines +84 to +91
name = base_name[:max_length]
if name not in existing_names:
return name
i = 1
while True:
suffix = f"_{i}"
truncated_base = base_name[: max_length - len(suffix)]
candidate = f"{truncated_base}{suffix}"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚡️Codeflash found 94% (0.94x) speedup for get_unique_name in src/backend/base/langflow/base/mcp/util.py

⏱️ Runtime : 2.63 milliseconds 1.35 milliseconds (best of 136 runs)

📝 Explanation and details Here is an optimized version of your function.
  • Optimize membership test by converting existing_names to a set (if not already) for O(1) lookup.
  • Avoid repeated string slicing and concatenation by precomputing values where possible.
  • Minimize the number of function calls in the loop.

Key changes:

  • Ensure existing_names is a set for much faster lookup.
  • Avoid redundant len(base_name) and len(suffix) calculation.
  • Minimize slice operations by reusing precomputed string parts.

All function signatures and return values are preserved.

Correctness verification report:

Test Status
⚙️ Existing Unit Tests 🔘 None Found
🌀 Generated Regression Tests 95 Passed
⏪ Replay Tests 🔘 None Found
🔎 Concolic Coverage Tests 🔘 None Found
📊 Tests Coverage 100.0%
🌀 Generated Regression Tests Details
import pytest  # used for our unit tests
from langflow.base.mcp.util import get_unique_name

# unit tests

# ------------------------
# Basic Test Cases
# ------------------------

def test_basic_unique_name_available():
    # Name is not in existing_names, and fits within max_length
    codeflash_output = get_unique_name("foo", 10, set())
    codeflash_output = get_unique_name("bar", 3, set())
    codeflash_output = get_unique_name("baz", 4, {"qux"})

def test_basic_name_truncation():
    # base_name longer than max_length, not in existing_names
    codeflash_output = get_unique_name("longname", 4, set())
    codeflash_output = get_unique_name("abcdef", 3, set())
    # base_name exactly max_length
    codeflash_output = get_unique_name("hello", 5, set())

def test_basic_suffix_generation():
    # base_name in existing_names, so suffix needed
    codeflash_output = get_unique_name("foo", 10, {"foo"})
    codeflash_output = get_unique_name("foo", 10, {"foo", "foo_1"})
    # base_name longer than max_length, suffix needed
    codeflash_output = get_unique_name("longname", 6, {"longna"})
    # base_name exactly max_length, suffix needed
    codeflash_output = get_unique_name("hello", 5, {"hello"})

def test_basic_multiple_existing_names():
    # Multiple conflicting names, should pick next available suffix
    existing = {"foo", "foo_1", "foo_2"}
    codeflash_output = get_unique_name("foo", 10, existing)
    existing = {"bar", "bar_1", "bar_2", "bar_3"}
    codeflash_output = get_unique_name("bar", 6, existing)

# ------------------------
# Edge Test Cases
# ------------------------

def test_edge_empty_base_name():
    # Empty base_name
    codeflash_output = get_unique_name("", 5, set())
    # Empty base_name, but "" is taken
    codeflash_output = get_unique_name("", 5, {""})
    # Empty base_name, "" and "_1" taken
    codeflash_output = get_unique_name("", 5, {"", "_1"})

def test_edge_zero_max_length():
    # max_length = 0, only possible name is ""
    codeflash_output = get_unique_name("abc", 0, set())
    # "" is taken, so next is "_1", but max_length=0, so truncated base is ""
    codeflash_output = get_unique_name("abc", 0, {""})
    codeflash_output = get_unique_name("abc", 0, {"", "_1"})

def test_edge_max_length_smaller_than_suffix():
    # max_length is less than length needed for suffix, so base truncates to ""
    # "_1" is 2 chars, max_length=1, so base is "", candidate is "_1"
    codeflash_output = get_unique_name("foo", 1, set())
    codeflash_output = get_unique_name("foo", 1, {"f"})
    codeflash_output = get_unique_name("foo", 1, {"f", "_1"})
    # max_length=2, "_1" is 2 chars, so base is "", candidate is "_1"
    codeflash_output = get_unique_name("foo", 2, {"fo"})

def test_edge_suffix_collision_with_truncated_names():
    # base_name and base_name_1 both in existing_names, should skip to _2
    existing = {"foo", "foo_1"}
    codeflash_output = get_unique_name("foo", 6, existing)
    # base_name truncates, and truncated+suffix collides
    existing = {"longn", "longn_1"}
    codeflash_output = get_unique_name("longname", 6, existing)

def test_edge_suffix_wraps_base_name_to_empty():
    # base_name is shorter than suffix, so candidate is just the suffix
    codeflash_output = get_unique_name("a", 2, {"a"})
    codeflash_output = get_unique_name("a", 2, {"a", "_1"})

def test_edge_existing_names_with_similar_prefixes():
    # Existing names are similar but not identical
    existing = {"foo", "foo_1", "foo_10", "foo_2"}
    codeflash_output = get_unique_name("foo", 10, existing)
    # Suffixes with gaps
    existing = {"foo", "foo_1", "foo_3"}
    codeflash_output = get_unique_name("foo", 10, existing)

def test_edge_non_string_existing_names():
    # existing_names contains non-string elements (should not crash, but we expect only string comparison)
    existing = {"foo", 123, None}
    codeflash_output = get_unique_name("foo", 10, existing)
    # If base_name is not in existing_names as string, should still return base_name
    existing = {123, None}
    codeflash_output = get_unique_name("foo", 10, existing)

def test_edge_suffix_number_grows():
    # Suffixes with double digits
    existing = {"foo"} | {f"foo_{i}" for i in range(1, 12)}
    codeflash_output = get_unique_name("foo", 10, existing)

def test_edge_max_length_exactly_suffix_length():
    # max_length matches the length of the suffix
    # "_1" is 2 chars, so max_length=2, base is truncated to "", candidate is "_1"
    codeflash_output = get_unique_name("foo", 2, {"fo"})
    # If "_1" is taken, next is "_2"
    codeflash_output = get_unique_name("foo", 2, {"fo", "_1"})

def test_edge_base_name_with_underscores():
    # base_name already ends with an underscore, suffix should still be appended
    existing = {"foo_"}
    codeflash_output = get_unique_name("foo_", 5, existing)
    # base_name with multiple underscores
    existing = {"foo__"}
    codeflash_output = get_unique_name("foo__", 6, existing)

def test_edge_existing_names_with_similar_suffixes():
    # existing_names has similar but not exact suffixes
    existing = {"foo_10", "foo_11"}
    codeflash_output = get_unique_name("foo", 10, existing)
    # base_name taken, but only high-numbered suffixes
    existing = {"foo", "foo_10", "foo_11"}
    codeflash_output = get_unique_name("foo", 10, existing)

# ------------------------
# Large Scale Test Cases
# ------------------------

def test_large_many_existing_names():
    # 1000 existing names, should pick next available
    existing = {f"name_{i}" for i in range(1000)}
    codeflash_output = get_unique_name("name", 10, existing)
    # All possible "name" and "name_1" to "name_999" taken, should return "name_1000"
    existing = {"name"} | {f"name_{i}" for i in range(1, 1000)}
    codeflash_output = get_unique_name("name", 10, existing)

def test_large_long_base_name_and_max_length():
    # base_name is very long, max_length is large, no existing names
    long_name = "a" * 1000
    codeflash_output = get_unique_name(long_name, 999, set())
    # base_name is very long, but max_length is small
    codeflash_output = get_unique_name(long_name, 5, set())

def test_large_suffix_generation_with_truncation():
    # base_name is long, max_length is small, and many suffixes are taken
    base_name = "abcdefghij"
    max_length = 5
    # Take "abcde", "abcd_1", ..., "abcd_9"
    existing = {base_name[:max_length]} | {f"{base_name[:max_length-2]}_{i}" for i in range(1, 10)}
    codeflash_output = get_unique_name(base_name, max_length, existing)

def test_large_performance_many_collisions():
    # Simulate many collisions to test performance
    base_name = "foo"
    max_length = 7
    # Take "foo", "foo_1", ..., "foo_999"
    existing = {"foo"} | {f"foo_{i}" for i in range(1, 1000)}
    codeflash_output = get_unique_name(base_name, max_length, existing)

def test_large_existing_names_with_varied_suffixes():
    # existing_names has a mix of numbers and similar names
    base_name = "item"
    max_length = 8
    existing = {f"item_{i}" for i in range(1, 500)} | {"item", "item_500", "item_999"}
    codeflash_output = get_unique_name(base_name, max_length, existing)

def test_large_existing_names_with_truncation_and_suffix():
    # base_name is long, max_length is small, and all possible truncated+suffix names are taken up to a point
    base_name = "abcdefghijk"
    max_length = 6
    # "abcdef", "abcde_1" to "abcde_99" taken
    existing = {base_name[:max_length]} | {f"{base_name[:max_length-2]}_{i}" for i in range(1, 100)}
    codeflash_output = get_unique_name(base_name, max_length, existing)
# codeflash_output is used to check that the output of the original code is the same as that of the optimized code.

import pytest  # used for our unit tests
from langflow.base.mcp.util import get_unique_name

# unit tests

# -------------------- BASIC TEST CASES --------------------

def test_basic_unique_name_not_in_existing():
    # Simple case: base_name fits, not in existing_names
    codeflash_output = get_unique_name("foo", 10, set())
    codeflash_output = get_unique_name("bar", 10, {"baz", "qux"})

def test_basic_unique_name_with_truncation():
    # base_name longer than max_length, should be truncated
    codeflash_output = get_unique_name("longname", 4, set())
    codeflash_output = get_unique_name("abcdefgh", 5, {"a", "ab"})

def test_basic_unique_name_with_existing():
    # base_name is present in existing_names, should add suffix
    codeflash_output = get_unique_name("foo", 10, {"foo"})
    codeflash_output = get_unique_name("foo", 10, {"foo", "foo_1"})

def test_basic_suffix_truncation():
    # Suffix causes truncation of base_name
    codeflash_output = get_unique_name("foobar", 6, {"foobar"})
    codeflash_output = get_unique_name("foobar", 6, {"foobar", "foob_1"})

def test_basic_suffix_multiple_digits():
    # Suffix with multiple digits, ensure correct truncation
    existing = {"foo", "fo_1", "fo_2", "fo_3", "fo_4", "fo_5", "fo_6", "fo_7", "fo_8", "fo_9", "fo_10"}
    codeflash_output = get_unique_name("foo", 4, existing)

# -------------------- EDGE TEST CASES --------------------

def test_edge_base_name_exact_max_length():
    # base_name exactly max_length, not in existing_names
    codeflash_output = get_unique_name("abcde", 5, set())
    # base_name exactly max_length, but in existing_names
    codeflash_output = get_unique_name("abcde", 5, {"abcde"})

def test_edge_base_name_shorter_than_max_length():
    # base_name shorter than max_length
    codeflash_output = get_unique_name("a", 10, set())
    codeflash_output = get_unique_name("a", 10, {"a"})

def test_edge_base_name_empty():
    # base_name is empty
    codeflash_output = get_unique_name("", 5, set())
    codeflash_output = get_unique_name("", 5, {""})
    # If max_length is 0, only possible name is ""
    codeflash_output = get_unique_name("foo", 0, set())
    codeflash_output = get_unique_name("foo", 0, {""})

def test_edge_max_length_smaller_than_suffix():
    # max_length smaller than base_name + suffix, must truncate base_name
    codeflash_output = get_unique_name("abcdef", 4, {"abcd"})
    # Suffix longer than max_length: edge case, should handle gracefully
    # For max_length=2, suffix is "_1" (2 chars), so base_name is truncated to 0
    codeflash_output = get_unique_name("foo", 2, {"fo"})
    # If "_1" is also taken, next is "_2"
    codeflash_output = get_unique_name("foo", 2, {"fo", "_1"})

def test_edge_suffix_causes_empty_base():
    # max_length == len(suffix), so base_name is empty
    codeflash_output = get_unique_name("foo", 2, set())
    codeflash_output = get_unique_name("foo", 2, {"fo"})
    # If "_1" is taken
    codeflash_output = get_unique_name("foo", 2, {"fo", "_1"})

def test_edge_non_ascii_names():
    # Non-ASCII base_name
    codeflash_output = get_unique_name("测试", 5, set())
    codeflash_output = get_unique_name("测试", 2, {"测试"})
    # Emoji
    codeflash_output = get_unique_name("😀😃😄", 4, set())
    codeflash_output = get_unique_name("😀😃😄", 2, {"😀😃"})

def test_edge_existing_names_with_similar_suffixes():
    # Existing names with similar suffixes but not matching the pattern
    existing = {"foo", "foo1", "foo_1", "foo_2"}
    codeflash_output = get_unique_name("foo", 10, existing)
    # Existing names with gaps in the suffix numbering
    existing = {"foo", "foo_1", "foo_3"}
    codeflash_output = get_unique_name("foo", 10, existing)

def test_edge_existing_names_as_list():
    # existing_names as a list instead of set
    codeflash_output = get_unique_name("foo", 10, ["foo", "foo_1"])

def test_edge_suffix_collision_with_base_name():
    # base_name that already ends with _1, _2, etc.
    existing = {"foo_1"}
    codeflash_output = get_unique_name("foo_1", 10, existing)
    existing = {"foo_1", "foo_1_1"}
    codeflash_output = get_unique_name("foo_1", 10, existing)

def test_edge_max_length_zero():
    # max_length is zero, only possible name is ""
    codeflash_output = get_unique_name("foo", 0, set())
    codeflash_output = get_unique_name("foo", 0, {""})
    codeflash_output = get_unique_name("foo", 0, {"", "_1"})

def test_edge_suffix_exceeds_max_length():
    # Suffix is longer than max_length, should still return a name
    codeflash_output = get_unique_name("foo", 2, {"fo", "_1", "_2", "_3"})

# -------------------- LARGE SCALE TEST CASES --------------------

def test_large_many_existing_names():
    # 1000 existing names, all possible foo, foo_1, ..., foo_999
    existing = {"foo"} | {f"foo_{i}" for i in range(1, 1000)}
    # Next unique should be foo_1000
    codeflash_output = get_unique_name("foo", 10, existing)

def test_large_many_collisions_with_truncation():
    # base_name="abcdefghij", max_length=8, existing names: abcdefgh, abcdef_1, ..., abcdef_99
    existing = {"abcdefgh"} | {f"abcdef_{i}" for i in range(1, 100)}
    # Next should be abcdef_100
    codeflash_output = get_unique_name("abcdefghij", 8, existing)

def test_large_existing_names_with_gaps():
    # Large set with gaps in suffixes
    existing = {"foo"} | {f"foo_{i}" for i in range(1, 1000) if i != 500}
    # Should fill the gap
    codeflash_output = get_unique_name("foo", 10, existing)

def test_large_performance_many_names():
    # Stress test: 999 names taken, base_name="bar", max_length=5
    existing = {"bar"} | {f"ba_{i}" for i in range(1, 1000)}
    # Next should be ba_1000
    codeflash_output = get_unique_name("bar", 5, existing)

def test_large_performance_suffix_length_growth():
    # Suffix length increases, causing more truncation
    existing = {"a"} | {f"_{i}" for i in range(1, 1000)}
    # For base_name="a", max_length=2, all "_1"..."_999" taken
    codeflash_output = get_unique_name("a", 2, existing)

def test_large_existing_names_as_list():
    # existing_names as a large list
    existing = ["foo"] + [f"foo_{i}" for i in range(1, 500)]
    codeflash_output = get_unique_name("foo", 10, existing)

# -------------------- DETERMINISM TEST --------------------

def test_determinism_repeated_calls():
    # Multiple calls with same input should yield same output
    existing = {"foo", "foo_1", "foo_2"}
    codeflash_output = get_unique_name("foo", 10, existing); result1 = codeflash_output
    codeflash_output = get_unique_name("foo", 10, existing); result2 = codeflash_output

# -------------------- TYPE AND ERROR HANDLING --------------------

def test_type_error_on_bad_existing_names():
    # existing_names must be iterable (set/list/tuple)
    with pytest.raises(TypeError):
        get_unique_name("foo", 5, None)
    with pytest.raises(TypeError):
        get_unique_name("foo", 5, 123)

def test_type_error_on_bad_max_length():
    # max_length must be int >= 0
    with pytest.raises(TypeError):
        get_unique_name("foo", "5", set())
    with pytest.raises(ValueError):
        get_unique_name("foo", -1, set())

def test_type_error_on_bad_base_name():
    # base_name must be str
    with pytest.raises(TypeError):
        get_unique_name(123, 5, set())
    with pytest.raises(TypeError):
        get_unique_name(None, 5, set())
# codeflash_output is used to check that the output of the original code is the same as that of the optimized code.

To test or edit this optimization locally git merge codeflash/optimize-pr8494-2025-06-12T19.01.15

Click to see suggested changes
Suggested change
name = base_name[:max_length]
if name not in existing_names:
return name
i = 1
while True:
suffix = f"_{i}"
truncated_base = base_name[: max_length - len(suffix)]
candidate = f"{truncated_base}{suffix}"
# Convert existing_names to set for faster lookup if it's not already a set
if not isinstance(existing_names, set):
existing_names = set(existing_names)
name = base_name[:max_length]
if name not in existing_names:
return name
# Precompute the part of base_name that can be re-used
base_name_len = len(base_name)
i = 1
while True:
suffix = f"_{i}"
allowed_base_len = max_length - len(suffix)
# Avoid extra slicing if allowed_base_len == base_name_len
if allowed_base_len >= base_name_len:
candidate = base_name + suffix
else:
candidate = base_name[:allowed_base_len] + suffix

@lucaseduoli lucaseduoli added lgtm This PR has been approved by a maintainer and removed lgtm This PR has been approved by a maintainer labels Jun 12, 2025
@lucaseduoli lucaseduoli added this pull request to the merge queue Jun 12, 2025
Merged via the queue into main with commit 1710c56 Jun 12, 2025
79 of 82 checks passed
@lucaseduoli lucaseduoli deleted the fix/truncation branch June 12, 2025 20:46
ogabrielluiz pushed a commit to bkatya2001/langflow that referenced this pull request Jun 24, 2025
…i#8494)

* updated mcp backend to truncate tools name to 30 chars

* updated frontend to truncate tools with 30 chars and truncate project name with 26 chars

* changed it to not exceed 25

* updated one click install to truncate at 26

* updated name

* used constants and helper functions to refactor code

* updated constants

---------

Co-authored-by: Edwin Jose <edwin.jose@datastax.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working lgtm This PR has been approved by a maintainer size:M This PR changes 30-99 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants