From 7d684bca9c5fa4658005726334b18cbc16a83418 Mon Sep 17 00:00:00 2001 From: Elias Bachaalany Date: Thu, 29 Jan 2026 20:48:54 -0800 Subject: [PATCH 1/8] feat: add create_proxy() factory function Add create_proxy() factory function for creating proxy servers. Matches Python's fastmcp.server.create_proxy() API. - Supports std::string URL (auto-detects HTTP/HTTPS/WS/WSS) - Supports client::Client instances (creates fresh sessions) - Session strategy: Always fresh sessions per request (for safety) - Note: unique_ptr not supported (use Client instead) Refers to Python fastmcp commit 3163e61e which replaced FastMCP.as_proxy() with create_proxy(). Completes parity task from kb/sync/review_result.md Phase 1, Task 1. Fixes: Feature gap - create_proxy() factory function Changes: - include/fastmcpp/proxy.hpp: Add create_proxy() template declaration - src/proxy.cpp: Implement URL and Client specializations --- include/fastmcpp/proxy.hpp | 39 +++++++++++++++++++++++++ src/proxy.cpp | 59 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 98 insertions(+) diff --git a/include/fastmcpp/proxy.hpp b/include/fastmcpp/proxy.hpp index 9dc45fc..691b0ab 100644 --- a/include/fastmcpp/proxy.hpp +++ b/include/fastmcpp/proxy.hpp @@ -149,4 +149,43 @@ class ProxyApp static client::PromptInfo prompt_to_info(const prompts::Prompt& prompt); }; +// =============================================================================== +// Factory Functions +// =============================================================================== + +/// Create a proxy server for the given target. +/// +/// This is the recommended way to create a proxy server. For lower-level control, +/// use ProxyApp directly. +/// +/// The target can be: +/// - A client::Client instance +/// - A URL string (HTTP/SSE/WebSocket) +/// +/// Note: To proxy to another FastMCP server instance, use FastMCP::mount() instead. +/// For transports, create a Client first then pass it to create_proxy(). +/// +/// Session strategy: Always creates fresh sessions per request for safety. +/// This differs from Python's behavior which can reuse connected client sessions. +/// +/// Args: +/// target: The backend to proxy to (Client or URL) +/// name: Optional proxy server name (defaults to "proxy") +/// version: Optional version string (defaults to "1.0.0") +/// +/// Returns: +/// A ProxyApp that proxies to target +/// +/// Example: +/// ```cpp +/// // Create a proxy to a remote HTTP server +/// auto proxy = create_proxy("http://localhost:8080/mcp"); +/// +/// // Create a proxy from an existing client +/// client::Client client(std::make_unique("http://remote/mcp")); +/// auto proxy = create_proxy(std::move(client)); +/// ``` +template +ProxyApp create_proxy(TargetT&& target, std::string name = "proxy", std::string version = "1.0.0"); + } // namespace fastmcpp diff --git a/src/proxy.cpp b/src/proxy.cpp index 61817e1..7b6c8ee 100644 --- a/src/proxy.cpp +++ b/src/proxy.cpp @@ -1,6 +1,7 @@ #include "fastmcpp/proxy.hpp" #include "fastmcpp/exceptions.hpp" +#include "fastmcpp/client/transports.hpp" #include @@ -341,4 +342,62 @@ client::GetPromptResult ProxyApp::get_prompt(const std::string& name, const Json return client.get_prompt_mcp(name, args); } +// =============================================================================== +// Factory Functions Implementation +// =============================================================================== + +// Specialization for URL string (assumes HTTP/SSE/WebSocket based on URL) +template<> +ProxyApp create_proxy(std::string&& url, std::string name, std::string version) +{ + auto factory = [url = std::move(url)]() -> client::Client { + // Detect transport type from URL + if (url.find("ws://") == 0 || url.find("wss://") == 0) + { + return client::Client(std::make_unique(url)); + } + else if (url.find("http://") == 0 || url.find("https://") == 0) + { + // Default to HTTP transport for regular HTTP URLs + // For SSE, user should create HttpSseTransport explicitly + return client::Client(std::make_unique(url)); + } + else + { + throw std::invalid_argument("Unsupported URL scheme: " + url); + } + }; + + return ProxyApp(std::move(factory), std::move(name), std::move(version)); +} + +// Explicit instantiation for std::string +template ProxyApp create_proxy(std::string&& url, std::string name, + std::string version); + +// Specialization for Client instance (always creates fresh session) +template<> +ProxyApp create_proxy(client::Client&& base_client, std::string name, + std::string version) +{ + auto factory = [base_client = std::move(base_client)]() mutable -> client::Client { + // Create fresh session from existing client configuration + return base_client.new_(); + }; + + return ProxyApp(std::move(factory), std::move(name), std::move(version)); +} + +// Explicit instantiation for client::Client +template ProxyApp create_proxy(client::Client&& base_client, std::string name, + std::string version); + +// Note: unique_ptr specialization not implemented. +// Create a Client from the transport first, then pass to create_proxy(). +// Example: create_proxy(client::Client(std::move(transport))); + +// Note: FastMCP* proxy specialization not implemented +// Use FastMCP::mount() for mounting another FastMCP server instead +// This avoids circular dependencies between FastMCP and ProxyApp + } // namespace fastmcpp From 87b1242a1b1591de02404fcfe5048387c9df0b0e Mon Sep 17 00:00:00 2001 From: Elias Bachaalany Date: Thu, 29 Jan 2026 21:27:56 -0800 Subject: [PATCH 2/8] docs: add create_proxy() tests and README section - Add unit tests for create_proxy() factory function: - test_create_proxy_from_client: Create proxy from Client instance - test_create_proxy_url_detection: Verify URL scheme auto-detection - test_create_proxy_with_local_tools: Verify local+remote tool mixing - Add Proxy server section to README with usage examples - Documents URL auto-detection (http/https -> HTTP, ws/wss -> WebSocket) - Documents local override precedence Completes Phase 1 Task 2 and Phase 2 Task 3 from review_result.md --- README.md | 37 +++++++++++++ tests/proxy/basic.cpp | 126 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 163 insertions(+) diff --git a/README.md b/README.md index 6c46f00..a8a9d61 100644 --- a/README.md +++ b/README.md @@ -232,6 +232,43 @@ int main() { } ``` +### Proxy server + +Create a proxy that forwards requests to a backend MCP server while allowing local overrides: + +```cpp +#include + +int main() { + // Create a proxy to a remote backend using URL + auto proxy = fastmcpp::create_proxy("http://backend:8080/mcp"); + + // Or create from an existing client + // auto proxy = fastmcpp::create_proxy(std::move(client), "MyProxy", "1.0.0"); + + // Add local tools that override or extend remote capabilities + fastmcpp::tools::Tool local_tool{ + "local_calculator", + fastmcpp::Json{{"type", "object"}, {"properties", {{"n", {{"type", "number"}}}}}}, + fastmcpp::Json{{"type", "number"}}, + [](const fastmcpp::Json& args) { return args.at("n").get() * 2; } + }; + proxy.local_tools().register_tool(local_tool); + + // Create MCP handler and serve + auto handler = fastmcpp::mcp::make_mcp_handler(proxy); + // Use handler with your preferred server transport... + + return 0; +} +``` + +The `create_proxy()` factory function automatically detects the transport type from the URL: +- `http://` or `https://` URLs use HTTP transport +- `ws://` or `wss://` URLs use WebSocket transport + +Local tools, resources, and prompts take precedence over remote ones with the same name. + ## Examples See the `examples/` directory for complete programs, including: diff --git a/tests/proxy/basic.cpp b/tests/proxy/basic.cpp index 496efcc..34572e4 100644 --- a/tests/proxy/basic.cpp +++ b/tests/proxy/basic.cpp @@ -344,6 +344,126 @@ void test_proxy_backend_unavailable() std::cout << " PASSED" << std::endl; } +// ========================================================================= +// create_proxy() factory function tests +// ========================================================================= + +void test_create_proxy_from_client() +{ + std::cout << "test_create_proxy_from_client..." << std::endl; + + // Create a client with mock transport + auto handler = create_backend_handler(); + client::Client base_client(std::make_unique(handler)); + + // Use create_proxy() factory function + auto proxy = create_proxy(std::move(base_client), "ClientProxy", "2.0.0"); + + assert(proxy.name() == "ClientProxy"); + assert(proxy.version() == "2.0.0"); + + // Should be able to list remote tools + auto tools = proxy.list_all_tools(); + assert(tools.size() == 2); // backend_add, backend_echo + + // Should be able to invoke remote tools + auto result = proxy.invoke_tool("backend_add", Json{{"a", 10}, {"b", 20}}); + assert(!result.isError); + + std::cout << " PASSED" << std::endl; +} + +void test_create_proxy_url_detection() +{ + std::cout << "test_create_proxy_url_detection..." << std::endl; + + // Test that create_proxy() correctly detects URL schemes + // Note: These will fail to connect but should parse correctly + + // HTTP URL - should create HttpTransport + try + { + auto proxy = create_proxy(std::string("http://localhost:9999/mcp")); + assert(proxy.name() == "proxy"); // default name + assert(proxy.version() == "1.0.0"); // default version + // Getting client will fail (no server), but proxy creation succeeded + std::cout << " HTTP URL: OK" << std::endl; + } + catch (const std::exception& e) + { + // Unexpected - URL parsing should work + std::cerr << " HTTP URL failed unexpectedly: " << e.what() << std::endl; + assert(false); + } + + // WebSocket URL - should create WebSocketTransport + try + { + auto proxy = create_proxy(std::string("ws://localhost:9999/mcp"), "WsProxy"); + assert(proxy.name() == "WsProxy"); + std::cout << " WS URL: OK" << std::endl; + } + catch (const std::exception& e) + { + std::cerr << " WS URL failed unexpectedly: " << e.what() << std::endl; + assert(false); + } + + // Invalid URL scheme - should throw + try + { + auto proxy = create_proxy(std::string("ftp://localhost/path")); + // Try to use it - this should fail + proxy.list_all_tools(); + assert(false); // Should have thrown + } + catch (const std::invalid_argument& e) + { + // Expected - unsupported scheme + std::cout << " Invalid scheme: correctly rejected" << std::endl; + } + catch (const std::exception&) + { + // Also acceptable - failed during use + std::cout << " Invalid scheme: rejected on use" << std::endl; + } + + std::cout << " PASSED" << std::endl; +} + +void test_create_proxy_with_local_tools() +{ + std::cout << "test_create_proxy_with_local_tools..." << std::endl; + + // Create proxy from client + auto handler = create_backend_handler(); + client::Client base_client(std::make_unique(handler)); + auto proxy = create_proxy(std::move(base_client)); + + // Add local tools + tools::Tool local_tool{"local_calc", + Json{{"type", "object"}, + {"properties", Json{{"n", Json{{"type", "number"}}}}}, + {"required", Json::array({"n"})}}, + Json{{"type", "number"}}, + [](const Json& args) { return args.at("n").get() * 100; }}; + proxy.local_tools().register_tool(local_tool); + + // Should see both local and remote tools + auto tools = proxy.list_all_tools(); + assert(tools.size() == 3); // local_calc + backend_add + backend_echo + + // Local tool should work + auto local_result = proxy.invoke_tool("local_calc", Json{{"n", 5}}); + assert(!local_result.isError); + + // Remote tool should work + auto remote_result = proxy.invoke_tool("backend_echo", Json{{"message", "test"}}); + assert(!remote_result.isError); + + std::cout << " PASSED" << std::endl; +} + int main() { std::cout << "=== ProxyApp Tests ===" << std::endl; @@ -358,6 +478,12 @@ int main() test_proxy_mcp_handler(); test_proxy_backend_unavailable(); + std::cout << "\n=== create_proxy() Factory Tests ===" << std::endl; + + test_create_proxy_from_client(); + test_create_proxy_url_detection(); + test_create_proxy_with_local_tools(); + std::cout << "\n=== All tests PASSED ===" << std::endl; return 0; } From a543f25bbd6d19be186a9a05eb3a421d9711d730 Mon Sep 17 00:00:00 2001 From: Elias Bachaalany Date: Thu, 29 Jan 2026 21:37:47 -0800 Subject: [PATCH 3/8] feat: add unified fastmcpp.hpp header Add main header that includes all commonly used components: - Core types, exceptions, content, settings - Client and transports - Server and context - Tools, resources, prompts managers - MCP handler - Proxy with create_proxy() factory - High-level FastMCP app Add compile test to verify header works correctly. Completes Phase 2 Task 4 from review_result.md --- CMakeLists.txt | 5 ++ include/fastmcpp.hpp | 57 +++++++++++++++++++++ tests/main_header/compile_test.cpp | 79 ++++++++++++++++++++++++++++++ 3 files changed, 141 insertions(+) create mode 100644 include/fastmcpp.hpp create mode 100644 tests/main_header/compile_test.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index f8d6683..b5455c9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -474,6 +474,11 @@ if(FASTMCPP_BUILD_TESTS) target_link_libraries(fastmcpp_proxy_basic PRIVATE fastmcpp_core) add_test(NAME fastmcpp_proxy_basic COMMAND fastmcpp_proxy_basic) + # Main header compile test + add_executable(fastmcpp_main_header tests/main_header/compile_test.cpp) + target_link_libraries(fastmcpp_main_header PRIVATE fastmcpp_core) + add_test(NAME fastmcpp_main_header COMMAND fastmcpp_main_header) + set_tests_properties(fastmcpp_stdio_client PROPERTIES LABELS "conformance" WORKING_DIRECTORY "$" diff --git a/include/fastmcpp.hpp b/include/fastmcpp.hpp new file mode 100644 index 0000000..465af3d --- /dev/null +++ b/include/fastmcpp.hpp @@ -0,0 +1,57 @@ +#pragma once + +/// @file fastmcpp.hpp +/// @brief Main header for fastmcpp - includes commonly used components +/// +/// This header provides convenient access to the most commonly used fastmcpp +/// components. For more specialized functionality, include the specific headers. +/// +/// Usage: +/// @code +/// #include +/// +/// int main() { +/// // Create a proxy to a remote server +/// auto proxy = fastmcpp::create_proxy("http://localhost:8080/mcp"); +/// +/// // Or use the client directly +/// fastmcpp::client::Client client( +/// std::make_unique("http://localhost:8080") +/// ); +/// +/// // Create an MCP handler +/// auto handler = fastmcpp::mcp::make_mcp_handler(proxy); +/// } +/// @endcode + +// Core types and exceptions +#include "fastmcpp/types.hpp" +#include "fastmcpp/exceptions.hpp" +#include "fastmcpp/content.hpp" +#include "fastmcpp/settings.hpp" + +// Client +#include "fastmcpp/client/client.hpp" +#include "fastmcpp/client/transports.hpp" +#include "fastmcpp/client/types.hpp" + +// Server +#include "fastmcpp/server/server.hpp" +#include "fastmcpp/server/context.hpp" + +// Tools, Resources, Prompts +#include "fastmcpp/tools/tool.hpp" +#include "fastmcpp/tools/manager.hpp" +#include "fastmcpp/resources/resource.hpp" +#include "fastmcpp/resources/manager.hpp" +#include "fastmcpp/prompts/prompt.hpp" +#include "fastmcpp/prompts/manager.hpp" + +// MCP handler +#include "fastmcpp/mcp/handler.hpp" + +// Proxy (create_proxy factory function) +#include "fastmcpp/proxy.hpp" + +// High-level app +#include "fastmcpp/app.hpp" diff --git a/tests/main_header/compile_test.cpp b/tests/main_header/compile_test.cpp new file mode 100644 index 0000000..fad9170 --- /dev/null +++ b/tests/main_header/compile_test.cpp @@ -0,0 +1,79 @@ +/// @file tests/main_header/compile_test.cpp +/// @brief Compile test for main fastmcpp.hpp header +/// +/// This test verifies that including just gives access to +/// all commonly used functionality including create_proxy(). + +#include "fastmcpp.hpp" + +#include +#include + +using namespace fastmcpp; + +int main() +{ + std::cout << "=== Main Header Compile Test ===" << std::endl; + + // Test 1: create_proxy is accessible + std::cout << "test_create_proxy_accessible..." << std::endl; + { + // Just verify it compiles - we can't connect to a real server + // The URL detection should work without network + auto proxy = create_proxy(std::string("http://localhost:9999/mcp")); + assert(proxy.name() == "proxy"); + assert(proxy.version() == "1.0.0"); + } + std::cout << " PASSED" << std::endl; + + // Test 2: Client types are accessible + std::cout << "test_client_types_accessible..." << std::endl; + { + // Verify client namespace is available + using Transport = client::ITransport; + using ClientType = client::Client; + (void)sizeof(Transport); + (void)sizeof(ClientType); + } + std::cout << " PASSED" << std::endl; + + // Test 3: Server types are accessible + std::cout << "test_server_types_accessible..." << std::endl; + { + auto srv = std::make_shared(); + assert(srv != nullptr); + } + std::cout << " PASSED" << std::endl; + + // Test 4: Tool/Resource/Prompt managers accessible + std::cout << "test_managers_accessible..." << std::endl; + { + tools::ToolManager tm; + resources::ResourceManager rm; + prompts::PromptManager pm; + (void)tm; + (void)rm; + (void)pm; + } + std::cout << " PASSED" << std::endl; + + // Test 5: MCP handler is accessible + std::cout << "test_mcp_handler_accessible..." << std::endl; + { + tools::ToolManager tm; + auto handler = mcp::make_mcp_handler("test", "1.0", tm); + assert(handler != nullptr); + } + std::cout << " PASSED" << std::endl; + + // Test 6: App class is accessible + std::cout << "test_app_accessible..." << std::endl; + { + using AppType = FastMCP; + (void)sizeof(AppType); + } + std::cout << " PASSED" << std::endl; + + std::cout << "\n=== All tests PASSED ===" << std::endl; + return 0; +} From 32b187551fe6b648943a2cbe3567967ffa7491bd Mon Sep 17 00:00:00 2001 From: Elias Bachaalany Date: Fri, 30 Jan 2026 10:34:56 -0800 Subject: [PATCH 4/8] fix: proxy string serialization and prompts content format - proxy.cpp: Use string value directly instead of dump() for string results to avoid double JSON serialization - handler.cpp: Unwrap single-element content arrays for prompts/get to match MCP protocol expectations --- src/mcp/handler.cpp | 3 ++- src/proxy.cpp | 13 +++++++++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/mcp/handler.cpp b/src/mcp/handler.cpp index 0b7ec0d..5c0529b 100644 --- a/src/mcp/handler.cpp +++ b/src/mcp/handler.cpp @@ -2583,7 +2583,8 @@ std::function make_mcp_handler(const Prox std::string role_str = (msg.role == client::Role::Assistant) ? "assistant" : "user"; - messages_array.push_back({{"role", role_str}, {"content", content_array}}); + fastmcpp::Json content_val = (content_array.size() == 1) ? content_array[0] : content_array; + messages_array.push_back({{"role", role_str}, {"content", content_val}}); } fastmcpp::Json response_result = {{"messages", messages_array}}; diff --git a/src/proxy.cpp b/src/proxy.cpp index 7b6c8ee..402b855 100644 --- a/src/proxy.cpp +++ b/src/proxy.cpp @@ -24,7 +24,7 @@ client::ToolInfo ProxyApp::tool_to_info(const tools::Tool& tool) info.name = tool.name(); info.description = tool.description(); info.inputSchema = tool.input_schema(); - if (!tool.output_schema().is_null()) + if (!tool.output_schema().is_null() && tool.output_schema().value("type", "") == "object") info.outputSchema = tool.output_schema(); if (tool.task_support() != TaskSupport::Forbidden) info.execution = fastmcpp::Json{{"taskSupport", to_string(tool.task_support())}}; @@ -215,6 +215,7 @@ client::CallToolResult ProxyApp::invoke_tool(const std::string& name, const Json try { auto result_json = local_tools_.invoke(name, args, enforce_timeout); + const auto& tool = local_tools_.get(name); // Convert to CallToolResult client::CallToolResult result; @@ -222,9 +223,17 @@ client::CallToolResult ProxyApp::invoke_tool(const std::string& name, const Json // Wrap result as text content client::TextContent text; - text.text = result_json.dump(); + // If result is already a string, use it directly; otherwise dump as JSON + if (result_json.is_string()) + text.text = result_json.get(); + else + text.text = result_json.dump(); result.content.push_back(text); + // If tool has output schema, set structuredContent + if (!tool.output_schema().is_null() && tool.output_schema().value("type", "") == "object") + result.structuredContent = result_json; + return result; } catch (const NotFoundError&) From 15ae2f1b01e66c11b392b81c86fcf1134c3418a8 Mon Sep 17 00:00:00 2001 From: Elias Bachaalany Date: Fri, 30 Jan 2026 11:10:12 -0800 Subject: [PATCH 5/8] fix: add create_proxy() overloads for string literals and lvalues The template-only API required rvalue std::string, breaking common usage patterns like create_proxy("http://localhost:8080/mcp"). Now provides non-template overloads: - create_proxy(const std::string& url, ...) - lvalue strings - create_proxy(const char* url, ...) - string literals - create_proxy(client::Client&& client, ...) - rvalue clients All documented examples now work as expected. --- include/fastmcpp/proxy.hpp | 15 +++++++++++-- src/proxy.cpp | 44 +++++++++++++++++++------------------- 2 files changed, 35 insertions(+), 24 deletions(-) diff --git a/include/fastmcpp/proxy.hpp b/include/fastmcpp/proxy.hpp index 691b0ab..e66e01f 100644 --- a/include/fastmcpp/proxy.hpp +++ b/include/fastmcpp/proxy.hpp @@ -185,7 +185,18 @@ class ProxyApp /// client::Client client(std::make_unique("http://remote/mcp")); /// auto proxy = create_proxy(std::move(client)); /// ``` -template -ProxyApp create_proxy(TargetT&& target, std::string name = "proxy", std::string version = "1.0.0"); + +// Non-template overloads for common use cases (preferred for usability) + +/// Create proxy from URL string (lvalue or literal) +ProxyApp create_proxy(const std::string& url, std::string name = "proxy", + std::string version = "1.0.0"); + +/// Create proxy from string literal +ProxyApp create_proxy(const char* url, std::string name = "proxy", std::string version = "1.0.0"); + +/// Create proxy from existing Client (takes ownership) +ProxyApp create_proxy(client::Client&& client, std::string name = "proxy", + std::string version = "1.0.0"); } // namespace fastmcpp diff --git a/src/proxy.cpp b/src/proxy.cpp index 402b855..378f28e 100644 --- a/src/proxy.cpp +++ b/src/proxy.cpp @@ -355,11 +355,12 @@ client::GetPromptResult ProxyApp::get_prompt(const std::string& name, const Json // Factory Functions Implementation // =============================================================================== -// Specialization for URL string (assumes HTTP/SSE/WebSocket based on URL) -template<> -ProxyApp create_proxy(std::string&& url, std::string name, std::string version) +namespace { - auto factory = [url = std::move(url)]() -> client::Client { +// Helper to create client factory from URL +ProxyApp::ClientFactory make_url_factory(std::string url) +{ + return [url = std::move(url)]() -> client::Client { // Detect transport type from URL if (url.find("ws://") == 0 || url.find("wss://") == 0) { @@ -376,18 +377,23 @@ ProxyApp create_proxy(std::string&& url, std::string name, std::str throw std::invalid_argument("Unsupported URL scheme: " + url); } }; +} +} // anonymous namespace - return ProxyApp(std::move(factory), std::move(name), std::move(version)); +// Non-template overload for const std::string& (lvalue strings) +ProxyApp create_proxy(const std::string& url, std::string name, std::string version) +{ + return ProxyApp(make_url_factory(url), std::move(name), std::move(version)); } -// Explicit instantiation for std::string -template ProxyApp create_proxy(std::string&& url, std::string name, - std::string version); +// Non-template overload for const char* (string literals) +ProxyApp create_proxy(const char* url, std::string name, std::string version) +{ + return ProxyApp(make_url_factory(std::string(url)), std::move(name), std::move(version)); +} -// Specialization for Client instance (always creates fresh session) -template<> -ProxyApp create_proxy(client::Client&& base_client, std::string name, - std::string version) +// Non-template overload for Client&& (takes ownership) +ProxyApp create_proxy(client::Client&& base_client, std::string name, std::string version) { auto factory = [base_client = std::move(base_client)]() mutable -> client::Client { // Create fresh session from existing client configuration @@ -397,16 +403,10 @@ ProxyApp create_proxy(client::Client&& base_client, std::string return ProxyApp(std::move(factory), std::move(name), std::move(version)); } -// Explicit instantiation for client::Client -template ProxyApp create_proxy(client::Client&& base_client, std::string name, - std::string version); - -// Note: unique_ptr specialization not implemented. -// Create a Client from the transport first, then pass to create_proxy(). -// Example: create_proxy(client::Client(std::move(transport))); - -// Note: FastMCP* proxy specialization not implemented -// Use FastMCP::mount() for mounting another FastMCP server instead +// Note: To proxy to a unique_ptr, create a Client first: +// create_proxy(client::Client(std::move(transport))); +// +// Note: To proxy to another FastMCP server instance, use FastMCP::mount() instead. // This avoids circular dependencies between FastMCP and ProxyApp } // namespace fastmcpp From ab7ddafca3dc9ab8c5b24e7ec7e4a35253b3f9ce Mon Sep 17 00:00:00 2001 From: Elias Bachaalany Date: Fri, 30 Jan 2026 11:33:18 -0800 Subject: [PATCH 6/8] docs: update README examples to use fluent Client API Replace raw JSON-RPC construction with clean high-level methods: - client.initialize() for MCP session setup - client.list_tools() for tool discovery - client.call_tool() for invoking tools - result.text() helper for extracting text results Examples now show the recommended API usage pattern. --- README.md | 79 +++++++++++++++++++++++++++++-------------------------- 1 file changed, 42 insertions(+), 37 deletions(-) diff --git a/README.md b/README.md index a8a9d61..14aec55 100644 --- a/README.md +++ b/README.md @@ -162,17 +162,25 @@ int main() { #include int main() { - auto transport = std::make_shared( - "http://localhost:8080" + // Create client with HTTP transport + fastmcpp::client::Client client( + std::make_unique("http://localhost:8080") ); - fastmcpp::client::Client client(transport); - auto response = client.call("tool/invoke", { - {"name", "calculator"}, - {"input", {{"operation", "add"}, {"a", 5}, {"b", 3}}} - }); + // Initialize MCP session + auto init = client.initialize(); + std::cout << "Connected to: " << init.serverInfo.name << std::endl; + + // List available tools + auto tools = client.list_tools(); + for (const auto& tool : tools) { + std::cout << "Tool: " << tool.name << std::endl; + } + + // Call a tool + auto result = client.call_tool("calculator", {{"a", 5}, {"b", 3}}); + std::cout << "Result: " << result.text() << std::endl; - std::cout << response.dump() << std::endl; return 0; } ``` @@ -207,27 +215,25 @@ int main() { ### Streamable HTTP client ```cpp +#include #include int main() { - fastmcpp::client::StreamableHttpTransport transport( - "http://localhost:8080", "/mcp" + // Create client with Streamable HTTP transport (MCP spec 2025-03-26) + fastmcpp::client::Client client( + std::make_unique( + "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"}}} - }} - }); + // Initialize MCP session (session ID managed automatically) + auto init = client.initialize(); + std::cout << "Server: " << init.serverInfo.name << std::endl; + + // Use the same clean API as other transports + auto tools = client.list_tools(); + auto result = client.call_tool("echo", {{"message", "Hello!"}}); - // Session ID is automatically managed via Mcp-Session-Id header - std::cout << "Session: " << transport.session_id() << std::endl; return 0; } ``` @@ -238,27 +244,26 @@ Create a proxy that forwards requests to a backend MCP server while allowing loc ```cpp #include +#include int main() { - // Create a proxy to a remote backend using URL + // Create a proxy to a remote backend auto proxy = fastmcpp::create_proxy("http://backend:8080/mcp"); - // Or create from an existing client - // auto proxy = fastmcpp::create_proxy(std::move(client), "MyProxy", "1.0.0"); - - // Add local tools that override or extend remote capabilities - fastmcpp::tools::Tool local_tool{ - "local_calculator", - fastmcpp::Json{{"type", "object"}, {"properties", {{"n", {{"type", "number"}}}}}}, - fastmcpp::Json{{"type", "number"}}, - [](const fastmcpp::Json& args) { return args.at("n").get() * 2; } - }; - proxy.local_tools().register_tool(local_tool); + // Add local tools that extend or override remote capabilities + proxy.local_tools().register_tool({ + "double", // name + {{"type", "object"}, {"properties", {{"n", {{"type", "number"}}}}}}, + {{"type", "number"}}, // output schema + [](const fastmcpp::Json& args) { return args["n"].get() * 2; } + }); - // Create MCP handler and serve + // Create MCP handler and serve via SSE auto handler = fastmcpp::mcp::make_mcp_handler(proxy); - // Use handler with your preferred server transport... + fastmcpp::server::SseServerWrapper server(handler, "127.0.0.1", 8080); + server.start(); + // Server runs until stopped... return 0; } ``` From 4434f871533f71ea387e959e347cbd99dd9311b9 Mon Sep 17 00:00:00 2001 From: Elias Bachaalany Date: Fri, 30 Jan 2026 11:47:14 -0800 Subject: [PATCH 7/8] docs: use schema_build helper for cleaner tool registration example --- README.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 14aec55..41e7a38 100644 --- a/README.md +++ b/README.md @@ -245,16 +245,19 @@ Create a proxy that forwards requests to a backend MCP server while allowing loc ```cpp #include #include +#include int main() { + using fastmcpp::util::schema_build::to_object_schema_from_simple; + // Create a proxy to a remote backend auto proxy = fastmcpp::create_proxy("http://backend:8080/mcp"); // Add local tools that extend or override remote capabilities proxy.local_tools().register_tool({ - "double", // name - {{"type", "object"}, {"properties", {{"n", {{"type", "number"}}}}}}, - {{"type", "number"}}, // output schema + "double", + to_object_schema_from_simple({{"n", "number"}}), // input: {n: number} + {{"type", "number"}}, // output schema [](const fastmcpp::Json& args) { return args["n"].get() * 2; } }); From e41debb7a7654b8063e3dbee48ec3abf51751ecf Mon Sep 17 00:00:00 2001 From: Elias Bachaalany Date: Fri, 30 Jan 2026 11:51:54 -0800 Subject: [PATCH 8/8] style: apply clang-format --- include/fastmcpp.hpp | 16 ++++++++-------- src/mcp/handler.cpp | 3 ++- src/proxy.cpp | 8 +++++--- tests/proxy/basic.cpp | 2 +- 4 files changed, 16 insertions(+), 13 deletions(-) diff --git a/include/fastmcpp.hpp b/include/fastmcpp.hpp index 465af3d..eafde11 100644 --- a/include/fastmcpp.hpp +++ b/include/fastmcpp.hpp @@ -25,10 +25,10 @@ /// @endcode // Core types and exceptions -#include "fastmcpp/types.hpp" -#include "fastmcpp/exceptions.hpp" #include "fastmcpp/content.hpp" +#include "fastmcpp/exceptions.hpp" #include "fastmcpp/settings.hpp" +#include "fastmcpp/types.hpp" // Client #include "fastmcpp/client/client.hpp" @@ -36,16 +36,16 @@ #include "fastmcpp/client/types.hpp" // Server -#include "fastmcpp/server/server.hpp" #include "fastmcpp/server/context.hpp" +#include "fastmcpp/server/server.hpp" // Tools, Resources, Prompts -#include "fastmcpp/tools/tool.hpp" -#include "fastmcpp/tools/manager.hpp" -#include "fastmcpp/resources/resource.hpp" -#include "fastmcpp/resources/manager.hpp" -#include "fastmcpp/prompts/prompt.hpp" #include "fastmcpp/prompts/manager.hpp" +#include "fastmcpp/prompts/prompt.hpp" +#include "fastmcpp/resources/manager.hpp" +#include "fastmcpp/resources/resource.hpp" +#include "fastmcpp/tools/manager.hpp" +#include "fastmcpp/tools/tool.hpp" // MCP handler #include "fastmcpp/mcp/handler.hpp" diff --git a/src/mcp/handler.cpp b/src/mcp/handler.cpp index 5c0529b..f04d69f 100644 --- a/src/mcp/handler.cpp +++ b/src/mcp/handler.cpp @@ -2583,7 +2583,8 @@ std::function make_mcp_handler(const Prox std::string role_str = (msg.role == client::Role::Assistant) ? "assistant" : "user"; - fastmcpp::Json content_val = (content_array.size() == 1) ? content_array[0] : content_array; + fastmcpp::Json content_val = + (content_array.size() == 1) ? content_array[0] : content_array; messages_array.push_back({{"role", role_str}, {"content", content_val}}); } diff --git a/src/proxy.cpp b/src/proxy.cpp index 378f28e..147b6ec 100644 --- a/src/proxy.cpp +++ b/src/proxy.cpp @@ -1,7 +1,7 @@ #include "fastmcpp/proxy.hpp" -#include "fastmcpp/exceptions.hpp" #include "fastmcpp/client/transports.hpp" +#include "fastmcpp/exceptions.hpp" #include @@ -360,7 +360,8 @@ namespace // Helper to create client factory from URL ProxyApp::ClientFactory make_url_factory(std::string url) { - return [url = std::move(url)]() -> client::Client { + return [url = std::move(url)]() -> client::Client + { // Detect transport type from URL if (url.find("ws://") == 0 || url.find("wss://") == 0) { @@ -395,7 +396,8 @@ ProxyApp create_proxy(const char* url, std::string name, std::string version) // Non-template overload for Client&& (takes ownership) ProxyApp create_proxy(client::Client&& base_client, std::string name, std::string version) { - auto factory = [base_client = std::move(base_client)]() mutable -> client::Client { + auto factory = [base_client = std::move(base_client)]() mutable -> client::Client + { // Create fresh session from existing client configuration return base_client.new_(); }; diff --git a/tests/proxy/basic.cpp b/tests/proxy/basic.cpp index 34572e4..8eaf3b9 100644 --- a/tests/proxy/basic.cpp +++ b/tests/proxy/basic.cpp @@ -384,7 +384,7 @@ void test_create_proxy_url_detection() try { auto proxy = create_proxy(std::string("http://localhost:9999/mcp")); - assert(proxy.name() == "proxy"); // default name + assert(proxy.name() == "proxy"); // default name assert(proxy.version() == "1.0.0"); // default version // Getting client will fail (no server), but proxy creation succeeded std::cout << " HTTP URL: OK" << std::endl;