Restore streaming read chunk limit

This commit is contained in:
Jeremy Rose
2026-06-09 17:31:37 -07:00
parent 2962e8f5d0
commit 2a0c10537d
11 changed files with 67 additions and 17 deletions

View File

@@ -916,7 +916,7 @@
"type": "string"
},
"maxBytes": {
"description": "Requested upper bound. Values above the server limit are capped.",
"description": "Requested upper bound. Values above `maxChunkBytes` are capped.",
"format": "uint32",
"minimum": 0.0,
"type": [

View File

@@ -9640,6 +9640,17 @@
"FsReadFileOpenResponse": {
"$schema": "http://json-schema.org/draft-07/schema#",
"description": "Successful response for `fs/readFile/open`.",
"properties": {
"maxChunkBytes": {
"description": "Maximum decoded bytes returned by one read.\n\nClients may use this value to size reusable read buffers.",
"format": "uint32",
"minimum": 0.0,
"type": "integer"
}
},
"required": [
"maxChunkBytes"
],
"title": "FsReadFileOpenResponse",
"type": "object"
},
@@ -9670,7 +9681,7 @@
"type": "string"
},
"maxBytes": {
"description": "Requested upper bound. Values above the server limit are capped.",
"description": "Requested upper bound. Values above `maxChunkBytes` are capped.",
"format": "uint32",
"minimum": 0.0,
"type": [

View File

@@ -5982,6 +5982,17 @@
"FsReadFileOpenResponse": {
"$schema": "http://json-schema.org/draft-07/schema#",
"description": "Successful response for `fs/readFile/open`.",
"properties": {
"maxChunkBytes": {
"description": "Maximum decoded bytes returned by one read.\n\nClients may use this value to size reusable read buffers.",
"format": "uint32",
"minimum": 0.0,
"type": "integer"
}
},
"required": [
"maxChunkBytes"
],
"title": "FsReadFileOpenResponse",
"type": "object"
},
@@ -6012,7 +6023,7 @@
"type": "string"
},
"maxBytes": {
"description": "Requested upper bound. Values above the server limit are capped.",
"description": "Requested upper bound. Values above `maxChunkBytes` are capped.",
"format": "uint32",
"minimum": 0.0,
"type": [

View File

@@ -1,6 +1,17 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"description": "Successful response for `fs/readFile/open`.",
"properties": {
"maxChunkBytes": {
"description": "Maximum decoded bytes returned by one read.\n\nClients may use this value to size reusable read buffers.",
"format": "uint32",
"minimum": 0.0,
"type": "integer"
}
},
"required": [
"maxChunkBytes"
],
"title": "FsReadFileOpenResponse",
"type": "object"
}

View File

@@ -6,7 +6,7 @@
"type": "string"
},
"maxBytes": {
"description": "Requested upper bound. Values above the server limit are capped.",
"description": "Requested upper bound. Values above `maxChunkBytes` are capped.",
"format": "uint32",
"minimum": 0.0,
"type": [

View File

@@ -5,4 +5,10 @@
/**
* Successful response for `fs/readFile/open`.
*/
export type FsReadFileOpenResponse = Record<string, never>;
export type FsReadFileOpenResponse = {
/**
* Maximum decoded bytes returned by one read.
*
* Clients may use this value to size reusable read buffers.
*/
maxChunkBytes: number, };

View File

@@ -7,6 +7,6 @@
*/
export type FsReadFileReadParams = { handleId: string, offset: number,
/**
* Requested upper bound. Values above the server limit are capped.
* Requested upper bound. Values above `maxChunkBytes` are capped.
*/
maxBytes?: number | null, };

View File

@@ -54,7 +54,12 @@ pub struct FsReadFileOpenParams {
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct FsReadFileOpenResponse {}
pub struct FsReadFileOpenResponse {
/// Maximum decoded bytes returned by one read.
///
/// Clients may use this value to size reusable read buffers.
pub max_chunk_bytes: u32,
}
/// Read a bounded chunk from an open file at an absolute byte offset.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
@@ -64,7 +69,7 @@ pub struct FsReadFileReadParams {
pub handle_id: String,
#[ts(type = "number")]
pub offset: u64,
/// Requested upper bound. Values above the server limit are capped.
/// Requested upper bound. Values above `maxChunkBytes` are capped.
#[ts(optional = nullable)]
pub max_bytes: Option<u32>,
}

View File

@@ -133,7 +133,8 @@ impl FsRequestProcessor {
connection_id: ConnectionId,
params: FsReadFileOpenParams,
) -> Result<FsReadFileOpenResponse, JSONRPCErrorError> {
self.handles(connection_id)
let max_chunk_bytes = self
.handles(connection_id)
.await
.open_read(
self.file_system()?,
@@ -143,7 +144,10 @@ impl FsRequestProcessor {
)
.await
.map_err(map_file_handle_error)?;
Ok(FsReadFileOpenResponse {})
Ok(FsReadFileOpenResponse {
max_chunk_bytes: u32::try_from(max_chunk_bytes)
.map_err(|_| internal_error("file read chunk limit exceeds u32"))?,
})
}
pub(crate) async fn read_file_read(

View File

@@ -421,7 +421,7 @@ async fn fs_streaming_read_supports_stat_and_positional_reads() -> Result<()> {
)
.await??,
)?;
assert_eq!(open, FsReadFileOpenResponse {});
assert!(open.max_chunk_bytes >= 4);
let wrong_type_id = mcp
.send_raw_request(

View File

@@ -30,13 +30,16 @@ Opens a file for positional reads.
Response:
```json
{}
{
"maxChunkBytes": 262144
}
```
### `fs/readFile/read`
Reads at most `maxBytes` starting at the absolute byte offset. If `maxBytes`
is omitted or exceeds the server's read limit, the server uses that limit.
is omitted or exceeds `maxChunkBytes`, the server uses `maxChunkBytes`.
Clients may use `maxChunkBytes` to size a reusable read buffer.
```json
{
@@ -182,10 +185,9 @@ Response:
concurrently.
- Reads are positional and do not maintain a server-side cursor.
- Writes are sequential appends.
- Each read transfers at most the server's read limit.
- Each write transfers at most the `maxChunkBytes` returned by
`fs/writeFile/open`.
- Both limits are currently 262144 bytes.
- Each read or write transfers at most the `maxChunkBytes` returned by its
corresponding open operation.
- `maxChunkBytes` is currently 262144 bytes.
- Backpressure comes from awaiting bounded read and write responses. Clients
should use a bounded pipeline of up to two read requests to hide transport
round-trip latency without accumulating unbounded response data.