Parity catch-up: v3.1.0 -> v3.3.1 + community PRs #42 + #43#44
Merged
Conversation
Bundles four small parity items from the v3.3.0b2 catch-up cycle:
F1 (T10 baseline regression — tool.version)
- ToolInfo::from_json: also surface version from _meta.fastmcp.version
(Python encodes version there; we still emit "version" at top level on
serialize). Fixes T10 "version_field_present" interop test.
- ProxyApp tools/list handler: emit tool_info.version when present
(parity with FastMCP-side tools/list builders).
F2 (Python commit 970b92bb): allow hyphens in resource template params.
- ResourceTemplate::match: keys returned to providers normalized
(`-` → `_`) so `{user-id}` exposes "user_id".
- Mirrors Python re-named-group convention (Python identifiers cannot
contain hyphens).
F11 (Python commit 73b7f2e4 #4036): add log_level to FastMCP errors.
- exceptions.hpp: add `log_level` int field (Python `logging` constants
Debug=10..Critical=50) on Error base class with default Error and
accessors. Reused as `int` (not enum) to avoid colliding with the
existing `fastmcpp::server::LogLevel` symbol.
F12 (Python commit a010927e #4042): experimental_capabilities kwarg.
- FastMCP: add experimental_capabilities() accessor + setter.
- handler.cpp: new advertise_experimental() helper called at all three
FastMCP `initialize` builders (1873/2579/3048-equivalent). ProxyApp
`initialize` intentionally skipped — ProxyApp does not own that field.
Verified:
- cmake --build (Debug): green.
- ctest -E fastmcpp_stdio_timeout (Debug): 102/102.
- tests/fastmcpp/tests/run_interop_tests.py --test T10 --category version
(Release): 3/3 tests pass (was 2/3 with version_field_present failing
on the v3.3.0b2 baseline before this commit).
Reference: fastmcp commits 970b92bb, 73b7f2e4, a010927e (and the
post-v3.3.0b2 _meta.fastmcp.version exposure that surfaced T10).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
F3 (Python commit b8597f94 #4101): hoist $defs to schema root in tool_transform.
- tool_transform.hpp build_transformed_schema: when ArgTransform.type_schema
contains $defs, hoist them into result.schema["$defs"] so MCP clients can
resolve $ref entries from the transformed property.
- Preserves any hoisted $defs across the result.schema = parent_schema reset.
F4 + F5 (Python commits 4b59e0d9 #3818 + e5b96343 #3785): boolean schemas + empty enums.
- json_schema_type.cpp convert(): JSON Schema permits boolean schemas at any
level. `true` accepts any value (pass through); `false` rejects all
(ValidationError). Previously crashed with json::type_error on
schema["type"].get<string>().
- enforce_enum_const: explicitly reject non-array enum and treat empty enum
as no-match (previous code would simply not match any value, but on a
non-array enum would crash).
F6 (Python commit 789a2986 #3959): graceful regex_error fallback in json_schema_to_type.
- cached_regex(): try/catch std::regex_error. On failure, return nullptr so
caller can drop the constraint. Mirrors Python behavior of silently
dropping unsupported regex patterns (lookahead, certain Unicode escapes)
so real-world OpenAPI specs (AWS, Azure) do not crash.
- cached_regex_required(): retained reference-returning overload for our
own built-in patterns (email/uri/date-time) which are guaranteed valid.
F7 (Python commit 923695bd #3682): strip discriminator after dereferencing.
- json_schema.cpp: new strip_discriminator() recursively removes
`discriminator` keys at union (anyOf/oneOf) boundaries after $defs
inlining. Properties literally named "discriminator" inside `properties:`
are preserved.
- Called from dereference_refs() right before returning.
Verified:
- cmake --build (Debug): green.
- ctest -E fastmcpp_stdio_timeout (Debug): 102/102 passing.
- tests/fastmcpp/tests/run_interop_tests.py --test all (Release):
241/241 tests across 9 scenarios (was 239/240 with T10 version_field
failing on the v3.3.0b2 baseline before F1).
Reference: fastmcp commits b8597f94, 4b59e0d9, e5b96343, 789a2986, 923695bd.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…e fixes
F8 (Python commit 042db1d0 #3768): OpenAPI 3.0 `nullable: true` in tool input schemas.
- openapi_provider.cpp consume_parameters: convert
`{type: string, nullable: true}` to JSON Schema `type: ["string", "null"]`
so json_schema_to_value and downstream validators accept null. Also handles
the no-`type`-key case (becomes `type: "null"`). `nullable` key stripped.
F9 (Python commit 82090938 #4118): drain in-flight Streamable HTTP responses on stop.
- StreamableHttpServerWrapper::stop(): reorder so svr_->stop() and
thread_.join() complete BEFORE clearing sessions_. This keeps the
sessions map populated during shutdown so any handler still flushing a
final SSE event can still resolve its session_id, mirroring Python's
"terminate active transports before lifespan teardown" intent.
F10 (Python commit 4bbc4eec #3756): ResponseLimitingMiddleware outputSchema bypass.
- response_limiting_middleware.cpp make_hook(): when truncating a
tools/call response, drop `structuredContent` (no longer matches the
registered outputSchema) and set `_meta = {}` (non-null) so MCP SDK
clients accept the truncated response as a vanilla CallToolResult and
bypass outputSchema validation. Applied at both shapes (route payload
and JSON-RPC envelope `result`).
Verified:
- cmake --build (Debug): green.
- ctest -E fastmcpp_stdio_timeout (Debug): 102/102 passing.
Reference: fastmcp commits 042db1d0, 82090938, 4bbc4eec.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ch-up cycle
F14 (Python commit d6b55c0b #3857): raise on unhandled content types in sampling.
- sampling_handlers.cpp build_openai_messages: detect image/audio/unknown
content types and throw a clear error rather than silently dropping. F16
(image/audio request-side conversion) deferred — explicit error message
points at it so callers know.
- build_anthropic_messages: same; audio additionally raises with note that
Anthropic Messages API does not accept audio.
F17 (Python commit 556fd8fa #3778): harden client.call_tool error handling.
- parse_call_tool_result: missing/non-array `content` no longer raises
ValidationError; returns empty CallToolResult so older servers / partial
responses don't crash callers.
- Test api_advanced.cpp `test_call_tool_error_and_data` updated to assert
the new behavior (intrinsically coupled to source change; permitted
in-submodule edit per workflow rules).
F18 (Python commit f5804f47 #3630): recover StdioTransport after subprocess exit.
- StdioTransport::request: on entry to the keep_alive branch, if state_ is
populated and the subprocess has already exited, tear down state_ (join
stderr thread) so the next call respawns cleanly. Previously a dead
subprocess caused every subsequent request to throw forever.
F19 (Python commit 99eaeb8a #3770): substitute server-variable defaults in OpenAPI base URL.
- openapi_provider.cpp ctor: when reading servers[0].url, expand
`{varName}` placeholders using servers[0].variables.<name>.default so
specs with `"url": "{protocol}://api.example.com"` produce a usable
base URL. Non-string defaults serialized via .dump() as fallback.
F20 (Python commits 6f30e89d #3595 + 16eb2ffc #3662): query param style/explode.
- openapi_provider.cpp invoke_route: arrays expand to multiple `key=val`
pairs (OpenAPI default style=form, explode=true); objects expand to
individual `prop=val` pairs (default explode=true drops the outer
param name). Per-param style/explode override metadata not yet plumbed
through RouteDefinition — partial implementation noted in
kb/sync/review_result.md F20.
F21 (Python commits 7dd57398 #3932 + ca76b828 #3611): body content-type dispatch.
- parse_routes: prefer `application/json` requestBody schema; fall back to
`application/x-www-form-urlencoded`, then `multipart/form-data` (capture
only — multipart serialization not yet implemented). Stored on
RouteDefinition::request_content_type.
- invoke_route: when content type is form-urlencoded and body is an object,
serialize as `key=val&...`. Otherwise dump JSON. The declared content
type is now sent in the HTTP Content-Type header.
F22: version bump 3.1.1 → 3.3.0 in CMakeLists.txt (matches upstream
fastmcp v3.3.0b2 reference SHA ee48a0fd).
Verified:
- cmake --build (Debug): green.
- ctest -E fastmcpp_stdio_timeout (Debug): 102/102 passing.
- tests/fastmcpp/tests/run_interop_tests.py --test all (Release):
241/241 tests across 9 scenarios.
Reference: fastmcp commits d6b55c0b, 556fd8fa, f5804f47, 99eaeb8a,
6f30e89d, 16eb2ffc, 7dd57398, ca76b828.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CMakeLists.txt 3.3.0 -> 3.3.1: track upstream fastmcp v3.3.1 (d8dcc273, 2026-05-15). README.md 2.15.0 -> 3.3.1: rectifies stale 'Current version' string left over from prior cycles; not a behavioral leap. Upstream v3.3.0b2 -> v3.3.1 delta is a pure Python import-graph refactor (#4150 "Decouple component imports from server"): moves 'fastmcp.server.auth.authorization' and 'fastmcp.server.tasks.config' into 'fastmcp.utilities.*', plus TYPE_CHECKING-only imports and function-local 'fastmcp.server.dependencies' imports to break the exposed circular imports. Plus 2 docs-only commits. No MCP protocol or C++ runtime behavior delta; no fastmcpp source changes required. Stacked on feature/parity-catchup-ee48a0fd (v3.3.0b2 catch-up). Recommended merge order: ee48a0fd first, then this branch. Post-bump verification: fastmcpp Debug ctest 103/103 GREEN. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…(PR #42) Merges PR #42 from @polaon. Squashed from 7 commits for clean history. Original author preserved; PR author and Copilot credited as co-authors below. Bugs fixed in src/server/streamable_http_server.cpp: 1. GET /mcp returned 405 without CORS headers (Access-Control-Allow-Origin missing). Browsers blocked the response. Fix: call apply_additional_response_headers(res) at the top of the GET handler. 2. DELETE /mcp had no handler at all. MCP Streamable HTTP spec (2025-03-26) permits DELETE for session termination, but httplib fell through to its default 404 with no CORS. Fix: register DELETE handler that erases the session from sessions_ when a valid Mcp-Session-Id is present and responds 204 No Content with CORS headers. 3. OPTIONS preflight advertised Access-Control-Allow-Methods: POST, OPTIONS only, so browsers rejected DELETE preflights pre-emptively. Fix: broaden to GET, POST, DELETE, OPTIONS. 4. Mcp-Session-Id response header not exposed to JS. Per CORS, browser JS cannot read non-safelisted headers without Access-Control-Expose-Headers. The POST handler attached Mcp-Session-Id but never set Expose-Headers, so response.headers.get('Mcp-Session-Id') returned null in browser JS. Fix: set Access-Control-Expose-Headers: Mcp-Session-Id on the POST path. 5. 401 Unauthorized + other early-return paths in the POST handler emitted responses BEFORE apply_additional_response_headers(res). Fix: move the apply_additional_response_headers call to the very top of the POST lambda, before the auth check and outside the try block, so every response path (success, 401, 503, 400, 404, 5xx via catch) carries the configured CORS / custom headers unconditionally. No public API changes. No new dependencies. SseServerWrapper not modified (same anti-pattern exists there but out of scope for this PR). CI: all 7 jobs green on PR (ubuntu/macos/windows x Debug+Release + format-check). Closes PR #42. Co-authored-by: polaon <107108922+polaon@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ient dispatch (PR #43) Merges PR #43 from @2530278940wc-dot (mogaitesheng). Squashed from 2 commits; original author preserved; PR author and Copilot credited as co-authors below. Two changes: 1. CMakeLists.txt: new opt-in option FASTMCPP_ENABLE_OPENSSL (default OFF). When ON: find_package(OpenSSL REQUIRED), defines CPPHTTPLIB_OPENSSL_SUPPORT, links OpenSSL::SSL + OpenSSL::Crypto into fastmcpp_core. Default builds are unchanged. 2. src/providers/openapi_provider.cpp: fix a compile error that occurred when CPPHTTPLIB_OPENSSL_SUPPORT was defined. The original code stored the client in std::unique_ptr<httplib::Client> and tried to assign std::make_unique<httplib::SSLClient>(...) into it, but httplib::Client and httplib::SSLClient do not share a base class. Split the dispatch into separate http and https branches with their own typed unique_ptr. Conflict resolution: PR #43 hardcoded 'application/json' as the content type. Our F1-F22 catch-up ( oute.request_content_type plumbing for F21 multipart/form-urlencoded) replaced that with route.request_content_type (via local variable ct). The merge preserves the dynamic content type in both http and https branches. Closes PR #43. Co-authored-by: mogaitesheng <260245642+2530278940wc-dot@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Squashed catch-up branch carrying all v3.1.0 -> v3.3.1 parity work plus two community PRs.
Contents (7 commits)
v3.3.0b2 catch-up (F1-F22) -- 4 commits by @0xeb
v3.3.1 bump -- 1 commit
Community PR integrations -- 2 commits with original author attribution
Verification
Deferred items (carried forward as documented)
F13/F15 sampling validator chain, F16 multimodal sampling request body, F20 per-param OpenAPI style/explode overrides, F21 OpenAPI multipart serialization.
Intentional divergences (carried forward as documented)
Auth ecosystem, distributed task queue, MCP Apps Phase 1, fastmcp dev apps browser UI, Google/Gemini sampling, ProxyProvider caching, Depends() DI, fastmcp-slim PyPI packaging variant, CSP-on-tool-metadata.
After merge: feature/parity-catchup-ee48a0fd can be deleted (its commits are included here).