Skip to content

Commit ec409a2

Browse files
refactor: remove SEP-1699 references and improve test quality
- Remove SEP-1699 references from all code comments - Improve tests to validate priming events instead of skipping them - Update readSSEEvent helper with documentation - Fix notification tests to properly sequence reads after priming
1 parent 54d359e commit ec409a2

File tree

4 files changed

+61
-29
lines changed

4 files changed

+61
-29
lines changed

src/client/streamableHttp.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1010,7 +1010,7 @@ describe('StreamableHTTPClientTransport', () => {
10101010
});
10111011
});
10121012

1013-
describe('SSE retry field handling (SEP-1699)', () => {
1013+
describe('SSE retry field handling', () => {
10141014
beforeEach(() => {
10151015
vi.useFakeTimers();
10161016
(global.fetch as Mock).mockReset();

src/client/streamableHttp.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ export class StreamableHTTPClientTransport implements Transport {
133133
private _reconnectionOptions: StreamableHTTPReconnectionOptions;
134134
private _protocolVersion?: string;
135135
private _hasCompletedAuthFlow = false; // Circuit breaker: detect auth success followed by immediate 401
136-
private _serverRetryMs?: number; // Server-provided retry delay from SSE retry field (SEP-1699)
136+
private _serverRetryMs?: number; // Server-provided retry delay from SSE retry field
137137

138138
onclose?: () => void;
139139
onerror?: (error: Error) => void;
@@ -248,7 +248,7 @@ export class StreamableHTTPClientTransport implements Transport {
248248
* @returns Time to wait in milliseconds before next reconnection attempt
249249
*/
250250
private _getNextReconnectionDelay(attempt: number): number {
251-
// SEP-1699: Use server-provided retry value if available
251+
// Use server-provided retry value if available
252252
if (this._serverRetryMs !== undefined) {
253253
return this._serverRetryMs;
254254
}
@@ -323,7 +323,7 @@ export class StreamableHTTPClientTransport implements Transport {
323323
.pipeThrough(
324324
new EventSourceParserStream({
325325
onRetry: (retryMs: number) => {
326-
// SEP-1699: Capture server-provided retry value for reconnection timing
326+
// Capture server-provided retry value for reconnection timing
327327
this._serverRetryMs = retryMs;
328328
}
329329
})
@@ -355,7 +355,7 @@ export class StreamableHTTPClientTransport implements Transport {
355355
}
356356
}
357357

358-
// SEP-1699: Handle graceful server-side disconnect
358+
// Handle graceful server-side disconnect
359359
// Server may close connection after sending event ID and retry field
360360
if (isReconnectable && this._abortController && !this._abortController.signal.aborted) {
361361
this._scheduleReconnection(

src/server/streamableHttp.test.ts

Lines changed: 52 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -178,13 +178,16 @@ const TEST_MESSAGES = {
178178
};
179179

180180
/**
181-
* Helper to extract text from SSE response
181+
* Helper to extract text from SSE response, skipping priming events.
182+
*
183+
* Priming events have empty data fields and are sent to establish resumability.
184+
* This helper keeps reading until it finds an event with actual content.
185+
*
182186
* Note: Can only be called once per response stream. For multiple reads,
183187
* get the reader manually and read multiple times.
184188
*/
185189
async function readSSEEvent(response: Response): Promise<string> {
186190
const reader = response.body?.getReader();
187-
// Keep reading until we get an event with actual data (skip priming events)
188191
while (true) {
189192
const { value, done } = await reader!.read();
190193
if (done) break;
@@ -531,17 +534,22 @@ describe('StreamableHTTPServerTransport', () => {
531534
expect(sseResponse.status).toBe(200);
532535
const reader = sseResponse.body?.getReader();
533536

534-
// Skip the priming event
535-
await reader!.read();
537+
// Read and validate the priming event
538+
const { value: primingValue } = await reader!.read();
539+
const primingText = new TextDecoder().decode(primingValue);
540+
expect(primingText).toContain('id: ');
541+
const primingIdMatch = primingText.match(/id: ([^\n]+)/);
542+
expect(primingIdMatch).toBeTruthy();
543+
const primingEventId = primingIdMatch![1];
536544

537-
// Send multiple notifications
545+
// Send a notification
538546
const notification1: JSONRPCMessage = {
539547
jsonrpc: '2.0',
540548
method: 'notifications/message',
541549
params: { level: 'info', data: 'First notification' }
542550
};
543551

544-
// Just send one and verify it comes through - then the stream should stay open
552+
// Send and verify it comes through - then the stream should stay open
545553
await transport.send(notification1);
546554

547555
const { value, done } = await reader!.read();
@@ -1358,6 +1366,14 @@ describe('StreamableHTTPServerTransport with resumability', () => {
13581366
expect(sseResponse.status).toBe(200);
13591367
expect(sseResponse.headers.get('content-type')).toBe('text/event-stream');
13601368

1369+
// Read from the stream
1370+
const reader = sseResponse.body?.getReader();
1371+
1372+
// Read and validate the priming event
1373+
const { value: primingValue } = await reader!.read();
1374+
const primingText = new TextDecoder().decode(primingValue);
1375+
expect(primingText).toContain('id: ');
1376+
13611377
// Send a notification that should be stored with an event ID
13621378
const notification: JSONRPCMessage = {
13631379
jsonrpc: '2.0',
@@ -1368,12 +1384,7 @@ describe('StreamableHTTPServerTransport with resumability', () => {
13681384
// Send the notification via transport
13691385
await transport.send(notification);
13701386

1371-
// Read from the stream and verify we got the notification with an event ID
1372-
const reader = sseResponse.body?.getReader();
1373-
1374-
// Skip the priming event
1375-
await reader!.read();
1376-
1387+
// Read the notification
13771388
const { value } = await reader!.read();
13781389
const text = new TextDecoder().decode(value);
13791390

@@ -1544,8 +1555,8 @@ describe('StreamableHTTPServerTransport in stateless mode', () => {
15441555
});
15451556
});
15461557

1547-
// Test SSE priming events (SEP-1699)
1548-
describe('StreamableHTTPServerTransport SSE priming events (SEP-1699)', () => {
1558+
// Test SSE priming events
1559+
describe('StreamableHTTPServerTransport SSE priming events', () => {
15491560
let server: Server;
15501561
let transport: StreamableHTTPServerTransport;
15511562
let baseUrl: URL;
@@ -1623,13 +1634,34 @@ describe('StreamableHTTPServerTransport SSE priming events (SEP-1699)', () => {
16231634

16241635
// Read the priming event
16251636
const reader = sseResponse.body?.getReader();
1626-
const { value } = await reader!.read();
1627-
const text = new TextDecoder().decode(value);
1637+
const { value: primingValue } = await reader!.read();
1638+
const primingText = new TextDecoder().decode(primingValue);
16281639

1629-
// Should have id field for resumability but NOT retry field
1630-
expect(text).toContain('id: ');
1631-
expect(text).toContain('data: ');
1632-
expect(text).not.toContain('retry:');
1640+
// Priming event should have id field for resumability but NOT retry field
1641+
expect(primingText).toContain('id: ');
1642+
expect(primingText).toContain('data: ');
1643+
expect(primingText).not.toContain('retry:');
1644+
1645+
// Extract priming event ID
1646+
const primingIdMatch = primingText.match(/id: ([^\n]+)/);
1647+
expect(primingIdMatch).toBeTruthy();
1648+
const primingEventId = primingIdMatch![1];
1649+
1650+
// Send a notification
1651+
const notification: JSONRPCMessage = {
1652+
jsonrpc: '2.0',
1653+
method: 'notifications/message',
1654+
params: { level: 'info', data: 'Test notification' }
1655+
};
1656+
await transport.send(notification);
1657+
1658+
// Read the notification
1659+
const { value: notifValue } = await reader!.read();
1660+
const notifText = new TextDecoder().decode(notifValue);
1661+
1662+
// Notification should have content and no retry field
1663+
expect(notifText).toContain('Test notification');
1664+
expect(notifText).not.toContain('retry:');
16331665
});
16341666

16351667
it('should include event ID in priming event for resumability', async () => {

src/server/streamableHttp.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -111,8 +111,8 @@ export interface StreamableHTTPServerTransportOptions {
111111

112112
/**
113113
* Retry interval in milliseconds to suggest to clients in SSE retry field.
114-
* When set, the server will send a retry field in SSE events to control
115-
* client reconnection timing (SEP-1699).
114+
* When set, the server will send a retry field in SSE priming events to control
115+
* client reconnection timing for polling behavior.
116116
*/
117117
retryInterval?: number;
118118
}
@@ -329,7 +329,7 @@ export class StreamableHTTPServerTransport implements Transport {
329329
// otherwise the client will just wait for the first message
330330
res.writeHead(200, headers).flushHeaders();
331331

332-
// SEP-1699: Send priming event with id and empty data to establish resumption capability
332+
// Send priming event to establish resumption capability
333333
// This primes the client's Last-Event-ID for reconnection
334334
const primingEventId = this._eventStore
335335
? await this._eventStore.storeEvent(this._standaloneSseStreamId, {} as JSONRPCMessage)
@@ -570,7 +570,7 @@ export class StreamableHTTPServerTransport implements Transport {
570570

571571
res.writeHead(200, headers);
572572

573-
// SEP-1699: Send priming event with id and empty data to establish resumption capability
573+
// Send priming event to establish resumption capability
574574
// This primes the client's Last-Event-ID for reconnection
575575
const primingEventId = this._eventStore
576576
? await this._eventStore.storeEvent(streamId, {} as JSONRPCMessage)

0 commit comments

Comments
 (0)