Skip to content
Merged
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
6 changes: 6 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ add_library(fastmcpp_core
src/server/http_server.cpp
src/server/stdio_server.cpp
src/server/sse_server.cpp
src/server/streamable_http_server.cpp
src/client/client.cpp
src/client/transports.cpp
src/util/json_schema.cpp
Expand Down Expand Up @@ -303,6 +304,11 @@ if(FASTMCPP_BUILD_TESTS)
target_link_libraries(fastmcpp_server_sse_http_integration PRIVATE fastmcpp_core)
add_test(NAME fastmcpp_server_sse_http_integration COMMAND fastmcpp_server_sse_http_integration)

# Streamable HTTP integration (MCP spec 2025-03-26)
add_executable(fastmcpp_server_streamable_http_integration tests/server/streamable_http_integration.cpp)
target_link_libraries(fastmcpp_server_streamable_http_integration PRIVATE fastmcpp_core)
add_test(NAME fastmcpp_server_streamable_http_integration COMMAND fastmcpp_server_streamable_http_integration)

add_executable(fastmcpp_server_auth_cors_security tests/server/auth_cors_security.cpp)
target_link_libraries(fastmcpp_server_auth_cors_security PRIVATE fastmcpp_core)
add_test(NAME fastmcpp_server_auth_cors_security COMMAND fastmcpp_server_auth_cors_security)
Expand Down
73 changes: 64 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,22 @@ fastmcpp is a C++ port of the Python [fastmcp](https://github.com/jlowin/fastmcp

**Status:** Beta – core MCP features track the Python `fastmcp` reference.

**Current version:** 2.13.0
**Current version:** 2.14.0

## Features

- Core MCP protocol implementation (JSON‑RPC).
- Multiple transports: STDIO, HTTP (SSE), WebSocket.
- Multiple transports: STDIO, HTTP (SSE), Streamable HTTP, WebSocket.
- Streamable HTTP transport (MCP spec 2025-03-26) with session management.
- Tool management and invocation.
- Resources and prompts support.
- Resource templates with URI pattern matching.
- JSON Schema validation.
- Middleware for request/response processing.
- McpApp high-level application class.
- ProxyApp for backend server proxying.
- ServerSession for bidirectional communication, sampling, and server-initiated notifications.
- Built-in middleware: Logging, Timing, Caching, RateLimiting, ErrorHandling.
- Tool transforms for input/output processing.
- Integration with MCP‑compatible CLI tools.
- Cross‑platform: Windows, Linux, macOS.

Expand Down Expand Up @@ -105,12 +111,6 @@ ctest --test-dir build -C Release -R fastmcp_smoke --output-on-failure
ctest --test-dir build -C Release -N
```

Current status (CI / WSL configuration):

- 24/24 tests passing (100% success rate).
- 3 streaming tests disabled due to infrastructure dependencies.
- C++ test line count is much smaller than the Python `fastmcp` suite (see CCSDK parity docs).

## Basic Usage

### STDIO MCP server
Expand Down Expand Up @@ -177,6 +177,61 @@ int main() {
}
```

### Streamable HTTP server (MCP spec 2025-03-26)

```cpp
#include <fastmcpp/tools/manager.hpp>
#include <fastmcpp/mcp/handler.hpp>
#include <fastmcpp/server/streamable_http_server.hpp>

int main() {
fastmcpp::tools::ToolManager tm;
// register tools on tm...

auto handler = fastmcpp::mcp::make_mcp_handler(
"myserver", "1.0.0", tm
);

// Streamable HTTP server on /mcp endpoint
fastmcpp::server::StreamableHttpServerWrapper server(
handler, "127.0.0.1", 8080, "/mcp"
);
server.start(); // non-blocking

std::this_thread::sleep_for(std::chrono::hours(1));
server.stop();
return 0;
}
```

### Streamable HTTP client

```cpp
#include <fastmcpp/client/transports.hpp>

int main() {
fastmcpp::client::StreamableHttpTransport transport(
"http://localhost:8080", "/mcp"
);

// Send initialize request
auto init_response = transport.request("mcp", {
{"jsonrpc", "2.0"},
{"id", 1},
{"method", "initialize"},
{"params", {
{"protocolVersion", "2024-11-05"},
{"capabilities", {}},
{"clientInfo", {{"name", "client"}, {"version", "1.0"}}}
}}
});

// Session ID is automatically managed via Mcp-Session-Id header
std::cout << "Session: " << transport.session_id() << std::endl;
return 0;
}
```

## Examples

See the `examples/` directory for complete programs, including:
Expand Down
53 changes: 53 additions & 0 deletions include/fastmcpp/client/transports.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -137,4 +137,57 @@ class SseClientTransport : public ITransport
std::unordered_map<int64_t, std::promise<fastmcpp::Json>> pending_requests_;
};

/// Streamable HTTP client transport for connecting to MCP servers using the
/// Streamable HTTP protocol (MCP spec version 2025-03-26).
///
/// This transport is simpler than SSE:
/// 1. Client sends POST requests to a single endpoint (default: /mcp)
/// 2. Server responds with JSON or SSE stream
/// 3. Session ID management via Mcp-Session-Id header
///
/// Reference: https://spec.modelcontextprotocol.io/specification/2025-03-26/basic/transports/
class StreamableHttpTransport : public ITransport
{
public:
/// Construct a Streamable HTTP client transport
/// @param base_url The base URL of the MCP server (e.g., "http://127.0.0.1:8080")
/// @param mcp_path Path for the MCP endpoint (default: "/mcp")
/// @param headers Additional headers to include in requests
explicit StreamableHttpTransport(std::string base_url, std::string mcp_path = "/mcp",
std::unordered_map<std::string, std::string> headers = {});

~StreamableHttpTransport();

/// Send a JSON-RPC request and wait for response
fastmcpp::Json request(const std::string& route, const fastmcpp::Json& payload) override;

/// Get the session ID (set after successful initialize)
std::string session_id() const;

/// Check if a session ID has been set
bool has_session() const;

/// Set callback for handling server-initiated notifications during streaming responses
void set_notification_callback(std::function<void(const fastmcpp::Json&)> callback);

private:
void parse_session_id_from_response(const std::string& headers);
fastmcpp::Json parse_response(const std::string& body, const std::string& content_type);
void process_sse_line(const std::string& line, std::vector<fastmcpp::Json>& messages);

std::string base_url_;
std::string mcp_path_;
std::unordered_map<std::string, std::string> headers_;

// Session management
mutable std::mutex session_mutex_;
std::string session_id_;

// Notification handling
std::function<void(const fastmcpp::Json&)> notification_callback_;

// Request ID generation
std::atomic<int64_t> next_id_{1};
};

} // namespace fastmcpp::client
35 changes: 35 additions & 0 deletions include/fastmcpp/server/session.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,41 @@ class ServerSession
return true;
}

/**
* Send a notification to the client (fire-and-forget, no response expected).
*
* @param method The JSON-RPC method name (e.g., "notifications/progress")
* @param params Notification parameters
*/
void send_notification(const std::string& method, const Json& params = Json::object())
{
Json notification = {{"jsonrpc", "2.0"}, {"method", method}, {"params", params}};

if (send_callback_)
send_callback_(notification);
}

/**
* Send a progress notification to the client.
*
* @param progress_token Token identifying the operation (from request _meta.progressToken)
* @param progress Current progress value
* @param total Total progress value (optional)
* @param message Progress message (optional)
*/
void send_progress(const std::string& progress_token, double progress, double total = 0.0,
const std::string& message = "")
{
Json params = {{"progressToken", progress_token}, {"progress", progress}};

if (total > 0.0)
params["total"] = total;
if (!message.empty())
params["message"] = message;

send_notification("notifications/progress", params);
}

/**
* Check if a JSON message is a response (has id, no method).
*/
Expand Down
164 changes: 164 additions & 0 deletions include/fastmcpp/server/streamable_http_server.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
#pragma once
#include "fastmcpp/server/session.hpp"
#include "fastmcpp/types.hpp"

#include <atomic>
#include <condition_variable>
#include <deque>
#include <functional>
#include <httplib.h>
#include <memory>
#include <mutex>
#include <string>
#include <thread>
#include <unordered_map>

namespace fastmcpp::server
{

/**
* Streamable HTTP MCP server wrapper.
*
* This transport implements the Streamable HTTP protocol for MCP communication
* per MCP spec version 2025-03-26:
* - Single POST endpoint (default: /mcp)
* - Session ID management via Mcp-Session-Id header
* - Responses can be JSON or SSE stream
*
* This is a simpler transport than SSE with a single endpoint.
* Clients send JSON-RPC requests via POST and receive responses in the
* same HTTP response (either as JSON or SSE stream for long-running operations).
*
* Usage:
* auto handler = fastmcpp::mcp::make_mcp_handler("myserver", "1.0.0", tools);
* StreamableHttpServerWrapper server(handler);
* server.start(); // Non-blocking - runs in background thread
* // ... server runs ...
* server.stop(); // Graceful shutdown
*
* Reference: https://spec.modelcontextprotocol.io/specification/2025-03-26/basic/transports/
*/
class StreamableHttpServerWrapper
{
public:
using McpHandler = std::function<fastmcpp::Json(const fastmcpp::Json&)>;

/**
* Construct a Streamable HTTP server with an MCP handler.
*
* @param handler Function that processes JSON-RPC requests and returns responses
* @param host Host address to bind to (default: "127.0.0.1")
* @param port Port to listen on (default: 18080)
* @param mcp_path Path for the MCP POST endpoint (default: "/mcp")
* @param auth_token Optional auth token for Bearer authentication (empty = no auth required)
* @param cors_origin Optional CORS origin to allow (empty = no CORS header, use "*" for
* wildcard)
*/
explicit StreamableHttpServerWrapper(McpHandler handler, std::string host = "127.0.0.1",
int port = 18080, std::string mcp_path = "/mcp",
std::string auth_token = "", std::string cors_origin = "");

~StreamableHttpServerWrapper();

/**
* Start the server in background (non-blocking).
*
* Launches a background thread that runs the HTTP server.
* Use stop() to terminate.
*
* @return true if server started successfully
*/
bool start();

/**
* Stop the server.
*
* Signals the server to stop and joins the background thread.
* Safe to call multiple times.
*/
void stop();

/**
* Check if server is currently running.
*/
bool running() const
{
return running_.load();
}

/**
* Get the port the server is listening on.
*/
int port() const
{
return port_;
}

/**
* Get the host address the server is bound to.
*/
const std::string& host() const
{
return host_;
}

/**
* Get the MCP endpoint path.
*/
const std::string& mcp_path() const
{
return mcp_path_;
}

/**
* Get the ServerSession for a given session ID.
*
* This allows server-initiated requests (sampling, elicitation) via
* the session's bidirectional transport.
*
* @param session_id The session to get
* @return Shared pointer to ServerSession, or nullptr if not found
*/
std::shared_ptr<ServerSession> get_session(const std::string& session_id) const
{
std::lock_guard<std::mutex> lock(sessions_mutex_);
auto it = sessions_.find(session_id);
if (it == sessions_.end())
return nullptr;
return it->second;
}

/**
* Get the number of active sessions.
*/
size_t session_count() const
{
std::lock_guard<std::mutex> lock(sessions_mutex_);
return sessions_.size();
}

private:
void run_server();
std::string generate_session_id();
bool check_auth(const std::string& auth_header) const;

McpHandler handler_;
std::string host_;
int port_;
std::string mcp_path_;
std::string auth_token_; // Optional Bearer token for authentication
std::string cors_origin_; // Optional CORS origin (empty = no CORS)

std::unique_ptr<httplib::Server> svr_;
std::thread thread_;
std::atomic<bool> running_{false};

// Security limits
static constexpr size_t MAX_SESSIONS = 1000;

// Active sessions mapped by session ID
std::unordered_map<std::string, std::shared_ptr<ServerSession>> sessions_;
mutable std::mutex sessions_mutex_;
};

} // namespace fastmcpp::server
Loading
Loading