Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 28 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
- **JSON-RPC 2.0 Communication**: Request/response communication based on JSON-RPC 2.0 standard
- **Resource Abstraction**: Standard interfaces for resources such as files, APIs, etc.
- **Tool Registration**: Register and call tools with structured parameters
- **Prompt Templates**: Register and expose prompt templates to clients for AI workflows
- **Extensible Architecture**: Easy to extend with new resource types and tools
- **Multi-Transport Support**: Supports HTTP and standard input/output (stdio) communication methods

Expand Down Expand Up @@ -75,17 +76,22 @@ Implements MCP server functionality.

### HTTP Server Example (`examples/server_example.cpp`)

Example MCP server implementation with custom tools:
Example MCP server implementation over HTTP/SSE with custom tools:
- Time tool: Get the current time
- Calculator tool: Perform mathematical operations
- Echo tool: Echo input with optional transformations (to uppercase, reverse)
- Greeting tool: Returns `Hello, `+ input name + `!`, defaults to `Hello, World!`

### Stdio Server Example (`examples/stdio_server_example.cpp`)

Example MCP server implementation over standard input/output (stdio), which is the default transport method for MCP clients like Claude Desktop, Cursor, and OpenCode.
It implements the same core tools but uses `server.start_stdio()` to communicate via pipes instead of opening an HTTP port.

### HTTP Client Example (`examples/client_example.cpp`)

Example MCP client connecting to a server:
- Get server information
- List available tools
- List available tools and prompts
- Call tools with parameters
- Access resources

Expand Down Expand Up @@ -156,6 +162,26 @@ server.register_tool(hello_tool, hello_handler);
auto file_resource = std::make_shared<mcp::file_resource>("<file_path>");
server.register_resource("file://<file_path>", file_resource);

// Register prompts
mcp::prompt draft_prompt = mcp::prompt_builder("draft_article")
.with_description("Draft a new article")
.with_argument("topic", "The main topic to write about", true)
.build();

server.register_prompt(draft_prompt, [](const mcp::json& args, const std::string /* session_id */) -> mcp::json {
std::string topic = args.value("topic", "Unknown");
std::string instruction = "Please write an article about " + topic;

// Return messages array as specified in the MCP protocol
return mcp::json::array({{
{"role", "user"},
{"content", {
{"type", "text"},
{"text", instruction}
}}
}});
});

// Start the server
server.start(true); // Blocking mode
```
Expand Down
7 changes: 6 additions & 1 deletion examples/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,9 @@ target_link_libraries(${TARGET} PRIVATE mcp)
target_include_directories(${TARGET} PRIVATE ${CMAKE_SOURCE_DIR}/include)
if(OPENSSL_FOUND)
target_link_libraries(${TARGET} PRIVATE ${OPENSSL_LIBRARIES})
endif()
endif()

set(TARGET stdio_server_example)
add_executable(${TARGET} stdio_server_example.cpp)
target_link_libraries(${TARGET} PRIVATE mcp)
target_include_directories(${TARGET} PRIVATE ${CMAKE_SOURCE_DIR}/include)
26 changes: 25 additions & 1 deletion examples/server_example.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,8 @@ int main() {
// {"resources", {{"subscribe", false}, {"listChanged", true}}}
// };
mcp::json capabilities = {
{"tools", mcp::json::object()}
{"tools", mcp::json::object()},
{"prompts", mcp::json::object()}
};
server.set_capabilities(capabilities);

Expand Down Expand Up @@ -170,6 +171,29 @@ int main() {
server.register_tool(calc_tool, calculator_handler);
server.register_tool(hello_tool, hello_handler);

// Register prompt
mcp::prompt hello_prompt = mcp::prompt_builder("hello_prompt")
.with_description("A prompt to generate a greeting")
.with_argument("name", "The name to greet", true)
.build();

server.register_prompt(hello_prompt, [](const mcp::json& args, const std::string& session_id) -> mcp::json {
std::string name = "World";
if (args.contains("name")) {
name = args["name"].get<std::string>();
}

mcp::json message = {
{"role", "user"},
{"content", {
{"type", "text"},
{"text", "Please greet " + name + " in a friendly way."}
}}
};

return mcp::json::array({message});
});

// // Register resources
// auto file_resource = std::make_shared<mcp::file_resource>("./Makefile");
// server.register_resource("file://./Makefile", file_resource);
Expand Down
203 changes: 203 additions & 0 deletions examples/stdio_server_example.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
/**
* @file stdio_server_example.cpp
* @brief Server example based on MCP protocol using Standard I/O transport
*
* This example demonstrates how to create an MCP server that communicates
* over standard input (stdin) and standard output (stdout).
* Follows the 2024-11-05 basic protocol specification.
*/
#include "mcp_server.h"
#include "mcp_tool.h"
#include "mcp_resource.h"

#include <iostream>
#include <chrono>
#include <ctime>
#include <thread>
#include <filesystem>
#include <algorithm>

// Tool handler for getting current time
mcp::json get_time_handler(const mcp::json& params, const std::string& /* session_id */) {
auto now = std::chrono::system_clock::now();
auto time_t_now = std::chrono::system_clock::to_time_t(now);

std::string time_str = std::ctime(&time_t_now);
// Remove trailing newline
if (!time_str.empty() && time_str[time_str.length() - 1] == '\n') {
time_str.erase(time_str.length() - 1);
}

return {
{
{"type", "text"},
{"text", time_str}
}
};
}

// Echo tool handler
mcp::json echo_handler(const mcp::json& params, const std::string& /* session_id */) {
mcp::json result = params;

if (params.contains("text")) {
std::string text = params["text"];

if (params.contains("uppercase") && params["uppercase"].get<bool>()) {
std::transform(text.begin(), text.end(), text.begin(), ::toupper);
result["text"] = text;
}

if (params.contains("reverse") && params["reverse"].get<bool>()) {
std::reverse(text.begin(), text.end());
result["text"] = text;
}
}

return {
{
{"type", "text"},
{"text", result["text"].get<std::string>()}
}
};
}

// Calculator tool handler
mcp::json calculator_handler(const mcp::json& params, const std::string& /* session_id */) {
if (!params.contains("operation")) {
throw mcp::mcp_exception(mcp::error_code::invalid_params, "Missing 'operation' parameter");
}

std::string operation = params["operation"];
double result = 0.0;

if (operation == "add") {
if (!params.contains("a") || !params.contains("b")) {
throw mcp::mcp_exception(mcp::error_code::invalid_params, "Missing 'a' or 'b' parameter");
}
result = params["a"].get<double>() + params["b"].get<double>();
} else if (operation == "subtract") {
if (!params.contains("a") || !params.contains("b")) {
throw mcp::mcp_exception(mcp::error_code::invalid_params, "Missing 'a' or 'b' parameter");
}
result = params["a"].get<double>() - params["b"].get<double>();
} else if (operation == "multiply") {
if (!params.contains("a") || !params.contains("b")) {
throw mcp::mcp_exception(mcp::error_code::invalid_params, "Missing 'a' or 'b' parameter");
}
result = params["a"].get<double>() * params["b"].get<double>();
} else if (operation == "divide") {
if (!params.contains("a") || !params.contains("b")) {
throw mcp::mcp_exception(mcp::error_code::invalid_params, "Missing 'a' or 'b' parameter");
}
if (params["b"].get<double>() == 0.0) {
throw mcp::mcp_exception(mcp::error_code::invalid_params, "Division by zero not allowed");
}
result = params["a"].get<double>() / params["b"].get<double>();
} else {
throw mcp::mcp_exception(mcp::error_code::invalid_params, "Unknown operation: " + operation);
}

return {
{
{"type", "text"},
{"text", std::to_string(result)}
}
};
}

// Custom API endpoint handler
mcp::json hello_handler(const mcp::json& params, const std::string& /* session_id */) {
std::string name = params.contains("name") ? params["name"].get<std::string>() : "World";
return {
{
{"type", "text"},
{"text", "Hello, " + name + "!"}
}
};
}

int main() {
// Ensure file directory exists
std::filesystem::create_directories("./files");

// Create and configure server
mcp::server::configuration srv_conf;
// Note: host and port are ignored in stdio mode

mcp::server server(srv_conf);
server.set_server_info("StdioExampleServer", "1.0.0");

// Set server capabilities
mcp::json capabilities = {
{"tools", mcp::json::object()},
{"prompts", mcp::json::object()}
};
server.set_capabilities(capabilities);

// Register tools
mcp::tool time_tool = mcp::tool_builder("get_time")
.with_description("Get current time")
.build();

mcp::tool echo_tool = mcp::tool_builder("echo")
.with_description("Echo input with optional transformations")
.with_string_param("text", "Text to echo")
.with_boolean_param("uppercase", "Convert to uppercase", false)
.with_boolean_param("reverse", "Reverse the text", false)
.build();

mcp::tool calc_tool = mcp::tool_builder("calculator")
.with_description("Perform basic calculations")
.with_string_param("operation", "Operation to perform (add, subtract, multiply, divide)")
.with_number_param("a", "First operand")
.with_number_param("b", "Second operand")
.build();

mcp::tool hello_tool = mcp::tool_builder("hello")
.with_description("Say hello")
.with_string_param("name", "Name to say hello to", "World")
.build();

server.register_tool(time_tool, get_time_handler);
server.register_tool(echo_tool, echo_handler);
server.register_tool(calc_tool, calculator_handler);
server.register_tool(hello_tool, hello_handler);

// Register prompt
mcp::prompt hello_prompt = mcp::prompt_builder("hello_prompt")
.with_description("A prompt to generate a greeting")
.with_argument("name", "The name to greet", true)
.build();

server.register_prompt(hello_prompt, [](const mcp::json& args, const std::string& session_id) -> mcp::json {
std::string name = "World";
if (args.contains("name")) {
name = args["name"].get<std::string>();
}

mcp::json message = {
{"role", "user"},
{"content", {
{"type", "text"},
{"text", "Please greet " + name + " in a friendly way."}
}}
};

return mcp::json::array({message});
});

// // Register resources
// auto file_resource = std::make_shared<mcp::file_resource>("./Makefile");
// server.register_resource("file://./Makefile", file_resource);

// Start server
// CRITICAL: We use std::cerr here to output logs, because any output to std::cout
// will corrupt the JSON-RPC pipe and crash the MCP client!
std::cerr << "Starting MCP server in STDIO mode..." << std::endl;
std::cerr << "Awaiting JSON-RPC requests on stdin..." << std::endl;

server.start_stdio();

return 0;
}
Loading