@@ -49,16 +49,144 @@ class PyLLMClient : public LLMClient
4949 }
5050};
5151
52+ class PyMCPTransport : public MCPTransport
53+ {
54+ public:
55+ Result sendMessage (const var& message) override
56+ {
57+ py::gil_scoped_acquire gil;
58+ auto override = py::get_override (this , " sendMessage" );
59+
60+ if (! override )
61+ py::pybind11_fail (" Tried to call pure virtual function \" MCPTransport.sendMessage\" " );
62+
63+ auto result = override (message);
64+
65+ if (py::isinstance<Result> (result))
66+ return result.cast <Result>();
67+
68+ if (result.is_none () || result.cast <bool >())
69+ return Result::ok ();
70+
71+ return Result::fail (" Python MCPTransport.sendMessage returned false" );
72+ }
73+
74+ ResultValue<var> receiveMessage (int timeoutMs = -1 ) override
75+ {
76+ py::gil_scoped_acquire gil;
77+ auto override = py::get_override (this , " receiveMessage" );
78+
79+ if (! override )
80+ py::pybind11_fail (" Tried to call pure virtual function \" MCPTransport.receiveMessage\" " );
81+
82+ auto result = override (timeoutMs);
83+ if (result.is_none ())
84+ return makeResultValueFail (" Python MCPTransport.receiveMessage returned None" );
85+
86+ return makeResultValueOk (result.cast <var>());
87+ }
88+
89+ void setMessageHandler (MessageHandler handler) override
90+ {
91+ py::gil_scoped_acquire gil;
92+ auto override = py::get_override (this , " setMessageHandler" );
93+
94+ if (! override )
95+ py::pybind11_fail (" Tried to call pure virtual function \" MCPTransport.setMessageHandler\" " );
96+
97+ override (std::move (handler));
98+ }
99+
100+ Result start () override
101+ {
102+ py::gil_scoped_acquire gil;
103+ auto override = py::get_override (this , " start" );
104+
105+ if (! override )
106+ py::pybind11_fail (" Tried to call pure virtual function \" MCPTransport.start\" " );
107+
108+ auto result = override ();
109+
110+ if (py::isinstance<Result> (result))
111+ return result.cast <Result>();
112+
113+ if (result.is_none () || result.cast <bool >())
114+ return Result::ok ();
115+
116+ return Result::fail (" Python MCPTransport.start returned false" );
117+ }
118+
119+ void stop () override
120+ {
121+ py::gil_scoped_acquire gil;
122+ auto override = py::get_override (this , " stop" );
123+
124+ if (! override )
125+ py::pybind11_fail (" Tried to call pure virtual function \" MCPTransport.stop\" " );
126+
127+ override ();
128+ }
129+
130+ bool isConnected () const noexcept override
131+ {
132+ py::gil_scoped_acquire gil;
133+ auto override = py::get_override (this , " isConnected" );
134+
135+ if (! override )
136+ return false ;
137+
138+ try
139+ {
140+ return override ().cast <bool >();
141+ }
142+ catch (...)
143+ {
144+ return false ;
145+ }
146+ }
147+ };
148+
52149String messageRepr (const LLMMessage& message)
53150{
54151 return " LLMMessage(role='" + LLMMessage::roleToString (message.role ) + " ', content='" + message.content + " ')" ;
55152}
153+
154+ py::object optionalJsonRpcRequestToPython (const std::optional<JsonRpcRequest>& value)
155+ {
156+ return value.has_value () ? py::cast (*value) : py::none ();
157+ }
158+
159+ py::object optionalJsonRpcResponseToPython (const std::optional<JsonRpcResponse>& value)
160+ {
161+ return value.has_value () ? py::cast (*value) : py::none ();
162+ }
163+
164+ py::object optionalJsonRpcErrorToPython (const std::optional<JsonRpcError>& value)
165+ {
166+ return value.has_value () ? py::cast (*value) : py::none ();
167+ }
168+
169+ py::object optionalMCPToolDefinitionToPython (const std::optional<MCPToolDefinition>& value)
170+ {
171+ return value.has_value () ? py::cast (*value) : py::none ();
172+ }
173+
174+ py::object optionalMCPResourceDefinitionToPython (const std::optional<MCPResourceDefinition>& value)
175+ {
176+ return value.has_value () ? py::cast (*value) : py::none ();
177+ }
56178} // namespace
57179
58180void registerYupAiBindings (py::module_& m)
59181{
60182 auto ai = m.def_submodule (" ai" );
61183
184+ ai.attr (" MCP_PARSE_ERROR" ) = MCPErrorCodes::parseError;
185+ ai.attr (" MCP_INVALID_REQUEST" ) = MCPErrorCodes::invalidRequest;
186+ ai.attr (" MCP_METHOD_NOT_FOUND" ) = MCPErrorCodes::methodNotFound;
187+ ai.attr (" MCP_INVALID_PARAMS" ) = MCPErrorCodes::invalidParams;
188+ ai.attr (" MCP_INTERNAL_ERROR" ) = MCPErrorCodes::internalError;
189+
62190 py::enum_<LLMMessage::Role> (ai, " LLMMessageRole" )
63191 .value (" system" , LLMMessage::Role::system)
64192 .value (" user" , LLMMessage::Role::user)
@@ -226,6 +354,157 @@ void registerYupAiBindings (py::module_& m)
226354 .def (" embed" , &EmbeddingModel::embed)
227355 .def (" embedBatch" , &EmbeddingModel::embedBatch)
228356 .def_static (" cosineSimilarity" , &EmbeddingModel::cosineSimilarity);
357+
358+ py::class_<JsonRpcError> (ai, " JsonRpcError" )
359+ .def (py::init<>())
360+ .def_readwrite (" code" , &JsonRpcError::code)
361+ .def_readwrite (" message" , &JsonRpcError::message)
362+ .def_readwrite (" data" , &JsonRpcError::data)
363+ .def (" toVar" , &JsonRpcError::toVar)
364+ .def_static (" fromVar" , [] (const var& value)
365+ {
366+ return optionalJsonRpcErrorToPython (JsonRpcError::fromVar (value));
367+ });
368+
369+ py::class_<JsonRpcRequest> (ai, " JsonRpcRequest" )
370+ .def (py::init<>())
371+ .def_readwrite (" jsonrpc" , &JsonRpcRequest::jsonrpc)
372+ .def_readwrite (" id" , &JsonRpcRequest::id)
373+ .def_readwrite (" method" , &JsonRpcRequest::method)
374+ .def_readwrite (" params" , &JsonRpcRequest::params)
375+ .def (" isNotification" , &JsonRpcRequest::isNotification)
376+ .def (" toVar" , &JsonRpcRequest::toVar)
377+ .def_static (" fromVar" , [] (const var& value)
378+ {
379+ return optionalJsonRpcRequestToPython (JsonRpcRequest::fromVar (value));
380+ });
381+
382+ py::class_<JsonRpcResponse> (ai, " JsonRpcResponse" )
383+ .def (py::init<>())
384+ .def_readwrite (" jsonrpc" , &JsonRpcResponse::jsonrpc)
385+ .def_readwrite (" id" , &JsonRpcResponse::id)
386+ .def_readwrite (" result" , &JsonRpcResponse::result)
387+ .def_readwrite (" error" , &JsonRpcResponse::error)
388+ .def (" isError" , &JsonRpcResponse::isError)
389+ .def (" toVar" , &JsonRpcResponse::toVar)
390+ .def_static (" fromVar" , [] (const var& value)
391+ {
392+ return optionalJsonRpcResponseToPython (JsonRpcResponse::fromVar (value));
393+ });
394+
395+ py::class_<MCPCapabilities> (ai, " MCPCapabilities" )
396+ .def (py::init<>())
397+ .def_readwrite (" supportsTools" , &MCPCapabilities::supportsTools)
398+ .def_readwrite (" supportsResources" , &MCPCapabilities::supportsResources)
399+ .def_readwrite (" supportsPrompts" , &MCPCapabilities::supportsPrompts)
400+ .def_readwrite (" supportsLogging" , &MCPCapabilities::supportsLogging)
401+ .def (" toVar" , &MCPCapabilities::toVar)
402+ .def_static (" fromVar" , &MCPCapabilities::fromVar);
403+
404+ py::class_<MCPToolDefinition> (ai, " MCPToolDefinition" )
405+ .def (py::init<>())
406+ .def_readwrite (" name" , &MCPToolDefinition::name)
407+ .def_readwrite (" description" , &MCPToolDefinition::description)
408+ .def_readwrite (" inputSchema" , &MCPToolDefinition::inputSchema)
409+ .def (" toVar" , &MCPToolDefinition::toVar)
410+ .def_static (" fromVar" , [] (const var& value)
411+ {
412+ return optionalMCPToolDefinitionToPython (MCPToolDefinition::fromVar (value));
413+ });
414+
415+ py::class_<MCPResourceDefinition> (ai, " MCPResourceDefinition" )
416+ .def (py::init<>())
417+ .def_readwrite (" uri" , &MCPResourceDefinition::uri)
418+ .def_readwrite (" name" , &MCPResourceDefinition::name)
419+ .def_readwrite (" description" , &MCPResourceDefinition::description)
420+ .def_readwrite (" mimeType" , &MCPResourceDefinition::mimeType)
421+ .def (" toVar" , &MCPResourceDefinition::toVar)
422+ .def_static (" fromVar" , [] (const var& value)
423+ {
424+ return optionalMCPResourceDefinitionToPython (MCPResourceDefinition::fromVar (value));
425+ });
426+
427+ py::class_<MCPTransport, PyMCPTransport> (ai, " MCPTransport" )
428+ .def (py::init<>())
429+ .def (" sendMessage" , &MCPTransport::sendMessage)
430+ .def (" receiveMessage" , [] (MCPTransport& self, int timeoutMs)
431+ {
432+ auto result = self.receiveMessage (timeoutMs);
433+ if (result.failed ())
434+ py::pybind11_fail (result.getErrorMessage ().toRawUTF8 ());
435+
436+ return result.getValue ();
437+ },
438+ " timeoutMs" _a = -1 )
439+ .def (" setMessageHandler" , &MCPTransport::setMessageHandler)
440+ .def (" start" , &MCPTransport::start)
441+ .def (" stop" , &MCPTransport::stop)
442+ .def (" isConnected" , &MCPTransport::isConnected);
443+
444+ py::class_<MCPClient> (ai, " MCPClient" )
445+ .def (py::init<std::unique_ptr<MCPTransport>>(), " transport" _a)
446+ .def (" initialize" , &MCPClient::initialize, " clientCapabilities" _a = MCPCapabilities {})
447+ .def (" listTools" , &MCPClient::listTools)
448+ .def (" callTool" , [] (MCPClient& self, const String& toolName, const var& arguments)
449+ {
450+ auto result = self.callTool (toolName, arguments);
451+ if (result.failed ())
452+ py::pybind11_fail (result.getErrorMessage ().toRawUTF8 ());
453+
454+ return result.getValue ();
455+ },
456+ " toolName" _a,
457+ " arguments" _a)
458+ .def (" listResources" , &MCPClient::listResources)
459+ .def (" readResource" , [] (MCPClient& self, const String& uri)
460+ {
461+ auto result = self.readResource (uri);
462+ if (result.failed ())
463+ py::pybind11_fail (result.getErrorMessage ().toRawUTF8 ());
464+
465+ return result.getValue ();
466+ },
467+ " uri" _a)
468+ .def (" registerToolsWith" , &MCPClient::registerToolsWith)
469+ .def (" getTransport" , &MCPClient::getTransport, py::return_value_policy::reference_internal);
470+
471+ py::class_<MCPServer::Options> (ai, " MCPServerOptions" )
472+ .def (py::init<>())
473+ .def_readwrite (" serverName" , &MCPServer::Options::serverName)
474+ .def_readwrite (" serverVersion" , &MCPServer::Options::serverVersion)
475+ .def_readwrite (" capabilities" , &MCPServer::Options::capabilities);
476+
477+ py::class_<MCPServer> (ai, " MCPServer" )
478+ .def (py::init<>())
479+ .def (py::init<MCPServer::Options>())
480+ .def (" registerTool" , [] (MCPServer& self, MCPToolDefinition tool, py::function function)
481+ {
482+ self.registerTool (std::move (tool), [function = std::move (function)] (const var& arguments) -> var
483+ {
484+ py::gil_scoped_acquire gil;
485+ return function (arguments).cast <var>();
486+ });
487+ },
488+ " tool" _a,
489+ " function" _a)
490+ .def (" registerLLMTool" , static_cast <void (MCPServer::*) (LLMTool)> (&MCPServer::registerTool), " tool" _a)
491+ .def (" unregisterTool" , &MCPServer::unregisterTool)
492+ .def (" registerResource" , [] (MCPServer& self, MCPResourceDefinition resource, py::function function)
493+ {
494+ self.registerResource (std::move (resource), [function = std::move (function)]() -> String
495+ {
496+ py::gil_scoped_acquire gil;
497+ return py::str (function ()).cast <String>();
498+ });
499+ },
500+ " resource" _a,
501+ " function" _a)
502+ .def (" unregisterResource" , &MCPServer::unregisterResource)
503+ .def (" start" , &MCPServer::start, " transport" _a)
504+ .def (" stop" , &MCPServer::stop)
505+ .def (" isRunning" , &MCPServer::isRunning)
506+ .def (" startStdio" , &MCPServer::startStdio)
507+ .def (" startHttp" , &MCPServer::startHttp, " port" _a);
229508}
230509
231510} // namespace yup::Bindings
0 commit comments