Skip to content
Closed
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
10 changes: 6 additions & 4 deletions lib/src/shared/protocol.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ import 'transport.dart';
final _logger = Logger("mcp_dart.shared.protocol");

bool _isProgressToken(Object? token) =>
token is String || (token is num && token.isFinite);
token is String ||
token is int ||
(token is double && token.isFinite && token == token.truncateToDouble());

const Set<String> _statelessCacheableResultMethods = {
Method.toolsList,
Expand Down Expand Up @@ -204,7 +206,7 @@ class RequestHandlerExtra {
if (!_isProgressToken(progressToken)) {
_logger.warn(
"Invalid progressToken type: ${progressToken.runtimeType}. "
"Expected string or finite number.",
"Expected string or integer.",
);
return;
}
Expand Down Expand Up @@ -1428,7 +1430,7 @@ abstract class Protocol {
_onerror(
ArgumentError(
"Received invalid progressToken: $progressToken. "
"Expected string or finite number.",
"Expected string or integer.",
),
);
return;
Expand Down Expand Up @@ -1650,7 +1652,7 @@ abstract class Protocol {
if (!_isProgressToken(requestedProgressToken)) {
return Future.error(
ArgumentError(
'progressToken must be a string or finite number when '
'progressToken must be a string or integer when '
'onprogress is set.',
),
);
Expand Down
4 changes: 2 additions & 2 deletions lib/src/shared/transport.dart
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,14 @@ abstract class Transport {
}

/// Optional capability for transports that can preserve JSON-RPC request IDs
/// with their full MCP shape (string or finite number) for request/stream
/// with their full MCP shape (string or integer) for request/stream
/// correlation.
///
/// Existing custom transports can keep implementing [Transport.send] with
/// `int? relatedRequestId`. Transports that need to route messages by string
/// request IDs should also implement this interface.
abstract class RequestIdAwareTransport {
/// Sends a JSON-RPC message while preserving a string-or-number request ID.
/// Sends a JSON-RPC message while preserving a string-or-integer request ID.
Future<void> sendWithRequestId(
JsonRpcMessage message, {
RequestId? relatedRequestId,
Expand Down
27 changes: 16 additions & 11 deletions lib/src/types/json_rpc.dart
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ typedef ProgressToken = dynamic;

/// Parses a wire progress token.
///
/// MCP progress tokens are JSON strings or finite numbers. Reject malformed wire
/// MCP progress tokens are JSON strings or integers. Reject malformed wire
/// shapes at decode boundaries instead of allowing dynamic values to leak into
/// higher-level protocol code.
ProgressToken parseProgressToken(
Expand All @@ -168,11 +168,12 @@ ProgressToken parseProgressToken(
if (value is String) {
return value;
}
if (value is num && value.isFinite) {
return value;
final integer = readOptionalInteger(value, fieldName);
if (integer != null) {
return integer;
}
throw FormatException(
'Invalid $fieldName: expected string or finite number, '
'Invalid $fieldName: expected string or integer, '
'got ${value.runtimeType}',
);
}
Expand All @@ -185,18 +186,19 @@ typedef RequestId = dynamic;

/// Parses a JSON-RPC request identifier.
///
/// JSON-RPC/MCP request IDs are JSON strings or finite numbers for SDK request
/// JSON-RPC/MCP request IDs are JSON strings or integers for SDK request
/// boundaries. Notifications omit the `id` member entirely, and responses may
/// omit the `id` member for JSON-RPC error cases.
RequestId parseRequestId(Object? value, {String fieldName = 'id'}) {
if (value is String) {
return value;
}
if (value is num && value.isFinite) {
return value;
final integer = readOptionalInteger(value, fieldName);
if (integer != null) {
return integer;
}
throw FormatException(
'Invalid $fieldName: expected string or finite number, '
'Invalid $fieldName: expected string or integer, '
'got ${value.runtimeType}',
);
}
Expand Down Expand Up @@ -293,8 +295,8 @@ void validateMetaKeyName(String key, {String fieldName = '_meta'}) {

/// Validates request metadata that can affect protocol behavior.
///
/// `_meta.progressToken` is an MCP wire token and must be a string or finite
/// number when present. [validateKeys] opts in to the MCP 2026 `_meta`
/// `_meta.progressToken` is an MCP wire token and must be a string or integer
/// when present. [validateKeys] opts in to the MCP 2026 `_meta`
/// key-name grammar without changing stable/legacy request parsing.
Map<String, dynamic>? validateRequestMeta(
Map<String, dynamic>? meta, {
Expand Down Expand Up @@ -508,7 +510,10 @@ class JsonRpcRequest extends JsonRpcMessage {
if (params != null)
...readJsonObject(params, 'JsonRpcRequest.params'),
if (meta != null)
'_meta': readJsonObject(meta, 'JsonRpcRequest._meta'),
'_meta': readJsonObject(
validateRequestMeta(meta),
'JsonRpcRequest._meta',
),
},
};
}
Expand Down
2 changes: 1 addition & 1 deletion packages/mcp_dart_cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ The CLI supports `sampling/createMessage` requests from the server (often used b

### Conformance

Run built-in fixture checks, MCP 2025-11-25 spec-critical checks, and deterministic fuzz checks for MCP protocol edge cases. The fixture suite covers JSON-RPC malformed-message handling, string and numeric request IDs, string and numeric progress tokens, and advertised protocol-version support. The spec suite covers raw-wire lifecycle, capability, elicitation, task-metadata, and progress-token dispatch and negative cases.
Run built-in fixture checks, MCP 2025-11-25 spec-critical checks, and deterministic fuzz checks for MCP protocol edge cases. The fixture suite covers JSON-RPC malformed-message handling, string and integer request IDs, string and integer progress tokens, fractional ID/token rejection, and advertised protocol-version support. The spec suite covers raw-wire lifecycle, capability, elicitation, task-metadata, and progress-token dispatch and negative cases.

```bash
# Run all built-in fixture cases
Expand Down
Loading