-
Notifications
You must be signed in to change notification settings - Fork 16
Developer guide
When using this SDK, developers don't need to concern themselves with the implementation details of the MCP protocol. You can implement your own MCP Server in just the following three simple steps, enabling seamless collaboration between large models and your own software.
Developers can complete the corresponding business tasks by inheriting the relevant Task classes in the SDK. For example, if the business MCP Server needs to expose the Call Tool capability to the large model, it is necessary to inherit MCP::ProcessCallToolRequest.
.h
#pragma once
#include <Task/BasicTask.h>
#include <Message/Request.h>
#include <string>
namespace Implementation
{
class CEchoTask : public MCP::ProcessCallToolRequest
{
public:
static constexpr const char* TOOL_NAME = "echo";
static constexpr const char* TOOL_DESCRIPTION = u8"Receive the data sent by the client and then return the exact same data to the client.";
static constexpr const char* TOOL_INPUT_SCHEMA = u8R"({"type":"object","properties":{"input":{"type":"string","description":"client input data"}},"required":["input"]})";
static constexpr const char* TOOL_ARGUMENT_INPUT = "input";
CEchoTask(const std::shared_ptr<MCP::Request>& spRequest)
: ProcessCallToolRequest(spRequest)
{
}
std::shared_ptr<CMCPTask> Clone() const override;
// If it's a time-consuming task, you need to start a thread to execute it asynchronously.
int Execute() override;
// This method is used to cancel time-consuming asynchronous tasks.
int Cancel() override;
};
}
.cpp
#include "EchoTask.h"
#include <Public/PublicDef.h>
#include <Public/StringHelper.h>
#include <fstream>
namespace Implementation
{
std::shared_ptr<MCP::CMCPTask> CEchoTask::Clone() const
{
auto spClone = std::make_shared<CEchoTask>(nullptr);
if (spClone)
{
*spClone = *this;
}
return spClone;
}
int CEchoTask::Cancel()
{
return MCP::ERRNO_OK;
}
int CEchoTask::Execute()
{
int iErrCode = MCP::ERRNO_INTERNAL_ERROR;
if (!IsValid())
return iErrCode;
Json::Value jArgument;
std::string strInput;
auto spCallToolRequest = std::dynamic_pointer_cast<MCP::CallToolRequest>(m_spRequest);
if (!spCallToolRequest)
goto PROC_END;
if (spCallToolRequest->strName.compare(TOOL_NAME) != 0)
goto PROC_END;
jArgument = spCallToolRequest->jArguments;
if (!jArgument.isMember(TOOL_ARGUMENT_INPUT) || !jArgument[TOOL_ARGUMENT_INPUT].isString())
goto PROC_END;
strInput = jArgument[TOOL_ARGUMENT_INPUT].asString();
iErrCode = MCP::ERRNO_OK;
PROC_END:
auto spExecuteResult = BuildResult();
if (spExecuteResult)
{
MCP::TextContent textContent;
textContent.strType = MCP::CONST_TEXT;
if (MCP::ERRNO_OK == iErrCode)
{
spExecuteResult->bIsError = false;
textContent.strText = strInput;
}
else
{
spExecuteResult->bIsError = true;
textContent.strText = u8"Unfortunately, the execution failed.";
textContent.strText += u8"Error code:";
textContent.strText += std::to_string(iErrCode);
}
spExecuteResult->vecTextContent.push_back(textContent);
iErrCode = NotifyResult(spExecuteResult);
}
return iErrCode;
}
}To facilitate developers in extending business - related logic, developers need to inherit MCP::CMCPServer to implement their own Server sub - classes. Due to the design principles of the MCP microservice architecture, all MCP Servers should follow the single - responsibility principle. Therefore, the SDK requires the implementation of the Server class as a Singleton. Here is an example:
.h
#pragma once
#include <Entity/Server.h>
namespace Implementation
{
// A server class for business operations is declared.
// It is used to customize unique logic, but it must be a singleton.
class CEchoServer : public MCP::CMCPServer<CEchoServer>
{
public:
static constexpr const char* SERVER_NAME = "echo_server";
static constexpr const char* SERVER_VERSION = "1.0.0.1";
// This is the initialization method, which is used to configure the Server.
// The Server can be started only after the configuration is successful.
int Initialize() override;
private:
friend class MCP::CMCPServer<CEchoServer>;
CEchoServer() = default;
static CEchoServer s_Instance;
};
}
.cpp
#include "EchoServer.h"
#include "EchoTask.h"
namespace Implementation
{
int CEchoServer::Initialize()
{
// 1. Set the basic information of the Server.
MCP::Implementation serverInfo;
serverInfo.strName = Implementation::CEchoServer::SERVER_NAME;
serverInfo.strVersion = Implementation::CEchoServer::SERVER_VERSION;
SetServerInfo(serverInfo);
// 2. Register the Server's capability declaration.
MCP::Tools tools;
RegisterServerToolsCapabilities(tools);
// 3. Register the descriptions of the Server's actual capabilities and their calling methods.
MCP::Tool tool;
tool.strName = Implementation::CEchoTask::TOOL_NAME;
tool.strDescription = Implementation::CEchoTask::TOOL_DESCRIPTION;
std::string strInputSchema = Implementation::CEchoTask::TOOL_INPUT_SCHEMA;
Json::Reader reader;
Json::Value jInputSchema(Json::objectValue);
if (!reader.parse(strInputSchema, jInputSchema) || !jInputSchema.isObject())
return MCP::ERRNO_PARSE_ERROR;
tool.jInputSchema = jInputSchema;
std::vector<MCP::Tool> vecTools;
vecTools.push_back(tool);
RegisterServerTools(vecTools, false);
// 4. Register the tasks for implementing the actual capabilities.
auto spCallToolsTask = std::make_shared<Implementation::CEchoTask>(nullptr);
if (!spCallToolsTask)
return MCP::ERRNO_INTERNAL_ERROR;
RegisterToolsTasks(Implementation::CEchoTask::TOOL_NAME, spCallToolsTask);
return MCP::ERRNO_OK;
}
CEchoServer CEchoServer::s_Instance;
}
Just as shown below, after configuring and starting the Server, the large model can smoothly call the Server's capabilities.
int LaunchEchoServer()
{
// 1. Configure the Server.
auto& server = Implementation::CEchoServer::GetInstance();
int iErrCode = server.Initialize();
if (MCP::ERRNO_OK == iErrCode)
{
// 2. Start the Server.
iErrCode = server.Start();
if (MCP::ERRNO_OK == iErrCode)
{
// 3. Stop the Server.
server.Stop();
}
}
return iErrCode;
}Please check How can I make an LLM call my MCP server? for more information.
Please check Inspector for detail.