[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

@@ -0,0 +1,73 @@
import { LoggingLevel } from "@modelcontextprotocol/sdk/types.js";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
// Map session ID to the interval for sending logging messages to the client
const logsUpdateIntervals: Map<string | undefined, NodeJS.Timeout | undefined> =
new Map<string | undefined, NodeJS.Timeout | undefined>();
/**
* Initiates a simulated logging process by sending random log messages to the client at a
* fixed interval. Each log message contains a random logging level and optional session ID.
*
* @param {McpServer} server - The server instance responsible for handling the logging messages.
* @param {string | undefined} sessionId - An optional identifier for the session. If provided,
* the session ID will be appended to log messages.
*/
export const beginSimulatedLogging = (
server: McpServer,
sessionId: string | undefined
) => {
const maybeAppendSessionId = sessionId ? ` - SessionId ${sessionId}` : "";
const messages: { level: LoggingLevel; data: string }[] = [
{ level: "debug", data: `Debug-level message${maybeAppendSessionId}` },
{ level: "info", data: `Info-level message${maybeAppendSessionId}` },
{ level: "notice", data: `Notice-level message${maybeAppendSessionId}` },
{
level: "warning",
data: `Warning-level message${maybeAppendSessionId}`,
},
{ level: "error", data: `Error-level message${maybeAppendSessionId}` },
{
level: "critical",
data: `Critical-level message${maybeAppendSessionId}`,
},
{ level: "alert", data: `Alert level-message${maybeAppendSessionId}` },
{
level: "emergency",
data: `Emergency-level message${maybeAppendSessionId}`,
},
];
// Set the interval to send logging messages to this client
if (!logsUpdateIntervals.has(sessionId)) {
logsUpdateIntervals.set(
sessionId,
setInterval(async () => {
// By using the `sendLoggingMessage` function to send the message, we
// ensure that the client's chosen logging level will be respected
await server.sendLoggingMessage(
messages[Math.floor(Math.random() * messages.length)],
sessionId
);
}, 15000)
);
}
};
/**
* Stops the simulated logging process for a given session.
*
* This function halts the periodic logging updates associated with the specified
* session ID by clearing the interval and removing the session's tracking
* reference. Session ID can be undefined for stdio.
*
* @param {string} [sessionId] - The optional unique identifier of the session.
*/
export const stopSimulatedLogging = (sessionId?: string) => {
// Remove active intervals
if (logsUpdateIntervals.has(sessionId)) {
const logsUpdateInterval = logsUpdateIntervals.get(sessionId);
clearInterval(logsUpdateInterval);
logsUpdateIntervals.delete(sessionId);
}
};