Skip to content

Commit 34b1f0c

Browse files
authored
feat: tasks, sampling tools loop, and sync parity updates (#22)
- Implement in-flight tasks with cancel + TTL (SEP-1686) - Add task status push notifications (running, terminal states) - Add statusMessage streaming support - Implement sampling tools loop - Add mount tool_names, streamable redirects, prompt meta - Cover elicitation defaults + nullability in tests - Fix MSVC assert popups in mounting tests
1 parent 31148d7 commit 34b1f0c

23 files changed

Lines changed: 2517 additions & 153 deletions

‎.github/workflows/ci.yml‎

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,6 @@ jobs:
1717
matrix:
1818
os: [ubuntu-latest, windows-latest, macos-latest]
1919
build_type: [Debug, Release]
20-
include:
21-
# ARM64 Linux builds
22-
- os: ubuntu-24.04-arm
23-
build_type: Debug
24-
- os: ubuntu-24.04-arm
25-
build_type: Release
2620

2721
runs-on: ${{ matrix.os }}
2822
name: ${{ matrix.os }} (${{ matrix.build_type }})

‎CMakeLists.txt‎

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
cmake_minimum_required(VERSION 3.16)
2-
project(fastmcpp VERSION 2.14.0 LANGUAGES CXX)
2+
project(fastmcpp VERSION 2.14.1 LANGUAGES CXX)
33

44
set(CMAKE_CXX_STANDARD 17)
55
set(CMAKE_CXX_STANDARD_REQUIRED ON)
@@ -18,6 +18,7 @@ add_library(fastmcpp_core
1818
src/app.cpp
1919
src/proxy.cpp
2020
src/mcp/handler.cpp
21+
src/mcp/tasks.cpp
2122
src/resources/resource.cpp
2223
src/resources/manager.cpp
2324
src/resources/template.cpp
@@ -30,6 +31,7 @@ add_library(fastmcpp_core
3031
src/server/context.cpp
3132
src/server/middleware.cpp
3233
src/server/security_middleware.cpp
34+
src/server/sampling.cpp
3335
src/server/http_server.cpp
3436
src/server/stdio_server.cpp
3537
src/server/sse_server.cpp
@@ -231,6 +233,10 @@ if(FASTMCPP_BUILD_TESTS)
231233
target_link_libraries(fastmcpp_sse_mcp_format PRIVATE fastmcpp_core)
232234
add_test(NAME fastmcpp_sse_mcp_format COMMAND fastmcpp_sse_mcp_format)
233235

236+
add_executable(fastmcpp_sse_tasks_notifications tests/server/sse_tasks_notifications.cpp)
237+
target_link_libraries(fastmcpp_sse_tasks_notifications PRIVATE fastmcpp_core)
238+
add_test(NAME fastmcpp_sse_tasks_notifications COMMAND fastmcpp_sse_tasks_notifications)
239+
234240
# Advanced test suites (Task 3.4)
235241
add_executable(fastmcpp_tools_validation tests/tools/validation.cpp)
236242
target_link_libraries(fastmcpp_tools_validation PRIVATE fastmcpp_core)
@@ -288,6 +294,10 @@ if(FASTMCPP_BUILD_TESTS)
288294
target_link_libraries(fastmcpp_server_context_sampling PRIVATE fastmcpp_core)
289295
add_test(NAME fastmcpp_server_context_sampling COMMAND fastmcpp_server_context_sampling)
290296

297+
add_executable(fastmcpp_server_sampling_tools tests/server/test_sampling_tools.cpp)
298+
target_link_libraries(fastmcpp_server_sampling_tools PRIVATE fastmcpp_core)
299+
add_test(NAME fastmcpp_server_sampling_tools COMMAND fastmcpp_server_sampling_tools)
300+
291301
add_executable(fastmcpp_server_elicitation_defaults tests/server/test_elicitation_defaults.cpp)
292302
target_link_libraries(fastmcpp_server_elicitation_defaults PRIVATE fastmcpp_core)
293303
add_test(NAME fastmcpp_server_elicitation_defaults COMMAND fastmcpp_server_elicitation_defaults)

‎include/fastmcpp/app.hpp‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#include <memory>
1111
#include <optional>
1212
#include <string>
13+
#include <unordered_map>
1314
#include <vector>
1415

1516
namespace fastmcpp
@@ -20,13 +21,17 @@ struct MountedApp
2021
{
2122
std::string prefix; // Prefix for tools/prompts (e.g., "weather")
2223
class FastMCP* app; // Non-owning pointer to mounted app
24+
std::optional<std::unordered_map<std::string, std::string>>
25+
tool_names; // Optional tool name overrides
2326
};
2427

2528
/// Proxy-mounted app with prefix (proxy mode)
2629
struct ProxyMountedApp
2730
{
2831
std::string prefix; // Prefix for tools/prompts
2932
std::unique_ptr<ProxyApp> proxy; // Owning pointer to proxy wrapper
33+
std::optional<std::unordered_map<std::string, std::string>>
34+
tool_names; // Optional tool name overrides
3035
};
3136

3237
/// MCP Application - bundles server metadata with managers
@@ -125,8 +130,14 @@ class FastMCP
125130
/// @param app The app to mount (must outlive this app in direct mode)
126131
/// @param prefix Optional prefix (empty string = no prefix)
127132
/// @param as_proxy If true, mount in proxy mode (uses MCP handler for communication)
133+
/// @param tool_names Optional mapping of original tool names to custom names. Keys are the
134+
/// original tool names from the mounted server (after any nested prefixing).
128135
void mount(FastMCP& app, const std::string& prefix = "", bool as_proxy = false);
129136

137+
/// Mount another app with optional tool name overrides.
138+
void mount(FastMCP& app, const std::string& prefix, bool as_proxy,
139+
std::optional<std::unordered_map<std::string, std::string>> tool_names);
140+
130141
/// Get list of directly mounted apps
131142
const std::vector<MountedApp>& mounted() const
132143
{
@@ -173,6 +184,10 @@ class FastMCP
173184
/// Get prompt messages by name (handles prefixed routing)
174185
std::vector<prompts::PromptMessage> get_prompt(const std::string& name, const Json& args) const;
175186

187+
/// Get prompt result by name (handles prefixed routing)
188+
/// Includes description and optional _meta parity with Python SDK (fastmcp 2.14.1+).
189+
prompts::PromptResult get_prompt_result(const std::string& name, const Json& args) const;
190+
176191
private:
177192
server::Server server_;
178193
tools::ToolManager tools_;

‎include/fastmcpp/mcp/handler.hpp‎

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ class SseServerWrapper;
2626
namespace fastmcpp::mcp
2727
{
2828

29+
/// Session accessor callback type - retrieves ServerSession for a session_id
30+
using SessionAccessor = std::function<std::shared_ptr<server::ServerSession>(const std::string&)>;
31+
2932
// Factory that produces a JSON-RPC handler compatible with ClaudeOptions::sdk_mcp_handlers.
3033
// It supports a subset of MCP methods needed for in-process tools:
3134
// - "initialize"
@@ -62,13 +65,15 @@ make_mcp_handler(const std::string& server_name, const std::string& version,
6265
// Uses app's aggregated lists and routing for mounted sub-apps
6366
std::function<fastmcpp::Json(const fastmcpp::Json&)> make_mcp_handler(const FastMCP& app);
6467

68+
// Overload: FastMCP handler with session access.
69+
// Enables server-initiated features (e.g., task status push) keyed by params._meta.session_id.
70+
std::function<fastmcpp::Json(const fastmcpp::Json&)>
71+
make_mcp_handler(const FastMCP& app, SessionAccessor session_accessor);
72+
6573
// MCP handler from ProxyApp - supports proxying to backend server
6674
// Uses app's aggregated lists (local + remote) and routing
6775
std::function<fastmcpp::Json(const fastmcpp::Json&)> make_mcp_handler(const ProxyApp& app);
6876

69-
/// Session accessor callback type - retrieves ServerSession for a session_id
70-
using SessionAccessor = std::function<std::shared_ptr<server::ServerSession>(const std::string&)>;
71-
7277
/// MCP handler with sampling support
7378
/// The session_accessor callback is used to get ServerSession for sampling requests.
7479
/// Session ID is extracted from params._meta.session_id (injected by SSE server).

‎include/fastmcpp/mcp/tasks.hpp‎

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
#pragma once
2+
3+
#include <string>
4+
5+
namespace fastmcpp::mcp::tasks
6+
{
7+
8+
/// Report a status message for the currently executing background task (SEP-1686).
9+
///
10+
/// This sends best-effort `notifications/tasks/status` updates (via the transport/session)
11+
/// when called from within a task execution context created by `mcp::make_mcp_handler(...)`.
12+
///
13+
/// No-op if called outside a background task context.
14+
void report_status_message(const std::string& message);
15+
16+
namespace detail
17+
{
18+
using StatusMessageFn = void (*)(void* ctx, const std::string& task_id, const std::string& message);
19+
20+
// Internal: set/clear the task context for the current thread.
21+
// Used by the MCP task execution runtime (TaskRegistry).
22+
void set_current_task(void* ctx, StatusMessageFn fn, std::string task_id);
23+
void clear_current_task();
24+
} // namespace detail
25+
26+
} // namespace fastmcpp::mcp::tasks

‎include/fastmcpp/prompts/prompt.hpp‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,21 @@ struct PromptMessage
2525
std::string content; // Message content
2626
};
2727

28+
/// Result of prompts/get (prompt rendering)
29+
struct PromptResult
30+
{
31+
std::vector<PromptMessage> messages;
32+
std::optional<std::string> description;
33+
std::optional<fastmcpp::Json> meta; // Returned as _meta in MCP prompts/get
34+
};
35+
2836
/// MCP Prompt definition
2937
struct Prompt
3038
{
3139
std::string name;
3240
std::optional<std::string> description;
41+
std::optional<fastmcpp::Json>
42+
meta; // Optional prompt metadata (returned as _meta in prompts/get)
3343
std::vector<PromptArgument> arguments;
3444
std::function<std::vector<PromptMessage>(const Json&)> generator; // Message generator
3545
fastmcpp::TaskSupport task_support{fastmcpp::TaskSupport::Forbidden}; // SEP-1686 task mode
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
#pragma once
2+
#include "fastmcpp/server/session.hpp"
3+
#include "fastmcpp/types.hpp"
4+
5+
#include <chrono>
6+
#include <functional>
7+
#include <optional>
8+
#include <string>
9+
#include <unordered_map>
10+
#include <vector>
11+
12+
namespace fastmcpp::server::sampling
13+
{
14+
15+
// ---------------------------------------------------------------------------
16+
// SEP-1577 sampling-with-tools helpers (server-initiated sampling/createMessage)
17+
// ---------------------------------------------------------------------------
18+
19+
struct Tool
20+
{
21+
std::string name;
22+
std::optional<std::string> description;
23+
fastmcpp::Json input_schema{fastmcpp::Json::object()};
24+
std::function<fastmcpp::Json(const fastmcpp::Json&)> fn;
25+
};
26+
27+
struct Message
28+
{
29+
std::string role; // "user" or "assistant"
30+
fastmcpp::Json content; // MCP SamplingMessageContentBlock or list thereof
31+
};
32+
33+
inline Message make_text_message(const std::string& role, const std::string& text)
34+
{
35+
return Message{role, fastmcpp::Json{{"type", "text"}, {"text", text}}};
36+
}
37+
38+
struct Options
39+
{
40+
std::optional<std::string> system_prompt;
41+
std::optional<float> temperature;
42+
int max_tokens{512};
43+
std::optional<fastmcpp::Json> model_preferences;
44+
std::optional<std::vector<std::string>> stop_sequences;
45+
std::optional<fastmcpp::Json> metadata;
46+
47+
std::optional<std::vector<Tool>> tools;
48+
// Simplified tool choice: "auto", "required", or "none"
49+
std::optional<std::string> tool_choice;
50+
51+
bool execute_tools{true};
52+
bool mask_error_details{false};
53+
int max_iterations{10};
54+
std::chrono::milliseconds timeout{ServerSession::DEFAULT_TIMEOUT};
55+
};
56+
57+
struct Step
58+
{
59+
fastmcpp::Json response; // CreateMessageResult(+WithTools) JSON
60+
std::vector<Message> history;
61+
62+
bool is_tool_use() const;
63+
std::optional<std::string> text() const;
64+
std::vector<fastmcpp::Json> tool_calls() const;
65+
};
66+
67+
struct Result
68+
{
69+
std::optional<std::string> text;
70+
fastmcpp::Json response;
71+
std::vector<Message> history;
72+
};
73+
74+
Step sample_step(std::shared_ptr<ServerSession> session, const std::vector<Message>& messages,
75+
const Options& options);
76+
77+
Result sample(std::shared_ptr<ServerSession> session, const std::vector<Message>& messages,
78+
Options options);
79+
80+
} // namespace fastmcpp::server::sampling

‎include/fastmcpp/server/session.hpp‎

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,8 +103,17 @@ class ServerSession
103103
capabilities_ = capabilities;
104104

105105
// Parse common capability flags
106+
supports_sampling_ = false;
107+
supports_sampling_tools_ = false;
108+
supports_elicitation_ = false;
109+
supports_roots_ = false;
106110
if (capabilities.contains("sampling") && capabilities["sampling"].is_object())
111+
{
107112
supports_sampling_ = true;
113+
const auto& sampling = capabilities["sampling"];
114+
if (sampling.contains("tools") && sampling["tools"].is_object())
115+
supports_sampling_tools_ = true;
116+
}
108117
if (capabilities.contains("elicitation") && capabilities["elicitation"].is_object())
109118
supports_elicitation_ = true;
110119
if (capabilities.contains("roots") && capabilities["roots"].is_object())
@@ -118,6 +127,13 @@ class ServerSession
118127
return supports_sampling_;
119128
}
120129

130+
/// Check if client supports sampling with tools (sampling.tools capability)
131+
bool supports_sampling_tools() const
132+
{
133+
std::lock_guard lock(cap_mutex_);
134+
return supports_sampling_tools_;
135+
}
136+
121137
/// Check if client supports elicitation
122138
bool supports_elicitation() const
123139
{
@@ -262,10 +278,14 @@ class ServerSession
262278
*
263279
* @param method The JSON-RPC method name (e.g., "notifications/progress")
264280
* @param params Notification parameters
281+
* @param meta Optional top-level _meta for the notification
265282
*/
266-
void send_notification(const std::string& method, const Json& params = Json::object())
283+
void send_notification(const std::string& method, const Json& params = Json::object(),
284+
const std::optional<Json>& meta = std::nullopt)
267285
{
268286
Json notification = {{"jsonrpc", "2.0"}, {"method", method}, {"params", params}};
287+
if (meta.has_value() && meta->is_object() && !meta->empty())
288+
notification["_meta"] = *meta;
269289

270290
if (send_callback_)
271291
send_callback_(notification);
@@ -331,6 +351,7 @@ class ServerSession
331351
bool supports_sampling_{false};
332352
bool supports_elicitation_{false};
333353
bool supports_roots_{false};
354+
bool supports_sampling_tools_{false};
334355

335356
// Pending requests
336357
std::mutex pending_mutex_;

0 commit comments

Comments
 (0)