diff --git a/mcpgateway/admin.py b/mcpgateway/admin.py index 80a3c0978..99a0d944b 100644 --- a/mcpgateway/admin.py +++ b/mcpgateway/admin.py @@ -1512,7 +1512,7 @@ async def admin_ui( >>> mock_tool = ToolRead( ... id="t1", name="T1", original_name="T1", url="http://t1.com", description="d", ... created_at=datetime.now(timezone.utc), updated_at=datetime.now(timezone.utc), - ... enabled=True, reachable=True, gateway_slug="default", original_name_slug="t1", + ... enabled=True, reachable=True, gateway_slug="default", custom_name_slug="t1", ... request_type="GET", integration_type="MCP", headers={}, input_schema={}, ... annotations={}, jsonpath_filter=None, auth=None, execution_count=0, ... metrics=ToolMetrics( @@ -1521,6 +1521,7 @@ async def admin_ui( ... avg_response_time=0.0, last_execution_time=None ... ), ... gateway_id=None, + ... customName="T1", ... tags=[] ... ) >>> server_service.list_servers = AsyncMock(return_value=[mock_server]) @@ -1633,34 +1634,35 @@ async def admin_list_tools( >>> mock_user = "test_user" >>> >>> # Mock tool data - >>> mock_tool = ToolRead( - ... id="tool-1", - ... name="Test Tool", - ... original_name="TestTool", - ... url="http://test.com/tool", - ... description="A test tool", - ... request_type="HTTP", - ... integration_type="MCP", - ... headers={}, - ... input_schema={}, - ... annotations={}, - ... jsonpath_filter=None, - ... auth=None, - ... created_at=datetime.now(timezone.utc), - ... updated_at=datetime.now(timezone.utc), - ... enabled=True, - ... reachable=True, - ... gateway_id=None, - ... execution_count=0, - ... metrics=ToolMetrics( - ... total_executions=5, successful_executions=5, failed_executions=0, - ... failure_rate=0.0, min_response_time=0.1, max_response_time=0.5, - ... avg_response_time=0.3, last_execution_time=datetime.now(timezone.utc) - ... ), - ... gateway_slug="default", - ... original_name_slug="test-tool", - ... tags=[] - ... ) # Added gateway_id=None + >>> mock_tool = ToolRead( + ... id="tool-1", + ... name="Test Tool", + ... original_name="TestTool", + ... url="http://test.com/tool", + ... description="A test tool", + ... request_type="HTTP", + ... integration_type="MCP", + ... headers={}, + ... input_schema={}, + ... annotations={}, + ... jsonpath_filter=None, + ... auth=None, + ... created_at=datetime.now(timezone.utc), + ... updated_at=datetime.now(timezone.utc), + ... enabled=True, + ... reachable=True, + ... gateway_id=None, + ... execution_count=0, + ... metrics=ToolMetrics( + ... total_executions=5, successful_executions=5, failed_executions=0, + ... failure_rate=0.0, min_response_time=0.1, max_response_time=0.5, + ... avg_response_time=0.3, last_execution_time=datetime.now(timezone.utc) + ... ), + ... gateway_slug="default", + ... custom_name_slug="test-tool", + ... customName="Test Tool", + ... tags=[] + ... ) # Added gateway_id=None >>> >>> # Mock the tool_service.list_tools method >>> original_list_tools = tool_service.list_tools @@ -1686,7 +1688,8 @@ async def admin_list_tools( ... failure_rate=0.0, min_response_time=0.0, max_response_time=0.0, ... avg_response_time=0.0, last_execution_time=None ... ), - ... gateway_slug="default", original_name_slug="inactive-tool", + ... gateway_slug="default", custom_name_slug="inactive-tool", + ... customName="Inactive Tool", ... tags=[] ... ) >>> tool_service.list_tools = AsyncMock(return_value=[mock_tool, mock_inactive_tool]) @@ -1772,7 +1775,8 @@ async def admin_get_tool(tool_id: str, db: Session = Depends(get_db), user: str ... failure_rate=0.0, min_response_time=0.0, max_response_time=0.0, avg_response_time=0.0, ... last_execution_time=None ... ), - ... gateway_slug="default", original_name_slug="get-tool", + ... gateway_slug="default", custom_name_slug="get-tool", + ... customName="Get Tool", ... tags=[] ... ) >>> @@ -2088,6 +2092,7 @@ async def admin_edit_tool( >>> # Happy path: Edit tool successfully >>> form_data_success = FormData([ ... ("name", "Updated_Tool"), + ... ("customName", "ValidToolName"), ... ("url", "http://updated.com"), ... ("requestType", "GET"), ... ("integrationType", "REST"), @@ -2110,6 +2115,7 @@ async def admin_edit_tool( >>> # Edge case: Edit tool with inactive checkbox checked >>> form_data_inactive = FormData([ ... ("name", "Inactive_Edit"), + ... ("customName", "ValidToolName"), ... ("url", "http://inactive.com"), ... ("is_inactive_checked", "true"), ... ("requestType", "GET"), @@ -2128,6 +2134,7 @@ async def admin_edit_tool( >>> # Error path: Tool name conflict (simulated with IntegrityError) >>> form_data_conflict = FormData([ ... ("name", "Conflicting_Name"), + ... ("customName", "Conflicting_Name"), ... ("url", "http://conflict.com"), ... ("requestType", "GET"), ... ("integrationType", "REST") @@ -2146,6 +2153,7 @@ async def admin_edit_tool( >>> # Error path: ToolError raised >>> form_data_tool_error = FormData([ ... ("name", "Tool_Error"), + ... ("customName", "Tool_Error"), ... ("url", "http://toolerror.com"), ... ("requestType", "GET"), ... ("integrationType", "REST") @@ -2164,6 +2172,7 @@ async def admin_edit_tool( >>> # Error path: Pydantic Validation Error >>> form_data_validation_error = FormData([ ... ("name", "Bad_URL"), + ... ("customName","Bad_Custom_Name"), ... ("url", "not-a-valid-url"), ... ("requestType", "GET"), ... ("integrationType", "REST") @@ -2181,6 +2190,7 @@ async def admin_edit_tool( >>> # Error path: Unexpected exception >>> form_data_unexpected = FormData([ ... ("name", "Crash_Tool"), + ... ("customName", "Crash_Tool"), ... ("url", "http://crash.com"), ... ("requestType", "GET"), ... ("integrationType", "REST") @@ -2202,13 +2212,13 @@ async def admin_edit_tool( """ LOGGER.debug(f"User {user} is editing tool ID {tool_id}") form = await request.form() - # Parse tags from comma-separated string tags_str = str(form.get("tags", "")) tags: list[str] = [tag.strip() for tag in tags_str.split(",") if tag.strip()] if tags_str else [] tool_data: dict[str, Any] = { "name": form.get("name"), + "custom_name": form.get("customName"), "url": form.get("url"), "description": form.get("description"), "headers": json.loads(form.get("headers") or "{}"), diff --git a/mcpgateway/alembic/versions/1fc1795f6983_merge_a2a_and_custom_name_changes.py b/mcpgateway/alembic/versions/1fc1795f6983_merge_a2a_and_custom_name_changes.py new file mode 100644 index 000000000..b3146d52f --- /dev/null +++ b/mcpgateway/alembic/versions/1fc1795f6983_merge_a2a_and_custom_name_changes.py @@ -0,0 +1,24 @@ +"""merge_a2a_and_custom_name_changes + +Revision ID: 1fc1795f6983 +Revises: add_a2a_agents_and_metrics, c9dd86c0aac9 +Create Date: 2025-08-20 19:04:40.589538 + +""" + +# Standard +from typing import Sequence, Union + +# revision identifiers, used by Alembic. +revision: str = "1fc1795f6983" +down_revision: Union[str, Sequence[str], None] = ("add_a2a_agents_and_metrics", "c9dd86c0aac9") +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + """Upgrade schema.""" + + +def downgrade() -> None: + """Downgrade schema.""" diff --git a/mcpgateway/alembic/versions/c9dd86c0aac9_remove_original_name_slug_and_added_.py b/mcpgateway/alembic/versions/c9dd86c0aac9_remove_original_name_slug_and_added_.py new file mode 100644 index 000000000..0f035a5eb --- /dev/null +++ b/mcpgateway/alembic/versions/c9dd86c0aac9_remove_original_name_slug_and_added_.py @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- +"""remove original_name_slug and added custom_name + +Revision ID: c9dd86c0aac9 +Revises: add_oauth_tokens_table +Create Date: 2025-08-19 15:15:26.509036 + +""" + +# Standard +from typing import Sequence, Union + +# Third-Party +from alembic import op +import sqlalchemy as sa + +# revision identifiers, used by Alembic. +revision: str = "c9dd86c0aac9" +down_revision: Union[str, Sequence[str], None] = "add_oauth_tokens_table" +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + """Upgrade schema.""" + # Remove original_name_slug column + op.alter_column("tools", "original_name_slug", new_column_name="custom_name_slug") + + # Add custom_name column + op.add_column("tools", sa.Column("custom_name", sa.String(), nullable=True)) + op.execute("UPDATE tools SET custom_name = original_name") + # ### end Alembic commands ### + + +def downgrade() -> None: + """Downgrade schema.""" + # Remove custom_name column + op.drop_column("tools", "custom_name") + + # Add original_name_slug column back + op.alter_column("tools", "custom_name_slug", new_column_name="original_name_slug") + # ### end Alembic commands ### diff --git a/mcpgateway/db.py b/mcpgateway/db.py index 1ff756416..670ee082d 100644 --- a/mcpgateway/db.py +++ b/mcpgateway/db.py @@ -369,7 +369,6 @@ class Tool(Base): id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: uuid.uuid4().hex) original_name: Mapped[str] = mapped_column(String, nullable=False) - original_name_slug: Mapped[str] = mapped_column(String, nullable=False) url: Mapped[str] = mapped_column(String, nullable=True) description: Mapped[Optional[str]] integration_type: Mapped[str] = mapped_column(default="MCP") @@ -403,6 +402,10 @@ class Tool(Base): auth_type: Mapped[Optional[str]] = mapped_column(default=None) # "basic", "bearer", or None auth_value: Mapped[Optional[str]] = mapped_column(default=None) + # custom_name,custom_name_slug + custom_name: Mapped[Optional[str]] = mapped_column(String, nullable=False) + custom_name_slug: Mapped[Optional[str]] = mapped_column(String, nullable=False) + # Federation relationship with a local gateway gateway_id: Mapped[Optional[str]] = mapped_column(ForeignKey("gateways.id")) # gateway_slug: Mapped[Optional[str]] = mapped_column(ForeignKey("gateways.slug")) @@ -431,15 +434,15 @@ def name(self): if self._computed_name: # pylint: disable=no-member return self._computed_name # orm column, resolved at runtime - original_slug = slugify(self.original_name) # pylint: disable=no-member + custom_name_slug = slugify(self.custom_name_slug) # pylint: disable=no-member # Gateway present → prepend its slug and the configured separator if self.gateway_id: # pylint: disable=no-member gateway_slug = slugify(self.gateway.name) # pylint: disable=no-member - return f"{gateway_slug}{settings.gateway_tool_name_separator}{original_slug}" + return f"{gateway_slug}{settings.gateway_tool_name_separator}{custom_name_slug}" # No gateway → only the original name slug - return original_slug + return custom_name_slug @name.setter def name(self, value): @@ -1123,7 +1126,7 @@ def failure_rate(self) -> float: float: The failure rate as a value between 0 and 1. Examples: - >>> tool = Tool(original_name="test_tool", original_name_slug="test-tool", input_schema={}) + >>> tool = Tool(custom_name="test_tool", custom_name_slug="test-tool", input_schema={}) >>> tool.failure_rate # No metrics yet 0.0 >>> tool.metrics = [ @@ -1285,7 +1288,7 @@ def update_tool_names_on_gateway_update(_mapper, connection, target): stmt = ( tools_table.update() .where(tools_table.c.gateway_id == target.id) - .values(name=new_gateway_slug + separator + tools_table.c.original_name_slug) + .values(name=new_gateway_slug + separator + tools_table.c.custom_name_slug) .execution_options(synchronize_session=False) # Important for bulk updates ) @@ -1617,18 +1620,29 @@ def set_a2a_agent_slug(_mapper, _conn, target): @event.listens_for(Tool, "before_insert") -def set_tool_name(_mapper, _conn, target): - """Set the computed name for a Tool before insert. +@event.listens_for(Tool, "before_update") +def set_custom_name_and_slug(mapper, connection, target): + """ + Event listener to set custom_name, custom_name_slug, and name for Tool before insert/update. + + - Sets custom_name to original_name if not provided. + - Calculates custom_name_slug from custom_name using slugify. + - Updates name to gateway_slug + separator + custom_name_slug. Args: - _mapper: Mapper - _conn: Connection - target: Target Tool instance + mapper: SQLAlchemy mapper for the Tool model. + connection: Database connection. + target: The Tool instance being inserted or updated. """ - - sep = settings.gateway_tool_name_separator - gateway_slug = target.gateway.slug if target.gateway_id else "" + # Set custom_name to original_name if not provided + if not target.custom_name: + target.custom_name = target.original_name + # Always update custom_name_slug from custom_name + target.custom_name_slug = slugify(target.custom_name) + # Update name field + gateway_slug = slugify(target.gateway.name) if target.gateway else "" if gateway_slug: - target.name = f"{gateway_slug}{sep}{slugify(target.original_name)}" + sep = settings.gateway_tool_name_separator + target.name = f"{gateway_slug}{sep}{target.custom_name_slug}" else: - target.name = slugify(target.original_name) + target.name = target.custom_name_slug diff --git a/mcpgateway/schemas.py b/mcpgateway/schemas.py index 929f3f658..d13728943 100644 --- a/mcpgateway/schemas.py +++ b/mcpgateway/schemas.py @@ -628,6 +628,7 @@ class ToolUpdate(BaseModelWithConfigDict): """ name: Optional[str] = Field(None, description="Unique name for the tool") + custom_name: Optional[str] = Field(None, description="Custom name for the tool") url: Optional[Union[str, AnyHttpUrl]] = Field(None, description="Tool endpoint URL") description: Optional[str] = Field(None, description="Tool description") integration_type: Optional[Literal["REST", "MCP", "A2A"]] = Field(None, description="Tool integration type") @@ -668,6 +669,19 @@ def validate_name(cls, v: str) -> str: """ return SecurityValidator.validate_tool_name(v) + @field_validator("custom_name") + @classmethod + def validate_custom_name(cls, v: str) -> str: + """Ensure custom tool names follow MCP naming conventions + + Args: + v (str): Value to validate + + Returns: + str: Value if validated as safe + """ + return SecurityValidator.validate_tool_name(v) + @field_validator("url") @classmethod def validate_url(cls, v: str) -> str: @@ -864,7 +878,8 @@ class ToolRead(BaseModelWithConfigDict): metrics: ToolMetrics name: str gateway_slug: str - original_name_slug: str + custom_name: str + custom_name_slug: str tags: List[str] = Field(default_factory=list, description="Tags for categorizing the tool") # Comprehensive metadata for audit tracking diff --git a/mcpgateway/services/gateway_service.py b/mcpgateway/services/gateway_service.py index bbfe65b17..739cc0d30 100644 --- a/mcpgateway/services/gateway_service.py +++ b/mcpgateway/services/gateway_service.py @@ -467,7 +467,8 @@ async def register_gateway( tools = [ DbTool( original_name=tool.name, - original_name_slug=slugify(tool.name), + custom_name=tool.name, + custom_name_slug=slugify(tool.name), url=normalized_url, description=tool.description, integration_type="MCP", # Gateway-discovered tools are MCP type @@ -632,7 +633,6 @@ async def fetch_tools_after_oauth(self, db: Session, gateway_id: str) -> Dict[st if not access_token: raise GatewayConnectionError(f"No valid OAuth tokens found for gateway {gateway.name}. Please complete the OAuth authorization flow first.") - # Now connect to MCP server with the access token authentication = {"Authorization": f"Bearer {access_token}"} @@ -832,8 +832,8 @@ async def update_gateway(self, db: Session, gateway_id: str, gateway_update: Gat if not existing_tool: gateway.tools.append( DbTool( - original_name=tool.name, - original_name_slug=slugify(tool.name), + custom_name=tool.custom_name, + custom_name_slug=slugify(tool.custom_name), url=gateway.url, description=tool.description, integration_type="MCP", # Gateway-discovered tools are MCP type @@ -1019,8 +1019,8 @@ async def toggle_gateway_status(self, db: Session, gateway_id: str, activate: bo if not existing_tool: gateway.tools.append( DbTool( - original_name=tool.name, - original_name_slug=slugify(tool.name), + custom_name=tool.custom_name, + custom_name_slug=slugify(tool.custom_name), url=gateway.url, description=tool.description, integration_type="MCP", # Gateway-discovered tools are MCP type diff --git a/mcpgateway/services/resource_service.py b/mcpgateway/services/resource_service.py index d3aa91173..71f827d91 100644 --- a/mcpgateway/services/resource_service.py +++ b/mcpgateway/services/resource_service.py @@ -421,7 +421,7 @@ async def read_resource(self, db: Session, uri: str, request_id: Optional[str] = >>> from unittest.mock import MagicMock >>> service = ResourceService() >>> db = MagicMock() - >>> uri = 'resource_uri' + >>> uri = 'http://example.com/resource.txt' >>> db.execute.return_value.scalar_one_or_none.return_value = MagicMock(content='test') >>> import asyncio >>> result = asyncio.run(service.read_resource(db, uri)) diff --git a/mcpgateway/services/tool_service.py b/mcpgateway/services/tool_service.py index db6944966..5a2ce2c1d 100644 --- a/mcpgateway/services/tool_service.py +++ b/mcpgateway/services/tool_service.py @@ -283,8 +283,9 @@ def _convert_tool_to_read(self, tool: DbTool) -> ToolRead: tool_dict["auth"] = None tool_dict["name"] = tool.name + tool_dict["custom_name"] = tool.custom_name tool_dict["gateway_slug"] = tool.gateway_slug if tool.gateway_slug else "" - tool_dict["original_name_slug"] = tool.original_name_slug + tool_dict["custom_name_slug"] = tool.custom_name_slug tool_dict["tags"] = tool.tags or [] return ToolRead.model_validate(tool_dict) @@ -378,7 +379,8 @@ async def register_tool( db_tool = DbTool( original_name=tool.name, - original_name_slug=slugify(tool.name), + custom_name=tool.name, + custom_name_slug=slugify(tool.name), url=str(tool.url), description=tool.description, integration_type=tool.integration_type, @@ -1005,6 +1007,8 @@ async def update_tool( raise ToolNotFoundError(f"Tool not found: {tool_id}") if tool_update.name is not None: tool.name = tool_update.name + if tool_update.custom_name is not None: + tool.custom_name = tool_update.custom_name if tool_update.url is not None: tool.url = str(tool_update.url) if tool_update.description is not None: diff --git a/mcpgateway/static/admin.js b/mcpgateway/static/admin.js index 52a37bb76..92452c061 100644 --- a/mcpgateway/static/admin.js +++ b/mcpgateway/static/admin.js @@ -1,3 +1,25 @@ +// Make URL field read-only for integration type MCP +function updateEditToolUrl() { + const editTypeField = document.getElementById("edit-tool-type"); + const editurlField = document.getElementById("edit-tool-url"); + if (editTypeField && editurlField) { + if (editTypeField.value === "MCP") { + editurlField.readOnly = true; + } else { + editurlField.readOnly = false; + } + } +} + +// Attach event listener after DOM is loaded or when modal opens +document.addEventListener("DOMContentLoaded", function () { + const TypeField = document.getElementById("edit-tool-type"); + if (TypeField) { + TypeField.addEventListener("change", updateEditToolUrl); + // Set initial state + updateEditToolUrl(); + } +}); /** * ==================================================================== * SECURE ADMIN.JS - COMPLETE VERSION WITH XSS PROTECTION @@ -1885,14 +1907,12 @@ async function editTool(toolId) { const response = await fetchWithTimeout( `${window.ROOT_PATH}/admin/tools/${toolId}`, ); - if (!response.ok) { // If the response is not OK, throw an error throw new Error(`HTTP ${response.status}: ${response.statusText}`); } const tool = await response.json(); - const isInactiveCheckedBool = isInactiveChecked("tools"); let hiddenField = safeGetElement("edit-show-inactive"); if (!hiddenField) { @@ -1915,9 +1935,12 @@ async function editTool(toolId) { // Validate and set fields const nameValidation = validateInputName(tool.name, "tool"); + const customNameValidation = validateInputName(tool.customName, "tool"); + const urlValidation = validateUrl(tool.url); const nameField = safeGetElement("edit-tool-name"); + const customNameField = safeGetElement("edit-tool-custom-name"); const urlField = safeGetElement("edit-tool-url"); const descField = safeGetElement("edit-tool-description"); const typeField = safeGetElement("edit-tool-type"); @@ -1925,6 +1948,9 @@ async function editTool(toolId) { if (nameField && nameValidation.valid) { nameField.value = nameValidation.value; } + if (customNameField && customNameValidation.valid) { + customNameField.value = customNameValidation.value; + } if (urlField && urlValidation.valid) { urlField.value = urlValidation.value; } @@ -2001,6 +2027,7 @@ async function editTool(toolId) { typeField.disabled = false; } updateEditToolRequestTypes(tool.requestType || null); // preselect from DB + updateEditToolUrl(tool.url || null); } // Request Type field handling (disable for MCP) @@ -2020,6 +2047,84 @@ async function editTool(toolId) { if (authTypeField) { authTypeField.value = tool.auth?.authType || ""; } + const editAuthTokenField = safeGetElement("edit-auth-token"); + // Prefill integration type from DB and set request types accordingly + if (typeField) { + // Always set value from DB, never from previous UI state + typeField.value = tool.integrationType; + // Remove any previous hidden field for type + const prevHiddenType = document.getElementById( + "hidden-edit-tool-type", + ); + if (prevHiddenType) { + prevHiddenType.remove(); + } + // Remove any previous hidden field for authType + const prevHiddenAuthType = document.getElementById( + "hidden-edit-auth-type", + ); + if (prevHiddenAuthType) { + prevHiddenAuthType.remove(); + } + // Disable integration type field for MCP tools (cannot be changed) + if (tool.integrationType === "MCP") { + typeField.disabled = true; + if (authTypeField) { + authTypeField.disabled = true; + // Add hidden field for authType + const hiddenAuthTypeField = document.createElement("input"); + hiddenAuthTypeField.type = "hidden"; + hiddenAuthTypeField.name = authTypeField.name; + hiddenAuthTypeField.value = authTypeField.value; + hiddenAuthTypeField.id = "hidden-edit-auth-type"; + authTypeField.form.appendChild(hiddenAuthTypeField); + } + if (urlField) { + urlField.readOnly = true; + } + if (headersField) { + headersField.setAttribute("readonly", "readonly"); + } + if (schemaField) { + schemaField.setAttribute("readonly", "readonly"); + } + if (editAuthTokenField) { + editAuthTokenField.setAttribute("readonly", "readonly"); + } + if (window.editToolHeadersEditor) { + window.editToolHeadersEditor.setOption("readOnly", true); + } + if (window.editToolSchemaEditor) { + window.editToolSchemaEditor.setOption("readOnly", true); + } + } else { + typeField.disabled = false; + if (authTypeField) { + authTypeField.disabled = false; + } + if (urlField) { + urlField.readOnly = false; + } + if (headersField) { + headersField.removeAttribute("readonly"); + } + if (schemaField) { + schemaField.removeAttribute("readonly"); + } + if (editAuthTokenField) { + editAuthTokenField.removeAttribute("readonly"); + } + if (window.editToolHeadersEditor) { + window.editToolHeadersEditor.setOption("readOnly", false); + } + if (window.editToolSchemaEditor) { + window.editToolSchemaEditor.setOption("readOnly", false); + } + } + // Update request types and URL field + updateEditToolRequestTypes(tool.requestType || null); + updateEditToolUrl(tool.url || null); + } // Auth containers const authBasicSection = safeGetElement("edit-auth-basic-fields"); @@ -3899,6 +4004,12 @@ function updateEditToolRequestTypes(selectedMethod = null) { return; } + // Track previous value using a data attribute + if (!editToolTypeSelect.dataset.prevValue) { + editToolTypeSelect.dataset.prevValue = editToolTypeSelect.value; + } + + // const prevType = editToolTypeSelect.dataset.prevValue; const selectedType = editToolTypeSelect.value; const allowedMethods = integrationRequestMap[selectedType] || []; @@ -3929,6 +4040,27 @@ function updateEditToolRequestTypes(selectedMethod = null) { // TOOL SELECT FUNCTIONALITY // =================================================================== +// Prevent manual REST→MCP changes in edit-tool-form +document.addEventListener("DOMContentLoaded", function () { + const editToolTypeSelect = document.getElementById("edit-tool-type"); + if (editToolTypeSelect) { + // Store the initial value for comparison + editToolTypeSelect.dataset.prevValue = editToolTypeSelect.value; + + editToolTypeSelect.addEventListener("change", function (e) { + const prevType = this.dataset.prevValue; + const selectedType = this.value; + if (prevType === "REST" && selectedType === "MCP") { + alert("You cannot change integration type from REST to MCP."); + this.value = prevType; + // Optionally, reset any dependent fields here + } else { + this.dataset.prevValue = selectedType; + } + }); + } +}); +//= ================================================================== function initToolSelect( selectId, pillsId, @@ -4152,6 +4284,7 @@ async function testTool(toolId) { } const tool = await response.json(); + console.log(`Tool ${toolId} fetched successfully`, tool); toolInputSchemaRegistry = tool; // 7. CLEAN STATE before proceeding @@ -6827,8 +6960,10 @@ function setupIntegrationTypeHandlers() { const editToolTypeSelect = safeGetElement("edit-tool-type"); if (editToolTypeSelect) { - editToolTypeSelect.addEventListener("change", () => - updateEditToolRequestTypes(), + editToolTypeSelect.addEventListener( + "change", + () => updateEditToolRequestTypes(), + // updateEditToolUrl(), ); } } diff --git a/mcpgateway/templates/admin.html b/mcpgateway/templates/admin.html index 0a596e54d..6be5d1078 100644 --- a/mcpgateway/templates/admin.html +++ b/mcpgateway/templates/admin.html @@ -1335,6 +1335,7 @@