diff --git a/mcpgateway/services/grpc_service.py b/mcpgateway/services/grpc_service.py index b841e168a..221a5beac 100644 --- a/mcpgateway/services/grpc_service.py +++ b/mcpgateway/services/grpc_service.py @@ -15,8 +15,8 @@ from datetime import datetime, timezone from typing import Any, Dict, List, Optional -# Third-Party try: + # Third-Party import grpc from grpc_reflection.v1alpha import reflection_pb2, reflection_pb2_grpc @@ -28,6 +28,7 @@ reflection_pb2 = None # type: ignore reflection_pb2_grpc = None # type: ignore +# Third-Party from sqlalchemy import and_, desc, select from sqlalchemy.orm import Session diff --git a/mcpgateway/services/prompt_service.py b/mcpgateway/services/prompt_service.py index 208b0ccc5..b0fcf94c7 100644 --- a/mcpgateway/services/prompt_service.py +++ b/mcpgateway/services/prompt_service.py @@ -670,8 +670,21 @@ async def get_prompt( }, ) as span: try: - # Ensure prompt_id is an int for database operations - prompt_id_int = int(prompt_id) if isinstance(prompt_id, str) else prompt_id + # Determine how to look up the prompt + prompt_id_int = None + prompt_name = None + + if isinstance(prompt_id, int): + prompt_id_int = prompt_id + elif isinstance(prompt_id, str): + # Try to convert to int first (for backward compatibility with numeric string IDs) + try: + prompt_id_int = int(prompt_id) + except ValueError: + # Not a numeric string, treat as prompt name + prompt_name = prompt_id + else: + prompt_id_int = prompt_id if self._plugin_manager: if not request_id: @@ -684,18 +697,40 @@ async def get_prompt( # Use modified payload if provided if pre_result.modified_payload: payload = pre_result.modified_payload - prompt_id_int = int(payload.prompt_id) if isinstance(payload.prompt_id, str) else payload.prompt_id + # Re-parse the modified prompt_id + if isinstance(payload.prompt_id, int): + prompt_id_int = payload.prompt_id + prompt_name = None + elif isinstance(payload.prompt_id, str): + try: + prompt_id_int = int(payload.prompt_id) + prompt_name = None + except ValueError: + prompt_name = payload.prompt_id + prompt_id_int = None arguments = payload.args - # Find prompt - prompt = db.execute(select(DbPrompt).where(DbPrompt.id == prompt_id_int).where(DbPrompt.is_active)).scalar_one_or_none() + # Find prompt by ID or name + if prompt_id_int is not None: + prompt = db.execute(select(DbPrompt).where(DbPrompt.id == prompt_id_int).where(DbPrompt.is_active)).scalar_one_or_none() + search_key = prompt_id_int + else: + # Look up by name (active prompts only) + # Note: Team/owner scoping could be added here when user context is available + prompt = db.execute(select(DbPrompt).where(DbPrompt.name == prompt_name).where(DbPrompt.is_active)).scalar_one_or_none() + search_key = prompt_name if not prompt: - inactive_prompt = db.execute(select(DbPrompt).where(DbPrompt.id == prompt_id_int).where(not_(DbPrompt.is_active))).scalar_one_or_none() + # Check if an inactive prompt exists + if prompt_id_int is not None: + inactive_prompt = db.execute(select(DbPrompt).where(DbPrompt.id == prompt_id_int).where(not_(DbPrompt.is_active))).scalar_one_or_none() + else: + inactive_prompt = db.execute(select(DbPrompt).where(DbPrompt.name == prompt_name).where(not_(DbPrompt.is_active))).scalar_one_or_none() + if inactive_prompt: - raise PromptNotFoundError(f"Prompt '{prompt_id_int}' exists but is inactive") + raise PromptNotFoundError(f"Prompt '{search_key}' exists but is inactive") - raise PromptNotFoundError(f"Prompt not found: {prompt_id_int}") + raise PromptNotFoundError(f"Prompt not found: {search_key}") if not arguments: result = PromptResult( @@ -721,7 +756,7 @@ async def get_prompt( if self._plugin_manager: post_result, _ = await self._plugin_manager.prompt_post_fetch( - payload=PromptPosthookPayload(prompt_id=str(prompt_id_int), result=result), global_context=global_context, local_contexts=context_table, violations_as_exceptions=True + payload=PromptPosthookPayload(prompt_id=str(prompt.id), result=result), global_context=global_context, local_contexts=context_table, violations_as_exceptions=True ) # Use modified payload if provided result = post_result.modified_payload.result if post_result.modified_payload else result diff --git a/mcpgateway/static/admin.js b/mcpgateway/static/admin.js index 6385fad05..9eeac6985 100644 --- a/mcpgateway/static/admin.js +++ b/mcpgateway/static/admin.js @@ -6606,19 +6606,18 @@ const promptTestState = { /** * Test a prompt by opening the prompt test modal */ -async function testPrompt(promptName) { +async function testPrompt(promptId) { try { - console.log(`Testing prompt: ${promptName}`); + console.log(`Testing prompt ID: ${promptId}`); // Debouncing to prevent rapid clicking const now = Date.now(); - const lastRequest = - promptTestState.lastRequestTime.get(promptName) || 0; + const lastRequest = promptTestState.lastRequestTime.get(promptId) || 0; const timeSinceLastRequest = now - lastRequest; const debounceDelay = 1000; if (timeSinceLastRequest < debounceDelay) { - console.log(`Prompt ${promptName} test request debounced`); + console.log(`Prompt ${promptId} test request debounced`); return; } @@ -6630,7 +6629,7 @@ async function testPrompt(promptName) { // Update button state const testButton = document.querySelector( - `[onclick*="testPrompt('${promptName}')"]`, + `[onclick*="testPrompt('${promptId}')"]`, ); if (testButton) { if (testButton.disabled) { @@ -6645,8 +6644,8 @@ async function testPrompt(promptName) { } // Record request time and mark as active - promptTestState.lastRequestTime.set(promptName, now); - promptTestState.activeRequests.add(promptName); + promptTestState.lastRequestTime.set(promptId, now); + promptTestState.activeRequests.add(promptId); // Fetch prompt details const controller = new AbortController(); @@ -6655,7 +6654,7 @@ async function testPrompt(promptName) { try { // Fetch prompt details from the prompts endpoint (view mode) const response = await fetch( - `${window.ROOT_PATH}/admin/prompts/${encodeURIComponent(promptName)}`, + `${window.ROOT_PATH}/admin/prompts/${encodeURIComponent(promptId)}`, { method: "GET", headers: { @@ -6682,7 +6681,7 @@ async function testPrompt(promptName) { const descElement = safeGetElement("prompt-test-modal-description"); if (titleElement) { - titleElement.textContent = `Test Prompt: ${prompt.name || promptName}`; + titleElement.textContent = `Test Prompt: ${prompt.name || promptId}`; } if (descElement) { if (prompt.description) { @@ -6719,7 +6718,7 @@ async function testPrompt(promptName) { } finally { // Always restore button state const testButton = document.querySelector( - `[onclick*="testPrompt('${promptName}')"]`, + `[onclick*="testPrompt('${promptId}')"]`, ); if (testButton) { testButton.disabled = false; @@ -6728,7 +6727,7 @@ async function testPrompt(promptName) { } // Clean up state - promptTestState.activeRequests.delete(promptName); + promptTestState.activeRequests.delete(promptId); } } diff --git a/mcpgateway/templates/admin.html b/mcpgateway/templates/admin.html index 3042f8878..4e3785831 100644 --- a/mcpgateway/templates/admin.html +++ b/mcpgateway/templates/admin.html @@ -3764,7 +3764,7 @@