diff --git a/.gitignore b/.gitignore index 7ecb7109b8..a6beb0bc5c 100644 --- a/.gitignore +++ b/.gitignore @@ -122,6 +122,9 @@ dist # Stores VSCode versions used for testing VSCode extensions .vscode-test +# Jetbrains IDEs +.idea/ + # yarn v2 .yarn/cache .yarn/unplugged diff --git a/package-lock.json b/package-lock.json index 56ba065ca9..fe72fc4328 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5818,7 +5818,7 @@ "version": "0.6.2", "license": "MIT", "dependencies": { - "@modelcontextprotocol/sdk": "^1.17.4", + "@modelcontextprotocol/sdk": "^1.17.5", "express": "^4.21.1", "zod": "^3.23.8", "zod-to-json-schema": "^3.23.5" @@ -5833,9 +5833,9 @@ } }, "src/everything/node_modules/@modelcontextprotocol/sdk": { - "version": "1.17.4", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.17.4.tgz", - "integrity": "sha512-zq24hfuAmmlNZvik0FLI58uE5sriN0WWsQzIlYnzSuKDAHFqJtBFrl/LfB1NLgJT5Y7dEBzaX4yAKqOPrcetaw==", + "version": "1.17.5", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.17.5.tgz", + "integrity": "sha512-QakrKIGniGuRVfWBdMsDea/dx1PNE739QJ7gCM41s9q+qaCYTHCdsIBXQVVXry3mfWAiaM9kT22Hyz53Uw8mfg==", "license": "MIT", "dependencies": { "ajv": "^6.12.6", diff --git a/src/everything/everything.ts b/src/everything/everything.ts index c313b6eaaa..c1a99172ee 100644 --- a/src/everything/everything.ts +++ b/src/everything/everything.ts @@ -14,12 +14,11 @@ import { ReadResourceRequestSchema, Resource, RootsListChangedNotificationSchema, - SetLevelRequestSchema, SubscribeRequestSchema, Tool, ToolSchema, UnsubscribeRequestSchema, - type Root, + type Root } from "@modelcontextprotocol/sdk/types.js"; import { z } from "zod"; import { zodToJsonSchema } from "zod-to-json-schema"; @@ -174,7 +173,6 @@ export const createServer = () => { let subsUpdateInterval: NodeJS.Timeout | undefined; let stdErrUpdateInterval: NodeJS.Timeout | undefined; - let logLevel: LoggingLevel = "debug"; let logsUpdateInterval: NodeJS.Timeout | undefined; // Store client capabilities let clientCapabilities: ClientCapabilities | undefined; @@ -182,50 +180,43 @@ export const createServer = () => { // Roots state management let currentRoots: Root[] = []; let clientSupportsRoots = false; - const messages = [ - { 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 => { - const currentLevel = messages.findIndex((msg) => logLevel === msg.level); - const messageLevel = messages.findIndex((msg) => level === msg.level); - return messageLevel < currentLevel; - }; - - // Function to start notification intervals when a client connects - const startNotificationIntervals = () => { - if (!subsUpdateInterval) { - subsUpdateInterval = setInterval(() => { - for (const uri of subscriptions) { - server.notification({ - method: "notifications/resources/updated", - params: { uri }, - }); - } - }, 10000); - } + let sessionId: string | undefined; + + // Function to start notification intervals when a client connects + const startNotificationIntervals = (sid?: string|undefined) => { + sessionId = sid; + if (!subsUpdateInterval) { + subsUpdateInterval = setInterval(() => { + for (const uri of subscriptions) { + server.notification({ + method: "notifications/resources/updated", + params: { uri }, + }); + } + }, 10000); + } - if (!logsUpdateInterval) { - logsUpdateInterval = setInterval(() => { - let message = { - method: "notifications/message", - params: messages[Math.floor(Math.random() * messages.length)], - }; - if (!isMessageIgnored(message.params.level as LoggingLevel)) - server.notification(message); - }, 20000); + console.log(sessionId) + 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}` }, + ]; + + if (!logsUpdateInterval) { + console.error("Starting logs update interval"); + logsUpdateInterval = setInterval(async () => { + await server.sendLoggingMessage( messages[Math.floor(Math.random() * messages.length)], sessionId); + }, 15000); } }; - - // Helper method to request sampling from client const requestSampling = async ( context: string, @@ -918,23 +909,6 @@ export const createServer = () => { 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 server.setNotificationHandler(RootsListChangedNotificationSchema, async () => { try { @@ -944,24 +918,18 @@ export const createServer = () => { currentRoots = response.roots; // Log the roots update for demonstration - await server.notification({ - method: "notifications/message", - params: { + await server.sendLoggingMessage({ level: "info", logger: "everything-server", data: `Roots updated: ${currentRoots.length} root(s) received from client`, - }, - }); + }, sessionId); } } catch (error) { - await server.notification({ - method: "notifications/message", - params: { + await server.sendLoggingMessage({ level: "error", logger: "everything-server", 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) { currentRoots = response.roots; - await server.notification({ - method: "notifications/message", - params: { + await server.sendLoggingMessage({ level: "info", logger: "everything-server", data: `Initial roots received: ${currentRoots.length} root(s) from client`, - }, - }); + }, sessionId); } else { - await server.notification({ - method: "notifications/message", - params: { + await server.sendLoggingMessage({ level: "warning", logger: "everything-server", data: "Client returned no roots set", - }, - }); + }, sessionId); } } catch (error) { - await server.notification({ - method: "notifications/message", - params: { + await server.sendLoggingMessage({ level: "error", logger: "everything-server", data: `Failed to request initial roots from client: ${error instanceof Error ? error.message : String(error)}`, - }, - }); + }, sessionId); } } else { - await server.notification({ - method: "notifications/message", - params: { + await server.sendLoggingMessage({ level: "info", logger: "everything-server", data: "Client does not support MCP roots protocol", - }, - }); + }, sessionId); } }; diff --git a/src/everything/package.json b/src/everything/package.json index 0cff945cb8..8e417a28a4 100644 --- a/src/everything/package.json +++ b/src/everything/package.json @@ -22,7 +22,7 @@ "start:streamableHttp": "node dist/streamableHttp.js" }, "dependencies": { - "@modelcontextprotocol/sdk": "^1.17.4", + "@modelcontextprotocol/sdk": "^1.17.5", "express": "^4.21.1", "zod": "^3.23.8", "zod-to-json-schema": "^3.23.5" diff --git a/src/everything/sse.ts b/src/everything/sse.ts index f414e02f2f..f201341948 100644 --- a/src/everything/sse.ts +++ b/src/everything/sse.ts @@ -26,7 +26,7 @@ app.get("/sse", async (req, res) => { console.error("Client Connected: ", transport.sessionId); // Start notification intervals after client connects - startNotificationIntervals(); + startNotificationIntervals(transport.sessionId); // Handle close of connection server.onclose = async () => { diff --git a/src/everything/stdio.ts b/src/everything/stdio.ts index a98fbc5371..5e01fd7fe9 100644 --- a/src/everything/stdio.ts +++ b/src/everything/stdio.ts @@ -2,21 +2,58 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { createServer } from "./everything.js"; +import { + LoggingLevel, + LoggingLevelSchema, + LoggingMessageNotification, + SetLevelRequestSchema +} from "@modelcontextprotocol/sdk/types.js"; console.error('Starting default (STDIO) server...'); async function main() { - const transport = new StdioServerTransport(); - const {server, cleanup} = createServer(); + const transport = new StdioServerTransport(); + 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 - process.on("SIGINT", async () => { - await cleanup(); - await server.close(); - process.exit(0); - }); + let logLevel: LoggingLevel = "debug"; + server.setRequestHandler(SetLevelRequestSchema, async (request) => { + const { level } = request.params; + logLevel = level; + return {}; + }); + + server.sendLoggingMessage = async (params: LoggingMessageNotification["params"], _: string|undefined): Promise => { + 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) => { diff --git a/src/everything/streamableHttp.ts b/src/everything/streamableHttp.ts index f748fd2ade..c4fed73803 100644 --- a/src/everything/streamableHttp.ts +++ b/src/everything/streamableHttp.ts @@ -22,7 +22,7 @@ app.post('/mcp', async (req: Request, res: Response) => { transport = transports.get(sessionId)!; } else if (!sessionId) { - const { server, cleanup } = createServer(); + const { server, cleanup, startNotificationIntervals } = createServer(); // New initialization request const eventStore = new InMemoryEventStore(); @@ -53,7 +53,11 @@ app.post('/mcp', async (req: Request, res: Response) => { await server.connect(transport); 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 { // Invalid request - no session ID or not initialization request res.status(400).json({