[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:
parent
cb073d877a
commit
9b25a3a41b
4 changed files with 581 additions and 1533 deletions
File diff suppressed because one or more lines are too long
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in a new issue