mirror of
https://github.com/modelcontextprotocol/servers.git
synced 2026-02-19 11:54:58 -05:00
[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:
@@ -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.
|
||||
|
||||
## High‑level 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 resource‑update notifications are opt‑in 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.
|
||||
|
||||
- Multi‑client 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 (JSON‑RPC), 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 SDK’s `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, template‑driven 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
|
||||
- Human‑readable 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 human‑readable “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 session‑scoped state.
|
||||
|
||||
4. Each transport is responsible for network/session lifecycle:
|
||||
- STDIO: simple process‑bound connection; closes on `SIGINT` and calls`clientConnect()` and `cleanup()`.
|
||||
- SSE: maintains a session map keyed by `sessionId`; calls `clientConnect(sessionId)` on connection, hooks server’s `onclose` to clean and remove session; exposes `/sse` (GET) and `/message` (POST) endpoints.
|
||||
- Streamable HTTP: exposes `/mcp` for POST (JSON‑RPC 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` (1–10), 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 backward‑compatible `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, random‑leveled logging for the invoking session. Respects the client’s 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 LLM’s 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 SDK’s `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 opt‑in and off by default. Use the `toggle-subscriber-updates` tool to start/stop a per‑session interval that emits `notifications/resources/updated { uri }` only for URIs that session has subscribed to.
|
||||
- Multiple concurrent clients are supported; each client’s 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 session‑scoped 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.
|
||||
|
||||
## Session‑scoped 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 client’s 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.
|
||||
### Multi‑client
|
||||
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)
|
||||
|
||||
22
src/everything/docs/extension.md
Normal file
22
src/everything/docs/extension.md
Normal 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)`.
|
||||
51
src/everything/docs/features.md
Normal file
51
src/everything/docs/features.md
Normal 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` (1–10), 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 backward‑compatible `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, random‑leveled logging for the invoking session. Respects the client’s 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 LLM’s 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 SDK’s `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 opt‑in 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 per‑session interval that emits `notifications/resources/updated { uri }` only for URIs that session has subscribed to.
|
||||
- Multiple concurrent clients are supported; each client’s 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.
|
||||
34
src/everything/docs/how-it-works.md
Normal file
34
src/everything/docs/how-it-works.md
Normal 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 session‑scoped state.
|
||||
|
||||
## Session‑scoped 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 client’s configured minimum logging level is respected by the SDK.
|
||||
@@ -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
|
||||
|
||||
113
src/everything/docs/startup.md
Normal file
113
src/everything/docs/startup.md
Normal 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, process‑bound 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 server’s `onclose` to clean and remove session.
|
||||
- Exposes
|
||||
- `/sse` **GET** (SSE stream)
|
||||
- `/message` **POST** (JSON‑RPC messages)
|
||||
- **Streamable HTTP**:
|
||||
- Supports multiple client connections.
|
||||
- Client transports are mapped to `sessionId`;
|
||||
- Calls `clientConnect(sessionId)` upon connection.
|
||||
- Exposes `/mcp` for
|
||||
- **POST** (JSON‑RPC 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 session‑scoped 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.
|
||||
177
src/everything/docs/structure.md
Normal file
177
src/everything/docs/structure.md
Normal 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`
|
||||
- Human‑readable 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 SDK’s `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, template‑driven 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 (JSON‑RPC), 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.
|
||||
@@ -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();
|
||||
|
||||
@@ -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
@@ -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);
|
||||
};
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user