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

Finalized Roots list changed handling and initial request. Final fit and finish work.

* Updated architecture.md
  - Added links to other docs
  - Refactor/extracted sections into extension.md, features.md, how-it-works.md, startup.md, and structure.md

* Removed everything.ts
  - all features are ported

* In roots.ts
  - refactor/renaned setRootsListChangedHandler to syncRoots
  - refactor handler logic to requestRoots function
  - Calls for roots list directly to get initial list

* In server/index.ts
  - import setRootsListChangedHandler
  - in clientConnected callback
    - call setRootsListChangedHandler passing server and sessionId

* In sse.ts, stdio.ts, and streamableHttp.ts
  - update inline and function docs

* In index.ts,
  - updated usage output

* In server/index.ts
  - refactor/extracted readInstructions to resources/index.ts
  - defined ServerFactoryResponse response type
This commit is contained in:
cliffhall 2025-12-12 20:12:18 -05:00
commit 9b25a3a41b
4 changed files with 581 additions and 1533 deletions

File diff suppressed because one or more lines are too long

View file

@ -1,19 +1,40 @@
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { dirname, join } from "path";
import { readFileSync } from "fs";
import { fileURLToPath } from "url";
import {
setSubscriptionHandlers,
stopSimulatedResourceUpdates,
} from "../resources/subscriptions.js";
import { registerTools } from "../tools/index.js";
import { registerResources } from "../resources/index.js";
import { registerResources, readInstructions } from "../resources/index.js";
import { registerPrompts } from "../prompts/index.js";
import { stopSimulatedLogging } from "./logging.js";
import { setRootsListChangedHandler } from "./roots.js";
import { syncRoots } from "./roots.js";
// Everything Server factory
export const createServer = () => {
// Server Factory response
export type ServerFactoryResponse = {
server: McpServer;
clientConnected: (sessionId?: string) => void;
cleanup: (sessionId?: string) => void;
};
/**
* `ServerInstance` factory
*
* This function initializes a `McpServer` with specific capabilities and instructions,
* registers tools, resources, and prompts, and configures resource subscription handlers.
*
* It returns the server instance along with callbacks for post-connection setup and cleanup tasks.
*
* @function
* @returns {ServerFactoryResponse} An object containing the server instance, a `clientConnected` callback
* for managing new client sessions, and a `cleanup` function for handling server-side cleanup when
* a session ends.
*
* Properties of the returned object:
* - `server` {Object}: The initialized server instance.
* - `clientConnected` {Function}: A post-connect callback to enable operations that require a `sessionId`.
* - `cleanup` {Function}: Function to perform cleanup operations for a closing session.
*/
export const createServer: () => ServerFactoryResponse = () => {
// Read the server instructions
const instructions = readInstructions();
@ -49,32 +70,17 @@ export const createServer = () => {
// Set resource subscription handlers
setSubscriptionHandlers(server);
// Return server instance, client connection handler, and cleanup function
// Return the ServerFactoryResponse
return {
server,
clientConnected: (sessionId?: string) => {
// Set the roots list changed handler
setRootsListChangedHandler(server, sessionId);
// Set a roots list changed handler and fetch the initial roots list from the client
syncRoots(server, sessionId);
},
cleanup: (sessionId?: string) => {
// Stop any simulated logging or resource updates that may have been initiated.
stopSimulatedLogging(sessionId);
stopSimulatedResourceUpdates(sessionId);
},
};
} satisfies ServerFactoryResponse;
};
// Read the server instructions from a file
function readInstructions(): string {
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const filePath = join(__dirname, "..", "docs", "server-instructions.md");
let instructions;
try {
instructions = readFileSync(filePath, "utf-8");
} catch (e) {
instructions = "Server instructions not loaded: " + e;
}
return instructions;
}

View file

@ -55,7 +55,7 @@ export const beginSimulatedLogging = (
// Send once immediately
sendSimulatedLoggingMessage(sessionId);
// Sen
// Send a randomly-leveled log message every 5 seconds
logsUpdateIntervals.set(
sessionId,
setInterval(() => sendSimulatedLoggingMessage(sessionId), 5000)

View file

@ -11,53 +11,67 @@ const roots: Map<string | undefined, Root[]> = new Map<
>();
/**
* Sets a handler for the "RootsListChanged" notification from the client.
* Sync the root directories from the client by requesting and updating the roots list for
* the specified session.
*
* This handler updates the local roots list when notified and logs relevant
* acknowledgement or error.
* Also sets up a notification handler to listen for changes in the roots list, ensuring that
* updates are automatically fetched and handled in real-time.
*
* @param {McpServer} mcpServer - The instance of the McpServer managing server communication.
* @param {string | undefined} sessionId - An optional session ID used for logging purposes.
* @param {McpServer} server - An instance of the MCP server used to communicate with the client.
* @param {string} [sessionId] - An optional session id used to associate the roots list with a specific client session.
*
* @throws {Error} In case of a failure to request the roots from the client, an error log message is sent.
*/
export const setRootsListChangedHandler = (
mcpServer: McpServer,
sessionId?: string
) => {
const server = mcpServer.server;
export const syncRoots = (server: McpServer, sessionId?: string) => {
// Function to request the updated roots list from the client
const requestRoots = async () => {
try {
// Request the updated roots list from the client
const response = await server.server.listRoots();
if (response && "roots" in response) {
// Store the roots list for this client
roots.set(sessionId, response.roots);
// Set the notification handler
server.setNotificationHandler(
RootsListChangedNotificationSchema,
async () => {
try {
// Request the updated roots list from the client
const response = await server.listRoots();
if (response && "roots" in response) {
// Store the roots list for this client
roots.set(sessionId, response.roots);
// Notify the client of roots received
await server.sendLoggingMessage(
{
level: "info",
logger: "everything-server",
data: `Roots updated: ${response.roots.length} root(s) received from client`,
},
sessionId
);
}
} catch (error) {
// Notify the client of roots received
await server.sendLoggingMessage(
{
level: "error",
level: "info",
logger: "everything-server",
data: `Failed to request roots from client: ${
error instanceof Error ? error.message : String(error)
}`,
data: `Roots updated: ${response.roots.length} root(s) received from client`,
},
sessionId
);
} else {
await server.sendLoggingMessage(
{
level: "info",
logger: "everything-server",
data: "Client returned no roots set",
},
sessionId
);
}
} catch (error) {
await server.sendLoggingMessage(
{
level: "error",
logger: "everything-server",
data: `Failed to request roots from client: ${
error instanceof Error ? error.message : String(error)
}`,
},
sessionId
);
}
};
// Set the list changed notification handler
server.server.setNotificationHandler(
RootsListChangedNotificationSchema,
requestRoots
);
// Request initial roots list after a brief delay
// Allows initial POST request to complete on streamableHttp transports
setTimeout(() => requestRoots(), 350);
};