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
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ jobs:

- name: Build (Unix)
if: runner.os != 'Windows'
run: cmake --build build --config ${{ matrix.build_type }} --parallel
run: cmake --build build --config ${{ matrix.build_type }} --parallel 2

- name: Build (Windows)
if: runner.os == 'Windows'
Expand Down
34 changes: 28 additions & 6 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ option(FASTMCPP_BUILD_TESTS "Build tests" ON)
option(FASTMCPP_BUILD_EXAMPLES "Build examples" ON)
option(FASTMCPP_ENABLE_POST_STREAMING "Enable POST streaming via libcurl (optional)" OFF)
option(FASTMCPP_FETCH_CURL "Fetch and build libcurl statically for POST streaming" ON)
option(FASTMCPP_ENABLE_SAMPLING_HTTP_HANDLERS "Enable built-in OpenAI/Anthropic sampling handlers (requires libcurl)" OFF)
option(FASTMCPP_ENABLE_WS_STREAMING_TESTS "Enable WebSocket streaming tests (requires external server)" OFF)
option(FASTMCPP_ENABLE_LOCAL_WS_TEST "Enable local WebSocket server test (depends on httplib ws server support)" OFF)

Expand Down Expand Up @@ -37,6 +38,7 @@ add_library(fastmcpp_core STATIC
src/server/sse_server.cpp
src/server/streamable_http_server.cpp
src/client/client.cpp
src/client/sampling_handlers.cpp
src/client/transports.cpp
src/util/json_schema.cpp
src/util/json_schema_type.cpp
Expand Down Expand Up @@ -94,8 +96,8 @@ endif()
target_include_directories(fastmcpp_core PUBLIC ${easywsclient_SOURCE_DIR})
target_sources(fastmcpp_core PRIVATE ${easywsclient_SOURCE_DIR}/easywsclient.cpp)

# Optional: libcurl for POST streaming receive support (modular)
if(FASTMCPP_ENABLE_POST_STREAMING)
# Optional: libcurl for POST streaming and sampling handlers (modular)
if(FASTMCPP_ENABLE_POST_STREAMING OR FASTMCPP_ENABLE_SAMPLING_HTTP_HANDLERS)
if(FASTMCPP_FETCH_CURL)
message(STATUS "FASTMCPP_FETCH_CURL=ON: fetching curl via FetchContent (static-only)")
include(FetchContent)
Expand Down Expand Up @@ -151,10 +153,18 @@ if(FASTMCPP_ENABLE_POST_STREAMING)
endif()

if(TARGET CURL::libcurl)
target_compile_definitions(fastmcpp_core PRIVATE FASTMCPP_POST_STREAMING)
target_link_libraries(fastmcpp_core PRIVATE CURL::libcurl)
target_compile_definitions(fastmcpp_core PRIVATE FASTMCPP_HAS_CURL)
if(FASTMCPP_ENABLE_POST_STREAMING)
target_compile_definitions(fastmcpp_core PRIVATE FASTMCPP_POST_STREAMING)
endif()
else()
message(STATUS "libcurl not found; POST streaming will be disabled at runtime (request_stream_post throws)")
if(FASTMCPP_ENABLE_POST_STREAMING)
message(STATUS "libcurl not found; POST streaming will be disabled at runtime (request_stream_post throws)")
endif()
if(FASTMCPP_ENABLE_SAMPLING_HTTP_HANDLERS)
message(STATUS "libcurl not found; built-in sampling handlers will be unavailable")
endif()
endif()
endif()

Expand Down Expand Up @@ -222,6 +232,9 @@ if(FASTMCPP_BUILD_TESTS)

add_test(NAME fastmcpp_cli_sum COMMAND fastmcpp client sum 2 3)
add_test(NAME fastmcpp_cli_tasks_help COMMAND fastmcpp tasks --help)
add_test(NAME fastmcpp_cli_tasks_demo COMMAND fastmcpp tasks demo)
add_executable(fastmcpp_cli_tasks_ux tests/cli/tasks_cli.cpp)
add_test(NAME fastmcpp_cli_tasks_ux COMMAND fastmcpp_cli_tasks_ux)

add_executable(fastmcpp_http_integration tests/server/http_integration.cpp)
target_link_libraries(fastmcpp_http_integration PRIVATE fastmcpp_core)
Expand Down Expand Up @@ -414,8 +427,12 @@ if(FASTMCPP_BUILD_TESTS)
add_test(NAME fastmcpp_client_api_icons COMMAND fastmcpp_client_api_icons)

add_executable(fastmcpp_client_tasks tests/client/tasks.cpp)
target_link_libraries(fastmcpp_client_tasks PRIVATE fastmcpp_core)
add_test(NAME fastmcpp_client_tasks COMMAND fastmcpp_client_tasks)
target_link_libraries(fastmcpp_client_tasks PRIVATE fastmcpp_core)
add_test(NAME fastmcpp_client_tasks COMMAND fastmcpp_client_tasks)

add_executable(fastmcpp_client_sampling_handlers tests/client/sampling_handlers.cpp)
target_link_libraries(fastmcpp_client_sampling_handlers PRIVATE fastmcpp_core)
add_test(NAME fastmcpp_client_sampling_handlers COMMAND fastmcpp_client_sampling_handlers)

add_executable(fastmcpp_server_middleware tests/server/middleware.cpp)
target_link_libraries(fastmcpp_server_middleware PRIVATE fastmcpp_core)
Expand All @@ -438,6 +455,11 @@ if(FASTMCPP_BUILD_TESTS)
target_link_libraries(fastmcpp_app_mounting PRIVATE fastmcpp_core)
add_test(NAME fastmcpp_app_mounting COMMAND fastmcpp_app_mounting)

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

# Proxy tests
add_executable(fastmcpp_proxy_basic tests/proxy/basic.cpp)
target_link_libraries(fastmcpp_proxy_basic PRIVATE fastmcpp_core)
Expand Down
75 changes: 75 additions & 0 deletions include/fastmcpp/app.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,38 @@ struct ProxyMountedApp
class FastMCP
{
public:
struct ToolOptions
{
std::optional<std::string> title;
std::optional<std::string> description;
std::optional<std::vector<Icon>> icons;
std::vector<std::string> exclude_args;
TaskSupport task_support{TaskSupport::Forbidden};
Json output_schema{Json::object()};
};

struct PromptOptions
{
std::optional<std::string> description;
std::optional<Json> meta;
std::vector<prompts::PromptArgument> arguments;
TaskSupport task_support{TaskSupport::Forbidden};
};

struct ResourceOptions
{
std::optional<std::string> description;
std::optional<std::string> mime_type;
TaskSupport task_support{TaskSupport::Forbidden};
};

struct ResourceTemplateOptions
{
std::optional<std::string> description;
std::optional<std::string> mime_type;
TaskSupport task_support{TaskSupport::Forbidden};
};

/// Construct app with metadata
explicit FastMCP(std::string name = "fastmcpp_app", std::string version = "1.0.0",
std::optional<std::string> website_url = std::nullopt,
Expand Down Expand Up @@ -117,6 +149,49 @@ class FastMCP
return server_;
}

// =========================================================================
// Ergonomic registration helpers (Python FastMCP decorator-style analogs)
// =========================================================================

/// Register a tool using either a full JSON Schema or a "simple" param map
/// (e.g., {"a":"number","b":"integer"}).
FastMCP& tool(std::string name, const Json& input_schema_or_simple, tools::Tool::Fn fn,
ToolOptions options);
FastMCP& tool(std::string name, const Json& input_schema_or_simple, tools::Tool::Fn fn);

/// Register a zero-argument tool (input schema defaults to {}).
FastMCP& tool(std::string name, tools::Tool::Fn fn, ToolOptions options);
FastMCP& tool(std::string name, tools::Tool::Fn fn);

/// Register a prompt generator (equivalent to Python's @server.prompt).
FastMCP& prompt(std::string name,
std::function<std::vector<prompts::PromptMessage>(const Json&)> generator,
PromptOptions options);
FastMCP& prompt(std::string name,
std::function<std::vector<prompts::PromptMessage>(const Json&)> generator);

/// Register a template-backed prompt (legacy Prompt template string).
FastMCP& prompt_template(std::string name, std::string template_string, PromptOptions options);
FastMCP& prompt_template(std::string name, std::string template_string);

/// Register a concrete resource (equivalent to Python's @server.resource for fixed URIs).
FastMCP& resource(std::string uri, std::string name,
std::function<resources::ResourceContent(const Json&)> provider,
ResourceOptions options);
FastMCP& resource(std::string uri, std::string name,
std::function<resources::ResourceContent(const Json&)> provider);

/// Register a resource template (equivalent to Python's @server.resource for templated URIs).
/// If parameters_schema_or_simple is empty, parameters are derived from the URI template.
FastMCP&
resource_template(std::string uri_template, std::string name,
std::function<resources::ResourceContent(const Json& params)> provider,
const Json& parameters_schema_or_simple, ResourceTemplateOptions options);
FastMCP&
resource_template(std::string uri_template, std::string name,
std::function<resources::ResourceContent(const Json& params)> provider,
const Json& parameters_schema_or_simple = Json::object());

// =========================================================================
// App Mounting
// =========================================================================
Expand Down
2 changes: 1 addition & 1 deletion include/fastmcpp/client/client.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ class Client
/// @param options Call options (timeout, meta, progress handler)
/// @return CallToolResult with content, error status, and metadata
CallToolResult call_tool_mcp(const std::string& name, const fastmcpp::Json& arguments,
const CallToolOptions& options = {})
const CallToolOptions& options = CallToolOptions{})
{

fastmcpp::Json payload = {{"name", name}, {"arguments", arguments}};
Expand Down
51 changes: 51 additions & 0 deletions include/fastmcpp/client/sampling_handlers.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
#pragma once

#include "fastmcpp/types.hpp"

#include <functional>
#include <optional>
#include <string>

namespace fastmcpp::client::sampling::handlers
{

struct OpenAICompatibleOptions
{
std::string base_url = "https://api.openai.com";
std::string endpoint_path = "/v1/chat/completions";

std::optional<std::string> api_key;
std::string api_key_env = "OPENAI_API_KEY";

std::string default_model = "gpt-4o-mini";
std::optional<std::string> organization;
std::optional<std::string> project;

int timeout_ms = 60000;
};

/// Create a sampling/createMessage callback that calls an OpenAI-compatible
/// chat completions endpoint and returns MCP CreateMessageResult(+WithTools).
std::function<fastmcpp::Json(const fastmcpp::Json&)>
create_openai_compatible_sampling_callback(OpenAICompatibleOptions options);

struct AnthropicOptions
{
std::string base_url = "https://api.anthropic.com";
std::string endpoint_path = "/v1/messages";

std::optional<std::string> api_key;
std::string api_key_env = "ANTHROPIC_API_KEY";

std::string default_model = "claude-sonnet-4-5";
std::string anthropic_version = "2023-06-01";

int timeout_ms = 60000;
};

/// Create a sampling/createMessage callback that calls the Anthropic Messages
/// API and returns MCP CreateMessageResult(+WithTools).
std::function<fastmcpp::Json(const fastmcpp::Json&)>
create_anthropic_sampling_callback(AnthropicOptions options);

} // namespace fastmcpp::client::sampling::handlers
5 changes: 5 additions & 0 deletions include/fastmcpp/client/types.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ struct ResourceTemplate
std::optional<std::string> title; ///< Human-readable title
std::optional<std::string> description;
std::optional<std::string> mimeType;
std::optional<fastmcpp::Json> parameters; ///< JSON Schema for template parameters
std::optional<fastmcpp::Json> annotations;
std::optional<std::vector<fastmcpp::Icon>> icons; ///< Icons for UI display
std::optional<fastmcpp::Json> _meta; ///< Protocol metadata
Expand Down Expand Up @@ -398,6 +399,8 @@ inline void to_json(fastmcpp::Json& j, const ResourceTemplate& t)
j["description"] = *t.description;
if (t.mimeType)
j["mimeType"] = *t.mimeType;
if (t.parameters)
j["parameters"] = *t.parameters;
if (t.annotations)
j["annotations"] = *t.annotations;
if (t.icons)
Expand All @@ -416,6 +419,8 @@ inline void from_json(const fastmcpp::Json& j, ResourceTemplate& t)
t.description = j["description"].get<std::string>();
if (j.contains("mimeType"))
t.mimeType = j["mimeType"].get<std::string>();
if (j.contains("parameters"))
t.parameters = j["parameters"];
if (j.contains("annotations"))
t.annotations = j["annotations"];
if (j.contains("icons"))
Expand Down
8 changes: 5 additions & 3 deletions include/fastmcpp/server/context.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -331,22 +331,24 @@ class Context
/// @param params Optional sampling parameters
/// @return SamplingResult with text/image/audio content
/// @throws std::runtime_error if sampling not available
SamplingResult sample(const std::string& message, const SamplingParams& params = {}) const
SamplingResult sample(const std::string& message,
const SamplingParams& params = SamplingParams{}) const
{
std::vector<SamplingMessage> msgs = {{"user", message}};
return sample(msgs, params);
}

SamplingResult sample(const std::vector<SamplingMessage>& messages,
const SamplingParams& params = {}) const
const SamplingParams& params = SamplingParams{}) const
{
if (!sampling_callback_)
throw std::runtime_error("Sampling not available: no sampling callback set");
return sampling_callback_(messages, params);
}

/// Convenience: sample and return just the text content
std::string sample_text(const std::string& message, const SamplingParams& params = {}) const
std::string sample_text(const std::string& message,
const SamplingParams& params = SamplingParams{}) const
{
auto result = sample(message, params);
return result.content;
Expand Down
Loading