Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
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
38 changes: 38 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,39 @@
# Build directories
build*/
out/
cmake-build-*/

# IDE files
.vscode/
.idea/
*.swp
*.swo
*~

# Compiled objects
*.o
*.obj
*.a
*.lib
*.so
*.dll
*.dylib

# Executables
*.exe
*.out

# CMake generated
CMakeCache.txt
CMakeFiles/
cmake_install.cmake
Makefile
compile_commands.json

# Package managers
vcpkg_installed/
_deps/

# OS files
.DS_Store
Thumbs.db
49 changes: 49 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,12 @@ option(FASTMCPP_ENABLE_LOCAL_WS_TEST "Enable local WebSocket server test (depend
add_library(fastmcpp_core
src/types.cpp
src/util/schema_build.cpp
src/app.cpp
src/proxy.cpp
src/mcp/handler.cpp
src/resources/resource.cpp
src/resources/manager.cpp
src/resources/template.cpp
src/prompts/prompt.cpp
src/prompts/manager.cpp
src/tools/tool.cpp
Expand Down Expand Up @@ -142,6 +145,18 @@ if(FASTMCPP_BUILD_TESTS)
target_link_libraries(fastmcpp_tools PRIVATE fastmcpp_core)
add_test(NAME fastmcpp_tools COMMAND fastmcpp_tools)

add_executable(fastmcpp_tools_transform tests/tools/test_tool_transform.cpp)
target_link_libraries(fastmcpp_tools_transform PRIVATE fastmcpp_core)
add_test(NAME fastmcpp_tools_transform COMMAND fastmcpp_tools_transform)

add_executable(fastmcpp_tools_transform_ext tests/tools/test_tool_transform_extended.cpp)
target_link_libraries(fastmcpp_tools_transform_ext PRIVATE fastmcpp_core)
add_test(NAME fastmcpp_tools_transform_ext COMMAND fastmcpp_tools_transform_ext)

add_executable(fastmcpp_tools_manager tests/tools/test_tool_manager.cpp)
target_link_libraries(fastmcpp_tools_manager PRIVATE fastmcpp_core)
add_test(NAME fastmcpp_tools_manager COMMAND fastmcpp_tools_manager)

add_executable(fastmcpp_integration tests/integration.cpp)
target_link_libraries(fastmcpp_integration PRIVATE fastmcpp_core)
add_test(NAME fastmcpp_integration COMMAND fastmcpp_integration)
Expand Down Expand Up @@ -222,6 +237,10 @@ if(FASTMCPP_BUILD_TESTS)
target_link_libraries(fastmcpp_resources_advanced PRIVATE fastmcpp_core)
add_test(NAME fastmcpp_resources_advanced COMMAND fastmcpp_resources_advanced)

add_executable(fastmcpp_resources_templates tests/resources/templates.cpp)
target_link_libraries(fastmcpp_resources_templates PRIVATE fastmcpp_core)
add_test(NAME fastmcpp_resources_templates COMMAND fastmcpp_resources_templates)

add_executable(fastmcpp_server_basic tests/server/basic.cpp)
target_link_libraries(fastmcpp_server_basic PRIVATE fastmcpp_core)
add_test(NAME fastmcpp_server_basic COMMAND fastmcpp_server_basic)
Expand Down Expand Up @@ -250,6 +269,21 @@ if(FASTMCPP_BUILD_TESTS)
add_executable(fastmcpp_server_context_meta tests/server/context_meta.cpp)
target_link_libraries(fastmcpp_server_context_meta PRIVATE fastmcpp_core)
add_test(NAME fastmcpp_server_context_meta COMMAND fastmcpp_server_context_meta)
add_executable(fastmcpp_server_context_full tests/server/test_context_full.cpp)
target_link_libraries(fastmcpp_server_context_full PRIVATE fastmcpp_core)
add_test(NAME fastmcpp_server_context_full COMMAND fastmcpp_server_context_full)

add_executable(fastmcpp_server_context_sse_integration tests/server/test_context_sse_integration.cpp)
target_link_libraries(fastmcpp_server_context_sse_integration PRIVATE fastmcpp_core)
add_test(NAME fastmcpp_server_context_sse_integration COMMAND fastmcpp_server_context_sse_integration)

add_executable(fastmcpp_server_context_sampling tests/server/test_context_sampling.cpp)
target_link_libraries(fastmcpp_server_context_sampling PRIVATE fastmcpp_core)
add_test(NAME fastmcpp_server_context_sampling COMMAND fastmcpp_server_context_sampling)

add_executable(fastmcpp_server_session tests/server/test_server_session.cpp)
target_link_libraries(fastmcpp_server_session PRIVATE fastmcpp_core)
add_test(NAME fastmcpp_server_session COMMAND fastmcpp_server_session)

add_executable(fastmcpp_server_security_limits tests/server/security_limits.cpp)
target_link_libraries(fastmcpp_server_security_limits PRIVATE fastmcpp_core)
Expand Down Expand Up @@ -301,13 +335,28 @@ if(FASTMCPP_BUILD_TESTS)
target_link_libraries(fastmcpp_server_middleware PRIVATE fastmcpp_core)
add_test(NAME fastmcpp_server_middleware COMMAND fastmcpp_server_middleware)

add_executable(fastmcpp_server_middleware_pipeline tests/server/test_middleware_pipeline.cpp)
target_link_libraries(fastmcpp_server_middleware_pipeline PRIVATE fastmcpp_core)
add_test(NAME fastmcpp_server_middleware_pipeline COMMAND fastmcpp_server_middleware_pipeline)

add_executable(fastmcpp_stdio_client tests/transports/stdio_client.cpp)
target_link_libraries(fastmcpp_stdio_client PRIVATE fastmcpp_core)
add_test(NAME fastmcpp_stdio_client COMMAND fastmcpp_stdio_client)

add_executable(fastmcpp_stdio_failure tests/transports/stdio_failure.cpp)
target_link_libraries(fastmcpp_stdio_failure PRIVATE fastmcpp_core)
add_test(NAME fastmcpp_stdio_failure COMMAND fastmcpp_stdio_failure)

# App mounting tests
add_executable(fastmcpp_app_mounting tests/app/mounting.cpp)
target_link_libraries(fastmcpp_app_mounting PRIVATE fastmcpp_core)
add_test(NAME fastmcpp_app_mounting COMMAND fastmcpp_app_mounting)

# Proxy tests
add_executable(fastmcpp_proxy_basic tests/proxy/basic.cpp)
target_link_libraries(fastmcpp_proxy_basic PRIVATE fastmcpp_core)
add_test(NAME fastmcpp_proxy_basic COMMAND fastmcpp_proxy_basic)

set_tests_properties(fastmcpp_stdio_client PROPERTIES
LABELS "conformance"
WORKING_DIRECTORY "$<TARGET_FILE_DIR:fastmcpp_stdio_client>"
Expand Down
192 changes: 192 additions & 0 deletions include/fastmcpp/app.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
#pragma once

#include "fastmcpp/client/types.hpp"
#include "fastmcpp/prompts/manager.hpp"
#include "fastmcpp/proxy.hpp"
#include "fastmcpp/resources/manager.hpp"
#include "fastmcpp/server/server.hpp"
#include "fastmcpp/tools/manager.hpp"

#include <memory>
#include <optional>
#include <string>
#include <vector>

namespace fastmcpp
{

/// Mounted app reference with prefix (direct mode)
struct MountedApp
{
std::string prefix; // Prefix for tools/prompts (e.g., "weather")
class McpApp* app; // Non-owning pointer to mounted app
};

/// Proxy-mounted app with prefix (proxy mode)
struct ProxyMountedApp
{
std::string prefix; // Prefix for tools/prompts
std::unique_ptr<ProxyApp> proxy; // Owning pointer to proxy wrapper
};

/// MCP Application - bundles server metadata with managers
///
/// Similar to Python's FastMCP class. Provides:
/// - Server metadata (name, version, icons, etc.)
/// - Tool, Resource, and Prompt managers
/// - App mounting support with prefixes
///
/// Usage:
/// ```cpp
/// McpApp main_app("MainApp", "1.0");
/// McpApp weather_app("WeatherApp", "1.0");
///
/// // Register tools on sub-app
/// weather_app.tools().register_tool(get_forecast_tool);
///
/// // Mount sub-app with prefix
/// main_app.mount(weather_app, "weather");
///
/// // Tools accessible as "weather_get_forecast"
/// ```
class McpApp
{
public:
/// Construct app with metadata
explicit McpApp(std::string name = "fastmcpp_app", std::string version = "1.0.0",
std::optional<std::string> website_url = std::nullopt,
std::optional<std::vector<Icon>> icons = std::nullopt);

// Metadata accessors
const std::string& name() const
{
return server_.name();
}
const std::string& version() const
{
return server_.version();
}
const std::optional<std::string>& website_url() const
{
return server_.website_url();
}
const std::optional<std::vector<Icon>>& icons() const
{
return server_.icons();
}

// Manager accessors
tools::ToolManager& tools()
{
return tools_;
}
const tools::ToolManager& tools() const
{
return tools_;
}

resources::ResourceManager& resources()
{
return resources_;
}
const resources::ResourceManager& resources() const
{
return resources_;
}

prompts::PromptManager& prompts()
{
return prompts_;
}
const prompts::PromptManager& prompts() const
{
return prompts_;
}

server::Server& server()
{
return server_;
}
const server::Server& server() const
{
return server_;
}

// =========================================================================
// App Mounting
// =========================================================================

/// Mount another app with an optional prefix
///
/// Tools are prefixed with underscore: "prefix_toolname"
/// Resources are prefixed in URI: "prefix+resource://..." or "resource://prefix/..."
/// Prompts are prefixed with underscore: "prefix_promptname"
///
/// @param app The app to mount (must outlive this app in direct mode)
/// @param prefix Optional prefix (empty string = no prefix)
/// @param as_proxy If true, mount in proxy mode (uses MCP handler for communication)
void mount(McpApp& app, const std::string& prefix = "", bool as_proxy = false);

/// Get list of directly mounted apps
const std::vector<MountedApp>& mounted() const
{
return mounted_;
}

/// Get list of proxy-mounted apps
const std::vector<ProxyMountedApp>& proxy_mounted() const
{
return proxy_mounted_;
}

// =========================================================================
// Aggregated Lists (includes mounted apps)
// =========================================================================

/// List all tools including from mounted apps
/// Tools from mounted apps have prefix: "prefix_toolname"
std::vector<std::pair<std::string, const tools::Tool*>> list_all_tools() const;

/// List all tools as ToolInfo (works for both direct and proxy mounts)
std::vector<client::ToolInfo> list_all_tools_info() const;

/// List all resources including from mounted apps
std::vector<resources::Resource> list_all_resources() const;

/// List all resource templates including from mounted apps
std::vector<resources::ResourceTemplate> list_all_templates() const;

/// List all prompts including from mounted apps
std::vector<std::pair<std::string, const prompts::Prompt*>> list_all_prompts() const;

// =========================================================================
// Routing (dispatches to correct app based on prefix)
// =========================================================================

/// Invoke a tool by name (handles prefixed routing)
Json invoke_tool(const std::string& name, const Json& args) const;

/// Read a resource by URI (handles prefixed routing)
resources::ResourceContent read_resource(const std::string& uri,
const Json& params = Json::object()) const;

/// Get prompt messages by name (handles prefixed routing)
std::vector<prompts::PromptMessage> get_prompt(const std::string& name, const Json& args) const;

private:
server::Server server_;
tools::ToolManager tools_;
resources::ResourceManager resources_;
prompts::PromptManager prompts_;
std::vector<MountedApp> mounted_;
std::vector<ProxyMountedApp> proxy_mounted_;

// Prefix utilities
static std::string add_prefix(const std::string& name, const std::string& prefix);
static std::pair<std::string, std::string> strip_prefix(const std::string& name);
static std::string add_resource_prefix(const std::string& uri, const std::string& prefix);
static std::string strip_resource_prefix(const std::string& uri, const std::string& prefix);
static bool has_resource_prefix(const std::string& uri, const std::string& prefix);
};

} // namespace fastmcpp
31 changes: 31 additions & 0 deletions include/fastmcpp/client/client.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,37 @@ class LoopbackTransport : public ITransport
std::shared_ptr<fastmcpp::server::Server> server_;
};

/// In-process transport that uses an MCP handler function
/// This is useful for proxy mode mounting where we want to communicate
/// with a mounted app via its MCP handler
class InProcessMcpTransport : public ITransport
{
public:
using HandlerFn = std::function<fastmcpp::Json(const fastmcpp::Json&)>;

explicit InProcessMcpTransport(HandlerFn handler) : handler_(std::move(handler)) {}

fastmcpp::Json request(const std::string& route, const fastmcpp::Json& payload) override
{
// Build JSON-RPC request
static int request_id = 0;
fastmcpp::Json jsonrpc_request = {
{"jsonrpc", "2.0"}, {"id", ++request_id}, {"method", route}, {"params", payload}};

// Call handler
fastmcpp::Json response = handler_(jsonrpc_request);

// Extract result or error
if (response.contains("error"))
throw fastmcpp::Error(response["error"].value("message", "Unknown error"));

return response.value("result", fastmcpp::Json::object());
}

private:
HandlerFn handler_;
};

// ============================================================================
// Call Options
// ============================================================================
Expand Down
Loading
Loading