Skip to content

Commit 92622d0

Browse files
committed
refactor logger tail http requests to api_client
1 parent f6e5d35 commit 92622d0

File tree

3 files changed

+113
-42
lines changed

3 files changed

+113
-42
lines changed

examples/basic/mcp_basic_agent/requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# Core framework dependency
2-
mcp-agent @ file://../../../ # Link to the local mcp-agent project root
2+
mcp-agent==0.1.16
33

44
# Additional dependencies specific to this example
55
anthropic

src/mcp_agent/cli/cloud/commands/logger/tail/main.py

Lines changed: 31 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@
1717

1818
from mcp_agent.cli.exceptions import CLIError
1919
from mcp_agent.cli.auth import load_credentials, UserCredentials
20-
from mcp_agent.cli.core.constants import DEFAULT_API_BASE_URL
20+
from mcp_agent.cli.cloud.commands.servers.utils import setup_authenticated_client
21+
from mcp_agent.cli.core.api_client import UnauthenticatedError
2122
from mcp_agent.cli.core.utils import parse_app_identifier, resolve_server_url
2223

2324
console = Console()
@@ -179,36 +180,22 @@ async def _fetch_logs(
179180
) -> None:
180181
"""Fetch logs one-time via HTTP API."""
181182

182-
api_base = DEFAULT_API_BASE_URL
183-
headers = {
184-
"Authorization": f"Bearer {credentials.api_key}",
185-
"Content-Type": "application/json",
186-
}
187-
188-
payload = {}
189-
190-
if app_id:
191-
payload["app_id"] = app_id
192-
elif config_id:
193-
payload["app_configuration_id"] = config_id
194-
else:
195-
raise CLIError("Unable to determine app or configuration ID from provided identifier")
196-
197-
if since:
198-
payload["since"] = since
199-
if limit:
200-
payload["limit"] = limit
183+
client = setup_authenticated_client()
201184

185+
# Map order_by parameter from CLI to API format
186+
order_by_param = None
202187
if order_by:
203188
if order_by == "timestamp":
204-
payload["orderBy"] = "LOG_ORDER_BY_TIMESTAMP"
189+
order_by_param = "LOG_ORDER_BY_TIMESTAMP"
205190
elif order_by == "severity":
206-
payload["orderBy"] = "LOG_ORDER_BY_LEVEL"
191+
order_by_param = "LOG_ORDER_BY_LEVEL"
207192

193+
# Map order parameter from CLI to API format
194+
order_param = None
208195
if asc:
209-
payload["order"] = "LOG_ORDER_ASC"
196+
order_param = "LOG_ORDER_ASC"
210197
elif desc:
211-
payload["order"] = "LOG_ORDER_DESC"
198+
order_param = "LOG_ORDER_DESC"
212199

213200
with Progress(
214201
SpinnerColumn(),
@@ -219,23 +206,26 @@ async def _fetch_logs(
219206
progress.add_task("Fetching logs...", total=None)
220207

221208
try:
222-
async with httpx.AsyncClient(timeout=30.0) as client:
223-
response = await client.post(
224-
f"{api_base}/mcp_app/get_app_logs",
225-
json=payload,
226-
headers=headers,
227-
)
228-
229-
if response.status_code == 401:
230-
raise CLIError("Authentication failed. Try running 'mcp-agent login'")
231-
elif response.status_code == 404:
232-
raise CLIError("App or configuration not found")
233-
elif response.status_code != 200:
234-
raise CLIError(f"API request failed: {response.status_code} {response.text}")
235-
236-
data = response.json()
237-
log_entries = data.get("logEntries", [])
238-
209+
response = await client.get_app_logs(
210+
app_id=app_id,
211+
app_configuration_id=config_id,
212+
since=since,
213+
limit=limit,
214+
order_by=order_by_param,
215+
order=order_param,
216+
)
217+
# Convert LogEntry models to dictionaries for compatibility with display functions
218+
log_entries = [entry.model_dump() for entry in response.log_entries_list]
219+
220+
except UnauthenticatedError:
221+
raise CLIError("Authentication failed. Try running 'mcp-agent login'")
222+
except httpx.HTTPStatusError as e:
223+
if e.response.status_code == 404:
224+
raise CLIError("App or configuration not found")
225+
elif e.response.status_code == 401:
226+
raise CLIError("Authentication failed. Try running 'mcp-agent login'")
227+
else:
228+
raise CLIError(f"API request failed: {e.response.status_code} {e.response.text}")
239229
except httpx.RequestError as e:
240230
raise CLIError(f"Failed to connect to API: {e}")
241231

src/mcp_agent/cli/mcp_app/api_client.py

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,28 @@ def is_valid_server_url_format(server_url: str) -> bool:
104104
return parsed.scheme in {"http", "https"} and bool(parsed.netloc)
105105

106106

107+
class LogEntry(BaseModel):
108+
"""Represents a single log entry."""
109+
timestamp: Optional[str] = None
110+
level: Optional[str] = None
111+
message: Optional[str] = None
112+
# Allow additional fields that might be present
113+
114+
class Config:
115+
extra = "allow"
116+
117+
118+
class GetAppLogsResponse(BaseModel):
119+
"""Response from get_app_logs API endpoint."""
120+
logEntries: Optional[List[LogEntry]] = []
121+
log_entries: Optional[List[LogEntry]] = []
122+
123+
@property
124+
def log_entries_list(self) -> List[LogEntry]:
125+
"""Get log entries regardless of field name format."""
126+
return self.logEntries or self.log_entries or []
127+
128+
107129
class MCPAppClient(APIClient):
108130
"""Client for interacting with the MCP App API service over HTTP."""
109131

@@ -586,3 +608,62 @@ async def can_delete_app_configuration(self, app_config_id: str) -> bool:
586608
resource_name=f"MCP_APP_CONFIG:{app_config_id}",
587609
action="MANAGE:MCP_APP_CONFIG",
588610
)
611+
612+
async def get_app_logs(
613+
self,
614+
app_id: Optional[str] = None,
615+
app_configuration_id: Optional[str] = None,
616+
since: Optional[str] = None,
617+
limit: Optional[int] = None,
618+
order_by: Optional[str] = None,
619+
order: Optional[str] = None,
620+
) -> GetAppLogsResponse:
621+
"""Get logs for an MCP App or App Configuration via the API.
622+
623+
Args:
624+
app_id: The UUID of the app to get logs for (mutually exclusive with app_configuration_id)
625+
app_configuration_id: The UUID of the app configuration to get logs for (mutually exclusive with app_id)
626+
since: Time filter for logs (e.g., "1h", "24h", "7d")
627+
limit: Maximum number of log entries to return
628+
order_by: Field to order by ("LOG_ORDER_BY_TIMESTAMP" or "LOG_ORDER_BY_LEVEL")
629+
order: Log ordering direction ("LOG_ORDER_ASC" or "LOG_ORDER_DESC")
630+
631+
Returns:
632+
GetAppLogsResponse: The retrieved log entries
633+
634+
Raises:
635+
ValueError: If neither or both app_id and app_configuration_id are provided, or if IDs are invalid
636+
httpx.HTTPStatusError: If the API returns an error (e.g., 404, 403)
637+
httpx.HTTPError: If the request fails
638+
"""
639+
# Validate inputs
640+
if not app_id and not app_configuration_id:
641+
raise ValueError("Either app_id or app_configuration_id must be provided")
642+
if app_id and app_configuration_id:
643+
raise ValueError("Only one of app_id or app_configuration_id can be provided")
644+
645+
if app_id and not is_valid_app_id_format(app_id):
646+
raise ValueError(f"Invalid app ID format: {app_id}")
647+
if app_configuration_id and not is_valid_app_config_id_format(app_configuration_id):
648+
raise ValueError(f"Invalid app configuration ID format: {app_configuration_id}")
649+
650+
# Prepare request payload
651+
payload = {}
652+
if app_id:
653+
payload["app_id"] = app_id
654+
if app_configuration_id:
655+
payload["app_configuration_id"] = app_configuration_id
656+
if since:
657+
payload["since"] = since
658+
if limit:
659+
payload["limit"] = limit
660+
if order_by:
661+
payload["order_by"] = order_by
662+
if order:
663+
payload["order"] = order
664+
665+
response = await self.post("/mcp_app/get_app_logs", payload)
666+
667+
# Parse the response
668+
data = response.json()
669+
return GetAppLogsResponse(**data)

0 commit comments

Comments
 (0)