Skip to content

Commit a11fa60

Browse files
TS0713rakduttamadhav165crivetimihai
authored
gateway_servers_actions_view (#429)
* Modified few things Signed-off-by: Satya <[email protected]> * removed if scenario which is not needed Signed-off-by: Satya <[email protected]> * unit test (#428) Signed-off-by: Rakhi Dutta <[email protected]> Signed-off-by: Satya <[email protected]> * fixed pylint warning coming for if elif - inside gateway_services.py Signed-off-by: Satya <[email protected]> * Fix flake8 Signed-off-by: Mihai Criveti <[email protected]> --------- Signed-off-by: Satya <[email protected]> Signed-off-by: Rakhi Dutta <[email protected]> Signed-off-by: Mihai Criveti <[email protected]> Co-authored-by: rakdutta <[email protected]> Co-authored-by: Madhav Kandukuri <[email protected]> Co-authored-by: Mihai Criveti <[email protected]>
1 parent e6dc9aa commit a11fa60

File tree

10 files changed

+188
-108
lines changed

10 files changed

+188
-108
lines changed

docs/docs/using/clients/.pages

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,4 @@ nav:
77
- cline.md
88
- continue.md
99
- openwebui.md
10-
- mcp-cli.md
10+
- mcp-cli.md

mcpgateway/cache/session_registry.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import httpx
3737

3838
# First-Party
39+
from mcpgateway import __version__
3940
from mcpgateway.config import settings
4041
from mcpgateway.db import get_db, SessionMessageRecord, SessionRecord
4142
from mcpgateway.models import Implementation, InitializeResult, ServerCapabilities
@@ -699,7 +700,7 @@ async def handle_initialize_logic(self, body: dict) -> InitializeResult:
699700
roots={"listChanged": True},
700701
sampling={},
701702
),
702-
serverInfo=Implementation(name=settings.app_name, version="1.0.0"),
703+
serverInfo=Implementation(name=settings.app_name, version=__version__),
703704
instructions=("MCP Gateway providing federated tools, resources and prompts. Use /admin interface for configuration."),
704705
)
705706

mcpgateway/federation/discovery.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
from zeroconf.asyncio import AsyncServiceBrowser, AsyncZeroconf
3030

3131
# First-Party
32+
from mcpgateway import __version__
3233
from mcpgateway.config import settings
3334
from mcpgateway.models import ServerCapabilities
3435

@@ -64,7 +65,7 @@ def __init__(self):
6465
port=settings.port,
6566
properties={
6667
"name": settings.app_name,
67-
"version": "1.0.0",
68+
"version": __version__,
6869
"protocol": PROTOCOL_VERSION,
6970
},
7071
)
@@ -350,7 +351,7 @@ async def _get_gateway_info(self, url: str) -> ServerCapabilities:
350351
"params": {
351352
"protocol_version": PROTOCOL_VERSION,
352353
"capabilities": {"roots": {"listChanged": True}, "sampling": {}},
353-
"client_info": {"name": settings.app_name, "version": "1.0.0"},
354+
"client_info": {"name": settings.app_name, "version": __version__},
354355
},
355356
}
356357

mcpgateway/main.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2155,7 +2155,7 @@ async def root_info():
21552155
dict: API info with app name, version, and UI/admin API status.
21562156
"""
21572157
logger.info("UI disabled, serving API info at root path")
2158-
return {"name": settings.app_name, "version": "1.0.0", "description": f"{settings.app_name} API - UI is disabled", "ui_enabled": False, "admin_api_enabled": ADMIN_API_ENABLED}
2158+
return {"name": settings.app_name, "version": __version__, "description": f"{settings.app_name} API - UI is disabled", "ui_enabled": False, "admin_api_enabled": ADMIN_API_ENABLED}
21592159

21602160

21612161
# Expose some endpoints at the root level as well

mcpgateway/services/gateway_service.py

Lines changed: 73 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -280,13 +280,14 @@ async def list_gateways(self, db: Session, include_inactive: bool = False) -> Li
280280
gateways = db.execute(query).scalars().all()
281281
return [GatewayRead.model_validate(g) for g in gateways]
282282

283-
async def update_gateway(self, db: Session, gateway_id: str, gateway_update: GatewayUpdate) -> GatewayRead:
283+
async def update_gateway(self, db: Session, gateway_id: str, gateway_update: GatewayUpdate, include_inactive: bool = True) -> GatewayRead:
284284
"""Update a gateway.
285285
286286
Args:
287287
db: Database session
288288
gateway_id: Gateway ID to update
289289
gateway_update: Updated gateway data
290+
include_inactive: Whether to include inactive gateways
290291
291292
Returns:
292293
Updated gateway information
@@ -302,88 +303,86 @@ async def update_gateway(self, db: Session, gateway_id: str, gateway_update: Gat
302303
if not gateway:
303304
raise GatewayNotFoundError(f"Gateway not found: {gateway_id}")
304305

305-
if not gateway.enabled:
306-
raise GatewayNotFoundError(f"Gateway '{gateway.name}' exists but is inactive")
307-
308-
# Check for name conflicts if name is being changed
309-
if gateway_update.name is not None and gateway_update.name != gateway.name:
310-
existing_gateway = db.execute(select(DbGateway).where(DbGateway.name == gateway_update.name).where(DbGateway.id != gateway_id)).scalar_one_or_none()
311-
312-
if existing_gateway:
313-
raise GatewayNameConflictError(
314-
gateway_update.name,
315-
enabled=existing_gateway.enabled,
316-
gateway_id=existing_gateway.id,
317-
)
318-
319-
# Update fields if provided
320-
if gateway_update.name is not None:
321-
gateway.name = gateway_update.name
322-
gateway.slug = slugify(gateway_update.name)
323-
if gateway_update.url is not None:
324-
gateway.url = gateway_update.url
325-
if gateway_update.description is not None:
326-
gateway.description = gateway_update.description
327-
if gateway_update.transport is not None:
328-
gateway.transport = gateway_update.transport
329-
330-
if getattr(gateway, "auth_type", None) is not None:
331-
gateway.auth_type = gateway_update.auth_type
332-
333-
# if auth_type is not None and only then check auth_value
334-
if getattr(gateway, "auth_value", {}) != {}:
335-
gateway.auth_value = gateway_update.auth_value
336-
337-
# Try to reinitialize connection if URL changed
338-
if gateway_update.url is not None:
339-
try:
340-
capabilities, tools = await self._initialize_gateway(gateway.url, gateway.auth_value, gateway.transport)
341-
new_tool_names = [tool.name for tool in tools]
306+
if gateway.enabled or include_inactive:
307+
# Check for name conflicts if name is being changed
308+
if gateway_update.name is not None and gateway_update.name != gateway.name:
309+
existing_gateway = db.execute(select(DbGateway).where(DbGateway.name == gateway_update.name).where(DbGateway.id != gateway_id)).scalar_one_or_none()
310+
311+
if existing_gateway:
312+
raise GatewayNameConflictError(
313+
gateway_update.name,
314+
enabled=existing_gateway.enabled,
315+
gateway_id=existing_gateway.id,
316+
)
317+
318+
# Update fields if provided
319+
if gateway_update.name is not None:
320+
gateway.name = gateway_update.name
321+
gateway.slug = slugify(gateway_update.name)
322+
if gateway_update.url is not None:
323+
gateway.url = gateway_update.url
324+
if gateway_update.description is not None:
325+
gateway.description = gateway_update.description
326+
if gateway_update.transport is not None:
327+
gateway.transport = gateway_update.transport
328+
329+
if getattr(gateway, "auth_type", None) is not None:
330+
gateway.auth_type = gateway_update.auth_type
331+
332+
# if auth_type is not None and only then check auth_value
333+
if getattr(gateway, "auth_value", {}) != {}:
334+
gateway.auth_value = gateway_update.auth_value
335+
336+
# Try to reinitialize connection if URL changed
337+
if gateway_update.url is not None:
338+
try:
339+
capabilities, tools = await self._initialize_gateway(gateway.url, gateway.auth_value, gateway.transport)
340+
new_tool_names = [tool.name for tool in tools]
342341

343-
for tool in tools:
344-
existing_tool = db.execute(select(DbTool).where(DbTool.original_name == tool.name).where(DbTool.gateway_id == gateway_id)).scalar_one_or_none()
345-
if not existing_tool:
346-
gateway.tools.append(
347-
DbTool(
348-
original_name=tool.name,
349-
original_name_slug=slugify(tool.name),
350-
url=gateway.url,
351-
description=tool.description,
352-
integration_type=tool.integration_type,
353-
request_type=tool.request_type,
354-
headers=tool.headers,
355-
input_schema=tool.input_schema,
356-
jsonpath_filter=tool.jsonpath_filter,
357-
auth_type=gateway.auth_type,
358-
auth_value=gateway.auth_value,
342+
for tool in tools:
343+
existing_tool = db.execute(select(DbTool).where(DbTool.original_name == tool.name).where(DbTool.gateway_id == gateway_id)).scalar_one_or_none()
344+
if not existing_tool:
345+
gateway.tools.append(
346+
DbTool(
347+
original_name=tool.name,
348+
original_name_slug=slugify(tool.name),
349+
url=gateway.url,
350+
description=tool.description,
351+
integration_type=tool.integration_type,
352+
request_type=tool.request_type,
353+
headers=tool.headers,
354+
input_schema=tool.input_schema,
355+
jsonpath_filter=tool.jsonpath_filter,
356+
auth_type=gateway.auth_type,
357+
auth_value=gateway.auth_value,
358+
)
359359
)
360-
)
361360

362-
gateway.capabilities = capabilities
363-
gateway.tools = [tool for tool in gateway.tools if tool.original_name in new_tool_names] # keep only still-valid rows
364-
gateway.last_seen = datetime.now(timezone.utc)
361+
gateway.capabilities = capabilities
362+
gateway.tools = [tool for tool in gateway.tools if tool.original_name in new_tool_names] # keep only still-valid rows
363+
gateway.last_seen = datetime.now(timezone.utc)
365364

366-
# Update tracking with new URL
367-
self._active_gateways.discard(gateway.url)
368-
self._active_gateways.add(gateway.url)
369-
except Exception as e:
370-
logger.warning(f"Failed to initialize updated gateway: {e}")
365+
# Update tracking with new URL
366+
self._active_gateways.discard(gateway.url)
367+
self._active_gateways.add(gateway.url)
368+
except Exception as e:
369+
logger.warning(f"Failed to initialize updated gateway: {e}")
371370

372-
gateway.updated_at = datetime.now(timezone.utc)
373-
db.commit()
374-
db.refresh(gateway)
371+
gateway.updated_at = datetime.now(timezone.utc)
372+
db.commit()
373+
db.refresh(gateway)
375374

376-
# Notify subscribers
377-
await self._notify_gateway_updated(gateway)
375+
# Notify subscribers
376+
await self._notify_gateway_updated(gateway)
378377

379-
logger.info(f"Updated gateway: {gateway.name}")
380-
return GatewayRead.model_validate(gateway)
378+
logger.info(f"Updated gateway: {gateway.name}")
379+
return GatewayRead.model_validate(gateway)
381380

382381
except Exception as e:
383382
db.rollback()
384383
raise GatewayError(f"Failed to update gateway: {str(e)}")
385384

386-
async def get_gateway(self, db: Session, gateway_id: str, include_inactive: bool = False) -> GatewayRead:
385+
async def get_gateway(self, db: Session, gateway_id: str, include_inactive: bool = True) -> GatewayRead:
387386
"""Get a specific gateway by ID.
388387
389388
Args:
@@ -401,10 +400,10 @@ async def get_gateway(self, db: Session, gateway_id: str, include_inactive: bool
401400
if not gateway:
402401
raise GatewayNotFoundError(f"Gateway not found: {gateway_id}")
403402

404-
if not gateway.enabled and not include_inactive:
405-
raise GatewayNotFoundError(f"Gateway '{gateway.name}' exists but is inactive")
403+
if gateway.enabled or include_inactive:
404+
return GatewayRead.model_validate(gateway)
406405

407-
return GatewayRead.model_validate(gateway)
406+
raise GatewayNotFoundError(f"Gateway not found: {gateway_id}")
408407

409408
async def toggle_gateway_status(self, db: Session, gateway_id: str, activate: bool, reachable: bool = True, only_update_reachable: bool = False) -> GatewayRead:
410409
"""Toggle gateway active status.

mcpgateway/static/admin.js

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1997,12 +1997,35 @@ async function viewGateway(gatewayId) {
19971997
statusP.appendChild(statusStrong);
19981998

19991999
const statusSpan = document.createElement("span");
2000-
statusSpan.className = `px-2 inline-flex text-xs leading-5 font-semibold rounded-full ${
2001-
gateway.isActive
2002-
? "bg-green-100 text-green-800"
2003-
: "bg-red-100 text-red-800"
2004-
}`;
2005-
statusSpan.textContent = gateway.isActive ? "Active" : "Inactive";
2000+
let statusText = "";
2001+
let statusClass = "";
2002+
let statusIcon = "";
2003+
if (!gateway.enabled) {
2004+
statusText = "Inactive";
2005+
statusClass = "bg-red-100 text-red-800";
2006+
statusIcon = `
2007+
<svg class="ml-1 h-4 w-4 text-red-600 self-center" fill="currentColor" viewBox="0 0 20 20">
2008+
<path fill-rule="evenodd" d="M6.293 6.293a1 1 0 011.414 0L10 8.586l2.293-2.293a1 1 0 111.414 1.414L11.414 10l2.293 2.293a1 1 0 11-1.414 1.414L10 11.414l-2.293 2.293a1 1 0 11-1.414-1.414L8.586 10 6.293 7.707a1 1 0 010-1.414z" clip-rule="evenodd"></path>
2009+
</svg>`;
2010+
} else if (gateway.enabled && gateway.reachable) {
2011+
statusText = "Active";
2012+
statusClass = "bg-green-100 text-green-800";
2013+
statusIcon = `
2014+
<svg class="ml-1 h-4 w-4 text-green-600 self-center" fill="currentColor" viewBox="0 0 20 20">
2015+
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm-1-4.586l5.293-5.293-1.414-1.414L9 11.586 7.121 9.707 5.707 11.121 9 14.414z" clip-rule="evenodd"></path>
2016+
</svg>`;
2017+
} else if (gateway.enabled && !gateway.reachable) {
2018+
statusText = "Offline";
2019+
statusClass = "bg-yellow-100 text-yellow-800";
2020+
statusIcon = `
2021+
<svg class="ml-1 h-4 w-4 text-yellow-600 self-center" fill="currentColor" viewBox="0 0 20 20">
2022+
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm-1-10h2v4h-2V8zm0 6h2v2h-2v-2z" clip-rule="evenodd"></path>
2023+
</svg>`;
2024+
}
2025+
2026+
statusSpan.className = `px-2 inline-flex text-xs leading-5 font-semibold rounded-full ${statusClass}`;
2027+
statusSpan.innerHTML = `${statusText} ${statusIcon}`;
2028+
20062029
statusP.appendChild(statusSpan);
20072030
container.appendChild(statusP);
20082031

0 commit comments

Comments
 (0)