Skip to content

Parity catch-up: v3.1.0 -> v3.3.1 + community PRs #42 + #43#44

Merged
0xeb merged 7 commits into
mainfrom
feature/parity-catchup-d8dcc273
May 16, 2026
Merged

Parity catch-up: v3.1.0 -> v3.3.1 + community PRs #42 + #43#44
0xeb merged 7 commits into
mainfrom
feature/parity-catchup-d8dcc273

Conversation

@0xeb
Copy link
Copy Markdown
Owner

@0xeb 0xeb commented May 16, 2026

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

  • feat(parity): F1 + F2 + F11 + F12 - initial Phase 3a catch-up
  • feat(parity): F3 + F4 + F5 + F6 + F7 - Phase 3a schema/transform fixes
  • feat(parity): F8 + F9 + F10 - Phase 3a/3b OpenAPI nullable + lifecycle fixes
  • feat(parity): F14 + F17 + F18 + F19 + F20 + F21 + F22 - close out catch-up cycle

v3.3.1 bump -- 1 commit

  • chore(version): bump 3.3.0 -> 3.3.1 (track upstream fastmcp v3.3.1)

Community PR integrations -- 2 commits with original author attribution

Verification

  • fastmcpp Debug ctest: 103/103 GREEN
  • Deep interop (fastmcp <-> fastmcpp): 241/241 across 9 scenarios GREEN

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).

0xeb and others added 7 commits May 12, 2026 23:37
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>
@0xeb 0xeb merged commit c54d21e into main May 16, 2026
5 of 7 checks passed
@0xeb 0xeb deleted the feature/parity-catchup-d8dcc273 branch May 16, 2026 18:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants