[WIP] Refactor everything server to be more modular and use recommended APIs.

Adding resource subscriptions:

* Updated architecture.md

* In server/index.ts
  - imported Transport, setSubscriptionHandlers,beginSimulatedResourceUpdates, and stopSimulatedResourceUpdates
  - call setSubscriptionHandlers passing server
  - in returned object,
    - refactor/renamed startNotificationIntervals placehodler to clientConnected, which takes a transport argument and calls beginSimulatedResourceUpdates, passing the transport
    - replaced cleanup placeholder with a function that takes an optional sessionId and calls stopSimulatedResourceUpdates, passing the sessionId

* In sse.ts, stdio.ts, and streamableHttp.ts
  - when transport is connected, called clientConnect, passing transport
  - when disconnecting, called cleanup, passing sessionId

* Added subscriptions.ts
  - tracks subscriber session id lists by URI
  - tracks transport by session id
  - tracks subscription update intervals by sessionId
  - in setSubscriptionHandlers
    - set request handlers for SubscribeRequestSchema and UnsubscribeRequestSchema
  - in beginSimulatedResourceUpdates
    - starts an interval to send updates to the transport for all subscribed resources
  - in stopSimulatedResourceUpdates
    - removes intervals and transport for gien session id
This commit is contained in:
cliffhall
2025-12-06 19:44:07 -05:00
parent 7b2ff6b064
commit 07867a5dd5
6 changed files with 231 additions and 21 deletions

View File

@@ -10,6 +10,8 @@ This document summarizes the current layout and runtime architecture of the `src
- `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.
- 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
```
@@ -35,7 +37,8 @@ src/everything
├── resources
│ ├── index.ts
│ ├── templates.ts
── files.ts
── files.ts
│ └── subscriptions.ts
├── docs
│ ├── server-instructions.md
│ └── architecture.md
@@ -52,22 +55,23 @@ At `src/everything`:
- index.ts
- Server factory that creates an `McpServer` with declared capabilities, loads server instructions, and registers tools, prompts, and resources.
- Exposes `{ server, cleanup, startNotificationIntervals }` to the chosen transport.
- Sets resource subscription handlers via `setSubscriptionHandlers(server)`.
- Exposes `{ server, clientConnected, cleanup }` to the chosen transport.
- 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`, creates the server via `createServer()`, and connects it. Handles `SIGINT` to close cleanly.
- Starts a `StdioServerTransport`, creates the server via `createServer()`, connects it, and invokes `clientConnected(transport)` so simulated resource updates can begin. Handles `SIGINT` to close cleanly.
- sse.ts
- Express server exposing:
- `GET /sse` to establish an SSE connection per session.
- `POST /message` for client messages.
- Manages a `Map<sessionId, SSEServerTransport>` for sessions. Calls `startNotificationIntervals(sessionId)` after connect (hook currently a noop in the factory).
- Manages a `Map<sessionId, SSEServerTransport>` for sessions. Calls `clientConnected(transport)` after connect so persession simulated resource updates start.
- 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, then reuses transport for subsequent requests.
- Uses an `InMemoryEventStore` for resumable sessions and tracks transports by `sessionId`. Connects a fresh server instance on initialization POST, invokes `clientConnected(transport)`, then reuses the transport for subsequent requests.
- tools/
@@ -140,14 +144,15 @@ At `src/everything`:
- 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 and two lifecycle hooks:
- `cleanup`: transport may call on shutdown (currently a noop).
- `startNotificationIntervals(sessionId?)`: currently a noop; wired in SSE transport for future periodic notifications.
- `clientConnected(transport)`: transports call this after connecting so the server can begin persession simulated resource update notifications over that specific transport.
- `cleanup(sessionId?)`: transports call this on session termination to stop simulated updates and remove sessionscoped state.
4. Each transport is responsible for network/session lifecycle:
- STDIO: simple processbound connection; closes on `SIGINT`.
- SSE: maintains a session map keyed by `sessionId`, 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`.
- STDIO: simple processbound connection; calls `clientConnected(transport)` after connect; closes on `SIGINT` and calls `cleanup()`.
- SSE: maintains a session map keyed by `sessionId`, calls `clientConnected(transport)` after connect, 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 `clientConnected(transport)` on initialization and `cleanup(sessionId)` on DELETE.
## Registered Features (current minimal set)
@@ -168,6 +173,11 @@ At `src/everything`:
- Dynamic Blob: `demo://resource/dynamic/blob/{index}` (base64 payload generated on the fly)
- Static Docs: `demo://resource/static/document/<filename>` (serves files from `src/everything/docs/` as static file-based resources)
- Resource Subscriptions and Notifications
- Clients may subscribe/unsubscribe to resource URIs using the MCP `resources/subscribe` and `resources/unsubscribe` requests.
- The server sends simulated update notifications with method `notifications/resources/updated { uri }` only to transports (sessions) that subscribed to that URI.
- Multiple concurrent clients are supported; each clients subscriptions are tracked per session and notifications are delivered independently over that clients transport.
## Extension Points
- Adding Tools
@@ -185,6 +195,17 @@ At `src/everything`:
- 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>>`.
- Tracks active transports per session: `Map<sessionId, Transport>`.
- Installs handlers via `setSubscriptionHandlers(server)` to process subscribe/unsubscribe requests and keep the maps updated.
- `clientConnected(transport)` (from the server factory) calls `beginSimulatedResourceUpdates(transport)`, which starts a persession interval that scans subscribed URIs and emits `notifications/resources/updated` to that session only when applicable.
- `cleanup(sessionId?)` calls `stopSimulatedResourceUpdates(sessionId)` to clear intervals and remove transport/state for the session.
- Design note: Notifications are sent over the specific subscribers transport rather than broadcasting via `server.notification`, ensuring that each client receives only the updates for its own subscriptions.
- Adding Transports
- Implement a new transport module under `transports/`.
- Add a case to `index.ts` so the CLI can select it.