@modelcontextprotocol/server-everything
Version:
MCP server that exercises all the features of the MCP protocol
156 lines (155 loc) • 7.34 kB
JavaScript
// The everything server's factory: builds one fully registered `McpServer`
// for the protocol era it is asked to serve (#4852).
//
// One factory backs every transport and both eras. `serveStdio` calls it once
// per stdio connection; the Streamable HTTP transport calls it once per
// 2025-era session and once per 2026-07-28 request; the SSE transport once
// per (2025-era) connection. Everything that must outlive a single call lives
// on the instance it builds, in the context below, never in a map keyed by a
// session id: the 2026-07-28 revision has no sessions.
//
// What differs by era is wired here, in one place:
// - 2025-era: the client's capabilities arrive in `initialize`, so the tools
// that need a client capability are registered once it has initialized, and
// the roots list is synced then. The features the 2026-07-28 revision
// removes (`logging/setLevel`-paced simulated logging, `resources/subscribe`
// timed updates) are registered here only, labelled deprecated.
// - 2026-07-28: there is no `initialize`, and each request carries the
// client's capabilities in its `_meta`, so the capability-gated tools are
// registered up front and the SDK refuses an embedded request the client
// did not declare (-32021). The tasks extension is served here only.
import { createRequestStateCodec, McpServer, } from "@modelcontextprotocol/server";
import { setSubscriptionHandlers } from "../resources/subscriptions.js";
import { registerConditionalTools, registerLegacyTools, registerModernTools, registerTools, } from "../tools/index.js";
import { registerResources, readInstructions } from "../resources/index.js";
import { registerPrompts } from "../prompts/index.js";
import { guardTaskTools, registerTasksExtension, TASKS_EXTENSION, } from "./tasks.js";
import { syncRoots } from "./roots.js";
import { instancePublisher, } from "./context.js";
import { SERVER_VERSION } from "../version.js";
import { SERVER_ICONS } from "./icons.js";
/**
* The process-wide `requestState` codec. The key is per process: on the
* 2026-07-28 HTTP path a later round may be served by a fresh instance, so
* the key cannot belong to one. A deployment of several processes would load
* a shared secret here instead.
*/
export const roundStateCodec = createRequestStateCodec({
key: crypto.getRandomValues(new Uint8Array(32)),
ttlSeconds: 600,
});
/**
* Cache hints for the 2026-07-28 cacheable results (`ttlMs` / `cacheScope`).
* The lists are the same for every client and change only with a
* `list_changed` notification, so a shared cache may hold them for a minute;
* `server/discover` changes only when the server is upgraded. 2025-era
* responses never carry these fields. A static document resource sets a
* longer hint of its own (resources/files.ts).
*/
export const CACHE_HINTS = {
"server/discover": { ttlMs: 5 * 60_000, cacheScope: "public" },
"tools/list": { ttlMs: 60_000, cacheScope: "public" },
"prompts/list": { ttlMs: 60_000, cacheScope: "public" },
"resources/list": { ttlMs: 60_000, cacheScope: "public" },
"resources/templates/list": { ttlMs: 60_000, cacheScope: "public" },
};
/**
* Server Factory
*
* Initializes a `McpServer` with its capabilities and instructions, and
* registers the tools, resources and prompts for the era it serves.
*
* @param {ServerOptions} options - The era to serve and where its change
* notifications go.
* @returns {ServerFactoryResponse} The server instance, and a `cleanup`
* function that ends its per-connection work when the connection closes.
*/
export const createServer = (options = {}) => {
const era = options.era ?? "legacy";
// Read the server instructions
const instructions = readInstructions();
const cleanups = [];
let initializeTimeout = null;
// Create the server
const server = new McpServer({
name: "mcp-servers/everything",
title: "Everything Reference Server",
icons: SERVER_ICONS,
version: SERVER_VERSION,
}, {
capabilities: {
// `tools: { listChanged: true }` is advertised by the first
// registerTool. Declaring it here would install McpServer's
// tools/call handler in the constructor, before guardTaskTools can
// wrap it.
prompts: {
listChanged: true,
},
resources: {
subscribe: true,
listChanged: true,
},
// Deprecated as of 2026-07-28 (SEP-2577), and still served on both
// eras: 2025-era clients set the level with `logging/setLevel`,
// 2026-07-28 clients per request in `_meta`.
logging: {},
// The tasks extension (`io.modelcontextprotocol/tasks`) is
// advertised on 2026-07-28 only, where it is served.
...(era === "modern" && { extensions: { [TASKS_EXTENSION]: {} } }),
},
instructions,
cacheHints: CACHE_HINTS,
requestState: { verify: roundStateCodec.verify },
});
// Resources a 2025-era client has subscribed to on this instance. On
// 2026-07-28 the client names them in its subscriptions/listen filter and
// the serving entry delivers only what it asked for.
const subscribed = new Set();
const context = {
era,
subscribed,
publish: options.publish ??
instancePublisher(server, (uri) => era === "modern" ? true : subscribed.has(uri)),
onCleanup: (fn) => cleanups.push(fn),
};
// A task tool refuses, with -32021, a client that has not declared what it
// needs; this wraps the tools/call handler the first registration installs.
if (era === "modern")
guardTaskTools(server);
// Register the tools, resources and prompts every era gets
registerTools(server, context);
registerResources(server, context);
registerPrompts(server);
if (era === "modern") {
// Each request carries the client's capabilities, so the tools that need
// one are registered up front, along with the tasks extension.
registerModernTools(server, context);
registerTasksExtension(server);
}
else {
registerLegacyTools(server, context);
setSubscriptionHandlers(server, context.subscribed);
// Perform post-initialization operations
server.server.oninitialized = async () => {
// Register conditional tools now that client capabilities are known.
// This finishes before the `notifications/initialized` handler finishes.
registerConditionalTools(server, context);
// Sync roots if the client supports them.
// This is delayed until after the `notifications/initialized` handler finishes,
// otherwise, the request gets lost.
initializeTimeout = setTimeout(() => syncRoots(server), 350);
};
}
// Return the ServerFactoryResponse
return {
server,
cleanup: () => {
// Stop any simulated logging or resource updates that may have been initiated.
for (const fn of cleanups.splice(0))
fn();
subscribed.clear();
if (initializeTimeout)
clearTimeout(initializeTimeout);
},
};
};