diff --git a/tools/agent/server/agent-routes.cpp b/tools/agent/server/agent-routes.cpp index ca39b5ed8c5..dbec08cde57 100644 --- a/tools/agent/server/agent-routes.cpp +++ b/tools/agent/server/agent-routes.cpp @@ -3,6 +3,7 @@ #include #include +#include #include #include @@ -75,6 +76,22 @@ struct sse_stream_res : server_http_res { } }; +// Wrapper that holds shared_ptr to sse_stream_res to extend its lifetime +// This ensures the SSE response object lives until both: +// 1. The HTTP framework is done with it +// 2. The worker thread callback is done with it +struct sse_shared_wrapper : server_http_res { + std::shared_ptr sse; + + explicit sse_shared_wrapper(std::shared_ptr s) : sse(std::move(s)) { + content_type = sse->content_type; + headers = sse->headers; + next = [this](std::string & output) -> bool { + return sse->next(output); + }; + } +}; + agent_routes::agent_routes(agent_session_manager & session_mgr) : session_mgr_(session_mgr) { @@ -106,6 +123,19 @@ agent_routes::agent_routes(agent_session_manager & session_mgr) if (body.contains("working_dir")) { config.working_dir = body["working_dir"].get(); } + // Skills configuration + if (body.contains("enable_skills")) { + config.enable_skills = body["enable_skills"].get(); + } + if (body.contains("skills_paths") && body["skills_paths"].is_array()) { + for (const auto & path : body["skills_paths"]) { + config.extra_skills_paths.push_back(path.get()); + } + } + // AGENTS.md configuration + if (body.contains("enable_agents_md")) { + config.enable_agents_md = body["enable_agents_md"].get(); + } } catch (const json::exception & e) { return make_error(400, std::string("Invalid JSON: ") + e.what()); } @@ -192,12 +222,14 @@ agent_routes::agent_routes(agent_session_manager & session_mgr) return make_error(400, std::string("Invalid JSON: ") + e.what()); } - // Create SSE streaming response - auto sse_res = std::make_unique(); - auto * sse_ptr = sse_res.get(); + // Create SSE streaming response with shared ownership + // The shared_ptr ensures the response lives until both: + // 1. The HTTP framework is done streaming + // 2. The worker thread callback is done + auto sse_shared = std::make_shared(); - // Start processing in background - session->send_message(content, [sse_ptr](const agent_event & event) { + // Start processing in background - capture shared_ptr to extend lifetime + session->send_message(content, [sse_shared](const agent_event & event) { std::string event_type; switch (event.type) { case agent_event_type::TEXT_DELTA: @@ -223,19 +255,20 @@ agent_routes::agent_routes(agent_session_manager & session_mgr) break; case agent_event_type::COMPLETED: event_type = "completed"; - sse_ptr->send(event_type, event.data); - sse_ptr->finish(); + sse_shared->send(event_type, event.data); + sse_shared->finish(); return; case agent_event_type::ERROR: event_type = "error"; - sse_ptr->send(event_type, event.data); - sse_ptr->finish(); + sse_shared->send(event_type, event.data); + sse_shared->finish(); return; } - sse_ptr->send(event_type, event.data); + sse_shared->send(event_type, event.data); }); - return sse_res; + // Return wrapper that holds shared_ptr reference + return std::make_unique(sse_shared); }; // GET /v1/agent/session/:id/messages - Get conversation history diff --git a/tools/agent/server/agent-server.cpp b/tools/agent/server/agent-server.cpp index b1623f52b2b..d5ea13472d4 100644 --- a/tools/agent/server/agent-server.cpp +++ b/tools/agent/server/agent-server.cpp @@ -9,6 +9,12 @@ #include "llama.h" #include "log.h" +// MCP support (Unix only) +#ifndef _WIN32 +#include "../mcp/mcp-server-manager.h" +#include "../mcp/mcp-tool-wrapper.h" +#endif + #include #include #include @@ -147,6 +153,28 @@ int main(int argc, char ** argv) { ctx_http.is_ready.store(true); LOG_INF("Model loaded successfully\n"); + // Initialize MCP servers (Unix only) + // MCP manager must be declared here to outlive session manager (tools hold pointer to it) +#ifndef _WIN32 + mcp_server_manager mcp_mgr; + int mcp_tools_count = 0; + std::string working_dir = "."; // Default working directory for MCP config search + std::string mcp_config = find_mcp_config(working_dir); + if (!mcp_config.empty()) { + LOG_INF("Loading MCP config from: %s\n", mcp_config.c_str()); + if (mcp_mgr.load_config(mcp_config)) { + int started = mcp_mgr.start_servers(); + if (started > 0) { + register_mcp_tools(mcp_mgr); + mcp_tools_count = static_cast(mcp_mgr.list_all_tools().size()); + LOG_INF("MCP: %d servers started, %d tools registered\n", started, mcp_tools_count); + } + } + } +#else + int mcp_tools_count = 0; +#endif + // Setup signal handlers shutdown_handler = [&ctx_server](int) { ctx_server.terminate(); @@ -171,6 +199,9 @@ int main(int argc, char ** argv) { LOG_INF("llama-agent-server is listening on %s\n", ctx_http.listening_address.c_str()); LOG_INF("============================================\n"); LOG_INF("\n"); + if (mcp_tools_count > 0) { + LOG_INF("MCP tools: %d\n", mcp_tools_count); + } LOG_INF("API Endpoints:\n"); LOG_INF(" POST /v1/agent/session - Create a new session\n"); LOG_INF(" GET /v1/agent/session/:id - Get session info\n"); diff --git a/tools/agent/server/agent-session.cpp b/tools/agent/server/agent-session.cpp index 5bf4f057205..54118c31a3b 100644 --- a/tools/agent/server/agent-session.cpp +++ b/tools/agent/server/agent-session.cpp @@ -1,8 +1,28 @@ #include "agent-session.h" +#include "../skills/skills-manager.h" +#include "../agents-md/agents-md-manager.h" +#include #include #include +// Get the user config directory for llama-agent +static std::string get_config_dir() { +#ifdef _WIN32 + const char * appdata = std::getenv("APPDATA"); + if (appdata) { + return std::string(appdata) + "\\llama-agent"; + } + return ""; +#else + const char * home = std::getenv("HOME"); + if (home) { + return std::string(home) + "/.llama-agent"; + } + return ""; +#endif +} + // agent_session implementation agent_session::agent_session(const std::string & id, @@ -21,6 +41,40 @@ agent_session::agent_session(const std::string & id, permissions_.set_project_root(config_.working_dir); } permissions_.set_yolo_mode(config_.yolo_mode); + + std::string config_dir = get_config_dir(); + + // Discover Skills (agentskills.io spec) + if (config_.enable_skills) { + skills_manager skills_mgr; + std::vector skill_paths; + + // Project-local skills (highest priority) + // Default to "." if working_dir not set, matching CLI behavior + std::string skills_working_dir = config_.working_dir.empty() ? "." : config_.working_dir; + skill_paths.push_back(skills_working_dir + "/.llama-agent/skills"); + + // User-global skills + if (!config_dir.empty()) { + skill_paths.push_back(config_dir + "/skills"); + } + + // Extra paths from config + for (const auto & path : config_.extra_skills_paths) { + skill_paths.push_back(path); + } + + skills_mgr.discover(skill_paths); + skills_prompt_section_ = skills_mgr.generate_prompt_section(); + } + + // Discover AGENTS.md files (agents.md spec) + if (config_.enable_agents_md) { + agents_md_manager agents_md_mgr; + std::string working_dir = config_.working_dir.empty() ? "." : config_.working_dir; + agents_md_mgr.discover(working_dir, config_dir); + agents_md_prompt_section_ = agents_md_mgr.generate_prompt_section(); + } } agent_session::~agent_session() { @@ -61,6 +115,15 @@ void agent_session::send_message(const std::string & content, agent_cfg.working_dir = config_.working_dir; agent_cfg.yolo_mode = config_.yolo_mode; + // Skills configuration + agent_cfg.enable_skills = config_.enable_skills; + agent_cfg.skills_search_paths = config_.extra_skills_paths; + agent_cfg.skills_prompt_section = skills_prompt_section_; + + // AGENTS.md configuration + agent_cfg.enable_agents_md = config_.enable_agents_md; + agent_cfg.agents_md_prompt_section = agents_md_prompt_section_; + loop_ = std::make_unique( server_ctx_, params_, diff --git a/tools/agent/server/agent-session.h b/tools/agent/server/agent-session.h index 67ccfe75134..ca372920c76 100644 --- a/tools/agent/server/agent-session.h +++ b/tools/agent/server/agent-session.h @@ -26,6 +26,13 @@ struct agent_session_config { int tool_timeout_ms = 120000; std::string working_dir; std::string system_prompt; // Optional custom system prompt + + // Skills configuration (agentskills.io spec) + bool enable_skills = true; + std::vector extra_skills_paths; + + // AGENTS.md configuration (agents.md spec) + bool enable_agents_md = true; }; // State of an agent session @@ -118,6 +125,10 @@ class agent_session { // Timestamps std::chrono::steady_clock::time_point created_at_; std::chrono::steady_clock::time_point last_activity_; + + // Discovered Skills and AGENTS.md content (cached at session creation) + std::string skills_prompt_section_; + std::string agents_md_prompt_section_; }; // Manages multiple agent sessions