#pragma once #include "picojson.h" #include #include #include #include #include #include #include #include namespace tp::taskboard { struct McpServerCommand { std::string executable; std::vector arguments; std::vector> environment; std::string stderrPath; }; struct McpToolResult { bool ok = false; picojson::value value; std::string error; }; class McpClient { public: using Completion = std::function; McpClient(); ~McpClient(); McpClient(const McpClient&) = delete; McpClient& operator=(const McpClient&) = delete; bool start(const McpServerCommand& command, std::string& error); void stop(); [[nodiscard]] bool isRunning() const; void callTool(const std::string& name, picojson::object arguments, Completion completion); McpToolResult callToolBlocking(const std::string& name, picojson::object arguments); void dispatchCompletions(); [[nodiscard]] size_t pendingCount() const; void waitForIdle(); void setOnResponse(std::function callback); private: struct Pending { Completion completion; }; bool spawn(const McpServerCommand& command, std::string& error); bool handshake(std::string& error); void readLoop(); void handleMessage(const std::string& line); long nextId(); bool send(const picojson::object& message); bool request(const std::string& method, picojson::object params, Completion completion); bool waitUntil(const std::function& finished); void failAll(const std::string& reason); void deliver(Completion completion, McpToolResult result); [[nodiscard]] static McpToolResult toolResult(const picojson::value& response); int mPid = -1; int mToServer = -1; int mFromServer = -1; std::thread mReader; mutable std::mutex mMutex; std::condition_variable mResponded; std::map mPending; std::deque> mReady; long mNextId = 1; bool mRunning = false; std::function mOnResponse; }; }