Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,9 @@ dist
# Stores VSCode versions used for testing VSCode extensions
.vscode-test

# Jetbrains IDEs
.idea/

# yarn v2
.yarn/cache
.yarn/unplugged
Expand Down
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

136 changes: 46 additions & 90 deletions src/everything/everything.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -174,58 +173,50 @@ 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;

// 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,
Expand Down Expand Up @@ -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 {
Expand All @@ -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);
}
});

Expand All @@ -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);
}
};

Expand Down
2 changes: 1 addition & 1 deletion src/everything/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
2 changes: 1 addition & 1 deletion src/everything/sse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 () => {
Expand Down
55 changes: 46 additions & 9 deletions src/everything/stdio.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<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) => {
Expand Down
8 changes: 6 additions & 2 deletions src/everything/streamableHttp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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({
Expand Down