[WIP] Refactor everything server to be more modular and use recommended APIs.

Adding simulated logging and refactoring subscriptions to not need to track transports

* Updated architecture.md

* In server/index.ts
  - remove import of Transport
  - import beginSimulatedLogging and stopSimulatedLogging
  - in clientConnected()
    - change argument to sessionId? instead of transport
    - add call to beginSimulatedLogging
    - send server and sessionId to beginSimulatedResourceUpdates and beginSimulatedLogging
  - in cleanup()
    - add call to stopSimulatedLogging passing sessionId

* Added server/logging.ts
  - Initialize logsUpdateIntervals to Map session ID to the interval for sending logging messages to the client
  - in beginSimulatedLogging()
    - create an array of logging meesages, customized with the sessionId if present
    - if the interval for the sessionId hasn't been set, create one, calling server.sendLoggingMessage with a random message to the client each time the interval elapses
  - in stopSimulatedLogging()
    - if a logging interval exists for the sessionId, clear it and remove it

* In subscriptions.ts
  - remove import of Transport
  - remove transports map
  - in beginSimulatedResourceUpdates()
    - change arguments to server and sessionId
    - check for the subsUpdateInterval for the session
    - remove all transport storage and interaction
    - instead use the server to send the notification
 - in stopSimulatedResourceUpdates()
   - remove management of transports map

* In sse.ts and streamableHttp.ts
  - when calling clientConnected, pass sessionId instead of transport

* In stdio.ts,
- when calling clientConnected, pass nothing instead of transport

* In subscriptions.ts
  - updated inline doc
This commit is contained in:
cliffhall
2025-12-07 19:32:18 -05:00
parent 8559fbd5a4
commit 16ed05957c
8 changed files with 130 additions and 57 deletions

View File

@@ -1,5 +1,4 @@
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { Transport } from "@modelcontextprotocol/sdk/shared/transport.js";
import {
SubscribeRequestSchema,
UnsubscribeRequestSchema,
@@ -11,12 +10,6 @@ const subscriptions: Map<string, Set<string | undefined>> = new Map<
Set<string | undefined>
>();
// Track transport by session id
const transports: Map<string | undefined, Transport> = new Map<
string | undefined,
Transport
>();
// Interval to send notifications to subscribers
const subsUpdateIntervals: Map<string | undefined, NodeJS.Timeout | undefined> =
new Map<string | undefined, NodeJS.Timeout | undefined>();
@@ -105,35 +98,28 @@ export const setSubscriptionHandlers = (server: McpServer) => {
/**
* Starts the process of simulating resource updates and sending server notifications
* to subscribed clients at regular intervals. If the update interval is already active,
* invoking this function will not start another interval.
* to the client for the resources they are subscribed to. If the update interval is
* already active, invoking this function will not start another interval.
*
* Note that tracking and sending updates on the transport of the subscriber allows for
* multiple clients to be connected and independently receive only updates about their
* own subscriptions. Had we used `server.notification` instead, all clients would
* receive updates for all subscriptions.
*
* @param {Transport} transport - The transport to the subscriber
* @param server
* @param sessionId
*/
export const beginSimulatedResourceUpdates = (transport: Transport) => {
const sessionId = transport?.sessionId;
if (!transports.has(sessionId)) {
// Store the transport
transports.set(sessionId, transport);
// Set the interval to send notifications to the subscribers
export const beginSimulatedResourceUpdates = (
server: McpServer,
sessionId: string | undefined
) => {
if (!subsUpdateIntervals.has(sessionId)) {
// Set the interval to send resource update notifications to this client
subsUpdateIntervals.set(
sessionId,
setInterval(async () => {
// Send notifications to all subscribers for each URI
// Search all URIs for ones this client is subscribed to
for (const uri of subscriptions.keys()) {
const subscribers = subscriptions.get(uri) as Set<string | undefined>;
// Get the transport for the subscriber and send the notification
// If this client is subscribed, send the notification
if (subscribers.has(sessionId)) {
const transport = transports.get(sessionId) as Transport;
await transport.send({
jsonrpc: "2.0",
await server.server.notification({
method: "notifications/resources/updated",
params: { uri },
});
@@ -162,8 +148,4 @@ export const stopSimulatedResourceUpdates = (sessionId?: string) => {
clearInterval(subsUpdateInterval);
subsUpdateIntervals.delete(sessionId);
}
// Remove transport for the session
if (transports.has(sessionId)) {
transports.delete(sessionId);
}
};