Implementing Custom MCP Methods¶
MCP Server¶
Handling Custom Requests/Notifications¶
Following request/notification transport callbacks are defined under the McpServerBase
class:
class MBASE_API McpServerBase : public mbase::logical_processor {
public:
...
virtual bool on_client_request_t(mbase::McpServerClient* in_client, const mbase::Json& in_msgid, const mbase::string& in_method, const mbase::Json& in_params)
{
/*
Invoked when a new request is received.
Returning false will make the MCP server behave as normal which is
processing the request and appending it to the MCP message queue waiting for
application thread to dispatch.
If you return true from this method,
the current request's responsibility is on your own.
Returning true also indicates that you are writing a custom request handler.
See: TODO -> Link about custom request handling
See: https://modelcontextprotocol.io/specification/2024-11-05/basic/messages
*/
}
virtual GENERIC on_client_notification_t(mbase::McpServerClient* in_client, const mbase::string& in_method, const mbase::Json& in_params)
{
/*
Invoked when a notification is sent from the client.
See: https://modelcontextprotocol.io/specification/2024-11-05/basic/messages
*/
}
...
};
In which the McpServerStdio
, McpServerHttpStreamableStateful
and McpServerHttpStreamableStateless
inherit from
but not defining the on_client_request_t
and on_client_notification_t
:
class MBASE_API McpServerStdio : public mbase::McpServerBase {
...
};
class MBASE_API McpServerHttpStreamableStateful : public mbase::McpServerHttpBase {
...
};
class MBASE_API McpServerHttpStreamableStateless : public mbase::McpServerHttpBase {
...
};
Writing a custom request/notification handling mandates for a user to inherit from one of those classes and override their fundamental callbacks.
The assumption of STDIO transport will result with the following example implementation:
class ExampleDerivedServer : public mbase::McpServerStdio {
public:
ExampleDerivedServer() : mbase::McpServerStdio("MCP Sample Server","1.0.0"){}
bool on_client_request_t(mbase::McpServerClient* in_client, const mbase::Json& in_msgid, const mbase::string& in_method, const mbase::Json& in_params) override
{
return true;
}
void on_client_notification_t(mbase::McpServerClient* in_client, const mbase::string& in_method, const mbase::Json& in_params) override
{
}
};
In which returning true implies the user is writing a custom request.
The workflow for writing a custom request/handling is as follows:
Read the method and params and write your business logic.
Generate an MCP response string.
Send the generated MCP payload.
Let’s first read the method:
bool on_client_request_t(mbase::McpServerClient* in_client, const mbase::Json& in_msgid, const mbase::string& in_method, const mbase::Json& in_params) override
{
if(in_method == "custom_method")
{
return true;
}
}
Then, we will call the mcp_generate_response
procedure which is defined under the mcp_packet_parsing.h
file:
bool on_client_request_t(mbase::McpServerClient* in_client, const mbase::Json& in_msgid, const mbase::string& in_method, const mbase::Json& in_params) override
{
if(in_method == "custom_method")
{
mbase::Json randomData;
randomData["example_key"] = "example_value";
randomData["example_key_2"] = 100; // arbitrary number
mbase::string generatedPacket = mbase::mcp_generate_response(in_msgid, randomData);
return true;
}
}
Then, send the generated MCP payload:
bool on_client_request_t(mbase::McpServerClient* in_client, const mbase::Json& in_msgid, const mbase::string& in_method, const mbase::Json& in_params) override
{
if(in_method == "custom_method")
{
mbase::Json randomData;
randomData["example_key"] = "example_value";
randomData["example_key_2"] = 100; // arbitrary number
mbase::string generatedPacket = mbase::mcp_generate_response(in_msgid, randomData);
in_client->send_mcp_payload(generatedPacket);
return true;
}
}
You can also send error messages by calling mcp_generate_error_message
:
bool on_client_request_t(mbase::McpServerClient* in_client, const mbase::Json& in_msgid, const mbase::string& in_method, const mbase::Json& in_params) override
{
if(in_method == "custom_method")
{
mbase::Json errorData;
errorData["random_data"] = "data";
mbase::string generatedPayload = mbase::mcp_generate_error_message(in_msgid, MBASE_MCP_INTERNAL_ERROR, "Example error message", errorData);
in_client->send_mcp_payload(generatedPayload);
return true;
}
}
When it comes to notification handling, sending a custom response is not possible since the notification message doesn’t have an message id associated with them.
Instead, you may do the following:
void on_client_notification_t(mbase::McpServerClient* in_client, const mbase::string& in_method, const mbase::Json& in_params) override
{
if(in_method == "custom_notif_1")
{
// do stuff
}
else if(in_method == "custom_notif_2")
{
// do other stuff
}
else if(in_method == "custom_notif_N")
{
// do different stuff
}
}
Sending Custom Requests/Notifications¶
Writing a custom request/notification is basically generating an
MCP payload by calling one of the procedures that are defined under the mcp_packet_parsing.h
file
and sending the generated payload.
Here are the payload generation procedures:
mbase::string mcp_generate_notification(
const mbase::string& in_method,
const mbase::Json& in_params = mbase::Json()
);
mbase::string mcp_create_request(
const mbase::string& in_id,
const mbase::string& in_method,
const mbase::Json& in_params = mbase::Json()
);
As an example, we will send a custom request to the client during a tool feature callback:
mbase::McpResponseTool echo(mbase::McpServerClient* in_client_instance, const mbase::McpMessageMap& in_msg_map, const mbase::Json& in_progress_token)
{
mbase::Json customParams;
customParams["param"] = "hello param";
mbase::string generatedPayload = mbase::mcp_create_request(mbase::string::generate_uuid(), "custom_method", customParams);
in_client_instance->send_mcp_payload(generatedPayload);
mbase::McpResponseTextTool toolResponse;
toolResponse.mText = "Random tool response";
return toolResponse;
}
Or notification:
mbase::McpResponseTool echo(mbase::McpServerClient* in_client_instance, const mbase::McpMessageMap& in_msg_map, const mbase::Json& in_progress_token)
{
mbase::Json customParams;
customParams["param"] = "hello param";
mbase::string generatedPayload = mbase::mcp_generate_notification("custom_notification", customParams);
in_client_instance->send_mcp_payload(generatedPayload);
mbase::McpResponseTextTool toolResponse;
toolResponse.mText = "Random tool response";
return toolResponse;
}
MCP Client¶
Handling Custom Requests/Notifications¶
Following request/notification transport callbacks are defined under the McpClientBase
class:
class MBASE_API McpClientBase {
public:
...
virtual bool on_server_request_t(McpServerStateBase* in_server, const mbase::Json& in_msgid, const mbase::string& in_method, const mbase::Json& in_params)
{
/*
IMPORTANT: Server to client requests are only possible on STDIO and SSE transports.
Invoked when a request is sent from the server.
Returning false will make the MCP client behave as normal which is processing the
request and appending it to the MCP message queue waiting for application thread
to dispatch.
If you return true from this method,
the current request's responsibility is on your own.
Returning true also indicates that you are writing a custom request handler.
See: TODO -> Link about custom request handling
See: https://modelcontextprotocol.io/specification/2024-11-05/basic/messages
*/
}
virtual GENERIC on_server_notification_t(McpServerStateBase* in_server, const mbase::string& in_method, const mbase::Json& in_params)
{
/*
Invoked when a notification is sent from the server.
See: https://modelcontextprotocol.io/specification/2024-11-05/basic/messages
*/
}
...
};
The workflow and concepts are same as handling requests/notifications in server. See: Handling Custom Requests/Notifications.
Sending Custom Responses/Notifications¶
The workflow is same with the server. See: Sending Custom Requests/Notifications.