Skip to content

Commit c6b06bf

Browse files
authored
Add McpApp, ProxyApp, middleware, context features, and sampling support (#16)
Middleware pipeline with built-in implementations (Logging, Timing, Caching, RateLimiting, ErrorHandling)
1 parent 6fbc48c commit c6b06bf

29 files changed

Lines changed: 8573 additions & 59 deletions

.gitignore

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,39 @@
1+
# Build directories
12
build*/
3+
out/
4+
cmake-build-*/
5+
6+
# IDE files
7+
.vscode/
8+
.idea/
9+
*.swp
10+
*.swo
11+
*~
12+
13+
# Compiled objects
14+
*.o
15+
*.obj
16+
*.a
17+
*.lib
18+
*.so
19+
*.dll
20+
*.dylib
21+
22+
# Executables
23+
*.exe
24+
*.out
25+
26+
# CMake generated
27+
CMakeCache.txt
28+
CMakeFiles/
29+
cmake_install.cmake
30+
Makefile
31+
compile_commands.json
32+
33+
# Package managers
34+
vcpkg_installed/
35+
_deps/
36+
37+
# OS files
38+
.DS_Store
39+
Thumbs.db

CMakeLists.txt

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,12 @@ option(FASTMCPP_ENABLE_LOCAL_WS_TEST "Enable local WebSocket server test (depend
1515
add_library(fastmcpp_core
1616
src/types.cpp
1717
src/util/schema_build.cpp
18+
src/app.cpp
19+
src/proxy.cpp
1820
src/mcp/handler.cpp
1921
src/resources/resource.cpp
2022
src/resources/manager.cpp
23+
src/resources/template.cpp
2124
src/prompts/prompt.cpp
2225
src/prompts/manager.cpp
2326
src/tools/tool.cpp
@@ -142,6 +145,18 @@ if(FASTMCPP_BUILD_TESTS)
142145
target_link_libraries(fastmcpp_tools PRIVATE fastmcpp_core)
143146
add_test(NAME fastmcpp_tools COMMAND fastmcpp_tools)
144147

148+
add_executable(fastmcpp_tools_transform tests/tools/test_tool_transform.cpp)
149+
target_link_libraries(fastmcpp_tools_transform PRIVATE fastmcpp_core)
150+
add_test(NAME fastmcpp_tools_transform COMMAND fastmcpp_tools_transform)
151+
152+
add_executable(fastmcpp_tools_transform_ext tests/tools/test_tool_transform_extended.cpp)
153+
target_link_libraries(fastmcpp_tools_transform_ext PRIVATE fastmcpp_core)
154+
add_test(NAME fastmcpp_tools_transform_ext COMMAND fastmcpp_tools_transform_ext)
155+
156+
add_executable(fastmcpp_tools_manager tests/tools/test_tool_manager.cpp)
157+
target_link_libraries(fastmcpp_tools_manager PRIVATE fastmcpp_core)
158+
add_test(NAME fastmcpp_tools_manager COMMAND fastmcpp_tools_manager)
159+
145160
add_executable(fastmcpp_integration tests/integration.cpp)
146161
target_link_libraries(fastmcpp_integration PRIVATE fastmcpp_core)
147162
add_test(NAME fastmcpp_integration COMMAND fastmcpp_integration)
@@ -222,6 +237,10 @@ if(FASTMCPP_BUILD_TESTS)
222237
target_link_libraries(fastmcpp_resources_advanced PRIVATE fastmcpp_core)
223238
add_test(NAME fastmcpp_resources_advanced COMMAND fastmcpp_resources_advanced)
224239

240+
add_executable(fastmcpp_resources_templates tests/resources/templates.cpp)
241+
target_link_libraries(fastmcpp_resources_templates PRIVATE fastmcpp_core)
242+
add_test(NAME fastmcpp_resources_templates COMMAND fastmcpp_resources_templates)
243+
225244
add_executable(fastmcpp_server_basic tests/server/basic.cpp)
226245
target_link_libraries(fastmcpp_server_basic PRIVATE fastmcpp_core)
227246
add_test(NAME fastmcpp_server_basic COMMAND fastmcpp_server_basic)
@@ -250,6 +269,21 @@ if(FASTMCPP_BUILD_TESTS)
250269
add_executable(fastmcpp_server_context_meta tests/server/context_meta.cpp)
251270
target_link_libraries(fastmcpp_server_context_meta PRIVATE fastmcpp_core)
252271
add_test(NAME fastmcpp_server_context_meta COMMAND fastmcpp_server_context_meta)
272+
add_executable(fastmcpp_server_context_full tests/server/test_context_full.cpp)
273+
target_link_libraries(fastmcpp_server_context_full PRIVATE fastmcpp_core)
274+
add_test(NAME fastmcpp_server_context_full COMMAND fastmcpp_server_context_full)
275+
276+
add_executable(fastmcpp_server_context_sse_integration tests/server/test_context_sse_integration.cpp)
277+
target_link_libraries(fastmcpp_server_context_sse_integration PRIVATE fastmcpp_core)
278+
add_test(NAME fastmcpp_server_context_sse_integration COMMAND fastmcpp_server_context_sse_integration)
279+
280+
add_executable(fastmcpp_server_context_sampling tests/server/test_context_sampling.cpp)
281+
target_link_libraries(fastmcpp_server_context_sampling PRIVATE fastmcpp_core)
282+
add_test(NAME fastmcpp_server_context_sampling COMMAND fastmcpp_server_context_sampling)
283+
284+
add_executable(fastmcpp_server_session tests/server/test_server_session.cpp)
285+
target_link_libraries(fastmcpp_server_session PRIVATE fastmcpp_core)
286+
add_test(NAME fastmcpp_server_session COMMAND fastmcpp_server_session)
253287

254288
add_executable(fastmcpp_server_security_limits tests/server/security_limits.cpp)
255289
target_link_libraries(fastmcpp_server_security_limits PRIVATE fastmcpp_core)
@@ -301,13 +335,28 @@ if(FASTMCPP_BUILD_TESTS)
301335
target_link_libraries(fastmcpp_server_middleware PRIVATE fastmcpp_core)
302336
add_test(NAME fastmcpp_server_middleware COMMAND fastmcpp_server_middleware)
303337

338+
add_executable(fastmcpp_server_middleware_pipeline tests/server/test_middleware_pipeline.cpp)
339+
target_link_libraries(fastmcpp_server_middleware_pipeline PRIVATE fastmcpp_core)
340+
add_test(NAME fastmcpp_server_middleware_pipeline COMMAND fastmcpp_server_middleware_pipeline)
341+
304342
add_executable(fastmcpp_stdio_client tests/transports/stdio_client.cpp)
305343
target_link_libraries(fastmcpp_stdio_client PRIVATE fastmcpp_core)
306344
add_test(NAME fastmcpp_stdio_client COMMAND fastmcpp_stdio_client)
307345

308346
add_executable(fastmcpp_stdio_failure tests/transports/stdio_failure.cpp)
309347
target_link_libraries(fastmcpp_stdio_failure PRIVATE fastmcpp_core)
310348
add_test(NAME fastmcpp_stdio_failure COMMAND fastmcpp_stdio_failure)
349+
350+
# App mounting tests
351+
add_executable(fastmcpp_app_mounting tests/app/mounting.cpp)
352+
target_link_libraries(fastmcpp_app_mounting PRIVATE fastmcpp_core)
353+
add_test(NAME fastmcpp_app_mounting COMMAND fastmcpp_app_mounting)
354+
355+
# Proxy tests
356+
add_executable(fastmcpp_proxy_basic tests/proxy/basic.cpp)
357+
target_link_libraries(fastmcpp_proxy_basic PRIVATE fastmcpp_core)
358+
add_test(NAME fastmcpp_proxy_basic COMMAND fastmcpp_proxy_basic)
359+
311360
set_tests_properties(fastmcpp_stdio_client PROPERTIES
312361
LABELS "conformance"
313362
WORKING_DIRECTORY "$<TARGET_FILE_DIR:fastmcpp_stdio_client>"

include/fastmcpp/app.hpp

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
#pragma once
2+
3+
#include "fastmcpp/client/types.hpp"
4+
#include "fastmcpp/prompts/manager.hpp"
5+
#include "fastmcpp/proxy.hpp"
6+
#include "fastmcpp/resources/manager.hpp"
7+
#include "fastmcpp/server/server.hpp"
8+
#include "fastmcpp/tools/manager.hpp"
9+
10+
#include <memory>
11+
#include <optional>
12+
#include <string>
13+
#include <vector>
14+
15+
namespace fastmcpp
16+
{
17+
18+
/// Mounted app reference with prefix (direct mode)
19+
struct MountedApp
20+
{
21+
std::string prefix; // Prefix for tools/prompts (e.g., "weather")
22+
class McpApp* app; // Non-owning pointer to mounted app
23+
};
24+
25+
/// Proxy-mounted app with prefix (proxy mode)
26+
struct ProxyMountedApp
27+
{
28+
std::string prefix; // Prefix for tools/prompts
29+
std::unique_ptr<ProxyApp> proxy; // Owning pointer to proxy wrapper
30+
};
31+
32+
/// MCP Application - bundles server metadata with managers
33+
///
34+
/// Similar to Python's FastMCP class. Provides:
35+
/// - Server metadata (name, version, icons, etc.)
36+
/// - Tool, Resource, and Prompt managers
37+
/// - App mounting support with prefixes
38+
///
39+
/// Usage:
40+
/// ```cpp
41+
/// McpApp main_app("MainApp", "1.0");
42+
/// McpApp weather_app("WeatherApp", "1.0");
43+
///
44+
/// // Register tools on sub-app
45+
/// weather_app.tools().register_tool(get_forecast_tool);
46+
///
47+
/// // Mount sub-app with prefix
48+
/// main_app.mount(weather_app, "weather");
49+
///
50+
/// // Tools accessible as "weather_get_forecast"
51+
/// ```
52+
class McpApp
53+
{
54+
public:
55+
/// Construct app with metadata
56+
explicit McpApp(std::string name = "fastmcpp_app", std::string version = "1.0.0",
57+
std::optional<std::string> website_url = std::nullopt,
58+
std::optional<std::vector<Icon>> icons = std::nullopt);
59+
60+
// Metadata accessors
61+
const std::string& name() const
62+
{
63+
return server_.name();
64+
}
65+
const std::string& version() const
66+
{
67+
return server_.version();
68+
}
69+
const std::optional<std::string>& website_url() const
70+
{
71+
return server_.website_url();
72+
}
73+
const std::optional<std::vector<Icon>>& icons() const
74+
{
75+
return server_.icons();
76+
}
77+
78+
// Manager accessors
79+
tools::ToolManager& tools()
80+
{
81+
return tools_;
82+
}
83+
const tools::ToolManager& tools() const
84+
{
85+
return tools_;
86+
}
87+
88+
resources::ResourceManager& resources()
89+
{
90+
return resources_;
91+
}
92+
const resources::ResourceManager& resources() const
93+
{
94+
return resources_;
95+
}
96+
97+
prompts::PromptManager& prompts()
98+
{
99+
return prompts_;
100+
}
101+
const prompts::PromptManager& prompts() const
102+
{
103+
return prompts_;
104+
}
105+
106+
server::Server& server()
107+
{
108+
return server_;
109+
}
110+
const server::Server& server() const
111+
{
112+
return server_;
113+
}
114+
115+
// =========================================================================
116+
// App Mounting
117+
// =========================================================================
118+
119+
/// Mount another app with an optional prefix
120+
///
121+
/// Tools are prefixed with underscore: "prefix_toolname"
122+
/// Resources are prefixed in URI: "prefix+resource://..." or "resource://prefix/..."
123+
/// Prompts are prefixed with underscore: "prefix_promptname"
124+
///
125+
/// @param app The app to mount (must outlive this app in direct mode)
126+
/// @param prefix Optional prefix (empty string = no prefix)
127+
/// @param as_proxy If true, mount in proxy mode (uses MCP handler for communication)
128+
void mount(McpApp& app, const std::string& prefix = "", bool as_proxy = false);
129+
130+
/// Get list of directly mounted apps
131+
const std::vector<MountedApp>& mounted() const
132+
{
133+
return mounted_;
134+
}
135+
136+
/// Get list of proxy-mounted apps
137+
const std::vector<ProxyMountedApp>& proxy_mounted() const
138+
{
139+
return proxy_mounted_;
140+
}
141+
142+
// =========================================================================
143+
// Aggregated Lists (includes mounted apps)
144+
// =========================================================================
145+
146+
/// List all tools including from mounted apps
147+
/// Tools from mounted apps have prefix: "prefix_toolname"
148+
std::vector<std::pair<std::string, const tools::Tool*>> list_all_tools() const;
149+
150+
/// List all tools as ToolInfo (works for both direct and proxy mounts)
151+
std::vector<client::ToolInfo> list_all_tools_info() const;
152+
153+
/// List all resources including from mounted apps
154+
std::vector<resources::Resource> list_all_resources() const;
155+
156+
/// List all resource templates including from mounted apps
157+
std::vector<resources::ResourceTemplate> list_all_templates() const;
158+
159+
/// List all prompts including from mounted apps
160+
std::vector<std::pair<std::string, const prompts::Prompt*>> list_all_prompts() const;
161+
162+
// =========================================================================
163+
// Routing (dispatches to correct app based on prefix)
164+
// =========================================================================
165+
166+
/// Invoke a tool by name (handles prefixed routing)
167+
Json invoke_tool(const std::string& name, const Json& args) const;
168+
169+
/// Read a resource by URI (handles prefixed routing)
170+
resources::ResourceContent read_resource(const std::string& uri,
171+
const Json& params = Json::object()) const;
172+
173+
/// Get prompt messages by name (handles prefixed routing)
174+
std::vector<prompts::PromptMessage> get_prompt(const std::string& name, const Json& args) const;
175+
176+
private:
177+
server::Server server_;
178+
tools::ToolManager tools_;
179+
resources::ResourceManager resources_;
180+
prompts::PromptManager prompts_;
181+
std::vector<MountedApp> mounted_;
182+
std::vector<ProxyMountedApp> proxy_mounted_;
183+
184+
// Prefix utilities
185+
static std::string add_prefix(const std::string& name, const std::string& prefix);
186+
static std::pair<std::string, std::string> strip_prefix(const std::string& name);
187+
static std::string add_resource_prefix(const std::string& uri, const std::string& prefix);
188+
static std::string strip_resource_prefix(const std::string& uri, const std::string& prefix);
189+
static bool has_resource_prefix(const std::string& uri, const std::string& prefix);
190+
};
191+
192+
} // namespace fastmcpp

include/fastmcpp/client/client.hpp

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,37 @@ class LoopbackTransport : public ITransport
5656
std::shared_ptr<fastmcpp::server::Server> server_;
5757
};
5858

59+
/// In-process transport that uses an MCP handler function
60+
/// This is useful for proxy mode mounting where we want to communicate
61+
/// with a mounted app via its MCP handler
62+
class InProcessMcpTransport : public ITransport
63+
{
64+
public:
65+
using HandlerFn = std::function<fastmcpp::Json(const fastmcpp::Json&)>;
66+
67+
explicit InProcessMcpTransport(HandlerFn handler) : handler_(std::move(handler)) {}
68+
69+
fastmcpp::Json request(const std::string& route, const fastmcpp::Json& payload) override
70+
{
71+
// Build JSON-RPC request
72+
static int request_id = 0;
73+
fastmcpp::Json jsonrpc_request = {
74+
{"jsonrpc", "2.0"}, {"id", ++request_id}, {"method", route}, {"params", payload}};
75+
76+
// Call handler
77+
fastmcpp::Json response = handler_(jsonrpc_request);
78+
79+
// Extract result or error
80+
if (response.contains("error"))
81+
throw fastmcpp::Error(response["error"].value("message", "Unknown error"));
82+
83+
return response.value("result", fastmcpp::Json::object());
84+
}
85+
86+
private:
87+
HandlerFn handler_;
88+
};
89+
5990
// ============================================================================
6091
// Call Options
6192
// ============================================================================

0 commit comments

Comments
 (0)