From 6672e8e06d06c87605462334d4f53322c9d68b78 Mon Sep 17 00:00:00 2001 From: timon0305 Date: Thu, 9 Jul 2026 04:13:55 +0200 Subject: [PATCH 1/3] test: MCP verification harness + bump SDK floor to ^1.29.0 (#202) Bump the declared @modelcontextprotocol/sdk floor from ^1.25.3 to ^1.29.0 to match the version the lockfile already resolved, readying the server for the upcoming MCP RC protocol revision. McpServer construction, tool registration, and the stdio transport use current SDK APIs and are unchanged. Add an end-to-end verification harness that drives the real server over an in-memory transport with the SDK client: initialize + protocol negotiation, the full registered tool surface (core + Alliance), and a round-trip tool call. The protocol assertions key off the SDK's LATEST_PROTOCOL_VERSION, so pinning the RC SDK re-runs the check with no test edits. Sync the SDK version references in README, PACKAGE_SPLIT_EVAL, and CHANGELOG. --- CHANGELOG.md | 5 + README.md | 2 +- docs/PACKAGE_SPLIT_EVAL.md | 2 +- package-lock.json | 2 +- package.json | 2 +- src/__tests__/mcp-rc-readiness.test.ts | 161 +++++++++++++++++++++++++ 6 files changed, 170 insertions(+), 4 deletions(-) create mode 100644 src/__tests__/mcp-rc-readiness.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index bb03d4d..da16a3e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,8 +8,13 @@ Tagged releases are published to npm from GitHub Actions when a **GitHub Release ## [Unreleased] +### Added + +- **Tests:** MCP verification harness ([src/__tests__/mcp-rc-readiness.test.ts](src/__tests__/mcp-rc-readiness.test.ts)) that drives the real server over an in-memory transport with the SDK client (initialize and protocol negotiation, the full registered tool surface, and a round-trip tool call), so a future SDK or MCP protocol bump is verified in one place. Protocol assertions key off the SDK's `LATEST_PROTOCOL_VERSION`, so pinning the RC SDK re-runs the check with no test edits. (#202) + ### Changed +- **Dependencies:** Bumped the declared `@modelcontextprotocol/sdk` floor from `^1.25.3` to `^1.29.0` to match the resolved version and ready the server for the upcoming MCP RC protocol revision. `McpServer` construction, tool registration, and the stdio transport are unchanged and re-verified by the harness above. (#202) - **Instructions:** Trimmed operator/install/deploy content (env-var setup, misconfiguration note, Alliance CLI index/rerank defaults, stderr logging config) from `CORE_SERVER_INSTRUCTIONS` and `ALLIANCE_INSTRUCTIONS_APPENDIX` — reduces per-session token cost; no behavior change. Replaced colliding Alliance appendix steps 4–5 with unnumbered "Manual Alliance flow" bullets (includes `PINECONE_DISABLE_SUGGEST_FLOW=true` escape clause). Full detail remains in [docs/CONFIGURATION.md](docs/CONFIGURATION.md). ## [0.4.0] - 2026-06-24 diff --git a/README.md b/README.md index bfd9ee3..85c8138 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ [![License: BSL-1.0](https://img.shields.io/badge/License-BSL--1.0-blue.svg)](https://opensource.org/licenses/BSL-1.0) [![CI](https://github.com/cppalliance/pinecone-read-only-mcp-typescript/workflows/CI/badge.svg)](https://github.com/cppalliance/pinecone-read-only-mcp-typescript/actions) -A [Model Context Protocol](https://modelcontextprotocol.io) (MCP) server that implements the MCP specification via `@modelcontextprotocol/sdk` v1.25+ and provides semantic search over Pinecone vector databases using hybrid search (dense + sparse) with reranking. +A [Model Context Protocol](https://modelcontextprotocol.io) (MCP) server that implements the MCP specification via `@modelcontextprotocol/sdk` v1.29+ and provides semantic search over Pinecone vector databases using hybrid search (dense + sparse) with reranking. Current version: 0.4.0 (npm `latest` after publish). Pin `@0.4.0` in install and MCP config for reproducible upgrades. diff --git a/docs/PACKAGE_SPLIT_EVAL.md b/docs/PACKAGE_SPLIT_EVAL.md index 600e59b..2d103e1 100644 --- a/docs/PACKAGE_SPLIT_EVAL.md +++ b/docs/PACKAGE_SPLIT_EVAL.md @@ -182,7 +182,7 @@ Both packages would share the same four production dependencies from the root `p | Dependency | Current range | Alignment note | | ---------- | ------------- | -------------- | -| `@modelcontextprotocol/sdk` | `^1.25.3` | MCP protocol surface; must stay aligned across core and Alliance | +| `@modelcontextprotocol/sdk` | `^1.29.0` | MCP protocol surface; must stay aligned across core and Alliance | | `@pinecone-database/pinecone` | `^7.1.0` | Client API used by `PineconeClient`; core owns the wrapper | | `dotenv` | `^17.2.3` | Env loading in config resolvers | | `zod` | `^4.3.6` | Tool input schemas and response validation | diff --git a/package-lock.json b/package-lock.json index 0b55e97..ec01ea6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "0.4.0", "license": "BSL-1.0", "dependencies": { - "@modelcontextprotocol/sdk": "^1.25.3", + "@modelcontextprotocol/sdk": "^1.29.0", "@pinecone-database/pinecone": "^8.0.0", "dotenv": "^17.2.3", "zod": "^4.3.6" diff --git a/package.json b/package.json index bb7e72b..09dfb75 100644 --- a/package.json +++ b/package.json @@ -73,7 +73,7 @@ "prepack": "npm run build" }, "dependencies": { - "@modelcontextprotocol/sdk": "^1.25.3", + "@modelcontextprotocol/sdk": "^1.29.0", "@pinecone-database/pinecone": "^8.0.0", "dotenv": "^17.2.3", "zod": "^4.3.6" diff --git a/src/__tests__/mcp-rc-readiness.test.ts b/src/__tests__/mcp-rc-readiness.test.ts new file mode 100644 index 0000000..af8a863 --- /dev/null +++ b/src/__tests__/mcp-rc-readiness.test.ts @@ -0,0 +1,161 @@ +import { afterEach, describe, expect, it } from 'vitest'; +import { Client } from '@modelcontextprotocol/sdk/client/index.js'; +import { InMemoryTransport } from '@modelcontextprotocol/sdk/inMemory.js'; +import { LATEST_PROTOCOL_VERSION } from '@modelcontextprotocol/sdk/types.js'; +import { SERVER_NAME, SERVER_VERSION } from '../constants.js'; +import { setupCoreServer, teardownServer, type ServerHandle } from '../core/setup.js'; +import { setupAllianceServer } from '../alliance/setup.js'; +import { resolveAllianceConfig } from '../alliance/config.js'; +import { createIsolatedContext } from '../core/server/server-context.js'; +import { + createTestServerContext, + isolateFromDefaultContext, + makeMockPineconeClient, +} from '../core/server/tools/test-helpers.js'; + +/** + * End-to-end MCP verification harness (#202). + * + * Drives the real `McpServer` over an in-memory transport with the SDK's own + * `Client`, so a future SDK bump (the 2026-07-28 RC protocol revision, once it + * ships) is verified in one place: the initialize handshake and protocol + * negotiation, the full registered tool surface, and a round-trip tool call. + * The protocol assertions key off the SDK's `LATEST_PROTOCOL_VERSION`, so when + * the RC SDK is pinned they re-check the server against the new revision with no + * edits here. If the RC is not published in time, this still guards the current + * pinned SDK. + */ + +const CORE_TOOLS = [ + 'list_namespaces', + 'namespace_router', + 'count', + 'query', + 'keyword_search', + 'query_documents', + 'generate_urls', + 'guided_query', +].sort(); + +/** A fresh, isolated core server (own context + mock client) so several can coexist. */ +async function freshCoreServer(namespaces: string[] = ['ns']): Promise { + const ctx = createTestServerContext({ client: makeMockPineconeClient(namespaces) as never }); + return setupCoreServer({ context: ctx }); +} + +/** Link the SDK Client to a live server over paired in-memory transports. */ +async function connectClient(server: ServerHandle): Promise { + const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair(); + await server.connect(serverTransport); + const client = new Client({ name: 'rc-readiness-harness', version: '0.0.0' }); + await client.connect(clientTransport); + return client; +} + +/** Send a raw JSON-RPC initialize and return the negotiated result. */ +async function rawInitialize( + server: ServerHandle, + requestedProtocolVersion: string +): Promise<{ protocolVersion: string; serverInfo: { name: string; version: string } }> { + const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair(); + await server.connect(serverTransport); + const result = await new Promise<{ + protocolVersion: string; + serverInfo: { name: string; version: string }; + }>((resolve, reject) => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + clientTransport.onmessage = (message: any) => { + if (message?.id !== 1) return; + if (message.error) reject(new Error(message.error.message)); + else resolve(message.result); + }; + void clientTransport + .start() + .then(() => + clientTransport.send({ + jsonrpc: '2.0', + id: 1, + method: 'initialize', + params: { + protocolVersion: requestedProtocolVersion, + capabilities: {}, + clientInfo: { name: 'raw-harness', version: '0.0.0' }, + }, + }) + ) + .catch(reject); + }); + return result; +} + +describe('MCP RC-readiness harness (#202)', () => { + afterEach(() => { + teardownServer(); + isolateFromDefaultContext(); + }); + + it('initializes over the transport and round-trips server metadata', async () => { + const server = await freshCoreServer(); + const client = await connectClient(server); + + // A resolved connect proves the initialize handshake and protocol + // negotiation succeeded: the SDK Client throws if the server answers with a + // protocolVersion outside SUPPORTED_PROTOCOL_VERSIONS. + expect(client.getServerVersion()).toEqual({ name: SERVER_NAME, version: SERVER_VERSION }); + expect(client.getServerCapabilities()?.tools).toBeDefined(); + expect(client.getInstructions()).toBeTruthy(); + + await client.close(); + }); + + it('exposes the full core tool surface through tools/list', async () => { + const server = await freshCoreServer(); + const client = await connectClient(server); + + const names = (await client.listTools()).tools.map((t) => t.name).sort(); + expect(names).toEqual(CORE_TOOLS); + + await client.close(); + }); + + it('registers the Alliance suggest_query_params tool on top of the core surface', async () => { + const ctx = createIsolatedContext( + resolveAllianceConfig({ apiKey: 'sk-test', indexName: 'test-index' }), + { client: makeMockPineconeClient(['ns']) as never } + ); + const server = await setupAllianceServer({ context: ctx }); + const client = await connectClient(server); + + const names = (await client.listTools()).tools.map((t) => t.name); + for (const core of CORE_TOOLS) expect(names).toContain(core); + expect(names).toContain('suggest_query_params'); + + await client.close(); + }); + + it('round-trips a tool call over the transport', async () => { + const server = await freshCoreServer(['alpha', 'beta']); + const client = await connectClient(server); + + const res = await client.callTool({ name: 'list_namespaces', arguments: {} }); + expect(res.isError ?? false).toBe(false); + // The seeded namespaces must round-trip through the handler and transport, + // not just any array, so a regression returning empty/garbage content fails. + const text = (res.content as Array<{ type: string; text: string }>)[0]?.text ?? ''; + expect(text).toContain('alpha'); + expect(text).toContain('beta'); + + await client.close(); + }); + + it('negotiates the SDK latest protocol version and falls back for an unknown request', async () => { + // A server connects to one transport for its lifetime, so use a fresh one per probe. + const negotiated = await rawInitialize(await freshCoreServer(), LATEST_PROTOCOL_VERSION); + expect(negotiated.protocolVersion).toBe(LATEST_PROTOCOL_VERSION); + expect(negotiated.serverInfo).toMatchObject({ name: SERVER_NAME, version: SERVER_VERSION }); + + // An unsupported request must not error; the server falls back to its latest. + const fallback = await rawInitialize(await freshCoreServer(), 'not-a-real-protocol-version'); + expect(fallback.protocolVersion).toBe(LATEST_PROTOCOL_VERSION); + }); +}); From 3f2d497f1b0feb41e82a0287d9de230da069dcf5 Mon Sep 17 00:00:00 2001 From: timon0305 Date: Fri, 10 Jul 2026 16:50:36 +0200 Subject: [PATCH 2/3] Address #205 review: correct stdio CHANGELOG claim + harden raw probe - CHANGELOG: the harness verifies McpServer construction and tool registration via server.connect() over the in-memory transport; the production StdioServerTransport wiring is unchanged and reviewed, not exercised (drop the overclaim that stdio is re-verified). - Raw initialize probe closes its client transport in a finally, and its onmessage handler is typed with the SDK JSONRPCMessage instead of any. --- CHANGELOG.md | 2 +- src/__tests__/mcp-rc-readiness.test.ts | 63 ++++++++++++++------------ 2 files changed, 36 insertions(+), 29 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cb5613a..4ec5c13 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,7 +18,7 @@ Tagged releases are published to npm from GitHub Actions when a **GitHub Release - **Breaking (`list_sources`):** response shape `sources: string[]` → `sources: { name, description? }[]`. Migration: [MIGRATION.md § Unreleased list_sources](docs/MIGRATION.md#unreleased-list_sources-response-shape). - **Breaking (library API):** `PineconeClient.listNamespacesWithMetadata()` now returns `{ namespaces, warnings }` instead of a bare array. Migration: [MIGRATION.md § Unreleased PineconeClient.listNamespacesWithMetadata](docs/MIGRATION.md#unreleased-pineconeclientlistnamespaceswithmetadata-return-shape). -- **Dependencies:** Bumped the declared `@modelcontextprotocol/sdk` floor from `^1.25.3` to `^1.29.0` to match the resolved version and ready the server for the upcoming MCP RC protocol revision. `McpServer` construction, tool registration, and the stdio transport are unchanged and re-verified by the harness above. (#202) +- **Dependencies:** Bumped the declared `@modelcontextprotocol/sdk` floor from `^1.25.3` to `^1.29.0` to match the resolved version and ready the server for the upcoming MCP RC protocol revision. `McpServer` construction and tool registration are re-verified by the harness above through `server.connect()` over the SDK in-memory transport; the production `StdioServerTransport` wiring in `src/index.ts` is unchanged and was reviewed, not exercised, by the harness. (#202) - **Instructions:** Trimmed operator/install/deploy content (env-var setup, misconfiguration note, Alliance CLI index/rerank defaults, stderr logging config) from `CORE_SERVER_INSTRUCTIONS` and `ALLIANCE_INSTRUCTIONS_APPENDIX` — reduces per-session token cost; no behavior change. Replaced colliding Alliance appendix steps 4–5 with unnumbered "Manual Alliance flow" bullets (includes `PINECONE_DISABLE_SUGGEST_FLOW=true` escape clause). Full detail remains in [docs/CONFIGURATION.md](docs/CONFIGURATION.md). ## [0.4.0] - 2026-06-24 diff --git a/src/__tests__/mcp-rc-readiness.test.ts b/src/__tests__/mcp-rc-readiness.test.ts index af8a863..3f8e12d 100644 --- a/src/__tests__/mcp-rc-readiness.test.ts +++ b/src/__tests__/mcp-rc-readiness.test.ts @@ -1,7 +1,7 @@ import { afterEach, describe, expect, it } from 'vitest'; import { Client } from '@modelcontextprotocol/sdk/client/index.js'; import { InMemoryTransport } from '@modelcontextprotocol/sdk/inMemory.js'; -import { LATEST_PROTOCOL_VERSION } from '@modelcontextprotocol/sdk/types.js'; +import { LATEST_PROTOCOL_VERSION, type JSONRPCMessage } from '@modelcontextprotocol/sdk/types.js'; import { SERVER_NAME, SERVER_VERSION } from '../constants.js'; import { setupCoreServer, teardownServer, type ServerHandle } from '../core/setup.js'; import { setupAllianceServer } from '../alliance/setup.js'; @@ -59,33 +59,40 @@ async function rawInitialize( ): Promise<{ protocolVersion: string; serverInfo: { name: string; version: string } }> { const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair(); await server.connect(serverTransport); - const result = await new Promise<{ - protocolVersion: string; - serverInfo: { name: string; version: string }; - }>((resolve, reject) => { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - clientTransport.onmessage = (message: any) => { - if (message?.id !== 1) return; - if (message.error) reject(new Error(message.error.message)); - else resolve(message.result); - }; - void clientTransport - .start() - .then(() => - clientTransport.send({ - jsonrpc: '2.0', - id: 1, - method: 'initialize', - params: { - protocolVersion: requestedProtocolVersion, - capabilities: {}, - clientInfo: { name: 'raw-harness', version: '0.0.0' }, - }, - }) - ) - .catch(reject); - }); - return result; + try { + return await new Promise<{ + protocolVersion: string; + serverInfo: { name: string; version: string }; + }>((resolve, reject) => { + clientTransport.onmessage = (message: JSONRPCMessage) => { + if (!('id' in message) || message.id !== 1) return; + if ('error' in message) { + reject(new Error(message.error.message)); + } else if ('result' in message) { + resolve( + message.result as { protocolVersion: string; serverInfo: { name: string; version: string } } + ); + } + }; + void clientTransport + .start() + .then(() => + clientTransport.send({ + jsonrpc: '2.0', + id: 1, + method: 'initialize', + params: { + protocolVersion: requestedProtocolVersion, + capabilities: {}, + clientInfo: { name: 'raw-harness', version: '0.0.0' }, + }, + }) + ) + .catch(reject); + }); + } finally { + await clientTransport.close(); + } } describe('MCP RC-readiness harness (#202)', () => { From 369147ca9df6b49d67079fba7dcd3f3b5e1a9851 Mon Sep 17 00:00:00 2001 From: timon0305 Date: Fri, 10 Jul 2026 16:55:48 +0200 Subject: [PATCH 3/3] Format mcp-rc-readiness.test.ts with Prettier The raw-probe rework tripped the format:check gate; apply prettier --write. --- src/__tests__/mcp-rc-readiness.test.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/__tests__/mcp-rc-readiness.test.ts b/src/__tests__/mcp-rc-readiness.test.ts index 3f8e12d..d08fe89 100644 --- a/src/__tests__/mcp-rc-readiness.test.ts +++ b/src/__tests__/mcp-rc-readiness.test.ts @@ -70,7 +70,10 @@ async function rawInitialize( reject(new Error(message.error.message)); } else if ('result' in message) { resolve( - message.result as { protocolVersion: string; serverInfo: { name: string; version: string } } + message.result as { + protocolVersion: string; + serverInfo: { name: string; version: string }; + } ); } };