Skip to content

@W-22169002: Use composition over inheritance for Server class#321

Open
anyoung-tableau wants to merge 4 commits intomainfrom
anyoung/server-composition
Open

@W-22169002: Use composition over inheritance for Server class#321
anyoung-tableau wants to merge 4 commits intomainfrom
anyoung/server-composition

Conversation

@anyoung-tableau
Copy link
Copy Markdown
Collaborator

@anyoung-tableau anyoung-tableau commented Apr 21, 2026

These changes update the Server class so it no longer inherits from the McpServer class from the SDK and instead keeps an instance of McpServer inside it. This change is being made to ultimately support a world where multiple Server instances can contain a shared McpServer instance. Concrete example: WebMcpServer and DesktopMcpServer can both exist and share a single instance of McpServer, allowing each to register their tools simultaneously. See index.combined.ts in #331

@anyoung-tableau anyoung-tableau marked this pull request as ready for review April 23, 2026 21:20
Copilot AI review requested due to automatic review settings April 23, 2026 21:20
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Refactors the internal Server wrapper to use composition rather than inheritance with the MCP SDK’s McpServer, enabling future scenarios where multiple server variants (e.g., web + desktop) can share a single McpServer instance.

Changes:

  • Replace Server extends McpServer with Server holding a readonly mcpServer: McpServer.
  • Update transports and logging to call MCP SDK APIs via server.mcpServer.* (connect/close/notification/registerTool).
  • Update unit tests and global test mocks to match the new Server shape.

Reviewed changes

Copilot reviewed 9 out of 10 changed files in this pull request and generated no comments.

Show a summary per file
File Description
src/tools/pulse/listMetricSubscriptions/listPulseMetricSubscriptions.test.ts Updates test mocking to stub server.mcpServer.registerTool.
src/testSetup.ts Updates the mocked Server shape to include mcpServer.server.notification.
src/server/express.ts Uses server.mcpServer.connect() / .close() with HTTP transport lifecycle.
src/server.ts Core refactor: Server now composes an McpServer instance and delegates registrations/handlers through it.
src/server.test.ts Updates expectations and stubs to use server.mcpServer.registerTool and server.mcpServer.server.setRequestHandler.
src/logging/notification.ts Sends notifications via server.mcpServer.server.notification.
src/logging/notification.test.ts Updates notification assertions to match the new nested server path.
src/index.ts Uses server.mcpServer.connect() for stdio transport startup.
package.json Bumps package version to 1.18.8.
package-lock.json Updates lockfile version metadata to 1.18.8.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/server/express.ts
res.on('close', () => {
transport.close();
server.close();
server.mcpServer.close();
Copy link
Copy Markdown
Contributor

@mattcfilbert mattcfilbert Apr 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Small concern with exposing mcpServer directly now that Server is composition-based

We already reach through it for connect() and close(), and res.on('close') closing the shared inner server feels risky if we ever have multiple wrappers over one McpServer

Do we want to keep mcpServer private and expose wrapper-owned lifecycle methods instead, so the ownership model stays clear?

Comment thread src/server.ts

registerRequestHandlers = (): void => {
this.server.setRequestHandler(SetLevelRequestSchema, async (request) => {
this.mcpServer.server.setRequestHandler(SetLevelRequestSchema, async (request) => {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will this leave us exposed to handler collisions in the shared-server case?

setRequestHandler(SetLevelRequestSchema, ...) is a single slot on the underlying SDK server, so if multiple wrappers call registerRequestHandlers() on the same McpServer, the later registration wins

Do we want that handler owned at the shared layer instead of per-wrapper?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants