Merge pull request #2669 from cliffhall/auto-log-level-support-in-everything-server

Everything server - fix regression + auto log level handling support
This commit is contained in:
Ola Hungerford 2025-09-02 22:05:12 -07:00 • committed by GitHub
commit e322daf123
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 107 additions and 107 deletions

3
.gitignore vendored
View file

@ -122,6 +122,9 @@ dist
# Stores VSCode versions used for testing VSCode extensions # Stores VSCode versions used for testing VSCode extensions
.vscode-test .vscode-test
# Jetbrains IDEs
.idea/
# yarn v2 # yarn v2
.yarn/cache .yarn/cache
.yarn/unplugged .yarn/unplugged

8
package-lock.json generated
View file

@ -5818,7 +5818,7 @@
"version": "0.6.2", "version": "0.6.2",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@modelcontextprotocol/sdk": "^1.17.4", "@modelcontextprotocol/sdk": "^1.17.5",
"express": "^4.21.1", "express": "^4.21.1",
"zod": "^3.23.8", "zod": "^3.23.8",
"zod-to-json-schema": "^3.23.5" "zod-to-json-schema": "^3.23.5"
@ -5833,9 +5833,9 @@
} }
}, },
"src/everything/node_modules/@modelcontextprotocol/sdk": { "src/everything/node_modules/@modelcontextprotocol/sdk": {
"version": "1.17.4", "version": "1.17.5",
"resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.17.4.tgz", "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.17.5.tgz",
"integrity": "sha512-zq24hfuAmmlNZvik0FLI58uE5sriN0WWsQzIlYnzSuKDAHFqJtBFrl/LfB1NLgJT5Y7dEBzaX4yAKqOPrcetaw==", "integrity": "sha512-QakrKIGniGuRVfWBdMsDea/dx1PNE739QJ7gCM41s9q+qaCYTHCdsIBXQVVXry3mfWAiaM9kT22Hyz53Uw8mfg==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"ajv": "^6.12.6", "ajv": "^6.12.6",

View file

@ -14,12 +14,11 @@ import {
ReadResourceRequestSchema, ReadResourceRequestSchema,
Resource, Resource,
RootsListChangedNotificationSchema, RootsListChangedNotificationSchema,
SetLevelRequestSchema,
SubscribeRequestSchema, SubscribeRequestSchema,
Tool, Tool,
ToolSchema, ToolSchema,
UnsubscribeRequestSchema, UnsubscribeRequestSchema,
type Root, type Root
} from "@modelcontextprotocol/sdk/types.js"; } from "@modelcontextprotocol/sdk/types.js";
import { z } from "zod"; import { z } from "zod";
import { zodToJsonSchema } from "zod-to-json-schema"; import { zodToJsonSchema } from "zod-to-json-schema";
@ -174,7 +173,6 @@ export const createServer = () => {
let subsUpdateInterval: NodeJS.Timeout | undefined; let subsUpdateInterval: NodeJS.Timeout | undefined;
let stdErrUpdateInterval: NodeJS.Timeout | undefined; let stdErrUpdateInterval: NodeJS.Timeout | undefined;
let logLevel: LoggingLevel = "debug";
let logsUpdateInterval: NodeJS.Timeout | undefined; let logsUpdateInterval: NodeJS.Timeout | undefined;
// Store client capabilities // Store client capabilities
let clientCapabilities: ClientCapabilities | undefined; let clientCapabilities: ClientCapabilities | undefined;
@ -182,50 +180,43 @@ export const createServer = () => {
// Roots state management // Roots state management
let currentRoots: Root[] = []; let currentRoots: Root[] = [];
let clientSupportsRoots = false; let clientSupportsRoots = false;
const messages = [ let sessionId: string | undefined;
{ level: "debug", data: "Debug-level message" },
{ level: "info", data: "Info-level message" },
{ level: "notice", data: "Notice-level message" },
{ level: "warning", data: "Warning-level message" },
{ level: "error", data: "Error-level message" },
{ level: "critical", data: "Critical-level message" },
{ level: "alert", data: "Alert level-message" },
{ level: "emergency", data: "Emergency-level message" },
];
const isMessageIgnored = (level: LoggingLevel): boolean => { // Function to start notification intervals when a client connects
const currentLevel = messages.findIndex((msg) => logLevel === msg.level); const startNotificationIntervals = (sid?: string|undefined) => {
const messageLevel = messages.findIndex((msg) => level === msg.level); sessionId = sid;
return messageLevel < currentLevel; if (!subsUpdateInterval) {
}; subsUpdateInterval = setInterval(() => {
for (const uri of subscriptions) {
server.notification({
method: "notifications/resources/updated",
params: { uri },
});
}
}, 10000);
}
// Function to start notification intervals when a client connects console.log(sessionId)
const startNotificationIntervals = () => { const maybeAppendSessionId = sessionId ? ` - SessionId ${sessionId}`: "";
if (!subsUpdateInterval) { const messages: { level: LoggingLevel; data: string }[] = [
subsUpdateInterval = setInterval(() => { { level: "debug", data: `Debug-level message${maybeAppendSessionId}` },
for (const uri of subscriptions) { { level: "info", data: `Info-level message${maybeAppendSessionId}` },
server.notification({ { level: "notice", data: `Notice-level message${maybeAppendSessionId}` },
method: "notifications/resources/updated", { level: "warning", data: `Warning-level message${maybeAppendSessionId}` },
params: { uri }, { level: "error", data: `Error-level message${maybeAppendSessionId}` },
}); { level: "critical", data: `Critical-level message${maybeAppendSessionId}` },
} { level: "alert", data: `Alert level-message${maybeAppendSessionId}` },
}, 10000); { level: "emergency", data: `Emergency-level message${maybeAppendSessionId}` },
} ];
if (!logsUpdateInterval) { if (!logsUpdateInterval) {
logsUpdateInterval = setInterval(() => { console.error("Starting logs update interval");
let message = { logsUpdateInterval = setInterval(async () => {
method: "notifications/message", await server.sendLoggingMessage( messages[Math.floor(Math.random() * messages.length)], sessionId);
params: messages[Math.floor(Math.random() * messages.length)], }, 15000);
};
if (!isMessageIgnored(message.params.level as LoggingLevel))
server.notification(message);
}, 20000);
} }
}; };
// Helper method to request sampling from client // Helper method to request sampling from client
const requestSampling = async ( const requestSampling = async (
context: string, context: string,
@ -918,23 +909,6 @@ export const createServer = () => {
throw new Error(`Unknown reference type`); throw new Error(`Unknown reference type`);
}); });
server.setRequestHandler(SetLevelRequestSchema, async (request) => {
const { level } = request.params;
logLevel = level;
// Demonstrate different log levels
await server.notification({
method: "notifications/message",
params: {
level: "debug",
logger: "test-server",
data: `Logging level set to: ${logLevel}`,
},
});
return {};
});
// Roots protocol handlers // Roots protocol handlers
server.setNotificationHandler(RootsListChangedNotificationSchema, async () => { server.setNotificationHandler(RootsListChangedNotificationSchema, async () => {
try { try {
@ -944,24 +918,18 @@ export const createServer = () => {
currentRoots = response.roots; currentRoots = response.roots;
// Log the roots update for demonstration // Log the roots update for demonstration
await server.notification({ await server.sendLoggingMessage({
method: "notifications/message",
params: {
level: "info", level: "info",
logger: "everything-server", logger: "everything-server",
data: `Roots updated: ${currentRoots.length} root(s) received from client`, data: `Roots updated: ${currentRoots.length} root(s) received from client`,
}, }, sessionId);
});
} }
} catch (error) { } catch (error) {
await server.notification({ await server.sendLoggingMessage({
method: "notifications/message",
params: {
level: "error", level: "error",
logger: "everything-server", logger: "everything-server",
data: `Failed to request roots from client: ${error instanceof Error ? error.message : String(error)}`, data: `Failed to request roots from client: ${error instanceof Error ? error.message : String(error)}`,
}, }, sessionId);
});
} }
}); });
@ -976,43 +944,31 @@ export const createServer = () => {
if (response && 'roots' in response) { if (response && 'roots' in response) {
currentRoots = response.roots; currentRoots = response.roots;
await server.notification({ await server.sendLoggingMessage({
method: "notifications/message",
params: {
level: "info", level: "info",
logger: "everything-server", logger: "everything-server",
data: `Initial roots received: ${currentRoots.length} root(s) from client`, data: `Initial roots received: ${currentRoots.length} root(s) from client`,
}, }, sessionId);
});
} else { } else {
await server.notification({ await server.sendLoggingMessage({
method: "notifications/message",
params: {
level: "warning", level: "warning",
logger: "everything-server", logger: "everything-server",
data: "Client returned no roots set", data: "Client returned no roots set",
}, }, sessionId);
});
} }
} catch (error) { } catch (error) {
await server.notification({ await server.sendLoggingMessage({
method: "notifications/message",
params: {
level: "error", level: "error",
logger: "everything-server", logger: "everything-server",
data: `Failed to request initial roots from client: ${error instanceof Error ? error.message : String(error)}`, data: `Failed to request initial roots from client: ${error instanceof Error ? error.message : String(error)}`,
}, }, sessionId);
});
} }
} else { } else {
await server.notification({ await server.sendLoggingMessage({
method: "notifications/message",
params: {
level: "info", level: "info",
logger: "everything-server", logger: "everything-server",
data: "Client does not support MCP roots protocol", data: "Client does not support MCP roots protocol",
}, }, sessionId);
});
} }
}; };

View file

@ -22,7 +22,7 @@
"start:streamableHttp": "node dist/streamableHttp.js" "start:streamableHttp": "node dist/streamableHttp.js"
}, },
"dependencies": { "dependencies": {
"@modelcontextprotocol/sdk": "^1.17.4", "@modelcontextprotocol/sdk": "^1.17.5",
"express": "^4.21.1", "express": "^4.21.1",
"zod": "^3.23.8", "zod": "^3.23.8",
"zod-to-json-schema": "^3.23.5" "zod-to-json-schema": "^3.23.5"

View file

@ -26,7 +26,7 @@ app.get("/sse", async (req, res) => {
console.error("Client Connected: ", transport.sessionId); console.error("Client Connected: ", transport.sessionId);
// Start notification intervals after client connects // Start notification intervals after client connects
startNotificationIntervals(); startNotificationIntervals(transport.sessionId);
// Handle close of connection // Handle close of connection
server.onclose = async () => { server.onclose = async () => {

View file

@ -2,21 +2,58 @@
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { createServer } from "./everything.js"; import { createServer } from "./everything.js";
import {
LoggingLevel,
LoggingLevelSchema,
LoggingMessageNotification,
SetLevelRequestSchema
} from "@modelcontextprotocol/sdk/types.js";
console.error('Starting default (STDIO) server...'); console.error('Starting default (STDIO) server...');
async function main() { async function main() {
const transport = new StdioServerTransport(); const transport = new StdioServerTransport();
const {server, cleanup} = createServer(); const {server, cleanup, startNotificationIntervals } = createServer();
await server.connect(transport); // Currently, for STDIO servers, automatic log-level support is not available, as levels are tracked by sessionId.
// The listener will be set, so if the STDIO server advertises support for logging, and the client sends a setLevel
// request, it will be handled and thus not throw a "Method not found" error. However, the STDIO server will need to
// implement its own listener and level handling for now. This will be remediated in a future SDK version.
// Cleanup on exit let logLevel: LoggingLevel = "debug";
process.on("SIGINT", async () => { server.setRequestHandler(SetLevelRequestSchema, async (request) => {
await cleanup(); const { level } = request.params;
await server.close(); logLevel = level;
process.exit(0); return {};
}); });
server.sendLoggingMessage = async (params: LoggingMessageNotification["params"], _: string|undefined): Promise<void> => {
const LOG_LEVEL_SEVERITY = new Map(
LoggingLevelSchema.options.map((level, index) => [level, index])
);
const isMessageIgnored = (level: LoggingLevel): boolean => {
const currentLevel = logLevel;
return (currentLevel)
? LOG_LEVEL_SEVERITY.get(level)! < LOG_LEVEL_SEVERITY.get(currentLevel)!
: false;
};
if (!isMessageIgnored(params.level)) {
return server.notification({method: "notifications/message", params})
}
}
await server.connect(transport);
startNotificationIntervals();
// Cleanup on exit
process.on("SIGINT", async () => {
await cleanup();
await server.close();
process.exit(0);
});
} }
main().catch((error) => { main().catch((error) => {

View file

@ -22,7 +22,7 @@ app.post('/mcp', async (req: Request, res: Response) => {
transport = transports.get(sessionId)!; transport = transports.get(sessionId)!;
} else if (!sessionId) { } else if (!sessionId) {
const { server, cleanup } = createServer(); const { server, cleanup, startNotificationIntervals } = createServer();
// New initialization request // New initialization request
const eventStore = new InMemoryEventStore(); const eventStore = new InMemoryEventStore();
@ -53,7 +53,11 @@ app.post('/mcp', async (req: Request, res: Response) => {
await server.connect(transport); await server.connect(transport);
await transport.handleRequest(req, res); await transport.handleRequest(req, res);
return; // Already handled
// Wait until initialize is complete and transport will have a sessionId
startNotificationIntervals(transport.sessionId);
return; // Already handled
} else { } else {
// Invalid request - no session ID or not initialization request // Invalid request - no session ID or not initialization request
res.status(400).json({ res.status(400).json({