Skip to content

Developer guide

ZeusChan edited this page Apr 29, 2025 · 11 revisions

1. How to develop your own MCP Server?

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.

1.1 Implement your business tasks.

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;
	}
}

1.2 Implement your business server.

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;
}

1.3 Configuration and startup of your server.

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;
}

2. How to debug your own MCP Server?

2.1 Debug your MCP Server using a well-known LLM client.

Please check How can I make an LLM call my MCP server? for more information.

2.2 Debug your MCP Server using the official debugging tool, Inspector.

Please check Inspector for detail.