Files
servers-modelcontextprotocol-1/src/everything/transports/sse.ts
cliffhall 1b8f376b90 Demonstrate registration of tools conditioned upon client capability support. Also, obviated need for clientConnected callback to pass sessionId because we defer initial fetching of roots happens when you run the get-roots-list tool.
* In how-it-works.md,
  - added a section on conditional tool registration

* In server/index.ts
  - import registerConditionalTools
  - in an oninitialized handler for the server, call registerConditionalTools
  - removed clientConnected from ServerFactoryResponse and all mentions in docs

* In tools/index.ts
  - export a registerConditionalTools function
  - refactor/move calls to registerGetRootsListTool, registerTriggerElicitationRequestTool, and registerTriggerSamplingRequestTool out of registerTools and into registerConditionalTools

* In server/roots.ts
  - only act if client supports roots
  - remove setInterval from call to requestRoots. It isn't happening during the initialze handshake anymore, so it doesn't interfere with that process if called immediaately

* In get-roots-list.ts, trigger-elicitation-request.ts, and trigger-sampling-request.ts,
  - only register tool if client supports capability

* Throughout the rest of the files, removing all references to `clientConnected`
2025-12-15 17:51:30 -05:00

78 lines
2.4 KiB
TypeScript

import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
import express from "express";
import { createServer } from "../server/index.js";
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({
origin: "*", // use "*" with caution in production
methods: "GET,POST",
preflightContinue: false,
optionsSuccessStatus: 204,
})
);
// 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, 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;
console.error(
"Client Reconnecting? This shouldn't happen; when client has a sessionId, GET /sse should not be called again.",
transport.sessionId
);
} else {
// Create and store transport for the new session
transport = new SSEServerTransport("/message", res);
transports.set(transport.sessionId, transport);
// Connect server to transport
await server.connect(transport);
const sessionId = transport.sessionId;
console.error("Client Connected: ", sessionId);
// Handle close of connection
server.server.onclose = async () => {
const sessionId = transport.sessionId;
console.error("Client Disconnected: ", sessionId);
transports.delete(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);
await transport.handlePostMessage(req, res);
} else {
console.error(`No transport found for sessionId ${sessionId}`);
}
});
// Start the express server
const PORT = process.env.PORT || 3001;
app.listen(PORT, () => {
console.error(`Server is running on port ${PORT}`);
});