Skip to content

Commit 307e536

Browse files
committed
feat: add custom reconnection scheduler for non-persistent environments
Resolves #1162 Adds support for custom reconnection scheduling to StreamableHTTPClientTransport, enabling proper reconnection handling in non-persistent environments like serverless functions, mobile apps, and desktop applications. Changes: - Add ReconnectionScheduler type and econnectionScheduler option to transport - Refactor _scheduleReconnection to support custom scheduling logic - Maintain backward compatibility with default setTimeout-based scheduling - Add comprehensive test coverage for custom scheduler scenarios - Add detailed documentation with practical examples for: - Serverless environments (immediate and deferred reconnection) - Mobile apps (platform-specific background scheduling) - Desktop apps (power management and sleep/wake handling) The custom scheduler receives three parameters: - reconnect: callback to initiate reconnection - delay: suggested delay in milliseconds - attemptCount: current reconnection attempt count When no custom scheduler is provided, the default setTimeout behavior is preserved, ensuring full backward compatibility
1 parent e6c71bb commit 307e536

3 files changed

Lines changed: 455 additions & 5 deletions

File tree

README.md

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
- [Eliciting User Input](#eliciting-user-input)
2929
- [Writing MCP Clients](#writing-mcp-clients)
3030
- [Proxy Authorization Requests Upstream](#proxy-authorization-requests-upstream)
31+
- [Custom Reconnection Scheduling for Non-Persistent Environments](#custom-reconnection-scheduling-for-non-persistent-environments)
3132
- [Backwards Compatibility](#backwards-compatibility)
3233
- [Documentation](#documentation)
3334
- [Contributing](#contributing)
@@ -1480,6 +1481,175 @@ This setup allows you to:
14801481
- Provide custom documentation URLs
14811482
- Maintain control over the OAuth flow while delegating to an external provider
14821483

1484+
### Custom Reconnection Scheduling for Non-Persistent Environments
1485+
1486+
By default, the Streamable HTTP client transport uses `setTimeout` to schedule automatic reconnections after connection failures. However, this approach doesn't work well in non-persistent environments like serverless functions, mobile apps, or desktop applications that may be suspended.
1487+
1488+
The SDK allows you to provide a custom reconnection scheduler to handle these scenarios:
1489+
1490+
#### Use Cases
1491+
1492+
- **Serverless Functions**: Reconnect immediately on the next function invocation instead of waiting for a timer
1493+
- **Mobile Apps**: Use platform-specific background scheduling (iOS Background Fetch, Android WorkManager)
1494+
- **Desktop Apps**: Handle sleep/wake cycles with OS-aware scheduling
1495+
- **Edge Functions**: Optimize for short-lived execution contexts
1496+
1497+
#### API
1498+
1499+
```typescript
1500+
type ReconnectionScheduler = (
1501+
reconnect: () => void, // Function to call to initiate reconnection
1502+
delay: number, // Suggested delay in milliseconds
1503+
attemptCount: number // Current reconnection attempt count (0-indexed)
1504+
) => void;
1505+
```
1506+
1507+
#### Example: Serverless Environment (Immediate Reconnection)
1508+
1509+
```typescript
1510+
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
1511+
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
1512+
1513+
// Serverless scheduler: reconnect immediately without setTimeout
1514+
const serverlessScheduler = (reconnect, delay, attemptCount) => {
1515+
// In serverless, timers don't persist across invocations
1516+
// Just reconnect immediately - the function will handle retries on next invocation
1517+
reconnect();
1518+
};
1519+
1520+
const transport = new StreamableHTTPClientTransport(
1521+
new URL('https://api.example.com/mcp'),
1522+
{
1523+
reconnectionOptions: {
1524+
initialReconnectionDelay: 1000,
1525+
maxReconnectionDelay: 30000,
1526+
reconnectionDelayGrowFactor: 2,
1527+
maxRetries: 5
1528+
},
1529+
reconnectionScheduler: serverlessScheduler
1530+
}
1531+
);
1532+
1533+
const client = new Client({
1534+
name: 'serverless-client',
1535+
version: '1.0.0'
1536+
});
1537+
1538+
await client.connect(transport);
1539+
```
1540+
1541+
#### Example: Serverless with Deferred Reconnection
1542+
1543+
For true serverless environments where the function may terminate before reconnection, you can store the reconnection callback for the next invocation:
1544+
1545+
```typescript
1546+
// Global or persistent storage for reconnection callback
1547+
let storedReconnect: (() => void) | undefined;
1548+
1549+
const deferredScheduler = (reconnect, delay, attemptCount) => {
1550+
// Store the reconnect callback instead of calling it
1551+
// In a real app, persist this to a database or queue
1552+
storedReconnect = reconnect;
1553+
console.log(`Reconnection scheduled for next invocation (attempt ${attemptCount})`);
1554+
};
1555+
1556+
const transport = new StreamableHTTPClientTransport(
1557+
new URL('https://api.example.com/mcp'),
1558+
{
1559+
reconnectionScheduler: deferredScheduler
1560+
}
1561+
);
1562+
1563+
// Later, on next function invocation:
1564+
if (storedReconnect) {
1565+
console.log('Triggering stored reconnection');
1566+
storedReconnect();
1567+
storedReconnect = undefined;
1568+
}
1569+
```
1570+
1571+
#### Example: Mobile App with Platform Scheduling
1572+
1573+
```typescript
1574+
// iOS/Android mobile app using platform-specific background tasks
1575+
const mobileScheduler = (reconnect, delay, attemptCount) => {
1576+
if (attemptCount > 3) {
1577+
console.log('Too many reconnection attempts, giving up');
1578+
return;
1579+
}
1580+
1581+
// Use native background task API (pseudocode)
1582+
BackgroundTaskManager.schedule({
1583+
taskId: 'mcp-reconnect',
1584+
delay: delay,
1585+
callback: () => {
1586+
console.log(`Background reconnection attempt ${attemptCount}`);
1587+
reconnect();
1588+
}
1589+
});
1590+
};
1591+
1592+
const transport = new StreamableHTTPClientTransport(
1593+
new URL('https://api.example.com/mcp'),
1594+
{
1595+
reconnectionOptions: {
1596+
initialReconnectionDelay: 5000,
1597+
maxReconnectionDelay: 60000,
1598+
reconnectionDelayGrowFactor: 1.5,
1599+
maxRetries: 3
1600+
},
1601+
reconnectionScheduler: mobileScheduler
1602+
}
1603+
);
1604+
```
1605+
1606+
#### Example: Desktop App with Power Management
1607+
1608+
```typescript
1609+
// Desktop app that respects system sleep/wake cycles
1610+
const desktopScheduler = (reconnect, delay, attemptCount) => {
1611+
const timeoutId = setTimeout(() => {
1612+
// Check if system was sleeping
1613+
const actualElapsed = Date.now() - scheduleTime;
1614+
if (actualElapsed > delay * 1.5) {
1615+
console.log('System was likely sleeping, reconnecting immediately');
1616+
reconnect();
1617+
} else {
1618+
reconnect();
1619+
}
1620+
}, delay);
1621+
1622+
const scheduleTime = Date.now();
1623+
1624+
// Handle system wake events (pseudocode)
1625+
powerMonitor.on('resume', () => {
1626+
clearTimeout(timeoutId);
1627+
console.log('System resumed, reconnecting immediately');
1628+
reconnect();
1629+
});
1630+
};
1631+
1632+
const transport = new StreamableHTTPClientTransport(
1633+
new URL('https://api.example.com/mcp'),
1634+
{
1635+
reconnectionScheduler: desktopScheduler
1636+
}
1637+
);
1638+
```
1639+
1640+
#### Default Behavior
1641+
1642+
If no custom scheduler is provided, the transport uses the default `setTimeout`-based scheduler:
1643+
1644+
```typescript
1645+
// Default scheduler (built-in)
1646+
const defaultScheduler = (reconnect, delay, attemptCount) => {
1647+
setTimeout(reconnect, delay);
1648+
};
1649+
```
1650+
1651+
This default scheduler works well for traditional long-running applications but may not be suitable for environments with lifecycle constraints.
1652+
14831653
### Backwards Compatibility
14841654

14851655
Clients and servers with StreamableHttp transport can maintain [backwards compatibility](https://modelcontextprotocol.io/specification/2025-03-26/basic/transports#backwards-compatibility) with the deprecated HTTP+SSE transport (from protocol version 2024-11-05) as follows

0 commit comments

Comments
 (0)