Skip to content

Commit 6672e8e

Browse files
committed
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.
1 parent d087a5e commit 6672e8e

6 files changed

Lines changed: 170 additions & 4 deletions

File tree

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,13 @@ Tagged releases are published to npm from GitHub Actions when a **GitHub Release
88

99
## [Unreleased]
1010

11+
### Added
12+
13+
- **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)
14+
1115
### Changed
1216

17+
- **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)
1318
- **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).
1419

1520
## [0.4.0] - 2026-06-24

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
[![License: BSL-1.0](https://img.shields.io/badge/License-BSL--1.0-blue.svg)](https://opensource.org/licenses/BSL-1.0)
66
[![CI](https://github.com/cppalliance/pinecone-read-only-mcp-typescript/workflows/CI/badge.svg)](https://github.com/cppalliance/pinecone-read-only-mcp-typescript/actions)
77

8-
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.
8+
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.
99

1010
Current version: 0.4.0 (npm `latest` after publish). Pin `@0.4.0` in install and MCP config for reproducible upgrades.
1111

docs/PACKAGE_SPLIT_EVAL.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ Both packages would share the same four production dependencies from the root `p
182182

183183
| Dependency | Current range | Alignment note |
184184
| ---------- | ------------- | -------------- |
185-
| `@modelcontextprotocol/sdk` | `^1.25.3` | MCP protocol surface; must stay aligned across core and Alliance |
185+
| `@modelcontextprotocol/sdk` | `^1.29.0` | MCP protocol surface; must stay aligned across core and Alliance |
186186
| `@pinecone-database/pinecone` | `^7.1.0` | Client API used by `PineconeClient`; core owns the wrapper |
187187
| `dotenv` | `^17.2.3` | Env loading in config resolvers |
188188
| `zod` | `^4.3.6` | Tool input schemas and response validation |

package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@
7373
"prepack": "npm run build"
7474
},
7575
"dependencies": {
76-
"@modelcontextprotocol/sdk": "^1.25.3",
76+
"@modelcontextprotocol/sdk": "^1.29.0",
7777
"@pinecone-database/pinecone": "^8.0.0",
7878
"dotenv": "^17.2.3",
7979
"zod": "^4.3.6"
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
import { afterEach, describe, expect, it } from 'vitest';
2+
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
3+
import { InMemoryTransport } from '@modelcontextprotocol/sdk/inMemory.js';
4+
import { LATEST_PROTOCOL_VERSION } from '@modelcontextprotocol/sdk/types.js';
5+
import { SERVER_NAME, SERVER_VERSION } from '../constants.js';
6+
import { setupCoreServer, teardownServer, type ServerHandle } from '../core/setup.js';
7+
import { setupAllianceServer } from '../alliance/setup.js';
8+
import { resolveAllianceConfig } from '../alliance/config.js';
9+
import { createIsolatedContext } from '../core/server/server-context.js';
10+
import {
11+
createTestServerContext,
12+
isolateFromDefaultContext,
13+
makeMockPineconeClient,
14+
} from '../core/server/tools/test-helpers.js';
15+
16+
/**
17+
* End-to-end MCP verification harness (#202).
18+
*
19+
* Drives the real `McpServer` over an in-memory transport with the SDK's own
20+
* `Client`, so a future SDK bump (the 2026-07-28 RC protocol revision, once it
21+
* ships) is verified in one place: the initialize handshake and protocol
22+
* negotiation, the full registered tool surface, and a round-trip tool call.
23+
* The protocol assertions key off the SDK's `LATEST_PROTOCOL_VERSION`, so when
24+
* the RC SDK is pinned they re-check the server against the new revision with no
25+
* edits here. If the RC is not published in time, this still guards the current
26+
* pinned SDK.
27+
*/
28+
29+
const CORE_TOOLS = [
30+
'list_namespaces',
31+
'namespace_router',
32+
'count',
33+
'query',
34+
'keyword_search',
35+
'query_documents',
36+
'generate_urls',
37+
'guided_query',
38+
].sort();
39+
40+
/** A fresh, isolated core server (own context + mock client) so several can coexist. */
41+
async function freshCoreServer(namespaces: string[] = ['ns']): Promise<ServerHandle> {
42+
const ctx = createTestServerContext({ client: makeMockPineconeClient(namespaces) as never });
43+
return setupCoreServer({ context: ctx });
44+
}
45+
46+
/** Link the SDK Client to a live server over paired in-memory transports. */
47+
async function connectClient(server: ServerHandle): Promise<Client> {
48+
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
49+
await server.connect(serverTransport);
50+
const client = new Client({ name: 'rc-readiness-harness', version: '0.0.0' });
51+
await client.connect(clientTransport);
52+
return client;
53+
}
54+
55+
/** Send a raw JSON-RPC initialize and return the negotiated result. */
56+
async function rawInitialize(
57+
server: ServerHandle,
58+
requestedProtocolVersion: string
59+
): Promise<{ protocolVersion: string; serverInfo: { name: string; version: string } }> {
60+
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
61+
await server.connect(serverTransport);
62+
const result = await new Promise<{
63+
protocolVersion: string;
64+
serverInfo: { name: string; version: string };
65+
}>((resolve, reject) => {
66+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
67+
clientTransport.onmessage = (message: any) => {
68+
if (message?.id !== 1) return;
69+
if (message.error) reject(new Error(message.error.message));
70+
else resolve(message.result);
71+
};
72+
void clientTransport
73+
.start()
74+
.then(() =>
75+
clientTransport.send({
76+
jsonrpc: '2.0',
77+
id: 1,
78+
method: 'initialize',
79+
params: {
80+
protocolVersion: requestedProtocolVersion,
81+
capabilities: {},
82+
clientInfo: { name: 'raw-harness', version: '0.0.0' },
83+
},
84+
})
85+
)
86+
.catch(reject);
87+
});
88+
return result;
89+
}
90+
91+
describe('MCP RC-readiness harness (#202)', () => {
92+
afterEach(() => {
93+
teardownServer();
94+
isolateFromDefaultContext();
95+
});
96+
97+
it('initializes over the transport and round-trips server metadata', async () => {
98+
const server = await freshCoreServer();
99+
const client = await connectClient(server);
100+
101+
// A resolved connect proves the initialize handshake and protocol
102+
// negotiation succeeded: the SDK Client throws if the server answers with a
103+
// protocolVersion outside SUPPORTED_PROTOCOL_VERSIONS.
104+
expect(client.getServerVersion()).toEqual({ name: SERVER_NAME, version: SERVER_VERSION });
105+
expect(client.getServerCapabilities()?.tools).toBeDefined();
106+
expect(client.getInstructions()).toBeTruthy();
107+
108+
await client.close();
109+
});
110+
111+
it('exposes the full core tool surface through tools/list', async () => {
112+
const server = await freshCoreServer();
113+
const client = await connectClient(server);
114+
115+
const names = (await client.listTools()).tools.map((t) => t.name).sort();
116+
expect(names).toEqual(CORE_TOOLS);
117+
118+
await client.close();
119+
});
120+
121+
it('registers the Alliance suggest_query_params tool on top of the core surface', async () => {
122+
const ctx = createIsolatedContext(
123+
resolveAllianceConfig({ apiKey: 'sk-test', indexName: 'test-index' }),
124+
{ client: makeMockPineconeClient(['ns']) as never }
125+
);
126+
const server = await setupAllianceServer({ context: ctx });
127+
const client = await connectClient(server);
128+
129+
const names = (await client.listTools()).tools.map((t) => t.name);
130+
for (const core of CORE_TOOLS) expect(names).toContain(core);
131+
expect(names).toContain('suggest_query_params');
132+
133+
await client.close();
134+
});
135+
136+
it('round-trips a tool call over the transport', async () => {
137+
const server = await freshCoreServer(['alpha', 'beta']);
138+
const client = await connectClient(server);
139+
140+
const res = await client.callTool({ name: 'list_namespaces', arguments: {} });
141+
expect(res.isError ?? false).toBe(false);
142+
// The seeded namespaces must round-trip through the handler and transport,
143+
// not just any array, so a regression returning empty/garbage content fails.
144+
const text = (res.content as Array<{ type: string; text: string }>)[0]?.text ?? '';
145+
expect(text).toContain('alpha');
146+
expect(text).toContain('beta');
147+
148+
await client.close();
149+
});
150+
151+
it('negotiates the SDK latest protocol version and falls back for an unknown request', async () => {
152+
// A server connects to one transport for its lifetime, so use a fresh one per probe.
153+
const negotiated = await rawInitialize(await freshCoreServer(), LATEST_PROTOCOL_VERSION);
154+
expect(negotiated.protocolVersion).toBe(LATEST_PROTOCOL_VERSION);
155+
expect(negotiated.serverInfo).toMatchObject({ name: SERVER_NAME, version: SERVER_VERSION });
156+
157+
// An unsupported request must not error; the server falls back to its latest.
158+
const fallback = await rawInitialize(await freshCoreServer(), 'not-a-real-protocol-version');
159+
expect(fallback.protocolVersion).toBe(LATEST_PROTOCOL_VERSION);
160+
});
161+
});

0 commit comments

Comments
 (0)