[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
3 changed files with 581 additions and 1533 deletions
|
|
@ -5,6 +5,7 @@ import cors from "cors";
|
|||
|
||||
console.error("Starting SSE server...");
|
||||
|
||||
// Express app with permissive CORS for testing with Inspector direct connect mode
|
||||
const app = express();
|
||||
app.use(
|
||||
cors({
|
||||
|
|
@ -13,16 +14,20 @@ app.use(
|
|||
preflightContinue: false,
|
||||
optionsSuccessStatus: 204,
|
||||
})
|
||||
); // Enable CORS for all routes so Inspector can connect
|
||||
);
|
||||
|
||||
// Map sessionId to transport for each client
|
||||
const transports: Map<string, SSEServerTransport> = new Map<
|
||||
string,
|
||||
SSEServerTransport
|
||||
>();
|
||||
|
||||
// Handle GET requests for new SSE streams
|
||||
app.get("/sse", async (req, res) => {
|
||||
let transport: SSEServerTransport;
|
||||
const { server, clientConnected, cleanup } = createServer();
|
||||
|
||||
// Session Id should not exist for GET /sse requests
|
||||
if (req?.query?.sessionId) {
|
||||
const sessionId = req?.query?.sessionId as string;
|
||||
transport = transports.get(sessionId) as SSEServerTransport;
|
||||
|
|
@ -31,15 +36,14 @@ app.get("/sse", async (req, res) => {
|
|||
transport.sessionId
|
||||
);
|
||||
} else {
|
||||
// Create and store transport for new session
|
||||
// Create and store transport for the new session
|
||||
transport = new SSEServerTransport("/message", res);
|
||||
transports.set(transport.sessionId, transport);
|
||||
|
||||
// Connect server to transport
|
||||
// Connect server to transport and invoke clientConnected callback
|
||||
await server.connect(transport);
|
||||
const sessionId = transport.sessionId;
|
||||
clientConnected(sessionId);
|
||||
|
||||
console.error("Client Connected: ", sessionId);
|
||||
|
||||
// Handle close of connection
|
||||
|
|
@ -47,13 +51,17 @@ app.get("/sse", async (req, res) => {
|
|||
const sessionId = transport.sessionId;
|
||||
console.error("Client Disconnected: ", sessionId);
|
||||
transports.delete(sessionId);
|
||||
await cleanup(sessionId);
|
||||
cleanup(sessionId);
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
// Handle POST requests for client messages
|
||||
app.post("/message", async (req, res) => {
|
||||
// Session Id should exist for POST /message requests
|
||||
const sessionId = req?.query?.sessionId as string;
|
||||
|
||||
// Get the transport for this session and use it to handle the request
|
||||
const transport = transports.get(sessionId);
|
||||
if (transport) {
|
||||
console.error("Client Message from", sessionId);
|
||||
|
|
@ -63,6 +71,7 @@ app.post("/message", async (req, res) => {
|
|||
}
|
||||
});
|
||||
|
||||
// Start the express server
|
||||
const PORT = process.env.PORT || 3001;
|
||||
app.listen(PORT, () => {
|
||||
console.error(`Server is running on port ${PORT}`);
|
||||
|
|
|
|||
|
|
@ -5,12 +5,22 @@ import { createServer } from "../server/index.js";
|
|||
|
||||
console.error("Starting default (STDIO) server...");
|
||||
|
||||
async function main() {
|
||||
/**
|
||||
* The main method
|
||||
* - Initializes the StdioServerTransport, sets up the server,
|
||||
* - Connects the transport to the server, invokes the `clientConnected` callback,
|
||||
* - Handles cleanup on process exit.
|
||||
*
|
||||
* @return {Promise<void>} A promise that resolves when the main function has executed and the process exits.
|
||||
*/
|
||||
async function main(): Promise<void> {
|
||||
const transport = new StdioServerTransport();
|
||||
const { server, clientConnected, cleanup } = createServer();
|
||||
|
||||
// Connect transport to server and invoke clientConnected callback
|
||||
await server.connect(transport);
|
||||
clientConnected();
|
||||
|
||||
// Cleanup on exit
|
||||
process.on("SIGINT", async () => {
|
||||
await server.close();
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import cors from "cors";
|
|||
|
||||
console.log("Starting Streamable HTTP server...");
|
||||
|
||||
// Express app with permissive CORS for testing with Inspector direct connect mode
|
||||
const app = express();
|
||||
app.use(
|
||||
cors({
|
||||
|
|
@ -16,13 +17,15 @@ app.use(
|
|||
optionsSuccessStatus: 204,
|
||||
exposedHeaders: ["mcp-session-id", "last-event-id", "mcp-protocol-version"],
|
||||
})
|
||||
); // Enable CORS for all routes so Inspector can connect
|
||||
);
|
||||
|
||||
// Map sessionId to server transport for each client
|
||||
const transports: Map<string, StreamableHTTPServerTransport> = new Map<
|
||||
string,
|
||||
StreamableHTTPServerTransport
|
||||
>();
|
||||
|
||||
// Handle POST requests for client messages
|
||||
app.post("/mcp", async (req: Request, res: Response) => {
|
||||
console.log("Received MCP POST request");
|
||||
try {
|
||||
|
|
@ -47,7 +50,6 @@ app.post("/mcp", async (req: Request, res: Response) => {
|
|||
// This avoids race conditions where requests might come in before the session is stored
|
||||
console.log(`Session initialized with ID: ${sessionId}`);
|
||||
transports.set(sessionId, transport);
|
||||
clientConnected(sessionId);
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -66,9 +68,8 @@ app.post("/mcp", async (req: Request, res: Response) => {
|
|||
// Connect the transport to the MCP server BEFORE handling the request
|
||||
// so responses can flow back through the same transport
|
||||
await server.connect(transport);
|
||||
|
||||
clientConnected(transport.sessionId);
|
||||
await transport.handleRequest(req, res);
|
||||
|
||||
return;
|
||||
} else {
|
||||
// Invalid request - no session ID or not initialization request
|
||||
|
|
@ -102,7 +103,7 @@ app.post("/mcp", async (req: Request, res: Response) => {
|
|||
}
|
||||
});
|
||||
|
||||
// Handle GET requests for SSE streams (using built-in support from StreamableHTTP)
|
||||
// Handle GET requests for SSE streams
|
||||
app.get("/mcp", async (req: Request, res: Response) => {
|
||||
console.log("Received MCP GET request");
|
||||
const sessionId = req.headers["mcp-session-id"] as string | undefined;
|
||||
|
|
@ -130,7 +131,7 @@ app.get("/mcp", async (req: Request, res: Response) => {
|
|||
await transport!.handleRequest(req, res);
|
||||
});
|
||||
|
||||
// Handle DELETE requests for session termination (according to MCP spec)
|
||||
// Handle DELETE requests for session termination
|
||||
app.delete("/mcp", async (req: Request, res: Response) => {
|
||||
const sessionId = req.headers["mcp-session-id"] as string | undefined;
|
||||
if (!sessionId || !transports.has(sessionId)) {
|
||||
|
|
@ -172,6 +173,7 @@ const server = app.listen(PORT, () => {
|
|||
console.error(`MCP Streamable HTTP Server listening on port ${PORT}`);
|
||||
});
|
||||
|
||||
// Handle server errors
|
||||
server.on("error", (err: unknown) => {
|
||||
const code =
|
||||
typeof err === "object" && err !== null && "code" in err
|
||||
|
|
|
|||
Loading…
Reference in a new issue