11
22#include < json.hpp>
33#include " toolcall-handler.h"
4+ #include < chrono>
5+ #include < stdexcept>
46
57#ifdef LLAMA_USE_CURL
6- # include " mcp_sse_transport.h"
8+ # include " mcp_sse_transport.h"
79#endif
810
911#include " mcp_stdio_transport.h"
@@ -18,10 +20,12 @@ std::shared_ptr<toolcall::handler> toolcall::create_handler(const toolcall::para
1820 if (params) {
1921 if (params.has_uri ()) {
2022#ifdef LLAMA_USE_CURL
21- handler.reset (new toolcall::handler (std::make_unique<toolcall::mcp_impl>(tools, choice)));
23+ handler.reset (new toolcall::handler (
24+ std::make_unique<toolcall::mcp_impl>(tools, choice)));
2225#endif
2326 } else {
24- handler.reset (new toolcall::handler (std::make_unique<toolcall::loopback_impl>(tools, choice)));
27+ handler.reset (new toolcall::handler (
28+ std::make_unique<toolcall::loopback_impl>(tools, choice)));
2529 }
2630 }
2731 return handler;
@@ -31,6 +35,10 @@ std::string toolcall::handler::tool_list() {
3135 return impl_->tool_list ();
3236}
3337
38+ bool toolcall::handler::tool_list_dirty () const {
39+ return impl_->tool_list_dirty ();
40+ }
41+
3442toolcall::action toolcall::handler::call (const std::string & request, std::string & response) {
3543 last_action_ = impl_->call (request, response);
3644 return last_action_;
@@ -39,20 +47,33 @@ toolcall::action toolcall::handler::call(const std::string & request, std::strin
3947const std::string & toolcall::handler::tool_choice () const {
4048 return impl_->tool_choice ();
4149}
50+
4251toolcall::action toolcall::handler::last_action () const {
4352 return last_action_;
4453}
4554
55+ void toolcall::handler::initialize () {
56+ impl_->initialize ();
57+ }
58+
4659#ifdef LLAMA_USE_CURL
4760toolcall::mcp_impl::mcp_impl (std::string server_uri, std::string tool_choice)
4861 : handler_impl(tool_choice),
49- transport_(new mcp_sse_transport(server_uri))
62+ transport_(new mcp_sse_transport(server_uri)),
63+ tools_(" []" ),
64+ tools_mutex_(),
65+ tools_populating_(),
66+ next_id_(1 )
5067{
51- transport_->start ();
5268}
5369#else
5470toolcall::mcp_impl::mcp_impl (std::string /* server_uri*/ , std::string tool_choice)
55- : handler_impl(tool_choice)
71+ : handler_impl(tool_choice),
72+ transport_(nullptr ),
73+ tools_(" []" ),
74+ tools_mutex_(),
75+ tools_populating_(),
76+ next_id_(1 )
5677{
5778}
5879#endif
@@ -61,15 +82,94 @@ toolcall::mcp_impl::mcp_impl(std::vector<std::string> argv, std::string tool_cho
6182 : handler_impl(tool_choice),
6283 transport_(new mcp_stdio_transport(argv))
6384{
85+ }
86+
87+ void toolcall::mcp_impl::initialize () {
88+ using on_response = toolcall::callback<mcp::initialize_response>;
89+ using on_list_changed = toolcall::callback<mcp::tools_list_changed_notification>;
90+
91+ if (transport_ == nullptr ) return ;
92+ std::unique_lock<std::mutex> lock (tools_mutex_);
93+
6494 transport_->start ();
95+
96+ mcp::capabilities caps;
97+ on_response set_caps = [this , &caps] (const mcp::initialize_response & resp) {
98+ std::unique_lock<std::mutex> lock (tools_mutex_);
99+ caps = resp.capabilities ();
100+ tools_populating_.notify_one ();
101+ };
102+
103+ transport_->subscribe (set_caps);
104+
105+ mcp::initialize_request req (next_id_++);
106+ transport_->send (req.toJson ());
107+
108+ tools_populating_.wait_for (lock, std::chrono::seconds (15 ));
109+ transport_->unsubscribe (set_caps);
110+
111+ on_list_changed update_dirty = [this ] (const mcp::tools_list_changed_notification &) {
112+ tool_list_dirty_ = true ;
113+ };
114+
115+ bool has_tools = false ;
116+ for (const auto & cap : caps) {
117+ if (cap.name == " tools" ) {
118+ has_tools = true ;
119+ if (cap.listChanged ) {
120+ transport_->subscribe (update_dirty);
121+ }
122+ break ;
123+ }
124+ }
125+ if (! has_tools) {
126+ throw std::runtime_error (" MCP server does not support toolcalls!" );
127+ }
128+ }
129+
130+ static std::string tools_list_to_oai_json (const mcp::tools_list & tools) {
131+ return " []" ; // TODO
65132}
66133
67134std::string toolcall::mcp_impl::tool_list () {
68- // Construct tools/list call and send to transport
69- return " []" ;// TODO
135+ using on_response = toolcall::callback<mcp::tools_list_response>;
136+
137+ if (tool_list_dirty_) {
138+ std::unique_lock<std::mutex> lock (tools_mutex_);
139+
140+ mcp::tools_list tools;
141+ on_response set_tools = [this , &tools] (const mcp::tools_list_response & resp) {
142+ std::unique_lock<std::mutex> lock (tools_mutex_);
143+
144+ tools.insert (tools.end (), resp.tools ().begin (), resp.tools ().end ());
145+ auto cursor = resp.next_cursor ();
146+ if (! cursor.empty ()) {
147+ mcp::tools_list_request req (std::to_string (next_id_++), cursor);
148+ transport_->send (req.toJson ());
149+ return ;
150+ }
151+ tool_list_dirty_ = false ;
152+ lock.unlock ();
153+ tools_populating_.notify_one ();
154+ };
155+
156+ transport_->subscribe (set_tools);
157+
158+ mcp::tools_list_request req (std::to_string (next_id_++));
159+ transport_->send (req.toJson ());
160+
161+ tools_populating_.wait_for (lock, std::chrono::seconds (15 ));
162+ transport_->unsubscribe (set_tools);
163+
164+ tools_ = tools_list_to_oai_json (tools);
165+ }
166+ return tools_;
70167}
71168
72169toolcall::action toolcall::mcp_impl::call (const std::string & /* request*/ , std::string & /* response*/ ) {
170+ if (transport_ == nullptr ) {
171+ return toolcall::DEFER;
172+ }
73173 // Construct tool call and send to transport
74174 return toolcall::ACCEPT; // TODO
75175}
0 commit comments