"""Endpoints for listing available profiles and tools."""
from typing import Annotated
from fastapi import APIRouter, Depends, HTTPException
from navi.api.deps import get_current_user, get_mcp_manager, get_profile_registry, get_tool_registry
from navi.auth import User
from navi.config import settings
from navi.core import ProfileRegistry, ToolRegistry
from navi.mcp import McpManager, load_mcp_servers
router = APIRouter(prefix="/agents", tags=["agents"])
@router.get("/profiles")
async def list_profiles(
profiles: Annotated[ProfileRegistry, Depends(get_profile_registry)],
user: Annotated[User | None, Depends(get_current_user)] = None,
) -> list[dict]:
is_admin = user is not None and user.role == "admin"
result = []
for p in profiles.all():
if getattr(p, "is_admin_only", False) and not is_admin:
continue
result.append({
"id": p.id,
"name": p.name,
"description": p.description,
"enabled_tools": p.enabled_tools,
"mcp_servers": p.mcp_servers,
"llm_backend": p.llm_backend,
"model": p.model,
"temperature": p.temperature,
"top_k": p.top_k,
"top_p": p.top_p,
"max_iterations": p.max_iterations,
"iteration_budget_enabled": p.iteration_budget_enabled,
"think_enabled": p.think_enabled,
"subagent_think_enabled": p.subagent_think_enabled,
})
return result
def _resolve_mcp_tools(
profile,
mcp_manager: McpManager | None,
tool_registry: ToolRegistry,
) -> list[str]:
"""Expand profile.mcp_servers into concrete tool names (mcp_server_tool)."""
if not profile.mcp_servers or not mcp_manager:
return []
names: list[str] = []
for server_name, groups in profile.mcp_servers.items():
if "*" in groups:
prefix = f"mcp:{server_name}:"
for tool in tool_registry.all():
if tool.name.startswith(prefix) and tool.name not in names:
names.append(tool.name)
else:
for group_name in groups:
for tool_name in mcp_manager.resolve_group(server_name, group_name):
full_name = f"mcp:{server_name}:{tool_name}"
if full_name not in names:
names.append(full_name)
return names
@router.get("/prompts")
async def list_system_prompts(
profiles: Annotated[ProfileRegistry, Depends(get_profile_registry)],
mcp_manager: Annotated[McpManager, Depends(get_mcp_manager)],
tool_registry: Annotated[ToolRegistry, Depends(get_tool_registry)],
) -> list[dict]:
"""Return the full built system prompt for every profile, broken into sections.
Mirrors Agent._build_system_prompt() exactly so the debug view shows
what the model actually receives, including the dynamic profiles block.
"""
all_profiles = profiles.all()
persona = settings.navi_persona.strip()
result = []
for profile in all_profiles:
sections = []
if persona:
sections.append({"label": "persona", "content": persona})
sections.append({"label": "profile", "content": profile.system_prompt})
other = [p for p in all_profiles if p.id != profile.id]
if other:
lines = [
"## Available profiles",
f"Current: **{profile.id}**",
]
for p in other:
desc = p.short_description or p.description
lines.append(f"· {p.id}: {desc}")
lines.append(
"→ Switch profiles on your own judgment — do not ask for permission. "
"When a task clearly fits another profile, call switch_profile immediately, "
"then inform the user which profile is now active and why. "
"Use list_profiles if you need details about a profile's capabilities."
)
sections.append({"label": "profiles block", "content": "\n".join(lines)})
full = "\n\n---\n\n".join(s["content"] for s in sections)
result.append({
"profile_id": profile.id,
"profile_name": profile.name,
"model": profile.model,
"enabled_tools": profile.enabled_tools,
"mcp_servers": profile.mcp_servers,
"resolved_mcp_tools": _resolve_mcp_tools(profile, mcp_manager, tool_registry),
"sections": sections,
"full": full,
"total_chars": len(full),
})
return result
@router.get("/tools")
async def list_tools(
tools: Annotated[ToolRegistry, Depends(get_tool_registry)],
) -> list[dict]:
return [
{
"name": t.name,
"description": t.description,
"parameters": getattr(t, "parameters", {}),
}
for t in tools.all()
]
@router.get("/mcp_servers")
async def list_mcp_servers(
mcp_manager: Annotated[McpManager, Depends(get_mcp_manager)],
profiles: Annotated[ProfileRegistry, Depends(get_profile_registry)],
) -> list[dict]:
"""Return all configured MCP servers with groups, tools, instructions and profile mappings."""
configs = load_mcp_servers(mcp_manager.config_path)
connected = mcp_manager.clients
all_profiles = profiles.all()
result = []
for name, cfg in configs.items():
client = connected.get(name)
server_tools = []
if client and client.connected:
try:
tools = await client.list_tools()
server_tools = [
{"name": t.name, "description": t.description or ""}
for t in tools
]
except Exception:
pass
# Build profile mappings
profile_refs = []
for p in all_profiles:
if name in (p.mcp_servers or {}):
profile_refs.append({
"profile_id": p.id,
"profile_name": p.name,
"groups": p.mcp_servers[name],
})
# Merge instructions: server-provided + config overlay
parts: list[str] = []
if client and client.instructions:
parts.append(client.instructions)
if cfg.instructions:
if parts:
parts.append("")
parts.append(cfg.instructions)
result.append({
"name": name,
"connected": client is not None and client.connected,
"transport": cfg.transport,
"url": cfg.url,
"command": cfg.command,
"groups": cfg.groups,
"instructions": "\n".join(parts) if parts else None,
"tools": server_tools,
"profiles": profile_refs,
})
return result