From 81a828815cb5894cd568c8fa7ef0d17ee94a0392 Mon Sep 17 00:00:00 2001 From: Felix Weinberger Date: Fri, 19 Dec 2025 14:28:32 +0100 Subject: [PATCH 1/2] docs: v2 follow-up improvements - Add v2 development notice at top of README - Fix npm badge URLs (add missing %2F encoding) - Update CONTRIBUTING.md for pnpm/corepack workflow - Document branch strategy (main vs v1.x) - Move InMemoryEventStore from server exports to test-helpers --- CONTRIBUTING.md | 45 +++++++++++++++---- README.md | 7 ++- packages/server/src/index.ts | 1 - .../src/helpers}/inMemoryEventStore.ts | 6 +-- test/helpers/src/index.ts | 1 + .../integration/test/taskResumability.test.ts | 3 +- 6 files changed, 47 insertions(+), 16 deletions(-) rename {packages/server/src/server => test/helpers/src/helpers}/inMemoryEventStore.ts (94%) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ace9542db..64c8ab140 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,20 +2,40 @@ We welcome contributions to the Model Context Protocol TypeScript SDK! This document outlines the process for contributing to the project. +## Branches + +This repository has two main branches: + +- **`main`** – v2 of the SDK (currently in development). This is a monorepo with split packages. +- **`v1.x`** – stable v1 release. Bug fixes and patches for v1 should target this branch. + +**Which branch should I use as a base?** + +- For **new features** or **v2-related work**: base your PR on `main` +- For **v1 bug fixes** or **patches**: base your PR on `v1.x` + ## Getting Started +This project uses [pnpm](https://pnpm.io/) as its package manager. If you don't have pnpm installed, enable it via [corepack](https://nodejs.org/api/corepack.html) (included with Node.js 16.9+): + +```bash +corepack enable +``` + +Then: + 1. Fork the repository 2. Clone your fork: `git clone https://github.com/YOUR-USERNAME/typescript-sdk.git` -3. Install dependencies: `npm install` -4. Build the project: `npm run build` -5. Run tests: `npm test` +3. Install dependencies: `pnpm install` +4. Build the project: `pnpm build:all` +5. Run tests: `pnpm test:all` ## Development Process -1. Create a new branch for your changes +1. Create a new branch for your changes (based on `main` or `v1.x` as appropriate) 2. Make your changes -3. Run `npm run lint` to ensure code style compliance -4. Run `npm test` to verify all tests pass +3. Run `pnpm lint:all` to ensure code style compliance +4. Run `pnpm test:all` to verify all tests pass 5. Submit a pull request ## Pull Request Guidelines @@ -28,8 +48,17 @@ We welcome contributions to the Model Context Protocol TypeScript SDK! This docu ## Running Examples -- Start the server: `npm run server` -- Run the client: `npm run client` +See [`examples/server/README.md`](examples/server/README.md) and [`examples/client/README.md`](examples/client/README.md) for a full list of runnable examples. + +Quick start: + +```bash +# Run a server example +pnpm --filter @modelcontextprotocol/examples-server exec tsx src/simpleStreamableHttp.ts + +# Run a client example (in another terminal) +pnpm --filter @modelcontextprotocol/examples-client exec tsx src/simpleStreamableHttp.ts +``` ## Code of Conduct diff --git a/README.md b/README.md index 5e5615035..f13727447 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,11 @@ # MCP TypeScript SDK -![NPM Version](https://img.shields.io/npm/v/%40modelcontextprotocol%server) ![NPM Version](https://img.shields.io/npm/v/%40modelcontextprotocol%client) ![MIT licensed](https://img.shields.io/npm/l/%40modelcontextprotocol%server) +> [!IMPORTANT] +> **This is the `main` branch which contains v2 of the SDK (currently in development).** +> +> For the stable v1 release, see the [`v1.x` branch](https://github.com/modelcontextprotocol/typescript-sdk/tree/v1.x) and its documentation. + +![NPM Version](https://img.shields.io/npm/v/%40modelcontextprotocol%2Fserver) ![NPM Version](https://img.shields.io/npm/v/%40modelcontextprotocol%2Fclient) ![MIT licensed](https://img.shields.io/npm/l/%40modelcontextprotocol%2Fserver)
Table of Contents diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index 8c9b9af5f..4b0c42053 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -1,6 +1,5 @@ export * from './server/completable.js'; export * from './server/express.js'; -export * from './server/inMemoryEventStore.js'; export * from './server/mcp.js'; export * from './server/server.js'; export * from './server/sse.js'; diff --git a/packages/server/src/server/inMemoryEventStore.ts b/test/helpers/src/helpers/inMemoryEventStore.ts similarity index 94% rename from packages/server/src/server/inMemoryEventStore.ts rename to test/helpers/src/helpers/inMemoryEventStore.ts index 57f9b1107..f78f782ea 100644 --- a/packages/server/src/server/inMemoryEventStore.ts +++ b/test/helpers/src/helpers/inMemoryEventStore.ts @@ -1,9 +1,7 @@ -import type { JSONRPCMessage } from '@modelcontextprotocol/core'; - -import type { EventStore } from './webStandardStreamableHttp.js'; +import type { EventStore, JSONRPCMessage } from '@modelcontextprotocol/server'; /** - * Simple in-memory implementation of the EventStore interface for resumability + * Simple in-memory implementation of the EventStore interface for resumability. * This is primarily intended for examples and testing, not for production use * where a persistent storage solution would be more appropriate. */ diff --git a/test/helpers/src/index.ts b/test/helpers/src/index.ts index 2154c889c..9d52ffee0 100644 --- a/test/helpers/src/index.ts +++ b/test/helpers/src/index.ts @@ -1,4 +1,5 @@ export * from './fixtures/zodTestMatrix.js'; export * from './helpers/http.js'; +export * from './helpers/inMemoryEventStore.js'; export * from './helpers/oauth.js'; export * from './helpers/tasks.js'; diff --git a/test/integration/test/taskResumability.test.ts b/test/integration/test/taskResumability.test.ts index 8dfd3a65a..93b2d7e19 100644 --- a/test/integration/test/taskResumability.test.ts +++ b/test/integration/test/taskResumability.test.ts @@ -4,13 +4,12 @@ import { createServer, type Server } from 'node:http'; import { Client, StreamableHTTPClientTransport } from '@modelcontextprotocol/client'; import { CallToolResultSchema, - InMemoryEventStore, LoggingMessageNotificationSchema, McpServer, StreamableHTTPServerTransport } from '@modelcontextprotocol/server'; import type { ZodMatrixEntry } from '@modelcontextprotocol/test-helpers'; -import { listenOnRandomPort, zodTestMatrix } from '@modelcontextprotocol/test-helpers'; +import { InMemoryEventStore, listenOnRandomPort, zodTestMatrix } from '@modelcontextprotocol/test-helpers'; describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => { const { z } = entry; From ea36d697e2a8386f855509a03bf22f03b0ce3281 Mon Sep 17 00:00:00 2001 From: Felix Weinberger Date: Fri, 19 Dec 2025 14:37:58 +0100 Subject: [PATCH 2/2] docs: v2 follow-up improvements - Add v2 development notice at top of README - Fix npm badge URLs (add missing %2F encoding) - Update CONTRIBUTING.md for pnpm/corepack workflow - Document branch strategy (main vs v1.x) - Remove InMemoryEventStore from server exports (inline in test instead) --- README.md | 6 +- .../helpers/src/helpers/inMemoryEventStore.ts | 77 ------------------- test/helpers/src/index.ts | 1 - .../integration/test/taskResumability.test.ts | 37 ++++++++- 4 files changed, 40 insertions(+), 81 deletions(-) delete mode 100644 test/helpers/src/helpers/inMemoryEventStore.ts diff --git a/README.md b/README.md index f13727447..dc0116c96 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,11 @@ # MCP TypeScript SDK > [!IMPORTANT] -> **This is the `main` branch which contains v2 of the SDK (currently in development).** +> **This is the `main` branch which contains v2 of the SDK (currently in development, pre-alpha).** > -> For the stable v1 release, see the [`v1.x` branch](https://github.com/modelcontextprotocol/typescript-sdk/tree/v1.x) and its documentation. +> We anticipate a stable v2 release in Q1 2026. Until then, **v1.x remains the recommended version** for production use. v1.x will continue to receive bug fixes and security updates for at least 6 months after v2 ships to give people time to upgrade. +> +> For v1 documentation and code, see the [`v1.x` branch](https://github.com/modelcontextprotocol/typescript-sdk/tree/v1.x). ![NPM Version](https://img.shields.io/npm/v/%40modelcontextprotocol%2Fserver) ![NPM Version](https://img.shields.io/npm/v/%40modelcontextprotocol%2Fclient) ![MIT licensed](https://img.shields.io/npm/l/%40modelcontextprotocol%2Fserver) diff --git a/test/helpers/src/helpers/inMemoryEventStore.ts b/test/helpers/src/helpers/inMemoryEventStore.ts deleted file mode 100644 index f78f782ea..000000000 --- a/test/helpers/src/helpers/inMemoryEventStore.ts +++ /dev/null @@ -1,77 +0,0 @@ -import type { EventStore, JSONRPCMessage } from '@modelcontextprotocol/server'; - -/** - * Simple in-memory implementation of the EventStore interface for resumability. - * This is primarily intended for examples and testing, not for production use - * where a persistent storage solution would be more appropriate. - */ -export class InMemoryEventStore implements EventStore { - private events: Map = new Map(); - - /** - * Generates a unique event ID for a given stream ID - */ - private generateEventId(streamId: string): string { - return `${streamId}_${Date.now()}_${Math.random().toString(36).substring(2, 10)}`; - } - - /** - * Extracts the stream ID from an event ID - */ - private getStreamIdFromEventId(eventId: string): string { - const parts = eventId.split('_'); - return parts.length > 0 ? parts[0]! : ''; - } - - /** - * Stores an event with a generated event ID - * Implements EventStore.storeEvent - */ - async storeEvent(streamId: string, message: JSONRPCMessage): Promise { - const eventId = this.generateEventId(streamId); - this.events.set(eventId, { streamId, message }); - return eventId; - } - - /** - * Replays events that occurred after a specific event ID - * Implements EventStore.replayEventsAfter - */ - async replayEventsAfter( - lastEventId: string, - { send }: { send: (eventId: string, message: JSONRPCMessage) => Promise } - ): Promise { - if (!lastEventId || !this.events.has(lastEventId)) { - return ''; - } - - // Extract the stream ID from the event ID - const streamId = this.getStreamIdFromEventId(lastEventId); - if (!streamId) { - return ''; - } - - let foundLastEvent = false; - - // Sort events by eventId for chronological ordering - const sortedEvents = [...this.events.entries()].sort((a, b) => a[0].localeCompare(b[0])); - - for (const [eventId, { streamId: eventStreamId, message }] of sortedEvents) { - // Only include events from the same stream - if (eventStreamId !== streamId) { - continue; - } - - // Start sending events after we find the lastEventId - if (eventId === lastEventId) { - foundLastEvent = true; - continue; - } - - if (foundLastEvent) { - await send(eventId, message); - } - } - return streamId; - } -} diff --git a/test/helpers/src/index.ts b/test/helpers/src/index.ts index 9d52ffee0..2154c889c 100644 --- a/test/helpers/src/index.ts +++ b/test/helpers/src/index.ts @@ -1,5 +1,4 @@ export * from './fixtures/zodTestMatrix.js'; export * from './helpers/http.js'; -export * from './helpers/inMemoryEventStore.js'; export * from './helpers/oauth.js'; export * from './helpers/tasks.js'; diff --git a/test/integration/test/taskResumability.test.ts b/test/integration/test/taskResumability.test.ts index 93b2d7e19..1e4d8a0fd 100644 --- a/test/integration/test/taskResumability.test.ts +++ b/test/integration/test/taskResumability.test.ts @@ -8,8 +8,43 @@ import { McpServer, StreamableHTTPServerTransport } from '@modelcontextprotocol/server'; +import type { EventStore, JSONRPCMessage } from '@modelcontextprotocol/server'; import type { ZodMatrixEntry } from '@modelcontextprotocol/test-helpers'; -import { InMemoryEventStore, listenOnRandomPort, zodTestMatrix } from '@modelcontextprotocol/test-helpers'; +import { listenOnRandomPort, zodTestMatrix } from '@modelcontextprotocol/test-helpers'; + +/** + * Simple in-memory EventStore for testing resumability. + */ +class InMemoryEventStore implements EventStore { + private events = new Map(); + + async storeEvent(streamId: string, message: JSONRPCMessage): Promise { + const eventId = `${streamId}_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`; + this.events.set(eventId, { streamId, message }); + return eventId; + } + + async replayEventsAfter( + lastEventId: string, + { send }: { send: (eventId: string, message: JSONRPCMessage) => Promise } + ): Promise { + if (!lastEventId || !this.events.has(lastEventId)) return ''; + const streamId = lastEventId.split('_')[0] ?? ''; + if (!streamId) return ''; + + let found = false; + const sorted = [...this.events.entries()].sort((a, b) => a[0].localeCompare(b[0])); + for (const [eventId, { streamId: sid, message }] of sorted) { + if (sid !== streamId) continue; + if (eventId === lastEventId) { + found = true; + continue; + } + if (found) await send(eventId, message); + } + return streamId; + } +} describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => { const { z } = entry;