diff --git a/.github/workflows/test_core.yml b/.github/workflows/test_core.yml index af71ddd3..7de11047 100644 --- a/.github/workflows/test_core.yml +++ b/.github/workflows/test_core.yml @@ -51,7 +51,7 @@ jobs: - name: Run official MCP 2025 client conformance run: > - npx -y @modelcontextprotocol/conformance@0.2.0-alpha.1 client + npx -y @modelcontextprotocol/conformance@0.2.0-alpha.2 client --command "dart run test/conformance/mcp_2026_rc_client.dart" --suite all --spec-version 2025-11-25 diff --git a/CHANGELOG.md b/CHANGELOG.md index 500d9e8a..322e1b1d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +## Unreleased + +### Conformance and release readiness + +- Updated official conformance gates to + `@modelcontextprotocol/conformance@0.2.0-alpha.2`, with 2026 RC runs pinned + to `DRAFT-2026-v1` and the current upstream draft fixture gap tracked as an + expected failure. + ## 2.3.0-dev.0 This is a dev preview for MCP `2026-07-28` draft/RC support. MCP diff --git a/doc/mcp-2026-rc.md b/doc/mcp-2026-rc.md index 1abd722b..25cd1704 100644 --- a/doc/mcp-2026-rc.md +++ b/doc/mcp-2026-rc.md @@ -116,13 +116,22 @@ behavior. ## Dev Release Checklist Use dev releases for MCP `2026-07-28` draft/RC testing until the official spec -is released. Dev versions must include a prerelease suffix such as -`2.3.0-dev.0` so pub.dev and GitHub treat them as preview builds. +is released. The initial SDK dev release, `mcp_dart 2.3.0-dev.0`, is already +published on pub.dev. Follow-up dev versions must increment the prerelease +suffix, such as `2.3.0-dev.1`, so pub.dev and GitHub treat them as preview +builds. -Before creating tags from `dev/2026-07-28-rc`, run: +Before creating follow-up dev tags from `dev/2026-07-28-rc`, run: ```sh dart analyze +dart run test/conformance/run_2025_server_conformance.dart +npx -y @modelcontextprotocol/conformance@0.2.0-alpha.2 client \ + --command "dart run test/conformance/mcp_2026_rc_client.dart" \ + --suite all \ + --spec-version 2025-11-25 +dart run test/conformance/run_2026_rc_server_conformance.dart +dart run test/conformance/run_2026_rc_client_conformance.dart dart pub publish --dry-run dart pub global run pana --no-warning dart run tool/validate_cli_publish.dart @@ -132,10 +141,11 @@ For dev packages, keep package documentation links pointed at `dev/2026-07-28-rc` until the draft work is ready to merge back to `main`. Restore those links to `main` as part of the final spec release prep. -Publish the SDK package first by running the `Create Release` workflow for -`mcp_dart` from `dev/2026-07-28-rc`. The publish workflow runs a dry-run check -before `dart pub publish --force`, and prerelease versions are marked as GitHub -prereleases rather than repository latest releases. +For follow-up dev releases, publish the SDK package first by running the +`Create Release` workflow for `mcp_dart` from `dev/2026-07-28-rc`. The publish +workflow runs a dry-run check before `dart pub publish --force`, and prerelease +versions are marked as GitHub prereleases rather than repository latest +releases. After `mcp_dart` is available on pub.dev, validate the CLI against the published SDK package: @@ -145,8 +155,10 @@ dart run tool/validate_cli_publish.dart --published-sdk ``` Then run the `Create Release` workflow for `mcp_dart_cli` from -`dev/2026-07-28-rc`. The CLI publish workflow removes the local SDK override -before publishing so users receive the published SDK dependency. +`dev/2026-07-28-rc` when a matching CLI dev release is needed. The initial CLI +dev release, `mcp_dart_cli 0.2.0-dev.0`, is already published. The CLI publish +workflow removes the local SDK override before publishing so users receive the +published SDK dependency. Install the dev CLI explicitly by version: diff --git a/packages/mcp_dart_cli/README.md b/packages/mcp_dart_cli/README.md index 19cd4874..95488c21 100644 --- a/packages/mcp_dart_cli/README.md +++ b/packages/mcp_dart_cli/README.md @@ -464,9 +464,10 @@ exported tree outside the monorepo git/.pubignore context: dart run tool/validate_cli_publish.dart ``` -Before the matching `mcp_dart` SDK dev package is published, this uses -`pubspec_overrides.yaml` so the CLI can validate against the local SDK checkout. -After publishing the SDK package, validate the CLI against the pub.dev SDK +For follow-up CLI dev releases whose matching `mcp_dart` SDK dev package is not +published yet, this uses `pubspec_overrides.yaml` so the CLI can validate +against the local SDK checkout. The initial `mcp_dart 2.3.0-dev.0` SDK package +is already published, so release validation should also cover the pub.dev SDK version: ```bash diff --git a/test/conformance/2026_rc_client_expected_failures.txt b/test/conformance/2026_rc_client_expected_failures.txt index 8db6db09..0ff0ef54 100644 --- a/test/conformance/2026_rc_client_expected_failures.txt +++ b/test/conformance/2026_rc_client_expected_failures.txt @@ -1,7 +1,10 @@ -# Expected failures for @modelcontextprotocol/conformance@0.2.0-alpha.1 +# Expected failures for @modelcontextprotocol/conformance@0.2.0-alpha.2 # against the 2026 RC/DRAFT client suite. # # Keep this list scenario-based so the baseline is easy to review. When a # scenario turns green, remove it from this file in the same PR as the fix. # -# No expected client failures are currently tracked. +# Upstream alpha.2 fixture gap: this scenario's mock server rejects +# DRAFT-2026-v1 with HTTP 400 and advertises only stable protocol versions. +# Keep it expected-fail until the conformance fixture is draft-capable. +json-schema-ref-no-deref diff --git a/test/conformance/2026_rc_expected_failures.txt b/test/conformance/2026_rc_expected_failures.txt index 4f802aca..cfadd3b2 100644 --- a/test/conformance/2026_rc_expected_failures.txt +++ b/test/conformance/2026_rc_expected_failures.txt @@ -1,4 +1,4 @@ -# Expected failures for @modelcontextprotocol/conformance@0.2.0-alpha.1 +# Expected failures for @modelcontextprotocol/conformance@0.2.0-alpha.2 # against the 2026 RC/DRAFT server suite. # # Keep this list scenario-based so the baseline is easy to review. When a diff --git a/test/conformance/README.md b/test/conformance/README.md index 35032fe3..270359a6 100644 --- a/test/conformance/README.md +++ b/test/conformance/README.md @@ -26,14 +26,14 @@ dart run test/conformance/run_2025_server_conformance.dart ``` The runner starts `mcp_2025_server.dart`, runs -`@modelcontextprotocol/conformance@0.2.0-alpha.1 server --suite all +`@modelcontextprotocol/conformance@0.2.0-alpha.2 server --suite all --spec-version 2025-11-25`, and writes artifacts under `.dart_tool/conformance/2025_server/`. Run the stable client suite from the repository root: ```bash -npx -y @modelcontextprotocol/conformance@0.2.0-alpha.1 client \ +npx -y @modelcontextprotocol/conformance@0.2.0-alpha.2 client \ --command "dart run test/conformance/mcp_2026_rc_client.dart" \ --suite all \ --spec-version 2025-11-25 \ @@ -55,8 +55,9 @@ dart run test/conformance/run_2026_rc_server_conformance.dart The runner starts a local `StreamableMcpServer` with JSON stateless responses enabled, runs the draft server scenarios from -`@modelcontextprotocol/conformance@0.2.0-alpha.1` one by one, and writes per-run -artifacts under `.dart_tool/conformance/2026_rc/`. +`@modelcontextprotocol/conformance@0.2.0-alpha.2` one by one with +`--spec-version DRAFT-2026-v1`, and writes per-run artifacts under +`.dart_tool/conformance/2026_rc/`. Expected failures live in `2026_rc_expected_failures.txt`. When a scenario is fixed, remove it from that file so the baseline remains useful. diff --git a/test/conformance/mcp_2026_rc_client.dart b/test/conformance/mcp_2026_rc_client.dart index 5ac61078..5925500c 100644 --- a/test/conformance/mcp_2026_rc_client.dart +++ b/test/conformance/mcp_2026_rc_client.dart @@ -24,23 +24,37 @@ Future main(List args) async { switch (scenario) { case 'initialize': - await _withClient(serverUrl, protocolVersion: latestProtocolVersion); + await _withClient(serverUrl, protocolVersion: protocolVersion); case 'tools_call': - await _withClient( - serverUrl, - protocolVersion: latestProtocolVersion, - action: (client) async { + if (isStatelessProtocolVersion(protocolVersion)) { + final client = _RawStatelessClient(serverUrl, protocolVersion); + try { + await client.request(Method.serverDiscover, const {}); await client.listTools(); await client.callTool( - const CallToolRequest( - name: 'add_numbers', - arguments: {'a': 2, 'b': 3}, - ), + 'add_numbers', + arguments: const {'a': 2, 'b': 3}, ); - }, - ); + } finally { + client.close(); + } + } else { + await _withClient( + serverUrl, + protocolVersion: protocolVersion, + action: (client) async { + await client.listTools(); + await client.callTool( + const CallToolRequest( + name: 'add_numbers', + arguments: {'a': 2, 'b': 3}, + ), + ); + }, + ); + } case 'elicitation-sep1034-client-defaults': - await _runElicitationDefaults(serverUrl); + await _runElicitationDefaults(serverUrl, protocolVersion); case 'request-metadata': await _runRequestMetadata(serverUrl, protocolVersion); case 'sep-2322-client-request-state': @@ -52,11 +66,11 @@ Future main(List args) async { case 'http-invalid-tool-headers': await _runInvalidToolHeaders(serverUrl, protocolVersion); case 'json-schema-ref-no-deref': - await _runSchemaRefNoDeref(serverUrl, latestProtocolVersion); + await _runSchemaRefNoDeref(serverUrl, protocolVersion); case 'sse-retry': await _withClient( serverUrl, - protocolVersion: latestProtocolVersion, + protocolVersion: protocolVersion, action: (client) async { await client.listTools(); await client.callTool( @@ -141,10 +155,13 @@ Future _withClient( } } -Future _runElicitationDefaults(Uri serverUrl) async { +Future _runElicitationDefaults( + Uri serverUrl, + String protocolVersion, +) async { await _withClient( serverUrl, - protocolVersion: latestProtocolVersion, + protocolVersion: protocolVersion, capabilities: const ClientCapabilities( elicitation: ClientElicitation( form: ClientElicitationForm(applyDefaults: true), @@ -357,7 +374,7 @@ Future _runAuthScenario( Map context, ) async { final provider = _ConformanceOAuthProvider(scenario, context); - final client = _RawOAuthClient(serverUrl, latestProtocolVersion, provider); + final client = _RawOAuthClient(serverUrl, protocolVersion, provider); const allowClientErrorScenarios = { 'auth/resource-mismatch', 'auth/scope-retry-limit', @@ -370,7 +387,9 @@ Future _runAuthScenario( try { await client.start(); - await client.initialize(); + await client.initialize( + maxAuthAttempts: scenario == 'auth/scope-retry-limit' ? 1 : 4, + ); switch (scenario) { case 'auth/authorization-server-migration': @@ -381,7 +400,7 @@ Future _runAuthScenario( await client.callTool('test-tool'); case 'auth/scope-retry-limit': try { - await client.listTools(maxAuthAttempts: 2); + await client.listTools(maxAuthAttempts: 1); } catch (_) { // The scenario only needs to observe a bounded number of auth // retries; the server intentionally never grants the scope. @@ -417,6 +436,14 @@ class _RawStatelessClient { _RawStatelessClient(this.serverUrl, this.protocolVersion); + void close() { + _httpClient.close(force: true); + } + + Future> listTools() { + return request(Method.toolsList, const {}); + } + Future> callTool( String name, { Map arguments = const {}, @@ -679,26 +706,41 @@ class _RawOAuthClient { Future close() => transport.close(); - Future initialize() async { - final id = _nextId++; - await _request( - JsonRpcInitializeRequest( - id: id, - initParams: InitializeRequest( - protocolVersion: protocolVersion, - capabilities: _draftCapabilities, - clientInfo: _clientInfo, + Future initialize({ + int maxAuthAttempts = 4, + }) async { + if (isStatelessProtocolVersion(protocolVersion)) { + await _request( + JsonRpcRequest( + id: _nextId++, + method: Method.serverDiscover, + meta: _requestMeta(), ), - ), - ); - await transport.send(const JsonRpcInitializedNotification()); + maxAuthAttempts: maxAuthAttempts, + ); + } else { + await _request( + JsonRpcInitializeRequest( + id: _nextId++, + initParams: InitializeRequest( + protocolVersion: protocolVersion, + capabilities: _draftCapabilities, + clientInfo: _clientInfo, + ), + ), + ); + await transport.send(const JsonRpcInitializedNotification()); + } } Future> listTools({ int maxAuthAttempts = 4, }) { return _request( - JsonRpcListToolsRequest(id: _nextId++), + JsonRpcListToolsRequest( + id: _nextId++, + meta: _requestMeta(), + ), maxAuthAttempts: maxAuthAttempts, ); } @@ -711,11 +753,23 @@ class _RawOAuthClient { JsonRpcCallToolRequest( id: _nextId++, params: CallToolRequest(name: name).toJson(), + meta: _requestMeta(), ), maxAuthAttempts: maxAuthAttempts, ); } + Map? _requestMeta() { + if (!isStatelessProtocolVersion(protocolVersion)) { + return null; + } + return buildProtocolRequestMeta( + protocolVersion: protocolVersion, + clientInfo: _clientInfo, + clientCapabilities: _draftCapabilities, + ); + } + Future> _request( JsonRpcRequest request, { int maxAuthAttempts = 4, diff --git a/test/conformance/run_2025_server_conformance.dart b/test/conformance/run_2025_server_conformance.dart index ed58e366..b5b17183 100644 --- a/test/conformance/run_2025_server_conformance.dart +++ b/test/conformance/run_2025_server_conformance.dart @@ -3,7 +3,7 @@ import 'dart:convert'; import 'dart:io'; const _defaultConformancePackage = - '@modelcontextprotocol/conformance@0.2.0-alpha.1'; + '@modelcontextprotocol/conformance@0.2.0-alpha.2'; const _defaultTimeout = Duration(seconds: 60); Future main(List args) async { diff --git a/test/conformance/run_2026_rc_client_conformance.dart b/test/conformance/run_2026_rc_client_conformance.dart index 7006e509..3659c4e8 100644 --- a/test/conformance/run_2026_rc_client_conformance.dart +++ b/test/conformance/run_2026_rc_client_conformance.dart @@ -3,7 +3,7 @@ import 'dart:convert'; import 'dart:io'; const _defaultConformancePackage = - '@modelcontextprotocol/conformance@0.2.0-alpha.1'; + '@modelcontextprotocol/conformance@0.2.0-alpha.2'; const _defaultTimeout = Duration(seconds: 30); const _draftClientScenarios = [ diff --git a/test/conformance/run_2026_rc_server_conformance.dart b/test/conformance/run_2026_rc_server_conformance.dart index c5e08eff..bcd5021f 100644 --- a/test/conformance/run_2026_rc_server_conformance.dart +++ b/test/conformance/run_2026_rc_server_conformance.dart @@ -3,7 +3,7 @@ import 'dart:convert'; import 'dart:io'; const _defaultConformancePackage = - '@modelcontextprotocol/conformance@0.2.0-alpha.1'; + '@modelcontextprotocol/conformance@0.2.0-alpha.2'; const _defaultTimeout = Duration(seconds: 25); const _draftServerScenarios = [ @@ -229,6 +229,8 @@ Future<_ScenarioResult> _runScenario({ serverUrl.toString(), '--suite', 'draft', + '--spec-version', + 'DRAFT-2026-v1', '--scenario', scenario, '--verbose',