[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
parent cb073d877a
commit 9b25a3a41b
16 changed files with 581 additions and 1533 deletions

View File

@@ -1,306 +1,27 @@
# Everything Server Architecture and Layout
# Everything Server Architecture
**Architecture
| [Project Structure](structure.md)
| [Startup Process](startup.md)
| [Server Features](features.md)
| [Extension Points](extension.md)
| [How It Works](how-it-works.md)**
This document summarizes the current layout and runtime architecture of the `src/everything` package. It explains how the server starts, how transports are wired, where tools, prompts, and resources are registered, and how to extend the system.
This documentation summarizes the current layout and runtime architecture of the `src/everything` package.
It explains how the server starts, how transports are wired, where tools, prompts, and resources are registered, and how to extend the system.
## Highlevel Overview
- Purpose: A minimal, modular MCP server showcasing core Model Context Protocol features. It exposes a simple tool, several prompts, and both static and dynamic resources, and can be run over multiple transports (STDIO, SSE, and Streamable HTTP).
- Design: A small “server factory” constructs the MCP server and registers features. Transports are separate entry points that create/connect the server and handle network concerns. Tools, prompts, and resources are organized in their own submodules.
- Design: A small “server factory” constructs the MCP server and registers features. Transports are separate entry points that create/connect the server and handle network concerns. Tools, prompts, and resources are organized in their own submodules. Simulated logging and resourceupdate notifications are optin and controlled by tools.
- Two server implementations exist:
### Purpose
A minimal, modular MCP server showcasing core Model Context Protocol features. It exposes simple tools, prompts, and resources, and can be run over multiple transports (STDIO, SSE, and Streamable HTTP).
- `server/index.ts`: The lightweight, modular server used by transports in this package.
- `server/everything.ts`: A comprehensive reference server (much larger, many tools/prompts/resources) kept for reference/testing but not wired up by default in the entry points.
### Design
A small “server factory” constructs the MCP server and registers features.
Transports are separate entry points that create/connect the server and handle network concerns.
Tools, prompts, and resources are organized in their own submodules.
- Multiclient subscriptions: The server supports multiple concurrent clients. Each client manages its own resource subscriptions and receives notifications only for the URIs it subscribed to, independent of other clients.
## Directory Layout
```
src/everything
├── index.ts
├── docs
│ ├── architecture.md
│ └── server-instructions.md
├── prompts
│ ├── index.ts
│ ├── args.ts
│ ├── completions.ts
│ ├── simple.ts
│ └── resource.ts
├── resources
│ ├── index.ts
│ ├── files.ts
│ ├── session.ts
│ ├── subscriptions.ts
│ └── templates.ts
├── server
│ ├── index.ts
│ ├── logging.ts
│ ├── roots.ts
│ └── everything.ts
├── transports
│ ├── sse.ts
│ ├── stdio.ts
│ └── streamableHttp.ts
├── tools
│ ├── index.ts
│ ├── echo.ts
│ ├── get-annotated-message.ts
│ ├── get-env.ts
│ ├── get-tiny-image.ts
│ ├── get-resource-links.ts
│ ├── get-resource-reference.ts
│ ├── get-roots-list.ts
│ ├── get-structured-content.ts
│ ├── get-sum.ts
│ ├── gzip-file-as-resource.ts
│ ├── long-running-operation.ts
│ ├── toggle-logging.ts
│ ├── toggle-subscriber-updates.ts
│ ├── trigger-elicitation-request.ts
│ └── trigger-sampling-request.ts
└── package.json
```
At `src/everything`:
- index.ts
- CLI entry that selects and runs a specific transport module based on the first CLI argument: `stdio`, `sse`, or `streamableHttp`.
- server/
- index.ts
- Server factory that creates an `McpServer` with declared capabilities, loads server instructions, and registers tools, prompts, and resources.
- Sets resource subscription handlers via `setSubscriptionHandlers(server)`.
- Exposes `{ server, cleanup }` to the chosen transport. Cleanup stops any running intervals in the server when the transport disconencts.
- logging.ts
- Implements simulated logging. Periodically sends randomized log messages at various levels to the connected client session. Started/stopped on demand via a dedicated tool.
- everything.ts
- A full “reference/monolith” implementation demonstrating most MCP features. Not the default path used by the transports in this package.
- transports/
- stdio.ts
- Starts a `StdioServerTransport`, created the server via `createServer()`, and connects it.
- Calls `clientConnected()` to inform the server of the connection.
- Handles `SIGINT` to close cleanly and calls `cleanup()` to remove any live intervals.
- sse.ts
- Express server exposing:
- `GET /sse` to establish an SSE connection per session.
- `POST /message` for client messages.
- Manages multiple connected clients via a transport map.
- Starts an `SSEServerTransport`, created the server via `createServer()`, and connects it to a new transport.
- Calls `clientConnected(sessionId)` to inform the server of the connection.
- On server disconnect, calls `cleanup()` to remove any live intervals.
- streamableHttp.ts
- Express server exposing a single `/mcp` endpoint for POST (JSONRPC), GET (SSE stream), and DELETE (session termination) using `StreamableHTTPServerTransport`.
- Uses an `InMemoryEventStore` for resumable sessions and tracks transports by `sessionId`.
- Connects a fresh server instance on initialization POST and reuses the transport for subsequent requests.
- Calls `clientConnected(sessionId)` to inform the server of the connection.
- tools/
- index.ts
- `registerTools(server)` orchestrator; delegates to tool factory/registration methods in individual tool files.
- echo.ts
- Registers an `echo` tool that takes a message and returns `Echo: {message}`.
- get-annotated-message.ts
- Registers an `annotated-message` tool which demonstrates annotated content items by emitting a primary `text` message with `annotations` that vary by `messageType` (`"error" | "success" | "debug"`), and optionally includes an annotated `image` (tiny PNG) when `includeImage` is true.
- get-env.ts
- Registers a `get-env` tool that returns the current process environment variables as formatted JSON text; useful for debugging configuration.
- get-resource-links.ts
- Registers a `get-resource-links` tool that returns an intro `text` block followed by multiple `resource_link` items.
- get-resource-reference.ts
- Registers a `get-resource-reference` tool that returns a reference for a selected dynamic resource.
- get-roots-list.ts
- Registers a `get-roots-list` tool that returns the last list of roots sent by the client.
- gzip-file-as-resource.ts
- Registers a `gzip-file-as-resource` tool that fetches content from a URL or data URI, compresses it, and then either:
- returns a `resource_link` to a session-scoped resource (default), or
- returns an inline `resource` with the gzipped data. The resource will be still discoverable for the duration of the session via `resources/list`.
- Uses `resources/session.ts` to register the gzipped blob as a per-session resource at a URI like `demo://resource/session/<name>` with `mimeType: application/gzip`.
- Environment controls:
- `GZIP_MAX_FETCH_SIZE` (bytes, default 10 MiB)
- `GZIP_MAX_FETCH_TIME_MILLIS` (ms, default 30000)
- `GZIP_ALLOWED_DOMAINS` (comma-separated allowlist; empty means all domains allowed)
- trigger-elicitation-request.ts
- Registers a `trigger-elicitation-request` tool that sends an `elicitation/create` request to the client/LLM and returns the elicitation result.
- trigger-sampling-request.ts
- Registers a `trigger-sampling-request` tool that sends a `sampling/createMessage` request to the client/LLM and returns the sampling result.
- get-structured-content.ts
- Registers a `get-structured-content` tool that demonstrates structuredContent block responses.
- get-sum.ts
- Registers an `get-sum` tool with a Zod input schema that sums two numbers `a` and `b` and returns the result.
- get-tiny-image.ts
- Registers a `get-tiny-image` tool, which returns a tiny PNG MCP logo as an `image` content item, along with surrounding descriptive `text` items.
- long-running-operation.ts
- Registers a `long-running-operation` tool that simulates a long-running task over a specified `duration` (seconds) and number of `steps`; emits `notifications/progress` updates when the client supplies a `progressToken`.
- toggle-logging.ts
- Registers a `toggle-logging` tool, which starts or stops simulated logging for the invoking session.
- toggle-subscriber-updates.ts
- Registers a `toggle-subscriber-updates` tool, which starts or stops simulated resource subscription update checks for the invoking session.
- prompts/
- index.ts
- `registerPrompts(server)` orchestrator; delegates to prompt factory/registration methods from in individual prompt files.
- simple.ts
- Registers `simple-prompt`: a prompt with no arguments that returns a single user message.
- args.ts
- Registers `args-prompt`: a prompt with two arguments (`city` required, `state` optional) used to compose a message.
- completions.ts
- Registers `completable-prompt`: a prompt whose arguments support server-driven completions using the SDKs `completable(...)` helper (e.g., completing `department` and context-aware `name`).
- resource.ts
- Exposes `registerEmbeddedResourcePrompt(server)` which registers `resource-prompt` — a prompt that accepts `resourceType` ("Text" or "Blob") and `resourceId` (integer), and embeds a dynamically generated resource of the requested type within the returned messages. Internally reuses helpers from `resources/templates.ts`.
- resources/
- index.ts
- `registerResources(server)` orchestrator; delegates to resource factory/registration methods from individual resource files.
- templates.ts
- Registers two dynamic, templatedriven resources using `ResourceTemplate`:
- Text: `demo://resource/dynamic/text/{index}` (MIME: `text/plain`)
- Blob: `demo://resource/dynamic/blob/{index}` (MIME: `application/octet-stream`, Base64 payload)
- The `{index}` path variable must be a finite positive integer. Content is generated on demand with a timestamp.
- Exposes helpers `textResource(uri, index)`, `textResourceUri(index)`, `blobResource(uri, index)`, and `blobResourceUri(index)` so other modules can construct and embed dynamic resources directly (e.g., from prompts).
- files.ts
- Registers static file-based resources for each file in the `docs/` folder.
- URIs follow the pattern: `demo://resource/static/document/<filename>`.
- Serves markdown files as `text/markdown`, `.txt` as `text/plain`, `.json` as `application/json`, others default to `text/plain`.
- docs/
- architecture.md (this document)
- server-instructions.md
- Humanreadable instructions intended to be passed to the client/LLM as for guidance on server use. Loaded by the server at startup and returned in the "initialize" exchange.
- package.json
- Package metadata and scripts:
- `build`: TypeScript compile to `dist/`, copies `docs/` into `dist/` and marks the compiled entry scripts as executable.
- `start:stdio`, `start:sse`, `start:streamableHttp`: Run built transports from `dist/`.
- Declares dependencies on `@modelcontextprotocol/sdk`, `express`, `cors`, `zod`, etc.
## Startup and Runtime Flow
1. A transport is chosen via the CLI entry `index.ts`:
- `node dist/index.js stdio` → loads `transports/stdio.js`
- `node dist/index.js sse` → loads `transports/sse.js`
- `node dist/index.js streamableHttp` → loads `transports/streamableHttp.js`
2. The transport creates the server via `createServer()` from `server/index.ts` and connects it to the chosen transport type from the MCP SDK.
3. The server factory (`server/index.ts`) does the following:
- Creates `new McpServer({ name, title, version }, { capabilities, instructions })`.
- Capabilities:
- `tools: {}`
- `logging: {}`
- `prompts: {}`
- `resources: { subscribe: true }`
- Loads humanreadable “server instructions” from the docs folder (`server-instructions.md`).
- Registers tools via `registerTools(server)`.
- Registers resources via `registerResources(server)`.
- Registers prompts via `registerPrompts(server)`.
- Sets up resource subscription handlers via `setSubscriptionHandlers(server)`.
- Returns the server, a `clientConnect(sessionId)` callback, and a `cleanup(sessionId?)` callback that stops any active intervals and removes any sessionscoped state.
4. Each transport is responsible for network/session lifecycle:
- STDIO: simple processbound connection; closes on `SIGINT` and calls`clientConnect()` and `cleanup()`.
- SSE: maintains a session map keyed by `sessionId`; calls `clientConnect(sessionId)` on connection, hooks servers `onclose` to clean and remove session; exposes `/sse` (GET) and `/message` (POST) endpoints.
- Streamable HTTP: exposes `/mcp` for POST (JSONRPC messages), GET (SSE stream), and DELETE (termination). Uses an event store for resumability and stores transports by `sessionId`. Calls `clientConnect(sessionId)` on connection and calls `cleanup(sessionId)` on DELETE.
## Registered Features (current minimal set)
- Tools
- `echo` (tools/echo.ts): Echoes the provided `message: string`. Uses Zod to validate inputs.
- `get-annotated-message` (tools/get-annotated-message.ts): Returns a `text` message annotated with `priority` and `audience` based on `messageType` (`error`, `success`, or `debug`); can optionally include an annotated `image`.
- `get-env` (tools/get-env.ts): Returns all environment variables from the running process as pretty-printed JSON text.
- `get-resource-links` (tools/get-resource-links.ts): Returns an intro `text` block followed by multiple `resource_link` items. For a requested `count` (110), alternates between dynamic Text and Blob resources using URIs from `resources/templates.ts`.
- `get-resource-reference` (tools/get-resource-reference.ts): Accepts `resourceType` (`text` or `blob`) and `resourceId` (positive integer). Returns a concrete `resource` content block (with its `uri`, `mimeType`, and data) with surrounding explanatory `text`.
- `get-roots-list` (tools/get-roots-list.ts): Returns the last list of roots sent by the client.
- `gzip-file-as-resource` (tools/gzip-file-as-resource.ts): Accepts a `name` and `data` (URL or data URI), fetches the data subject to size/time/domain constraints, compresses it, registers it as a session resource at `demo://resource/session/<name>` with `mimeType: application/gzip`, and returns either a `resource_link` (default) or an inline `resource` depending on `outputType`.
- `get-structured-content` (tools/get-structured-content.ts): Demonstrates structured responses. Accepts `location` input and returns both backwardcompatible `content` (a `text` block containing JSON) and `structuredContent` validated by an `outputSchema` (temperature, conditions, humidity).
- `get-sum` (tools/get-sum.ts): For two numbers `a` and `b` calculates and returns their sum. Uses Zod to validate inputs.
- `get-tiny-image` (tools/get-tiny-image.ts): Returns a tiny PNG MCP logo as an `image` content item with brief descriptive text before and after.
- `long-running-operation` (tools/long-running-operation.ts): Simulates a multi-step operation over a given `duration` and number of `steps`; reports progress via `notifications/progress` when a `progressToken` is provided by the client.
- `toggle-logging` (tools/toggle-logging.ts): Starts or stops simulated, randomleveled logging for the invoking session. Respects the clients selected minimum logging level.
- `toggle-subscriber-updates` (tools/toggle-subscriber-updates.ts): Starts or stops simulated resource update notifications for URIs the invoking session has subscribed to.
- `trigger-sampling-request` (tools/trigger-sampling-request.ts): Issues a `sampling/createMessage` request to the client/LLM using provided `prompt` and optional generation controls; returns the LLMs response payload.
- Prompts
- `simple-prompt` (prompts/simple.ts): No-argument prompt that returns a static user message.
- `args-prompt` (prompts/args.ts): Two-argument prompt with `city` (required) and `state` (optional) used to compose a question.
- `completable-prompt` (prompts/completions.ts): Demonstrates argument auto-completions with the SDKs `completable` helper; `department` completions drive context-aware `name` suggestions.
- `resource-prompt` (prompts/resource.ts): Accepts `resourceType` ("Text" or "Blob") and `resourceId` (string convertible to integer) and returns messages that include an embedded dynamic resource of the selected type generated via `resources/templates.ts`.
- Resources
- Dynamic Text: `demo://resource/dynamic/text/{index}` (content generated on the fly)
- Dynamic Blob: `demo://resource/dynamic/blob/{index}` (base64 payload generated on the fly)
- Static Documents: `demo://resource/static/document/<filename>` (serves files from `src/everything/docs/` as static file-based resources)
- Session Scoped: `demo://resource/session/<name>` (per-session resources registered dynamically; available only for the lifetime of the session)
- Resource Subscriptions and Notifications
- Clients may subscribe/unsubscribe to resource URIs using the MCP `resources/subscribe` and `resources/unsubscribe` requests.
- Simulated update notifications are optin and off by default. Use the `toggle-subscriber-updates` tool to start/stop a persession interval that emits `notifications/resources/updated { uri }` only for URIs that session has subscribed to.
- Multiple concurrent clients are supported; each clients subscriptions are tracked per session and notifications are delivered independently via the server instance associated with that session.
- Logging
- Simulated logging is available but off by default. Use the `toggle-logging` tool to start/stop periodic log messages of varying levels (debug, info, notice, warning, error, critical, alert, emergency) per session. Clients can control the minimum level they receive via the standard MCP `logging/setLevel` request.
## Extension Points
- Adding Tools
- Create a new file under `tools/` with your `registerXTool(server)` function that registers the tool via `server.registerTool(...)`.
- Export and call it from `tools/index.ts` inside `registerTools(server)`.
- Adding Prompts
- Create a new file under `prompts/` with your `registerXPrompt(server)` function that registers the prompt via `server.registerPrompt(...)`.
- Export and call it from `prompts/index.ts` inside `registerPrompts(server)`.
- Adding Resources
- Create a new file under `resources/` with your `registerXResources(server)` function using `server.registerResource(...)` (optionally with `ResourceTemplate`).
- Export and call it from `resources/index.ts` inside `registerResources(server)`.
## Resource Subscriptions How It Works
- Module: `resources/subscriptions.ts`
- Tracks subscribers per URI: `Map<uri, Set<sessionId>>`.
- Installs handlers via `setSubscriptionHandlers(server)` to process subscribe/unsubscribe requests and keep the map updated.
- Updates are started/stopped on demand by the `toggle-subscriber-updates` tool, which calls `beginSimulatedResourceUpdates(server, sessionId)` and `stopSimulatedResourceUpdates(sessionId)`.
- `cleanup(sessionId?)` calls `stopSimulatedResourceUpdates(sessionId)` to clear intervals and remove sessionscoped state.
- Design note: Each client session has its own `McpServer` instance; periodic checks run per session and invoke `server.notification(...)` on that instance, so messages are delivered only to the intended client.
## Sessionscoped Resources How It Works
- Module: `resources/session.ts`
- `getSessionResourceURI(name: string)`: Builds a session resource URI: `demo://resource/session/<name>`.
- `registerSessionResource(server, resource, type, payload)`: Registers a resource with the given `uri`, `name`, and `mimeType`, returning a `resource_link`. The content is served from memory for the life of the session only. Supports `type: "text" | "blob"` and returns data in the corresponding field.
- Intended usage: tools can create and expose per-session artifacts without persisting them. For example, `tools/gzip-file-as-resource.ts` gzips fetched content, registers it as a session resource with `mimeType: application/gzip`, and returns either a `resource_link` or an inline `resource` based on `outputType`.
## Simulated Logging How It Works
- Module: `server/logging.ts`
- Periodically sends randomized log messages at different levels. Messages can include the session ID for clarity during demos.
- Started/stopped on demand via the `toggle-logging` tool, which calls `beginSimulatedLogging(server, sessionId?)` and `stopSimulatedLogging(sessionId?)`. Note that transport disconnect triggers `cleanup()` which also stops any active intervals.
- Uses `server.sendLoggingMessage({ level, data }, sessionId?)` so that the clients configured minimum logging level is respected by the SDK.
- Adding Transports
- Implement a new transport module under `transports/`.
- Add a case to `index.ts` so the CLI can select it.
### Multiclient
The server supports multiple concurrent clients. Tracking per session data is demonstrated with
resource subscriptions and simulated logging.
## Build and Distribution
@@ -308,6 +29,12 @@ At `src/everything`:
- The `build` script copies `docs/` into `dist/` so instruction files ship alongside the compiled server.
- The CLI bin is configured in `package.json` as `mcp-server-everything``dist/index.js`.
## Relationship to the Full Reference Server
## [Project Structure](structure.md)
The large `server/everything.ts` shows a comprehensive MCP server showcasing many features (tools with schemas, prompts, resource operations, notifications, etc.). The current transports in this package use the lean factory from `server/index.ts` instead, keeping the runtime small and focused while preserving the reference implementation for learning and experimentation.
## [Startup Process](startup.md)
## [Server Features](features.md)
## [Extension Points](extension.md)
## [How It Works](how-it-works.md)

View File

@@ -0,0 +1,22 @@
# Everything Server - Extension Points
**[Architecture](architecture.md)
| [Project Structure](structure.md)
| [Startup Process](startup.md)
| [Server Features](features.md)
| Extension Points
| [How It Works](how-it-works.md)**
## Adding Tools
- Create a new file under `tools/` with your `registerXTool(server)` function that registers the tool via `server.registerTool(...)`.
- Export and call it from `tools/index.ts` inside `registerTools(server)`.
## Adding Prompts
- Create a new file under `prompts/` with your `registerXPrompt(server)` function that registers the prompt via `server.registerPrompt(...)`.
- Export and call it from `prompts/index.ts` inside `registerPrompts(server)`.
## Adding Resources
- Create a new file under `resources/` with your `registerXResources(server)` function using `server.registerResource(...)` (optionally with `ResourceTemplate`).
- Export and call it from `resources/index.ts` inside `registerResources(server)`.

View File

@@ -0,0 +1,51 @@
# Everything Server - Features
**[Architecture](architecture.md)
| [Project Structure](structure.md)
| [Startup Process](startup.md)
| Server Features
| [Extension Points](extension.md)
| [How It Works](how-it-works.md)**
## Tools
- `echo` (tools/echo.ts): Echoes the provided `message: string`. Uses Zod to validate inputs.
- `get-annotated-message` (tools/get-annotated-message.ts): Returns a `text` message annotated with `priority` and `audience` based on `messageType` (`error`, `success`, or `debug`); can optionally include an annotated `image`.
- `get-env` (tools/get-env.ts): Returns all environment variables from the running process as pretty-printed JSON text.
- `get-resource-links` (tools/get-resource-links.ts): Returns an intro `text` block followed by multiple `resource_link` items. For a requested `count` (110), alternates between dynamic Text and Blob resources using URIs from `resources/templates.ts`.
- `get-resource-reference` (tools/get-resource-reference.ts): Accepts `resourceType` (`text` or `blob`) and `resourceId` (positive integer). Returns a concrete `resource` content block (with its `uri`, `mimeType`, and data) with surrounding explanatory `text`.
- `get-roots-list` (tools/get-roots-list.ts): Returns the last list of roots sent by the client.
- `gzip-file-as-resource` (tools/gzip-file-as-resource.ts): Accepts a `name` and `data` (URL or data URI), fetches the data subject to size/time/domain constraints, compresses it, registers it as a session resource at `demo://resource/session/<name>` with `mimeType: application/gzip`, and returns either a `resource_link` (default) or an inline `resource` depending on `outputType`.
- `get-structured-content` (tools/get-structured-content.ts): Demonstrates structured responses. Accepts `location` input and returns both backwardcompatible `content` (a `text` block containing JSON) and `structuredContent` validated by an `outputSchema` (temperature, conditions, humidity).
- `get-sum` (tools/get-sum.ts): For two numbers `a` and `b` calculates and returns their sum. Uses Zod to validate inputs.
- `get-tiny-image` (tools/get-tiny-image.ts): Returns a tiny PNG MCP logo as an `image` content item with brief descriptive text before and after.
- `long-running-operation` (tools/long-running-operation.ts): Simulates a multi-step operation over a given `duration` and number of `steps`; reports progress via `notifications/progress` when a `progressToken` is provided by the client.
- `toggle-logging` (tools/toggle-logging.ts): Starts or stops simulated, randomleveled logging for the invoking session. Respects the clients selected minimum logging level.
- `toggle-subscriber-updates` (tools/toggle-subscriber-updates.ts): Starts or stops simulated resource update notifications for URIs the invoking session has subscribed to.
- `trigger-sampling-request` (tools/trigger-sampling-request.ts): Issues a `sampling/createMessage` request to the client/LLM using provided `prompt` and optional generation controls; returns the LLMs response payload.
## Prompts
- `simple-prompt` (prompts/simple.ts): No-argument prompt that returns a static user message.
- `args-prompt` (prompts/args.ts): Two-argument prompt with `city` (required) and `state` (optional) used to compose a question.
- `completable-prompt` (prompts/completions.ts): Demonstrates argument auto-completions with the SDKs `completable` helper; `department` completions drive context-aware `name` suggestions.
- `resource-prompt` (prompts/resource.ts): Accepts `resourceType` ("Text" or "Blob") and `resourceId` (string convertible to integer) and returns messages that include an embedded dynamic resource of the selected type generated via `resources/templates.ts`.
## Resources
- Dynamic Text: `demo://resource/dynamic/text/{index}` (content generated on the fly)
- Dynamic Blob: `demo://resource/dynamic/blob/{index}` (base64 payload generated on the fly)
- Static Documents: `demo://resource/static/document/<filename>` (serves files from `src/everything/docs/` as static file-based resources)
- Session Scoped: `demo://resource/session/<name>` (per-session resources registered dynamically; available only for the lifetime of the session)
## Resource Subscriptions and Notifications
- Simulated update notifications are optin and off by default.
- Clients may subscribe/unsubscribe to resource URIs using the MCP `resources/subscribe` and `resources/unsubscribe` requests.
- Use the `toggle-subscriber-updates` tool to start/stop a persession interval that emits `notifications/resources/updated { uri }` only for URIs that session has subscribed to.
- Multiple concurrent clients are supported; each clients subscriptions are tracked per session and notifications are delivered independently via the server instance associated with that session.
## Simulated Logging
- Simulated logging is available but off by default.
- Use the `toggle-logging` tool to start/stop periodic log messages of varying levels (debug, info, notice, warning, error, critical, alert, emergency) per session.
- Clients can control the minimum level they receive via the standard MCP `logging/setLevel` request.

View File

@@ -0,0 +1,34 @@
# Everything Server - How It Works
**[Architecture](architecture.md)
| [Project Structure](structure.md)
| [Startup Process](startup.md)
| [Server Features](features.md)
| [Extension Points](extension.md)
| How It Works**
## Resource Subscriptions
Each client manages its own resource subscriptions and receives notifications only for the URIs it subscribed to, independent of other clients.
### Module: `resources/subscriptions.ts`
- Tracks subscribers per URI: `Map<uri, Set<sessionId>>`.
- Installs handlers via `setSubscriptionHandlers(server)` to process subscribe/unsubscribe requests and keep the map updated.
- Updates are started/stopped on demand by the `toggle-subscriber-updates` tool, which calls `beginSimulatedResourceUpdates(server, sessionId)` and `stopSimulatedResourceUpdates(sessionId)`.
- `cleanup(sessionId?)` calls `stopSimulatedResourceUpdates(sessionId)` to clear intervals and remove sessionscoped state.
## Sessionscoped Resources
### Module: `resources/session.ts`
- `getSessionResourceURI(name: string)`: Builds a session resource URI: `demo://resource/session/<name>`.
- `registerSessionResource(server, resource, type, payload)`: Registers a resource with the given `uri`, `name`, and `mimeType`, returning a `resource_link`. The content is served from memory for the life of the session only. Supports `type: "text" | "blob"` and returns data in the corresponding field.
- Intended usage: tools can create and expose per-session artifacts without persisting them. For example, `tools/gzip-file-as-resource.ts` compresses fetched content, registers it as a session resource with `mimeType: application/gzip`, and returns either a `resource_link` or an inline `resource` based on `outputType`.
## Simulated Logging
### Module: `server/logging.ts`
- Periodically sends randomized log messages at different levels. Messages can include the session ID for clarity during demos.
- Started/stopped on demand via the `toggle-logging` tool, which calls `beginSimulatedLogging(server, sessionId?)` and `stopSimulatedLogging(sessionId?)`. Note that transport disconnect triggers `cleanup()` which also stops any active intervals.
- Uses `server.sendLoggingMessage({ level, data }, sessionId?)` so that the clients configured minimum logging level is respected by the SDK.

View File

@@ -1,4 +1,13 @@
Testing and demonstration server for MCP protocol features.
# Everything Server - Instructions
**[Architecture](architecture.md)
| [Project Structure](structure.md)
| [Startup Process](startup.md)
| [Server Features](features.md)
| [Extension Points](extension.md)
| [How It Works](how-it-works.md)**
A testing and demonstration server for MCP protocol features.
TODO: Update this doc
## Resources

View File

@@ -0,0 +1,113 @@
# Everything Server - Startup Flow
**[Architecture](architecture.md)
| [Project Structure](structure.md)
| Startup Process
| [Server Features](features.md)
| [Extension Points](extension.md)
| [How It Works](how-it-works.md)**
## 1. Everything Server Launcher
- Usage `node dist/index.js [stdio|sse|streamableHttp]`
- Runs the specified **transport manager** to handle client connections.
- Specify transport type on command line (default `stdio`)
- `stdio``transports/stdio.js`
- `sse``transports/sse.js`
- `streamableHttp``transports/streamableHttp.js`
## 2. The Transport Manager
- Creates a server instance using `createServer()` from `server/index.ts`
- Connects it to the chosen transport type from the MCP SDK.
- Calls the `clientConnected()` callback upon transport connection.
- Handles communication according to the MCP specs for the chosen transport.
- **STDIO**:
- One simple, processbound connection.
- Calls`clientConnect()` upon connection.
- Closes and calls `cleanup()` on `SIGINT`.
- **SSE**:
- Supports multiple client connections.
- Client transports are mapped to `sessionId`;
- Calls `clientConnect(sessionId)` upon connection.
- Hooks servers `onclose` to clean and remove session.
- Exposes
- `/sse` **GET** (SSE stream)
- `/message` **POST** (JSONRPC messages)
- **Streamable HTTP**:
- Supports multiple client connections.
- Client transports are mapped to `sessionId`;
- Calls `clientConnect(sessionId)` upon connection.
- Exposes `/mcp` for
- **POST** (JSONRPC messages)
- **GET** (SSE stream)
- **DELETE** (termination)
- Uses an event store for resumability and stores transports by `sessionId`.
- Calls `cleanup(sessionId)` on **DELETE**.
## 3. The Server Factory
- Invoke `createServer()` from `server/index.ts`
- Creates a new `McpServer` instance with
- **Capabilities**:
- `tools: {}`
- `logging: {}`
- `prompts: {}`
- `resources: { subscribe: true }`
- **Server Instructions**
- Loaded from the docs folder (`server-instructions.md`).
- **Registrations**
- Registers **tools** via `registerTools(server)`.
- Registers **resources** via `registerResources(server)`.
- Registers **prompts** via `registerPrompts(server)`.
- **Other Request Handlers**
- Sets up resource subscription handlers via `setSubscriptionHandlers(server)`.
- Roots list change handler is added post-connection via
- **Returns**
- The `McpServer` instance
- A `clientConnect(sessionId)` callback that enables post-connection setup
- A `cleanup(sessionId?)` callback that stops any active intervals and removes any sessionscoped state
## Enabling Multiple Clients
Some of the transport managers defined in the `transports` folder can support multiple clients.
In order to do so, they must map certain data to a session identifier.
### About the `clientConnected` callback returned by the Server Factory
Some server functions require a `sessionId` but can't reach it via its scope.
For instance, the automatic log-level handling in the Typescript SDK tracks
the client's requested logging level by `sessionId`. In order
So, the Server Factory provides a callback to allow the chosen Transport Manager
to provide the server with the `sessionId` (or `undefined`) for each new connection.
### On `clientConnected` vs `server.oninitialized` for post-connection setup
#### Q:
> Why not hook `server.server.oninitialized` to trigger post-connection setup?
> You could call `syncRoots` in a handler, obviating the `clientConnected` hook.
#### A:
In `oninitialized`, a transport is connected, but there is no way to access it
or its `sessionId`. Therefore, calling any function that needs a `sessionId` is
right out.
#### Q:
> Why is it important to have access to the `sessionId` anywhere but in a request
> handler?
### A:
When setting up a server that tracks any data per session, you need to map
that data to a `sessionId`. See `logging.ts` and `subscriptions.ts` for examples.
In an STDIO server, it doesn't matter because there is one client per server.
Features that track data by `sessionId` can accept `undefined` for that value
and still track session-scoped data for STDIO clients.
But with HTTP protocols, you can have multiple clients. So you have to track
their logging intervals, resource subscriptions, and other session-scoped
data per client.

View File

@@ -0,0 +1,177 @@
# Everything Server - Project Structure
**[Architecture](architecture.md)
| Project Structure
| [Startup Process](startup.md)
| [Server Features](features.md)
| [Extension Points](extension.md)
| [How It Works](how-it-works.md)**
```
src/everything
├── index.ts
├── package.json
├── docs
│ ├── architecture.md
│ └── server-instructions.md
├── prompts
│ ├── index.ts
│ ├── args.ts
│ ├── completions.ts
│ ├── simple.ts
│ └── resource.ts
├── resources
│ ├── index.ts
│ ├── files.ts
│ ├── session.ts
│ ├── subscriptions.ts
│ └── templates.ts
├── server
│ ├── index.ts
│ ├── logging.ts
│ ├── roots.ts
│ └── everything.ts
├── tools
│ ├── index.ts
│ ├── echo.ts
│ ├── get-annotated-message.ts
│ ├── get-env.ts
│ ├── get-resource-links.ts
│ ├── get-resource-reference.ts
│ ├── get-roots-list.ts
│ ├── get-structured-content.ts
│ ├── get-sum.ts
│ ├── get-tiny-image.ts
│ ├── gzip-file-as-resource.ts
│ ├── long-running-operation.ts
│ ├── toggle-logging.ts
│ ├── toggle-subscriber-updates.ts
│ ├── trigger-elicitation-request.ts
│ └── trigger-sampling-request.ts
└── transports
├── sse.ts
├── stdio.ts
└── streamableHttp.ts
```
# Project Contents
## `src/everything`:
### `index.ts`
- CLI entry point that selects and runs a specific transport module based on the first CLI argument: `stdio`, `sse`, or `streamableHttp`.
### `package.json`
- Package metadata and scripts:
- `build`: TypeScript compile to `dist/`, copies `docs/` into `dist/` and marks the compiled entry scripts as executable.
- `start:stdio`, `start:sse`, `start:streamableHttp`: Run built transports from `dist/`.
- Declares dependencies on `@modelcontextprotocol/sdk`, `express`, `cors`, `zod`, etc.
### `docs/`
- `architecture.md`
- This document.
- `server-instructions.md`
- Humanreadable instructions intended to be passed to the client/LLM as for guidance on server use. Loaded by the server at startup and returned in the "initialize" exchange.
### `prompts/`
- `index.ts`
- `registerPrompts(server)` orchestrator; delegates to prompt factory/registration methods from in individual prompt files.
- `simple.ts`
- Registers `simple-prompt`: a prompt with no arguments that returns a single user message.
- `args.ts`
- Registers `args-prompt`: a prompt with two arguments (`city` required, `state` optional) used to compose a message.
- `completions.ts`
- Registers `completable-prompt`: a prompt whose arguments support server-driven completions using the SDKs `completable(...)` helper (e.g., completing `department` and context-aware `name`).
- `resource.ts`
- Exposes `registerEmbeddedResourcePrompt(server)` which registers `resource-prompt` — a prompt that accepts `resourceType` ("Text" or "Blob") and `resourceId` (integer), and embeds a dynamically generated resource of the requested type within the returned messages. Internally reuses helpers from `resources/templates.ts`.
### `resources/`
- `index.ts`
- `registerResources(server)` orchestrator; delegates to resource factory/registration methods from individual resource files.
- `templates.ts`
- Registers two dynamic, templatedriven resources using `ResourceTemplate`:
- Text: `demo://resource/dynamic/text/{index}` (MIME: `text/plain`)
- Blob: `demo://resource/dynamic/blob/{index}` (MIME: `application/octet-stream`, Base64 payload)
- The `{index}` path variable must be a finite positive integer. Content is generated on demand with a timestamp.
- Exposes helpers `textResource(uri, index)`, `textResourceUri(index)`, `blobResource(uri, index)`, and `blobResourceUri(index)` so other modules can construct and embed dynamic resources directly (e.g., from prompts).
- `files.ts`
- Registers static file-based resources for each file in the `docs/` folder.
- URIs follow the pattern: `demo://resource/static/document/<filename>`.
- Serves markdown files as `text/markdown`, `.txt` as `text/plain`, `.json` as `application/json`, others default to `text/plain`.
### `server/`
- `index.ts`
- Server factory that creates an `McpServer` with declared capabilities, loads server instructions, and registers tools, prompts, and resources.
- Sets resource subscription handlers via `setSubscriptionHandlers(server)`.
- Exposes `{ server, cleanup }` to the chosen transport. Cleanup stops any running intervals in the server when the transport disconencts.
- `logging.ts`
- Implements simulated logging. Periodically sends randomized log messages at various levels to the connected client session. Started/stopped on demand via a dedicated tool.
- `everything.ts`
- A full “reference/monolith” implementation demonstrating most MCP features. Not the default path used by the transports in this package.
### `tools/`
- `index.ts`
- `registerTools(server)` orchestrator; delegates to tool factory/registration methods in individual tool files.
- `echo.ts`
- Registers an `echo` tool that takes a message and returns `Echo: {message}`.
- `get-annotated-message.ts`
- Registers an `annotated-message` tool which demonstrates annotated content items by emitting a primary `text` message with `annotations` that vary by `messageType` (`"error" | "success" | "debug"`), and optionally includes an annotated `image` (tiny PNG) when `includeImage` is true.
- `get-env.ts`
- Registers a `get-env` tool that returns the current process environment variables as formatted JSON text; useful for debugging configuration.
- `get-resource-links.ts`
- Registers a `get-resource-links` tool that returns an intro `text` block followed by multiple `resource_link` items.
- `get-resource-reference.ts`
- Registers a `get-resource-reference` tool that returns a reference for a selected dynamic resource.
- `get-roots-list.ts`
- Registers a `get-roots-list` tool that returns the last list of roots sent by the client.
- `gzip-file-as-resource.ts`
- Registers a `gzip-file-as-resource` tool that fetches content from a URL or data URI, compresses it, and then either:
- returns a `resource_link` to a session-scoped resource (default), or
- returns an inline `resource` with the gzipped data. The resource will be still discoverable for the duration of the session via `resources/list`.
- Uses `resources/session.ts` to register the gzipped blob as a per-session resource at a URI like `demo://resource/session/<name>` with `mimeType: application/gzip`.
- Environment controls:
- `GZIP_MAX_FETCH_SIZE` (bytes, default 10 MiB)
- `GZIP_MAX_FETCH_TIME_MILLIS` (ms, default 30000)
- `GZIP_ALLOWED_DOMAINS` (comma-separated allowlist; empty means all domains allowed)
- `trigger-elicitation-request.ts`
- Registers a `trigger-elicitation-request` tool that sends an `elicitation/create` request to the client/LLM and returns the elicitation result.
- `trigger-sampling-request.ts`
- Registers a `trigger-sampling-request` tool that sends a `sampling/createMessage` request to the client/LLM and returns the sampling result.
- `get-structured-content.ts`
- Registers a `get-structured-content` tool that demonstrates structuredContent block responses.
- `get-sum.ts`
- Registers an `get-sum` tool with a Zod input schema that sums two numbers `a` and `b` and returns the result.
- `get-tiny-image.ts`
- Registers a `get-tiny-image` tool, which returns a tiny PNG MCP logo as an `image` content item, along with surrounding descriptive `text` items.
- `long-running-operation.ts`
- Registers a `long-running-operation` tool that simulates a long-running task over a specified `duration` (seconds) and number of `steps`; emits `notifications/progress` updates when the client supplies a `progressToken`.
- `toggle-logging.ts`
- Registers a `toggle-logging` tool, which starts or stops simulated logging for the invoking session.
- `toggle-subscriber-updates.ts`
- Registers a `toggle-subscriber-updates` tool, which starts or stops simulated resource subscription update checks for the invoking session.
### `transports/`
- `stdio.ts`
- Starts a `StdioServerTransport`, created the server via `createServer()`, and connects it.
- Calls `clientConnected()` to inform the server of the connection.
- Handles `SIGINT` to close cleanly and calls `cleanup()` to remove any live intervals.
- `sse.ts`
- Express server exposing:
- `GET /sse` to establish an SSE connection per session.
- `POST /message` for client messages.
- Manages multiple connected clients via a transport map.
- Starts an `SSEServerTransport`, created the server via `createServer()`, and connects it to a new transport.
- Calls `clientConnected(sessionId)` to inform the server of the connection.
- On server disconnect, calls `cleanup()` to remove any live intervals.
- `streamableHttp.ts`
- Express server exposing a single `/mcp` endpoint for POST (JSONRPC), GET (SSE stream), and DELETE (session termination) using `StreamableHTTPServerTransport`.
- Uses an `InMemoryEventStore` for resumable sessions and tracks transports by `sessionId`.
- Connects a fresh server instance on initialization POST and reuses the transport for subsequent requests.
- Calls `clientConnected(sessionId)` to inform the server of the connection.

View File

@@ -21,8 +21,13 @@ async function run() {
await import("./transports/streamableHttp.js");
break;
default:
console.error(`Unknown script: ${scriptName}`);
console.log("Available scripts:");
console.error(`-`.repeat(53));
console.error(` Everything Server Launcher`);
console.error(` Usage: node ./index.js [stdio|sse|streamableHttp]`);
console.error(` Default transport: stdio`);
console.error(`-`.repeat(53));
console.error(`Unknown transport: ${scriptName}`);
console.log("Available transports:");
console.log("- stdio");
console.log("- sse");
console.log("- streamableHttp");
@@ -34,4 +39,4 @@ async function run() {
}
}
run();
await run();

View File

@@ -1,6 +1,9 @@
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { registerResourceTemplates } from "./templates.js";
import { registerFileResources } from "./files.js";
import { fileURLToPath } from "url";
import { dirname, join } from "path";
import { readFileSync } from "fs";
/**
* Register the resources with the MCP server.
@@ -10,3 +13,24 @@ export const registerResources = (server: McpServer) => {
registerResourceTemplates(server);
registerFileResources(server);
};
/**
* Reads the server instructions from the corresponding markdown file.
* Attempts to load the content of the file located in the `docs` directory.
* If the file cannot be loaded, an error message is returned instead.
*
* @return {string} The content of the server instructions file, or an error message if reading fails.
*/
export function readInstructions(): string {
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const filePath = join(__dirname, "..", "docs", "instructions.md");
let instructions;
try {
instructions = readFileSync(filePath, "utf-8");
} catch (e) {
instructions = "Server instructions not loaded: " + e;
}
return instructions;
}

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);
};

View File

@@ -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}`);

View File

@@ -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();

View File

@@ -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