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: 0 additions & 2 deletions include/fastmcpp/client/client.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -1050,10 +1050,8 @@ class Client
// ValidationError so older servers / partial responses do not crash the client.
// - "content" present but not an array is treated as empty (do not crash).
if (body.contains("content") && body["content"].is_array())
{
for (const auto& c : body["content"])
result.content.push_back(parse_content_block(c));
}
// else: leave result.content empty

if (body.contains("structuredContent"))
Expand Down
5 changes: 2 additions & 3 deletions include/fastmcpp/client/types.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -401,9 +401,8 @@ inline void from_json(const fastmcpp::Json& j, ToolInfo& t)
// Python fastmcp >= 2.x exposes per-tool version via _meta.fastmcp.version (see
// fastmcp_slim/fastmcp/utilities/components.py:get_meta). Surface it as ToolInfo.version
// if no top-level "version" was provided so the proxy passthrough preserves the field.
if (!t.version && j["_meta"].is_object() && j["_meta"].contains("fastmcp")
&& j["_meta"]["fastmcp"].is_object()
&& j["_meta"]["fastmcp"].contains("version"))
if (!t.version && j["_meta"].is_object() && j["_meta"].contains("fastmcp") &&
j["_meta"]["fastmcp"].is_object() && j["_meta"]["fastmcp"].contains("version"))
{
const auto& v = j["_meta"]["fastmcp"]["version"];
if (v.is_string())
Expand Down
2 changes: 1 addition & 1 deletion include/fastmcpp/tools/tool_transform.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ build_transformed_schema(const Json& parent_schema,
if (k == "$defs" && v.is_object())
{
hoisted_defs = v;
continue; // do not also write it under the property
continue; // do not also write it under the property
}
new_prop[k] = v;
}
Expand Down
3 changes: 1 addition & 2 deletions src/client/sampling_handlers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -261,8 +261,7 @@ static fastmcpp::Json build_openai_messages(const fastmcpp::Json& params)
"' content not yet supported (F16 / fastmcp #3550); cannot dispatch sampling "
"request");
// Unknown type — surface clearly so callers don't get silent data loss.
throw std::runtime_error(
"OpenAI sampling handler: unhandled content type '" + t + "'");
throw std::runtime_error("OpenAI sampling handler: unhandled content type '" + t + "'");
}

std::string text = join_text_blocks(content);
Expand Down
3 changes: 2 additions & 1 deletion src/providers/openapi_provider.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -444,7 +444,8 @@ Json OpenAPIProvider::invoke_route(const RouteDefinition& route, const Json& arg

std::ostringstream query;
bool first = true;
auto append_pair = [&](const std::string& key, const std::string& val) {
auto append_pair = [&](const std::string& key, const std::string& val)
{
query << (first ? "?" : "&");
first = false;
query << url_encode_component(key) << "=" << url_encode_component(val);
Expand Down
3 changes: 2 additions & 1 deletion src/server/response_limiting_middleware.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,8 @@ AfterHook ResponseLimitingMiddleware::make_hook() const
// outputSchema after truncation) and signal bypass via `_meta = {}` so MCP SDK
// clients accept the response as a vanilla CallToolResult instead of failing
// outputSchema validation. Apply at both shapes (route payload + JSON-RPC envelope).
auto bypass_output_schema = [](fastmcpp::Json& obj) {
auto bypass_output_schema = [](fastmcpp::Json& obj)
{
if (obj.contains("structuredContent"))
obj.erase("structuredContent");
if (!obj.contains("_meta") || !obj["_meta"].is_object())
Expand Down
9 changes: 5 additions & 4 deletions src/util/json_schema_type.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@ const std::regex& cached_regex_required(const std::string& key, const std::strin
{
auto* p = cached_regex(key, pattern);
if (!p)
throw fastmcpp::ValidationError("internal regex compile failure for built-in pattern: " + key);
throw fastmcpp::ValidationError("internal regex compile failure for built-in pattern: " +
key);
return *p;
}

Expand Down Expand Up @@ -152,8 +153,8 @@ SchemaValue handle_string(const fastmcpp::Json& schema, const fastmcpp::Json& in
}
else if (fmt == "date-time")
{
const auto& dt_re =
cached_regex_required("date-time", R"(^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z$)");
const auto& dt_re = cached_regex_required(
"date-time", R"(^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z$)");
if (!std::regex_match(value, dt_re))
throw fastmcpp::ValidationError("Invalid date-time format at " + path);
}
Expand Down Expand Up @@ -363,7 +364,7 @@ SchemaValue convert(const fastmcpp::Json& schema, const fastmcpp::Json& instance
if (schema.is_boolean())
{
if (schema.get<bool>())
return instance; // true: accept-any, pass through
return instance; // true: accept-any, pass through
throw fastmcpp::ValidationError("schema=false rejects all values at " + path);
}

Expand Down
11 changes: 7 additions & 4 deletions tests/tools/test_tool_timeout.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,14 @@ void test_tool_timeout_triggers()
Tool slow_tool("slow", Json::object(), Json::object(),
[](const Json&) -> Json
{
sleep_for_at_least(50ms);
// Large sleep margin so the timeout fires reliably even on
// slow CI runners (macOS Debug) where scheduling jitter can
// delay future::wait_for() past the worker's sleep duration.
sleep_for_at_least(5s);
return Json{{"ok", true}};
});

slow_tool.set_timeout(10ms);
slow_tool.set_timeout(50ms);

bool threw = false;
try
Expand Down Expand Up @@ -72,11 +75,11 @@ void test_manager_timeout_toggle()
Tool slow_tool("slow_manager", Json::object(), Json::object(),
[](const Json&) -> Json
{
sleep_for_at_least(40ms);
sleep_for_at_least(5s);
return Json{{"ok", true}};
});

slow_tool.set_timeout(10ms);
slow_tool.set_timeout(50ms);

ToolManager tm;
tm.register_tool(slow_tool);
Expand Down
13 changes: 13 additions & 0 deletions tests/transports/stdio_lifecycle.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@
#include <string>
#include <vector>

#ifndef _WIN32
#include <csignal>
#endif

static std::string find_stdio_server_binary()
{
namespace fs = std::filesystem;
Expand All @@ -30,6 +34,15 @@ int main()
using fastmcpp::Json;
using fastmcpp::client::StdioTransport;

#ifndef _WIN32
// Ignore SIGPIPE: writing to a closed subprocess stdin (e.g. when the child
// has already exited, as in Test 1's `sh -c "exit 42"`) must produce
// EPIPE/return -1, not kill this test binary. macOS Debug runners under
// CI load can race the child's exit ahead of our first write, surfacing
// this as a SIGPIPE-induced test failure.
signal(SIGPIPE, SIG_IGN);
#endif

// Test 1: Server process crash surfaces TransportError with context
std::cout << "Test: server crash surfaces TransportError...\n";
{
Expand Down
Loading